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
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,13 @@ jobs:

- name: No CRLF in scripts
run: |
if grep -rlU $'\r' --include='*.sh' --include='Makefile' . ; then
# By name or by shebang: tools/kryptik and the run files have no extension.
git ls-files | while IFS= read -r f; do
case "$f" in *.sh|*.rs|*.py|Makefile|*/Makefile) ;; *) [ "$(head -c 2 "$f" 2>/dev/null)" = '#!' ] || continue ;; esac
if grep -qU $'\r' "$f"; then echo "$f"; fi
done > /tmp/crlf
if [ -s /tmp/crlf ]; then
cat /tmp/crlf
echo "CRLF found - scripts will fail on Linux with 'bad interpreter'"
exit 1
fi
Expand Down
2 changes: 1 addition & 1 deletion build/stages/05-kernel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export LFS_TGT

# PATH stays the chroot's /usr/bin:/usr/sbin; never the /tools cross toolchain.

KRYPTIK_JOBS="${KRYPTIK_JOBS:-$(nproc)}"
KRYPTIK_JOBS="${KRYPTIK_JOBS:-$(kryptik_default_jobs)}"
export MAKEFLAGS="-j${KRYPTIK_JOBS}"

stage_contract "${BASH_SOURCE[0]}" "kernel-" gcc
Expand Down
9 changes: 5 additions & 4 deletions docs/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ off the thread beside the kernel, since the kernel's own execution carries no
cookie, and that is the leak the mitigations exist for. Closing it with SMT on
means a flush on every kernel entry, which costs more than the threads give.
No measurement changes which boundary the cookies leave open. The cookies
stay as defence in depth for a machine whose firmware has no SMT to turn off;
with no sibling thread online the kernel refuses the cookie (`ENODEV`) and
stay as defence in depth for when SMT is on all the same: plain `nosmt` lets
root turn it back on through `/sys/devices/system/cpu/smt/control`. With no
sibling thread online the kernel refuses the cookie (`ENODEV`) and
`kryptikd status` says `no-smt`.

## ADR-012: Device firmware from linux-firmware, on the verified root
Expand Down Expand Up @@ -190,8 +191,8 @@ it a machine runs whatever microcode its firmware last shipped, which on older
machines means known, unfixed CPU vulnerabilities.

**Cost:** about 135 MB after zstd (385 MB of files; Intel Wi-Fi is 56 MB and
amdgpu 38 MB compressed) on a 2.7 GB root image, and an input nobody here can
read.
amdgpu 38 MB compressed) on a root image of about 1.7 GB, and an input nobody
here can read.

## ADR-013: A driver is built in only when boot needs it

