Pass data through a common client
#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 );
libc
Use the -l c option to qcc to link against this library. This library is usually included automatically.
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.
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.
The client now sends this message to pid with the fully qualified pathname.
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.
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:
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:
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.
These calls don't block.
The only difference between these functions is the way they indicate errors:
/* * 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; }
Safety: | |
---|---|
Cancellation point | No |
Interrupt handler | No |
Signal handler | Yes |
Thread | Yes |
chroot(), MsgReceive(), MsgReceivev(), open(), rand()