Skip to content

fix(kernel): contain syscall user-memory faults - #952

Merged
forkwright merged 4 commits into
mainfrom
fix/871-fault-contained-uaccess
Aug 26, 2026
Merged

fix(kernel): contain syscall user-memory faults#952
forkwright merged 4 commits into
mainfrom
fix/871-fault-contained-uaccess

Conversation

@forkwright

Copy link
Copy Markdown
Owner

Closes #871

Summary

  • Centralize syscall user-memory access in direction-aware copy_from_user / copy_to_user primitives that validate the complete overflow-checked range against the live caller VAS and PL0 permissions.
  • Use exact ARM unprivileged-transfer fixup sites so a post-validation LDRBT or STRBT abort becomes EFAULT; unrelated PL1 faults still halt.
  • Migrate fd, pipe, socket, futex, time, IPC, UART, exec/argv, stat/getcwd, and signal-frame pointer families. Untrusted counts use bounded transport-sized chunks, and stateful reads use peek/copy/commit or rollback.
  • Make private futex wait queues VAS-scoped, harden exec argv and signal-stack arithmetic against wraparound, and return EFAULT for failed futex copyin.
  • Add a real QEMU syscall permission/fault matrix, raw fixup probes, and the existing kernel-fault negative control to CI and the target ledger.

Acceptance evidence

The QEMU uaccess witness exercises a live caller page table rather than a host predicate:

  • mapped anonymous RW copyin and copyout succeed;
  • a read-only page succeeds as a source and fails as a destination;
  • PROT_NONE, unmapped, stale-after-munmap, and cross-page partial ranges return EFAULT;
  • ARMv7 execute-only mappings are refused (EINVAL) because this MMU cannot express execute-without-data-read, while the pure descriptor tests reject execute-only data access;
  • raw LDRBT and STRBT faults bypass prevalidation and recover only at the registered transfer PCs;
  • the caller continues into the service loop after contained syscall failures;
  • the separate kfault witness still halts on an unrelated PL1 undefined instruction (runner rc 4).

Cross-process and transition coverage includes distinct-L1 address-space isolation, same-VA/different-VAS futex ownership, caller-owned fd/socket fixtures, forked-child copyout, independent exec replacement, fork-then-exec copyin in both old and replacement VASes, and real target mprotect / munmap transitions. Pipe publication and read consumption occur only after successful copyout, satisfying #868's no-side-effect failure boundary.

Verification

  • env -u CARGO_TARGET_DIR scripts/kernel-clippy.sh — all nine ARM feature configurations passed.
  • env -u CARGO_TARGET_DIR scripts/kernel-host-tests.sh — 2,571/2,571 default and 2,575/2,575 debug-console passed; one intentional skip in each.
  • scripts/check-target-test-ledger.sh — 129 rows, 29 both-mechanism, 9 target-only, 2,576 runnable host tests, no drift.
  • scripts/check-witness-extraction.sh, cargo fmt --all --manifest-path crates/thumos/Cargo.toml -- --check, git diff --check, and changed-shell syntax — passed.
  • env -u CARGO_TARGET_DIR scripts/witness/uaccess.sh — passed.
  • env -u CARGO_TARGET_DIR scripts/witness/kfault.sh — passed with expected runner rc 4.
  • env -u CARGO_TARGET_DIR scripts/witness-run-all.shALL KERNEL QEMU WITNESSES: PASS, including fork, exec, and forkexec.
  • kanon gate --full --git-diff origin/main --stamp . — workspace check, dependency audit, clippy, and 997/997 nextest passed; only Kanon lint failed.

Kanon #756 baseline receipt

A fresh linked worktree at exact base a6f855bcc54b2083d05b274f4aa2a3654dadb4a6 reproduced exactly the branch result: 163 violations, 28 suppressed, strict-equivalent 191, with identical rule counts. This is the known repository lint debt tracked by #756. Per the documented exception, the branch was pushed with --no-verify; no Gate-Passed trailer was fabricated.

Truthful residuals

  • UDP recvfrom prevalidates both outputs and preserves the datagram on a payload transfer fault, but a late payload fault can leave the already-copied source-address bytes in user memory; the pair is not byte-atomic.
  • /dev/urandom preserves file data/offset behavior, but its volatile PRNG state may advance if a post-validation copyout fault occurs.
  • The current four-register dispatch ABI supplies zero address arguments to sendto / recvfrom; direct helpers support address buffers. The existing addr_len output and execve envp remain unimplemented/ignored rather than being newly claimed here.
  • fix(security): validate syscall buffers against the caller VAS #890 supplied the earlier caller-VAS validation and its host-fixture corrections; this PR closes the remaining fault-containment, race, migration, and target-witness scope.
  • No fix(thumos): record the KDF parameters a device's secrets were derived under #929 KDF, secrets, encryption, or kinit files are touched.

