Skip to content

B5 syscall boundary: error taxonomy (T-020) + EL0→EL1 SVC dispatch (T-021)#34

Merged
cemililik merged 12 commits into
mainfrom
t-021-syscall-dispatch
May 29, 2026
Merged

B5 syscall boundary: error taxonomy (T-020) + EL0→EL1 SVC dispatch (T-021)#34
cemililik merged 12 commits into
mainfrom
t-021-syscall-dispatch

Conversation

@cemililik
Copy link
Copy Markdown
Collaborator

@cemililik cemililik commented May 29, 2026

Opens the B5 syscall boundary. This PR bundles two tasks per the maintainer's request — they are reviewable as two distinct commit groups (T-020 is the pure-Rust error-taxonomy foundation; T-021 is the security-critical hardware-facing trap/dispatch path). ADRs 0030 (syscall ABI + K2-5 taxonomy split) and 0031 (five-syscall v1 set) are Accepted and instantiated here.

Commit map

Commit Task What
476710b docs(adr): propose ADR-0030/0031
93d5960 docs(adr): accept ADR-0030/0031
d20e6d0 T-020 feat(ipc): split IpcError::InvalidCapabilityStaleHandle / WrongObjectKind / MissingRight
324457a T-020 feat(cap): redact Capability / CapObject Debug (K3-9)
4777f9a T-020 test(ipc): pin StaleHandle + WrongObjectKind on ipc_cancel_recv
1df1b52 T-020 docs(roadmap): T-020 In Review; narrow B5 acceptance to the current-EL proxy
7b35ed6 T-020 test(ipc): make wrong-kind tests prove kind-before-rights
806c966 T-021 feat(syscalls): EL0→EL1 SVC dispatch — trampoline, panic-free dispatcher, copy-user
5145d4d T-021 test(syscalls): review-round follow-up — +4 dispatch tests, compile-time payload guard

T-020 — syscall error taxonomy (the pure-Rust foundation)

  • Splits IpcError::InvalidCapability into three handleable variants (StaleHandle / WrongObjectKind / MissingRight), validation order resolve → kind → rights, across ipc/mod.rs + sched/mod.rs + tests.
  • Redacts the Capability and CapObject Debug impls so a userspace-reachable log path cannot leak a kernel object's slot index / generation.

T-021 — EL0→EL1 SVC dispatch (the security-critical half)

New architecture-agnostic, panic-free, host-tested kernel syscall module:

  • error.rsSyscallError composing CapError / IpcError via From, with a stable numeric status encoding (0=Ok, 1-3 top-level, 0x10x Cap, 0x20x Ipc; exhaustive-without-wildcard so a new variant breaks the build).
  • abi.rsSyscallNumber::decode (the v1 set; number 0 reserved-invalid; the release debug-gate on console_write via cfg!(debug_assertions)), the register frames, and value↔register packing (Message, outcomes, Option<CapHandle> with the u64::MAX null sentinel).
  • user_access.rsUserAccessWindow + validated copy_from_user / copy_to_user (range-check-then-copy; wrap + zero-length handled; never derefs an unvalidated user pointer).
  • dispatch.rs — the panic-free dispatcher + per-syscall handlers + the debug-console capability check; control-plane syscalls (task_yield / task_exit) return a SyscallEffect directive rather than touching the scheduler.

Capability surface: CapObject::DebugConsole (singleton, no handle) + CapRights::CONSOLE_WRITE (bit 7) + CapHandle::from_raw (ABI-decode constructor; reconstructed handles are validated by lookup).

