Skip to content

Commit 589379e

Browse files
[3.13] gh-160898: Reject null bytes in socket.getaddrinfo() host and port (GH-160899)
(cherry picked from commit 131a3a6) (cherry picked from commit ee60d5d) Co-authored-by: Seth Larson <seth@python.org> Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 42155a0 commit 589379e

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

‎Doc/library/socket.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,9 @@ The :mod:`socket` module also offers various network-related services:
943943
for IPv6 multicast addresses, string representing an address will not
944944
contain ``%scope_id`` part.
945945

946+
.. versionchanged:: next
947+
Null characters are now rejected in the *host* and *port* parameters.
948+
946949
.. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
947950

948951
.. function:: getfqdn([name])

‎Lib/test/test_socket.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,6 +2026,39 @@ def test_getnameinfo_ipv6_scopeid_numeric(self):
20262026
nameinfo = socket.getnameinfo(sockaddr, socket.NI_NUMERICHOST | socket.NI_NUMERICSERV)
20272027
self.assertEqual(nameinfo, ('ff02::1de:c0:face:8d%' + str(ifindex), '1234'))
20282028

2029+
def test_getaddrinfo_null_bytes(self):
2030+
# getaddrinfo() must reject embedded null bytes instead of silently
2031+
# truncating the host or port at the C boundary. The checks run
2032+
# before any name resolution, so no network access is needed.
2033+
hosts = [
2034+
# embedded
2035+
'local\0host', b'local\0host',
2036+
# trailing
2037+
'localhost\0', b'localhost\0',
2038+
# leading (truncates to '')
2039+
'\0localhost', b'\0localhost',
2040+
'\0', b'\0',
2041+
# non-ASCII, goes through IDNA
2042+
'b\xfccher\0.example',
2043+
]
2044+
for host in hosts:
2045+
with self.subTest(host=host):
2046+
with self.assertRaisesRegex(ValueError, 'host'):
2047+
socket.getaddrinfo(host, 80)
2048+
2049+
ports = [
2050+
'8\x000',
2051+
b'8\x000',
2052+
'http\0',
2053+
b'http\0',
2054+
'\0',
2055+
b'\0'
2056+
]
2057+
for port in ports:
2058+
with self.subTest(port=port):
2059+
with self.assertRaisesRegex(ValueError, 'port'):
2060+
socket.getaddrinfo("localhost", port)
2061+
20292062
def test_str_for_enums(self):
20302063
# Make sure that the AF_* and SOCK_* constants have enum-like string
20312064
# reprs.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.. cve: CVE-2026-116566
2+
3+
:func:`socket.getaddrinfo` now rejects null bytes within *host* and *port*
4+
parameters as this previously caused the values to be silently truncated.

‎Modules/socketmodule.c‎

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6755,6 +6755,7 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
67556755
PyObject *pobj = (PyObject *)NULL;
67566756
PyObject *pstr = NULL;
67576757
const char *hptr, *pptr;
6758+
Py_ssize_t hlen = 0, plen = 0;
67586759
int family, socktype, protocol, flags;
67596760
int error;
67606761
PyObject *all = (PyObject *)NULL;
@@ -6775,33 +6776,46 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
67756776
return NULL;
67766777
assert(PyBytes_Check(idna));
67776778
hptr = PyBytes_AS_STRING(idna);
6779+
hlen = PyBytes_GET_SIZE(idna);
67786780
} else if (PyBytes_Check(hobj)) {
6779-
hptr = PyBytes_AsString(hobj);
6781+
hptr = PyBytes_AS_STRING(hobj);
6782+
hlen = PyBytes_GET_SIZE(hobj);
67806783
} else {
67816784
PyErr_SetString(PyExc_TypeError,
67826785
"getaddrinfo() argument 1 must be string or None");
67836786
return NULL;
67846787
}
6788+
if (hptr != NULL && strlen(hptr) != (size_t)hlen) {
6789+
PyErr_SetString(PyExc_ValueError,
6790+
"getaddrinfo() host must not contain null characters");
6791+
goto err;
6792+
}
67856793
if (PyLong_CheckExact(pobj)) {
67866794
pstr = PyObject_Str(pobj);
67876795
if (pstr == NULL)
67886796
goto err;
67896797
assert(PyUnicode_Check(pstr));
6790-
pptr = PyUnicode_AsUTF8(pstr);
6798+
pptr = PyUnicode_AsUTF8AndSize(pstr, &plen);
67916799
if (pptr == NULL)
67926800
goto err;
67936801
} else if (PyUnicode_Check(pobj)) {
6794-
pptr = PyUnicode_AsUTF8(pobj);
6802+
pptr = PyUnicode_AsUTF8AndSize(pobj, &plen);
67956803
if (pptr == NULL)
67966804
goto err;
67976805
} else if (PyBytes_Check(pobj)) {
67986806
pptr = PyBytes_AS_STRING(pobj);
6807+
plen = PyBytes_GET_SIZE(pobj);
67996808
} else if (pobj == Py_None) {
68006809
pptr = (char *)NULL;
68016810
} else {
68026811
PyErr_SetString(PyExc_OSError, "Int or String expected");
68036812
goto err;
68046813
}
6814+
if (pptr != NULL && strlen(pptr) != (size_t)plen) {
6815+
PyErr_SetString(PyExc_ValueError,
6816+
"getaddrinfo() port must not contain null characters");
6817+
goto err;
6818+
}
68056819
#if defined(__APPLE__) && defined(AI_NUMERICSERV)
68066820
if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) {
68076821
/* On OSX up to at least OSX 10.8 getaddrinfo crashes

0 commit comments

Comments
 (0)