svn commit: r228032 - stable/9/sys/kern

Konstantin Belousov kib at FreeBSD.org
Sun Nov 27 18:49:21 UTC 2011


Author: kib
Date: Sun Nov 27 18:49:16 2011
New Revision: 228032
URL: http://svn.freebsd.org/changeset/base/228032

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
  Approved by:	re (bz)

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

Modified: stable/9/sys/kern/sys_generic.c
==============================================================================
--- stable/9/sys/kern/sys_generic.c	Sun Nov 27 17:51:13 2011	(r228031)
+++ stable/9/sys/kern/sys_generic.c	Sun Nov 27 18:49:16 2011	(r228032)
@@ -831,6 +831,54 @@ sys_select(struct thread *td, struct sel
 	    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, 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-stable mailing list