Bug description
Origin and AI assistance
This failure was initially identified while reviewing production crash logs available to Datadog. The investigation, reproducer, proposed patch, tests, and this issue write-up were produced primarily with GPT-5.6 Sol (high), with human direction, review, and local validation.
A code object containing INSTRUMENTED_LINE or INSTRUMENTED_INSTRUCTION without the corresponding monitoring data can cause a null-pointer dereference when CPython tries to de-instrument its bytecode.
CodeType.replace() is a low-level API and permits malformed bytecode, but other malformed opcodes fail with SystemError when used. Runtime-only instrumented opcodes should likewise fail safely rather than terminate the process.
On current main, this Python-only reproducer crashes when reading code.co_code:
import dis
def func():
pass
bytecode = bytearray(func.__code__.co_code)
bytecode[0] = dis._all_opmap["INSTRUMENTED_LINE"]
code = func.__code__.replace(co_code=bytes(bytecode))
code.co_code
The same applies to INSTRUMENTED_INSTRUCTION.
The native path on a debug build from main at c3aefdb9eff0734058376b96fc86d89b1a345d75 is:
_Py_GetBaseCodeUnit
-> deopt_code
-> _PyCode_GetCode
-> code.co_code getter
_Py_GetBaseCodeUnit() assumes that code->_co_monitoring and the opcode-specific monitoring array are non-null:
if (opcode == INSTRUMENTED_LINE) {
opcode = _PyCode_GetOriginalOpcode(code->_co_monitoring->lines, i);
}
if (opcode == INSTRUMENTED_INSTRUCTION) {
opcode = code->_co_monitoring->per_instruction_opcodes[i];
}
A newly constructed code object has _co_monitoring == NULL, so materializing its public bytecode dereferences a null pointer.
I would expect code.co_code to raise SystemError for this inconsistent internal state. I have a patch that validates the required monitoring data in deopt_code() before calling _Py_GetBaseCodeUnit().
Observed results:
- CPython 3.12.13:
SIGSEGV
- CPython 3.13.14:
SIGSEGV
- CPython 3.14.6:
SIGSEGV
- CPython 3.16.0a0 from
main at c3aefdb9ef: SIGSEGV
CPython versions tested on
3.12, 3.13, 3.14, 3.16, CPython main branch
Operating systems tested on
Linux
Linked PRs
Bug description
Origin and AI assistance
This failure was initially identified while reviewing production crash logs available to Datadog. The investigation, reproducer, proposed patch, tests, and this issue write-up were produced primarily with GPT-5.6 Sol (high), with human direction, review, and local validation.
A code object containing
INSTRUMENTED_LINEorINSTRUMENTED_INSTRUCTIONwithout the corresponding monitoring data can cause a null-pointer dereference when CPython tries to de-instrument its bytecode.CodeType.replace()is a low-level API and permits malformed bytecode, but other malformed opcodes fail withSystemErrorwhen used. Runtime-only instrumented opcodes should likewise fail safely rather than terminate the process.On current
main, this Python-only reproducer crashes when readingcode.co_code:The same applies to
INSTRUMENTED_INSTRUCTION.The native path on a debug build from
mainatc3aefdb9eff0734058376b96fc86d89b1a345d75is:_Py_GetBaseCodeUnit()assumes thatcode->_co_monitoringand the opcode-specific monitoring array are non-null:A newly constructed code object has
_co_monitoring == NULL, so materializing its public bytecode dereferences a null pointer.I would expect
code.co_codeto raiseSystemErrorfor this inconsistent internal state. I have a patch that validates the required monitoring data indeopt_code()before calling_Py_GetBaseCodeUnit().Observed results:
SIGSEGVSIGSEGVSIGSEGVmainatc3aefdb9ef:SIGSEGVCPython versions tested on
3.12, 3.13, 3.14, 3.16, CPython main branch
Operating systems tested on
Linux
Linked PRs