Skip to content

fix(driver-portable): guard GGUF tensor bounds to surface truncation instead of SIGBUS#423

Merged
shsym merged 1 commit into
pie.app/v1-base-shmemfrom
task/688-gguf-bounds-guard
Jun 16, 2026
Merged

fix(driver-portable): guard GGUF tensor bounds to surface truncation instead of SIGBUS#423
shsym merged 1 commit into
pie.app/v1-base-shmemfrom
task/688-gguf-bounds-guard

Conversation

@shsym

@shsym shsym commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Problem

When the embedded portable driver loads a truncated or corrupt GGUF, the driver thread dies with a SIGBUS and the process exits with no surfaced cause.

GGUFArchive maps the whole model file with mmap and hands out tensor pointers as mmap_base + data_offset + tensor_offset. gguf_init_from_file runs with no_alloc=true, so it validates only the metadata header and never touches the tensor-data section. A GGUF whose declared tensor region runs past the on-disk size therefore passes init and then SIGBUSes on the first tensor access — an mmap read beyond the file's last mapped page (for example, the token_embd dequant reads at load time).

Because the embedded driver runs as a thread inside the engine process with install_signal_handlers=0, that SIGBUS kills the whole process. It bypasses the C++ run_inproc try/catch (which only catches std::exception) and the host's startup-exit detection (which only catches clean nonzero returns, not signals). The result is an opaque signal death with the real cause unsurfaced.

Note: this is unrelated to python_snapshot / the Python WASM runtime — that path is guarded separately and is never reached on a missing runtime.

Fix

Add a bounds check in the GGUFArchive constructor, before any tensor is read: for each tensor, assert data_offset + tensor_offset + nbytes <= mapped size (overflow-safe), otherwise throw a std::runtime_error naming the offending tensor and the sizes.

The throw propagates through the driver's run_inproc catch, which prints [pie-driver-portable] fatal: <reason> to stderr and returns nonzero, so the cause now reaches the host instead of an opaque signal death. Covers single-file and sharded loads (each shard runs through the same constructor).

Existence, magic, and header validation are already handled by gguf_init_from_file; this closes the remaining truncation/bounds gap.

Test

Adds portable_gguf_archive: writes a valid one-tensor GGUF, truncates it into the data section, and asserts the constructor throws a "truncated or corrupt" error, while the intact file opens cleanly.

Verification

  • portable_gguf_archive C++ test: PASS, and mutation-proven (removing the guard makes the test fail rather than SIGBUS).
  • cargo build -p pie-server --release with PIE_PORTABLE_METAL=1: PASS — the guard compiles in the real portable build and the engine binary links.
  • Downstream app lint + unit suite (828 tests, 9 skipped): PASS.
  • HTTP E2E was blocked only by an orthogonal chat-apc prebuilt-wasm freshness gate (the consuming tree sits on this base while the committed prebuilt is stamped against an older pin); it trips on the bare base too and is independent of this change.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8779658-3d6b-4b32-8b4b-67b2b8f5856c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/688-gguf-bounds-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ingim

ingim commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Do truncated or corrupted GGUF files come up often enough that we should plan around them? I don't have a great feel for how frequent this is.

If so, I'm wondering if it make sense for Pie to just reject them. dealing with the problematic gguf binaries feels like out of pie's responsibility

@shsym
shsym force-pushed the task/688-gguf-bounds-guard branch from 4b28594 to eebaaf9 Compare June 16, 2026 20:20
A GGUF whose declared tensor region runs past the on-disk size passes
gguf_init_from_file (no_alloc=true validates only the metadata header)
yet SIGBUSes on first tensor read — an uncatchable fault that kills the
embedded driver thread with no surfaced cause. Bounds-guard every tensor
extent against the mapped file size in the GGUFArchive constructor and
throw a clean std::runtime_error so the failure reaches stderr.
@shsym
shsym force-pushed the task/688-gguf-bounds-guard branch from eebaaf9 to 43bf942 Compare June 16, 2026 20:40
@shsym
shsym merged commit 24913ad into pie.app/v1-base-shmem Jun 16, 2026
7 checks passed
@shsym
shsym deleted the task/688-gguf-bounds-guard branch June 16, 2026 20:43
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