https://github.com/python/cpython/commit/131a3a66c5f400b588f9fd2732a5ed4e89623ee3 commit: 131a3a66c5f400b588f9fd2732a5ed4e89623ee3 branch: main author: Seth Larson <[email protected]> committer: StanFromIreland <[email protected]> date: 2026-10-29T17:15:29Z summary:
gh-160898: Reject null bytes in `socket.getaddrinfo()` host and port (#160899) Co-authored-by: Stan Ulbrych <[email protected]> files: A Misc/NEWS.d/next/Security/2026-10-27-16-25-40.gh-issue-160898.epi53X.rst M Doc/library/socket.rst M Lib/test/test_socket.py M Modules/socketmodule.c diff --git a/Doc/library/socket.rst b/Doc/library/socket.rst index 054ccc556740c2..5a1cd95da527ea 100644 --- a/Doc/library/socket.rst +++ b/Doc/library/socket.rst @@ -1065,6 +1065,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 51b666bbaba827..b37925af1e2d0f 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -2104,6 +2104,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 00000000000000..2eb04454cbd9a7 --- /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 97146b1d752c3d..4df03284cbdd24 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -7018,6 +7018,7 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *pobj, struct addrinfo *res0 = NULL; PyObject *pstr = NULL; const char *hptr, *pptr; + Py_ssize_t hlen = 0, plen = 0; int error; PyObject *all = (PyObject *)NULL; PyObject *idna = NULL; @@ -7030,13 +7031,20 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *pobj, 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 (PyIndex_Check(pobj)) { pstr = PyNumber_Index(pobj); if (pstr == NULL) @@ -7046,21 +7054,27 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *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 _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
