Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 43 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ check-compiler:
repro_home_b="$$(/usr/bin/mktemp -d -- "$${TMPDIR:-/tmp}/orange-repro-home.XXXXXXXX")"; \
trap '/usr/bin/rm -rf -- "$$cargo_home" "$$repro_home_b"' EXIT; \
repro_home_b="$$(CDPATH= cd -- "$$repro_home_b" && pwd -P)"; \
gate_tools="$$(/usr/bin/mktemp -d -- "$${TMPDIR:-/tmp}/orange-gate-tools.XXXXXXXX")"; \
trap '/usr/bin/rm -rf -- "$$cargo_home" "$$repro_home_b" "$$gate_tools"' EXIT; \
gate_tools="$$(CDPATH= cd -- "$$gate_tools" && pwd -P)"; \
/usr/bin/mkdir -- "$$cargo_home/home" "$$cargo_home/tmp" "$$gate_tools/toolchain"; \
toolchain_root="$$(/usr/bin/env -i HOME="$$HOME" LANG=C LC_ALL=C PATH="$$PATH" RUSTUP_TOOLCHAIN=1.96.1 rustc --print sysroot)"; \
toolchain_root="$$(CDPATH= cd -- "$$toolchain_root" && pwd -P)"; \
[[ -f "$$toolchain_root/bin/cargo" && ! -L "$$toolchain_root/bin/cargo" && -x "$$toolchain_root/bin/cargo" && -f "$$toolchain_root/bin/rustc" && ! -L "$$toolchain_root/bin/rustc" && -x "$$toolchain_root/bin/rustc" ]] || { printf '%s\n' 'selected Rust toolchain root is invalid' >&2; exit 1; }; \
gate_uid="$$(/usr/bin/id -u)"; \
gate_gid="$$(/usr/bin/id -g)"; \
namespace_setup='set -euo pipefail; toolchain_root="$$1"; gate_tools="$$2"; shift 2; /usr/bin/mount --bind "$$toolchain_root" "$$gate_tools/toolchain"; /usr/bin/mount --options remount,bind,ro,nosuid,nodev "$$gate_tools/toolchain"; /usr/bin/mount --types tmpfs --options mode=755,nosuid,nodev,noexec tmpfs /home; exec "$$@"'; \
namespace_runner=( \
/usr/bin/unshare \
--user \
Expand All @@ -30,6 +38,13 @@ check-compiler:
--kill-child=KILL \
--mount-proc \
--net \
/bin/bash \
-p \
-c \
"$$namespace_setup" \
gate-namespace \
"$$toolchain_root" \
"$$gate_tools" \
/usr/bin/setpriv \
--bounding-set=-all \
--inh-caps=-all \
Expand All @@ -48,6 +63,13 @@ check-compiler:
--kill-child=KILL \
--mount-proc \
--net \
/bin/bash \
-p \
-c \
"$$namespace_setup" \
gate-namespace \
"$$toolchain_root" \
"$$gate_tools" \
/usr/bin/setpriv \
--bounding-set=-all \
--inh-caps=-all \
Expand All @@ -64,18 +86,34 @@ check-compiler:
exec 8<&- 9<&-; \
cd -- /; \
"$${namespace_runner[@]}" \
"$$gate_tools/fs-sandbox" \
--dir / \
--ro /usr \
--ro /etc \
--ro /proc \
--ro /sys \
--ro "$$gate_tools" \
--ro "$$gate_tools/toolchain" \
--rw /dev/null \
--rw /dev/zero \
--rw /dev/random \
--rw /dev/urandom \
--rw "$$cargo_home" \
--rw "$$repro_home_b" \
-- \
/usr/bin/env -i \
CARGO_HOME="$$cargo_home" \
CARGO_NET_OFFLINE=true \
CARGO_TARGET_DIR="$$cargo_home/target" \
CARGO_TERM_COLOR=never \
HOME="$$HOME" \
HOME="$$cargo_home/home" \
LANG=C \
LC_ALL=C \
PATH="$$PATH" \
PATH="$$gate_tools/toolchain/bin:/usr/bin:/bin" \
RUSTDOCFLAGS="-D warnings" \
RUSTUP_TOOLCHAIN=1.96.1 \
SOURCE_DATE_EPOCH=0 \
TMPDIR="$$cargo_home/tmp" \
TZ=UTC \
"$$@" \
); \
Expand Down Expand Up @@ -117,8 +155,10 @@ check-compiler:
/usr/bin/cmp --silent -- "$$repro_source_paths" "$$repro_source_paths_after" || { printf '%s\n' 'tracked source membership changed during archive capture' >&2; exit 1; }; \
/usr/bin/rm -- "$$repro_source_paths_after"; \
verify_capture_identity; \
/usr/bin/env -i LANG=C LC_ALL=C PATH=/usr/bin:/bin /usr/bin/cc -std=c17 -O2 -D_FORTIFY_SOURCE=3 -fPIE -pie -Wall -Wextra -Werror -pedantic -Wl,-z,relro,-z,now "$$cargo_home/check-src/tools/fs_sandbox.c" -o "$$gate_tools/fs-sandbox"; \
[[ -f "$$gate_tools/fs-sandbox" && ! -L "$$gate_tools/fs-sandbox" && -x "$$gate_tools/fs-sandbox" ]] || { printf '%s\n' 'filesystem sandbox build is invalid' >&2; exit 1; }; \
manifest="$$cargo_home/check-src/compiler/Cargo.toml"; \
run_cargo /bin/bash -p -c 'for capability_set in CapInh CapPrm CapEff CapBnd CapAmb; do [[ "$$(/usr/bin/sed -n "s/^$${capability_set}:[[:space:]]*//p" /proc/self/status)" == 0000000000000000 ]] || exit 1; done; [[ $$$$ == 1 && $$PPID == 0 && "$$(/usr/bin/id -u)" == "$$1" && "$$(/usr/bin/id -g)" == "$$2" && "$$(/usr/bin/sed -n "s/^NoNewPrivs:[[:space:]]*//p" /proc/self/status)" == 1 && ! -e /proc/self/fd/8 && ! -e /proc/self/fd/9 && -z "$$(/usr/bin/sed -n "2p" /proc/net/route)" ]]' gate-isolation "$$gate_uid" "$$gate_gid"; \
run_cargo /bin/bash -p -c 'for capability_set in CapInh CapPrm CapEff CapBnd CapAmb; do [[ "$$(/usr/bin/sed -n "s/^$${capability_set}:[[:space:]]*//p" /proc/self/status)" == 0000000000000000 ]] || exit 1; done; for descriptor in /proc/self/fd/*; do [[ ! -e "$$descriptor" || "$${descriptor##*/}" =~ ^[012]$$ ]] || exit 1; done; ! /usr/bin/head -c 1 -- "$$3/Makefile" >/dev/null 2>&1 || exit 1; [[ $$$$ == 1 && $$PPID == 0 && "$$(/usr/bin/id -u)" == "$$1" && "$$(/usr/bin/id -g)" == "$$2" && "$$HOME" == "$$4" && "$$PATH" == "$$5/toolchain/bin:/usr/bin:/bin" && "$$(/usr/bin/sed -n "s/^NoNewPrivs:[[:space:]]*//p" /proc/self/status)" == 1 && -z "$$(/usr/bin/sed -n "2p" /proc/net/route)" ]]' gate-isolation "$$gate_uid" "$$gate_gid" "$$repository_root" "$$cargo_home/home" "$$gate_tools"; \
run_cargo /usr/bin/env PYTHONHASHSEED=0 /usr/bin/python3 -S -P -B -X utf8 -W error::ResourceWarning "$$cargo_home/check-src/tools/validate_foundation.py"; \
run_cargo /usr/bin/env PYTHONHASHSEED=0 PYTHONPYCACHEPREFIX="$$cargo_home/snapshot-python-cache" /usr/bin/python3 -S -P -B -X utf8 -W error::ResourceWarning -c 'import sys, unittest; sys.path.insert(0, sys.argv.pop(1)); unittest.main(module=None)' "$$cargo_home/check-src" discover -s "$$cargo_home/check-src/tools/tests" -p 'test_*.py'; \
run_cargo /usr/bin/env PYTHONHASHSEED=0 /usr/bin/python3 -S -P -B -X utf8 -W error::ResourceWarning "$$cargo_home/check-src/tools/validate_foundation.py"; \
Expand Down
25 changes: 19 additions & 6 deletions compiler/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,19 @@ The user-namespace path retains its namespace-granted capabilities only long
enough for `setpriv` to remove every capability from the inheritable, permitted,
effective, bounding, and ambient sets. The privileged supervisor removes the
same sets while restoring the invoking identity. Both paths assert the restored
identity, five empty capability sets, private process view, and empty route table
before copied code runs. The namespace
supervisor kills its child if supervision is interrupted. Trusted gate operations
alone retain the descriptors used for later extraction and identity checks. The
copied validator first policy-checks that exact exported tree before its
foundation test modules import.
identity, five empty capability sets, private process view, and empty route table.
Before the drop, the namespace supervisor bind-mounts the selected toolchain
read-only and covers `/home` with a private non-executable `tmpfs`. A protected C
launcher built from the captured tree requires Landlock ABI 3 or newer, permits
directory-name traversal but grants file reads and execution only to the system
roots and selected toolchain, and grants writes only to the two gate roots and
four admitted character devices. It also closes every inherited descriptor above
standard error before execution. Copied commands receive isolated `HOME`,
`TMPDIR`, and `PATH` values; a runtime assertion confirms that the original
checkout is unreadable. The namespace supervisor kills its child if supervision
is interrupted. Trusted gate operations alone retain the descriptors used for
later extraction and identity checks. The copied validator first policy-checks
that exact exported tree before its foundation test modules import.
After those tests, it policy-checks the tree again before Cargo, so Python-test
drift cannot reach Rust execution. Formatting, linting, documentation, and Rust
tests use the same extracted check root. A third policy check runs after all Rust
Expand All @@ -86,6 +93,12 @@ non-symlink files with identical complete modes and bytes. This is
source-relocated same-host reproducibility evidence, not a cross-platform or
independently rebuilt claim.

