From owner-freebsd-bugs@FreeBSD.ORG Thu Dec 22 11:00:35 2005 Return-Path: X-Original-To: freebsd-bugs@hub.freebsd.org Delivered-To: freebsd-bugs@hub.freebsd.org Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 43B1F16A420 for ; Thu, 22 Dec 2005 11:00:35 +0000 (GMT) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (freefall.freebsd.org [216.136.204.21]) by mx1.FreeBSD.org (Postfix) with ESMTP id 912FA43D86 for ; Thu, 22 Dec 2005 11:00:17 +0000 (GMT) (envelope-from gnats@FreeBSD.org) Received: from freefall.freebsd.org (gnats@localhost [127.0.0.1]) by freefall.freebsd.org (8.13.4/8.13.4) with ESMTP id jBMB0HZE075640 for ; Thu, 22 Dec 2005 11:00:17 GMT (envelope-from gnats@freefall.freebsd.org) Received: (from gnats@localhost) by freefall.freebsd.org (8.13.4/8.13.4/Submit) id jBMB0H2e075639; Thu, 22 Dec 2005 11:00:17 GMT (envelope-from gnats) Resent-Date: Thu, 22 Dec 2005 11:00:17 GMT Resent-Message-Id: <200512221100.jBMB0H2e075639@freefall.freebsd.org> Resent-From: FreeBSD-gnats-submit@FreeBSD.org (GNATS Filer) Resent-To: freebsd-bugs@FreeBSD.org Resent-Reply-To: FreeBSD-gnats-submit@FreeBSD.org, Andrey Simonenko Received: from mx1.FreeBSD.org (mx1.freebsd.org [216.136.204.125]) by hub.freebsd.org (Postfix) with ESMTP id 030EA16A41F for ; Thu, 22 Dec 2005 10:57:34 +0000 (GMT) (envelope-from simon@comsys.ntu-kpi.kiev.ua) Received: from comsys.ntu-kpi.kiev.ua (comsys.ntu-kpi.kiev.ua [195.245.194.142]) by mx1.FreeBSD.org (Postfix) with ESMTP id D3EA343D66 for ; Thu, 22 Dec 2005 10:57:32 +0000 (GMT) (envelope-from simon@comsys.ntu-kpi.kiev.ua) Received: from pm513-1.comsys.ntu-kpi.kiev.ua (pm513-1.comsys.ntu-kpi.kiev.ua [10.18.52.101]) (authenticated bits=0) by comsys.ntu-kpi.kiev.ua (8.12.10/8.12.10) with ESMTP id jBMB8MVO033226 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=FAIL) for ; Thu, 22 Dec 2005 13:08:27 +0200 (EET) Received: by pm513-1.comsys.ntu-kpi.kiev.ua (Postfix, from userid 1001) id EC34F5C020; Thu, 22 Dec 2005 12:57:30 +0200 (EET) Message-Id: <20051222105730.GA1228@pm513-1.comsys.ntu-kpi.kiev.ua> Date: Thu, 22 Dec 2005 12:57:30 +0200 From: Andrey Simonenko To: FreeBSD-gnats-submit@FreeBSD.org Cc: Subject: kern/90800: [patch] it is possible to fake credentials in LOCAL_CREDS X-BeenThere: freebsd-bugs@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Bug reports List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 22 Dec 2005 11:00:35 -0000 >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 #include #include #include #include #include #include #include #include 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 #include #include #include #include #include #include #include #include #include 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: