diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 8a3793b9072b78..7d1591f241c244 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1297,6 +1297,10 @@ def testGetServBy(self): self.assertRaises(OverflowError, socket.getservbyport, -1) self.assertRaises(OverflowError, socket.getservbyport, 65536) + def testGetProtoByName(self): + self.assertEqual(socket.getprotobyname('tcp'), 6) + self.assertRaises(OSError, socket.getprotobyname, 'non-existent proto') + def testDefaultTimeout(self): # Testing default timeout # The default timeout should initially be None diff --git a/Misc/NEWS.d/next/Library/2025-04-17-00-14-41.gh-issue-127081.aEM3Hk.rst b/Misc/NEWS.d/next/Library/2025-04-17-00-14-41.gh-issue-127081.aEM3Hk.rst new file mode 100644 index 00000000000000..225f3be417ce0e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-17-00-14-41.gh-issue-127081.aEM3Hk.rst @@ -0,0 +1,2 @@ +Fix thread safety issues with getservbyname, getservbyport, and +getprotobyname. diff --git a/Misc/NEWS.d/next/Library/2025-04-21-01-00-47.gh-issue-127081.o56pE1.rst b/Misc/NEWS.d/next/Library/2025-04-21-01-00-47.gh-issue-127081.o56pE1.rst new file mode 100644 index 00000000000000..9e82771130b45a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-04-21-01-00-47.gh-issue-127081.o56pE1.rst @@ -0,0 +1,3 @@ +Fix libc thread safety issues with :mod:`socket` by replacing +``getservbyname``, ``getservbyport``, and ``getprotobyname`` with ``*_r`` +re-entrant versions. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index f55a9583b8586a..dbdf31e1828667 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -6282,7 +6282,7 @@ Return the true host name, a list of aliases, and a list of IP addresses,\n\ for a host. The host argument is a string giving a host name or IP number."); #endif -#ifdef HAVE_GETSERVBYNAME +#if defined(HAVE_GETSERVBYNAME_R) || defined (HAVE_GETSERVBYNAME) /* Python interface to getservbyname(name). This only returns the port number, since the other info is already known or not useful (like the list of aliases). */ @@ -6292,6 +6292,12 @@ static PyObject * socket_getservbyname(PyObject *self, PyObject *args) { const char *name, *proto=NULL; +#ifdef HAVE_GETSERVBYNAME_R + struct servent entry; + /* TODO: The man page says 1024 is usually enough, start with that and + retry if insufficient? */ + char buf[16384]; +#endif struct servent *sp; if (!PyArg_ParseTuple(args, "s|s:getservbyname", &name, &proto)) return NULL; @@ -6301,7 +6307,11 @@ socket_getservbyname(PyObject *self, PyObject *args) } Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_GETSERVBYNAME_R + getservbyname_r(name, proto, &entry, buf, sizeof(buf), &sp); +#else sp = getservbyname(name, proto); +#endif Py_END_ALLOW_THREADS if (sp == NULL) { PyErr_SetString(PyExc_OSError, "service/proto not found"); @@ -6318,7 +6328,7 @@ The optional protocol name, if given, should be 'tcp' or 'udp',\n\ otherwise any protocol will match."); #endif -#ifdef HAVE_GETSERVBYPORT +#if defined(HAVE_GETSERVBYPORT_R) || defined (HAVE_GETSERVBYPORT) /* Python interface to getservbyport(port). This only returns the service name, since the other info is already known or not useful (like the list of aliases). */ @@ -6329,6 +6339,12 @@ socket_getservbyport(PyObject *self, PyObject *args) { int port; const char *proto=NULL; +#ifdef HAVE_GETSERVBYPORT_R + struct servent entry; + /* TODO: The man page says 1024 is usually enough, start with that and + retry if insufficient? */ + char buf[16384]; +#endif struct servent *sp; if (!PyArg_ParseTuple(args, "i|s:getservbyport", &port, &proto)) return NULL; @@ -6344,7 +6360,11 @@ socket_getservbyport(PyObject *self, PyObject *args) } Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_GETSERVBYPORT_R + getservbyport_r(htons((short)port), proto, &entry, buf, sizeof(buf), &sp); +#else sp = getservbyport(htons((short)port), proto); +#endif Py_END_ALLOW_THREADS if (sp == NULL) { PyErr_SetString(PyExc_OSError, "port/proto not found"); @@ -6361,7 +6381,7 @@ The optional protocol name, if given, should be 'tcp' or 'udp',\n\ otherwise any protocol will match."); #endif -#ifdef HAVE_GETPROTOBYNAME +#if defined(HAVE_GETPROTOBYNAME_R) || defined (HAVE_GETPROTOBYNAME) /* Python interface to getprotobyname(name). This only returns the protocol number, since the other info is already known or not useful (like the list of aliases). */ @@ -6371,11 +6391,21 @@ static PyObject * socket_getprotobyname(PyObject *self, PyObject *args) { const char *name; +#ifdef HAVE_GETPROTOBYNAME_R + struct protoent entry; + /* TODO: The man page says 1024 is usually enough, start with that and + retry if insufficient? */ + char buf[16384]; +#endif struct protoent *sp; if (!PyArg_ParseTuple(args, "s:getprotobyname", &name)) return NULL; Py_BEGIN_ALLOW_THREADS +#ifdef HAVE_GETPROTOBYNAME_R + getprotobyname_r(name, &entry, buf, sizeof(buf), &sp); +#else sp = getprotobyname(name); +#endif Py_END_ALLOW_THREADS if (sp == NULL) { PyErr_SetString(PyExc_OSError, "protocol not found"); @@ -7428,15 +7458,15 @@ static PyMethodDef socket_methods[] = { {"sethostname", socket_sethostname, METH_VARARGS, sethostname_doc}, #endif -#ifdef HAVE_GETSERVBYNAME +#if defined(HAVE_GETSERVBYNAME_R) || defined (HAVE_GETSERVBYNAME) {"getservbyname", socket_getservbyname, METH_VARARGS, getservbyname_doc}, #endif -#ifdef HAVE_GETSERVBYPORT +#if defined(HAVE_GETSERVBYPORT_R) || defined (HAVE_GETSERVBYPORT) {"getservbyport", socket_getservbyport, METH_VARARGS, getservbyport_doc}, #endif -#ifdef HAVE_GETPROTOBYNAME +#if defined (HAVE_GETPROTOBYNAME_R) || defined (HAVE_GETPROTOBYNAME) {"getprotobyname", socket_getprotobyname, METH_VARARGS, getprotobyname_doc}, #endif diff --git a/configure b/configure index decb8f2449d162..0083730ae3c535 100755 --- a/configure +++ b/configure @@ -22615,6 +22615,129 @@ fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getservbyname_r" >&5 +printf %s "checking for getservbyname_r... " >&6; } +if test ${ac_cv_func_getservbyname_r+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *x=getservbyname_r + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_getservbyname_r=yes +else case e in #( + e) ac_cv_func_getservbyname_r=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_getservbyname_r" >&5 +printf "%s\n" "$ac_cv_func_getservbyname_r" >&6; } + if test "x$ac_cv_func_getservbyname_r" = xyes +then : + +printf "%s\n" "#define HAVE_GETSERVBYNAME_R 1" >>confdefs.h + +fi + + + + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getservbyport_r" >&5 +printf %s "checking for getservbyport_r... " >&6; } +if test ${ac_cv_func_getservbyport_r+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *x=getservbyport_r + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_getservbyport_r=yes +else case e in #( + e) ac_cv_func_getservbyport_r=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_getservbyport_r" >&5 +printf "%s\n" "$ac_cv_func_getservbyport_r" >&6; } + if test "x$ac_cv_func_getservbyport_r" = xyes +then : + +printf "%s\n" "#define HAVE_GETSERVBYPORT_R 1" >>confdefs.h + +fi + + + + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for getprotobyname_r" >&5 +printf %s "checking for getprotobyname_r... " >&6; } +if test ${ac_cv_func_getprotobyname_r+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +void *x=getprotobyname_r + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO" +then : + ac_cv_func_getprotobyname_r=yes +else case e in #( + e) ac_cv_func_getprotobyname_r=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_func_getprotobyname_r" >&5 +printf "%s\n" "$ac_cv_func_getprotobyname_r" >&6; } + if test "x$ac_cv_func_getprotobyname_r" = xyes +then : + +printf "%s\n" "#define HAVE_GETPROTOBYNAME_R 1" >>confdefs.h + +fi + + + + + diff --git a/configure.ac b/configure.ac index 004797b5233c20..6a53fe27c27c37 100644 --- a/configure.ac +++ b/configure.ac @@ -5395,6 +5395,9 @@ PY_CHECK_NETDB_FUNC([getservbyport]) PY_CHECK_NETDB_FUNC([gethostbyname]) PY_CHECK_NETDB_FUNC([gethostbyaddr]) PY_CHECK_NETDB_FUNC([getprotobyname]) +PY_CHECK_NETDB_FUNC([getservbyname_r]) +PY_CHECK_NETDB_FUNC([getservbyport_r]) +PY_CHECK_NETDB_FUNC([getprotobyname_r]) dnl PY_CHECK_SOCKET_FUNC(FUNCTION) AC_DEFUN([PY_CHECK_SOCKET_FUNC], [PY_CHECK_FUNC([$1], [ diff --git a/pyconfig.h.in b/pyconfig.h.in index aa086d49e90a5b..fe240cfda71f0b 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -557,6 +557,9 @@ /* Define if you have the 'getprotobyname' function. */ #undef HAVE_GETPROTOBYNAME +/* Define if you have the 'getprotobyname_r' function. */ +#undef HAVE_GETPROTOBYNAME_R + /* Define to 1 if you have the 'getpwent' function. */ #undef HAVE_GETPWENT @@ -587,9 +590,15 @@ /* Define if you have the 'getservbyname' function. */ #undef HAVE_GETSERVBYNAME +/* Define if you have the 'getservbyname_r' function. */ +#undef HAVE_GETSERVBYNAME_R + /* Define if you have the 'getservbyport' function. */ #undef HAVE_GETSERVBYPORT +/* Define if you have the 'getservbyport_r' function. */ +#undef HAVE_GETSERVBYPORT_R + /* Define to 1 if you have the 'getsid' function. */ #undef HAVE_GETSID