Bug report
Bug description:
_PyBytes_Resize() appears to handle invalid arguments differently from _PyTuple_Resize() and similar helper APIs.
When *pv == NULL, _PyBytes_Resize() dereferences the pointer through PyBytes_Check(v) before reaching its existing PyErr_BadInternalCall() error path.
Minimal reproducer:
from _testcapi import bytes_resize
bytes_resize(None, 0, False)
This results in a segmentation fault.
With UBSan enabled:
Include/object.h:234:14: runtime error: member access within null pointer of type 'struct PyObject'
GDB backtrace:
#0 _Py_TYPE_impl (ob=0x0) at Include/object.h:234
#1 _PyBytes_Resize() at Objects/bytesobject.c:3349
#2 bytes_resize() at Modules/_testcapi/bytes.c:35
The relevant implementation is:
v = *pv;
if (!PyBytes_Check(v) || newsize < 0) {
*pv = 0;
Py_DECREF(v);
PyErr_BadInternalCall();
return -1;
}
The crash occurs while evaluating PyBytes_Check(v), so the PyErr_BadInternalCall() path is never reached.
For comparison, _PyTuple_Resize() performs an explicit NULL check before the type check:
v = (PyTupleObject *)*pv;
if (v == NULL || !Py_IS_TYPE(v, &PyTuple_Type) ||
(Py_SIZE(v) != 0 && !_PyObject_IsUniquelyReferenced(*pv))) {
*pv = 0;
Py_XDECREF(v);
PyErr_BadInternalCall();
return -1;
}
The corresponding _testcapi wrapper behaves accordingly:
>>> from _testcapi import _tuple_resize
>>> _tuple_resize(None, 0, False)
SystemError: Objects/tupleobject.c:1048: bad argument to internal function
whereas:
>>> from _testcapi import bytes_resize
>>> bytes_resize(None, 0, False)
# segmentation fault
I also noticed that Lib/test/test_capi/test_bytes.py already has the bytes_resize(None, ...) cases commented out as CRASHES.
All production callers I inspected appear to satisfy the documented precondition that *pv points to an existing bytes object, so this does not appear to affect normal CPython execution.
Is the current behavior for _PyBytes_Resize() intentional, or should it reject NULL consistently with _PyTuple_Resize() by reaching PyErr_BadInternalCall() instead of dereferencing the pointer first?
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Bug report
Bug description:
_PyBytes_Resize()appears to handle invalid arguments differently from_PyTuple_Resize()and similar helper APIs.When
*pv == NULL,_PyBytes_Resize()dereferences the pointer throughPyBytes_Check(v)before reaching its existingPyErr_BadInternalCall()error path.Minimal reproducer:
This results in a segmentation fault.
With UBSan enabled:
GDB backtrace:
The relevant implementation is:
The crash occurs while evaluating
PyBytes_Check(v), so thePyErr_BadInternalCall()path is never reached.For comparison,
_PyTuple_Resize()performs an explicitNULLcheck before the type check:The corresponding
_testcapiwrapper behaves accordingly:whereas:
I also noticed that
Lib/test/test_capi/test_bytes.pyalready has thebytes_resize(None, ...)cases commented out asCRASHES.All production callers I inspected appear to satisfy the documented precondition that
*pvpoints to an existing bytes object, so this does not appear to affect normal CPython execution.Is the current behavior for
_PyBytes_Resize()intentional, or should it rejectNULLconsistently with_PyTuple_Resize()by reachingPyErr_BadInternalCall()instead of dereferencing the pointer first?CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux