kern/90800: [patch] it is possible to fake credentials in LOCAL_CREDS

Andrey Simonenko simon at comsys.ntu-kpi.kiev.ua
Thu Dec 22 03:00:35 PST 2005


>Number:         90800
>Category:       kern
>Synopsis:       [patch] it is possible to fake credentials in LOCAL_CREDS
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Dec 22 11:00:17 GMT 2005
>Closed-Date:
>Last-Modified:
>Originator:     Andrey Simonenko
>Release:        FreeBSD 6.0-STABLE i386
>Organization:
>Environment:

FreeBSD 6.0-STABLE
src/sys/kern/uipc_usrreq.c,v 1.155.2.2

>Description:

In FreeBSD 6.0 LOCAL_CREDS option for Unix domain sockets was
added, like NetBSD (and probably other systems) now a receiver
can get credential information about a sender.

In FreeBSD < 6.0 there is a method to receive sender's credentials
if a sender "sends" credentials to a receiver (the kernel constructs
and fills struct cmsgcred).

Both methods use the same control message type SCM_CREDS with the
same control message level SOL_SOCKET, so they are indistinguishable
for a receiver.

Now if a sender sends cmsgcred's style credentials and a receiver
expects LOCAL_CREDS's style credentials, then a receiver will
get two control messages: first with the cmsgcred structure and
second with the sockcred structure.  Remember that a receiver
expects to get the sockcred structure.

Result picture (for 32bit system at least):

struct cmsgcred {                   struct sockcred {
pid_t	cmcred_pid;      - FAKE ->  uid_t	sc_uid;
uid_t	cmcred_uid;      - FAKE ->  uid_t	sc_euid;
uid_t	cmcred_euid;     - FAKE ->  gid_t	sc_gid;
gid_t	cmcred_gid;      - FAKE ->  gid_t	sc_egid;
short   cmcred_ngroups;             int		sc_ngroups;
gid_t	cmcred_groups[...];         gid_t	sc_groups[1];
};                                  };

First FAKE is the most serious.

>How-To-Repeat:

Here I include client and server to test the problem,
compile them and run.

Client cli.c:

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <unistd.h>

int
main(void)
{
	int	sockfd;
	struct iovec iov[1];
	struct msghdr msg;
	struct cmsghdr *cmptr;
	struct sockaddr_un servaddr;
	union {
		struct cmsghdr	cm;
		char		control[CMSG_SPACE(sizeof(struct cmsgcred))];
	} control_un;

	printf("PID %lu\n", (u_long)getpid());
	system("id");

	if ( (sockfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
		err(1, "socket(AF_LOCAL, SOCK_STREAM, 0)");

	bzero(&servaddr, sizeof servaddr);
	servaddr.sun_family = AF_LOCAL;
	strncpy(servaddr.sun_path, "/tmp/sock.un", sizeof(servaddr.sun_path) - 1);

	if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof servaddr) < 0)
		err(1, "connect");

	bzero(&msg, sizeof msg);
	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof control_un.control;

	cmptr = CMSG_FIRSTHDR(&msg);
	cmptr->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
	cmptr->cmsg_level = SOL_SOCKET;
	cmptr->cmsg_type= SCM_CREDS;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	iov[0].iov_base = "hello";
	iov[0].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	if (sendmsg(sockfd, &msg, 0) < 0)
		err(1, "sendmsg");

	if (close(sockfd) < 0)
		err(1, "close");

	return 0;
}

Server srv.c:

#include <err.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <unistd.h>

#include <netinet/in.h>

static void
get_msg(int fd)
{
	int	i;
	char	buf[10];
	ssize_t	nread;
	struct msghdr msg;
	struct iovec iov[1];
	union {
		struct cmsghdr	cm;
		char		control[10240];
	} control_un;
	struct cmsghdr *cmptr;
	struct sockcred *sockcred;

	msg.msg_control = control_un.control;
	msg.msg_controllen = sizeof control_un.control;

	msg.msg_name = NULL;
	msg.msg_namelen = 0;

	bzero(buf, sizeof buf);
	iov[0].iov_base = buf;
	iov[0].iov_len = 5;
	msg.msg_iov = iov;
	msg.msg_iovlen = 1;

	nread = recvmsg(fd, &msg, 0);

	if (nread < 0)
		err(1, "recvmsg");
	if (nread != 5)
		errx(1, "recvmsg");

	printf("received `%s'\n", buf);

	if (msg.msg_flags & MSG_CTRUNC)
		errx(1, "control data was truncated");

	if (msg.msg_controllen < sizeof(struct cmsghdr))
		warnx("sender's credentials are not returned (msg_controllen = %d)",
		    (int)msg.msg_controllen);
	else {
		printf("msg_controllen %d\n", msg.msg_controllen);
		for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr)) {
			if (cmptr->cmsg_len < CMSG_LEN(SOCKCREDSIZE(1)))
				errx(1, "incorrect control message with credentials (less than %u bytes)",
				    CMSG_LEN(SOCKCREDSIZE(1)));
			printf("cmsg_len %d\n", cmptr->cmsg_len);
			if (cmptr->cmsg_level != SOL_SOCKET)
				errx(1, "cmsg_level != SOL_SOCKET");
			if (cmptr->cmsg_type != SCM_CREDS)
				errx(1, "cmsg_type != SCM_CREDS");
			sockcred = (struct sockcred *)CMSG_DATA(cmptr);
			printf("UID %lu, GID %lu,",
			    (u_long)sockcred->sc_uid,
			    (u_long)sockcred->sc_gid);
			for (i = 0; i < sockcred->sc_ngroups; ++i)
				printf(" %lu", (u_long)sockcred->sc_groups[i]);
			printf("\n");
		}
	}

	if (close(fd) < 0)
		err(1, "close");
}

