Bug report
Bug description:
Found by the CI UBSan job, e.g. this run:
Python/generated_cases.c.h:8158:17: runtime error: applying non-zero offset 44 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:8158:17
Note this requires applying #152311 which seems to change the state ever so slightly uncovering the UB. Locally I get with clang:
$ ./python -m test test_monitoring
Using random seed: 1062411673
0:00:00 load avg: 1.00 mem: 41.2 MiB Run 1 test sequentially in a single process
0:00:00 load avg: 1.00 mem: 41.2 MiB [1/1] test_monitoring
Python/generated_cases.c.h:8158:17: runtime error: applying non-zero offset 44 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:8158:17
Python/generated_cases.c.h:8050:13: runtime error: applying non-zero offset 2 to null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:8050:13
Python/generated_cases.c.h:13861:59: runtime error: applying non-zero offset to non-null pointer 0x000000000002 produced null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:13861:59
Python/generated_cases.c.h:13881:57: runtime error: applying non-zero offset to non-null pointer 0x000000000002 produced null pointer
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior Python/generated_cases.c.h:13881:57
0:00:00 load avg: 1.00 mem: 43.0 MiB [1/1] test_monitoring passed
== Tests result: SUCCESS ==
1 test OK.
Total duration: 357 ms
Total tests: run=98
Total test files: run=1/1
Result: SUCCESS
The UB is reported, e.g. at:
| INSTRUMENTED_JUMP(this_instr, next_instr + oparg, PY_MONITORING_EVENT_BRANCH_RIGHT); |
Which calls INSTRUMENTED_JUMP:
| // If a trace function sets a new f_lineno and | |
| // *then* raises, we use the destination when searching | |
| // for an exception handler, displaying the traceback, and so on | |
| #define INSTRUMENTED_JUMP(src, dest, event) \ | |
| do { \ | |
| if (tstate->tracing) {\ | |
| next_instr = dest; \ | |
| } else { \ | |
| _PyFrame_SetStackPointer(frame, stack_pointer); \ | |
| next_instr = _Py_call_instrumentation_jump(this_instr, tstate, event, frame, src, dest); \ | |
| stack_pointer = _PyFrame_GetStackPointer(frame); \ | |
| if (next_instr == NULL) { \ | |
| next_instr = (dest)+1; \ | |
| JUMP_TO_LABEL(error); \ | |
| } \ | |
| } \ | |
| } while (0); |
If _Py_call_instrumentation_jump raises it returns NULL (setting next_instr). But then on the error path, we do next_instr + oparg resulting in the UB.
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs
- gh-152375: Fix UB in
INSTRUMENTED_JUMP#152376 - [3.15] gh-152375: Fix undefined behaviour in the
INSTRUMENTED_JUMPmacro (GH-152376) #152566 - [3.14] gh-152375: Fix undefined behaviour in the
INSTRUMENTED_JUMPmacro (GH-152376) #152567 - [3.13] gh-152375: Fix undefined behaviour in the
INSTRUMENTED_JUMPmacro (GH-152376) #152577