Index: sys/compat/cloudabi/cloudabi_sock.c =================================================================== --- sys/compat/cloudabi/cloudabi_sock.c +++ sys/compat/cloudabi/cloudabi_sock.c @@ -120,9 +120,10 @@ sizeof(int); if (nfds > fdslen) { /* Unable to store file descriptors. */ - nfds = fdslen; *rflags |= CLOUDABI_SOCK_RECV_FDS_TRUNCATED; + m_dispose_extcontrolm(control); + break; } error = copyout(CMSG_DATA(chdr), fds, nfds * sizeof(int)); @@ -135,9 +136,13 @@ *rfdslen += nfds; } } - m_free(control); + if (control != NULL) { + if (error != 0) + m_dispose_extcontrolm(control); + m_free(control); + } } - return (0); + return (error); } int Index: sys/compat/freebsd32/freebsd32_misc.c =================================================================== --- sys/compat/freebsd32/freebsd32_misc.c +++ sys/compat/freebsd32/freebsd32_misc.c @@ -1133,15 +1133,11 @@ maxlen = msg->msg_controllen; msg->msg_controllen = 0; - m = control; ctlbuf = msg->msg_control; - - while (m && len > 0) { + for (m = control; m != NULL && len > 0; m = m->m_next) { cm = mtod(m, struct cmsghdr *); clen = m->m_len; - while (cm != NULL) { - if (sizeof(struct cmsghdr) > clen || cm->cmsg_len > clen) { error = EINVAL; @@ -1160,7 +1156,8 @@ copylen = sizeof(struct cmsghdr); if (len < copylen) { msg->msg_flags |= MSG_CTRUNC; - copylen = len; + m_dispose_extcontrolm(m); + goto exit; } error = copyout(cm, ctlbuf, copylen); @@ -1177,7 +1174,8 @@ copylen = datalen_out; if (len < copylen) { msg->msg_flags |= MSG_CTRUNC; - copylen = len; + m_dispose_extcontrolm(m); + goto exit; } error = copyout(data, ctlbuf, copylen); @@ -1190,20 +1188,18 @@ if (CMSG_SPACE(datalen) < clen) { clen -= CMSG_SPACE(datalen); cm = (struct cmsghdr *) - ((caddr_t)cm + CMSG_SPACE(datalen)); + ((caddr_t)cm + CMSG_SPACE(datalen)); } else { clen = 0; cm = NULL; } - } - m = m->m_next; + } + msg->msg_controllen = (len <= 0) ? maxlen : + ctlbuf - (caddr_t)msg->msg_control; } - msg->msg_controllen = (len <= 0) ? maxlen : ctlbuf - (caddr_t)msg->msg_control; - exit: return (error); - } int @@ -1240,12 +1236,12 @@ error = kern_recvit(td, uap->s, &msg, UIO_USERSPACE, controlp); if (error == 0) { msg.msg_iov = uiov; - + if (control != NULL) error = freebsd32_copy_msg_out(&msg, control); else msg.msg_controllen = 0; - + if (error == 0) error = freebsd32_copyoutmsghdr(&msg, uap->msg); } Index: sys/compat/linux/linux_socket.c =================================================================== --- sys/compat/linux/linux_socket.c +++ sys/compat/linux/linux_socket.c @@ -1338,9 +1338,8 @@ bsd_to_linux_cmsg_type(cm->cmsg_type); linux_cmsg->cmsg_level = bsd_to_linux_sockopt_level(cm->cmsg_level); - if (linux_cmsg->cmsg_type == -1 - || cm->cmsg_level != SOL_SOCKET) - { + if (linux_cmsg->cmsg_type == -1 || + cm->cmsg_level != SOL_SOCKET) { error = EINVAL; goto bad; } @@ -1348,8 +1347,7 @@ data = CMSG_DATA(cm); datalen = (caddr_t)cm + cm->cmsg_len - (caddr_t)data; - switch (cm->cmsg_type) - { + switch (cm->cmsg_type) { case SCM_RIGHTS: if (flags & LINUX_MSG_CMSG_CLOEXEC) { fds = datalen / sizeof(int); @@ -1402,6 +1400,7 @@ } else { linux_msg.msg_flags |= LINUX_MSG_CTRUNC; + m_dispose_extcontrolm(control); goto out; } } @@ -1429,8 +1428,12 @@ error = copyout(&linux_msg, msghdr, sizeof(linux_msg)); bad: + if (control != NULL) { + if (error != 0) + m_dispose_extcontrolm(control); + m_freem(control); + } free(iov, M_IOV); - m_freem(control); free(linux_cmsg, M_LINUX); return (error); Index: sys/kern/uipc_syscalls.c =================================================================== --- sys/kern/uipc_syscalls.c +++ sys/kern/uipc_syscalls.c @@ -894,7 +894,7 @@ { struct uio auio; struct iovec *iov; - struct mbuf *m, *control = NULL; + struct mbuf *control, *m; caddr_t ctlbuf; struct file *fp; struct socket *so; @@ -941,6 +941,7 @@ if (KTRPOINT(td, KTR_GENIO)) ktruio = cloneuio(&auio); #endif + control = NULL; len = auio.uio_resid; error = soreceive(so, &fromsa, &auio, NULL, (mp->msg_control || controlp) ? &control : NULL, @@ -1004,30 +1005,22 @@ control->m_data += sizeof (struct cmsghdr); } #endif + ctlbuf = mp->msg_control; len = mp->msg_controllen; - m = control; mp->msg_controllen = 0; - ctlbuf = mp->msg_control; - - while (m && len > 0) { - unsigned int tocopy; - - if (len >= m->m_len) - tocopy = m->m_len; - else { - mp->msg_flags |= MSG_CTRUNC; - tocopy = len; - } - - if ((error = copyout(mtod(m, caddr_t), - ctlbuf, tocopy)) != 0) + for (m = control; m != NULL && len >= m->m_len; m = m->m_next) { + if ((error = copyout(mtod(m, caddr_t), ctlbuf, + m->m_len)) != 0) goto out; - ctlbuf += tocopy; - len -= tocopy; - m = m->m_next; + ctlbuf += m->m_len; + len -= m->m_len; + mp->msg_controllen += m->m_len; + } + if (m != NULL) { + mp->msg_flags |= MSG_CTRUNC; + m_dispose_extcontrolm(m); } - mp->msg_controllen = ctlbuf - (caddr_t)mp->msg_control; } out: fdrop(fp, td); @@ -1039,8 +1032,11 @@ if (error == 0 && controlp != NULL) *controlp = control; - else if (control) + else if (control != NULL) { + if (error != 0) + m_dispose_extcontrolm(control); m_freem(control); + } return (error); } @@ -1562,3 +1558,51 @@ } return (error); } + +/* + * Dispose of externalized rights from an SCM_RIGHTS message. This function + * should be used in error or truncation cases to avoid leaking file descriptors + * into the recipient's (the current thread's) table. + */ +void +m_dispose_extcontrolm(struct mbuf *m) +{ + struct cmsghdr *cm; + struct file *fp; + struct thread *td; + socklen_t clen, datalen; + int error, fd, *fds, nfd; + + td = curthread; + for (; m != NULL; m = m->m_next) { + if (m->m_type != MT_EXTCONTROL) + continue; + cm = mtod(m, struct cmsghdr *); + clen = m->m_len; + while (clen > 0) { + if (clen < sizeof(*cm)) + panic("%s: truncated mbuf %p", __func__, m); + datalen = CMSG_SPACE(cm->cmsg_len - CMSG_SPACE(0)); + if (clen < datalen) + panic("%s: truncated mbuf %p", __func__, m); + + if (cm->cmsg_level == SOL_SOCKET && + cm->cmsg_type == SCM_RIGHTS) { + fds = (int *)CMSG_DATA(cm); + nfd = (cm->cmsg_len - CMSG_SPACE(0)) / + sizeof(int); + + while (nfd-- > 0) { + fd = *fds++; + error = fget(td, fd, &cap_no_rights, + &fp); + if (error == 0) + fdclose(td, fp, fd); + } + } + clen -= datalen; + cm = (struct cmsghdr *)((uint8_t *)cm + datalen); + } + m_chtype(m, MT_CONTROL); + } +} Index: sys/kern/uipc_usrreq.c =================================================================== --- sys/kern/uipc_usrreq.c +++ sys/kern/uipc_usrreq.c @@ -2046,6 +2046,13 @@ &fdep[i]->fde_caps); unp_externalize_fp(fdep[i]->fde_file); } + + /* + * The new type indicates that the mbuf data refers to + * kernel resources that may need to be released before + * the mbuf is freed. + */ + m_chtype(*controlp, MT_EXTCONTROL); FILEDESC_XUNLOCK(fdesc); free(fdep[0], M_FILECAPS); } else { Index: sys/sys/mbuf.h =================================================================== --- sys/sys/mbuf.h +++ sys/sys/mbuf.h @@ -568,7 +568,8 @@ #define MT_EXP4 12 /* for experimental use */ #define MT_CONTROL 14 /* extra-data protocol message */ -#define MT_OOBDATA 15 /* expedited data */ +#define MT_EXTCONTROL 15 /* control message with externalized contents */ +#define MT_OOBDATA 16 /* expedited data */ #define MT_NOINIT 255 /* Not a type but a flag to allocate a non-initialized mbuf */ @@ -633,6 +634,7 @@ void m_demote(struct mbuf *, int, int); struct mbuf *m_devget(char *, int, int, struct ifnet *, void (*)(char *, caddr_t, u_int)); +void m_dispose_extcontrolm(struct mbuf *m); struct mbuf *m_dup(const struct mbuf *, int); int m_dup_pkthdr(struct mbuf *, const struct mbuf *, int); void m_extadd(struct mbuf *, char *, u_int, m_ext_free_t,