diff options
| author | dsl <dsl@NetBSD.org> | 2007-06-01 22:53:52 +0000 |
|---|---|---|
| committer | dsl <dsl@NetBSD.org> | 2007-06-01 22:53:52 +0000 |
| commit | d7f93c5c67bd310917c686eadbea09e214305f16 (patch) | |
| tree | ca02585ba2f1b9b2d6abda8276b4f0e2b3f288b5 /sys/compat/linux/common/linux_socket.c | |
| parent | d95fed731596e3e08213f314871d79293e2f148c (diff) | |
Split sys_bind() and sys_connect() so that compat code can use common code
once the 'address' has been copied into an mbuf.
Add extra flags for 'struct msghdr.msg_flags' to indicate that the address
and control are already in mbufs, and that the uio structure is in userspace
for sending data, rename sendit() to do_sys_sendmsg() to ensure no old code
passes in random flags.
Changes to compat code to use new functions - removing some stackgap use.
Fix a 'use after free' in compat_43_sys_recvmsg.
I ***THINK*** the code that converts 'cmsg' formatted data is borked!
svr4_stream.c ought to be generated from svr4_32_stream.c during the build.
Diffstat (limited to 'sys/compat/linux/common/linux_socket.c')
| -rw-r--r-- | sys/compat/linux/common/linux_socket.c | 439 |
1 files changed, 167 insertions, 272 deletions
diff --git a/sys/compat/linux/common/linux_socket.c b/sys/compat/linux/common/linux_socket.c index bd0f7601dc2..4bb7500a212 100644 --- a/sys/compat/linux/common/linux_socket.c +++ b/sys/compat/linux/common/linux_socket.c @@ -1,4 +1,4 @@ -/* $NetBSD: linux_socket.c,v 1.72 2007/05/29 21:32:28 christos Exp $ */ +/* $NetBSD: linux_socket.c,v 1.73 2007/06/01 22:53:52 dsl Exp $ */ /*- * Copyright (c) 1995, 1998 The NetBSD Foundation, Inc. @@ -42,8 +42,9 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: linux_socket.c,v 1.72 2007/05/29 21:32:28 christos Exp $"); +__KERNEL_RCSID(0, "$NetBSD: linux_socket.c,v 1.73 2007/06/01 22:53:52 dsl Exp $"); +#include "opt_ktrace.h" #if defined(_KERNEL_OPT) #include "opt_inet.h" #endif @@ -77,6 +78,9 @@ __KERNEL_RCSID(0, "$NetBSD: linux_socket.c,v 1.72 2007/05/29 21:32:28 christos E #include <sys/kauth.h> #include <sys/syscallargs.h> +#ifdef KTRACE +#include <sys/ktrace.h> +#endif #include <lib/libkern/libkern.h> @@ -122,8 +126,8 @@ int linux_to_bsd_ip_sockopt __P((int)); int linux_to_bsd_tcp_sockopt __P((int)); int linux_to_bsd_udp_sockopt __P((int)); int linux_getifhwaddr __P((struct lwp *, register_t *, u_int, void *)); -static int linux_sa_get __P((struct lwp *, int, void **, struct sockaddr **, - const struct osockaddr *, socklen_t *)); +static int linux_get_sa(struct lwp *, int, struct mbuf **, + const struct osockaddr *, int); static int linux_sa_put __P((struct osockaddr *osa)); static int linux_to_bsd_msg_flags __P((int)); static int bsd_to_linux_msg_flags __P((int)); @@ -383,31 +387,34 @@ linux_sys_sendto(l, v, retval) syscallarg(struct osockaddr *) to; syscallarg(int) tolen; } */ *uap = v; - struct proc *p = l->l_proc; - struct sys_sendto_args bsa; - socklen_t tolen; + struct msghdr msg; + struct iovec aiov; + struct mbuf *nam; + int bflags; + int error; - SCARG(&bsa, s) = SCARG(uap, s); - SCARG(&bsa, buf) = SCARG(uap, msg); - SCARG(&bsa, len) = (size_t) SCARG(uap, len); - SCARG(&bsa, flags) = SCARG(uap, flags); - tolen = SCARG(uap, tolen); - if (SCARG(uap, to)) { - struct sockaddr *sa; - int error; - void *sg = stackgap_init(p, 0); - - error = linux_sa_get(l, SCARG(uap, s), &sg, &sa, - SCARG(uap, to), &tolen); - if (error) - return (error); + /* Translate message flags. */ + bflags = linux_to_bsd_msg_flags(SCARG(uap, flags)); + if (bflags < 0) + /* Some supported flag */ + return EINVAL; - SCARG(&bsa, to) = sa; - } else - SCARG(&bsa, to) = NULL; - SCARG(&bsa, tolen) = tolen; + /* Read in and convert the sockaddr */ + error = linux_get_sa(l, SCARG(uap, s), &nam, SCARG(uap, to), + SCARG(uap, tolen)); + if (error) + return (error); + msg.msg_flags = MSG_NAMEMBUF; - return (sys_sendto(l, &bsa, retval)); + msg.msg_name = nam; + msg.msg_namelen = SCARG(uap, tolen); + msg.msg_iov = &aiov; + msg.msg_iovlen = 1; + msg.msg_control = 0; + aiov.iov_base = __UNCONST(SCARG(uap, msg)); + aiov.iov_len = SCARG(uap, len); + + return do_sys_sendmsg(l, SCARG(uap, s), &msg, bflags, retval); } int @@ -421,53 +428,31 @@ linux_sys_sendmsg(l, v, retval) syscallarg(struct msghdr *) msg; syscallarg(u_int) flags; } */ *uap = v; - struct proc *p = l->l_proc; struct msghdr msg; int error; - struct iovec aiov[UIO_SMALLIOV], *iov; - void *sg = 0; int bflags; - u_int8_t *control=NULL; - - error = copyin(SCARG(uap, msg), (void *)&msg, sizeof(msg)); - if (error) - return (error); - if ((unsigned int)msg.msg_iovlen > UIO_SMALLIOV) { - if ((unsigned int)msg.msg_iovlen > IOV_MAX) - return (EMSGSIZE); - iov = malloc(sizeof(struct iovec) * msg.msg_iovlen, - M_IOV, M_WAITOK); - } else - iov = aiov; - if ((unsigned int)msg.msg_iovlen > 0) { - error = copyin((void *)msg.msg_iov, (void *)iov, - (size_t)(msg.msg_iovlen * sizeof(struct iovec))); - if (error) - goto done; - } - msg.msg_iov = iov; - msg.msg_flags = 0; + struct mbuf *nam; + u_int8_t *control; + struct mbuf *ctl_mbuf = NULL; - /* Convert the sockaddr if necessary */ - if (msg.msg_name) { - struct sockaddr *sa; - sg = stackgap_init(p, 0); - - error = linux_sa_get(l, SCARG(uap, s), &sg, &sa, - (struct osockaddr *) msg.msg_name, &msg.msg_namelen); - if (error) - goto done; - msg.msg_name = sa; - } + msg.msg_flags = MSG_IOVUSRSPACE; /* * Translate message flags. */ bflags = linux_to_bsd_msg_flags(SCARG(uap, flags)); - if (bflags < 0) { + if (bflags < 0) /* Some supported flag */ - error = EINVAL; - goto done; + return EINVAL; + + if (msg.msg_name) { + /* Read in and convert the sockaddr */ + error = linux_get_sa(l, SCARG(uap, s), &nam, msg.msg_name, + msg.msg_namelen); + if (error) + return (error); + msg.msg_flags |= MSG_NAMEMBUF; + msg.msg_name = nam; } /* @@ -475,25 +460,12 @@ linux_sys_sendmsg(l, v, retval) */ if (CMSG_FIRSTHDR(&msg)) { struct cmsghdr cmsg, *cc; - int changed = 0; ssize_t resid = msg.msg_controllen; size_t clen, cidx = 0, cspace; - /* - * Limit the size even more than what sockargs() would do, - * We need to fit into stackgap space. - */ - if (msg.msg_controllen > (STACKGAPLEN / 2)) { - /* Sorry guys! */ - error = EMSGSIZE; - goto done; - } - - control = malloc((clen = msg.msg_controllen), M_TEMP, M_WAITOK); - if (!control) { - error = ENOMEM; - goto done; - } + ctl_mbuf = m_get(M_WAIT, MT_CONTROL); + clen = MLEN; + control = mtod(ctl_mbuf, void *); cc = CMSG_FIRSTHDR(&msg); do { @@ -517,10 +489,8 @@ linux_sys_sendmsg(l, v, retval) switch (cmsg.cmsg_level) { case LINUX_SOL_SOCKET: /* It only differs on some archs */ - if (LINUX_SOL_SOCKET != SOL_SOCKET) { + if (LINUX_SOL_SOCKET != SOL_SOCKET) cmsg.cmsg_level = SOL_SOCKET; - changed = 1; - } switch(cmsg.cmsg_type) { case LINUX_SCM_RIGHTS: @@ -545,11 +515,19 @@ linux_sys_sendmsg(l, v, retval) u_int8_t *nc; clen = cidx + cspace; - nc = realloc(control, clen, M_TEMP, M_WAITOK); + if (clen >= PAGE_SIZE) { + error = EINVAL; + goto done; + } + nc = realloc(clen <= MLEN ? NULL : control, + clen, M_TEMP, M_WAITOK); if (!nc) { error = ENOMEM; goto done; } + if (cidx <= MLEN) + /* Old buffer was in mbuf... */ + memcpy(nc, control, cidx); control = nc; } @@ -567,66 +545,32 @@ linux_sys_sendmsg(l, v, retval) if (error) goto done; - /* - * If there is alignment difference, we changed - * layout of cmsg. - */ - if (LINUX_CMSG_ALIGNDIFF) - changed = 1; - resid -= cspace; cidx += cspace; } while ((cc = LINUX_CMSG_NXTHDR(&msg, cc)) && resid > 0); - /* - * If any of the passed control message needed - * a change, put the changed data into stackgap - * and adjust msg appropriately. - */ - if (changed) { - char *newc; - - /* - * Check again the total len is maximum half of - * stackgap. The length might change if the - * alignment is different. - */ - if (clen > STACKGAPLEN/2) { - error = EMSGSIZE; - goto done; - } - - /* - * Allocate space on stack within stackgap, and - * copy changed data there. - */ - if (!sg) - sg = stackgap_init(p, STACKGAPLEN/3); - newc = stackgap_alloc(p, &sg, clen); - if (!newc) { - error = ENOMEM; - goto done; - } - - error = copyout(control, newc, clen); - if (error) - goto done; - - msg.msg_control = newc; - msg.msg_controllen = clen; + /* If we allocated a buffer, attach to mbuf */ + if (cidx > MLEN) { + MEXTADD(ctl_mbuf, control, clen, M_MBUF, NULL, NULL); + ctl_mbuf->m_flags |= M_EXT_RW; } - - free(control, M_TEMP); control = NULL; + ctl_mbuf->m_len = cidx; + + msg.msg_control = ctl_mbuf; + msg.msg_flags |= MSG_CONTROLMBUF; } - error = sendit(l, SCARG(uap, s), &msg, bflags, retval); + error = do_sys_sendmsg(l, SCARG(uap, s), &msg, bflags, retval); + /* Freed internally */ + ctl_mbuf = NULL; done: - if (control) - free(control, M_TEMP); - if (iov != aiov) - free(iov, M_IOV); + if (ctl_mbuf != NULL) { + if (control != NULL && control != mtod(ctl_mbuf, void *)) + free(control, M_MBUF); + m_free(ctl_mbuf); + } return (error); } @@ -737,8 +681,7 @@ linux_sys_recvmsg(l, v, retval) goto done; /* Fixup sockaddr */ - error = copyin((void *)SCARG(uap, msg), (void *)&msg, - sizeof(msg)); + error = copyin(SCARG(uap, msg), &msg, sizeof(msg)); if (error) goto done; @@ -1143,7 +1086,7 @@ linux_getifhwaddr(struct lwp *l, register_t *retval, u_int fd, goto out; } - error = copyin(data, (void *)&lreq, sizeof(lreq)); + error = copyin(data, &lreq, sizeof(lreq)); if (error) goto out; lreq.if_name[IF_NAME_LEN-1] = '\0'; /* just in case */ @@ -1166,14 +1109,12 @@ linux_getifhwaddr(struct lwp *l, register_t *retval, u_int fd, if (sadl->sdl_family != AF_LINK || sadl->sdl_type != IFT_ETHER) continue; - memcpy((void *)&lreq.hwaddr.sa_data, - LLADDR(sadl), + memcpy(&lreq.hwaddr.sa_data, LLADDR(sadl), MIN(sadl->sdl_alen, sizeof(lreq.hwaddr.sa_data))); lreq.hwaddr.sa_family = sadl->sdl_family; - error = copyout((void *)&lreq, data, - sizeof(lreq)); + error = copyout(&lreq, data, sizeof(lreq)); goto out; } } else { @@ -1210,14 +1151,13 @@ linux_getifhwaddr(struct lwp *l, register_t *retval, u_int fd, if (ifnum--) /* not the reqested iface */ continue; - memcpy((void *)&lreq.hwaddr.sa_data, + memcpy(&lreq.hwaddr.sa_data, LLADDR(sadl), MIN(sadl->sdl_alen, sizeof(lreq.hwaddr.sa_data))); lreq.hwaddr.sa_family = sadl->sdl_family; - error = copyout((void *)&lreq, data, - sizeof(lreq)); + error = copyout(&lreq, data, sizeof(lreq)); found = 1; break; } @@ -1253,7 +1193,7 @@ linux_ioctl_socket(l, uap, retval) int (*ioctlf)(struct file *, u_long, void *, struct lwp *); struct ioctl_pt pt; - fdp = p->p_fd; + fdp = p->p_fd; if ((fp = fd_getfile(fdp, SCARG(uap, fd))) == NULL) return (EBADF); @@ -1320,7 +1260,7 @@ linux_ioctl_socket(l, uap, retval) SCARG(&ia, com) = OSIOCDELMULTI; break; case LINUX_SIOCGIFHWADDR: - error = linux_getifhwaddr(l, retval, SCARG(uap, fd), + error = linux_getifhwaddr(l, retval, SCARG(uap, fd), SCARG(uap, data)); dosys = 0; break; @@ -1352,24 +1292,15 @@ linux_sys_connect(l, v, retval) syscallarg(const struct sockaddr *) name; syscallarg(int) namelen; } */ *uap = v; - struct proc *p = l->l_proc; int error; - struct sockaddr *sa; - struct sys_connect_args bca; - void *sg = stackgap_init(p, 0); - socklen_t namlen; + struct mbuf *nam; - namlen = SCARG(uap, namelen); - error = linux_sa_get(l, SCARG(uap, s), &sg, &sa, - SCARG(uap, name), &namlen); + error = linux_get_sa(l, SCARG(uap, s), &nam, SCARG(uap, name), + SCARG(uap, namelen)); if (error) return (error); - SCARG(&bca, s) = SCARG(uap, s); - SCARG(&bca, name) = sa; - SCARG(&bca, namelen) = namlen; - - error = sys_connect(l, &bca, retval); + error = do_sys_connect(l, SCARG(uap, s), nam); if (error == EISCONN) { struct file *fp; @@ -1377,7 +1308,7 @@ linux_sys_connect(l, v, retval) int s, state, prflags; /* getsock() will use the descriptor for us */ - if (getsock(p->p_fd, SCARG(uap, s), &fp) != 0) + if (getsock(l->l_proc->p_fd, SCARG(uap, s), &fp) != 0) return EISCONN; s = splsoftnet(); @@ -1410,28 +1341,15 @@ linux_sys_bind(l, v, retval) syscallarg(const struct osockaddr *) name; syscallarg(int) namelen; } */ *uap = v; - struct proc *p = l->l_proc; int error; - socklen_t namlen; - struct sys_bind_args bsa; + struct mbuf *nam; - namlen = SCARG(uap, namelen); - SCARG(&bsa, s) = SCARG(uap, s); - if (SCARG(uap, name)) { - struct sockaddr *sa; - void *sg = stackgap_init(p, 0); - - error = linux_sa_get(l, SCARG(uap, s), &sg, &sa, - SCARG(uap, name), &namlen); - if (error) - return (error); - - SCARG(&bsa, name) = sa; - } else - SCARG(&bsa, name) = NULL; - SCARG(&bsa, namelen) = namlen; + error = linux_get_sa(l, SCARG(uap, s), &nam, SCARG(uap, name), + SCARG(uap, namelen)); + if (error) + return (error); - return (sys_bind(l, &bsa, retval)); + return do_sys_bind(l, SCARG(uap, s), nam); } int @@ -1479,60 +1397,66 @@ linux_sys_getpeername(l, v, retval) } /* - * Copy the osockaddr structure pointed to by osa to kernel, adjust - * family and convert to sockaddr, allocate stackgap and put the - * the converted structure there, address on stackgap returned in sap. + * Copy the osockaddr structure pointed to by osa to mbuf, adjust + * family and convert to sockaddr. */ static int -linux_sa_get(l, s, sgp, sap, osa, osalen) - struct lwp *l; - int s; - void **sgp; - struct sockaddr **sap; - const struct osockaddr *osa; - socklen_t *osalen; +linux_get_sa(struct lwp *l, int s, struct mbuf **mp, const struct osockaddr *osa, int salen) { - int error=0, bdom; - struct sockaddr *sa, *usa; - struct osockaddr *kosa = (struct osockaddr *) &sa; - struct proc *p = l->l_proc; - socklen_t alloclen; -#ifdef INET6 - int oldv6size; - struct sockaddr_in6 *sin6; -#endif + int error, bdom; + struct sockaddr *sa; + struct osockaddr *kosa; + struct mbuf *m; - if (*osalen < 2 || *osalen > UCHAR_MAX || !osa) { - DPRINTF(("bad osa=%p osalen=%d\n", osa, *osalen)); - return (EINVAL); + if (salen == 1 || salen > UCHAR_MAX) { + DPRINTF(("bad osa=%p salen=%d\n", osa, salen)); + return EINVAL; } - alloclen = *osalen; -#ifdef INET6 - oldv6size = 0; - /* - * Check for old (pre-RFC2553) sockaddr_in6. We may accept it - * if it's a v4-mapped address, so reserve the proper space - * for it. - */ - if (alloclen == sizeof (struct sockaddr_in6) - sizeof (u_int32_t)) { - alloclen = sizeof (struct sockaddr_in6); - oldv6size = 1; - } -#endif + /* We'll need the address in an mbuf later, so copy into one here */ + m = m_get(M_WAIT, MT_SONAME); + if (salen > MLEN) + MEXTMALLOC(m, salen, M_WAITOK); - kosa = (struct osockaddr *) malloc(alloclen, M_TEMP, M_WAITOK); + m->m_len = salen; - if ((error = copyin(osa, (void *) kosa, *osalen))) { - DPRINTF(("error copying osa %d\n", error)); - goto out; + if (salen == 0) + return 0; + + kosa = mtod(m, void *); + if ((error = copyin(osa, kosa, salen))) { + DPRINTF(("error %d copying osa %p len %d\n", + error, osa, salen)); + goto bad; } + if (KTRPOINT(l->l_proc, KTR_USER)) + ktrkuser(l, "linux sockaddr", kosa, salen); bdom = linux_to_bsd_domain(kosa->sa_family); if (bdom == -1) { DPRINTF(("bad linux family=%d\n", kosa->sa_family)); error = EINVAL; - goto out; + goto bad; + } + + /* + * If the family is unspecified, use address family of the socket. + * This avoid triggering strict family checks in netinet/in_pcb.c et.al. + */ + if (bdom == AF_UNSPEC) { + struct file *fp; + struct socket *so; + + /* getsock() will use the descriptor for us */ + if ((error = getsock(l->l_proc->p_fd, s, &fp)) != 0) + goto bad; + + so = (struct socket *)fp->f_data; + bdom = so->so_proto->pr_domain->dom_family; + + FILE_UNUSE(fp, l); + + DPRINTF(("AF_UNSPEC family adjusted to %d\n", bdom)); } #ifdef INET6 @@ -1543,16 +1467,15 @@ linux_sa_get(l, s, sgp, sap, osa, osalen) * * Still accept addresses for which the scope id is not used. */ - if (oldv6size && bdom == AF_INET6) { - sin6 = (struct sockaddr_in6 *)kosa; - if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || - (!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && - !IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && - !IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr) && - !IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && - !IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))) { - sin6->sin6_scope_id = 0; - } else { + if (bdom == AF_INET6 && salen == sizeof (struct sockaddr_in6) - sizeof (u_int32_t)) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)kosa; + if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) && + (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) || + IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) || + IN6_IS_ADDR_V4COMPAT(&sin6->sin6_addr) || + IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || + IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr))) { + struct proc *p = l->l_proc; int uid = l->l_cred ? kauth_cred_geteuid(l->l_cred) : -1; log(LOG_DEBUG, @@ -1560,40 +1483,23 @@ linux_sa_get(l, s, sgp, sap, osa, osalen) "sockaddr_in6 rejected", p->p_pid, p->p_comm, uid); error = EINVAL; - goto out; + goto bad; } + salen = sizeof (struct sockaddr_in6); + sin6->sin6_scope_id = 0; } #endif - /* - * If the family is unspecified, use address family of the - * socket. This avoid triggering COMPAT_43 struct socket family check - * in sockargs() on little-endian machines, and strict family checks - * in netinet/in_pcb.c et.al. - */ - if (bdom == AF_UNSPEC) { - struct file *fp; - struct socket *so; - - /* getsock() will use the descriptor for us */ - if ((error = getsock(p->p_fd, s, &fp)) != 0) - goto out; - - so = (struct socket *)fp->f_data; - bdom = so->so_proto->pr_domain->dom_family; - - FILE_UNUSE(fp, l); - - DPRINTF(("AF_UNSPEC family adjusted to %d\n", bdom)); - } - - if (bdom == AF_INET) { - alloclen = sizeof(struct sockaddr_in); - } + if (bdom == AF_INET) + salen = sizeof(struct sockaddr_in); sa = (struct sockaddr *) kosa; sa->sa_family = bdom; - sa->sa_len = alloclen; + sa->sa_len = salen; + m->m_len = salen; + if (KTRPOINT(l->l_proc, KTR_USER)) + ktrkuser(l, "new sockaddr", kosa, salen); + #ifdef DEBUG_LINUX DPRINTF(("family %d, len = %d [ ", sa->sa_family, sa->sa_len)); for (bdom = 0; bdom < sizeof(sa->sa_data); bdom++) @@ -1601,23 +1507,12 @@ linux_sa_get(l, s, sgp, sap, osa, osalen) DPRINTF(("\n")); #endif - usa = (struct sockaddr *) stackgap_alloc(p, sgp, alloclen); - if (!usa) { - error = ENOMEM; - goto out; - } - - if ((error = copyout(sa, usa, alloclen))) { - DPRINTF(("error copying out socket %d\n", error)); - goto out; - } - - *sap = usa; + *mp = m; + return 0; - out: - *osalen = alloclen; - free(kosa, M_TEMP); - return (error); + bad: + m_free(m); + return error; } static int @@ -1634,7 +1529,7 @@ linux_sa_put(osa) */ len = sizeof(sa.sa_len) + sizeof(sa.sa_family); - error = copyin((void *) osa, (void *) &sa, len); + error = copyin(osa, &sa, len); if (error) return (error); |