int
main(void)
{
	int	optval, listenfd, connfd;
	struct sockaddr_un servaddr;

	if ( (listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
		err(1, "socket(AF_LOCAL, SOCK_STREAM, 0)");

	bzero(&servaddr, sizeof servaddr);
	servaddr.sun_family = AF_LOCAL;
	strncpy(servaddr.sun_path, "/tmp/sock.un", sizeof(servaddr.sun_path) - 1);

	(void)unlink("/tmp/sock.un");
	if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof servaddr) < 0)
		err(1, "bind");

	if (listen(listenfd, 5) < 0)
		err(1, "listen(5)");


	for (;;) {
		if ( (connfd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL)) < 0)
			err(1, "accept");
		optval = 1;
		if (setsockopt(connfd, 0, LOCAL_CREDS, &optval, sizeof optval) < 0)
			err(1, "setsockopt");
		get_msg(connfd);
	}

	return 0;
}

Running on original 6.0-STABLE:

% ./cli
PID 627
uid=1001(simon) gid=20(staff) groups=20(staff), 0(wheel)

% ./srv
received `hello'
msg_controllen 140
cmsg_len 96
UID 627, GID 1001, 20 20 0
cmsg_len 44
UID 1001, GID 20, 20 20 0

Running on 6.0-STABLE + patch:

% ./cli
PID 611
uid=1001(simon) gid=20(staff) groups=20(staff), 0(wheel)

% ./srv
received `hello'
msg_controllen 44
cmsg_len 44
UID 1001, GID 20, 20 20 0

>Fix:

In uipc_usrreq.c:unp_addsockcred() remove already linked SCM_CREDS
control messages, because they contain the cmsgcred structure.
Also I prepend a new control message to the head, I do not see
why this can be wrong, if this is wrong it will require few if()s
to append a new control message to the tail.

--- uipc_usrreq.c.orig	Wed Nov 30 16:58:29 2005
+++ uipc_usrreq.c	Wed Dec 21 23:16:44 2005
@@ -1546,8 +1546,9 @@
 struct mbuf *
 unp_addsockcred(struct thread *td, struct mbuf *control)
 {
-	struct mbuf *m, *n;
+	struct mbuf *m, *n, *n_prev;
 	struct sockcred *sc;
+	const struct cmsghdr *cm;
 	int ngroups;
 	int i;
 
@@ -1556,7 +1557,6 @@
 	m = sbcreatecontrol(NULL, SOCKCREDSIZE(ngroups), SCM_CREDS, SOL_SOCKET);
 	if (m == NULL)
 		return (control);
-	m->m_next = NULL;
 
 	sc = (struct sockcred *) CMSG_DATA(mtod(m, struct cmsghdr *));
 	sc->sc_uid = td->td_ucred->cr_ruid;
@@ -1568,16 +1568,30 @@
 		sc->sc_groups[i] = td->td_ucred->cr_groups[i];
 
 	/*
-	 * If a control message already exists, append us to the end.
+	 * Unlink SCM_CREDS control messages (struct cmsgcred), since
+	 * just created SCM_CREDS control message (struct sockcred) has
+	 * another format.
 	 */
-	if (control != NULL) {
-		for (n = control; n->m_next != NULL; n = n->m_next)
-			;
-		n->m_next = m;
-	} else
-		control = m;
+	if (control != NULL)
+		for (n = control, n_prev = NULL; n != NULL;) {
+			cm = mtod(n, struct cmsghdr *);
+    			if (cm->cmsg_level == SOL_SOCKET &&
+			    cm->cmsg_type == SCM_CREDS) {
+    				if (n_prev == NULL)
+					control = n->m_next;
+				else
+					n_prev->m_next = n->m_next;
+				n = m_free(n);
+			} else {
+				n_prev = n;
+				n = n->m_next;
+			}
+		}
 
-	return (control);
+	/* Prepend it to the head. */
+	m->m_next = control;
+
+	return (m);
 }
 
 /*
>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list