MsgKeyData(), MsgKeyData_r()

Pass data through a common client

Synopsis:

#include <sys/neutrino.h>

int MsgKeyData( int rcvid,
                int op,
                uint32_t key,
                uint32_t * key2,
                const iov_t * msg,
                int parts );

int MsgKeyData_r( int rcvid,
                  int op,
                  uint32_t key,
                  uint32_t * key2,
                  const iov_t * msg,
                  int parts );

Arguments:

rcvid
The return value from MsgReceive*().
op
The operation to perform; one of:
key
A private value for key (this can be a value returned by the rand() function).
key2
A pointer to a key. What the function stores in this location depends on the op argument:
msg
A pointer to a portion of the reply data to be keyed.
parts
The number of parts in msg.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

The MsgKeyData() and MsgKeyData_r() kernel calls allow two privileged processes to pass data through a common client while verifying that the client hasn't modified the data. This is best explained by an example.

These functions are identical except in the way they indicate errors. See the Returns section for details.

A program calls open() with a filename. The open() function sends a message to the Process Manager, which is responsible for pathname management.


MsgSendv, client to process manager


MsgSendv(), client to process manager.

The Process Manager resolves the pathname, resulting in a fully qualified network path and the process ID to send the open() request to. This information is replied to the client.


MsgReplyv, process manager to client


MsgReplyv(), process manager to client.

The client now sends this message to pid with the fully qualified pathname.


MsgSendv, client to filesystem manager


MsgSendv(), client to filesystem manager

Note that the client can change the pathname before it sends it to the Filesystem Manager. In fact, it could skip the call to the Process Manager and manufacture any pathname it desired. The Filesystem Manager always performs permission checking. Therefore, changing or manufacturing pathnames isn't normally something to be concerned about, except in one case: chroot() lets you specify a prefix that must be applied to all pathnames.

In the above example, the client may have had a chroot() of /net/node2/home/dan. This should limit the process from accessing files outside of /net/node2/home/dan. For example:

User path Mapped to chroot() path
/bin/ls /net/node2/home/dan/bin/ls
/ /net/node2/home/dan

The process has had its root set to a subdirectory, limiting the files it can access. For this to work, it's necessary to prevent the client from changing or manufacturing its own pathnames.


Note: In QNX Neutrino, only the Process Manager handles a user chroot(). Unlike a monolithic kernel where the filesystem shares the same address space as the kernel and the chroot() information, QNX I/O managers reside in separate address spaces and might not even reside on the same machine.

The solution to this problem is the MsgKeyData() call. When the Process Manager receives the open() message, it generates the reply data. Before replying, it calls MsgKeyData(), with these arguments:

rcvid
The return value from MsgReceive*().
op
_NTO_KEYDATA_CALCULATE
key
A private value for key (this can be a value returned by the rand() function).
key2
A pointer to a new key that should be returned to the client in a unkeyed area of the message.
msg
A pointer to a portion of the reply data to be keyed.
parts
The number of parts in msg.

The client now sends the message to the File Manager. On receipt of the message, the File Manager calls MsgKeyData() with the same arguments as above, except for:

op
_NTO_KEYDATA_VERIFY
key
The key that's provided in the message.

MsgKeyData() sets the key pointed to by key2 to zero if no tampering has occurred.

Note that there are actually two keys involved. A public key that's returned to the client and a private key that the Process Manager generated. The algorithm uses both keys and the data for verification.

Blocking states

These calls don't block.

Returns:

The only difference between these functions is the way they indicate errors:

MsgKeyData()
If an error occurs, -1 is returned and errno is set. Any other value returned indicates success.
MsgKeyData_r()
EOK is returned on success. This function does NOT set errno. If an error occurs, any value in the Errors section may be returned.

Errors:

ESRCH
The thread indicated by rcvid doesn't exist.
EFAULT
A fault occurred when the kernel tried to access the buffers provided.

Examples:

/*
 * This program demonstrates the use of MsgKeyData() as a way
 * of a client handing off data from a source server to a 
 * destination server such that if the client tampers with 
 * the data, the destination server will know about it.
 */

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/neutrino.h>

