[Rehearsal/Fork/POSIX] Validate catch-state and spawn fixes before main-first activation - #1094
Closed
brandonpayton wants to merge 17 commits into
Conversation
The fork instrumenter is a declared input to program builds. Regenerate the program package index so every cache key names the corrected activation-local catch implementation instead of silently reusing artifacts built by the previous instrumenter. Package manifests and ABI remain unchanged.
Contributor
Phase B-1 matrix build status —
|
| Package | Arch | Status | Sha |
|---|---|---|---|
| bc | wasm32 | built | 2e7bb9ff |
| coreutils | wasm32 | built | 33ca2b6c |
| dash | wasm32 | built | 2ec3ac4e |
| diffutils | wasm32 | built | 5fd58502 |
| file | wasm32 | built | 4c75af58 |
| findutils | wasm32 | built | e9894ce9 |
| gawk | wasm32 | built | 85ef6c0f |
| grep | wasm32 | built | ccfc4ba2 |
| m4 | wasm32 | built | a7063dcf |
| make | wasm32 | built | 49a0f581 |
| ncurses | wasm32 | built | 1d43a87f |
| posix-utils-lite | wasm32 | built | 2308c7f1 |
| sed | wasm32 | built | 7ba13641 |
| bash | wasm32 | built | 1d813d7f |
| rootfs | wasm32 | built | 4feb895e |
Auto-generated; replaced on each push. Raw data in the publish-status workflow artifact.
Add a maintainer-controlled staging scope that validates the current program projection, force-selects every rootfs wasm32 dependency generation into the PR release, and visibly defers unrelated program generations. Keep the limited run from authorizing prepare-merge and cover malformed, stale, missing, duplicate, identity-mismatched, and non-wasm32 projections.
Exercise the fail-closed path explicitly when the validated program index has no rootfs package projection.
Fail the limited prepublication gate if its target release changes after preflight instead of hiding the race with a canonical-release supplement.
SYS_SPAWN used the channel-sized scratch address for every blob while checking only the remaining WebAssembly memory. Environments above one channel therefore overwrote adjacent Rust heap state and could make a completed child disappear from waitpid. Validate the complete transport, preserve the 4 MiB ARG_MAX contract, and lazily allocate one bounded kernel-owned buffer for large blobs. Reuse is safe because copy and kernel parsing are synchronous on the kernel worker event loop. Add boundary-focused host coverage, a two-spawn executable regression, and architecture documentation.
LAMP, nginx+PHP, and WordPress image builds boot NodeKernelHost for opcache prewarming or database installation. Their declared host/src input intentionally makes kernel-worker behavior part of the generated image identity. Regenerate only those three projected cache identities after the large SYS_SPAWN transport correction. The existing import-closure contract verifies that kernel-worker changes affect exactly these image packages.
This was referenced Jul 25, 2026
brandonpayton
marked this pull request as draft
July 25, 2026 08:05
Member
Author
|
Closing this rehearsal PR so it cannot be mistaken for the activation or publication path. Its commits and CI remain useful test evidence, but graph reachability is not artifact provenance. The replacement will land normally from current |
This was referenced Jul 25, 2026
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.
Why
The ABI 42 Bash bottle crashes during the in-guest Homebrew lifecycle because code generated by
wasm-fork-instrumentlets ordinary exception handling overwrite unrelated memory.For every supported plain WebAssembly catch, the instrumenter injects code that remembers the selected catch arm and its scalar payload. That generated code used the module-wide
_wpk_fork_bufpointer even during normal catch execution, not just duringfork(). The pointer is zero before the first fork and may name released, reusable continuation storage after replay. Recursive calls also shared the same catch tuple. A normal catch could therefore corrupt low memory, stale continuation storage, or another activation's state.This is a Kandelo instrumenter bug, not behavior written by Bash and not an unavoidable property of WebAssembly exceptions. Clearing the pointer alone is insufficient because address zero is valid linear memory; the remembered values must belong to the function call that caught them.
Validation also exposed a separate
posix_spawnownership bug. The host copied every spawn blob into a 65,608-byte allocation but checked only whether it fit in the kernel's entire WebAssembly memory. The current development environment produces an 83,772-byte blob, so it overwrote the adjacent Rust heap. The resulting corruption could make a child complete and then disappear fromwaitpid.What changed
Activation-owned catch replay
-1for the mixedcatch_refpath instead of consulting stale table contents._wpk_fork_bufbefore returning to normal execution as a secondary lifetime defense.Bounded spawn transport
execve.lamp,nginx-php-vfs, andwordpress. The package input-closure test enforces this dependency.Focused prepublication staging
stage-rootfs-closure-onlymode that derives the exact current 15-package wasm32rootfsclosure and records every deferred package.merge-gateremains pending andprepare-mergerejects the limited label.ABI decision
This change does not alter exported functions, descriptor fields, state values, imports, host parsing rules, or syscall wire formats.
Each instrumented artifact already declares its own module-specific prefix and frame sizes. Existing artifacts retain their existing generated code; rebuilt artifacts describe the smaller prefix and larger activation frames. Fork-using packages must be rebuilt to receive the correction.
The spawn fix uses the existing
kernel_alloc_scratchexport and existing spawn wire format. It corrects ownership and bounds in the shared Node/browser host path.Reference-typed locals, caught references, mutable reference globals, and arbitrary mutable table state remain a separate correctness boundary. A fresh fork child has a fresh Wasm instance, so raw auxiliary-table references cannot be treated as preserved. That broader work is being handed to a separate ABI-epoch workstream that will reconstruct supported scalar exception state and reject non-serializable reference state before execution. This PR does not claim that work is complete.
An independent audit after this head was assembled also found pre-existing allocation-capacity hazards in PTY input, initial-cwd, and vectored/socket I/O scratch transfers. They are not caused or changed by this PR and are being handled in a separate systematic scratch-ownership workstream. This PR claims the complete spawn transport fix, not repository-wide hostile-input scratch safety.
Regression coverage
Activation ownership:
Spawn transport:
waitpidreaping.Validated on the integrated product tree:
1d813d7f9db4979fc5c0eef6b37e213cb73a42395757cc92ef8ebeee2e62913d; andFor the real Bash module, activation ownership reduces the instrumented file from 3,360,883 to 3,348,482 bytes (12,401 bytes, or 0.37%). The gzipped difference is 769 bytes. Performance was not measured.
GitHub staging run
30144350443completed successfully on this exact head: all 15 intended wasm32 rootfs generations passed, the library matrix stayed skipped, no unrelated package entered the matrix, and final release materialization succeeded.Exact Node.js/Chromium shell run
30144350446did not reach either boot proof. It resolved or built 40 of 47 selected generations, then failed for two coherent-tree reasons:This is useful pre-merge evidence rather than final release evidence. It confirms that the focused 15-entry rootfs staging path is green and that the earlier disk-clobber failure is gone, while also showing that this pre-main integration tree is not a coherent final package-production source. The corrected Bash bottle, exact browser-input closure, Node.js/Chromium boot proof, and live first-/third-party Homebrew lifecycle remain required.
Integration note
This PR is intentionally based on
integration/homebrew-post-frozen-product-batch-qk044, but Git reachability is not sufficient package provenance. Neither an ancestry-only merge nor a protected pre-main source tag would make bottles built from this head bottles built frommain.Accordingly:
pr-1094-stagingartifacts are validation evidence only;main, without prematurely switching the product shell.After that merge, every final dependency archive, bottle, index entry, and browser closure must be rebuilt from the exact resulting
maincommit. The tap controller must pin that same commit. The limited staging mode proves the bounded rootfs build behavior; it does not authorize merge or publication.Brandon approved the corrected fork/spawn change, but the integration and publication sequence is being replaced by this main-first provenance path. This PR must not be merged, closed, or used as a final artifact source until that split is complete and its staging evidence is preserved.