Skip to content

Commit c53e593

Browse files
[3.14] 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 270a30a commit c53e593

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

1058+
.. versionchanged:: next
1059+
Null characters are now rejected in the *host* and *port* parameters.
1060+
10581061
.. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
10591062

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

‎Lib/test/test_socket.py‎

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

2041+
def test_getaddrinfo_null_bytes(self):
2042+
# getaddrinfo() must reject embedded null bytes instead of silently
2043+
# truncating the host or port at the C boundary. The checks run
2044+
# before any name resolution, so no network access is needed.
2045+
hosts = [
2046+
# embedded
2047+
'local\0host', b'local\0host',
2048+
# trailing
2049+
'localhost\0', b'localhost\0',
2050+
# leading (truncates to '')
2051+
'\0localhost', b'\0localhost',
2052+
'\0', b'\0',
2053+
# non-ASCII, goes through IDNA
2054+
'b\xfccher\0.example',
2055+
]
2056+
for host in hosts:
2057+
with self.subTest(host=host):
2058+
with self.assertRaisesRegex(ValueError, 'host'):
2059+
socket.getaddrinfo(host, 80)
2060+
2061+
ports = [
2062+
'8\x000',
2063+
b'8\x000',
2064+
'http\0',
2065+
b'http\0',
2066+
'\0',
2067+
b'\0'
2068+
]
2069+
for port in ports:
2070+
with self.subTest(port=port):
2071+
with self.assertRaisesRegex(ValueError, 'port'):
2072+
socket.getaddrinfo("localhost", port)
2073+
20412074
def test_str_for_enums(self):
20422075
# Make sure that the AF_* and SOCK_* constants have enum-like string
20432076
# 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
@@ -6922,6 +6922,7 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
69226922
PyObject *pobj = (PyObject *)NULL;
69236923
PyObject *pstr = NULL;
69246924
const char *hptr, *pptr;
6925+
Py_ssize_t hlen = 0, plen = 0;
69256926
int family, socktype, protocol, flags;
69266927
int error;
69276928
PyObject *all = (PyObject *)NULL;
@@ -6942,13 +6943,20 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
69426943
return NULL;
69436944
assert(PyBytes_Check(idna));
69446945
hptr = PyBytes_AS_STRING(idna);
6946+
hlen = PyBytes_GET_SIZE(idna);
69456947
} else if (PyBytes_Check(hobj)) {
6946-
hptr = PyBytes_AsString(hobj);
6948+
hptr = PyBytes_AS_STRING(hobj);
6949+
hlen = PyBytes_GET_SIZE(hobj);
69476950
} else {
69486951
PyErr_SetString(PyExc_TypeError,
69496952
"getaddrinfo() argument 1 must be string or None");
69506953
return NULL;
69516954
}
6955+
if (hptr != NULL && strlen(hptr) != (size_t)hlen) {
6956+
PyErr_SetString(PyExc_ValueError,
6957+
"getaddrinfo() host must not contain null characters");
6958+
goto err;
6959+
}
69526960
if (PyIndex_Check(pobj)) {
69536961
pstr = PyNumber_Index(pobj);
69546962
if (pstr == NULL)
@@ -6958,21 +6966,27 @@ socket_getaddrinfo(PyObject *self, PyObject *args, PyObject* kwargs)
69586966
if (pstr == NULL)
69596967
goto err;
69606968
assert(PyUnicode_Check(pstr));
6961-
pptr = PyUnicode_AsUTF8(pstr);
6969+
pptr = PyUnicode_AsUTF8AndSize(pstr, &plen);
69626970
if (pptr == NULL)
69636971
goto err;
69646972
} else if (PyUnicode_Check(pobj)) {
6965-
pptr = PyUnicode_AsUTF8(pobj);
6973+
pptr = PyUnicode_AsUTF8AndSize(pobj, &plen);
69666974
if (pptr == NULL)
69676975
goto err;
69686976
} else if (PyBytes_Check(pobj)) {
69696977
pptr = PyBytes_AS_STRING(pobj);
6978+
plen = PyBytes_GET_SIZE(pobj);
69706979
} else if (pobj == Py_None) {
69716980
pptr = (char *)NULL;
69726981
} else {
69736982
PyErr_SetString(PyExc_OSError, "Int or String expected");
69746983
goto err;
69756984
}
6985+
if (pptr != NULL && strlen(pptr) != (size_t)plen) {
6986+
PyErr_SetString(PyExc_ValueError,
6987+
"getaddrinfo() port must not contain null characters");
6988+
goto err;
6989+
}
69766990
#if defined(__APPLE__) && defined(AI_NUMERICSERV)
69776991
if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) {
69786992
/* On OSX up to at least OSX 10.8 getaddrinfo crashes

0 commit comments

Comments
 (0)