typedef struct {
    int     public_key;
    char    text[10];
} IPC_t;

int chid_src, chid_dst;

void* server_src_thread(void* parm);
void* server_dst_thread(void* parm);

main()
{
    pthread_t   tid[2];
    IPC_t       msg;
    int         coid;
    int         status;

    pthread_create(&tid[0], NULL, server_src_thread, NULL);
    pthread_create(&tid[1], NULL, server_dst_thread, NULL);
    sleep(3); 
    /* give time for channels to be created, sloppy but simple */

    /*
     * Send to server_src_thread for some data.  
     * The data will include some text and a public 
     * key for that text.
     */

    coid = ConnectAttach(0, 0, chid_src, 0, 0);
    MsgSend(coid, NULL, 0, &msg, sizeof(msg));
    ConnectDetach(coid);

    /*
     * Now send to server_dst_thread with the reply from 
     * server_src_thread. We didn't modify the 'text' so it
     * should reply success.  Note that we're including the 
     * public key.
     */

    coid = ConnectAttach(0, 0, chid_dst, 0, 0);
    status = MsgSend(coid, &msg, sizeof(msg), &msg, sizeof(msg));
    printf("Sent unmodified text to server_dst_thread.  
            Replied with %s\n", status == EOK ? "EOK" : "EINVAL" );

    /*
     * Now tamper with the original 'text' (which we aren't 
     * supposed to do) and send to server_dst_thread again 
     * but with the modified 'text' and the public key.  
     * Since we tampered with the 'text', server_dst_thread 
     * should reply failure. 
     */

    strcpy(msg.text, "NEWDATA");
    status = MsgSend(coid, &msg, sizeof(msg), &msg, sizeof(msg));
    printf("Sent modified text to server_dst_thread.  
            Replied with %s\n", status == EOK ? "EOK" : "EINVAL" );

    return 0;
}

void* server_src_thread(void* parm)
{
    int             rcvid;
    int             private_key;  /* the kernel keeps this */
    iov_t           keyed_area_iov;
    IPC_t           msg;
    struct timespec t;

    chid_src = ChannelCreate(0);
    while (1) {
        rcvid = MsgReceive(chid_src, &msg, sizeof(msg), NULL);

        /*
         * Give MsgKeyData() the private key and it will 
         * calculate a public key for the 'text' member of 
         * the message.  The kernel will keep the private key 
         * and we reply with the public key. 
         * Note that we use the number of nanoseconds since the 
         * last second as a way of getting a 32-bit pseudo  
         * random number for the private key.
         */

        clock_gettime(CLOCK_REALTIME, &t);
        private_key = t.tv_nsec; /* nanoseconds since last second */
        strcpy(msg.text, "OKDATA");
        SETIOV(&keyed_area_iov, &msg.text, sizeof(msg.text));
        MsgKeyData(rcvid, _NTO_KEYDATA_CALCULATE, private_key, 
                   &msg.public_key, &keyed_area_iov, 1);

        MsgReply(rcvid, 0, &msg, sizeof(msg));
    }
    return NULL;
}

void* server_dst_thread(void* parm)
{
    int     rcvid, tampered, status;
    iov_t   keyed_area_iov;
    IPC_t   msg;

    chid_dst = ChannelCreate(0);
    while (1) {
        rcvid = MsgReceive(chid_dst, &msg, sizeof(msg), NULL);

        /*
         * Use the public key to see if the data 
         * has been tampered with.
         */

        SETIOV(&keyed_area_iov, &msg.text, sizeof(msg.text));
        MsgKeyData(rcvid, _NTO_KEYDATA_VERIFY, msg.public_key, 
                   &tampered, &keyed_area_iov, 1);

        if (tampered)
            status = EINVAL; /* reply: 'text' was modified */
        else
            status = EOK;    /* reply: 'text' was okay */
        MsgReply(rcvid, status, &msg, sizeof(msg));
    }
    return NULL;
}

Classification:

QNX Neutrino

Safety:
Cancellation point No
Interrupt handler No
Signal handler Yes
Thread Yes

See also:

chroot(), MsgReceive(), MsgReceivev(), open(), rand()