fix(npz): null memcpy on a zero-row Fortran-order .npy#87
Merged
Conversation
The per-column gather in build_2d_table sizes its scratch buffer to
rows * item_size. At zero rows the buffer is empty, so tmp.data() is null,
and the Fortran-order branch then calls memcpy(nullptr, src, 0). A zero
length does not make a null argument legal — both memcpy parameters are
declared non-null — so this is undefined behaviour, and UBSan traps it:
main.cpp:9721: runtime error: null pointer passed as argument 1,
which is declared to never be null
Found by the .npy fuzz harness from #85; it turned up on an unrelated PR's
fuzz smoke step, which is time-bounded and so explores a different corpus
each run. The C-order branch was already a no-op at zero rows; both are now
guarded at rows > 0 so the intent is explicit.
Not only reachable from a crafted file — this is a legitimate archive:
np.savez(f, a=np.asfortranarray(np.zeros((0, 3))))
Minimal repro (.npy v1.0, fortran_order True, shape (0, 3), no data bytes)
is what the new fixture encodes.
Fixture tiny.zerorow.npz carries both memory orders; the suite asserts each
tab opens with exit 0 and reports 0 rows, so the ASan/UBSan CI job is the
regression test rather than depending on the fuzzer reaching the same input
again. 379 tests pass, and the same suite reruns green under ASan+UBSan.
fuzz_npy is clean over 1.48 M iterations seeded with the repro.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found by the
.npyfuzz harness from #85. It surfaced on the fuzz smoke step of an unrelated PR (#86) — that step is time-bounded (-max_total_time=40), so it explores a different corpus each run and this had simply not been reached before.The bug
The per-column gather in
build_2d_tablesizes its scratch buffer torows * item_size. At zero rows the buffer is empty, sotmp.data()is null — and the Fortran-order branch then callsmemcpy(nullptr, src, 0).A zero length does not make a null argument legal: both
memcpyparameters are declared non-null, so this is undefined behaviour, and UBSan traps it.This is not only reachable from a crafted file. It is a perfectly legitimate archive:
The fix
Guard the gather at
rows > 0. The C-order branch was already a no-op at zero rows (for r < rowsdoesn't execute); both are guarded so the intent is explicit rather than accidental.Verification
tiny.zerorow.npzcarries both memory orders (empty_f,empty_c); the suite asserts each tab opens with exit 0 and reports 0 rows. That makes the ASan/UBSan CI job the regression test, rather than depending on the fuzzer reaching the same input again.fuzz_npyis clean over 1.48 M iterations seeded with the minimal repro (a v1.0.npy,fortran_order: True,shape: (0, 3), no data bytes).This is the third and fourth-plus finding from the memory-safety pair added in #80/#84/#85, and the same null-
memcpyclass as the LociSSD v4 empty-block fix in #84.🤖 Generated with Claude Code