Skip to content

Commit 1481508

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

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
@@ -992,6 +992,9 @@ The :mod:`socket` module also offers various network-related services:
992992
for IPv6 multicast addresses, string representing an address will not
993993
contain ``%scope_id`` part.
994994

995+
.. versionchanged:: next
996+
Null characters are now rejected in the *host* and *port* parameters.
997+
995998
.. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
996999

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

‎Lib/test/test_socket.py‎

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

1926+
def test_getaddrinfo_null_bytes(self):
1927+
# getaddrinfo() must reject embedded null bytes instead of silently
1928+
# truncating the host or port at the C boundary. The checks run
1929+
# before any name resolution, so no network access is needed.
1930+
hosts = [
1931+
# embedded
1932+
'local\0host', b'local\0host',
1933+
# trailing
1934+
'localhost\0', b'localhost\0',
1935+
# leading (truncates to '')
1936+
'\0localhost', b'\0localhost',
1937+
'\0', b'\0',
1938+
# non-ASCII, goes through IDNA
1939+
'b\xfccher\0.example',
1940+
]
1941+
for host in hosts:
1942+
with self.subTest(host=host):
1943+
with self.assertRaisesRegex(ValueError, 'host'):
1944+
socket.getaddrinfo(host, 80)
1945+
1946+
ports = [
1947+
'8\x000',
1948+
b'8\x000',
1949+
'http\0',
1950+
b'http\0',
1951+
'\0',
1952+
b'\0'
1953+
]
1954+
for port in ports:
1955+
with self.subTest(port=port):
1956+
with self.assertRaisesRegex(ValueError, 'port'):
1957+
socket.getaddrinfo("localhost", port)
1958+
19261959
def test_str_for_enums(self):
19271960
# Make sure that the AF_* and SOCK_* constants have enum-like string
19281961
# 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
@@ -6746,6 +6746,7 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
67466746
PyObject *pobj = (PyObject *)NULL;
67476747
PyObject *pstr = NULL;
67486748
const char *hptr, *pptr;
6749+
Py_ssize_t hlen = 0, plen = 0;
67496750
int family, socktype, protocol, flags;
67506751
int error;
67516752
PyObject *all = (PyObject *)NULL;
@@ -6766,33 +6767,46 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
67666767
return NULL;
67676768
assert(PyBytes_Check(idna));
67686769
hptr = PyBytes_AS_STRING(idna);
6770+
hlen = PyBytes_GET_SIZE(idna);
67696771
} else if (PyBytes_Check(hobj)) {
6770-
hptr = PyBytes_AsString(hobj);
6772+
hptr = PyBytes_AS_STRING(hobj);
6773+
hlen = PyBytes_GET_SIZE(hobj);
67716774
} else {
67726775
PyErr_SetString(PyExc_TypeError,
67736776
"getaddrinfo() argument 1 must be string or None");
67746777
return NULL;
67756778
}
6779+
if (hptr != NULL && strlen(hptr) != (size_t)hlen) {
6780+
PyErr_SetString(PyExc_ValueError,
6781+
"getaddrinfo() host must not contain null characters");
6782+
goto err;
6783+
}
67766784
if (PyLong_CheckExact(pobj)) {
67776785
pstr = PyObject_Str(pobj);
67786786
if (pstr == NULL)
67796787
goto err;
67806788
assert(PyUnicode_Check(pstr));
6781-
pptr = PyUnicode_AsUTF8(pstr);
6789+
pptr = PyUnicode_AsUTF8AndSize(pstr, &plen);
67826790
if (pptr == NULL)
67836791
goto err;
67846792
} else if (PyUnicode_Check(pobj)) {
6785-
pptr = PyUnicode_AsUTF8(pobj);
6793+
pptr = PyUnicode_AsUTF8AndSize(pobj, &plen);
67866794
if (pptr == NULL)
67876795
goto err;
67886796
} else if (PyBytes_Check(pobj)) {
67896797
pptr = PyBytes_AS_STRING(pobj);
6798+
plen = PyBytes_GET_SIZE(pobj);
67906799
} else if (pobj == Py_None) {
67916800
pptr = (char *)NULL;
67926801
} else {
67936802
PyErr_SetString(PyExc_OSError, "Int or String expected");
67946803
goto err;
67956804
}
6805+
if (pptr != NULL && strlen(pptr) != (size_t)plen) {
6806+
PyErr_SetString(PyExc_ValueError,
6807+
"getaddrinfo() port must not contain null characters");
6808+
goto err;
6809+
}
67966810
#if defined(__APPLE__) && defined(AI_NUMERICSERV)
67976811
if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) {
67986812
/* On OSX up to at least OSX 10.8 getaddrinfo crashes

0 commit comments

Comments
 (0)