From: Neal Norwitz <nnorwitz@gmail.com>
Date: Tue, 7 Feb 2006 07:04:46 +0000 (+0000)
Subject: Bug #876637, prevent stack corruption when socket descriptor
X-Git-Tag: v2.5a0~711
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=082b2df33f4a916392dca2260fdcdd1cbe3a8329;p=python

Bug #876637, prevent stack corruption when socket descriptor
is larger than FD_SETSIZE.

This can only be acheived with ulimit -n SOME_NUMBER_BIGGER_THAN_FD_SETSIZE
which is typically only available to root.  Since this wouldn't normally
be run in a test (ie, run as root), it doesn't seem too worthwhile to
add a normal test.  The bug report has one version of a test.  I've
written another.  Not sure what the best thing to do is.

Do the check before calling internal_select() because we can't set
an error in between Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS.
This seemed the clearest solution, ie handle before calling internal_select()
rather than inside.  Plus there is at least one place outside
of internal_select() that needed to be handled.

Will backport.
---

diff --git a/Misc/NEWS b/Misc/NEWS
index d998434bea..1e4ad2a825 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -216,6 +216,9 @@ Core and builtins
 Extension Modules
 -----------------
 
+- Bug #876637, prevent stack corruption when socket descriptor
+  is larger than FD_SETSIZE.
+
 - Patch #1407135, bug #1424041: harmonize mmap behavior of anonymous memory.
   mmap.mmap(-1, size) now returns anonymous memory in both Unix and Windows.
   mmap.mmap(0, size) should not be used on Windows for anonymous memory.
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index b8097e1003..6448cb00ae 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -74,6 +74,7 @@ typedef enum {
 	SOCKET_IS_BLOCKING,
 	SOCKET_HAS_TIMED_OUT,
 	SOCKET_HAS_BEEN_CLOSED,
+	SOCKET_INVALID,
 	SOCKET_OPERATION_OK
 } timeout_state;
 
@@ -272,6 +273,9 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file)
 		} else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
 			PyErr_SetString(PySSLErrorObject, "Underlying socket has been closed.");
 			goto fail;
+		} else if (sockstate == SOCKET_INVALID) {
+			PyErr_SetString(PySSLErrorObject, "Underlying socket too large for select().");
+			goto fail;
 		} else if (sockstate == SOCKET_IS_NONBLOCKING) {
 			break;
 		}
@@ -372,6 +376,10 @@ check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing)
 	if (s->sock_fd < 0)
 		return SOCKET_HAS_BEEN_CLOSED;
 
+	/* Guard against socket too large for select*/
+	if (s->sock_fd >= FD_SETSIZE)
+		return SOCKET_INVALID;
+
 	/* Construct the arguments to select */
 	tv.tv_sec = (int)s->sock_timeout;
 	tv.tv_usec = (int)((s->sock_timeout - tv.tv_sec) * 1e6);
@@ -409,6 +417,9 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
 	} else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
 		PyErr_SetString(PySSLErrorObject, "Underlying socket has been closed.");
 		return NULL;
+	} else if (sockstate == SOCKET_INVALID) {
+		PyErr_SetString(PySSLErrorObject, "Underlying socket too large for select().");
+		return NULL;
 	}
 	do {
 		err = 0;
@@ -467,6 +478,9 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
 		PyErr_SetString(PySSLErrorObject, "The read operation timed out");
 		Py_DECREF(buf);
 		return NULL;
+	} else if (sockstate == SOCKET_INVALID) {
+		PyErr_SetString(PySSLErrorObject, "Underlying socket too large for select().");
+		return NULL;
 	}
 	do {
 		err = 0;
diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c
index cdefc58d78..1ec3166e1b 100644
--- a/Modules/socketmodule.c
+++ b/Modules/socketmodule.c
@@ -395,6 +395,16 @@ static int taskwindow;
    there has to be a circular reference. */
 static PyTypeObject sock_type;
 
+/* Can we call select() with this socket without a buffer overrun? */
+#define IS_SELECTABLE(s) ((s)->sock_fd < FD_SETSIZE)
+
+static PyObject*
+select_error(void)
+{
+	PyErr_SetString(socket_error, "unable to select on socket");
+	return NULL;
+}
+
 /* Convenience function to raise an error according to errno
    and return a NULL pointer from a function. */
 
@@ -1408,6 +1418,9 @@ sock_accept(PySocketSockObject *s)
 	newfd = -1;
 #endif
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 	Py_BEGIN_ALLOW_THREADS
 	timeout = internal_select(s, 0);
 	if (!timeout)
@@ -1736,7 +1749,8 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen,
 #ifdef MS_WINDOWS
 
 	if (s->sock_timeout > 0.0) {
-		if (res < 0 && WSAGetLastError() == WSAEWOULDBLOCK) {
+		if (res < 0 && WSAGetLastError() == WSAEWOULDBLOCK &&
+		    IS_SELECTABLE(s)) {
 			/* This is a mess.  Best solution: trust select */
 			fd_set fds;
 			fd_set fds_exc;
@@ -1781,7 +1795,7 @@ internal_connect(PySocketSockObject *s, struct sockaddr *addr, int addrlen,
 #else
 
 	if (s->sock_timeout > 0.0) {
-		if (res < 0 && errno == EINPROGRESS) {
+		if (res < 0 && errno == EINPROGRESS && IS_SELECTABLE(s)) {
 			timeout = internal_select(s, 1);
 			res = connect(s->sock_fd, addr, addrlen);
 			if (res < 0 && errno == EISCONN)
@@ -2084,6 +2098,9 @@ sock_recv(PySocketSockObject *s, PyObject *args)
 	if (buf == NULL)
 		return NULL;
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 #ifndef __VMS
 	Py_BEGIN_ALLOW_THREADS
 	timeout = internal_select(s, 0);
@@ -2177,6 +2194,9 @@ sock_recvfrom(PySocketSockObject *s, PyObject *args)
 	if (buf == NULL)
 		return NULL;
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 	Py_BEGIN_ALLOW_THREADS
 	memset(&addrbuf, 0, addrlen);
 	timeout = internal_select(s, 0);
@@ -2238,6 +2258,9 @@ sock_send(PySocketSockObject *s, PyObject *args)
 	if (!PyArg_ParseTuple(args, "s#|i:send", &buf, &len, &flags))
 		return NULL;
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 #ifndef __VMS
 	Py_BEGIN_ALLOW_THREADS
 	timeout = internal_select(s, 1);
@@ -2303,6 +2326,9 @@ sock_sendall(PySocketSockObject *s, PyObject *args)
 	if (!PyArg_ParseTuple(args, "s#|i:sendall", &buf, &len, &flags))
 		return NULL;
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 	Py_BEGIN_ALLOW_THREADS
 	do {
 		timeout = internal_select(s, 1);
@@ -2357,6 +2383,9 @@ sock_sendto(PySocketSockObject *s, PyObject *args)
 	if (!getsockaddrarg(s, addro, &addr, &addrlen))
 		return NULL;
 
+	if (!IS_SELECTABLE(s))
+		return select_error();
+
 	Py_BEGIN_ALLOW_THREADS
 	timeout = internal_select(s, 1);
 	if (!timeout)