static PyObject *
bytearray_index_impl(PyByteArrayObject *self, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
/*[clinic end generated code: output=067a1e78efc672a7 input=c37f177cfee19fe4]*/
{
return _Py_bytes_index(PyByteArray_AS_STRING(self), PyByteArray_GET_SIZE(self),
sub, start, end);
}
PyObject *
_Py_bytes_index(const char *str, Py_ssize_t len, PyObject *sub,
Py_ssize_t start, Py_ssize_t end)
{
Py_ssize_t result = find_internal(str, len, "index", sub, start, end, +1);
if (result == -2)
return NULL;
if (result == -1) {
PyErr_SetString(PyExc_ValueError,
"subsection not found");
return NULL;
}
return PyLong_FromSsize_t(result);
}
Py_LOCAL_INLINE(Py_ssize_t)
find_internal(const char *str, Py_ssize_t len,
const char *function_name, PyObject *subobj,
Py_ssize_t start, Py_ssize_t end,
int dir)
{
char byte;
Py_buffer subbuf;
const char *sub;
Py_ssize_t sub_len;
Py_ssize_t res;
if (!parse_args_finds_byte(function_name, &subobj, &byte)) {
return -2;
}
if (subobj) {
if (PyObject_GetBuffer(subobj, &subbuf, PyBUF_SIMPLE) != 0)
return -2;
sub = subbuf.buf;
sub_len = subbuf.len;
}
else {
sub = &byte;
sub_len = 1;
}
ADJUST_INDICES(start, end, len);
if (end - start < sub_len)
res = -1;
else if (sub_len == 1) {
if (dir > 0)
// Bug: *str has been freed already
res = stringlib_find_char(
str + start, end - start,
*sub);
else
res = stringlib_rfind_char(
str + start, end - start,
*sub);
if (res >= 0)
res += start;
}
...
}
Py_LOCAL_INLINE(int)
parse_args_finds_byte(const char *function_name, PyObject **subobj, char *byte)
{
if (PyObject_CheckBuffer(*subobj)) {
return 1;
}
if (!_PyIndex_Check(*subobj)) {
PyErr_Format(PyExc_TypeError,
"argument should be integer or bytes-like object, "
"not '%.200s'",
Py_TYPE(*subobj)->tp_name);
return 0;
}
// Implicitly call __index__ method that free the *subobj buffer
Py_ssize_t ival = PyNumber_AsSsize_t(*subobj, NULL);
if (ival == -1 && PyErr_Occurred()) {
return 0;
}
if (ival < 0 || ival > 255) {
PyErr_SetString(PyExc_ValueError, "byte must be in range(0, 256)");
return 0;
}
*subobj = NULL;
*byte = (char)ival;
return 1;
}
What happened?
bytearray.index(x)convertsxto a single byte using index before performing the search.A crafted
__index__can callbytearray.clear(), freeing or resizing the buffer afterstrhave been captured. The subsequent search (stringlib_find_char) then dereferences the stale pointer, resulting in a heap use-after-free.Note that, this may share the same root cause as #142559 and #142558.
Proof of Concept:
Affected Versions:
Details
Python 3.9.24+ (heads/3.9:9c4638d, Oct 17 2025, 11:19:30)Python 3.10.19+ (heads/3.10:0142619, Oct 17 2025, 11:20:05) [GCC 13.3.0]Python 3.11.14+ (heads/3.11:88f3f5b, Oct 17 2025, 11:20:44) [GCC 13.3.0]Python 3.12.12+ (heads/3.12:8cb2092, Oct 17 2025, 11:21:35) [GCC 13.3.0]Python 3.13.9+ (heads/3.13:0760a57, Oct 17 2025, 11:22:25) [GCC 13.3.0]Python 3.14.0+ (heads/3.14:889e918, Oct 17 2025, 11:23:02) [GCC 13.3.0]Python 3.15.0a1+ (heads/main:fbf0843, Oct 17 2025, 11:23:37) [GCC 13.3.0]Vulnerable Code Snippet
Details
Sanitizer
Details
Linked PRs