Skip to content

wasm test run exposes allocate-before-validate in display-list parser; catch_unwind backstop absent on wasm #142

Description

@FarhanAliRaza

Summary

The Rust core was cross-validated on Linux (glibc + musl), Windows (mingw cross-link), macOS (cargo check, both arches), and wasm. Compilation and linking are clean on every target. Running the test suite on wasm (wasm32-wasip1-threads under wasmtime v24 with -S threads) surfaced two runtime failures — 101/103 pass:

1. Display-list parser allocates before validating (aborts on wasm, memory-DoS vector on native)

raster::tests::malformed_buffer_is_rejected_not_panicked aborts the wasm module with:

memory allocation of 1212696648 bytes failed

Reader::pts (src/raster.rs:1360) does Vec::with_capacity(n) where n is an untrusted u32 from the command buffer, before checking that 8*n bytes are actually present. A 5-byte buffer claiming ~151M points reserves ~1.2 GB up front.

  • On 64-bit native this "passes" only via OS overcommit: the reserve succeeds, the parse hits end-of-buffer, returns None, memory is freed untouched.
  • On wasm32 (no overcommit, 4 GB linear-memory ceiling) the allocation fails and Rust aborts — allocation failure is not catchable.
  • Same allocate-before-validate pattern for the gradient stop count (src/raster.rs:~1915) and both dash counts (~1933, ~2378).

This contradicts the "malformed op is rejected, not faked/panicked" contract, and is a hostile-input memory-DoS vector on native too (a display list can force multi-GB transient reserves).

Proposed fix: clamp every count-driven reserve to what the remaining buffer can encode, e.g. a Reader::bounded(count, item_size) helper returning count.min(remaining / item_size); parse loops already bail correctly on truncation. (Fix was prototyped and passes both native 103/103 and the wasm raster test; withheld pending this issue.)

2. ffi_guard panic backstop does not exist on wasm

tests::ffi_guard_maps_panic_to_sentinel aborts: ffi_guard (src/lib.rs:48) relies on std::panic::catch_unwind, but wasm32 targets have panic=abort semantics — there is no unwinding to catch, so the deliberate test panic kills the module.

  • Platform property, not a code bug.
  • Test should be gated #[cfg(panic = "unwind")].
  • Worth recording in the dossier: on any future wasm build (e.g. Pyodide/JupyterLite), an internal panic aborts the wasm instance — the C-ABI sentinel backstop only exists on unwinding platforms.

3. (minor) fuzz_parallel_matches_serial on threadless wasm

On plain wasm32-wasip1 (no threads) this test force-drives threads = 2/3/5 into the impls and aborts on thread::scope spawn. Production is unaffected (par_threadsavailable_parallelism → 1 → serial path). Passes fine on wasm32-wasip1-threads. Only relevant if threadless wasm ever becomes a tested config; would need a cfg gate.

Cross-platform validation matrix (2026-07-22)

Target Method Result
x86_64-unknown-linux-gnu host + clean rust:1-slim Docker, build + tests ✅ 103/103
x86_64-unknown-linux-musl rust:alpine Docker, build + tests ✅ 103/103 (cdylib needs -C target-feature=-crt-static)
x86_64-pc-windows-gnu Docker + mingw-w64 full cdylib link xy_core.dll links
x86_64-pc-windows-msvc cargo check
x86_64 / aarch64-apple-darwin cargo check (no Apple SDK in Docker)
wasm32-unknown-unknown full build
wasm32-wasip1-threads wasmtime v24 -S threads, full test run ⚠️ 101/103 (items 1–2 above)

Repro for the wasm test run:

rustup target add wasm32-wasip1-threads
curl -sSf https://wasmtime.dev/install.sh | bash -s -- --version v24.0.0   # newer wasmtime removed -S threads
CARGO_TARGET_WASM32_WASIP1_THREADS_RUNNER='wasmtime run -S threads' \
  cargo test --release --target wasm32-wasip1-threads

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