diff --git a/configs/kernelarm64.config b/configs/kernelarm64.config index cc80de9..9624fb8 100644 --- a/configs/kernelarm64.config +++ b/configs/kernelarm64.config @@ -3938,11 +3938,35 @@ CONFIG_PROC_MEM_ALWAYS_FORCE=y # CONFIG_PROC_MEM_FORCE_PTRACE is not set # CONFIG_PROC_MEM_NO_FORCE is not set # CONFIG_MSEAL_SYSTEM_MAPPINGS is not set -# CONFIG_SECURITY is not set -# CONFIG_SECURITYFS is not set +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +# CONFIG_SECURITY_NETWORK is not set +# CONFIG_SECURITY_PATH is not set +CONFIG_LSM_MMAP_MIN_ADDR=65536 +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +# CONFIG_SECURITY_APPARMOR is not set +# CONFIG_SECURITY_LOADPIN is not set +# CONFIG_SECURITY_YAMA is not set +# CONFIG_SECURITY_SAFESETID is not set +CONFIG_SECURITY_LOCKDOWN_LSM=y +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y +# CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set +# CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set +# CONFIG_SECURITY_LANDLOCK is not set +CONFIG_INTEGRITY=y +CONFIG_INTEGRITY_SIGNATURE=y +CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y +CONFIG_INTEGRITY_PLATFORM_KEYRING=y +CONFIG_LOAD_UEFI_KEYS=y +# CONFIG_IMA is not set +# CONFIG_EVM is not set # CONFIG_STATIC_USERMODEHELPER is not set # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_LSM="lockdown,integrity" # # Kernel hardening options @@ -4162,12 +4186,23 @@ CONFIG_CRYPTO_JITTERENTROPY_OSR=1 # end of Accelerated Cryptographic Algorithms for CPU (arm64) # CONFIG_CRYPTO_HW is not set -# CONFIG_ASYMMETRIC_KEY_TYPE is not set +CONFIG_ASYMMETRIC_KEY_TYPE=y +CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y +CONFIG_X509_CERTIFICATE_PARSER=y +CONFIG_PKCS7_MESSAGE_PARSER=y +# CONFIG_PKCS8_PRIVATE_KEY_PARSER is not set +# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set # # Certificates for signature checking # -# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set +CONFIG_SYSTEM_TRUSTED_KEYRING=y +CONFIG_SYSTEM_TRUSTED_KEYS="" +# CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set +CONFIG_SECONDARY_TRUSTED_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" +# CONFIG_SYSTEM_REVOCATION_LIST is not set # end of Certificates for signature checking # CONFIG_CRYPTO_KRB5 is not set diff --git a/configs/kernelx64.config b/configs/kernelx64.config index e3d118a..d456e97 100644 --- a/configs/kernelx64.config +++ b/configs/kernelx64.config @@ -246,7 +246,7 @@ CONFIG_PERF_EVENTS=y # Kexec and crash features # CONFIG_KEXEC_CORE=y -CONFIG_KEXEC=y +# CONFIG_KEXEC is not set # CONFIG_KEXEC_FILE is not set # CONFIG_KEXEC_HANDOVER is not set # CONFIG_CRASH_DUMP is not set @@ -3936,12 +3936,36 @@ CONFIG_PROC_MEM_ALWAYS_FORCE=y # CONFIG_PROC_MEM_FORCE_PTRACE is not set # CONFIG_PROC_MEM_NO_FORCE is not set # CONFIG_MSEAL_SYSTEM_MAPPINGS is not set -# CONFIG_SECURITY is not set -# CONFIG_SECURITYFS is not set +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +# CONFIG_SECURITY_NETWORK is not set +# CONFIG_SECURITY_PATH is not set +CONFIG_LSM_MMAP_MIN_ADDR=65536 +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +# CONFIG_SECURITY_APPARMOR is not set +# CONFIG_SECURITY_LOADPIN is not set +# CONFIG_SECURITY_YAMA is not set +# CONFIG_SECURITY_SAFESETID is not set +CONFIG_SECURITY_LOCKDOWN_LSM=y +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y +# CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set +# CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set +# CONFIG_SECURITY_LANDLOCK is not set +CONFIG_INTEGRITY=y +CONFIG_INTEGRITY_SIGNATURE=y +CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y +CONFIG_INTEGRITY_PLATFORM_KEYRING=y +CONFIG_LOAD_UEFI_KEYS=y +# CONFIG_IMA is not set +# CONFIG_EVM is not set CONFIG_INTEL_TXT=y # CONFIG_STATIC_USERMODEHELPER is not set # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_LSM="lockdown,integrity" # # Kernel hardening options @@ -4168,12 +4192,23 @@ CONFIG_CRYPTO_AES_NI_INTEL=y # end of Accelerated Cryptographic Algorithms for CPU (x86) # CONFIG_CRYPTO_HW is not set -# CONFIG_ASYMMETRIC_KEY_TYPE is not set +CONFIG_ASYMMETRIC_KEY_TYPE=y +CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y +CONFIG_X509_CERTIFICATE_PARSER=y +CONFIG_PKCS7_MESSAGE_PARSER=y +# CONFIG_PKCS8_PRIVATE_KEY_PARSER is not set +# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set # # Certificates for signature checking # -# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set +CONFIG_SYSTEM_TRUSTED_KEYRING=y +CONFIG_SYSTEM_TRUSTED_KEYS="" +# CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set +CONFIG_SECONDARY_TRUSTED_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" +# CONFIG_SYSTEM_REVOCATION_LIST is not set # end of Certificates for signature checking # CONFIG_CRYPTO_KRB5 is not set diff --git a/configs/kernelx86.config b/configs/kernelx86.config index 6767015..ad35a78 100755 --- a/configs/kernelx86.config +++ b/configs/kernelx86.config @@ -240,7 +240,7 @@ CONFIG_PERF_EVENTS=y # Kexec and crash features # CONFIG_KEXEC_CORE=y -CONFIG_KEXEC=y +# CONFIG_KEXEC is not set # CONFIG_CRASH_DUMP is not set # end of Kexec and crash features # end of General setup @@ -3844,11 +3844,35 @@ CONFIG_KEYS=y CONFIG_PROC_MEM_ALWAYS_FORCE=y # CONFIG_PROC_MEM_FORCE_PTRACE is not set # CONFIG_PROC_MEM_NO_FORCE is not set -# CONFIG_SECURITY is not set -# CONFIG_SECURITYFS is not set +CONFIG_SECURITY=y +CONFIG_SECURITYFS=y +# CONFIG_SECURITY_NETWORK is not set +# CONFIG_SECURITY_PATH is not set +CONFIG_LSM_MMAP_MIN_ADDR=65536 +# CONFIG_SECURITY_SELINUX is not set +# CONFIG_SECURITY_SMACK is not set +# CONFIG_SECURITY_TOMOYO is not set +# CONFIG_SECURITY_APPARMOR is not set +# CONFIG_SECURITY_LOADPIN is not set +# CONFIG_SECURITY_YAMA is not set +# CONFIG_SECURITY_SAFESETID is not set +CONFIG_SECURITY_LOCKDOWN_LSM=y +CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y +CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y +# CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY is not set +# CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set +# CONFIG_SECURITY_LANDLOCK is not set +CONFIG_INTEGRITY=y +CONFIG_INTEGRITY_SIGNATURE=y +CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y +CONFIG_INTEGRITY_PLATFORM_KEYRING=y +CONFIG_LOAD_UEFI_KEYS=y +# CONFIG_IMA is not set +# CONFIG_EVM is not set # CONFIG_STATIC_USERMODEHELPER is not set # CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT is not set CONFIG_DEFAULT_SECURITY_DAC=y +CONFIG_LSM="lockdown,integrity" # # Kernel hardening options @@ -4053,12 +4077,23 @@ CONFIG_CRYPTO_AES_NI_INTEL=y # end of Accelerated Cryptographic Algorithms for CPU (x86) # CONFIG_CRYPTO_HW is not set -# CONFIG_ASYMMETRIC_KEY_TYPE is not set +CONFIG_ASYMMETRIC_KEY_TYPE=y +CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y +CONFIG_X509_CERTIFICATE_PARSER=y +CONFIG_PKCS7_MESSAGE_PARSER=y +# CONFIG_PKCS8_PRIVATE_KEY_PARSER is not set +# CONFIG_SIGNED_PE_FILE_VERIFICATION is not set # # Certificates for signature checking # -# CONFIG_SYSTEM_BLACKLIST_KEYRING is not set +CONFIG_SYSTEM_TRUSTED_KEYRING=y +CONFIG_SYSTEM_TRUSTED_KEYS="" +# CONFIG_SYSTEM_EXTRA_CERTIFICATE is not set +CONFIG_SECONDARY_TRUSTED_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_KEYRING=y +CONFIG_SYSTEM_BLACKLIST_HASH_LIST="" +# CONFIG_SYSTEM_REVOCATION_LIST is not set # end of Certificates for signature checking # CONFIG_CRYPTO_KRB5 is not set diff --git a/docs/adr/0009-secure-boot-kernel-hardening.md b/docs/adr/0009-secure-boot-kernel-hardening.md new file mode 100644 index 0000000..f1a1850 --- /dev/null +++ b/docs/adr/0009-secure-boot-kernel-hardening.md @@ -0,0 +1,155 @@ +# 0009 — Secure Boot kernel hardening, and why lockdown is not yet activated + +## Status + +Partially implemented. The kernel configuration is in place; the code that +*activates* lockdown when Secure Boot is on is deliberately not, and the reason +is recorded below so the next person does not have to rediscover it. + +## Context + +A site that mandates UEFI Secure Boot must sign the FOS kernel with its own key +and enrol that key per machine. FOG automates the signing +(`build.sh --sign-key`, plus the installer side in FOGProject/fogproject), but +signing only gets the kernel *loaded*. It says nothing about what the kernel +then permits. + +The reason that matters is not academic. If FOG ever wants its own +Microsoft-signed shim — the only route to Secure Boot working out of the box, +rather than after a per-machine visit — `rhboot/shim-review` asks directly: + +> How does your signed kernel enforce lockdown when your system runs with +> Secure Boot enabled? + +A signed kernel that does not enforce lockdown is a Secure Boot bypass: anyone +can boot it on any machine that trusts the signer and then use `/dev/mem`, +`iopl()` or `kexec_load()` to do whatever they like to a kernel the firmware +just vouched for. That is why reviewers ask, and it is the single largest gap +between FOS today and a kernel anyone would sign for general use. + +Two things about FOS make this easier than it usually is: + +- **There are no modules.** `# CONFIG_MODULES is not set`; the config is 1829 + `=y` and zero `=m`. The out-of-tree Realtek drivers are not external modules + either — `addKernelPackages()` copies them into the source tree and appends + to `Kconfig`/`Makefile`, so they are built in and covered by the kernel's own + signature. This answers shim-review's other recurring question, about + ephemeral per-build module-signing keys, with "we build no modules". +- **`CONFIG_EFI_STUB=y` already.** Under Secure Boot the kernel is started by + the firmware's loader rather than iPXE's, which requires the stub. + +## Decision + +Enable the lockdown LSM and the platform keyring infrastructure in all three +architecture configs, but **do not force lockdown on**: + +- `CONFIG_SECURITY=y`, `CONFIG_SECURITYFS=y` — neither was set, so the lockdown + LSM could not even be selected. +- `CONFIG_SECURITY_LOCKDOWN_LSM=y` and `_EARLY=y`. The early variant matters + because some boot parameters are parsed before LSM init would otherwise run. +- `CONFIG_LSM="lockdown,integrity"`. An LSM that is built but absent from the + ordered list never initialises. Set explicitly rather than left for + `oldconfig` to default, because the upstream default string names LSMs this + kernel does not build. +- `CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE=y` — see below. +- `CONFIG_ASYMMETRIC_KEY_TYPE=y` and the keyrings + (`SYSTEM_TRUSTED_KEYRING`, `SECONDARY_TRUSTED_KEYRING`, + `SYSTEM_BLACKLIST_KEYRING`, `INTEGRITY_PLATFORM_KEYRING`, + `LOAD_UEFI_KEYS`). `LOAD_UEFI_KEYS` is the one that imports the firmware's + `db` and `MokList` into the platform keyring; without it the kernel cannot + see the key the shim just validated against. +- `# CONFIG_KEXEC is not set`. The old `kexec_load()` syscall is + unconditionally blocked by lockdown and FOS never uses it, so removing it + beats leaving it to be refused at runtime. + +### Why not `LOCK_DOWN_KERNEL_FORCE_INTEGRITY` + +It would be one line and it would activate lockdown today. It is the wrong +default anyway, because it locks the kernel down on **every** boot — including +the overwhelming majority of FOG deployments that never turn Secure Boot on. +Lockdown blocks `/dev/mem`, `/dev/port`, `iopl`/`ioperm`, raw PCI BAR access +and direct MSR access. FOS's hardware inventory is the obvious thing at risk: +modern `dmidecode` prefers `/sys/firmware/dmi/tables/`, but anything that falls +back to `/dev/mem` would start failing for users who gained nothing in return. + +Distributions all resolve this the same way: build the LSM in, leave it +inactive, and activate it at boot **only when the firmware reports Secure Boot +is on**. That is the behaviour FOS wants. + +## The part that is not done, and what it actually takes + +Activating lockdown from the Secure Boot state is **not upstream**. It is a +downstream patch every distribution carries, and the investigation below is +recorded because the shape of it is not obvious from the outside: + +- `security_lock_kernel_down()` — the function Fedora's and Ubuntu's patches + call — **does not exist in mainline 6.18**. `include/linux/security.h` + exports only `security_locked_down()`, the query. The distro patches add the + setter to the LSM infrastructure, touching `include/linux/security.h`, + `security/security.c` and `include/linux/lsm_hook_defs.h`. +- `efi_enabled(EFI_SECURE_BOOT)` **also does not exist in mainline 6.18**; + `EFI_SECURE_BOOT` is a downstream flag. Upstream x86 keeps the state in + `boot_params.secure_boot` and, in `setup_arch()` + (`arch/x86/kernel/setup.c`), only *prints* it: + + ```c + if (efi_enabled(EFI_BOOT)) { + switch (boot_params.secure_boot) { + case efi_secureboot_mode_enabled: + pr_info("Secure boot enabled\n"); + ``` + + That `case` is exactly where the distro patches insert the lockdown call. +- A smaller alternative exists: `lockdown_lsm_init()` in + `security/lockdown/lockdown.c` already calls a **file-local** + `lock_kernel_down()` for the FORCE_* options, so a single-file patch there + could activate lockdown without touching LSM infrastructure at all. It still + needs an architecture-neutral way to read the Secure Boot state at LSM init + time, which is the unsolved part. + +This was left unwritten on purpose. `build.sh` applies `patch/kernel/linux.patch` +with `patch -p1` and **exits non-zero if it fails**, so a patch that does not +apply cleanly breaks every build for everyone, and takes the Intel VMD patch +down with it. A multi-file, architecture-specific kernel patch that has never +been compiled or booted does not belong in that file. Whoever picks this up +should build and boot it first. + +### Settle the initrd question before going further + +FOS boots as `bzImage` plus a network-supplied kernel command line (`mode=`, +`type=`) plus an unsigned ext2 `init.xz`. shim-review does not literally +mandate a Unified Kernel Image — lockdown in integrity mode is the accepted +mitigation, and UKIs are mentioned only in passing — but for a payload shaped +like this one a reviewer will push hard, because a signed kernel that accepts +an arbitrary command line and an unverified initrd is most of the way back to +the bypass lockdown is supposed to prevent. + +That question is worth answering **before** anyone writes the lockdown patch. +If FOG cannot move to a UKI, an application for its own shim probably is not +winnable, and the patch buys nothing on its own. + +## Consequences + +- The configs are inert for now: lockdown is compiled in but never activated, + so behaviour is unchanged for every existing user. That is the point — this + lands the reviewable, testable half without a flag day. +- `CONFIG_SECURITY=y` pulls a lot of new Kconfig into the build. The three + configs are hand-edited and `make oldconfig` **silently drops symbols whose + dependencies are not met**, so a config can look right in git and produce a + kernel that is missing lockdown entirely. `tests/checks/secureboot-config.sh` + asserts the symbols are present, and with `-b` re-checks the `.config` + Kconfig actually produced after a build. Run it with `-b` after building. +- **This has not been built or booted.** No kernel was compiled while making + this change. Before merging: build all three architectures, boot with Secure + Boot off and image a machine end to end, and confirm the Realtek NICs and + Intel VMD NVMe still work — those two are the entire reason FOS carries a + custom kernel, and `CONFIG_SECURITY`/keyring churn is exactly the kind of + change that could disturb them. + +## References + +- `rhboot/shim-review` — +- Lockdown LSM — , + `man 7 kernel_lockdown` +- ADR 0003, for the "fail loud rather than continue" principle this follows in + refusing to ship an untested patch. diff --git a/tests/checks/secureboot-config.sh b/tests/checks/secureboot-config.sh new file mode 100755 index 0000000..a6af705 --- /dev/null +++ b/tests/checks/secureboot-config.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# +# Assertion harness for the Secure Boot hardening symbols in configs/kernel*.config. +# +# tests/checks/secureboot-config.sh # check the committed configs +# tests/checks/secureboot-config.sh -b # also check post-oldconfig .config +# # in any kernelsource/ present +# +# Why this exists. build.sh copies configs/kernel.config to .config and +# runs `make oldconfig`, which SILENTLY DROPS any symbol whose dependencies are +# not met. A hand-edited config can therefore look completely correct in git and +# still produce a kernel with lockdown missing, and nothing anywhere says so. +# That failure would only surface as a Secure Boot client behaving differently +# from the one you tested. See docs/adr/0009-secure-boot-kernel-hardening.md +# +# The -b mode is the one that actually proves anything, because it inspects the +# config Kconfig produced rather than the one we wrote. Run it after a build. + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="$HERE/../.." + +checkBuilt=0 +[[ ${1:-} == -b ]] && checkBuilt=1 + +# Symbols that must be present and enabled for a Secure Boot capable kernel. +# LOAD_UEFI_KEYS is what imports the firmware's db/MokList into the platform +# keyring; without it the kernel cannot see the key the shim validated against. +REQUIRED=( + CONFIG_SECURITY=y + CONFIG_SECURITYFS=y + CONFIG_SECURITY_LOCKDOWN_LSM=y + CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y + CONFIG_INTEGRITY=y + CONFIG_INTEGRITY_SIGNATURE=y + CONFIG_INTEGRITY_ASYMMETRIC_KEYS=y + CONFIG_INTEGRITY_PLATFORM_KEYRING=y + CONFIG_LOAD_UEFI_KEYS=y + CONFIG_ASYMMETRIC_KEY_TYPE=y + CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE=y + CONFIG_X509_CERTIFICATE_PARSER=y + CONFIG_PKCS7_MESSAGE_PARSER=y + CONFIG_SYSTEM_TRUSTED_KEYRING=y + CONFIG_SECONDARY_TRUSTED_KEYRING=y + CONFIG_SYSTEM_BLACKLIST_KEYRING=y + CONFIG_EFI_STUB=y +) + +# Symbols that must NOT be enabled. kexec_load() is unconditionally blocked by +# lockdown and FOS never uses it. FORCE_INTEGRITY would lock the kernel down on +# every boot, including the overwhelming majority that never turn Secure Boot +# on -- see the ADR for why that is the wrong default here. +FORBIDDEN=( + CONFIG_KEXEC=y + CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y + CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY=y +) + +fails=0 +checked=0 + +checkConfig() { + local label="$1" file="$2" sym + [[ -f $file ]] || return 0 + checked=$((checked + 1)) + for sym in "${REQUIRED[@]}"; do + if ! grep -qxF "$sym" "$file"; then + echo "FAIL [$label] missing: $sym" + fails=$((fails + 1)) + fi + done + for sym in "${FORBIDDEN[@]}"; do + if grep -qxF "$sym" "$file"; then + echo "FAIL [$label] must not be set: $sym" + fails=$((fails + 1)) + fi + done + # The lockdown LSM only initialises if it is in the ordered LSM list. + if ! grep -q '^CONFIG_LSM=.*lockdown' "$file"; then + echo "FAIL [$label] CONFIG_LSM does not include lockdown" + fails=$((fails + 1)) + fi + echo " checked $label" +} + +for arch in x64 x86 arm64; do + checkConfig "configs/kernel${arch}.config" "$REPO/configs/kernel${arch}.config" +done + +if [[ $checkBuilt -eq 1 ]]; then + built=0 + for arch in x64 x86 arm64; do + f="$REPO/kernelsource${arch}/.config" + if [[ -f $f ]]; then + built=1 + checkConfig "kernelsource${arch}/.config (post-oldconfig)" "$f" + fi + done + if [[ $built -eq 0 ]]; then + echo + echo "NOTE: -b given but no kernelsource/.config found -- nothing" + echo " post-oldconfig was checked. Build first, then re-run." + fi +fi + +echo +if [[ $fails -eq 0 ]]; then + echo "PASS: $checked config(s) carry the Secure Boot hardening symbols" + exit 0 +fi +echo "FAILED: $fails problem(s) across $checked config(s)" +exit 1