Showing 2 posts
One of the problems of learning new stuff based on trial-and-error iterations is that it is very easy to miss important details... but that's the price to pay when there is no decent documentation available for a given feature. We saw yesterday multiple details about LOCAL_CREDS socket credentials and, as you may deduce, I missed some. First of all I assumed that setting the LOCAL_CREDS option only affected the next received message (I didn't mention this explicitly in the post though). It turns out that this is incorrect: enabling this option makes the socket transmit credentials information on each message until the option is disabled again. Secondly, setting the LOCAL_CREDS option on a server socket (one configured with the listen(2) call) results in all sockets created from it through accept(2) to also carry the flag enabled. In other words, it is inherited. These features are interesting because, when using combined, avoid the need for the synchronization protocol outlined in the previous post — in some cases only. If the credentials are to be transmitted at the very beginning of the connection, the server can follow these steps: Create the server socket and configure it with bind(2) and listen(2).Before entering the accept(2) loop, set the LOCAL_CREDS option on the server socket.Enter the accept(2) loop and start accepting clients.For each new client:Receive its first message.Get the credentials from it.Disable the LOCAL_CREDS option from the socket used to communicate with that specific client.It couldn't be easier! This is still different to all other socket credentials methods I know of but can be easily adapted to protocols that were not designed to support LOCAL_CREDS (i.e. that do not implement the synchronization explained in the previous post).
August 28, 2006
·
Tags:
<a href="/tags/credentials">credentials</a>, <a href="/tags/netbsd">netbsd</a>, <a href="/tags/sockets">sockets</a>
Continue reading (about
2 minutes)
Socket credentials is a feature that allows a user process to receive the credentials (UID, GID, etc.) of the process at the other end of a communication socket in a safe way. The operating system is in charge of managing this information, sent separately from the data flow, so that the user processes cannot fake it. There are many different implementations of this concept out there as you can imagine. For some reason I assumed for a long time that NetBSD didn't support any kind of socket credentials. However, I recently discovered that it indeed supports them through the LOCAL_CREDS socket option. Unfortunately it behaves quite differently from other methods. This poses some annoying portability problems in applications not designed in the first place to support it (e.g. D-Bus, the specific program I'm fighting right now). LOCAL_CREDS works as follows: The receiver interested in remote credentials uses setsockopt(2) to enable the LOCAL_CREDS option in the socket.The sender sends a message through the channel either with write(2) or sendmsg(2). It needn't do anything special other than ensuring that the message is sent after the receiver has enabled the LOCAL_CREDS option.The receiver gets the message using recvmsg(2) and parses the out of band data stored in the control buffer: a struct sockcred message that contains the remote credentials (UID, GID, etc.). This does not provide the PID of the remote process though, as other implementations do.The tricky part here is to ensure that the sender writes the message after the receiver has enabled the LOCAL_CREDS option. If this is not guaranteed, a race condition appears and the behavior becomes random: some times the receiver will get socket credentials, some times it will not. To ensure this restriction there needs to be some kind of synchronization protocol between the two peers. This is illustrated in the following example: it assumes a client/server model and a "go on" message used to synchronize. The server could do: Wait for client connection.Set LOCAL_CREDS option on remote socket.Send a "go on" message to client. Wait for a response, which carries the credentials.Parse the credentials. And the client could do: Connect to server.Wait until "go on" message.Send any message to the server.To conclude, a sample example program that shows how to manage the LOCAL_CREDS option. socketpair(2) is used for simplicity, but this can easily be extrapolated to two independent programs. #include <sys/param.h> #include <sys/types.h> #include <sys/inttypes.h> #include <sys/socket.h> #include <sys/un.h> #include <err.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main(void) { int sv[2]; int on = 1; ssize_t len; struct iovec iov; struct msghdr msg; struct { struct cmsghdr hdr; struct sockcred cred; gid_t groups[NGROUPS - 1]; } cmsg; /* * Create a pair of interconnected sockets for simplicity: * sv[0] - Receive end (this program). * sv[1] - Write end (the remote program, theorically). */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) err(EXIT_FAILURE, "socketpair"); /* * Enable the LOCAL_CREDS option on the reception socket. */ if (setsockopt(sv[0], 0, LOCAL_CREDS, &on, sizeof(on)) == -1) err(EXIT_FAILURE, "setsockopt"); /* * The remote application writes the message AFTER setsockopt * has been used by the receiver. If you move this above the * setsockopt call, you will see how it does not work as * expected. */ if (write(sv[1], &on, sizeof(on)) == -1) err(EXIT_FAILURE, "write"); /* * Prepare space to receive the credentials message. */ iov.iov_base = &on; iov.iov_len = 1; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &cmsg; msg.msg_controllen = sizeof(struct cmsghdr) + SOCKCREDSIZE(NGROUPS); memset(&cmsg, 0, sizeof(cmsg)); /* * Receive the message. */ len = recvmsg(sv[0], &msg, 0); if (len < 0) err(EXIT_FAILURE, "recvmsg"); printf("Got %zu bytesn", len); /* * Print out credentials information, if received * appropriately. */ if (cmsg.hdr.cmsg_type == SCM_CREDS) { printf("UID: %" PRIdMAX "n", (intmax_t)cmsg.cred.sc_uid); printf("EUID: %" PRIdMAX "n", (intmax_t)cmsg.cred.sc_euid); printf("GID: %" PRIdMAX "n", (intmax_t)cmsg.cred.sc_gid); printf("EGID: %" PRIdMAX "n", (intmax_t)cmsg.cred.sc_egid); if (cmsg.cred.sc_ngroups > 0) { int i; printf("Supplementary groups:"); for (i = 0; i < cmsg.cred.sc_ngroups; i++) printf(" %" PRIdMAX, (intmax_t)cmsg.cred.sc_groups[i]); printf("n"); } } else errx(EXIT_FAILURE, "Message did not include credentials"); close(sv[0]); close(sv[1]); return EXIT_SUCCESS; }
August 27, 2006
·
Tags:
<a href="/tags/credentials">credentials</a>, <a href="/tags/netbsd">netbsd</a>, <a href="/tags/sockets">sockets</a>
Continue reading (about
4 minutes)