MsgKeyData(), MsgKeyData_r()

Updated: April 19, 2023

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:
  • _NTO_KEYDATA_CALCULATE — calculate a new key.
  • _NTO_KEYDATA_CALCULATE_REUSE — reuse a key that was previously generated by the kernel.
  • _NTO_KEYDATA_PATHSIGN* — used internally.
  • _NTO_KEYDATA_VERIFY — verify the key.
key
A private value for key. The value that you use depends on your system's security requirements. If security is important, you should use a value read from /dev/random (see the entry for random in the Utilities Reference); otherwise you might choose to use a statistically random number returned by rand().

If op is _NTO_KEYDATA_CALCULATE, you can pass 0 for key to request that the kernel generate a pseudo-random number to use for the key.

key2
A pointer to a key. What the function stores in this location depends on the op argument:
  • _NTO_KEYDATA_CALCULATE — the new key.
  • _NTO_KEYDATA_CALCULATE_REUSE — the previously generated key.
  • _NTO_KEYDATA_VERIFY — zero if no tampering has occurred.
msg
A pointer to a portion of the reply data to be keyed.
parts
The number of parts in msg. This number must not exceed 524288, or the function will fail with EINVAL.

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.

Note: In order to successfully call these functions, your process must have the PROCMGR_AID_KEYDATA ability enabled. For more information, see procmgr_ability(). In QNX Neutrino 7.1 or later, you don't need this ability if the operation is _NTO_KEYDATA_VERIFY.

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.

Figure 1. 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.

Figure 2. MsgReplyv(), process manager to client.

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

Figure 3. 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 the QNX Neutrino RTOS, 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 Neutrino 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 the key.
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, this function returns -1 and sets errno. Any other value returned indicates success.
MsgKeyData_r()
If successful, this function returns EOK. This function does NOT set errno, even on success. If an error occurs, it may return any value from the Errors section.

Errors:

EINVAL
The number of IOV parts exceeds 524288, or the op argument is invalid.
EPERM
The calling process doesn't have the required permission; see procmgr_ability().
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