admin added 3 commits August 26, 2026 16:50
Centralize directional copyin/copyout behind caller-VAS validation and ARM abort fixups, migrate syscall pointer families, and preserve kernel-owned state when output transfers fail. Add host rollback coverage and a real QEMU permission/fault matrix with a raw fixup probe and unrelated-kernel-fault negative control.
Reject signal-stack underflow without context mutation, return EFAULT for invalid futex words, and keep UDP-only recvfrom output validation out of the TCP ABI.

@forkwright forkwright left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Independent semantic review verdict: APPROVE at exact head 317f478162a6c9570f7b230040ddf8739bd00f7c against base a6f855bcc54b2083d05b274f4aa2a3654dadb4a6. GitHub cannot record a formal approval because the shared automation login is also the PR author; this is the immutable-head reviewer verdict.

I independently reviewed the security-sensitive diff and its callers. The live-VAS walk enforces the complete overflow-checked range plus direction-specific PL0 permission; the ARM transfer boundary uses only the two labelled LDRBT/STRBT PCs for recovery, while unrelated PL1 aborts remain fatal. The migrated fd, pipe, socket, futex, time, IPC, exec, and signal paths keep user pointers inside bounded copy helpers. Stateful reads defer queue/OFD consumption until copyout succeeds, pipe publication rolls back, futex keys include the caller page table, and signal copy faults preserve the outer active exception frame.

Independent evidence on Nomos at this exact SHA:

  • env -u CARGO_TARGET_DIR scripts/kernel-host-tests.sh: 2,571/2,571 default and 2,575/2,575 debug-console passed; one intentional skip in each configuration.
  • env -u CARGO_TARGET_DIR scripts/witness/uaccess.sh: PASS with raw fault-fixup recovery, direction split, PROT_NONE/unmapped/cross-page rejection, syscall containment, and continued service-loop progress.
  • Both fmt checks, changed-shell syntax, and git diff --check passed; the review worktree is clean.
  • All hosted required checks are green, including kernel i686 tests/ARM build/QEMU chain, workspace tests, clippy/gates, audit/deny/OSV, and rustfmt.

The disclosed residuals are accurate and bounded: UDP source-address and payload outputs are not pairwise byte-atomic under a late second transfer fault, but the datagram is retained; /dev/urandom volatile PRNG state can advance on a late copyout failure while file offsets remain unchanged; and the existing four-register/address-length/envp ABI limitations are not newly claimed as implemented. None reopens the caller-VAS isolation, privileged-fault containment, or kernel-state commit boundary accepted here. No hardware fidelity is claimed.

No blocking finding.

@forkwright forkwright left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Independent integration verdict: APPROVE at exact head a1e44426a3340eff9f0e1f3b7ffad9ff0e78c0d8 on base edd574d43d8574c23501428d78f56c4c4527cd30.

I verified the remote ref, exact parents (317f478162a6c9570f7b230040ddf8739bd00f7c + edd574d43d8574c23501428d78f56c4c4527cd30), and tree 8de7afcc324169f4faaba24405377fffe169761b. Compared with Git’s synthetic merge, this tree differs only in the two intentional semantic resolutions: the canonical runner now orders boot watchdog kfault uaccess ..., and the extraction guard inventories both watchdog and uaccess.

Every shared/adjacent seam retains both parent invariants: CI contains watchdog then the unrelated-kernel-fault negative control then uaccess; Cargo/main retain all three watchdog probes plus uaccess production/QEMU exclusions; the timer IRQ keeps watchdog observe/decide/pet-or-withhold while the data-abort path keeps exact-PC uaccess recovery ahead of ordinary fatal handling; clippy pairs all four probes with QEMU; and the target ledger retains both witness families without hardware overclaim.

Independent exact-tree evidence on Nomos:

  • both fmt scopes, git diff --check, changed-shell syntax, witness extraction, target ledger, and wiring inventory passed;
  • scripts/kernel-clippy.sh passed all 13 configurations, including the three watchdog probes and uaccess;
  • scripts/witness/watchdog.sh passed controlled reset, frozen-owner expiry, and immutable hung-shutdown expiry;
  • scripts/witness/kfault.sh passed with expected rc 4;
  • scripts/witness/uaccess.sh passed with the modeled watchdog initialized before raw LDRBT/STRBT fixup recovery, full permission matrix, and continued service-loop progress;
  • generated root transcripts were removed and the worktree was clean.

