Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More

9.8.1 Problem

Using a Unix domain socket, you want to find out information about the process that is on the other end of the connection, such as its user and group IDs.

9.8.2 Solution

Most Unix domain socket implementations provide support for receiving the credentials of the peer process involved in a Unix domain socket connection. Using this information, we can discover the user ID and group ID of the process on the other end of the connection. Credential information is not passed automatically. For all implementations, the receiver must explicitly ask for the information. With some implementations, the information must be explicitly sent. In general, when you're designing a system that will exchange credentials, you should be sure to coordinate on both ends exactly when the credentials will be requested and sent.

This recipe works on FreeBSD, Linux, and NetBSD. Unfortunately, not all Unix domain socket implementations provide support for credentials. At the time of this writing, the Darwin kernel (the core of MacOS X), OpenBSD, and Solaris do not support credentials.

9.8.3 Discussion

In addition to the previously mentioned platform support limitations with credentials, a second problem is that different implementations exchange the information in different ways. On FreeBSD systems, for example, the information must be explicitly sent, and the receiver must be able to handle receiving it. On Linux systems, the information is automatically sent if the receiver asks for it.

A third problem is that not all implementations pass the same information. Linux passes the process ID, user ID, and group ID of the sending process. FreeBSD includes all groups that the process is a member of, but it does not include the process ID. At a minimum, you can expect to get the process's user and group IDs and nothing more.

#include <errno.h> #include <stdlib.h> #include <string.h> #include <sys/param.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> #if !defined(linux) && !defined(__NetBSD__) #include <sys/ucred.h> #endif #ifndef SCM_CREDS #define SCM_CREDS SCM_CREDENTIALS #endif #ifndef linux # ifndef __NetBSD__ # define SPC_PEER_UID(c) ((c)->cr_uid) # define SPC_PEER_GID(c) ((c)->cr_groups[0]) # else # define SPC_PEER_UID(c) ((c)->sc_uid) # define SPC_PEER_GID(c) ((c)->sc_gid) # endif #else # define SPC_PEER_UID(c) ((c)->uid) # define SPC_PEER_GID(c) ((c)->gid) #endif #ifdef __NetBSD__ typedef struct sockcred spc_credentials; #else typedef struct ucred spc_credentials; #endif spc_credentials *spc_get_credentials(int sd) { int nb, sync; char ctrl[CMSG_SPACE(sizeof(struct ucred))]; size_t size; struct iovec iov[1] = { { 0, 0 } }; struct msghdr msg = { 0, 0, iov, 1, ctrl, sizeof(ctrl), 0 }; struct cmsghdr *cmptr; spc_credentials *credentials; #ifdef LOCAL_CREDS nb = 1; if (setsockopt(sd, 0, LOCAL_CREDS, &nb, sizeof(nb)) == -1) return 0; #else #ifdef SO_PASSCRED nb = 1; if (setsockopt(sd, SOL_SOCKET, SO_PASSCRED, &nb, sizeof(nb)) == -1) return 0; #endif #endif do { msg.msg_iov->iov_base = (void *)&sync; msg.msg_iov->iov_len = sizeof(sync); nb = recvmsg(sd, &msg, 0); } while (nb == -1 && (errno == EINTR || errno == EAGAIN)); if (nb == -1) return 0; if (msg.msg_controllen < sizeof(struct cmsghdr)) return 0; cmptr = CMSG_FIRSTHDR(&msg); #ifndef __NetBSD__ size = sizeof(spc_credentials); #else if (cmptr->cmsg_len < SOCKCREDSIZE(0)) return 0; size = SOCKCREDSIZE(((cred *)CMSG_DATA(cmptr))->sc_ngroups); #endif if (cmptr->cmsg_len != CMSG_LEN(size)) return 0; if (cmptr->cmsg_level != SOL_SOCKET) return 0; if (cmptr->cmsg_type != SCM_CREDS) return 0; if (!(credentials = (spc_credentials *)malloc(size))) return 0; *credentials = *(spc_credentials *)CMSG_DATA(cmptr); return credentials; } int spc_send_credentials(int sd) { int sync = 0x11223344; struct iovec iov[1] = { { 0, 0, } }; struct msghdr msg = { 0, 0, iov, 1, 0, 0, 0 }; #if !defined(linux) && !defined(__NetBSD__) char ctrl[CMSG_SPACE(sizeof(spc_credentials))]; struct cmsghdr *cmptr; msg.msg_control = ctrl; msg.msg_controllen = sizeof(ctrl); cmptr = CMSG_FIRSTHDR(&msg); cmptr->cmsg_len = CMSG_LEN(sizeof(spc_credentials)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_CREDS; memset(CMSG_DATA(cmptr), 0, sizeof(spc_credentials)); #endif msg.msg_iov->iov_base = (void *)&sync; msg.msg_iov->iov_len = sizeof(sync); return (sendmsg(sd, &msg, 0) != -1); }

On all platforms, it is possible to obtain credentials from a peer at any point during the connection; however, it often makes the most sense to get the information immediately after the connection is established. For example, if your server needs to get the credentials of each client that connects, the server code might look something like this:

typedef void (*spc_client_fn)(spc_socket_t *, spc_credentials *, void *); void spc_unix_server(spc_client_fn callback, void *arg) { spc_socket_t *client, *listener; spc_credentials *credentials; listener = spc_socket_listen(SOCK_STREAM, 0, "127.0.0.1", 2222); while ((client = spc_socket_accept(listener)) != 0) { if (!(credentials = spc_get_credentials(client->sd))) { printf("Unable to get credentials from connecting client!\n"); spc_socket_close(client); } else { printf("Client credentials:\n\tuid: %d\n\tgid: %d\n", SPC_PEER_UID(credentials), SPC_PEER_GID(credentials)); /* do something with the credentials and the connection ... */ callback(client, credentials, arg); } } }

The corresponding client code might look something like this:

spc_socket_t *spc_unix_connect(void) { spc_socket_t *conn; if (!(conn = spc_socket_connect("127.0.0.1", 2222))) { printf("Unable to connect to the server!\n"); return 0; } if (!spc_send_credentials(conn->sd)) { printf("Unable to send credentials to the server!\n"); spc_socket_close(conn); return 0; } printf("Credentials were successfully sent to the server.\n"); return conn; }

Note finally that while it is possible to obtain credentials from a peer at any point during the connection, many implementations will send the credentials only once. If you need the credential information at more than one point during a conversation, you should make sure to save the information that was obtained the first time it was needed.

Категории