Summary
An intermittent SIGSEGV inside dgemm_kernel_HASWELL, delivered at the kernel's final ret with a general-protection signature (a non-canonical popped return address), during an ordinary double-precision GEMM under OPENBLAS_CORETYPE=Haswell. It traces to a stack buffer overrun in the kernel's packing loop for a large inner dimension k.
Environment
- OpenBLAS 0.3.34, as bundled in the
scipy-openblas64 wheel shipped with numpy 2.5.2 and 2.5.3 (object libscipy_openblas64_*.so).
- x86_64,
OPENBLAS_CORETYPE=Haswell, OPENBLAS_NUM_THREADS=1, on GitHub-hosted ubuntu-latest runners (Intel CPUs, AVX-512 disabled via NPY_DISABLE_CPU_FEATURES=X86_V4).
- Python 3.13.
- The
dgemm_kernel_HASWELL machine code is byte-identical between the 0.3.34 objects in the 2.5.2 and 2.5.3 bundles (sha256 b3b73ae31406241b3ff4acba2c7b9d180a28a871b00351cdf2a7844eae430973, 28076 bytes), so this is not a version regression.
Symptom
SIGSEGV in dgemm_kernel_HASWELL at the symbol's final ret (object offset 0x6dab from the symbol start), with a general-protection signature and a non-canonical instruction pointer. It occurs on some hosted-runner CPUs and not others running the identical binary, intermittently (roughly one run in three on an affected runner).
Mechanism
Disassembly shows the Haswell dgemm kernel packs into a fixed 0x7080-byte stack buffer, storing 96 bytes per inner-dimension (k) step with no bound on k. For k at or above about 319 the packing overruns the six saved callee-saved registers and the return-address slot on the kernel's own frame, corrupting the return address, so the final ret faults.
We instrumented the k argument at kernel entry (an LD_PRELOAD shim that logs the third integer argument at each dgemm_kernel_HASWELL call) together with an in-process SIGSEGV backtrace handler:
- On faulting runs, the crashing call has k = 332 (and k = 352 for a 700x700 GEMM).
- On non-faulting runs of the identical binary on other hosts, k never exceeds 256.
The level-3 blocking that sets k is chosen at run time from the host cache topology, which is why the fault is host-dependent for the same matrix sizes.
Impact
Besides the crash, a draw whose overrun covers the saved registers but not the return address would return normally with corrupted operand registers, that is, a silently wrong numeric result. We have not observed that case, but the same unbounded packing makes it possible in principle.
Request
Add a bound check, or size the packing buffer for the maximum k the level-3 driver can pass, in dgemm_kernel_HASWELL, so a large k cannot overrun the frame.
We can share the exact diagnostic (the LD_PRELOAD k-logging shim and the SIGSEGV backtrace handler) and the captured per-call k logs on request.
Summary
An intermittent SIGSEGV inside
dgemm_kernel_HASWELL, delivered at the kernel's finalretwith a general-protection signature (a non-canonical popped return address), during an ordinary double-precision GEMM underOPENBLAS_CORETYPE=Haswell. It traces to a stack buffer overrun in the kernel's packing loop for a large inner dimension k.Environment
scipy-openblas64wheel shipped with numpy 2.5.2 and 2.5.3 (objectlibscipy_openblas64_*.so).OPENBLAS_CORETYPE=Haswell,OPENBLAS_NUM_THREADS=1, on GitHub-hostedubuntu-latestrunners (Intel CPUs, AVX-512 disabled viaNPY_DISABLE_CPU_FEATURES=X86_V4).dgemm_kernel_HASWELLmachine code is byte-identical between the 0.3.34 objects in the 2.5.2 and 2.5.3 bundles (sha256b3b73ae31406241b3ff4acba2c7b9d180a28a871b00351cdf2a7844eae430973, 28076 bytes), so this is not a version regression.Symptom
SIGSEGV in
dgemm_kernel_HASWELLat the symbol's finalret(object offset0x6dabfrom the symbol start), with a general-protection signature and a non-canonical instruction pointer. It occurs on some hosted-runner CPUs and not others running the identical binary, intermittently (roughly one run in three on an affected runner).Mechanism
Disassembly shows the Haswell dgemm kernel packs into a fixed
0x7080-byte stack buffer, storing 96 bytes per inner-dimension (k) step with no bound on k. For k at or above about 319 the packing overruns the six saved callee-saved registers and the return-address slot on the kernel's own frame, corrupting the return address, so the finalretfaults.We instrumented the k argument at kernel entry (an
LD_PRELOADshim that logs the third integer argument at eachdgemm_kernel_HASWELLcall) together with an in-process SIGSEGV backtrace handler:The level-3 blocking that sets k is chosen at run time from the host cache topology, which is why the fault is host-dependent for the same matrix sizes.
Impact
Besides the crash, a draw whose overrun covers the saved registers but not the return address would return normally with corrupted operand registers, that is, a silently wrong numeric result. We have not observed that case, but the same unbounded packing makes it possible in principle.
Request
Add a bound check, or size the packing buffer for the maximum k the level-3 driver can pass, in
dgemm_kernel_HASWELL, so a large k cannot overrun the frame.We can share the exact diagnostic (the
LD_PRELOADk-logging shim and the SIGSEGV backtrace handler) and the captured per-call k logs on request.