Skip to content

changed the old name to the new one #18

changed the old name to the new one

changed the old name to the new one #18

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# Every crate except kernel/ — see kernel/Cargo.toml's `[workspace]` stanza
# for why it's a standalone package instead of a member here.
host:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: fmt check
run: cargo fmt --check
- name: clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: test
run: cargo test --workspace
# Freestanding x86_64-unknown-none build. Needs nightly from Phase 2
# onward: exception handlers use `extern "x86-interrupt"`, which is still
# unstable (rust-lang/rust#40180) despite being the standard way every
# x86_64 Rust kernel defines them — see kernel/src/lib.rs's
# `#![feature(abi_x86_interrupt)]`. `--target` is explicit rather than
# relying on a `.cargo/config.toml` default: kernel/.cargo/config.toml
# deliberately has none (see the comment there — an ambient default here
# once leaked into the `kernel-tests` job's nested xtask invocation below,
# forcing a host-side tool to try building for a bare-metal target).
# `--bins --lib` (not `--all-targets`): the *default* libtest harness
# still can't run on a bare target — `kernel-tests` below is what
# actually exercises `tests/*.rs`, via its own non-libtest mechanism.
kernel:
runs-on: ubuntu-latest
defaults:
run:
working-directory: kernel
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
targets: x86_64-unknown-none
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
with:
workspaces: kernel
- name: fmt check
run: cargo fmt --check
- name: clippy (lib + bin only)
run: cargo clippy --target x86_64-unknown-none --bins --lib -- -D warnings
- name: build
run: cargo build --target x86_64-unknown-none --bins --lib
# The real "Phase 8" harness: `cargo test` (run from kernel/, targeting
# x86_64-unknown-none) builds each `kernel/tests/*.rs` as its own bootable
# binary (`harness = false` in Cargo.toml — no libtest, since there's no
# `test`/panic-unwind runtime on bare metal), boots it for real in QEMU
# via kernel/.cargo/config.toml's `runner` (which delegates to xtask's
# `test-runner` subcommand), and reads pass/fail from the isa-debug-exit
# device (kernel/src/qemu_exit.rs) — a real exit code, not string-matched
# serial output. This is what catches architecture-specific bugs (like the
# GDT/segment-register one in the README) that `cargo test --workspace` on
# the host never could.
kernel-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
targets: x86_64-unknown-none
# `rust-src`: the `bootloader` crate's build script (invoked via
# `xtask` in this job) compiles its own boot-sector/stage-2/3/4
# helper binaries for bare-metal targets, which needs to build
# core/alloc from source — without this component that fails with
# "does not exist, unable to build with the standard library".
components: llvm-tools, rust-src
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
kernel
xtask
grid-sandbox-host
- name: install QEMU
run: sudo apt-get update && sudo apt-get install -y qemu-system-x86
# `grid_sandbox_wasm` below needs this binary already built on disk
# (`include_bytes!` embeds it at kernel-test compile time) — see
# `kernel/tests/grid_sandbox_wasm.rs`'s own doc comment. Built here,
# not as part of the `kernel-tests` `cargo test` invocation, since
# it's a separate standalone package (own `[workspace]`, same
# reasoning as `kernel`/`xtask` — see its `Cargo.toml`).
- name: build grid-sandbox-host (payload for grid_sandbox_wasm)
working-directory: grid-sandbox-host
run: cargo build --target x86_64-unknown-none --release
- name: cargo test (boots every kernel/tests/*.rs in QEMU)
working-directory: kernel
# `--test basic_boot`, not a bare `cargo test`: with no target
# selection, cargo also tries to build the lib's own unit-test
# harness (`--lib`), which needs `test`/panic-unwind — unavailable
# on a bare-metal target regardless of `harness = false` on the
# integration tests. Add `--test <name>` for each new file under
# tests/ as they're added; there's no single flag that means "every
# tests/*.rs but not lib/bin unit tests" (`--tests`, despite the
# name, still pulls in `--lib`/`--bins` too).
run: |
cargo test --target x86_64-unknown-none --test basic_boot
cargo test --target x86_64-unknown-none --test guard_page
cargo test --target x86_64-unknown-none --test thread_reclaim
cargo test --target x86_64-unknown-none --test watchdog
cargo test --target x86_64-unknown-none --test pci_scan
cargo test --target x86_64-unknown-none --test process_isolation
cargo test --target x86_64-unknown-none --test elf_loader
cargo test --target x86_64-unknown-none --test scheduler_address_space
cargo test --target x86_64-unknown-none --test ring3_cooperative
cargo test --target x86_64-unknown-none --test grid_sandbox_wasm
cargo test --target x86_64-unknown-none --test citadel_demo
# Mobile L1 (aarch64/TrustZone), separate from the x86_64 `kernel` job
# above — different target triple, different toolchain requirement (this
# one builds on stable). Alpha scope so far: EL3 boot, a real exception
# vector table (catches and resumes a deliberate fault with full context
# preserved), a fully proven GIC bring-up (SGI delivered through the IRQ
# vector, acknowledged and EOI'd), the EL3->EL1 Non-secure drop (the
# actual TrustZone boundary), EL1's own MMU (identity-mapped, verified
# with AT S1E1R actually translating, not just enabling without
# crashing), and the RIL isolation boundary itself (EL1->EL0 drop, SVC
# syscall gate, capability-token-gated resource check reusing
# capability-manager) — see kernel-arm/src/main.rs's doc comment for
# full status and the "why a separate crate from kernel/" decision.
kernel-arm:
runs-on: ubuntu-latest
defaults:
run:
working-directory: kernel-arm
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
targets: aarch64-unknown-none
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
with:
workspaces: kernel-arm
- name: fmt check
run: cargo fmt --check
- name: clippy
run: cargo clippy --target aarch64-unknown-none -- -D warnings
- name: build
run: cargo build --target aarch64-unknown-none --release
- name: install QEMU (aarch64)
run: sudo apt-get update && sudo apt-get install -y qemu-system-arm
- name: boot in QEMU and check EL3 boot, exception resume, GIC, EL3->EL1 drop
run: |
timeout 10 qemu-system-aarch64 \
-M virt,secure=on,gic-version=2 -cpu cortex-a53 -nographic \
-kernel target/aarch64-unknown-none/release/runix-kernel-arm \
| tee /tmp/arm-boot.log || true
grep -q "Runix ARM kernel: boot OK" /tmp/arm-boot.log
grep -q "Runix ARM kernel: CurrentEL = EL3" /tmp/arm-boot.log
grep -q "Runix ARM kernel: VBAR_EL3 installed" /tmp/arm-boot.log
# Proves the exception vector table actually catches a fault and
# resumes with the interrupted register context intact, not just
# that installing VBAR_EL3 didn't crash on its own.
grep -q "EXCEPTION: vector 4 (Synchronous (current EL, SPx))" /tmp/arm-boot.log
grep -q "Runix ARM kernel: EXCEPTION test OK (resumed after brk)" /tmp/arm-boot.log
# Proves a Software Generated Interrupt is actually delivered
# through the GIC into the IRQ vector, not just pending at the
# distributor (see gic.rs's doc comment for what that distinction
# cost to find).
grep -q "EXCEPTION: IRQ fired, GIC interrupt id = 0" /tmp/arm-boot.log
grep -q "Runix ARM kernel: GIC test OK" /tmp/arm-boot.log
# The actual TrustZone boundary.
grep -q "Runix ARM kernel: reached EL1 (dropped from EL3, SCR_EL3.NS=1)" /tmp/arm-boot.log
grep -q "Runix ARM kernel: CurrentEL = EL1" /tmp/arm-boot.log
grep -q "Runix ARM kernel: VBAR_EL1 installed" /tmp/arm-boot.log
grep -q "Runix ARM kernel: MMU enabled" /tmp/arm-boot.log
# Proves the MMU is actually translating, not just that
# SCTLR_EL1.M's write didn't crash: AT S1E1R asks the hardware
# itself to translate UART0's VA and report the result.
grep -q "fault=false, PA=0x9000000" /tmp/arm-boot.log
# The RIL isolation boundary: EL1->EL0 drop, SVC gate, and a real
# capability-manager token check distinguishing an authorized
# channel from an unauthorized one — not just that EL0 code ran.
grep -q "Runix ARM kernel: heap initialized" /tmp/arm-boot.log
grep -q "Runix ARM kernel: RIL capability issued" /tmp/arm-boot.log
# "U" (SYS_WRITE gate proof) immediately followed by "B" (0x42,
# a real EL0 stack strb/ldrb round-trip) on the same line, since
# neither syscall emits a newline -- proves real EL0/EL1 page-
# granular memory isolation actually grants data access on
# el0_demo's own page, not just that instruction fetch works.
grep -q "^UB$" /tmp/arm-boot.log
grep -q "SVC: SYS_RIL_ACCESS channel 0 authorized" /tmp/arm-boot.log
grep -q "SVC: SYS_RIL_ACCESS channel 99 DENIED" /tmp/arm-boot.log
# The check gates real per-operation I/O, not just a one-time
# access decision: a byte sent on the authorized channel round
# -trips back out (0x41, echoed as 'A'), the same operation on
# the unauthorized channel is denied outright.
grep -q "SVC: SYS_RIL_SEND channel 0 byte 0x41 authorized" /tmp/arm-boot.log
grep -q "SVC: SYS_RIL_RECV channel 0 authorized, byte 0x41" /tmp/arm-boot.log
grep -q "^A$" /tmp/arm-boot.log
grep -q "SVC: SYS_RIL_SEND channel 99 DENIED" /tmp/arm-boot.log
grep -q "SVC: SYS_RIL_RECV channel 99 DENIED" /tmp/arm-boot.log
# Basic SIM provisioning: the same capability check gating a
# real state machine (sim.rs) instead of RIL's byte mailbox --
# a full Uninitialized -> Provisioned -> Activated walk on an
# authorized slot, denied outright on an unauthorized one.
grep -q "SVC: SYS_SIM_STATUS slot 0 authorized, state Uninitialized" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_PROVISION slot 0 identity 0x1234 authorized" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_STATUS slot 0 authorized, state Provisioned" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_ACTIVATE slot 0 authorized" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_STATUS slot 0 authorized, state Activated" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_PROVISION slot 99 DENIED" /tmp/arm-boot.log
grep -q "SVC: SYS_SIM_STATUS slot 99 DENIED" /tmp/arm-boot.log
- name: boot in QEMU without secure=on and check the no-EL3 path
# Regression check for a real, previously-shipped bug: this used to
# produce zero UART output (rust_start ran the EL3-only phase
# unconditionally, which UNDEFINED-traps from EL1 with no VBAR_EL1
# installed yet to catch it) -- see main.rs's doc comment and
# docs/STATUS.md's "Real bugs" section for the full account.
run: |
timeout 8 qemu-system-aarch64 \
-M virt,gic-version=2 -cpu cortex-a53 -nographic \
-kernel target/aarch64-unknown-none/release/runix-kernel-arm \
| tee /tmp/arm-boot-nosecure.log || true
grep -q "Runix ARM kernel: boot OK" /tmp/arm-boot-nosecure.log
grep -q "Runix ARM kernel: CurrentEL = EL1" /tmp/arm-boot-nosecure.log
grep -q "no EL3 present -- skipping" /tmp/arm-boot-nosecure.log
# Must NOT claim a security-state switch that never happened.
grep -q "no EL3 ever ran, so no security-state switch to report" \
/tmp/arm-boot-nosecure.log
grep -q "Runix ARM kernel: MMU enabled" /tmp/arm-boot-nosecure.log
grep -q "^UB$" /tmp/arm-boot-nosecure.log
grep -q "SVC: SYS_RIL_ACCESS channel 0 authorized" /tmp/arm-boot-nosecure.log
grep -q "SVC: SYS_RIL_RECV channel 0 authorized, byte 0x41" \
/tmp/arm-boot-nosecure.log
grep -q "^A$" /tmp/arm-boot-nosecure.log
grep -q "SVC: SYS_SIM_STATUS slot 0 authorized, state Activated" \
/tmp/arm-boot-nosecure.log
grep -q "SVC: SYS_SIM_PROVISION slot 99 DENIED" /tmp/arm-boot-nosecure.log
# Complements kernel-tests above: boots the *full* main.rs demo (every
# phase's feature, not just basic_boot's minimal boot+CPU-init smoke
# test) and greps its serial output. Coarser and more brittle than a real
# exit code, but it's the only thing here that actually exercises
# everything main.rs does end to end in one run.
boot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: nightly
targets: x86_64-unknown-none
# `rust-src`: the `bootloader` crate's build script (invoked via
# `xtask` in this job) compiles its own boot-sector/stage-2/3/4
# helper binaries for bare-metal targets, which needs to build
# core/alloc from source — without this component that fails with
# "does not exist, unable to build with the standard library".
components: llvm-tools, rust-src
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
kernel
xtask
grid-sandbox-host
# main.rs now `include_bytes!`s grid-sandbox-host's compiled output
# directly (Phase B7 — see citadel.rs's doc comment), so the kernel
# itself won't even compile without this built first, the same
# ordering requirement kernel-tests' grid_sandbox_wasm job already
# has for the same reason.
- name: build grid-sandbox-host (payload for Phase B7)
working-directory: grid-sandbox-host
run: cargo build --target x86_64-unknown-none --release
- name: install QEMU
run: sudo apt-get update && sudo apt-get install -y qemu-system-x86
- name: build boot image
working-directory: xtask
run: cargo run -- build --release
- name: boot in QEMU and check for kernel serial output
run: |
# 90s, not 20s: a legacy multi-stage BIOS boot (SeaBIOS -> this
# crate's own stage 2/3/4 -> kernel ELF load) reading a
# multi-megabyte payload has real, observed variance depending
# on the runner's disk/CPU emulation path -- 20s was cutting it
# close even before Phase B7 added a ~2 MB embedded binary to
# read.
timeout 90 qemu-system-x86_64 \
-drive format=raw,file=target/runix-bios.img \
-serial stdio -display none -no-reboot -m 128M \
| tee /tmp/boot.log || true
grep -q "Runix kernel: boot OK" /tmp/boot.log
grep -q "Runix kernel: CPU init OK" /tmp/boot.log
grep -q "EXCEPTION: BREAKPOINT" /tmp/boot.log
grep -q "Runix kernel: breakpoint exception handled, execution resumed" /tmp/boot.log
grep -q "Runix kernel: memory init OK" /tmp/boot.log
grep -q "Runix kernel: heap alloc test OK (box=41, vec_len=100, vec_sum=4950)" /tmp/boot.log
grep -q "Runix kernel: interrupts enabled" /tmp/boot.log
grep -q "Runix kernel: timer interrupt OK" /tmp/boot.log
grep -q "Runix kernel: scheduler test OK" /tmp/boot.log
# Fair round-robin, not just "didn't crash": each of the three
# spawned threads must get all 3 of its turns, in order, before
# the run ends — a scheduler that starves one thread or replays
# another's context would produce a different sequence here.
grep -q "thread A: iteration 2" /tmp/boot.log
grep -q "thread B: iteration 2" /tmp/boot.log
grep -q "thread C: iteration 2" /tmp/boot.log
grep -q "Runix kernel: syscall ABI OK" /tmp/boot.log
# The exact bytes, in order — proves the channel didn't drop,
# duplicate, or reorder anything crossing the syscall boundary.
grep -q "Runix kernel: IPC test OK (Phase 6: received \['X', 'Y', 'Z'\] via port 0)" /tmp/boot.log
# Capability gate (B4): authorized sender's send lands (return 0),
# unauthorized sender's is denied (return u64::MAX) before it ever
# reaches the channel — only 'K' shows up, never 'X'.
grep -q "thread sender_authorized: SYS_IPC_SEND returned 0" /tmp/boot.log
grep -q "thread sender_unauthorized: SYS_IPC_SEND returned 18446744073709551615" /tmp/boot.log
grep -q "Runix kernel: capability gate OK (Phase B4: port 1 received Some('K')" /tmp/boot.log
# Revocation (B5): a token valid on every count verify() itself
# checks (signature, expiry, resource) is still denied once
# explicitly revoked — port 2 gets nothing.
grep -q "thread sender_revoked: SYS_IPC_SEND returned 18446744073709551615" /tmp/boot.log
grep -q "Runix kernel: capability revocation OK (Phase B5: port 2 received None" /tmp/boot.log
# CITADEL (B6): demo call proves the allowlist gate itself works
# — a module signed for its exact bytes is accepted, tampered
# bytes are refused.
grep -q "Runix kernel: CITADEL boot authorization OK (Phase B6: authorized=Ok(())" /tmp/boot.log
# CITADEL (B7): the same gate, now actually deciding whether a
# real binary (grid-sandbox-host) gets loaded and run at all —
# authorized, then parsed/loaded/run as an isolated ring 3
# process hosting wasmi, printing "Hi" via two host.print calls
# that round-trip through the syscall gate.
grep -q "Runix kernel: grid-sandbox-host authorized by CITADEL allowlist" /tmp/boot.log
grep -q "Runix kernel: grid-sandbox-host loaded, entry point" /tmp/boot.log
grep -q "^Hi" /tmp/boot.log
grep -q "Runix kernel: grid-sandbox-host ran wasmi in an isolated ring 3 process" /tmp/boot.log
grep -q "Runix kernel: entering ring 3" /tmp/boot.log
# Printed by user_hello() running at CPL 3, one byte per SYS_WRITE
# syscall — proves the syscall gate's DPL actually admits a ring 3
# caller, not just that the ring 0 -> ring 3 jump didn't fault.
grep -q "^USR$" /tmp/boot.log