Repository navigation
fix: get CI green again — rustix RUSTSEC-2026-0486 and the ts spin tenant - #2916
Conversation
The nightly `ts-stress-soak` job went red on its first scheduled run while
every PR run of the same test stayed green.
`ts_mixed_hostile_and_good_tenants_stay_isolated` asserted that the spin
tenant (`while (true) {}`, 300 ms `max_duration`) exits 124. But ZapCode
charges one allocation per VM stack push and never decrements the counter
(`zapcode_vendor/sandbox.rs`, `track_allocation`), so `max_allocations` is a
per-run budget of pushes, not a live heap bound. The loop spends about one per
iteration, so the two limits race: a release build burns the 1,000,000 default
in a few milliseconds and exits 1 with "allocation limit exceeded", while a
debug build reaches 300 ms first and exits 124. PR CI runs debug, the soak runs
release, so the assertion picked the winner of a build-speed race.
Both outcomes are correct bounded termination, so the spin tenant now asserts
only that it stopped on one of its own limits. Dropping a precise assertion
would have cost coverage, so each limit is instead pinned on its own, and
deterministically in either profile:
- `ts_runaway_loop_stops_on_the_wall_clock` raises the allocation budget past
what a 300 ms loop can spend, so the clock binds and exit 124 is exact.
This is the case the soak was really meant to cover (TM-TS-027).
- `ts_runaway_loop_stops_on_the_allocation_budget` lowers it to 50,000 against
a 60 s limit, so the push budget binds and exit 1 is exact.
No product change: the limits already bounded the loop and no tenant could
reach the host abort either way.
Before: release soak fails in 0.33s, exit 1 "allocation limit exceeded" vs
expected 124, on 3 of 4 spin tenants.
After: 7/7 green in release at the nightly's 300 rounds (112.91s) and 7/7
green in debug (2.09s).
Knowledge records that `max_allocations` bounds operations rather than heap,
and why the spin assertion is deliberately loose.
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
bashkit | 315e2a6 | Commit Preview URL Branch Preview URL |
Nov 11 2026, 10:03 AM |
The Audit job went red on every PR and on main without any change to the repository: RUSTSEC-2026-0486 was published against rustix on 2026-10-27, and `scripts/audit-lockfiles.sh` reads the advisory database live. This is exactly the case the scheduled advisory scan exists for. The advisory is an unsoundness in six xattr functions (`fgetxattr`, `flistxattr`, `getxattr`, `lgetxattr`, `listxattr`, `llistxattr`), which mark unwritten bytes as initialized when handed a zero-length buffer. It covers `>=1.0.0, <1.1.6`, and the scan runs `cargo audit --deny unsound`, so it fails rather than warns. Three lockfiles carried an affected version, and all three move to the patched 1.1.6: - `Cargo.lock` 1.1.4 - `crates/bashkit/fuzz/Cargo.lock` 1.1.5 - `examples/hyperlight/host/Cargo.lock` 1.1.4 rustix is transitive everywhere (tempfile, polling, memfd and four others), and bashkit calls none of the six functions, so this is a supply-chain hygiene bump rather than a reachable defect. A patched release exists, so nothing is suppressed and the threat model needs no new entry. Also folds in the `rust-dependencies` group bump from #2913, which was blocked on a stale `supply-chain/imports.lock`: toml 1.2.0 -> 1.2.1, serde_spanned 1.1.2 -> 1.1.3 and toml_datetime 1.1.2 -> 1.1.3. All three are already publisher-trusted to Ed Page (epage) through 2027-05-16, so refreshing the publisher records in imports.lock is all `cargo vet` needed - no new audit or exemption. rustix was already an exemption at 1.1.4 and stays one at 1.1.6: nobody reviewed the 1000-line upstream diff, and an audit entry would assert a source review that did not happen. Verified: `scripts/audit-lockfiles.sh` exits 0 with the rustix denial gone (remaining atomic-polyfill, ttf-parser and crypto-bigint warnings are the pre-existing allowed ones), `cargo vet --locked` succeeds (97 fully audited, 79 partially audited, 585 exempted), the workspace builds with http_client,ssh,sqlite,typescript, and the main test slice passes ~7,900 tests. The only local failure is `ssh_supabase_connects`, which dials the live public supabase.sh over port 22; this sandbox refuses outbound 22, and that test is a network gate that passes in CI.
Review summary (PR #2916, head
|
Review follow-up. The comment pointed at SPIN_ALLOCATIONS to explain which limit stops this tenant, but the tenant is built by `tenant(16, 300)`, which keeps the default `max_allocations`; SPIN_ALLOCATIONS is only the budget for `ts_runaway_loop_stops_on_the_wall_clock`. A reader following the pointer would have tried to tune this tenant through a constant that does not govern it. Names the default budget instead and keeps the explanation local. No assertion or behavior change.
Two red CI signals, two independent causes, neither from a code change in this
repo. Kept as two commits.
What changed
1.
fix(deps): bump rustix to 1.1.6 for RUSTSEC-2026-0486— fixes theAuditjob, currently red on every PR and on main.RUSTSEC-2026-0486 was published against rustix on 2026-10-27 and
scripts/audit-lockfiles.shreads the advisory database live, so the job wentred with no repository change. It is an unsoundness in six xattr functions
(
fgetxattr,flistxattr,getxattr,lgetxattr,listxattr,llistxattr), which mark unwritten bytes as initialized when handed azero-length buffer, covering
>=1.0.0, <1.1.6. The scan runscargo audit --deny unsound, so it fails rather than warns.Three lockfiles carried an affected version; all three move to the patched
1.1.6:
Cargo.lockcrates/bashkit/fuzz/Cargo.lockexamples/hyperlight/host/Cargo.lockrustix is transitive everywhere (tempfile, polling, memfd and four others) and
bashkit calls none of the six functions, so this is supply-chain hygiene rather
than a reachable defect. A patched release exists, so nothing is suppressed
and the threat model needs no new entry.
This also folds in the
rust-dependenciesgroup bump from #2913, which wasblocked on a stale
supply-chain/imports.lock: toml 1.2.0 → 1.2.1,serde_spanned 1.1.2 → 1.1.3, toml_datetime 1.1.2 → 1.1.3. All three are
already publisher-trusted to Ed Page (epage) through 2027-05-16, so refreshing
the publisher records was all
cargo vetneeded — no new audit or exemption.rustix was already an exemption at 1.1.4 and stays one at 1.1.6: nobody
reviewed the 1000-line upstream diff, and an audit entry would assert a source
review that did not happen. #2913 can be closed as superseded.
2.
test(ts): stop the spin tenant racing two resource limits— fixes thenightly
ts-stress-soakjob. Test-only; no product change.ts_mixed_hostile_and_good_tenants_stay_isolatedasserted that its spintenant (
while (true) {}, 300 msmax_duration) exits 124. But ZapCodecharges one allocation per VM stack push and never decrements the counter
(
zapcode_vendor/sandbox.rs,track_allocation), somax_allocationsis aper-run budget of operations, not a live heap bound — that is
max_memory.The loop spends about one push per iteration, so the two limits race:
allocation limit exceededPR CI runs debug; the soak runs release. The assertion pinned 124, so it could
only ever hold in debug. Both outcomes are correct bounded termination, so the
spin tenant now asserts only that it stopped on one of its own limits, and each
limit is instead pinned on its own, deterministically in both profiles:
ts_runaway_loop_stops_on_the_wall_clockraises the allocation budget pastwhat a 300 ms loop can spend, so the clock binds and exit 124 is exact. This
is the case the soak was really meant to cover (TM-TS-027).
ts_runaway_loop_stops_on_the_allocation_budgetlowers it to 50,000 againsta 60 s limit, so the push budget binds and exit 1 is exact.
Why they are together
The
Auditgate runs on every PR, so no PR — including a standalone version ofcommit 2 — can go green until the rustix bump lands. Commit 2 was pushed first
and its CI run showed exactly one failure,
Audit, on the advisory above.Before / After
Audit — before (this PR's own first CI run, and every other open PR):
...repeated for
crates/bashkit/fuzz/Cargo.lock(1.1.5) andexamples/hyperlight/host/Cargo.lock(1.1.4).After,
./scripts/audit-lockfiles.shlocally:The rustix denial is gone; the remaining warnings are the pre-existing allowed
ones.
cargo vet --lockedalso succeeds:Vetting Succeeded (97 fully audited, 79 partially audited, 585 exempted).Nightly soak — before, in release:
It failed in 0.33 s — the allocation budget was gone long before the 300 ms
clock.
After, release at the nightly's 300 rounds
(
BASHKIT_STRESS_ROUNDS=300 cargo test --release -p bashkit --features typescript --test integration -- ts_tenant_stress):And in debug, as PR CI runs it:
test result: ok. 7 passed; 0 failed; finished in 2.09s.Risk
diff touches only rustix and the three toml-family crates and nothing else.
rustix 1.1.6 is a patch release whose only relevant change is the soundness
fix. Workspace builds with
http_client,ssh,sqlite,typescriptand the maintest slice passes ~7,900 tests.
mixed test's spin tenant, and the coverage it gave up is now held by two
stricter tests. A broken wall clock would leave
ts_runaway_loop_stops_on_the_wall_clockhanging until the job timeoutrather than failing fast, which is the usual failure mode for a timeout test.
Checklist
Verified locally:
./scripts/audit-lockfiles.sh(exit 0),cargo vet --locked,cargo fmt --check,cargo clippy --all-targets --features typescript,http_client,ssh,sqlite -- -D warnings,just check-okf,just check-doc-links,cargo build --workspace --features http_client,ssh,sqlite,typescript,cargo test --workspace --lib --bins --tests --features http_client,ssh,sqlite(~7,900 pass; sole failure is
ssh_supabase_connects, which dials the livepublic supabase.sh on port 22 — refused in this sandbox, a network gate that
passes in CI), and the ts stress tests in both profiles as shown above.