Skip to content

arm64: klauspost/compress v1.19.0/v1.19.1 corrupt the saved link register during zstd decompression #1016

Closed as not planned

Description

@lizthegrey

Describe the bug, including details regarding any error messages, version, and platform.

Heads-up about an upstream bug I introduced in klauspost/compress, which arrow-go picks up. I have no prior involvement with this project, so apologies for arriving cold — I am notifying the dependents I could identify as actually shipping the affected versions.

What it is

klauspost/compress v1.19.0 and v1.19.1 contain generated arm64 assembly for the zstd decoder in which every function local is placed 8 bytes too low, so local 0 overwrites the saved link register. It affects four functions: sequenceDecs_decode_arm64, sequenceDecs_decode_56_arm64, sequenceDecs_decodeSync_arm64, sequenceDecs_decodeSync_safe_arm64.

Root cause is an architecture difference the code generator did not account for: on x86 CALL pushes the return address above the frame, so local 0 sits at SP+0; on arm64 BL leaves it in the link register and the callee spills it to the bottom of its own frame, so 0(RSP) is the saved LR and locals start at 8(RSP).

Scope — please read before prioritising

  • No data corruption. Decoded output is always correct. Every local shifts by the same amount, so loads and stores agree — which is exactly why the upstream test suite and fuzzing never caught it.
  • arm64 only. amd64 is unaffected.
  • Decompression only. Compression does not touch these functions.
  • Manifests only when something walks the stack while one of those frames is live: GC stack scanning, panics, or CPU profiling. The likeliest trigger is a continuous profiler, since SIGPROF interrupts arbitrary code and unwinds immediately.

The symptom would appear to be a Go runtime bug rather than a compression one — fatal error: traceback stuck, runtime: unexpected return pc, or a crash inside runtime.gentraceback / runtime.scanobject / runtime.sigprof — which is why I am flagging it rather than waiting for reports.

arrow-go's exposure

  • v18.7.0 requires klauspost/compress v1.19.0
  • HEAD requires v1.19.1
  • Both parquet/compress/zstd.go and arrow/ipc/compression.go use zstd.NewReader, so Parquet and IPC decode are both on the affected path

Suggested action

Nothing to change in arrow-go itself. The upstream fix is klauspost/compress#1176, currently awaiting review; when a patch release is cut, a dependency bump is sufficient. If anyone is running arm64 with continuous profiling and seeing unexplained runtime crashes, pinning v1.18.x is a workaround in the meantime.

Honest caveat

I have not reproduced a crash from this. The diagnosis is from the frame layout, verified by disassembling probe functions at multiple frame sizes; the failure mode is inferred from it rather than observed. If you have seen an arm64 traceback crash with Parquet or IPC zstd decode in the interrupted goroutine, a stack dump on the upstream PR would be valuable — it would turn a reasoned diagnosis into a confirmed one.

Component(s)

Parquet

Activity

  1. lizthegrey commented on Jul 29, 2026

    @lizthegrey
    Author

    Closing this heads-up — follow-up analysis showed the crash scenario cannot occur on any current Go runtime, so my original severity claim was overstated. Verified against the shipped binaries and go1.26.5 runtime source: (1) the affected functions are leaves whose epilogue never reloads LR from the stack, so normal execution never reads the clobbered slot; (2) the Go runtime never async-preempts assembly functions, so GC stack scanning, runtime.Stack, and goroutine profiles can never observe these frames; (3) the only readers — SIGPROF tracebacks and frame-pointer unwinds — fail soft, truncating the sample rather than throwing. Actual impact: corrupted CPU-profile samples and broken debugger/core-dump backtraces through the zstd decode frames on arm64. No crashes, no data corruption, Parquet/IPC decode output is always correct. The upstream fix (klauspost/compress#1176, description now revised) will flow to arrow-go via a routine dependency bump once released — nothing to prioritise here. Apologies for arriving cold with an alarmist framing.

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