Landlock does not mediate every metadata operation, and the gate intentionally
allows directory listing without file contents so the selected Rust toolchain can
discover its sysroot. Standard input, output, and error remain caller-provided;
the system C compiler, launcher source, kernel, mount implementation, and
allowlisted system trees remain trusted boundaries.

`orangec` accepts up to 256 source inputs in argument order. Argument parsing
inspects at most 4 MiB (`4 * 1024 * 1024` bytes) of encoded command-line
arguments per invocation, charged before each argument is interpreted.
Expand Down
4 changes: 2 additions & 2 deletions docs/operations/CI_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ tools and services are ambient rather than admitted, fixed inputs:
| Bash and Python 3 standard library | First-party checks and composite Actions | Runner-provided executables; exact versions and binary/package digests are not captured. No PyPI packages are installed by first-party checks |
| Git | Checkout implementation and validator repository inventory | The first-party validator selects `/usr/bin/git`, fixes child-command lookup to `/usr/bin:/bin`, requires a literal local `.git` directory with regular config/index files and no common-directory or object alternates, clears inherited/system/global Git controls, and overrides the local settings relevant to its commands; other local config remains an ambient input, and the runner executable's exact version, package provenance, license, and binary digest are not captured |
| Docker daemon, kernel, and CPU | zizmor and Scorecard containers | Runner-provided execution boundary; versions, configuration, and host identity are not captured |
| `cmp`, `curl`, `sha256sum`, `tar`, `stat`, `install`, `mktemp`, `rm`, `uname`, `sudo`, `unshare`, and `setpriv` | Compare captured sources; download, verify, extract, inspect, install, clean temporary tools, create private mount, PID, `/proc`, and network namespaces, and restore an unprivileged copied-command identity when user-namespace mapping is unavailable | Runner-provided system tools; exact versions, provenance, package licenses, and binary digests are not captured. Namespace availability, passwordless namespace-setup policy, privilege dropping, and enforcement depend on the mutable runner image and kernel |
| `cc`, `cmp`, `curl`, `mount`, `sha256sum`, `tar`, `stat`, `install`, `mktemp`, `rm`, `uname`, `sudo`, `unshare`, and `setpriv` | Build the protected Landlock launcher; compare captured sources; download, verify, extract, inspect, install, clean temporary tools; bind the selected toolchain read-only; hide `/home`; create private mount, PID, `/proc`, and network namespaces; and restore an unprivileged copied-command identity when user-namespace mapping is unavailable | Runner-provided system tools; exact versions, provenance, package licenses, and binary digests are not captured. C compilation, Landlock ABI and enforcement, mount/namespace availability, passwordless namespace-setup policy, and privilege dropping depend on the mutable runner image and kernel |
| GNU Make | Protected compiler recipe and optional local `make check` entry point | Required hosted CI invokes the compiler target with built-in rules and variables disabled; exact binary version, provenance, and package license are unrecorded |
| Artifact, code-scanning, GHCR, and external web services | Storage, analysis upload, image retrieval, and link observations | Mutable hosted services; terms and service behavior are external assumptions, not repository-pinned software inputs |