Expand Down
10 changes: 4 additions & 6 deletions tools/artifact-manifest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,11 @@ emit_inputs() {
emit_tree() {
local hashes; hashes="$(mktemp)"
local meta; meta="$(mktemp)"
local ferr; ferr="$(mktemp)"
local raw; raw="$(mktemp)"
# A RETURN trap outlives the function, so it clears itself.
# shellcheck disable=SC2064
trap "rm -f '$hashes' '$meta'" RETURN

local ferr; ferr="$(mktemp)"
local raw; raw="$(mktemp)"
# shellcheck disable=SC2064
trap "rm -f '$ferr' '$raw'" RETURN
trap "rm -f '$hashes' '$meta' '$ferr' '$raw'; trap - RETURN" RETURN

# common.sh's ERR trap exits, and it fires even under set +e.
local frc=0
Expand Down
4 changes: 2 additions & 2 deletions tools/check-pin-reviews.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ set -uo pipefail
SURVEY=""; REVIEWS="$(dirname "${BASH_SOURCE[0]}")/pin-reviews.tsv"; NO_HELD=0
while [[ $# -gt 0 ]]; do
case "$1" in
--survey) SURVEY="${2:-}"; shift 2 ;;
--reviews) REVIEWS="${2:-}"; shift 2 ;;
--survey) SURVEY="${2:?--survey needs a file}"; shift 2 ;;
--reviews) REVIEWS="${2:?--reviews needs a file}"; shift 2 ;;
--no-held) NO_HELD=1; shift ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
Expand Down
10 changes: 7 additions & 3 deletions tools/git-hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -36,28 +36,32 @@ done < <(git diff --cached --name-only --diff-filter=ACM \

# build/lib/common.sh is sourced, not executed, and stays 644.

# The staged file's first line: tools/kryptik and the run files are scripts with no extension.
first_line() { git show ":$1" 2>/dev/null | head -1; }

# --- CRLF -------------------------------------------------------------------
# .gitattributes normalises line endings, but `add -f` or an editor can still
# stage CRLF, which gives "bad interpreter: bash^M".
while IFS= read -r f; do
[[ -z "$f" ]] && continue
[[ "$f" =~ \.(sh|rs|py)$|(^|/)Makefile$ || "$(first_line "$f")" == '#!'* ]] || continue
if git show ":${f}" 2>/dev/null | grep -qU $'\r'; then
echo "pre-commit: ERROR ${f} contains CRLF; scripts must be LF" >&2
fail=1
fi
done < <(git diff --cached --name-only --diff-filter=ACM \
| grep -E '\.(sh|rs)$|Makefile' || true)
done < <(git diff --cached --name-only --diff-filter=ACM)

# --- shell syntax -----------------------------------------------------------
while IFS= read -r f; do
[[ -z "$f" ]] && continue
[[ -f "$f" ]] || continue
[[ "$f" == *.sh || "$(first_line "$f")" =~ ^#!.*[/\ ](ba|da)?sh([[:space:]]|$) ]] || continue
if ! bash -n "$f" 2>/dev/null; then
echo "pre-commit: ERROR ${f} has a shell syntax error" >&2
bash -n "$f" 2>&1 | sed 's/^/ /' >&2
fail=1
fi
done < <(git diff --cached --name-only --diff-filter=ACM | grep -E '\.sh$' || true)
done < <(git diff --cached --name-only --diff-filter=ACM)

if [[ "$fail" -ne 0 ]]; then
echo >&2
Expand Down
2 changes: 1 addition & 1 deletion tools/image/install-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ deny "$P1" 'Kernel panic|Oops:' "no panic during the in
# From the host side: the partition table the guest wrote.
sfdisk -l "$DISK" 2>/dev/null | grep -E '^/|Disklabel' | sed 's/^/ /'
if [[ "$(sfdisk -l "$DISK" 2>/dev/null | grep -c "^${DISK}")" -eq 4 ]]; then green "host sees four partitions on the target"; else red "host does not see four partitions"; fi
lbls="$(blkid -p -O 0 "$DISK" >/dev/null 2>&1; sfdisk -d "$DISK" 2>/dev/null | grep -o 'name="[^"]*"' | tr '\n' ' ')"
lbls="$(sfdisk -d "$DISK" 2>/dev/null | grep -o 'name="[^"]*"' | tr '\n' ' ')"
if [[ "$lbls" == *kryptik-esp* && "$lbls" == *kryptik-a* && "$lbls" == *kryptik-b* && "$lbls" == *kryptik-state* ]]; then
green "host sees the four partition labels"; else red "host labels: ${lbls}"; fi
if [[ "$FAIL" -ne 0 ]]; then printf '\nphase 1 failed; not booting the result.\n%d passed, %d failed\n' "$PASS" "$FAIL"; exit 1; fi
Expand Down
4 changes: 2 additions & 2 deletions tools/install/kryptik-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ done

# --- every external tool, checked before the first write -------------------
missing=""
for tool in sfdisk partx blockdev blkid cryptsetup stty mkfs.ext4 dd sha256sum mount umount sync awk sed \
readlink lsblk head cmp cp mkdir stat tr; do
for tool in sfdisk partx blockdev blkid cryptsetup veritysetup stty mkfs.ext4 dd sha256sum mount umount sync awk sed \
readlink lsblk head cmp cp mkdir stat tr cut basename mv mountpoint rmdir chmod rm cat sleep; do
command -v "$tool" >/dev/null 2>&1 || missing="${missing} ${tool}"
done
[ -z "$missing" ] || die "this system is missing:${missing}
Expand Down
1 change: 0 additions & 1 deletion tools/source-notes.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
# means.

# package kind location note
bc recipe-transformation build/recipes/bc.sh:s_bc s_bc() overwrites upstream's bc/fix-libmath_h with a locally authored sed script. Its output, libmath.h, is compiled into the shipped binary, so sources.lock does not fully describe what bc was built from. The recipe documents and justifies this, and verifies the result functionally (bc -l must evaluate s(0)), but the replacement file is in no lockfile. GNU bc 1.08 makes exactly this change upstream as bc/fix-libmath.sed, so moving the pin would remove the transformation rather than merely hide it.
expat undesignated-signer https://libexpat.github.io/ The release signature verifies against primary key 3176EF7DB2367F1FCA4F306B1F9B0E909AF37285, which gentoo.org's Web Key Directory publishes over HTTPS for sping@gentoo.org - a route that does not depend on the signature, so this is NOT circular trust. What is missing is upstream's own statement: libexpat designates no release signer and publishes no fingerprint on its site, in SECURITY.md, or in its release notes. Verified absent on 2026-09-11.
linux-firmware recipe-transformation build/recipes/firmware.sh:s_firmware The files build/config/firmware.list selects are stored zstd-compressed under /lib/firmware and the kernel decompresses them on load, so the bytes on the verified root are not the tarball's bytes (their decompressed content is). Shipping them uncompressed would remove this.
libnl undesignated-signer https://github.com/thom311/libnl/releases The release tarball carries a detached .sig that verifies against 67DA3FAEBAE276BA58FC6CE314F18A98993AECD5, but libnl publishes no statement of who signs its releases: not on the release page, not in the repository, and the key is in no Web Key Directory. Checked 2026-09-19. A published fingerprint from upstream would remove this.
Expand Down
15 changes: 14 additions & 1 deletion tools/update/kryptik-recover
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ for lblname in kryptik-state kryptik-media; do
done
mountpoint -q /run/kryptik-recover 2>/dev/null && umount /run/kryptik-recover
mkdir -p /run/kryptik-recover
cleanup() { mountpoint -q /run/kryptik-recover 2>/dev/null && umount /run/kryptik-recover; rmdir /run/kryptik-recover 2>/dev/null || true; }
# The ESP image first: it is a file on the medium.
cleanup() {
for d in /run/kryptik-recover-esp /run/kryptik-recover-media /run/kryptik-recover; do
# One that will not unmount must not keep the others mounted.
mountpoint -q "$d" 2>/dev/null && umount "$d" 2>/dev/null || true
rmdir "$d" 2>/dev/null || true
done
}
trap cleanup EXIT INT TERM
slot_dev() { case "$1" in a) echo "$SA" ;; b) echo "$SB" ;; *) die "slot must be a or b" ;; esac; }
has_fs() { [ "$(dd if="$1" bs=1 skip=1080 count=2 status=none | od -An -tx1 | tr -d ' \n')" = "53ef" ]; }
Expand Down Expand Up @@ -118,6 +125,12 @@ if [ -n "$RESTORE" ]; then
kdir=/run/kryptik-recover-esp/EFI/kryptik ;;
esac
bytes="$(sed -n 's/^ "total_bytes": \([0-9]*\).*/\1/p' "$rj")"; sha="$(sed -n 's/^ "sha256": "\([0-9a-f]*\)".*/\1/p' "$rj")"; ver="$(sed -n 's/^ "version": "\([^"]*\)".*/\1/p' "$rj")"
# The medium's record, not signed material: each field's form is checked, as the installer does.
case "$bytes" in ''|*[!0-9]*) die "root.json: total_bytes is not a number" ;; esac
[ "${#bytes}" -le 15 ] || die "root.json: total_bytes is too large"
case "$sha" in *[!0-9a-f]*) die "root.json: sha256 is not a hash" ;; esac
[ "${#sha}" -eq 64 ] || die "root.json: sha256 is not a hash"
case "$ver" in ''|*[!A-Za-z0-9._-]*) die "root.json: version is not a version" ;; esac
[ "$(blockdev --getsize64 "$sd")" -ge "$bytes" ] || die "slot $RESTORE is smaller than the medium's root image"
# As kryptik-update does, the ESP stops naming the slot before it is
# rewritten, and is unmounted meanwhile: a restore cut short leaves nothing
Expand Down
Loading