diff options
author | kib <kib@FreeBSD.org> | 2011-11-13 10:28:01 +0000 |
---|---|---|
committer | kib <kib@FreeBSD.org> | 2011-11-13 10:28:01 +0000 |
commit | ca682488f300f9d4de73109f4b4ff0cff1fc3cc3 (patch) | |
tree | 9205e85c565352e45d99bcf206234debf4068147 /sys/kern/sys_generic.c | |
parent | 6280e6aab30ab8155db6f5501b64a45218baf2e1 (diff) | |
download | FreeBSD-src-ca682488f300f9d4de73109f4b4ff0cff1fc3cc3.zip FreeBSD-src-ca682488f300f9d4de73109f4b4ff0cff1fc3cc3.tar.gz |
To limit amount of the kernel memory allocated, and to optimize the
iteration over the fdsets, kern_select() limits the length of the
fdsets copied in by the last valid file descriptor index. If any bit
is set in a mask above the limit, current implementation ignores the
filedescriptor, instead of returning EBADF.
Fix the issue by scanning the tails of fdset before entering the
select loop and returning EBADF if any bit above last valid
filedescriptor index is set. The performance impact of the additional
check is only imposed on the (somewhat) buggy applications that pass
bad file descriptors to select(2) or pselect(2).
PR: kern/155606, kern/162379
Discussed with: cognet, glebius
Tested by: andreast (powerpc, all 64/32bit ABI combinations, big-endian),
marius (sparc64, big-endian)
MFC after: 2 weeks
Diffstat (limited to 'sys/kern/sys_generic.c')
-rw-r--r-- | sys/kern/sys_generic.c | 66 |
1 files changed, 63 insertions, 3 deletions
diff --git a/sys/kern/sys_generic.c b/sys/kern/sys_generic.c index 8d4ba09..3be2689 100644 --- a/sys/kern/sys_generic.c +++ b/sys/kern/sys_generic.c @@ -831,6 +831,54 @@ sys_select(struct thread *td, struct select_args *uap) NFDBITS)); } +/* + * In the unlikely case when user specified n greater then the last + * open file descriptor, check that no bits are set after the last + * valid fd. We must return EBADF if any is set. + * + * There are applications that rely on the behaviour. + * + * nd is fd_lastfile + 1. + */ +static int +select_check_badfd(fd_set *fd_in, int nd, int ndu, int abi_nfdbits) +{ + char *addr, *oaddr; + int b, i, res; + uint8_t bits; + + if (nd >= ndu || fd_in == NULL) + return (0); + + oaddr = NULL; + bits = 0; /* silence gcc */ + for (i = nd; i < ndu; i++) { + b = i / NBBY; +#if BYTE_ORDER == LITTLE_ENDIAN + addr = (char *)fd_in + b; +#else + addr = (char *)fd_in; + if (abi_nfdbits == NFDBITS) { + addr += rounddown(b, sizeof(fd_mask)) + + sizeof(fd_mask) - 1 - b % sizeof(fd_mask); + } else { + addr += rounddown(b, sizeof(uint32_t)) + + sizeof(uint32_t) - 1 - b % sizeof(uint32_t); + } +#endif + if (addr != oaddr) { + res = fubyte(addr); + if (res == -1) + return (EFAULT); + oaddr = addr; + bits = res; + } + if ((bits & (1 << (i % NBBY))) != 0) + return (EBADF); + } + return (0); +} + int kern_select(struct thread *td, int nd, fd_set *fd_in, fd_set *fd_ou, fd_set *fd_ex, struct timeval *tvp, int abi_nfdbits) @@ -845,14 +893,26 @@ kern_select(struct thread *td, int nd, fd_set *fd_in, fd_set *fd_ou, fd_mask s_selbits[howmany(2048, NFDBITS)]; fd_mask *ibits[3], *obits[3], *selbits, *sbp; struct timeval atv, rtv, ttv; - int error, timo; + int error, lf, ndu, timo; u_int nbufbytes, ncpbytes, ncpubytes, nfdbits; if (nd < 0) return (EINVAL); fdp = td->td_proc->p_fd; - if (nd > fdp->fd_lastfile + 1) - nd = fdp->fd_lastfile + 1; + ndu = nd; + lf = fdp->fd_lastfile; + if (nd > lf + 1) + nd = lf + 1; + + error = select_check_badfd(fd_in, nd, ndu, abi_nfdbits); + if (error != 0) + return (error); + error = select_check_badfd(fd_ou, nd, ndu, abi_nfdbits); + if (error != 0) + return (error); + error = select_check_badfd(fd_ex, nd, ndu, abi_nfdbits); + if (error != 0) + return (error); /* * Allocate just enough bits for the non-null fd_sets. Use the |