Skip to content

Integrate safetensors (Rust) into OSS-Fuzz - #16152

Open
ahkarl13 wants to merge 2 commits into
google:masterfrom
ahkarl13:add-safetensors-project
Open

ahkarl13 wants to merge 2 commits into
google:masterfrom
ahkarl13:add-safetensors-project

Conversation

@ahkarl13

@ahkarl13 ahkarl13 commented Sep 18, 2026 •

Copy link
Copy Markdown

Adds an OSS-Fuzz integration for safetensors, the Rust crate used across the ML ecosystem to load model-weight files. It is not currently in OSS-Fuzz.

Updated after review. This PR originally staged its own harness in projects/safetensors/fuzz/ and pointed at huggingface/safetensors. Per @DavidKorczynski's review it now builds the fuzz targets that live in the upstream repository, and the repository's move to the safetensors/ org is reflected in project.yaml. The coordination with maintainers is in the comment below.

What it fuzzes

The crate's untrusted-input surface: SafeTensors::deserialize parses an 8-byte little-endian header length, a JSON metadata header, and each tensor's (dtype, shape, [begin, end)) offset descriptor against the backing buffer, then hands out TensorViews that index into that buffer. The header-length and offset math is the classic out-of-bounds seam for memory-format deserializers, and the slicing layer on top of it (TensorView::sliced_data) does further index arithmetic against a shape the file itself controls.

How it builds

The fuzz targets live upstream in safetensors/fuzz, so build.sh builds whatever that crate declares rather than pinning target names here:

cd "$SRC/safetensors/safetensors"
cargo fuzz build -O --debug-assertions

FUZZ_RELEASE="fuzz/target/x86_64-unknown-linux-gnu/release"
for target in $(cargo fuzz list); do
  cp "$FUZZ_RELEASE/$target" "$OUT/"
done

That picks up fuzz_target_1 today and the two targets in safetensors/safetensors#857 once they land, without this directory having to track names.

--debug-assertions is deliberate: the arithmetic in slice_byte_ranges is where the bugs found so far live, and without it an underflow wraps silently instead of trapping.

Verified locally against current upstream main

$ python3 infra/helper.py build_fuzzers --sanitizer address safetensors
...
++ cargo fuzz list
+ cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_target_1 /out/

$ python3 infra/helper.py check_build safetensors
INFO:__main__:Check build passed.

$ python3 infra/helper.py run_fuzzer safetensors fuzz_target_1 -- -max_total_time=60
Done 21087314 runs in 61 second(s)      # 345k exec/s, peak RSS 120MB, no crashes

Bugs the targets have already found upstream

Details

  • Language: Rust (cargo-fuzz), libFuzzer + AddressSanitizer, x86_64.
  • primary_contact / auto_ccs: ahkarl13@gmail.com — I am not a maintainer, and I'm glad to hand this to one of the safetensors maintainers if they'd rather own the crash reports.

Adds an OSS-Fuzz project for the safetensors Rust crate, which is widely
used to load model-weight files across the ML ecosystem and is not
currently fuzzed.

A libFuzzer target exercises SafeTensors::deserialize, the untrusted-input
entry point that parses the 8-byte little-endian header length, the JSON
metadata header, and each tensor's (dtype, shape, offset) descriptors
against the backing buffer.

Built and run locally with infra/helper.py (build_fuzzers --sanitizer
address; run_fuzzer smoke test).
@google-cla

google-cla Bot commented Sep 18, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@github-actions

Copy link
Copy Markdown

ahkarl13 is integrating a new project:
- Main repo: https://github.com/huggingface/safetensors
- Criticality score: 0.48258

@DavidKorczynski DavidKorczynski left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Are you a maintainer? If not, please do a full coordination with maintainers.
  • Please land the fuzzers in the upstream repository

Per review: the fuzz targets live in safetensors/safetensors (safetensors/fuzz), so drop the copy staged in this directory and build whatever that crate declares via cargo fuzz list. Also point at the repository's current location (it moved from huggingface/ to the safetensors/ org) and turn on debug assertions, since the slicing code this parser exposes is arithmetic-heavy and an underflow otherwise wraps silently.
@ahkarl13

Copy link
Copy Markdown
Author

Thanks for the review — both points addressed, and you were right on both counts.

Am I a maintainer? No. I'm an outside contributor.

Coordination with maintainers. I've opened three things upstream and pointed them at this PR:

The same target also independently rediscovered safetensors/safetensors#843 (an existing report, fix pending in #844) from a random input, which is a reasonable sign the integration would earn its keep.

Landing the fuzzers upstream. They're already there — safetensors/fuzz has had a cargo-fuzz crate all along; it just wasn't wired to anything. So this PR no longer stages its own copy. build.sh now builds whatever that crate declares:

cd "$SRC/safetensors/safetensors"
cargo fuzz build -O --debug-assertions
FUZZ_RELEASE="fuzz/target/x86_64-unknown-linux-gnu/release"
for target in $(cargo fuzz list); do
  cp "$FUZZ_RELEASE/$target" "$OUT/"
done

That way it picks up whatever upstream has, today and after #857 lands, without this directory needing to track target names. --debug-assertions is deliberate: the slicing arithmetic is where both of the above bugs live, and without it an underflow wraps silently instead of trapping.

Two other corrections while I was in here: the repository moved from huggingface/safetensors to safetensors/safetensors, so homepage and main_repo now point at its current location, and dropping the staged fuzz/ directory should also clear the header-check failure.

Verified locally against the current upstream main, not just asserted:

$ python3 infra/helper.py build_fuzzers --sanitizer address safetensors
...
++ cargo fuzz list
+ cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_target_1 /out/

$ python3 infra/helper.py check_build safetensors
INFO:__main__:Check build passed.

$ python3 infra/helper.py run_fuzzer safetensors fuzz_target_1 -- -max_total_time=60
Done 21087314 runs in 61 second(s)      (345k exec/s, peak RSS 120MB, no crashes)

Outside the OSS-Fuzz image, with #856's fix and #844's guard applied, the two targets from #857 run 44.7M and 14.6M executions clean.

Happy to change any of this — including waiting on #857 before this merges, or switching primary_contact to a maintainer once they weigh in.

@ahkarl13 ahkarl13 changed the title Integrate huggingface/safetensors (Rust) Integrate safetensors (Rust) into OSS-Fuzz Sep 21, 2026

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants