From 589379e4f11e07c15269dfc00dae2bd648e2b4da Mon Sep 17 00:00:00 2001 From: Seth Larson Date: Thu, 29 Oct 2026 12:15:29 -0500 Subject: [PATCH] [3.13] gh-160898: Reject null bytes in `socket.getaddrinfo()` host and port (GH-160899) (cherry picked from commit 131a3a66c5f400b588f9fd2732a5ed4e89623ee3) (cherry picked from commit ee60d5db8f1668db57f3fc46b97ecf6cd0e251ac) Co-authored-by: Seth Larson Co-authored-by: Stan Ulbrych --- Doc/library/socket.rst | 3 ++ Lib/test/test_socket.py | 33 +++++++++++++++++++ ...-10-27-16-25-40.gh-issue-160898.epi53X.rst | 4 +++ Modules/socketmodule.c | 20 +++++++++-- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Security/2026-10-27-16-25-40.gh-issue-160898.epi53X.rst diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 471727b49dafa67..839c981e57e58fd 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -943,6 +943,9 @@ The :mod:`socket` module also offers various network-related services: for IPv6 multicast addresses, string representing an address will not contain ``%scope_id`` part. + .. versionchanged:: next + Null characters are now rejected in the *host* and *port* parameters. + .. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs .. function:: getfqdn([name]) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 2eba9dcd2bfe538..fb8c3720511840a 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -2026,6 +2026,39 @@ def test_getnameinfo_ipv6_scopeid_numeric(self): nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV) self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + str(ifindex), '1234')) + def test_getaddrinfo_null_bytes(self): + # getaddrinfo() must reject embedded null bytes instead of silently + # truncating the host or port at the C boundary. The checks run + # before any name resolution, so no network access is needed. + hosts = [ + # embedded + 'local\0host', b'local\0host', + # trailing + 'localhost\0', b'localhost\0', + # leading (truncates to '') + '\0localhost', b'\0localhost', + '\0', b'\0', + # non-ASCII, goes through IDNA + 'b\xfccher\0.example', + ] + for host in hosts: + with self.subTest(host=host): + with self.assertRaisesRegex(ValueError, 'host'): + socket.getaddrinfo(host, 80) + + ports = [ + '8\x000', + b'8\x000', + 'http\0', + b'http\0', + '\0', + b'\0' + ] + for port in ports: + with self.subTest(port=port): + with self.assertRaisesRegex(ValueError, 'port'): + socket.getaddrinfo("localhost", port) + def test_str_for_enums(self): # Make sure that the AF_* and SOCK_* constants have enum-like string # reprs. diff --git a/Misc/NEWS.d/next/Security/2026-10-27-16-25-40.gh-issue-160898.epi53X.rst b/Misc/NEWS.d/next/Security/2026-10-27-16-25-40.gh-issue-160898.epi53X.rst new file mode 100644 index 000000000000000..2eb04454cbd9a7f --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-10-27-16-25-40.gh-issue-160898.epi53X.rst @@ -0,0 +1,4 @@ +.. cve: CVE-2026-116566 + +:func:`socket.getaddrinfo` now rejects null bytes within *host* and *port* +parameters as this previously caused the values to be silently truncated. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index 0968aafd327302a..9b62ef93b868d53 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -6755,6 +6755,7 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs) PyObject *pobj = (PyObject *)NULL; PyObject *pstr = NULL; const char *hptr, *pptr; + Py_ssize_t hlen = 0, plen = 0; int family, socktype, protocol, flags; int error; PyObject *all = (PyObject *)NULL; @@ -6775,33 +6776,46 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs) return NULL; assert(PyBytes_Check(idna)); hptr = PyBytes_AS_STRING(idna); + hlen = PyBytes_GET_SIZE(idna); } else if (PyBytes_Check(hobj)) { - hptr = PyBytes_AsString(hobj); + hptr = PyBytes_AS_STRING(hobj); + hlen = PyBytes_GET_SIZE(hobj); } else { PyErr_SetString(PyExc_TypeError, "getaddrinfo() argument 1 must be string or None"); return NULL; } + if (hptr != NULL && strlen(hptr) != (size_t)hlen) { + PyErr_SetString(PyExc_ValueError, + "getaddrinfo() host must not contain null characters"); + goto err; + } if (PyLong_CheckExact(pobj)) { pstr = PyObject_Str(pobj); if (pstr == NULL) goto err; assert(PyUnicode_Check(pstr)); - pptr = PyUnicode_AsUTF8(pstr); + pptr = PyUnicode_AsUTF8AndSize(pstr, &plen); if (pptr == NULL) goto err; } else if (PyUnicode_Check(pobj)) { - pptr = PyUnicode_AsUTF8(pobj); + pptr = PyUnicode_AsUTF8AndSize(pobj, &plen); if (pptr == NULL) goto err; } else if (PyBytes_Check(pobj)) { pptr = PyBytes_AS_STRING(pobj); + plen = PyBytes_GET_SIZE(pobj); } else if (pobj == Py_None) { pptr = (char *)NULL; } else { PyErr_SetString(PyExc_OSError, "Int or String expected"); goto err; } + if (pptr != NULL && strlen(pptr) != (size_t)plen) { + PyErr_SetString(PyExc_ValueError, + "getaddrinfo() port must not contain null characters"); + goto err; + } #if defined(__APPLE__) && defined(AI_NUMERICSERV) if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) { /* On OSX up to at least OSX 10.8 getaddrinfo crashes