Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 13 Nov 2011 10:28:01 +0000 (UTC)
From:      Konstantin Belousov <kib@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r227485 - head/sys/kern
Message-ID:  <201111131028.pADAS1Ip087280@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: kib
Date: Sun Nov 13 10:28:01 2011
New Revision: 227485
URL: http://svn.freebsd.org/changeset/base/227485

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

Modified:
  head/sys/kern/sys_generic.c

Modified: head/sys/kern/sys_generic.c
==============================================================================
--- head/sys/kern/sys_generic.c	Sun Nov 13 06:39:49 2011	(r227484)
+++ head/sys/kern/sys_generic.c	Sun Nov 13 10:28:01 2011	(r227485)
@@ -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



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201111131028.pADAS1Ip087280>