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:
  1. The receiver interested in remote credentials uses setsockopt(2) to enable the LOCAL_CREDS option in the socket.
  2. 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.
  3. 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:
  1. Wait for client connection.
  2. Set LOCAL_CREDS option on remote socket.
  3. Send a "go on" message to client.
  4. Wait for a response, which carries the credentials.
  5. Parse the credentials.
And the client could do:
  1. Connect to server.
  2. Wait until "go on" message.
  3. 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;
}

Go to posts index

Comments from the original Blogger-hosted post: