diff --git a/build/stages/04-base-system.sh b/build/stages/04-base-system.sh index 1cbc6134..aa4815fd 100755 --- a/build/stages/04-base-system.sh +++ b/build/stages/04-base-system.sh @@ -1,15 +1,12 @@ #!/usr/bin/env bash -# Stage 04: the hardened base system, built in the chroot with the full flag -# set from build/config/hardening.env. The recipes are one file per step under -# build/recipes; this file holds the flags, the helpers, the order and the runner. +# Stage 04: the hardened base system, built in the chroot; one recipe per step in build/recipes. # usage: make system (or, in the chroot, 04-base-system.sh [--redo ]) # 04-base-system.sh --list print the build order and stop source "$(dirname "${BASH_SOURCE[0]}")/../lib/common.sh" load_config -# --- hardening -------------------------------------------------------------- -# The first stage with hardening flags: these packages ship (docs/hardening.md). +# --- hardening: the first stage with flags, since these packages ship ------- load_hardening validate_hardening_exceptions @@ -47,8 +44,7 @@ mkdir -p "$STAMPS" "$LOGS" "$BUILDDIR" # a step argument that stands for files the step reads by path. tree_digest() { local f - # if, not &&: a last path that is not there must not fail the loop, and - # with it the stage's PACKAGES assignment. + # if, not &&: a missing last path must not fail the loop and the PACKAGES assignment. for f in "$@"; do if [[ -d "$f" ]]; then find "$f" -type f -print0 elif [[ -f "$f" ]]; then printf '%s\0' "$f" @@ -173,8 +169,7 @@ PACKAGES=( # Before python, whose install (ensurepip) unzips a bundled wheel. "zlib" "s_zlib" "python" "s_python" - # No XS modules: texinfo links them without the hardening, and texi2any - # runs as plain Perl without them. + # No XS modules: texinfo links them unhardened, and texi2any runs as plain Perl. "texinfo" "native_build texinfo-${V_TEXINFO}.tar.xz texinfo-${V_TEXINFO} --disable-perl-xs" # --disable-makeinstall-chown: wall's setgid tty is under that hook, not the setuid one. "util-linux" "native_build util-linux-${V_UTIL_LINUX}.tar.xz util-linux-${V_UTIL_LINUX} --libdir=/usr/lib --runstatedir=/run --disable-chfn-chsh --disable-login --disable-nologin --disable-su --disable-setpriv --disable-runuser --disable-pylibmount --disable-liblastlog2 --disable-makeinstall-setuid --disable-makeinstall-chown --disable-static --without-python" @@ -186,8 +181,7 @@ PACKAGES=( "readline" "s_readline" "m4" "native_build m4-${V_M4}.tar.xz m4-${V_M4}" "flex" "native_build flex-${V_FLEX}.tar.gz flex-${V_FLEX} --disable-static" - # Before everything that asks pkg-config for its dependencies (e2fsprogs, - # iproute2, kmod, eudev). + # Before everything that asks pkg-config for dependencies (e2fsprogs, iproute2, kmod, eudev). "pkgconf" "s_pkgconf" "binutils" "s_binutils_native" "gmp" "native_build gmp-${V_GMP}.tar.xz gmp-${V_GMP} --enable-cxx --disable-static" @@ -199,8 +193,7 @@ PACKAGES=( "acl" "native_build acl-${V_ACL}.tar.xz acl-${V_ACL} --disable-static" "libcap" "s_libcap" "shadow" "s_shadow" - # --enable-pc-files needs --with-pkg-config-libdir, or no .pc files are - # installed and pkg-config finds no ncursesw. + # --enable-pc-files needs --with-pkg-config-libdir, or pkg-config finds no ncursesw. "ncurses" "native_build ncurses-${V_NCURSES}.tar.gz ncurses-${V_NCURSES} --mandir=/usr/share/man --with-shared --without-debug --without-normal --with-cxx-shared --enable-pc-files --with-pkg-config-libdir=/usr/lib/pkgconfig" "sed" "native_build sed-${V_SED}.tar.xz sed-${V_SED}" "psmisc" "native_build psmisc-${V_PSMISC}.tar.xz psmisc-${V_PSMISC}" @@ -211,8 +204,7 @@ PACKAGES=( "inetutils" "s_inetutils" "less" "native_build less-${V_LESS}.tar.gz less-${V_LESS} --sysconfdir=/etc" "openssl" "s_openssl" - # --with-gcc-arch=x86-64, not LFS's "native": inert while CFLAGS are set, - # but the image must never be tuned to the build machine's CPU. + # x86-64, not LFS's "native": inert while CFLAGS are set, but never tune to the build CPU. "libffi" "native_build libffi-${V_LIBFFI}.tar.gz libffi-${V_LIBFFI} --disable-static --with-gcc-arch=x86-64" "python-final" "s_python_final" "coreutils" "s_coreutils" @@ -226,14 +218,12 @@ PACKAGES=( "patch" "native_build patch-${V_PATCH}.tar.xz patch-${V_PATCH}" "tar" "s_tar" "groff" "s_groff" - # For the kernel build, which generates timeconst.h with `bc -q`. After flex - # and bison, which bc needs. + # The kernel build runs `bc -q` for timeconst.h; after flex and bison, which bc needs. "bc" "s_bc" # --disable-manpages: kmod's man pages need scdoc, which is not pinned. "kmod" "native_build kmod-${V_KMOD}.tar.xz kmod-${V_KMOD} --sysconfdir=/etc --with-openssl --with-xz --with-zstd --with-zlib --disable-manpages" "libpipeline" "native_build libpipeline-${V_LIBPIPELINE}.tar.gz libpipeline-${V_LIBPIPELINE}" - # gdbm before man-db, whose configure otherwise picks another database - # interface silently. + # Before man-db, whose configure otherwise silently picks another database interface. "gdbm" "s_gdbm" "man-db" "s_man_db" "procps-ng" "native_build procps-ng-${V_PROCPS}.tar.xz procps-ng-${V_PROCPS} --docdir=/usr/share/doc/procps-ng-${V_PROCPS} --disable-static --disable-kill" @@ -294,8 +284,7 @@ PACKAGES=( "fonts" "s_fonts" "lynx" "s_lynx" "nano" "native_build nano-${V_NANO}.tar.xz nano-${V_NANO} --sysconfdir=/etc --enable-utf8" - # The desktop's own pieces; the proxy's path and hash are arguments, as for - # kryptikd. + # The desktop's own pieces; the proxy's path and hash are arguments, as for kryptikd. "desktop" "s_desktop ${KRYPTIK_WLPROXY_BIN:-none} $([[ -f "${KRYPTIK_WLPROXY_BIN:-}" ]] && sha256_of "${KRYPTIK_WLPROXY_BIN}" || echo absent) $(sha256_of "${KRYPTIK_ROOT}/tools/desktop/kryptik-launch.c" 2>/dev/null || echo none) $(sha256_of "${KRYPTIK_ROOT}/tools/desktop/kryptik-session" 2>/dev/null || echo none) $(sha256_of "${KRYPTIK_ROOT}/tools/desktop/kryptik-chrome" 2>/dev/null || echo none) $(sha256_of "${KRYPTIK_ROOT}/tools/desktop/wlprobe.c" 2>/dev/null || echo none)" # From here the steps configure the system rather than build packages. @@ -352,8 +341,7 @@ if [[ "$MODE" == "list" ]]; then exit 0 fi -# The commit that built this image, for /etc/os-release, passed in from outside -# (the chroot has no git): no commit is better than a wrong one. +# For /etc/os-release, passed in as the chroot has no git; no commit is better than a wrong one. KRYPTIK_BUILD_COMMIT="${KRYPTIK_BUILD_COMMIT:-unknown}" export KRYPTIK_BUILD_COMMIT @@ -366,15 +354,14 @@ echo # Outside the chroot the packages would link against host libraries. require_inside_chroot "stage 04" "system" -# Built by stage 02's toolchain: rebuilding it invalidates every stamp here. -# gcc2 is its last build step; verify after it is a check. +# Stage 02's last build step (verify is a check): a toolchain rebuild invalidates every stamp here. stage_depends_on "tt-" gcc2 for ((i = 0; i < ${#PACKAGES[@]}; i += 2)); do name="${PACKAGES[i]}" recipe="${PACKAGES[i+1]}" [[ -n "$recipe" ]] || die "${name}: a row with no recipe" - # shellcheck disable=SC2086 # recipe is a deliberately word-split command + # shellcheck disable=SC2086 # recipe is a word-split command step "$name" $recipe done diff --git a/tools/acceptance.sh b/tools/acceptance.sh index 28da1937..2d4cf196 100755 --- a/tools/acceptance.sh +++ b/tools/acceptance.sh @@ -29,8 +29,7 @@ export NO_COLOR=1 # shellcheck source=/dev/null source "${ROOT}/build/lib/common.sh" trap - ERR; set +e -# sudo resets PATH and HOME, and rustup installs per user: take cargo from -# $HOME or else the sudo user's home, and point RUSTUP_HOME there too. +# sudo resets PATH and HOME: find cargo under $HOME or the sudo user's home, with its RUSTUP_HOME. for h in "${HOME:-/root}" "$(getent passwd "${SUDO_USER:-}" 2>/dev/null | cut -d: -f6)"; do [[ -n "$h" && -d "$h/.cargo/bin" ]] || continue PATH="$h/.cargo/bin:${PATH}" @@ -62,8 +61,7 @@ OUT="${OUT:-${KRYPTIK_WORK}/acceptance/${START_TS}}" mkdir -p "$OUT" || die "cannot create ${OUT}" MARK="${OUT}/.start"; : > "$MARK" -# The parts' results, and everything beside them (logs, boot records, -# REVISION.txt) copied here, where the report and the export look. +# Copy the parts' logs, boot records and REVISION.txt here, where the report and export look. PARTS=() if [[ "${#MERGE[@]}" -gt 0 ]]; then mapfile -t PARTS < <(find "${MERGE[@]}" -name results.tsv | sort) @@ -112,10 +110,6 @@ VER_A=""; [[ -n "$PAYLOAD_A" ]] && VER_A="$(version_of_payload "$PAYLOAD_A")" MEDIA_USB_A=""; [[ -n "$VER_A" && -f "${IMGDIR}/kryptik-${VER_A}-usb.img" ]] && MEDIA_USB_A="${IMGDIR}/kryptik-${VER_A}-usb.img" # ------------------------------------------------------------- certificate -- -# The Secure Boot certificate the medium under test carries, read out of its -# ESP (/kryptik/kryptik-sb.crt): the one the boot tests enrol and the export -# publishes, whichever key signed the kernels, a development build's own or -# the key medium's. medium_cert() { # medium_cert USB-IMAGE OUT; 0 when a certificate was read local start start="$(sfdisk -d "$1" 2>/dev/null | awk -F'[ ,]+' '$1 ~ /1$/ { for (i = 1; i <= NF; i++) if ($i == "start=") print $(i + 1); exit }')" @@ -124,15 +118,14 @@ medium_cert() { # medium_cert USB-IMAGE OUT; 0 when a certificate was read && openssl x509 -in "$2" -noout > /dev/null 2>&1; then return 0; fi rm -f "$2"; return 1 } +# The boot tests enrol and the export publishes the medium's own certificate, whoever signed it. MEDIUM_CERT=""; CERT_NAME="" if [[ -f "$MEDIA_USB" ]] && medium_cert "$MEDIA_USB" "${OUT}/kryptik-sb.crt"; then MEDIUM_CERT="${OUT}/kryptik-sb.crt" CERT_NAME="$(openssl x509 -in "$MEDIUM_CERT" -noout -subject -nameopt multiline | sed -n 's/^ *commonName *= *//p')" fi -# A production pair, built with a throwaway key medium and kept apart, since -# its versions would sort above A and B here: the two highest payloads, the -# lower one's USB medium, and the certificate its media carry. +# The production pair is kept apart, as its versions would sort above A and B here. PRODDIR="${KRYPTIK_WORK}/images-production" PROD_A=""; PROD_B=""; PROD_USB_A=""; PROD_DESC="none" versions=() @@ -160,10 +153,7 @@ REV="$(g rev-parse HEAD 2>/dev/null || echo unknown)" REV_DESC="$(g describe --always --dirty --long 2>/dev/null || echo unknown)" DIRTY="$(g status --porcelain 2>/dev/null)" -# A part of a split run writes down what it tested and what it ran on. The -# merge takes only parts that tested this revision on these media and ran on -# one firmware and one QEMU, and its report names theirs: the merging machine -# boots nothing, and its packages may be newer than the parts' were. +# A merge takes only parts that tested this revision and media, all on one firmware and QEMU. tested() { printf 'revision %s (%s)\nusb %s\niso %s\n' "$REV" "$REV_DESC" "$H_USB" "$H_ISO"; } ran_on() { printf 'firmware-sha256 %s\nfirmware-package %s\nqemu %s\nkvm %s\n' "$H_FW" "$FW_PKG" "$QEMU_VER" "$KVM"; } parts_disagree() { # the first part that tested or ran on something else, and what @@ -187,6 +177,7 @@ if [[ -n "$ONLY" ]]; then elif [[ "${#PARTS[@]}" -gt 0 ]]; then disagree="$(parts_disagree)" [[ -z "$disagree" ]] || die "not one run: ${disagree}; this merge tests $(tested | tr '\n' ';')" + # The merging machine boots nothing: report the firmware and QEMU the parts ran on. id="$(dirname "${PARTS[0]}")/identity" H_FW="$(sed -n 's/^firmware-sha256 //p' "$id")"; FW_PKG="$(sed -n 's/^firmware-package //p' "$id")" QEMU_VER="$(sed -n 's/^qemu //p' "$id")"; KVM="$(sed -n 's/^kvm //p' "$id")" @@ -213,9 +204,8 @@ checks_in() { printf '%s' "-" } -# item SUITE NAME M|O host|vm|post MINPASS FN [PREREQ-FN] -# MINPASS: passed checks the driver must report, so a launcher that starts -# nothing cannot pass every denial. PREREQ-FN prints why the item cannot run. +# item SUITE NAME M|O host|vm|post MINPASS FN [PREREQ-FN]; PREREQ-FN prints why it cannot run +# MINPASS: the fewest passed checks to accept, so a driver that starts nothing cannot pass. item() { local suite="$1" name="$2" mand="$3" kind="$4" minp="$5" fn="$6" pre="${7:-}" local log="${OUT}/${suite}-${name}.log" rc res checks="-" note="" reason="" t0 @@ -341,8 +331,7 @@ it_sources_lock() { ( cd "$KRYPTIK_SOURCES" && sha256sum --check --quiet --strict "${ROOT}/sources.lock" ) || return 1 echo " ok: $(grep -c . "${ROOT}/sources.lock") entries verified" } -# Each medium against its sidecar, by the hash the run took of it at the start -# and names in its identity. +# Check each medium's sidecar against the hash the run took at the start. it_media_hashes() { local ok=0 f h for f in "$MEDIA_USB" "$MEDIA_ISO"; do @@ -383,8 +372,7 @@ it_keyboard() { "${IMG}/keyboard-test.sh" --usb "$MEDIA_USB"; } it_integrity() { "${IMG}/integrity-test.sh" --usb "$MEDIA_USB"; } it_zones() { "${IMG}/zones-test.sh" --usb "$MEDIA_USB"; } it_gui() { "${IMG}/gui-test.sh" --usb "$MEDIA_USB"; } -# Each update suite is handed the other flow's newest payload too, signed by -# keys the release it installed does not trust, which it must refuse. +# Each update suite must refuse the other flow's newest payload, signed by keys it does not trust. it_update() { local foreign=(); [[ -n "$PROD_B" ]] && foreign=(--foreign "$PROD_B") "${IMG}/update-test.sh" --usb-a "$MEDIA_USB_A" --payload-a "$PAYLOAD_A" --payload-b "$PAYLOAD_B" --vars clean "${foreign[@]}" @@ -529,13 +517,11 @@ it_export() { # B's own root.json: images/root.json is whichever release was built last. if [[ -n "$PAYLOAD_B" && -f "${PAYLOAD_B}/root.json" ]]; then cp "${PAYLOAD_B}/root.json" "${d}/" elif [[ -f "${IMGDIR}/root.json" ]]; then cp "${IMGDIR}/root.json" "${d}/"; fi - # The certificate the media carry, as they carry it, and in DER form for - # a firmware's enrolment menu. + # The media's certificate as they carry it, and in DER for a firmware's enrolment menu. if [[ -n "$MEDIUM_CERT" ]] && cp "$MEDIUM_CERT" "${d}/kryptik-sb.crt" \ && openssl x509 -in "$MEDIUM_CERT" -outform DER -out "${d}/kryptik-sb.der" 2>/dev/null; then : else echo " no certificate read out of ${MEDIA_USB:-(no medium)} to publish"; ok=1; fi - # The media's checksums as stage 06 signed them, and the anchor the tested - # image carries, read out of B's root image: what a download is checked by. + # A download is checked by the signed media checksums and the anchor in B's root image. if [[ -n "$VER" && -f "${IMGDIR}/${sums}" && -f "${IMGDIR}/${sums}.sig" ]]; then cp "${IMGDIR}/${sums}" "${IMGDIR}/${sums}.sig" "${d}/" else @@ -553,8 +539,7 @@ it_export() { cp "${PAYLOAD_B}/manifest" "${d}/manifest-${VER_B}" [[ -f "${PAYLOAD_B}/manifest.sig" ]] && cp "${PAYLOAD_B}/manifest.sig" "${d}/manifest-${VER_B}.sig" fi - # The update payload as the channel serves it, file for file: the release - # page gets these from the export (tools/release-publish.sh). + # The payload as the channel serves it; tools/release-publish.sh puts it on the release page. if [[ -n "$PAYLOAD_B" && -d "$PAYLOAD_B" ]]; then mkdir -p "${d}/payload" for f in "$PAYLOAD_B"/*; do @@ -583,8 +568,7 @@ it_export() { fi return "$ok" } -# The release's notes (tools/release-notes.sh), from every row before this -# one. What changed runs from the latest release tag before this revision. +# Release notes from the rows so far, with what changed since the last release tag. it_notes() { local prev prev="$(g describe --tags --abbrev=0 --match 'v[0-9]*' HEAD^ 2>/dev/null || true)" @@ -592,8 +576,7 @@ it_notes() { || { rm -f "${EXPORT}/RELEASE-NOTES.md"; return 1; } echo "wrote ${EXPORT}/RELEASE-NOTES.md${prev:+ (changes since ${prev})}" } -# Hash every export file but the media (it_export's lines); run last, once the -# report, results and RELEASE.txt are final. +# Hash every export file but the media (it_export listed those); run once all of it is final. seal_export() { # seal_export DIR ( cd "$1" && find . -type f ! -name SHA256SUMS ! -name '*.img' ! -name '*.iso' -print0 | sort -z | xargs -0 sha256sum ) >> "$1/SHA256SUMS" } @@ -631,8 +614,7 @@ echo echo "================================================================" sed -n '/^| suite/,/^$/p' "${OUT}/REPORT.md" echo "Verdict: ${V} (report: ${OUT}/REPORT.md)" -# The 12 GB VM disks only help debug a failure, and on WSL they grow the host's -# virtual disk for good: remove them once the whole run has passed. +# The 12 GB VM disks only help debug a failure, and grow a WSL host's virtual disk for good. if [[ "$V" == PASS ]]; then rm -f "${KRYPTIK_WORK}"/vm/*.img "${KRYPTIK_WORK}"/vm/*.fd "${KRYPTIK_WORK}"/vm/*.pristine 2>/dev/null rm -rf "${KRYPTIK_WORK}"/vm/bad 2>/dev/null diff --git a/tools/install/kryptik-install.sh b/tools/install/kryptik-install.sh index d0546051..d6969d34 100755 --- a/tools/install/kryptik-install.sh +++ b/tools/install/kryptik-install.sh @@ -71,8 +71,7 @@ part_dev() { esac } -# The boot services' answers to which disk a device is on and which partitions -# are this system's own. +# kryptik_root_disk and kryptik_part, as the boot services resolve them. . /usr/libexec/kryptik/devices.sh . /usr/libexec/kryptik/keyboard.sh # What the medium's root.json may be trusted for, shared with kryptik-recover. @@ -118,9 +117,7 @@ for h in "/sys/class/block/$tname/holders/"* "/sys/class/block/$tname/$tname"*/h done [ -z "$held" ] || die "${TARGET} is in use: held open by${held}. Close them first, or pick another disk." -# A disk that carries Kryptik (an old installation, a medium, a test-control -# disk) may hold the only copy of someone's state: it is replaced only when -# asked for by name. +# A disk that carries Kryptik may hold the only copy of someone's state: replaced only on request. labels="" for p in "/sys/class/block/$tname/$tname"*; do [ -e "$p/partition" ] || continue @@ -173,8 +170,7 @@ case "$media" in mount -t vfat -o ro,loop "$ESP_SRC" "$MNT_BASE/esp" || die "could not mount the medium's ESP image" ROOT_JSON="$MNT_BASE/media/root.json" ROOT_SRC=/dev/sr0 - # The signed command line's linear table is "0 N linear /dev/sr0 START": - # the root image starts at sector START of the medium. + # The signed linear table "0 N linear /dev/sr0 START" puts the root image at sector START. ROOT_OFF="$(sed -n 's/.*linear \/dev\/sr0 \([0-9]*\).*/\1/p' /proc/cmdline | head -1)" [ -n "$ROOT_OFF" ] || die "could not read the root image offset from the signed command line" ROOT_OFF=$(( ROOT_OFF * 512 )) diff --git a/tools/tests/services.sh b/tools/tests/services.sh index 391fb328..c075502d 100755 --- a/tools/tests/services.sh +++ b/tools/tests/services.sh @@ -1,6 +1,5 @@ #!/usr/bin/env bash -# Structural checks on the s6-rc service tree, offline: mistakes s6-rc-compile -# would find only at the end of stage 04, inside the chroot. +# The s6-rc service tree, offline: mistakes s6-rc-compile would find only at the end of stage 04. set -uo pipefail @@ -147,8 +146,7 @@ check "no dependency cycle" "$([[ -z "$cycle" ]] && echo ok)" echo echo "-- every longrun bounds its own stop" -# s6-svc -d waits forever unless timeout-kill bounds it, and some processes -# ignore SIGTERM (getty-tty1's interactive bash). +# s6-svc -d waits forever without timeout-kill, and some ignore SIGTERM (getty-tty1's bash). for d in "${SRC}"/*/; do svc="$(basename "$d")" [[ -f "${d}type" ]] || continue @@ -169,8 +167,7 @@ done echo echo "-- every directory in the source tree is a service definition" -# s6-rc-compile reads every directory here as a service; one without a type -# file stops the whole compile. +# s6-rc-compile reads every directory as a service; one without a type file stops the compile. for d in "${SRC}"/*/; do svc="$(basename "$d")" if [[ -f "${d}type" ]]; then