The message-only amend from the locally tested merge preserved the exact tree and parents, and its chore(main): ... subject passes the canonical grammar. The push used the documented #756 exception only after exact main/branch Kanon parity (163 findings, 28 suppressed, strict-equivalent 191); no branch-specific gate failure was bypassed.

No integration blocker. New-head hosted checks are still being monitored. The existing physical AGM M7 watchdog receipt remains explicitly unverified.

@forkwright

Copy link
Copy Markdown
Owner Author

Hosted exact-head receipt complete for a1e44426a3340eff9f0e1f3b7ffad9ff0e78c0d8: all required checks are green. The 8m16s kernel job passed the complete i686/ARM/QEMU chain, including watchdog, unrelated-kfault negative control, uaccess, downstream witnesses, extraction guard, and all feature clippy passes: https://github.com/forkwright/thumos/actions/runs/33020514682/job/98349525368

Remote head remains exact, the review worktree is clean, and GitHub now reports MERGEABLE / CLEAN. This completes the hosted receipt for the independent integration APPROVE; no branch mutation or merge was performed.

@forkwright
forkwright merged commit 437edaf into main Aug 26, 2026
13 checks passed
@forkwright
forkwright deleted the fix/871-fault-contained-uaccess branch August 26, 2026 22:51
forkwright added a commit that referenced this pull request Aug 26, 2026
## Summary

- run the canonical source-only wiring inventory check inside the
branch-protection-required kernel context before the docs-only
exemption, with checkout available on every path
- remove the duplicate check from unrequired rustfmt while preserving
the guarded full post-boot checker
- add one deterministic topology regression that invokes the canonical
checker against a malformed isolated inventory and proves later
build/QEMU steps remain docs-only guarded
- refresh the kernel-core inventory row to describe the uaccess work
landed by #890/#952 and #868, including the remaining
non-atomic/state/ABI residuals rather than treating #871 as a live owner

## Rationale

The admission property belongs to the already-required kernel context: a
malformed docs-only inventory must make that context red without paying
for kernel build or QEMU. The regression checks workflow composition and
reuses `scripts/check-wiring-inventory.sh --no-log`; it does not create
a second inventory checker.

The general closed-owner freshness mechanism is already tracked by
[Kanon #3709](forkwright/kanon#3709), so this
repair corrects the stale row without adding a competing Thumos
mechanism.

## Verification

Exact remote head: `739d67173f040cd5fc7ab173d9b8cb1bf27e08c2`

At the authored repair commit, the ordinary pre-push hook passed fmt,
workspace check, fitness, cargo-deny, workspace clippy, and nextest
(997/997). It stopped only on the known repository-wide Kanon lint
baseline. After the additive merge of current main, an apples-to-apples
linked-worktree comparison was exact: branch and current main each
report 163 violations, 28 suppressed findings, strict equivalent 191,
with identical rule distributions. The one non-force `--no-verify`
feature-branch push was explicitly authorized from that immutable
receipt.

Exact-head scoped receipts:

- `bash -n` and `shellcheck` on `scripts/check-docs-only-kernel-gate.sh`
- topology/malformed negative fixture passes and invokes the canonical
inventory checker
- `scripts/check-wiring-inventory.sh --no-log`: 123 modules / 30
capabilities / 23 witness markers / 9 compiled-only owners
- doc inventory, witness extraction, and kernel build-entrypoint checks
pass
- Actionlint and TOML parsing pass
- `kanon gate --tier=nobuild --force --paths ...`: 0 findings
- `git diff --check`: clean

No `Gate-Passed` trailer is asserted; hosted full admission remains
authoritative.

Closes #944.

Co-authored-by: admin <admin@ardentleatherworks.com>
forkwright added a commit that referenced this pull request Sep 3, 2026
🤖 I have created a release *beep* *boop*
---


## [0.9.1](v0.9.0...v0.9.1)
(2026-09-03)


### Bug Fixes

* **ci:** correct kernel-window remediation
([#945](#945))
([4680d75](4680d75)),
closes [#933](#933)
* **ci:** enforce inventory in required kernel gate
([#955](#955))
([b2c706b](b2c706b))
* **kernel:** contain syscall user-memory faults
([#952](#952))
([437edaf](437edaf))
* **watchdog:** bound failed shutdowns
([#946](#946))
([edd574d](edd574d))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cody Kickertz <cody.kickertz@pm.me>
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.

security: validate syscall buffers against the caller VAS and permissions

1 participant