Skip to content

dgemm_kernel_HASWELL: stack buffer overrun for large k (SIGSEGV at the kernel's final ret) #6026

Description

@yarink80

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions