Skip to content

Commit 2cd1085

Browse files
[3.11] 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 ffbb750 commit 2cd1085

4 files changed

Lines changed: 63 additions & 7 deletions

File tree

‎Doc/library/socket.rst‎

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

862+
.. versionchanged:: next
863+
Null characters are now rejected in the *host* and *port* parameters.
864+
862865
.. function:: getfqdn([name])
863866

864867
Return a fully qualified domain name for *name*. If *name* is omitted or empty,

‎Lib/test/test_socket.py‎

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

1862+
def test_getaddrinfo_null_bytes(self):
1863+
# getaddrinfo() must reject embedded null bytes instead of silently
1864+
# truncating the host or port at the C boundary. The checks run
1865+
# before any name resolution, so no network access is needed.
1866+
hosts = [
1867+
# embedded
1868+
'local\0host', b'local\0host',
1869+
# trailing
1870+
'localhost\0', b'localhost\0',
1871+
# leading (truncates to '')
1872+
'\0localhost', b'\0localhost',
1873+
'\0', b'\0',
1874+
# non-ASCII, goes through IDNA
1875+
'b\xfccher\0.example',
1876+
]
1877+
for host in hosts:
1878+
with self.subTest(host=host):
1879+
with self.assertRaisesRegex(ValueError, 'host'):
1880+
socket.getaddrinfo(host, 80)
1881+
1882+
ports = [
1883+
'8\x000',
1884+
b'8\x000',
1885+
'http\0',
1886+
b'http\0',
1887+
'\0',
1888+
b'\0'
1889+
]
1890+
for port in ports:
1891+
with self.subTest(port=port):
1892+
with self.assertRaisesRegex(ValueError, 'port'):
1893+
socket.getaddrinfo("localhost", port)
1894+
18621895
def test_str_for_enums(self):
18631896
# Make sure that the AF_* and SOCK_* constants have enum-like string
18641897
# 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: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6557,8 +6557,9 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
65576557
struct addrinfo *res0 = NULL;
65586558
PyObject *hobj = NULL;
65596559
PyObject *pobj = (PyObject *)NULL;
6560-
char pbuf[30];
6560+
PyObject *pstr = NULL;
65616561
const char *hptr, *pptr;
6562+
Py_ssize_t hlen = 0, plen = 0;
65626563
int family, socktype, protocol, flags;
65636564
int error;
65646565
PyObject *all = (PyObject *)NULL;
@@ -6579,31 +6580,46 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
65796580
return NULL;
65806581
assert(PyBytes_Check(idna));
65816582
hptr = PyBytes_AS_STRING(idna);
6583+
hlen = PyBytes_GET_SIZE(idna);
65826584
} else if (PyBytes_Check(hobj)) {
6583-
hptr = PyBytes_AsString(hobj);
6585+
hptr = PyBytes_AS_STRING(hobj);
6586+
hlen = PyBytes_GET_SIZE(hobj);
65846587
} else {
65856588
PyErr_SetString(PyExc_TypeError,
65866589
"getaddrinfo() argument 1 must be string or None");
65876590
return NULL;
65886591
}
6592+
if (hptr != NULL && strlen(hptr) != (size_t)hlen) {
6593+
PyErr_SetString(PyExc_ValueError,
6594+
"getaddrinfo() host must not contain null characters");
6595+
goto err;
6596+
}
65896597
if (PyLong_CheckExact(pobj)) {
6590-
long value = PyLong_AsLong(pobj);
6591-
if (value == -1 && PyErr_Occurred())
6598+
pstr = PyObject_Str(pobj);
6599+
if (pstr == NULL)
6600+
goto err;
6601+
assert(PyUnicode_Check(pstr));
6602+
pptr = PyUnicode_AsUTF8AndSize(pstr, &plen);
6603+
if (pptr == NULL)
65926604
goto err;
6593-
PyOS_snprintf(pbuf, sizeof(pbuf), "%ld", value);
6594-
pptr = pbuf;
65956605
} else if (PyUnicode_Check(pobj)) {
6596-
pptr = PyUnicode_AsUTF8(pobj);
6606+
pptr = PyUnicode_AsUTF8AndSize(pobj, &plen);
65976607
if (pptr == NULL)
65986608
goto err;
65996609
} else if (PyBytes_Check(pobj)) {
66006610
pptr = PyBytes_AS_STRING(pobj);
6611+
plen = PyBytes_GET_SIZE(pobj);
66016612
} else if (pobj == Py_None) {
66026613
pptr = (char *)NULL;
66036614
} else {
66046615
PyErr_SetString(PyExc_OSError, "Int or String expected");
66056616
goto err;
66066617
}
6618+
if (pptr != NULL && strlen(pptr) != (size_t)plen) {
6619+
PyErr_SetString(PyExc_ValueError,
6620+
"getaddrinfo() port must not contain null characters");
6621+
goto err;
6622+
}
66076623
#if defined(__APPLE__) && defined(AI_NUMERICSERV)
66086624
if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) {
66096625
/* On OSX up to at least OSX 10.8 getaddrinfo crashes

0 commit comments

Comments
 (0)