diff --git a/.gitattributes b/.gitattributes index 748cd70d..722c576e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,4 @@ -# LF everywhere, whatever core.autocrlf says: a CRLF script fails with -# "bad interpreter". +# LF everywhere, whatever core.autocrlf says: a CRLF script fails with "bad interpreter". * text=auto eol=lf *.sh text eol=lf diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml index 42aabf1c..d2f9cb8d 100644 --- a/.github/actions/prepare/action.yml +++ b/.github/actions/prepare/action.yml @@ -12,11 +12,7 @@ runs: steps: - shell: bash run: | - # Everything the build or a test writes, temporary files and Rust - # targets included, goes under /mnt, and the image's SDKs are deleted: - # a full disk kills the runner before it writes a report. Deleting them - # takes minutes, so it runs on in the background, detached from the - # step's output; nothing needs the room that soon. + # A full disk kills the runner unreported: write under /mnt, delete the SDKs in the background. sudo mkdir -p /mnt/kryptik sudo chown "$(id -u):$(id -g)" /mnt/kryptik mkdir -p "$KRYPTIK_WORK" "$KRYPTIK_SOURCES" "$KRYPTIK_OUT" /mnt/kryptik/tmp /mnt/kryptik/cargo @@ -33,8 +29,7 @@ runs: run: | sudo pip install --break-system-packages virt-firmware==26.9 virt-fw-vars --help > /dev/null - # /dev/kvm exists on a hosted runner but only root may open it; without - # it every VM falls back to TCG and runs into its timeout. + # Only root may open /dev/kvm on a hosted runner; without it every VM falls back to TCG and times out. echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm @@ -46,8 +41,7 @@ runs: with: path: /mnt/kryptik/sources key: sources-v3-${{ hashFiles('sources.lock') }} - # A changed lock restores the previous set and fetches only what is - # new; every file is still hashed against the lock. + # A changed lock restores the previous set; every file is still hashed against the lock. restore-keys: ${{ inputs.sources == 'fetch' && 'sources-' || '' }} - if: inputs.sources == 'fetch' && steps.sources.outputs.cache-hit != 'true' @@ -55,12 +49,10 @@ runs: run: | make sources ./tools/prune-sources.sh - # The signatures too, so the set this job saves is the one CI's - # signature gate reads from disk; this pass is not the verdict. + # Fetches the signatures into the saved set for CI's gate; this pass is not the verdict. ./tools/verify-signatures.sh > /dev/null 2>&1 || true - # Saved as soon as the set is verified, not when the job ends: a job that - # fails later would otherwise fetch the lock's new files again next time. + # Saved now rather than at the end, so a job that fails later does not refetch the new files. - if: inputs.sources == 'fetch' && steps.sources.outputs.cache-hit != 'true' uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b678181e..ec73f311 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,8 +5,7 @@ on: branches: [main] pull_request: schedule: - # Weekly, for the kernel EOL check: a pinned kernel does not change, but - # its support status does (ADR-009). + # Weekly: a pinned kernel's support status changes even when the pin does not (ADR-009). - cron: '0 6 * * 1' permissions: @@ -23,8 +22,7 @@ jobs: - name: Every commit is DevomB run: | - # On a pull request the checkout is GitHub's synthetic merge commit, - # which never reaches main; check the branch head instead. + # A pull request's checkout is a synthetic merge commit; check the branch head instead. if [ "${{ github.event_name }}" = "pull_request" ]; then ./tools/check-commit-identity.sh "${{ github.event.pull_request.head.sha }}" else @@ -55,8 +53,7 @@ jobs: - name: Tool fixture suites run: | - # Exit 77 means "cannot run here" (a suite that needs root and a - # built sysroot). Those are listed separately, never counted as passes. + # Exit 77 means the suite cannot run here: it is listed as skipped, never counted as a pass. fail=0 skipped="" for t in tools/tests/*.sh tools/tests/*.py; do @@ -87,8 +84,7 @@ jobs: - name: Executable bits are recorded run: | - # A commit from Windows records 100644, and a Linux clone then fails - # with "Permission denied". + # A commit from Windows records 100644; a Linux clone then fails with "Permission denied". fail=0 for f in build/stages/*.sh tools/*.sh tools/tests/*.sh tools/tests/*.py \ tools/image/*.sh tools/image/*.py \ @@ -112,9 +108,7 @@ jobs: manifest: name: Source manifest runs-on: ubuntu-latest - # The Distro workflow's path: a cache's version is a hash of its path, so - # under any other one a pull request misses main's copy and saves a whole - # second one of its own, and those evicted the build trees. + # The Distro workflow's path: a cache's version hashes its path, so any other misses main's copy. env: KRYPTIK_SOURCES: /mnt/kryptik/sources steps: @@ -125,9 +119,7 @@ jobs: sudo mkdir -p /mnt/kryptik && sudo chown "$(id -u):$(id -g)" /mnt/kryptik ./tools/fetch-sources.sh --list - # The provenance gate hashes the tarballs, so they are cached. restore-keys - # brings back the last set; `make sources` re-hashes every file against - # the lock and fetches only what is missing, and the prune drops the rest. + # Restores the last set; `make sources` checks it against the lock and fetches what is missing. - name: Upstream sources (cached by sources.lock) id: sources uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 @@ -137,9 +129,7 @@ jobs: restore-keys: | sources- - # The signatures are fetched here too, into the set the cache keeps: - # the gate below then reads them from disk instead of asking sixty - # hosts again on every run. What this pass says is not the verdict. + # Fetches the signatures into the cached set for the gate below; this pass is not the verdict. - name: Fetch and verify the sources if: steps.sources.outputs.cache-hit != 'true' run: | @@ -147,8 +137,7 @@ jobs: ./tools/prune-sources.sh ./tools/verify-signatures.sh > /dev/null 2>&1 || true - # Not from a pull request: its cache is scoped to its merge ref, so every - # one whose lock differs from main's would keep a whole copy of its own. + # Not from a pull request: its cache is scoped to the merge ref and would duplicate the set. - name: Save the sources if: ${{ github.event_name != 'pull_request' && steps.sources.outputs.cache-hit != 'true' }} uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 @@ -156,8 +145,7 @@ jobs: path: /mnt/kryptik/sources key: ${{ steps.sources.outputs.cache-primary-key }} - # Informational: a mirror that is down or throttling is not a defect here. - # A one-byte ranged GET, because several GNU mirrors refuse HEAD. + # Informational, as mirrors go down; a one-byte ranged GET, since some GNU mirrors refuse HEAD. - name: Every source URL is reachable continue-on-error: true run: | @@ -183,17 +171,13 @@ jobs: done < <(./tools/fetch-sources.sh --list) echo if [ "$missing" -gt 0 ]; then - echo "$missing source(s) unreachable this run." - echo "Usually a transient mirror problem. Investigate only if the" - echo "same package fails across several runs." + echo "$missing source(s) unreachable; investigate if the same one fails across several runs." else echo "all sources reachable" fi - name: Provenance of unsigned sources - # `gh` needs GH_TOKEN even for a public repository. Strict on push and - # on the schedule; informational on pull requests, where a fork gets - # no token. + # Strict on push and schedule; informational on pull requests, where a fork gets no token. env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -208,12 +192,10 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - # One assurance class per source and no total: a signature, a signed - # tag and a publisher checksum are different strengths of evidence. + # One class per source and no total: the classes are different strengths of evidence. ./tools/provenance-inventory.sh - # The gate: every signature verifies against a key a publisher states, - # or tools/source-notes.tsv says which routes to the key were tried. + # Each signature must verify against its publisher's key, or tools/source-notes.tsv says why not. - name: Signatures, strict run: ./tools/verify-signatures.sh --strict @@ -239,9 +221,7 @@ jobs: - name: kryptikd unit tests run: cd compartments/kryptikd && cargo test - # kryptik-wlproxy and zoneid: their unit and live tests, and the shipped - # zone files against the identity invariant. The acceptance runs the same - # suite hours later; this is the answer a compositor change gets first. + # wlproxy and zoneid tests, and the shipped zone files against the identity invariant. - name: Compositor tests run: tools/tests/compositor.sh @@ -252,17 +232,14 @@ jobs: - name: Allow unprivileged user namespaces run: | - # Ubuntu 24.04 restricts unprivileged user namespaces, which the zone - # suites need here. (Kryptik itself disables them: zones are created - # with privilege.) + # Ubuntu 24.04 restricts unprivileged user namespaces, which the zone suites need here. sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 echo "userns check:" unshare --user --map-root-user id -u - name: kryptikd builds for musl run: | - # The shipped binary is static musl; glibc-only assumptions show up - # here (ioctl's request type differs, for one). + # The shipped binary is static musl; this catches glibc-only assumptions. rustup target add x86_64-unknown-linux-musl cd compartments/kryptikd cargo build --release --target x86_64-unknown-linux-musl @@ -275,24 +252,17 @@ jobs: - name: Real-launcher suite (the launch path itself) run: | - # adversarial.sh tests the primitives; this tests `kryptikd run` - # applying them in order. Unprivileged here, so groups K (privileged - # launch) and M (cgroup limits) report NOT RUN. + # Unprivileged here, so groups K (privileged launch) and M (cgroup limits) report NOT RUN. cd compartments/kryptikd && cargo build --quiet cd ../.. && ./compartments/tests/launcher.sh | tee /tmp/launcher.out exit "${PIPESTATUS[0]}" - - name: The runner must ADMIT what it could not test + - name: The launcher suite reports its gaps run: | - # On a runner the launcher suite must end "PASSED WITH GAPS". A clean - # pass would mean the root-only checks ran vacuously or stopped - # reporting themselves. They run as root in the Distro workflow's - # acceptance (zones-test). + # Groups K and M need root: they run in the Distro workflow's zones-test, never here. if ! grep -q 'PASSED WITH GAPS' /tmp/launcher.out; then - echo "::error::The launcher suite reported a clean pass on a CI runner." - echo "Group K (privileged launch) and group M (cgroup limits) cannot run" - echo "here. If they are no longer reported as NOT RUN, they are either" - echo "running vacuously or have been dropped. Neither is a pass." + echo "::error::The launcher suite passed cleanly on a runner, where groups K and M cannot run." + echo "They either ran vacuously or were dropped; neither is a pass." grep -E 'passed,|not run' /tmp/launcher.out || true exit 1 fi @@ -312,22 +282,17 @@ jobs: | **target kernel (root, installed system, userns restricted)** | **no — the Distro workflow's acceptance run** | The privileged launch path, the cgroup limits, the zone lifecycle - under root and the boot itself are tested by `make acceptance` on - the built media under KVM. A green tick here is not evidence about - them. + under root and the boot are tested by `make acceptance` on the built + media under KVM, not here. SUMMARY - name: The user-facing command run: | - # `kryptik` wraps kryptikd. It must not claim encryption the build - # cannot deliver, execute its own config file, or offer commands - # that do not work. ./compartments/tests/cli.sh - name: The launch daemon, over its socket run: | - # A runner has no XDG_RUNTIME_DIR; give it one as a session would, - # or the registry and proxy-socket checks cannot run. + # A runner has no XDG_RUNTIME_DIR, which the registry and proxy-socket checks need. uid="$(id -u)" sudo install -d -m 0700 -o "$uid" -g "$(id -g)" "/run/user/$uid" export XDG_RUNTIME_DIR="/run/user/$uid" @@ -336,8 +301,7 @@ jobs: - name: The zone boundary's regression probes run: | - # Exit status is the number of failures; checks that need root and - # the target kernel say NOT RUN and are covered by zones-test. + # Exits with the number of failures; root-only checks say NOT RUN and run in zones-test. cd compartments/kryptikd && cargo build --quiet cd ../.. && ./compartments/kryptikd/probes/boundary-checks.sh @@ -347,12 +311,10 @@ jobs: steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 - # Every pinned series with a published support window is still inside it. - name: Pinned series are supported upstream run: ./tools/check-support-status.sh --strict - # Survey (network), then gate (no network). Informational on a pull - # request: an upstream release this morning is not the PR's fault. + # Survey, then gate; informational on a pull request, since an upstream release is not its fault. - name: Pins behind upstream are reviewed env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -370,8 +332,7 @@ jobs: - name: Kernel config fragments validate against pinned source run: | - # merge_config.sh drops unknown symbols silently, so every fragment - # symbol is checked against the pinned source. + # merge_config.sh drops unknown symbols silently; check each against the pinned source. mkdir -p sources # Exact names: '^linux ' would also match linux-hardened. ./tools/fetch-sources.sh --list \ @@ -384,12 +345,9 @@ jobs: ./tools/validate-kernel-config.sh --hardened ./tools/validate-kernel-config.sh --boot - - name: The resolved config honours every fragment line and kernel-hardening-checker accepts it + - name: Resolved kernel config keeps every fragment line and passes the hardening checker run: | - # Resolves the config as stage 05 does and refuses a dropped line - # (kconfig drops one silently for an unmet dependency), then holds - # kernel-hardening-checker's failures to checker-accepted.txt. - # The GCC plugin options exist only with plugin headers installed. + # The GCC plugin options exist only with the plugin headers installed. sudo apt-get install -y --no-install-recommends \ "gcc-$(gcc -dumpversion | cut -d. -f1)-plugin-dev" flex bison make check-kernel-hardening diff --git a/.shellcheckrc b/.shellcheckrc index 109105b8..f52e6962 100644 --- a/.shellcheckrc +++ b/.shellcheckrc @@ -1,4 +1,2 @@ -# SC2164 ("cd ... || exit"): every script sources build/lib/common.sh, whose -# `set -Eeuo pipefail` and ERR trap already abort on a failed cd. A script -# that does not source it should re-enable the check locally. +# common.sh's ERR trap aborts on a failed cd; a script that does not source it should re-enable SC2164. disable=SC2164