Expand All @@ -279,7 +279,7 @@ recorded identities and digests.

| Result | What is currently fixed | What the result actually establishes | Why it is not reproducible evidence yet |
| --- | --- | --- | --- |
| `scripts/ci/check-repository` | Repository bytes, Cargo lock, selected Rust version, allowlisted Python and Cargo environments, fresh bytecode/build cache roots, and private mount, PID, `/proc`, and network namespaces for copied commands; the gate uses an unprivileged user namespace where available or a privilege-dropping namespace supervisor otherwise, removes every inheritable, permitted, effective, bounding, and ambient capability before copied code, and runs Cargo locked/offline after the toolchain exists | Rust formatting, lint, docs, debug and optimized unit/CLI tests, repository invariants, and synthetic/adversarial fixture expectations pass under the executing toolchain and OS without copied-code visibility of the trusted parent process, Linux capabilities, privilege-reacquisition permission, or a new external network path | Rust archives, Python, shell, Make, kernel, namespace and privilege-drop implementations, filesystem, and host tools are not content-fixed; no signed execution manifest or independently replayable environment is emitted |
| `scripts/ci/check-repository` | Repository bytes, Cargo lock, selected Rust version, allowlisted Python and Cargo environments, fresh bytecode/build cache roots, private mount, PID, `/proc`, and network namespaces, a read-only selected-toolchain bind, hidden `/home`, and a protected Landlock allowlist for copied commands; the gate uses an unprivileged user namespace where available or a privilege-dropping namespace supervisor otherwise, removes all five capability sets, closes nonstandard descriptors, and runs Cargo locked/offline | Rust formatting, lint, docs, debug and optimized unit/CLI tests, repository invariants, and synthetic/adversarial fixture expectations pass under the executing toolchain and OS without copied-code file contents outside admitted roots, trusted-parent process visibility, inherited nonstandard descriptors, Linux capabilities, privilege-reacquisition permission, or a new external network path | Rust archives, C compiler, Python, shell, Make, kernel, Landlock, namespace and privilege-drop implementations, filesystem, and host tools are not content-fixed; directory names and unmediated metadata operations remain visible, standard descriptors are caller-provided, and no signed execution manifest or independently replayable environment is emitted |
| Required hosted CI | Repository revision, direct Action SHAs, actionlint archive digest, and zizmor image digest | The configured repository, Markdown, workflow, and workflow-security methods passed in one hosted run | Runner image, Node, Git, Docker host, Action bundles, and service behavior are not fully archived or fixed |
| Dependency Review | Event base/head identities and the selected Action revision | GitHub reported no configured dependency-policy violation for its then-current dependency data | Dependency graph/API state and service implementation are external, time-indexed inputs |
| External Links | lychee archive digest, link-check flags, and repository locators | The non-excluded endpoints produced accepted responses during the run | Remote content, DNS, TLS, routing, rate limits, and server policy change independently of repository bytes |
Expand Down
Loading