Skip to content

Commit 131a3a6

Browse files
gh-160898: Reject null bytes in socket.getaddrinfo() host and port (#160899)
Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 7563195 commit 131a3a6

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

1068+
.. versionchanged:: next
1069+
Null characters are now rejected in the *host* and *port* parameters.
1070+
10681071
.. _Happy Eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
10691072

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

‎Lib/test/test_socket.py‎

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

2107+
def test_getaddrinfo_null_bytes(self):
2108+
# getaddrinfo() must reject embedded null bytes instead of silently
2109+
# truncating the host or port at the C boundary. The checks run
2110+
# before any name resolution, so no network access is needed.
2111+
hosts = [
2112+
# embedded
2113+
'local\0host', b'local\0host',
2114+
# trailing
2115+
'localhost\0', b'localhost\0',
2116+
# leading (truncates to '')
2117+
'\0localhost', b'\0localhost',
2118+
'\0', b'\0',
2119+
# non-ASCII, goes through IDNA
2120+
'b\xfccher\0.example',
2121+
]
2122+
for host in hosts:
2123+
with self.subTest(host=host):
2124+
with self.assertRaisesRegex(ValueError, 'host'):
2125+
socket.getaddrinfo(host, 80)
2126+
2127+
ports = [
2128+
'8\x000',
2129+
b'8\x000',
2130+
'http\0',
2131+
b'http\0',
2132+
'\0',
2133+
b'\0'
2134+
]
2135+
for port in ports:
2136+
with self.subTest(port=port):
2137+
with self.assertRaisesRegex(ValueError, 'port'):
2138+
socket.getaddrinfo("localhost", port)
2139+
21072140
def test_str_for_enums(self):
21082141
# Make sure that the AF_* and SOCK_* constants have enum-like string
21092142
# 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
@@ -7018,6 +7018,7 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *pobj,
70187018
struct addrinfo *res0 = NULL;
70197019
PyObject *pstr = NULL;
70207020
const char *hptr, *pptr;
7021+
Py_ssize_t hlen = 0, plen = 0;
70217022
int error;
70227023
PyObject *all = (PyObject *)NULL;
70237024
PyObject *idna = NULL;
@@ -7030,13 +7031,20 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *pobj,
70307031
return NULL;
70317032
assert(PyBytes_Check(idna));
70327033
hptr = PyBytes_AS_STRING(idna);
7034+
hlen = PyBytes_GET_SIZE(idna);
70337035
} else if (PyBytes_Check(hobj)) {
7034-
hptr = PyBytes_AsString(hobj);
7036+
hptr = PyBytes_AS_STRING(hobj);
7037+
hlen = PyBytes_GET_SIZE(hobj);
70357038
} else {
70367039
PyErr_SetString(PyExc_TypeError,
70377040
"getaddrinfo() argument 1 must be string or None");
70387041
return NULL;
70397042
}
7043+
if (hptr != NULL && strlen(hptr) != (size_t)hlen) {
7044+
PyErr_SetString(PyExc_ValueError,
7045+
"getaddrinfo() host must not contain null characters");
7046+
goto err;
7047+
}
70407048
if (PyIndex_Check(pobj)) {
70417049
pstr = PyNumber_Index(pobj);
70427050
if (pstr == NULL)
@@ -7046,21 +7054,27 @@ _socket_getaddrinfo_impl(PyObject *module, PyObject *hobj, PyObject *pobj,
70467054
if (pstr == NULL)
70477055
goto err;
70487056
assert(PyUnicode_Check(pstr));
7049-
pptr = PyUnicode_AsUTF8(pstr);
7057+
pptr = PyUnicode_AsUTF8AndSize(pstr, &plen);
70507058
if (pptr == NULL)
70517059
goto err;
70527060
} else if (PyUnicode_Check(pobj)) {
7053-
pptr = PyUnicode_AsUTF8(pobj);
7061+
pptr = PyUnicode_AsUTF8AndSize(pobj, &plen);
70547062
if (pptr == NULL)
70557063
goto err;
70567064
} else if (PyBytes_Check(pobj)) {
70577065
pptr = PyBytes_AS_STRING(pobj);
7066+
plen = PyBytes_GET_SIZE(pobj);
70587067
} else if (pobj == Py_None) {
70597068
pptr = (char *)NULL;
70607069
} else {
70617070
PyErr_SetString(PyExc_OSError, "Int or String expected");
70627071
goto err;
70637072
}
7073+
if (pptr != NULL && strlen(pptr) != (size_t)plen) {
7074+
PyErr_SetString(PyExc_ValueError,
7075+
"getaddrinfo() port must not contain null characters");
7076+
goto err;
7077+
}
70647078
#if defined(__APPLE__) && defined(AI_NUMERICSERV)
70657079
if ((flags & AI_NUMERICSERV) && (pptr == NULL || (pptr[0] == '0' && pptr[1] == 0))) {
70667080
/* On OSX up to at least OSX 10.8 getaddrinfo crashes

0 commit comments

Comments
 (0)