Repository navigation
Open
KushagraAgarwal525/numpy
#47Description
Describe the issue:
It's possible to segfault the interpreter by (re)initializing a random generator with invalid data, then calling random_raw.
Reproduce the code example:
import numpy.random
p = numpy.random.PCG64DXSM()
try:
p.__init__(("",))
except ValueError:
pass
p.random_raw()Error message:
Thread 1 "python" received signal SIGSEGV, Segmentation fault.
0x00007ffff4137f7a in __pyx_f_5numpy_6random_6_pcg64_pcg64_cm_uint64 ()
from /home/danzin/venvs/3.13_upstream_fusil_venv/lib/python3.13t/site-packages/numpy/random/_pcg64.cpython-313t-x86_64-linux-gnu.so
#0 0x00007ffff4137f7a in __pyx_f_5numpy_6random_6_pcg64_pcg64_cm_uint64 ()
from /home/danzin/venvs/3.13_upstream_fusil_venv/lib/python3.13t/site-packages/numpy/random/_pcg64.cpython-313t-x86_64-linux-gnu.so
#1 0x00007ffff4dd45d6 in __pyx_f_5numpy_6random_7_common_random_raw ()
from /home/danzin/venvs/3.13_upstream_fusil_venv/lib/python3.13t/site-packages/numpy/random/_common.cpython-313t-x86_64-linux-gnu.so
#2 0x00007ffff53af54b in __pyx_pw_5numpy_6random_13bit_generator_12BitGenerator_11random_raw ()
from /home/danzin/venvs/3.13_upstream_fusil_venv/lib/python3.13t/site-packages/numpy/random/bit_generator.cpython-313t-x86_64-linux-gnu.so
#3 0x0000555555a484db in _PyObject_VectorcallTstate (tstate=0x5555566c6300 <_PyRuntime+326528>,
callable=0x7fffb438ce90, args=0x7fffffffcc38, nargsf=15750249268501108917,
kwnames=0x5555564cdf0c <PyCFunction_Type+12>) at ./Include/internal/pycore_call.h:168
#4 0x0000555555dbd570 in _PyEval_EvalFrameDefault (tstate=<optimized out>,
frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:813
#5 0x0000555555dac3eb in PyEval_EvalCode (co=co@entry=0x7fffb4844990,
globals=globals@entry=0x7fffb4755770, locals=locals@entry=0x7fffb4755770)
at Python/ceval.c:604
#6 0x0000555555f5d469 in run_eval_code_obj (
tstate=tstate@entry=0x5555566c6300 <_PyRuntime+326528>, co=co@entry=0x7fffb4844990,
globals=globals@entry=0x7fffb4755770, locals=locals@entry=0x7fffb4755770)
at Python/pythonrun.c:1381
#7 0x0000555555f5cb3f in run_mod (mod=mod@entry=0x7fffb45a8758,
filename=filename@entry=0x7fffb423d210, globals=0x7fffb4755770, locals=0x7fffb4755770,
flags=<optimized out>, arena=arena@entry=0x7fffb4051870, interactive_src=0x0,
generate_new_source=0) at Python/pythonrun.c:1466
#8 0x0000555555f54b3a in pyrun_file (fp=<optimized out>, filename=0x7fffb423d210, start=257,
globals=<optimized out>, locals=<optimized out>, closeit=<optimized out>,
flags=<optimized out>) at Python/pythonrun.c:1295
#9 _PyRun_SimpleFileObject (fp=<optimized out>, fp@entry=0x515000000f80,
filename=filename@entry=0x7fffb423d210, closeit=<optimized out>, closeit@entry=1,
flags=<optimized out>, flags@entry=0x7fffffffd6d0) at Python/pythonrun.c:517
#10 0x0000555555f53f36 in _PyRun_AnyFileObject (fp=fp@entry=0x515000000f80,
filename=filename@entry=0x7fffb423d210, closeit=closeit@entry=1,
flags=flags@entry=0x7fffffffd6d0) at Python/pythonrun.c:77
#11 0x0000555555fbcb55 in pymain_run_file_obj (program_name=0x7fffb4da3030,
filename=0x7fffb423d210, skip_source_first_line=0) at Modules/main.c:410
#12 pymain_run_file (config=config@entry=0x555556697d88 <_PyRuntime+136712>) at Modules/main.c:429
#13 0x0000555555fba882 in pymain_run_python (exitcode=0x7fffffffd784) at Modules/main.c:696
#14 Py_RunMain () at Modules/main.c:775
#15 0x0000555555fbb73e in pymain_main (args=<optimized out>) at Modules/main.c:805
#16 0x0000555555fbb8a4 in Py_BytesMain (argc=2, argv=<optimized out>) at Modules/main.c:829
#17 0x00007ffff7c2a3b8 in __libc_start_call_main (main=main@entry=0x55555588a630 <main>,
argc=argc@entry=2, argv=argv@entry=0x7fffffffdb68)
at ../sysdeps/nptl/libc_start_call_main.h:58
#18 0x00007ffff7c2a47b in __libc_start_main_impl (main=0x55555588a630 <main>, argc=2,
argv=0x7fffffffdb68, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
stack_end=0x7fffffffdb58) at ../csu/libc-start.c:360
#19 0x00005555557a9105 in _start ()Python and NumPy Versions:
2.3.0.dev0+git20250415.e151f0d
3.13.3+ experimental free-threading build (heads/3.13:83cb89b941b, Apr 18 2025, 20:59:43) [Clang 19.1.7 (++20250114103253+cd708029e0b2-1exp120250114103309.40)]
Runtime Environment:
[{'numpy_version': '2.3.0.dev0+git20250415.e151f0d',
'python': '3.13.3+ experimental free-threading build '
'(heads/3.13:83cb89b941b, Apr 18 2025, 20:59:43) [Clang 19.1.7 '
'(++20250114103253+cd708029e0b2-1exp120250114103309.40)]',
'uname': uname_result(system='Linux', node='beesknees', release='6.11.0-24-generic', version='https://github.com/numpy/numpy/pull/24-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 14 18:13:56 UTC 2025', machine='x86_64')},
{'simd_extensions': {'baseline': ['SSE', 'SSE2', 'SSE3'],
'found': ['SSSE3',
'SSE41',
'POPCNT',
'SSE42',
'AVX',
'F16C',
'FMA3',
'AVX2',
'AVX512F',
'AVX512CD',
'AVX512_SKX',
'AVX512_CLX',
'AVX512_CNL',
'AVX512_ICL'],
'not_found': ['AVX512_KNL', 'AVX512_KNM', 'AVX512_SPR']}},
{'architecture': 'SkylakeX',
'filepath': '/home/danzin/venvs/3.13_upstream_fusil_venv/lib/python3.13t/site-packages/numpy.libs/libscipy_openblas64_-56d6093b.so',
'internal_api': 'openblas',
'num_threads': 16,
'prefix': 'libscipy_openblas',
'threading_layer': 'pthreads',
'user_api': 'blas',
'version': '0.3.29'}]
Context for the issue:
I have been fuzzing Numpy using fusil by @vstinner. I realize these crashes are unlikely to be triggered in normal usage and therefore might be of low priority.
The fuzzing was done with an ASAN free-threading clang build and not confirmed on a GILfull non-sanitizer GCC build yet.
Activity
ngoldbaum commentedon Apr 23, 2025
I can reproduce this in an ASAN build of Python:
Ping @rkern because this is happening inside the Cython RNG code.
rkern commentedon Apr 23, 2025
BitGenerator.__init__()(re)sets the state to a null pointer before trying to process the arguments. When you instantiate the object and then (re)run the__init__()with an invalid argument, the state gets (re)set but the exception gets in the way. In typical usage, this exception won't be a problem because one typically instantiates and runs the__init__()in the same expression before the user gets a hold of the instance, so they never get a chance to call methods on the invalid instance. But in this circumstance of rerunning__init__(), you already have a reference to the instance.https://github.com/numpy/numpy/blob/main/numpy/random/bit_generator.pyx#L525
Another, slightly more realistic, reproducer would be to call
__new__()then__init__()separately.From the first example, we can see that we can prevent most of the badness if we also make sure to reset the
lockattribute back toNoneif the rest of the__init__()fails (e.g. with atry: except Exception: self.lock = None; raise. Access to the state should be protected by the lock everywhere, so keeping itNonewhen we think it is likely to be invalid will cause graceful(ish) exceptions instead of segmentation faults. This probably only needs to be done inBitGenerator.__init__(), butPhilox.__init__()might also need a similar treatment in case its handling of itskeyargument fails.BUG: preserve BitGenerator state after failed reinitialization