BSP (hardware-facing): tyrne_sync_trampoline (vectors.s) installed at both VBAR_EL1+0x200 (current-EL, the B5 path) and +0x400 (lower-EL AArch64, the B6 EL0 path) — saves the full x0x30 + SP_EL0 + ELR_EL1 + SPSR_EL1 frame, routes ESR_EL1.EC == SVC64 to a Rust syscall_entry, else to the existing panic path. SyscallTrapFrame (272 B, #[repr(C)], const-asserted). kernel_entry runs an EL1 kernel-stub SVC smoke.

Security properties verified

An adversarial multi-agent review-round + first-hand tracing confirmed no input a B5 caller can supply crashes the kernel, bypasses a capability check, or dereferences an unvalidated pointer:

  • Panic-free: every path from x8/x0x5/a forged handle/a huge-or-zero len/a wrapping pointer terminates in a typed SyscallError value.
  • Capability-gated (P1/P4): console_write validates the debug-console cap (resolve → kind → CONSOLE_WRITE) before any range check or output; send/recv gate inside ipc_send/ipc_recv.
  • No unvalidated deref: copy_* validate [ptr, ptr+len) against the active-AS window (half-open bounds, wrap via checked_add, zero-length short-circuit) before the single copy_nonoverlapping.

Gates (all green, first-hand)

cargo fmt --check · cargo host-clippy -D warnings · cargo kernel-clippy -D warnings · cargo kernel-build · host tests 240 (43 hal + 240 kernel + 53 test-hal) · cargo test --release (release debug-gate path) · cargo miri test --workspace --exclude tyrne-bsp-qemu-virt clean (0 UB) · mutation-check (the new transfer-cap test is non-vacuous).

QEMU smoke (debug): the EL1 kernel-stub issues two SVC #0s — console_write emits its buffer via the syscall path (status 0x0, 63 bytes) + a reserved-invalid number returns BadSyscallNumber (0x1). -d int,unimp,guest_errors shows exactly two SVC exceptions at the current-EL vector (ESR 0x15/0x56000000 = SVC64, EL1→EL1), clean ERET, no new fault class; the cooperative demo still runs to tyrne: all tasks complete.

Audit entries

  • UNSAFE-2026-0029 — the SVC sync trampoline asm + syscall_entry frame access.
  • UNSAFE-2026-0030 — the validated copy-from/to-user byte move.

Scope / deferred (B6)

The real EL0 +0x400 round-trip (EL0↔EL1 transition + copy-user against a separate userspace TTBR0_EL1) is wired but runtime-verified in B6 per ADR-0030 §Simulation. The review-round's three B6 carry-forward gates are tracked in phase-b.md §B6 ("T-021 carry-forward gates"): per-task console_write window + per-page user-VA translation (return FaultAddress, never panic), SP_EL1 init on the +0x400 entry, and the SYSCALL_STUB_TABLE → current-task-table swap.

Security-relevant (the EL0→EL1 trust boundary) — flagged for explicit security review per CLAUDE.md §non-negotiable #1.

Refs: ADR-0030, ADR-0031
Audit: UNSAFE-2026-0029, UNSAFE-2026-0030

🤖 Generated with Claude Code

Summary by Sourcery

Implement the kernel-side syscall boundary with a panic-free SVC-based dispatch path, refine IPC error taxonomy and capability diagnostics for userspace, and extend the BSP to route SVC traps through a new full-register trampoline and syscall entry, with comprehensive tests and documentation.

New Features:

  • Introduce a syscall module with a panic-free EL0→EL1 SVC dispatch path, including ABI decoding, syscall error encoding, and validated user-memory access
  • Add a debug-console capability and associated CONSOLE_WRITE right, and wire a debug-only console_write syscall through the new syscall dispatcher

Bug Fixes:

  • Refine IPC capability error handling by splitting InvalidCapability into distinct StaleHandle, WrongObjectKind, and MissingRight variants, and update IPC and scheduler paths accordingly

Enhancements:

  • Redact capability and CapObject debug output to avoid leaking kernel object identities via diagnostics
  • Extend exception handling to route SVC sync exceptions through a new full-register save/restore trampoline shared by current-EL and lower-EL paths
  • Document the syscall ABI, initial syscall set, and updated exception/syscall behaviour across ADRs, architecture docs, roadmap, and glossary entries

Documentation:

  • Add and update ADRs and architecture documentation for the syscall ABI, initial syscall set, error taxonomy split, and security model, including simulation and forward-compatibility notes

Tests:

  • Add extensive host tests for syscall ABI encoding/decoding, dispatcher behaviour, capability gating, user-access validation, and IPC error taxonomy ordering, plus a BSP-level SVC smoke test from an EL1 kernel stub

Summary by CodeRabbit

  • New Features
    • EL0→EL1 syscall boundary with a panic-free dispatcher, initial syscall set (send, recv, task_yield, task_exit, console_write), debug-console support, and validated user-memory copy helpers.
  • Behavior
    • Stable syscall ABI: register-based args/returns, typed non-zero error/status codes, safe handling of bad syscall numbers and user faults (no panics).
  • Documentation
    • New ADRs and design/task docs added; roadmap, architecture, glossary, audits, and task indices updated.
  • Security
    • Capability diagnostics redact kernel object identities; finer-grained IPC error taxonomy.

Review Change Stack

cemililik and others added 9 commits May 29, 2026 07:03
ADR-0030 settles the EL0->EL1 syscall calling convention (x8 = number,
x0-x5 args, x0 status + x1-x7 payload, SVC #0), the dedicated-status
error encoding, and the K2-5 split of IpcError::InvalidCapability into
StaleHandle / WrongObjectKind / MissingRight (with the per-subject-cap
security argument and the arena-staleness ordering caveat). ADR-0031
fixes the v1 syscall set (send, recv, console_write [capability-gated +
release debug-gated], task_yield, task_exit), reserves number 0 as
invalid, and pins each call's register layout; every object-naming
syscall performs a capability check (P1/P4).

Opens T-020 (error taxonomy + Capability/CapObject Debug redaction — the
pure-Rust foundation, In Progress) and T-021 (SVC trap trampoline +
panic-free dispatcher + copy-from/to-user — Ready, the security-critical
hardware-facing half) to ground both ADRs' dependency chains per ADR-0025
Rule 1. Both ADRs land at Proposed; Accept follows in a separate commit.

Refs: ADR-0030, ADR-0031

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…view

Flip ADR-0030 and ADR-0031 Proposed -> Accepted in a commit separate from
the propose draft, per write-adr skill section 10. The careful re-read plus
a same-day maintainer review surfaced and corrected several drafting issues
*before* this Accept — all folded into the proposed bodies above, so the
Accepted text is correct from the start (no post-Accept body edit):
  - an SVC from a B5 EL1 kernel-stub takes the current-EL (VBAR_EL1+0x200)
    sync vector, not the lower-EL (+0x400) EL0 vector, so the real EL0
    round-trip is runtime-verified in B6, not B5;
  - console_write is capability-gated on a debug-console capability (it was
    ambient authority, a P1/P4 violation); the release debug-gate is a
    separate, independent defense-in-depth gate;
  - the syscall numbers 1..5 are a fixed decision (tests regression-verify
    them), and the payload registers are x1..x7.
Adds the additive ADR-0017 revision rider recording that the IpcError
taxonomy is refined (not superseded) and the three-primitive surface is
unchanged.

Refs: ADR-0030, ADR-0031

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per ADR-0030's K2-5 bundle, replace the collapsed IpcError::InvalidCapability
with StaleHandle / WrongObjectKind / MissingRight so the in-kernel and the
future userspace error spaces agree and each failure is a distinct, handleable
case. Validation now resolves in the order resolve -> type-check -> authority
(kind before rights), matching CapError's InvalidHandle/WrongKind/
InsufficientRights shape, across validate_ep_cap, validate_notif_cap, and
sched::resolve_ep_cap; the four arena-staleness sites map to StaleHandle.
Revealing which check failed is safe for a per-subject, unforgeable capability
table (ADR-0030 security argument). Remaps the existing rights/stale test
assertions and adds 5 new tests pinning each variant (incl. wrong-kind-with-
right, proving kind-before-rights, and a destroyed-endpoint StaleHandle).
InvalidTransferCap is intentionally left intact (note C3-008). Updates
docs/architecture/ipc.md taxonomy section.

Security-relevant (capabilities + IPC). fmt / host-test (194 kernel) /
host-clippy / kernel-clippy / kernel-build / miri (no UB) all green.

Refs: ADR-0030

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per ADR-0030 "Security of the taxonomy split" / B5 sub-item 6 (K3-9, security
review section 6): a userspace-reachable log path (the future console_write
syscall) must never disclose the kernel object a capability names. Replace the
derived Debug on Capability with a hand-written impl that shows rights but
prints the object as <redacted>, and redact CapObject likewise (kind-only
Debug, hiding the wrapped slot index + generation). The individual kernel-
object handle types keep their derived Debug for kernel-internal diagnostics
(they never cross to userspace; T-021's console_write review gates that). Two
host tests pin both redaction layers. Broadens security-model.md's
"no unredacted Debug/Display" rule to capabilities.

The CapObject redaction was folded in from an adversarial self-review that
flagged it as a latent defense-in-depth gap (no current production formatter,
but conservative per CLAUDE.md rule 1). Security-relevant.

Refs: ADR-0030

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add two tests so ipc_cancel_recv pins all three split variants
(it already had MissingRight): a Task cap carrying RECV proves the
kind-before-rights ordering (WrongObjectKind), and a cap whose endpoint
was destroyed exercises the arena-staleness branch (StaleHandle). This
makes ADR-0030's row-3 verification mapping accurate for cancel_recv
(it previously over-claimed cancel coverage). Kernel host tests 194 -> 196.

Refs: ADR-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the remaining maintainer-review findings (the ADR append-only fix
landed via the propose/accept rebase; this commit covers the rest):

- Major: phase-b §B5 acceptance over-promised a real EL0->EL1 round-trip,
  which ADR-0030 shows is impossible at B5 (an EL1 kernel-stub SVC takes
  the current-EL 0x200 vector, not the lower-EL 0x400 EL0 vector). Narrow
  B5 to "dispatch mechanism verified via the current-EL kernel-stub" and
  move the real EL0 0x400 round-trip to the B6 acceptance criteria.
- Minor: current.md banner said "In Progress" while the fields said
  "In Review"; fix the banner and the two broken T-021 links (../).
- Move T-020 to In Review in the task index + task doc; record the
  maintainer-review round and the row-to-verification mapping (now incl.
  the two new cancel_recv variant tests) in T-020's review history.
- Add EL0/EL1, SVC, Syscall, and Syscall ABI glossary entries and note
  the taxonomy split on the ipc.md architecture status row.

Refs: ADR-0030, ADR-0031
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second-round review found the four *_wrong_object_kind tests handed the
cap the operation's own right, so they returned WrongObjectKind under
*both* the chosen kind-first order and a hypothetical rights-first
regression — i.e. ordering-agnostic, proving nothing (a rights-first flip
would not fail them). Fix: each test now uses a wrong-kind cap that also
LACKS the required right (CapRights::empty()), the only input that
discriminates the order (WrongObjectKind under kind-first, MissingRight
under rights-first), so a regression to rights-first now fails the tests.
Updates the section comment and T-020 AC#4 to state what each test
actually proves; corrects T-020's stale test counts (AC#6 194 -> 196;
review history "8 new" -> "9 new").

(The stale-variant references in the Turkish technical-analysis IPC
chapter were also refreshed on disk for local reference, but that tree is
gitignored, so it is not part of this commit / the repo.)

No production code change; fmt / host-test 196 / clippy / build / miri
(no UB) all green.

Refs: ADR-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…her, copy-user

Land the security-critical hardware-facing half of B5 (T-021): the EL0→EL1
SVC trap path that instantiates ADR-0030's calling convention and ADR-0031's
five-syscall v1 set.

New architecture-agnostic, panic-free, host-tested kernel `syscall` module:
- error.rs: SyscallError composing CapError/IpcError via From, with a stable
  numeric status encoding (0 = Ok; 1-3 top-level; 0x10x = Cap; 0x20x = Ipc).
- abi.rs: SyscallNumber decode (release debug-gate on console_write via
  cfg!(debug_assertions)), the register frame types, value↔register packing
  for Message/outcomes, and the Option<CapHandle> null-handle sentinel.
- user_access.rs: UserAccessWindow + validated copy_from_user/copy_to_user
  (range-check-then-copy; wrap and zero-length handled; never derefs an
  unvalidated user pointer).
- dispatch.rs: the panic-free dispatcher + per-syscall handlers + the
  debug-console capability check; control-plane syscalls (task_yield/exit)
  return a SyscallEffect directive rather than touching the scheduler.

Capability surface: CapObject::DebugConsole (singleton, no handle) +
CapRights::CONSOLE_WRITE (bit 7, added to KNOWN_BITS) + CapHandle::from_raw
(ABI-decode constructor; reconstructed handles are validated by lookup).

BSP (hardware-facing): tyrne_sync_trampoline in vectors.s installed at both
VBAR_EL1+0x200 (current-EL, the B5 path) and +0x400 (lower-EL AArch64, the B6
EL0 path) — saves the full x0-x30 + SP_EL0 + ELR_EL1 + SPSR_EL1 frame, routes
ESR_EL1.EC==SVC64 to a Rust syscall_entry, else to the existing panic path.
SyscallTrapFrame (272 B, #[repr(C)], const-asserted to match the asm).
kernel_entry runs an EL1 kernel-stub SVC smoke (console_write + bad-number).

Gates: fmt / host-clippy / kernel-clippy / kernel-build clean; host tests 236
(+40); cargo test --release green (the debug-gate release-path tests);
cargo miri test --workspace --exclude tyrne-bsp-qemu-virt clean (43+236+53).
QEMU smoke (debug): two SVCs taken at the current-EL vector (ESR 0x15/SVC64,
EL1→EL1), clean ERET; console_write emits its buffer via the syscall path
(status 0x0, 63 bytes); a reserved-invalid number returns BadSyscallNumber
(0x1); -d int,unimp,guest_errors shows no new fault class; the cooperative
demo still runs to "tyrne: all tasks complete".

The real EL0 +0x400 round-trip (EL0↔EL1 transition + copy-user against a
separate userspace TTBR0_EL1) is wired but runtime-verified in B6 per
ADR-0030 §Simulation.

Refs: ADR-0030, ADR-0031
Audit: UNSAFE-2026-0029, UNSAFE-2026-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le-time payload guard

Apply the actionable findings from the T-021 adversarial review-round (which
confirmed no live B5 defect). All changes are test coverage, a behavior-
preserving defensive refactor, and B6 forward-gate tracking — no production
behavior change (QEMU trace byte-stable; the const-generic emits identical
register values).

Test coverage (+4 dispatch-level tests; host tests 236 -> 240) closing gaps the
review surfaced:
- send_with_transfer_cap_then_recv_returns_cap_in_x6 — the x5 transfer-handle
  decode -> ipc_send cap_take AND ipc_recv -> encode_recv_outcome x6 cap-pack,
  end-to-end through dispatch (previously untested; verified non-vacuous via a
  mutation check — breaking the x6 pack makes it fail).
- send_with_stale_transfer_handle_returns_invalid_transfer_cap — status 0x205.
- recv_with_no_sender_returns_pending_packing — Pending packing (x1=pending,
  x2..x7 zeroed).
- console_write_exactly_one_chunk_emits_all_bytes — the len == CONSOLE_WRITE_CHUNK
  loop boundary (debug-gated).

Hardening (nit): SyscallReturn::with_payload is now a const-generic
with_payload::<IDX> with `const { assert!(IDX < 7) }`, turning the (already
unreachable-from-untrusted-input) runtime index panic into a compile-time error
at the call site — matching the kernel's compile-time-guard idiom. Call sites
updated to the ::<N> turbofish.

Clarity: the three scattered "unreachable re-validation" comments in
sys_console_write consolidated into one inequality-chain proof.

Docs: phase-b.md §B6 gains an explicit "T-021 carry-forward gates" list (per-task
console_write window + per-page user-VA translation returning FaultAddress not
panic; SP_EL1 init on the +0x400 entry; SYSCALL_STUB_TABLE -> current-task table)
so B6 cannot miss them; T-021 review history records the round.

Gates re-run green: fmt / host-clippy / kernel-clippy / kernel-build clean;
host-test 240; test --release green; miri --workspace excl BSP clean (43+240+53,
0 UB); QEMU smoke round-trip byte-stable.

Refs: ADR-0030, ADR-0031
Audit: UNSAFE-2026-0029, UNSAFE-2026-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 29, 2026

Reviewer's Guide

Opens the B5 syscall boundary by splitting IPC capability errors into precise variants, redacting capability Debug output, and adding a new panic-free, architecture-agnostic syscall module plus an EL1 SVC trap/dispatch path with a debug-console capability, validated copy-from/to-user, and an SVC sync trampoline wired at both current-EL and lower-EL vectors.

Sequence diagram for SVC-based syscall dispatch with console_write

sequenceDiagram
    actor Caller as EL1_kernel_stub
    participant Vec as Sync_vector_+0x200
    participant Tramp as tyrne_sync_trampoline
    participant Entry as syscall_entry
    participant Disp as syscall::dispatch
    participant CapTbl as CapabilityTable
    participant UA as UserAccessWindow
    participant Cons as Console

    Caller->>Vec: SVC #0 (x8=ConsoleWrite, x0=cons_cap, x1=ptr, x2=len)
    Vec->>Tramp: branch to tyrne_sync_trampoline
    Tramp->>Tramp: save x0..x30, SP_EL0, ELR_EL1, SPSR_EL1
    Tramp->>Entry: bl syscall_entry(*mut SyscallTrapFrame)

    Entry->>Disp: dispatch(SyscallContext, SyscallArgs)
    Disp->>CapTbl: validate_debug_console_cap(caller_table, cons_cap)
    CapTbl-->>Disp: Ok or CapError
    alt Cap error
        Disp-->>Entry: SyscallEffect::Resume(error SyscallReturn)
    else Capability ok
        Disp->>UA: UserAccessWindow::validate(ptr, len)
        UA-->>Disp: Ok or SyscallError::FaultAddress
        alt FaultAddress
            Disp-->>Entry: SyscallEffect::Resume(error SyscallReturn)
        else Range ok
            loop chunks
                Disp->>UA: copy_from_user(user_window, chunk_ptr, buf)
                UA-->>Disp: Ok or SyscallError
                Disp->>Cons: write_bytes(buf[..chunk])
            end
            Disp-->>Entry: SyscallEffect::Resume(ok SyscallReturn)
        end
    end

    Entry-->>Tramp: write x0(status), x1..x7(payload) into frame
    Tramp->>Tramp: restore registers, SP_EL0, ELR_EL1, SPSR_EL1
    Tramp-->>Caller: eret (status & payload in x0..x7)
Loading

File-Level Changes

Change Details Files
Refine IPC capability error taxonomy and update IPC/scheduler call sites and tests to use granular variants.
  • Replace IpcError::InvalidCapability with StaleHandle, WrongObjectKind, and MissingRight, documenting semantics and validation order.
  • Update ipc_send/ipc_recv/ipc_notify/ipc_cancel_recv and scheduler resolve_ep_cap to map resolve→type-check→rights into the new variants, including arena staleness as StaleHandle.
  • Extend IPC and scheduler tests to pin all new variants, including wrong-kind plus missing-right cases that prove kind-before-rights ordering and stale-handle cases for dropped or destroyed endpoints.
kernel/src/ipc/mod.rs
kernel/src/sched/mod.rs
docs/architecture/ipc.md
docs/decisions/0017-ipc-primitive-set.md
Redact capability Debug output and introduce a debug-console capability kind and right.
  • Replace derived Debug impls for Capability and CapObject with hand-written redacting versions that hide slot index/generation while preserving rights and kind diagnostics.
  • Add CapObject::DebugConsole and CapKind::DebugConsole plus CapRights::CONSOLE_WRITE, and tests ensuring Debug redaction works as specified.
  • Document capability redaction and debug-console object in security-model and architecture docs.
kernel/src/cap/mod.rs
kernel/src/cap/rights.rs
kernel/src/cap/table.rs
docs/architecture/security-model.md
Introduce a kernel syscall module implementing the ADR-0030/0031 ABI, error space, and panic-free dispatcher.
  • Add syscall::error with SyscallError composing CapError/IpcError and a stable numeric status encoding, plus exhaustive per-variant mappings.
  • Add syscall::abi defining SyscallNumber decode (with release debug-gate for console_write), SyscallArgs/Return/Effect, and helpers for packing messages and capability handles with a null-handle sentinel.
  • Add syscall::dispatch with a panic-free dispatcher and per-syscall handlers for send/recv/task_yield/task_exit/console_write that validate capabilities and return SyscallEffect directives.
  • Add syscall::user_access providing UserAccessWindow and copy_from_user/copy_to_user built on validated ranges and copy_nonoverlapping, with exhaustive host tests and a dedicated unsafe audit entry.
kernel/src/syscall/mod.rs
kernel/src/syscall/error.rs
kernel/src/syscall/abi.rs
kernel/src/syscall/dispatch.rs
kernel/src/syscall/user_access.rs
docs/decisions/0030-syscall-abi.md
docs/decisions/0031-initial-syscall-set.md
Wire the BSP SVC sync trampoline, syscall entry glue, and EL1 kernel-stub SVC smoke for the B5 proxy path.
  • Extend vectors.s with tyrne_sync_trampoline installed at VBAR_EL1+0x200 and +0x400, saving a 272-byte SyscallTrapFrame, routing SVC64 EC to syscall_entry, and otherwise panicking.
  • Add bsp-qemu-virt syscall.rs defining SyscallTrapFrame and syscall_entry that bridges the trap frame to the kernel dispatcher and writes results back, plus a StaticCell-backed syscall stub capability table.
  • Add an EL1 kernel_entry syscall_boundary_smoke that mints a debug-console cap, issues two SVC #0 calls (console_write and bad number), and logs the observed statuses, with associated unsafe annotations and audit entry UNSAFE-2026-0029.
bsp-qemu-virt/src/vectors.s
bsp-qemu-virt/src/syscall.rs
bsp-qemu-virt/src/main.rs
docs/architecture/exceptions.md
docs/audits/unsafe-log.md
Update roadmap, ADR index, glossary, and analysis docs to reflect ADR-0030/0031 acceptance and T-020/T-021 status and carry-forward gates.
  • Mark ADR-0030 and ADR-0031 as Accepted in the ADR index and reference them from architecture docs (ipc, exceptions, architecture README).
  • Update phase-b and current roadmap docs to record T-020/T-021 In Review, describe the B5 EL1 stub proxy vs B6 real EL0 round-trip, and enumerate T-021 B6 carry-forward gates.
  • Extend glossary with EL0/EL1, SVC, syscall, and syscall ABI definitions, and update security-model and task listings to mention capability Debug redaction and new tasks.
  • Add detailed T-020 and T-021 task-analysis docs summarising scope, acceptance criteria, design notes, and review history.
docs/decisions/README.md
docs/roadmap/phases/phase-b.md
docs/roadmap/current.md
docs/analysis/tasks/phase-b/T-020-syscall-error-taxonomy.md
docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md
docs/architecture/README.md
docs/glossary.md
docs/architecture/security-model.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5c48e13-c65f-48fb-94cf-9932d470817e

📥 Commits

Reviewing files that changed from the base of the PR and between 2c713c0 and 1a7deab.

📒 Files selected for processing (2)
  • docs/audits/unsafe-log.md
  • kernel/src/syscall/user_access.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • kernel/src/syscall/user_access.rs
  • docs/audits/unsafe-log.md

📝 Walkthrough

Walkthrough

This PR implements the EL0→EL1 syscall dispatch boundary for the Tyrne kernel. It introduces a panic-free kernel dispatcher with stable error encoding, validated user memory access, and capability-gated console I/O. The IPC capability validation error space is split into three granular variants following a prescribed validation order. The BSP wires an EL1 sync exception trampoline to the dispatcher and exercises the boundary via a kernel-stub smoke test. Two ADRs (0030, 0031) and comprehensive task specifications document the design decisions and acceptance criteria.

Changes

Syscall Dispatcher Implementation and Integration

Layer / File(s) Summary
Syscall ABI and Error Encoding
kernel/src/syscall/abi.rs, kernel/src/syscall/error.rs, kernel/src/cap/rights.rs, kernel/src/cap/table.rs
Defines SyscallNumber, SyscallArgs, SyscallReturn, and SyscallEffect types for the syscall register ABI contract. SyscallError enum with stable numeric encoding (top-level codes 1–3, composed ranges 0x100–0x1ff for capabilities, 0x200+ for IPC). Adds CONSOLE_WRITE right, CapHandle::from_raw() ABI decoder, and NULL_CAP_HANDLE sentinel.
Syscall Dispatcher and Handlers
kernel/src/syscall/dispatch.rs
Implements panic-free dispatch() entry point that decodes syscall numbers and routes to handlers: send/recv delegates to IPC and encodes outcomes; task_yield/task_exit return scheduler directives; console_write validates capability, user window, and copies data in chunks. Comprehensive tests for all paths including capability failures, transfer capabilities, and chunk boundary correctness.
Validated User Memory Access
kernel/src/syscall/user_access.rs
Implements UserAccessWindow to validate contiguous user VA ranges [base, base+len) and rejects wrap-around/out-of-bounds faults. Provides copy_from_user and copy_to_user that validate before calling core::ptr::copy, ensuring no dereference on fault paths.
Capability DebugConsole and Debug Redaction
kernel/src/cap/mod.rs
Adds CapKind::DebugConsole singleton variant and CapObject::DebugConsole (no handle). Replaces derived Debug with manual implementations: Capability redacts object identity while showing rights; CapObject shows kind but redacts handle identity. Prevents logging-based information disclosure.
IPC Error Taxonomy Split
kernel/src/ipc/mod.rs, kernel/src/sched/mod.rs
Splits IpcError::InvalidCapability into StaleHandle (arena lookup miss), WrongObjectKind, and MissingRight following ADR-0030 validation order (resolve → type-check → authority-check). Updates validate_ep_cap/validate_notif_cap validators, all IPC entrypoint error docs, scheduler resolver, and all tests.
BSP EL1 Sync Trampoline and Entry Point
bsp-qemu-virt/src/vectors.s, bsp-qemu-virt/src/syscall.rs
Adds tyrne_sync_trampoline in assembly that saves 272-byte frame, routes SVC64 to Rust syscall_entry, or calls panic_entry for non-SVC sync. syscall_entry reads frame, builds SyscallContext from BSP statics, calls dispatch, and writes results back per SyscallEffect.
BSP Boot Syscall Smoke Test
bsp-qemu-virt/src/main.rs
Adds SYSCALL_STUB_TABLE static and syscall_boundary_smoke() function. Smoke executes EL1 SVC #0`` traps to verify console_write success path (returns byte count) and error path (bad syscall number returns typed error). Wired into `kernel_entry` after IPC publication and before scheduler startup.
Syscall Module Public API
kernel/src/syscall/mod.rs, kernel/src/lib.rs
Creates syscall module root that wires abi, dispatch, error, and user_access submodules and re-exports public API. Adds pub mod syscall to kernel crate exports and updates crate-level documentation.

Design, Requirements, and Decision Documentation

Layer / File(s) Summary
ADR-0030 and ADR-0031
docs/decisions/0030-syscall-abi.md, docs/decisions/0031-initial-syscall-set.md, docs/decisions/README.md
ADR-0030 settles v1 syscall ABI (x8 number, x0–x5 args, x0 status, x1–x7 payload) and SyscallError encoding with stable numeric codes. Specifies IpcError::InvalidCapability split into three variants with validation precedence. ADR-0031 defines B-phase five-syscall set with per-syscall register layouts and capability checks.
Task Specifications T-020 and T-021
docs/analysis/tasks/phase-b/README.md, docs/analysis/tasks/phase-b/T-020-syscall-error-taxonomy.md, docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md
T-020 specifies error taxonomy split, validation mapping changes, and Debug redaction acceptance criteria. T-021 specifies vector-slot installation at +0x200 and +0x400, panic-free dispatcher behavior, capability gating, user-copy safety, trap-frame layout, and QEMU smoke acceptance gates.
Architecture and Security Updates
docs/architecture/exceptions.md, docs/architecture/ipc.md, docs/architecture/security-model.md, docs/architecture/README.md
Updates exception documentation to describe syscall dispatch at both sync slots. Documents trap-frame layout, panic-free invariants, and dispatcher rule set. Updates IPC error taxonomy documentation with validation order and security rationale. Clarifies capability debug redaction discipline and information-leak prevention.
Unsafe Audit Log
docs/audits/unsafe-log.md
Appends UNSAFE-2026-0029 documenting the sync trampoline and syscall_entry contract: frame layout, ESR dispatch, read/dispatch/write-back flow, and stated invariants with smoke verification. Appends UNSAFE-2026-0030 documenting validated userspace byte copies and the amendment to use memmove semantics.
Roadmap and Reference Updates
docs/roadmap/current.md, docs/roadmap/phases/phase-b.md, docs/glossary.md, docs/decisions/0017-ipc-primitive-set.md
Updates roadmap to reflect T-021 in review on PR #34. Sharpens B5 acceptance criteria (dispatcher install, panic-free errors, copy safety, taxonomy, redaction) and B6 gates (real EL0→EL1 round-trip, SP_EL1 init, per-task windows). Adds glossary entries for EL0/EL1, SVC, syscall, syscall ABI. Updates architecture README and prior ADR revisions.

Sequence Diagram(s)

sequenceDiagram
  participant Trampoline
  participant SyscallEntry
  participant Dispatch
  participant IPC
  participant Console
  Trampoline->>SyscallEntry: SyscallTrapFrame ptr
  SyscallEntry->>Dispatch: SyscallContext + SyscallArgs
  alt ConsoleWrite
    Dispatch->>Console: validate cap / copy_from_user / write chunks
    Console-->>Dispatch: byte count / status
  else Send/Recv
    Dispatch->>IPC: ipc_send / ipc_recv
    IPC-->>Dispatch: outcome / status
  end
  Dispatch-->>SyscallEntry: SyscallEffect (Resume / Reschedule / Terminate)
  SyscallEntry->>Trampoline: write back registers / eret
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HodeTech/Tyrne#4: BSP boot-sequence changes adjacent to the smoke insertion point in kernel_entry.
  • HodeTech/Tyrne#10: Exception-vector/trampoline work affecting the same assembly vectors.
  • HodeTech/Tyrne#6: Scheduler/boot initialization edits that interact with the added smoke and publish points.

"I hopped to the vector, swift and spry,
A syscall song beneath the sky.
No panics, just encoded signs,
Bytes and hops in tidy lines. 🐇🌿"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary changes: implementing B5 syscall boundary via error taxonomy (T-020) and EL0→EL1 SVC dispatch (T-021). It is specific, directly related to the main changeset, and avoids vague terms.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t-021-syscall-dispatch

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

current.md + T-021 task file cite PR #34 (base main, 9 commits, bundles T-020 +
T-021 in one combined review per the maintainer's call). Matches the project's
PR-reference convention (cf. T-019/PR #31, B4/PR #33).

Refs: ADR-0030, ADR-0031
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements the EL0→EL1 SVC syscall dispatch boundary (Task T-021) and refactors the IPC error taxonomy (Task T-020) in accordance with ADR-0030 and ADR-0031. It introduces a panic-free, architecture-agnostic kernel syscall subsystem that decodes the register ABI, validates capabilities, and performs memory-safe user-space copies. On the BSP side, it implements the assembly-level SVC sync trap trampoline and register frame handling, which are smoke-tested via an EL1 kernel-stub. Additionally, IpcError::InvalidCapability is split into granular StaleHandle, WrongObjectKind, and MissingRight variants, and custom redacting Debug implementations are added to Capability and CapObject to prevent diagnostic leaks of internal handle identities. As there are no review comments, no feedback is provided on them.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The NULL_CAP_HANDLE sentinel assumes CapHandle indices/generations are constrained to the low 48 bits; consider adding a compile-time assertion (or a doc comment on CapHandle’s fields) to lock in those bit-width assumptions so future changes can’t accidentally collide with the sentinel.
  • Syscall tests repeatedly construct the same SyscallContext scaffolding (endpoint arena, queues, table, fake console, user window); extracting a small helper/builder for this setup would make the tests shorter and reduce the chance of subtle inconsistencies between cases.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `NULL_CAP_HANDLE` sentinel assumes CapHandle indices/generations are constrained to the low 48 bits; consider adding a compile-time assertion (or a doc comment on CapHandle’s fields) to lock in those bit-width assumptions so future changes can’t accidentally collide with the sentinel.
- Syscall tests repeatedly construct the same `SyscallContext` scaffolding (endpoint arena, queues, table, fake console, user window); extracting a small helper/builder for this setup would make the tests shorter and reduce the chance of subtle inconsistencies between cases.

## Individual Comments

### Comment 1
<location path="bsp-qemu-virt/src/syscall.rs" line_range="88-97" />
<code_context>
+/// EL0 task derives a tighter window from its own mapped region (see
+/// [`UserAccessWindow`]'s module docs). The subtraction is a `const` (no runtime
+/// arithmetic): `PMM_EXTENT_END > PMM_EXTENT_START` by construction.
+const SYSCALL_USER_WINDOW_LEN: usize = crate::PMM_EXTENT_END - crate::PMM_EXTENT_START;
+
+/// Rust entry for the `SVC` sync trampoline (`vectors.s`).
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Guard against accidental SYSCALL_USER_WINDOW_LEN underflow at compile time

This relies on `PMM_EXTENT_END > PMM_EXTENT_START`, but if that invariant is ever broken (e.g. misconfigured PMM extent), the `const` subtraction will wrap in release and corrupt the user window length. Please enforce the ordering at compile time, e.g. with a `const` assertion like `const _: () = assert!(crate::PMM_EXTENT_END >= crate::PMM_EXTENT_START);` before computing the difference, or via a build-time check in `build.rs`, so misconfigurations fail the build deterministically.

```suggestion
/// Length of the syscall copy-from/to-user window in B5: the whole
/// identity-mapped RAM extent the bootstrap address space covers.
///
/// The B5 EL1 kernel-stub runs on the bootstrap AS, which identity-maps the
/// managed extent (per [ADR-0027 §Decision outcome (a)]), so the stub's buffer
/// — a `.rodata`-resident `&[u8]` in the kernel image — is in range. B6's real
/// EL0 task derives a tighter window from its own mapped region (see
/// [`UserAccessWindow`]'s module docs). The subtraction is a `const` (no runtime
/// arithmetic): `PMM_EXTENT_END > PMM_EXTENT_START` by construction.
///
/// Guard against misconfigured PMM extents: enforce ordering at compile time so
/// that the subtraction cannot underflow and wrap in release builds.
const _: () = assert!(crate::PMM_EXTENT_END >= crate::PMM_EXTENT_START);

const SYSCALL_USER_WINDOW_LEN: usize = crate::PMM_EXTENT_END - crate::PMM_EXTENT_START;
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread bsp-qemu-virt/src/syscall.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md`:
- Line 11: Update the sentence that claims "discharges rows 0/1/2/4/5" so it
explicitly scopes the discharge to the B5 mechanism-side via the proxy (+0x200)
and not the EL0 runtime verification (+0x400); state that EL0 runtime proof is
deferred to Phase B6. Refer to the same ADR identifiers (ADR-0030, ADR-0031),
the row numbers (0/1/2/4/5), the proxy offset `+0x200`, the runtime offset
`+0x400`, and the deferred `task_create_from_image` wrapper to make the
distinction unambiguous.

In `@docs/roadmap/current.md`:
- Around line 7-10: The blockquote sections (the two leading > paragraphs
starting "**2026-05-29 update — T-021..." and "**2026-05-29 update — B5
opened...") contain blank lines between adjacent quoted paragraphs which
violates markdownlint MD028/no-blanks-blockquote; edit docs/roadmap/current.md
to remove the empty lines inside each blockquote so quoted paragraphs are
contiguous (no empty line between lines beginning with >), preserving all text
and line breaks otherwise so the blockquotes remain semantically identical.

In `@kernel/src/syscall/user_access.rs`:
- Around line 114-154: The unsafe block uses core::ptr::copy_nonoverlapping but
UserAccessWindow::validate only proves bounds, not that the user buffer and
kernel slice are disjoint, making the non-overlap precondition unsound; change
the copy to core::ptr::copy (or otherwise prove non-aliasing for the
kernel-owned dst/src) in the routines that perform user/kernel memory moves (the
unsafe block containing core::ptr::copy_nonoverlapping in copy_from_user /
copy_to_user), and update the surrounding SAFETY comment to remove the unproven
“source and destination are disjoint” claim and document that overlapping copies
are handled by using core::ptr::copy (or document the new aliasing-proof).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 43743d4c-d48f-4c7f-8d5e-d685c2f2daa7

📥 Commits

Reviewing files that changed from the base of the PR and between 003738a and d448540.

📒 Files selected for processing (29)
  • bsp-qemu-virt/src/main.rs
  • bsp-qemu-virt/src/syscall.rs
  • bsp-qemu-virt/src/vectors.s
  • docs/analysis/tasks/phase-b/README.md
  • docs/analysis/tasks/phase-b/T-020-syscall-error-taxonomy.md
  • docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md
  • docs/architecture/README.md
  • docs/architecture/exceptions.md
  • docs/architecture/ipc.md
  • docs/architecture/security-model.md
  • docs/audits/unsafe-log.md
  • docs/decisions/0017-ipc-primitive-set.md
  • docs/decisions/0030-syscall-abi.md
  • docs/decisions/0031-initial-syscall-set.md
  • docs/decisions/README.md
  • docs/glossary.md
  • docs/roadmap/current.md
  • docs/roadmap/phases/phase-b.md
  • kernel/src/cap/mod.rs
  • kernel/src/cap/rights.rs
  • kernel/src/cap/table.rs
  • kernel/src/ipc/mod.rs
  • kernel/src/lib.rs
  • kernel/src/sched/mod.rs
  • kernel/src/syscall/abi.rs
  • kernel/src/syscall/dispatch.rs
  • kernel/src/syscall/error.rs
  • kernel/src/syscall/mod.rs
  • kernel/src/syscall/user_access.rs

Comment thread docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md Outdated
Comment thread docs/roadmap/current.md Outdated
Comment thread kernel/src/syscall/user_access.rs
…guard fixes

Address the second review-round on PR #34. Fix only still-valid issues; one
finding (release-wrap of a const) was refuted but its requested guard kept as a
clearer compile-time tripwire; one (test-scaffolding helper) skipped.

Soundness (headline): copy_from_user / copy_to_user are SAFE `pub fn`s, so they
must be sound for every input — but `UserAccessWindow::validate` proves *bounds*,
not *disjointness*, and under the v1 identity map (VA == PA) a caller could pass
a user_ptr range that aliases the kernel-owned dst/src slice, making
`copy_nonoverlapping`'s non-overlap precondition violable from safe code (UB).
Switch both moves to `core::ptr::copy` (memmove), which is correct for any
overlap; drop the unprovable "source and destination are disjoint" claim from
the SAFETY comments and document why `copy` (not `copy_nonoverlapping`) is the
sound choice. Behaviour is identical for the non-overlapping case (all current
callers are disjoint), so QEMU/Miri/host evidence is unchanged. UNSAFE-2026-0030
gains an append-only Amendment recording the change (title/anchor preserved).

Hardening (compile-time guards, no runtime cost):
- abi.rs: a `const _: () = assert!(NULL_CAP_HANDLE > max-packable-handle-word)`
  locks the sentinel-collision-freedom invariant — a future CapHandle widening
  that could push a packed word into the sentinel's bit range fails the build.
- bsp syscall.rs: an explicit `const _: () = assert!(PMM_EXTENT_END >=
  PMM_EXTENT_START)` with a clear message in front of SYSCALL_USER_WINDOW_LEN.
  (The reviewer's "wraps in release" premise is incorrect — the subtraction is a
  `const`, and const-eval rejects underflow at build time, never wraps — but the
  named assert gives a clearer failure than a raw const-eval overflow error.)

Docs:
- T-021 §Informs: scope the ADR-0030 §Simulation discharge — rows 2/4 in full +
  the mechanism half of rows 0/1/5 via the EL1-stub proxy at the current-EL
  +0x200 vector; the EL0-runtime half of rows 0/1/5 (the +0x400 vector, the
  EL0↔EL1 transition, copy-user vs a separate userspace TTBR0_EL1) is deferred to
  B6, not discharged here.
- current.md: remove the blank lines between adjacent banner paragraphs so they
  form one contiguous `>` blockquote (matches the file's existing multi-paragraph
  style; resolves markdownlint MD028).

Skipped: extracting a SyscallContext test-builder — the scaffolding verbosity is
largely forced by the borrow structure (tests declare + later inspect the
borrowed locals), so a helper saves only the struct-literal line per test and
isn't worth churning the just-reviewed test suite on an in-review PR.

Gates re-run green: fmt / host-clippy / kernel-clippy / kernel-build; host-test
240; test --release 233; miri --workspace excl BSP clean (0 UB); QEMU smoke
round-trip byte-stable.

Refs: ADR-0030, ADR-0031
Audit: UNSAFE-2026-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
kernel/src/syscall/user_access.rs (1)

162-162: ⚡ Quick win

Add an overlap regression test for both directions.

The reason this switched to core::ptr::copy is that the safe API must stay sound even when the user range aliases the kernel slice. Please pin that with one overlapping copy_from_user case and one overlapping copy_to_user case; otherwise a future cleanup back to copy_nonoverlapping can reintroduce UB without a failing test.

🧪 Example test shape
#[test]
fn copy_from_user_allows_overlap() {
    let mut backing: Vec<u8> = (0..8u8).collect();
    let base = backing.as_mut_ptr() as usize;
    let window = UserAccessWindow::new(base, backing.len());

    copy_from_user(&window, base, &mut backing[2..6]).unwrap();
    assert_eq!(&backing[..], &[0, 1, 0, 1, 2, 3, 6, 7]);
}

#[test]
fn copy_to_user_allows_overlap() {
    let mut backing: Vec<u8> = (0..8u8).collect();
    let base = backing.as_mut_ptr() as usize;
    let window = UserAccessWindow::new(base, backing.len());

    let src = &backing[2..6];
    copy_to_user(&window, base + 1, src).unwrap();
    assert_eq!(&backing[..], &[0, 2, 3, 4, 5, 5, 6, 7]);
}

Also applies to: 207-207

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kernel/src/syscall/user_access.rs` at line 162, Add two regression tests
ensuring overlapping copies are allowed: one for copy_from_user and one for
copy_to_user. Implement a test that creates a backing Vec<u8>, builds a
UserAccessWindow from its pointer/length, then calls copy_from_user with a
destination slice that overlaps the source range and asserts the resulting
backing bytes match the expected overlapping-copy outcome, and likewise a second
test that calls copy_to_user with a source slice that overlaps the destination
range and asserts the expected bytes. Place tests near existing user_access
tests so future refactors of core::ptr::copy vs copy_nonoverlapping on functions
copy_from_user and copy_to_user will fail if UB is reintroduced. Ensure both
tests exercise opposite overlap directions (dst overlaps src and src overlaps
dst).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/audits/unsafe-log.md`:
- Line 670: Update the amendment header to include the actual commit SHA and
make the text self-consistent: record the commit SHA historically at the top of
the amendment, explicitly mark the original `copy_nonoverlapping` wording and
the "rejected alternatives" reference as historical/superseded, and adjust the
invariant (3) and SAFETY comments to reflect the switch from
`copy_nonoverlapping` to `core::ptr::copy`; ensure the header follows the
audit-log format and clearly states that the named alternative
(`copy_nonoverlapping`) is superseded rather than unchanged.

---

Nitpick comments:
In `@kernel/src/syscall/user_access.rs`:
- Line 162: Add two regression tests ensuring overlapping copies are allowed:
one for copy_from_user and one for copy_to_user. Implement a test that creates a
backing Vec<u8>, builds a UserAccessWindow from its pointer/length, then calls
copy_from_user with a destination slice that overlaps the source range and
asserts the resulting backing bytes match the expected overlapping-copy outcome,
and likewise a second test that calls copy_to_user with a source slice that
overlaps the destination range and asserts the expected bytes. Place tests near
existing user_access tests so future refactors of core::ptr::copy vs
copy_nonoverlapping on functions copy_from_user and copy_to_user will fail if UB
is reintroduced. Ensure both tests exercise opposite overlap directions (dst
overlaps src and src overlaps dst).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 737ff6aa-bacc-4702-b1de-7fb59d2e7c6e

📥 Commits

Reviewing files that changed from the base of the PR and between d448540 and 2c713c0.

📒 Files selected for processing (6)
  • bsp-qemu-virt/src/syscall.rs
  • docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md
  • docs/audits/unsafe-log.md
  • docs/roadmap/current.md
  • kernel/src/syscall/abi.rs
  • kernel/src/syscall/user_access.rs
✅ Files skipped from review due to trivial changes (1)
  • docs/analysis/tasks/phase-b/T-021-syscall-dispatch.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • docs/roadmap/current.md
  • bsp-qemu-virt/src/syscall.rs
  • kernel/src/syscall/abi.rs

Comment thread docs/audits/unsafe-log.md Outdated
@cemililik
Copy link
Copy Markdown
Collaborator Author

Responding to the two high-level items in the @sourcery-ai review (they have no inline anchor, so noting the disposition here):

1. NULL_CAP_HANDLE bit-width assertion — ✅ Done in 2c713c0.
Added a compile-time guard in kernel/src/syscall/abi.rs next to the sentinel:

const _: () = assert!(
    NULL_CAP_HANDLE > (((u32::MAX as u64) << 16) | (u16::MAX as u64)),
    "NULL_CAP_HANDLE must exceed every packable CapHandle word (see encode_cap_handle)"
);

A live handle packs (generation: u32) << 16 | (index: u16) (bits 0..48); the sentinel (u64::MAX) sets bits 48..64. The assertion locks that the widest packable word stays below the sentinel, so a future CapHandle widening (or a change to the packing shift) that could collide fails the build rather than silently aliasing a live handle.

2. Extract a SyscallContext test-builder — ⏭️ Skipped (deliberately), with reason.
The repeated scaffolding is largely forced by the borrow structure rather than incidental: SyscallContext holds &mut borrows of the arena / queues / table locals, and several tests inspect those same locals after dispatch (e.g. the transfer-cap round-trip asserts ctx.caller_table.lookup(...) post-dispatch), so the locals must be declared in the test body regardless. A builder would therefore save only the struct-literal line per test while adding a helper with a five-lifetime signature — a marginal DRY gain not worth churning a just-reviewed, security-critical test suite on an in-review PR. Happy to do it as a follow-up if preferred.

All re-validated after the follow-up: fmt / host-clippy / kernel-clippy / kernel-build clean; host-test 240; test --release 233; miri --workspace --exclude tyrne-bsp-qemu-virt 0 UB; QEMU smoke round-trip byte-stable.

… the soundness basis

Review-round on PR #34 (two findings; each verified against current code):

1. UNSAFE-2026-0030 amendment (docs/audits/unsafe-log.md) — VALID, fixed. The
   amendment added in 2c713c0 (a) lacked the commit SHA the audit-log format
   wants and (b) over-claimed that switching to `core::ptr::copy` makes the copy
   "overlap-tolerant". An empirical Miri probe disproved that: an overlapping
   (user_ptr, kernel-slice) pair is UB *regardless* of the copy primitive —
   `copy_from_user`'s `dst: &mut [u8]` (and `copy_to_user`'s `src: &[u8]`)
   parameter is exclusive / shared, so an aliasing access through the exposed
   `user_ptr` violates that borrow (Stacked Borrows: "not granting access to tag
   <wildcard> … strongly protected"). The amendment now carries SHA 2c713c0,
   marks the original `copy_nonoverlapping` §Operation / invariant(3) /
   rejected-alternatives wording as superseded, and states the true soundness
   basis: the user/kernel **disjointness** invariant (user_ptr = userspace,
   kernel slice = distinct allocation in v1 / separate AS in B6), under which
   both `copy` and `copy_nonoverlapping` are sound. `core::ptr::copy` is kept as
   the conservative primitive. The copy_from_user / copy_to_user SAFETY comments
   are corrected to match (invariant 3 = disjointness, not "overlap-tolerant").

2. Add overlapping-copy regression tests — SKIPPED, with reason. The requested
   tests assert "overlapping copies are allowed", but overlap is UB here (see
   above — Miri-confirmed via a temporary probe, now removed), independent of
   `copy` vs `copy_nonoverlapping`. Such tests would (a) break the Miri gate and
   (b) codify an unsound expectation. The real invariant is disjointness, which
   the existing tests + the structural user/kernel split already cover; an
   overlapping call correctly fails under Miri's borrow model.

No code-behaviour change (the `core::ptr::copy` calls are unchanged; only SAFETY
comments + the audit amendment text). Gates: fmt / host-clippy / kernel-clippy /
kernel-build clean; host-test 240; miri (syscall) 0 UB. Production code is
byte-identical to 2c713c0, already validated with full miri + test --release +
QEMU smoke.

Refs: ADR-0030, ADR-0031
Audit: UNSAFE-2026-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik cemililik merged commit f98e1af into main May 29, 2026
7 checks passed
cemililik added a commit that referenced this pull request May 29, 2026
B5 closure performance baseline — the performance leg of the B5 syscall-boundary
closure trio. Re-baseline of kernel footprint + boot-to-end timing after T-020 +
T-021 (PR #34, f98e1af) versus the 2026-05-28 B4 closure baseline.

Footprint (release): .text 34,648 (+1,524) / .rodata 4,856 (+296) / .bss 50,592
(+2,272) = ~88.0 KiB (+4.76 %) — the smallest non-refactor .text growth in Phase
B (the syscall boundary is a thin validator/dispatch layer). Tests 339 (43 hal +
240 kernel + 53 test-hal + 3 doc; +53 kernel), miri clean (0 UB, run locally).
Release harness band p10/p50/p90 = 17.645 / 20.300 / 24.706 ms.

Verdict: baseline, no proposal. The cycle's decisive measurement was a same-host
back-to-back control (the B4 binary 3ab029f rebuilt + re-measured this session):
it proves the ~+2.9 ms p10/p50 delta vs B4 is REAL B5 code (the boot SVC-smoke —
2 exception round-trips + cold TCG translation), not host jitter, correcting an
initial mis-read of the noisier raw band. One-time-at-boot, ~us on real hardware.
The control also shows the QEMU-TCG harness is nearing its resolving floor for
small milestones (per-milestone signal ~ session jitter). Adds the harness report
docs/analysis/reports/perf-baseline-2026-05-29-B5-closure.md and the README index
row.

Refs: ADR-0013, ADR-0030, ADR-0031
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cemililik added a commit that referenced this pull request May 29, 2026
B5 closure retrospective — the business leg of the B5 syscall-boundary closure
trio — plus the roadmap forward motion it drives. Formally closes Milestone B5
(Syscall boundary): T-020 (IpcError taxonomy split + Capability/CapObject Debug
redaction) + T-021 (EL0 to EL1 SVC dispatch) merged via PR #34 (f98e1af);
ADR-0030 + ADR-0031 Accepted.

What landed (canonical metrics, reproduced live at afeed10): 339 host tests
(+53 kernel) incl. local miri 0 UB; release ELF ~88.0 KiB (+4.76 % vs B4);
release smoke clean to "all tasks complete" (the release trace itself proves the
console_write debug-gate — status=0x1 in release); the debug smoke shows the full
console_write SVC round-trip (status=0x0, bytes=63); -d = 712/776 PL011 + 2
expected SVC exceptions (EL1 to EL1, ESR 0x15, clean ERET), zero new fault class;
audit log 30 entries (29 Active) — UNSAFE-2026-0029/0030.

What we learned: the pure-Rust (T-020) / hardware-boundary (T-021) split per
CLAUDE.md section 6 let the most security-sensitive milestone land safely in one
calendar day without skipping rigor; an adversarial pass + Miri corrected a real
copy-user soundness over-claim (disjointness, not the copy primitive, is the
basis); a same-host perf control corrected a host-jitter mis-attribution; ABI
front-loading kept the syscall numbers a decision, not an accident.

Side-effects: current.md banner + Pathfinder flipped to B5-closed / B6-next;
phase-b.md section B5 status -> Closed; test-count drift (236/240 mid-arc -> live
339) reconciled; business-reviews README index row added. Next: B6 (first
userspace "hello") — the deferred task_create_from_image bridge + the 3 T-021
carry-forward gates + the ADR-0033 high-half opening; B6's review is the Phase B
retrospective.

Refs: ADR-0013, ADR-0030, ADR-0031, ADR-0017, ADR-0033
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cemililik added a commit that referenced this pull request May 29, 2026
…estone B5) (#35)

* docs(analysis): security-review B5-syscall-boundary

Standalone consolidated security pass over the B5 syscall boundary (T-020 IpcError
split + Capability/CapObject Debug redaction K3-9; T-021 EL0→EL1 SVC dispatch),
merged via PR #34 (f98e1af). Performed with a fresh 8-axis checklist after the
PR's three code-review rounds + the adversarial multi-agent pass.

Verdict: **Approve.** All eight applicable axes pass (cryptography N/A). The
dispatcher is panic-free on every register-supplied input; every object-naming
syscall is capability-gated before any effect (console_write on the new
debug-console cap, closing the ambient-authority slip caught before ADR Accept);
copy-from/to-user never dereferences an unvalidated user pointer, with the
soundness basis honestly recorded as the user/kernel disjointness invariant
(an empirical Miri probe disproved an earlier "overlap-tolerant" over-claim —
overlap is UB regardless of the copy primitive via the &mut/& borrow exclusivity);
T-020's Debug redaction closes the K3-9 secrets-leak path exactly where B5 first
creates a userspace-reachable log channel. Both new unsafe entries
(UNSAFE-2026-0029 trampoline asm, UNSAFE-2026-0030 copy-user) are policy-conformant;
0029 carries the required second-reviewer sign-off. Gates: host-test 240,
test --release green, miri --workspace clean (0 UB), QEMU +0x200 proxy round-trip
byte-stable with no new fault class.

B5 builds the boundary mechanism but the real EL0 +0x400 transition is B6; three
B6 carry-forward gates (per-task console_write window + per-page translation;
SP_EL1 init; SYSCALL_STUB_TABLE → current-task table) are tracked in phase-b.md
§B6. This pass can serve as the security leg of the eventual B5 closure trio
(business + performance legs deferred — "security review first").

Refs: ADR-0013, ADR-0030, ADR-0031, ADR-0014
Security-Review: @cemililik (+ Claude Opus 4.8 agent)
Audit: UNSAFE-2026-0029, UNSAFE-2026-0030
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(analysis): reconcile security-model.md SMMUv3-CI claim with ADR-0036 (B5 review finding)

The B5 syscall-boundary security review (this PR) re-surfaced the one finding
actionable outside a phase gate: security-model.md §threat-model #7 + §Open
questions still described QEMU `virt` as "launched with SMMUv3 and used in CI to
catch driver misbehaviour" — a live contradiction with Accepted ADR-0036 (QEMU
`virt` is GICv2 / no-IOMMU in v1; SMMUv3 is exposed only under an explicit
`iommu=smmuv3` launch Tyrne does not use). It was N/A as a v1 *defect* (no
bus-master driver exists, so the DMA boundary is inactive) but a stale doc claim,
forward-flagged since the 2026-05-28 B4 closure.

Reconciled conservatively: both sentences now state the v1 GICv2/no-IOMMU reality,
point at ADR-0036, and reframe the SMMU-in-CI gate as a future IOMMU-equipped-target
(Jetson Orin) item — preserving the model's IOMMU intent, correcting only the
stale QEMU-`virt`-has-SMMUv3 claim. The review doc's §8 + forward-flags are updated
to mark this RECONCILED (no longer carried forward). All other review forward-flags
are correctly phase-deferred (B6 carry-forward gates / Phase-E fault containment /
preemption-time ipc_send hardening / B5+ cap_map rights ADR) — no action now.

Refs: ADR-0013, ADR-0036
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(analysis): performance-review B5-closure

B5 closure performance baseline — the performance leg of the B5 syscall-boundary
closure trio. Re-baseline of kernel footprint + boot-to-end timing after T-020 +
T-021 (PR #34, f98e1af) versus the 2026-05-28 B4 closure baseline.

Footprint (release): .text 34,648 (+1,524) / .rodata 4,856 (+296) / .bss 50,592
(+2,272) = ~88.0 KiB (+4.76 %) — the smallest non-refactor .text growth in Phase
B (the syscall boundary is a thin validator/dispatch layer). Tests 339 (43 hal +
240 kernel + 53 test-hal + 3 doc; +53 kernel), miri clean (0 UB, run locally).
Release harness band p10/p50/p90 = 17.645 / 20.300 / 24.706 ms.

Verdict: baseline, no proposal. The cycle's decisive measurement was a same-host
back-to-back control (the B4 binary 3ab029f rebuilt + re-measured this session):
it proves the ~+2.9 ms p10/p50 delta vs B4 is REAL B5 code (the boot SVC-smoke —
2 exception round-trips + cold TCG translation), not host jitter, correcting an
initial mis-read of the noisier raw band. One-time-at-boot, ~us on real hardware.
The control also shows the QEMU-TCG harness is nearing its resolving floor for
small milestones (per-milestone signal ~ session jitter). Adds the harness report
docs/analysis/reports/perf-baseline-2026-05-29-B5-closure.md and the README index
row.

Refs: ADR-0013, ADR-0030, ADR-0031
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): business-review B5-closure

B5 closure retrospective — the business leg of the B5 syscall-boundary closure
trio — plus the roadmap forward motion it drives. Formally closes Milestone B5
(Syscall boundary): T-020 (IpcError taxonomy split + Capability/CapObject Debug
redaction) + T-021 (EL0 to EL1 SVC dispatch) merged via PR #34 (f98e1af);
ADR-0030 + ADR-0031 Accepted.

What landed (canonical metrics, reproduced live at afeed10): 339 host tests
(+53 kernel) incl. local miri 0 UB; release ELF ~88.0 KiB (+4.76 % vs B4);
release smoke clean to "all tasks complete" (the release trace itself proves the
console_write debug-gate — status=0x1 in release); the debug smoke shows the full
console_write SVC round-trip (status=0x0, bytes=63); -d = 712/776 PL011 + 2
expected SVC exceptions (EL1 to EL1, ESR 0x15, clean ERET), zero new fault class;
audit log 30 entries (29 Active) — UNSAFE-2026-0029/0030.

What we learned: the pure-Rust (T-020) / hardware-boundary (T-021) split per
CLAUDE.md section 6 let the most security-sensitive milestone land safely in one
calendar day without skipping rigor; an adversarial pass + Miri corrected a real
copy-user soundness over-claim (disjointness, not the copy primitive, is the
basis); a same-host perf control corrected a host-jitter mis-attribution; ABI
front-loading kept the syscall numbers a decision, not an accident.

Side-effects: current.md banner + Pathfinder flipped to B5-closed / B6-next;
phase-b.md section B5 status -> Closed; test-count drift (236/240 mid-arc -> live
339) reconciled; business-reviews README index row added. Next: B6 (first
userspace "hello") — the deferred task_create_from_image bridge + the 3 T-021
carry-forward gates + the ADR-0033 high-half opening; B6's review is the Phase B
retrospective.

Refs: ADR-0013, ADR-0030, ADR-0031, ADR-0017, ADR-0033
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(roadmap): pre-B6 prep — reconcile B5-done status + B6 opening sequence

Pre-B6 documentation preparation on the B5-closure branch (rides in PR #35),
done before B6 development starts on its own branch off main. Docs-only.

(1) B5-done status reconcile (drift sweep). With B5 closed, several live docs
still treated the syscall boundary as future/next:
- CLAUDE.md: "the syscall ABI and first userspace task are next" -> the syscall
  boundary is done; the first EL0 task is next.
- README.md status table: "Syscall ABI + EL0 entry | Next - Phase B5" -> split
  into "Syscall ABI + dispatcher = Done (B5)" + "First userspace hello (EL0) =
  Next (B6)".
- phase-b ADR ledger: ADR-0030 / ADR-0031 marked Accepted 2026-05-29; ADR-0033 /
  ADR-0034 triggers corrected from B5/B5+ to B6 (B5 closed via the SVC proxy
  without surfacing the per-task TTBR0 swap, so the trigger is now B6).
- architecture docs (task-loader / memory-management / boot): "ADR-0033 gated on
  B5 surfacing per-task TTBR0 swap" -> B6, and "until B5 adds a per-task context
  surface" -> B6 — these would have misled a B6 dev about which milestone owns
  the EL0 context + high-half work.

(2) B6 opening sequence & prerequisites — the careful B6 plan, made durable in
phase-b.md section B6. States the gating prerequisite (the kernel must stay
reachable from every task's active translation, else an EL0 SVC vector fetch
translation-faults), the ADRs that open B6 (ADR-0033 kernel-in-every-AS + the
EL0-task-context decision + optional ADR-0034), and the dependency-ordered task
sequence: ADR-0033 impl -> EL0 context -> task_create_from_image -> the 3 T-021
carry-forward gates -> tyrne-user + userland/hello -> wire-up + EL0 round-trip
smoke -> Phase B retrospective. Decisions remain open for their ADRs; this fixes
only the order + rationale.

All links resolve; docs-only, no kernel changes.

Refs: ADR-0013, ADR-0027, ADR-0030, ADR-0031, ADR-0033
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: PR #35 review-round — fix assert quote, ADR-0033 link, ADR-0034 table pipe

Three review findings on the B5 closure docs, each verified against the source:

- abi.rs assert quote (security review section 1): the doc quoted
  assert!(NULL_CAP_HANDLE > ((u32::MAX << 16) | u16::MAX)) but the actual
  abi.rs:42 uses `as u64` casts — (((u32::MAX as u64) << 16) | (u16::MAX as u64))
  (without them the shift overflows in u32 arithmetic). Quote corrected to match.

- ADR-0033 link (security review section 8): the label said "ADR-0033" but the
  link targeted 0027-kernel-virtual-memory-layout.md (a mismatch). ADR-0033
  (high-half migration) is genuinely the right concept — 0027 only reserves the
  slot — so rather than relabel to ADR-0027 (which would misattribute high-half
  to 0027), ADR-0033 is now plain text (matching the unlinked ADR-0034 in the
  same sentence) with a correctly-labeled "reserved in ADR-0027" cross-reference.

- ADR-0034 ledger row (phase-b.md): the inline code USER|EXECUTE (introduced in
  the pre-B6 prep commit) had an unescaped pipe, splitting the table row into
  five columns; escaped to USER\|EXECUTE. All 14 ledger rows now have 4 columns.

Validated: assert quote matches abi.rs:42 verbatim; all links resolve; every
ADR-ledger row is well-formed (5 delimiters = 4 columns). Docs-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant