svn commit: r228038 - stable/8/sys/kern

Konstantin Belousov kib at FreeBSD.org
Sun Nov 27 19:36:37 UTC 2011


Author: kib
Date: Sun Nov 27 19:36:36 2011
New Revision: 228038
URL: http://svn.freebsd.org/changeset/base/228038

Log:
  MFC r227485:
  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

Modified:
  stable/8/sys/kern/sys_generic.c
Directory Properties:
  stable/8/sys/   (props changed)

Modified: stable/8/sys/kern/sys_generic.c
==============================================================================
--- stable/8/sys/kern/sys_generic.c	Sun Nov 27 19:13:45 2011	(r228037)
+++ stable/8/sys/kern/sys_generic.c	Sun Nov 27 19:36:36 2011	(r228038)
@@ -829,6 +829,54 @@ select(struct thread *td, struct select_
 	    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)
@@ -843,14 +891,26 @@ kern_select(struct thread *td, int nd, f
 	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


More information about the svn-src-all mailing list