From b4f2eac5f822afe53b5d71b6c35a2eff8c1d8fd5 Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:00:07 +0530 Subject: [PATCH 1/5] Add python3-venv to the dev docker --- Dockerfile.dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 8eb8f45..83cf1e3 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -21,7 +21,7 @@ RUN DEBIAN_FRONTEND=noninteractive apt-get -qq update && \ dnsmasq \ swtpm swtpm-tools \ tpm2-tools xxd \ - gh jq + gh jq python3-venv RUN groupadd -g 1000 vscode-dc && useradd -u 1000 -g 1000 -d /src -s /bin/bash vscode-dc From 28660089f1b0ddd8d9a71762fdad5596d8e7c4aa Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:00:22 +0530 Subject: [PATCH 2/5] bubblewrap no-longer exists, this broke the docker runtime build --- Dockerfile.runtime | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile.runtime b/Dockerfile.runtime index f81ca85..16a923c 100644 --- a/Dockerfile.runtime +++ b/Dockerfile.runtime @@ -24,7 +24,6 @@ FROM scratch # Copy the binaries from the builder stage COPY --from=builder /target/busybox /bin/busybox -COPY --from=builder /target/bubblewrap /bin/bwrap COPY --from=builder /target/stage1 /bin/stage1 # Set the entrypoint to busybox shell From e170bc8407bccc9f93d836b4394523e502780eac Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:00:42 +0530 Subject: [PATCH 3/5] Kernel hardening (via sysctl & kernel cmdline) This generally matches or exceeds (in strictness) the Fedora Core settings Most stage2 users shouldn't have to worry, but some things are permanently disabled: * echo 1 > /proc/sys/kernel/modules_disabled # No more module loading, even signed * echo 1 > /proc/sys/kernel/kexec_load_disabled # No kexec, defense-in-depth with lockdown * echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled # Permanently disable unprivileged BPF (one-way) Everything else can be managed by stage2 as root if it wishes --- tools/build-uki/build.sh | 26 ++++++++++-- tools/build-uki/init | 91 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/tools/build-uki/build.sh b/tools/build-uki/build.sh index 48fb73f..fc4c15b 100755 --- a/tools/build-uki/build.sh +++ b/tools/build-uki/build.sh @@ -200,19 +200,37 @@ echo "Building UKI..." UKI_PATH="${OUTPUT_DIR}/linux.efi" # Create cmdline file with architecture-specific console settings +# The kernel command line is embedded in the signed UKI, so the VM operator cannot change it. +# +# Security flags (threat model: VM operator is the adversary): +# hibernate=no - Disable hibernation; prevents memory being written to operator-controlled EBS volume +# lockdown=confidentiality - Kernel lockdown LSM; blocks /dev/mem, /proc/kcore, unsigned modules, unsigned kexec +# debugfs=off - Disable debugfs entirely; lockdown restricts some access but this removes the surface +# oops=panic - Halt on kernel oops; don't continue in a potentially exploitable state +# iommu.strict=1 - Strict IOMMU TLB invalidation on DMA unmap; prevents DMA-based attacks from virtual devices +# slab_nomerge - Prevent slab cache merging; makes slab-based kernel exploits harder +# randomize_kstack_offset=on - Randomize kernel stack offset per syscall; makes stack-based exploits less reliable +# page_alloc.shuffle=1 - Randomize page allocator freelists; makes heap layout less predictable +# init_on_alloc=1 - Zero-fill memory on allocation; prevents info leaks from recycled memory +# init_on_free=1 - Zero-fill memory on free; makes use-after-free exploitation harder +# crashkernel=0 - Reserve no memory for kdump; prevents crash dumps even if NMI is sent via cloud API +# vsyscall=none - (x86_64 only) Remove legacy vsyscall page; eliminates a fixed-address ROP gadget +# +# NOTE: SysRq is disabled via /proc/sys/kernel/sysrq in the init script, not here. +# "sysrq=" is not a valid kernel cmdline parameter (the kernel ignores it). CMDLINE_PATH="${OUTPUT_DIR}/cmdline.txt" +CMDLINE_COMMON="earlycon hibernate=no lockdown=confidentiality debugfs=off oops=panic crashkernel=0 iommu.strict=1 slab_nomerge randomize_kstack_offset=on page_alloc.shuffle=1 init_on_alloc=1 init_on_free=1 ro" if [ "${ARCH}" = "x86_64" ]; then - # earlycon: auto-detect via ACPI SPCR table for early boot output # console=ttyS0: EC2 PCI UART (only serial device, gets ttyS0) # console=ttyS1: QEMU q35 ISA serial (default COM1 is ttyS0 with no backend, # explicit isa-serial device becomes ttyS1) - echo "earlycon console=ttyS0,115200n8 console=ttyS1,115200n8 ro" > "${CMDLINE_PATH}" + CMDLINE_SERIAL="console=ttyS0,115200n8 console=ttyS1,115200n8 vsyscall=none" else - # earlycon: auto-detect via SPCR (arm64 also auto-registers SPCR as regular console) # console=ttyAMA0: PL011 UART on QEMU virt # console=ttyS0: 16550 PCI UART on EC2 Graviton and QEMU (PCI serial) - echo "earlycon console=ttyAMA0,115200n8 console=ttyS0,115200n8 ro" > "${CMDLINE_PATH}" + CMDLINE_SERIAL="console=ttyAMA0,115200n8 console=ttyS0,115200n8" fi +echo "${CMDLINE_SERIAL} ${CMDLINE_COMMON}" > "${CMDLINE_PATH}" UNAME_PATH="${OUTPUT_DIR}/uname.txt" echo "${KERNEL_VERSION}" > "${UNAME_PATH}" diff --git a/tools/build-uki/init b/tools/build-uki/init index 759627b..c5d8a6b 100644 --- a/tools/build-uki/init +++ b/tools/build-uki/init @@ -1,7 +1,10 @@ #!/bin/busybox sh set -xe BB=/bin/busybox + +##################################################################### # Mount essential filesystems +# $BB mount -t proc none /proc $BB mount -t sysfs none /sys $BB mount -t devtmpfs none /dev @@ -11,7 +14,9 @@ $BB mount -t tmpfs none /tmp # $BB modprobe efivarfs # $BB mount -t efivarfs none /sys/firmware/efi/efivars -# Load hardware RNG modules +##################################################################### +# Load kernel modules +# $BB modprobe rng-core $BB modprobe intel-rng 2>/dev/null || true $BB modprobe amd-rng 2>/dev/null || true @@ -27,6 +32,9 @@ $BB modprobe vhost $BB modprobe vhost_vsock $BB modprobe nitro_enclaves +##################################################################### +# Setup networking +# $BB ip link set lo up # Find first non-loopback interface ETH_IFACE=$($BB ip link show | $BB grep -v loopback | $BB grep -v 'lo:' | $BB head -1 | $BB awk '{print $2}' | $BB sed 's/://') @@ -39,9 +47,88 @@ else exit fi +##################################################################### +# Lock down kernel before handing off to stage1/stage2. +# +# One-way toggles (irreversible, even by root): +echo 1 > /proc/sys/kernel/modules_disabled # No more module loading, even signed +echo 1 > /proc/sys/kernel/kexec_load_disabled # No kexec, defense-in-depth with lockdown +echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled # Permanently disable unprivileged BPF (one-way) +# SysRq: operator can send BREAK+key via serial console API to dump kernel state +# (SysRq+l/m/p) or trigger panic (SysRq+c). lockdown does NOT block SysRq. +# This is a sysctl, not a kernel cmdline param. /proc/sysrq-trigger still works +# for root, but that's within stage2's existing privilege boundary. +echo 0 > /proc/sys/kernel/sysrq # Disable SysRq via keyboard/serial +# Reversible but raises the bar: +echo 2 > /proc/sys/kernel/kptr_restrict # Hide kernel pointers from root +echo 3 > /proc/sys/kernel/perf_event_paranoid # Disallow all perf events +echo 3 > /proc/sys/kernel/yama/ptrace_scope # No ptrace at all +echo 1 > /proc/sys/kernel/dmesg_restrict # Restrict dmesg to CAP_SYSLOG +echo 2 > /proc/sys/net/core/bpf_jit_harden # Harden BPF JIT for all users +# Fail closed: crash immediately on abnormal conditions rather than continuing +# in a potentially exploitable state. crashkernel=0 + lockdown ensures no +# memory dump is produced on panic. +echo 0 > /proc/sys/kernel/nmi_watchdog # Disable NMI watchdog (hypervisor monitors liveness) +echo 1 > /proc/sys/kernel/panic_on_oops # Panic on oops (also set via cmdline oops=panic) +echo 1 > /proc/sys/kernel/softlockup_panic # Panic on soft lockup +[ -f /proc/sys/kernel/hung_task_panic ] && echo 1 > /proc/sys/kernel/hung_task_panic # Panic on hung task +# x86-only NMI sysctls (not present on aarch64): +[ -f /proc/sys/kernel/panic_on_io_nmi ] && echo 1 > /proc/sys/kernel/panic_on_io_nmi +[ -f /proc/sys/kernel/panic_on_unrecovered_nmi ] && echo 1 > /proc/sys/kernel/panic_on_unrecovered_nmi +[ -f /proc/sys/kernel/unknown_nmi_panic ] && echo 1 > /proc/sys/kernel/unknown_nmi_panic + +##################################################################### +# Prudent protection: standard hardening that systemd would normally +# apply via sysctl.d. Since we don't run systemd, stage2 users would +# otherwise get an unhardened baseline. +# +# Core dumps: +echo /dev/null > /proc/sys/kernel/core_pattern # Discard all core dumps +echo 0 > /proc/sys/fs/suid_dumpable # No core dumps from setuid programs +# Filesystem protections: +echo 1 > /proc/sys/fs/protected_hardlinks # Block hardlink-based privesc +echo 1 > /proc/sys/fs/protected_symlinks # Block symlink attacks in sticky dirs +echo 2 > /proc/sys/fs/protected_fifos # Block FIFO attacks in sticky dirs +echo 2 > /proc/sys/fs/protected_regular # Block regular file attacks in sticky dirs +# Address space: +echo 2 > /proc/sys/kernel/randomize_va_space # Full ASLR +# Attack surface reduction (stage2 can re-enable as root if needed): +echo 0 > /proc/sys/vm/unprivileged_userfaultfd # Disable unprivileged userfaultfd (heap race primitive) +echo 2 > /proc/sys/kernel/io_uring_disabled # Disable io_uring for all users (prolific CVE source) +echo 0 > /proc/sys/user/max_user_namespaces # Disable user namespaces (unlocks large attack surface) +echo 0 > /proc/sys/dev/tty/ldisc_autoload # No auto-loading TTY line disciplines (exploit target) +echo 0 > /proc/sys/dev/tty/legacy_tiocsti # Disable TIOCSTI terminal injection ioctl +echo 65536 > /proc/sys/vm/mmap_min_addr # Prevent NULL page mapping (null deref exploits) +# Kernel instrumentation (no business running in production): +echo 0 > /proc/sys/kernel/ftrace_enabled # Disable function tracer +echo 0 > /proc/sys/kernel/stack_tracer_enabled # Disable stack depth tracer +# Network hardening: +echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter # Strict reverse path filtering +echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter +echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects # Ignore ICMP redirects +echo 0 > /proc/sys/net/ipv4/conf/default/accept_redirects +echo 0 > /proc/sys/net/ipv6/conf/all/accept_redirects +echo 0 > /proc/sys/net/ipv6/conf/default/accept_redirects +echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects # Don't send ICMP redirects +echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects +echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route # Reject source-routed packets +echo 0 > /proc/sys/net/ipv4/conf/default/accept_source_route +echo 0 > /proc/sys/net/ipv6/conf/all/accept_source_route +echo 0 > /proc/sys/net/ipv6/conf/default/accept_source_route +echo 1 > /proc/sys/net/ipv4/tcp_syncookies # SYN flood protection +echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra # Ignore Router Advertisements +echo 0 > /proc/sys/net/ipv6/conf/default/accept_ra +echo fq_codel > /proc/sys/net/core/default_qdisc # Fair queuing + bufferbloat control (Fedora default) +# Fedora sysctl.d defaults we inherit: +echo 4194304 > /proc/sys/kernel/pid_max # Larger PID space (Fedora 50-pid-max.conf) + +##################################################################### +# Finally, exec stage1 exec /bin/stage1 -# Shutdown +##################################################################### +# Oops! FUBAR! Shutdown! +# echo "[ERROR] Failed to run stage1" $BB sleep 2 $BB poweroff -f From 5113ff863091ef59d2132683fd170ddcf2ed31fa Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Wed, 11 Feb 2026 14:49:44 +0530 Subject: [PATCH 4/5] Improved init logging, now it consistently uses kprint style (via /dev/kmsg) [ 1.078824] Run /init as init process [ 1.080182] init: Filesystems mounted [ 1.080440] init: Loading kernel modules [ 1.081234] tpm tpm0: auth session is active [ 1.230661] NET: Registered PF_VSOCK protocol family [ 1.246823] init: Setting up networking [ 1.248723] udhcpc: started, v1.37.0 [ 1.251318] udhcpc: broadcasting discover [ 4.262387] udhcpc: broadcasting discover [ 4.278389] udhcpc: broadcasting select for 10.0.2.18, server 10.0.2.1 [ 4.296387] udhcpc: lease of 10.0.2.18 obtained from 10.0.2.1, lease time 43200 [ 4.298528] init: Locking down kernel [ 4.299290] init: Executing stage1 --- tools/build-uki/init | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tools/build-uki/init b/tools/build-uki/init index c5d8a6b..11516c9 100644 --- a/tools/build-uki/init +++ b/tools/build-uki/init @@ -1,7 +1,13 @@ #!/bin/busybox sh -set -xe +set -eu BB=/bin/busybox +# Log to kernel ring buffer so timestamps match printk's clock +kmsg() { echo "$*" > /dev/kmsg; } +log() { kmsg "<6>init: $*"; } +warn() { kmsg "<4>init: $*"; } +die() { kmsg "<3>init: $*"; $BB poweroff -f; } + ##################################################################### # Mount essential filesystems # @@ -9,6 +15,7 @@ $BB mount -t proc none /proc $BB mount -t sysfs none /sys $BB mount -t devtmpfs none /dev $BB mount -t tmpfs none /tmp +log "Filesystems mounted" # Skip efivarfs - not needed after firmware stage # $BB modprobe efivarfs @@ -17,6 +24,7 @@ $BB mount -t tmpfs none /tmp ##################################################################### # Load kernel modules # +log "Loading kernel modules" $BB modprobe rng-core $BB modprobe intel-rng 2>/dev/null || true $BB modprobe amd-rng 2>/dev/null || true @@ -25,7 +33,7 @@ $BB modprobe failover $BB modprobe net_failover $BB modprobe virtio_pci $BB modprobe virtio_net -$BB modprobe gve 2>/dev/null || echo "[WARN] gve driver not available" +$BB modprobe gve 2>/dev/null || warn "gve driver not available" $BB modprobe ena 2>/dev/null || true $BB modprobe vsock $BB modprobe vhost @@ -35,20 +43,24 @@ $BB modprobe nitro_enclaves ##################################################################### # Setup networking # +log "Setting up networking" $BB ip link set lo up # Find first non-loopback interface ETH_IFACE=$($BB ip link show | $BB grep -v loopback | $BB grep -v 'lo:' | $BB head -1 | $BB awk '{print $2}' | $BB sed 's/://') if [ -n "$ETH_IFACE" ]; then $BB ip link set "$ETH_IFACE" up - $BB udhcpc -i "$ETH_IFACE" -n -s /bin/udhcpc.script + $BB udhcpc -i "$ETH_IFACE" -n -s /bin/udhcpc.script 2>&1 | while IFS= read -r line; do kmsg "<6>$line"; done + # Pipeline swallows exit code (no pipefail in ash), so verify we got an IP + $BB ip addr show "$ETH_IFACE" | $BB grep -q 'inet ' || { log "DHCP failed on $ETH_IFACE"; $BB poweroff -f; } else - echo "[ERROR] No network interface found" - $BB poweroff -f - exit + log "No network interface found" + $BB poweroff -f + exit fi ##################################################################### # Lock down kernel before handing off to stage1/stage2. +log "Locking down kernel" # # One-way toggles (irreversible, even by root): echo 1 > /proc/sys/kernel/modules_disabled # No more module loading, even signed @@ -124,6 +136,7 @@ echo 4194304 > /proc/sys/kernel/pid_max # Larger PID space (F ##################################################################### # Finally, exec stage1 +log "Executing stage1" exec /bin/stage1 ##################################################################### From 44e657bb7f7d3acb06d438b99727b6dd7484ccd6 Mon Sep 17 00:00:00 2001 From: user <303926+HarryR@users.noreply.github.com> Date: Wed, 11 Feb 2026 15:09:43 +0530 Subject: [PATCH 5/5] Improve /bin/init logging, now records sha256 hash of stage1, does modprobe first, sysctl second The full output from the init script for example would be: [ 1.083159] Run /init as init process [ 1.084357] init: Filesystems mounted [ 1.084841] init: modprobe: rng-core [ 1.085426] tpm tpm0: auth session is active [ 1.204799] init: modprobe: failover [ 1.209766] init: modprobe: net_failover [ 1.210664] init: modprobe: virtio_pci [ 1.218214] init: modprobe: virtio_net [ 1.225938] init: modprobe: gve [ 1.234520] init: modprobe: ena [ 1.238768] NET: Registered PF_VSOCK protocol family [ 1.239154] init: modprobe: vsock [ 1.245645] init: modprobe: vhost [ 1.252218] init: modprobe: vhost_vsock [ 1.255616] init: modprobe: nitro_enclaves [ 1.256157] init: sysctl: kernel.modules_disabled=1 [ 1.256719] init: sysctl: kernel.kexec_load_disabled=1 [ 1.257305] init: sysctl: kernel.unprivileged_bpf_disabled=1 [ 1.257947] init: sysctl: kernel.sysrq=0 [ 1.258436] init: sysctl: kernel.kptr_restrict=2 [ 1.258981] init: sysctl: kernel.perf_event_paranoid=3 [ 1.259549] init: sysctl: kernel.yama.ptrace_scope=3 [ 1.260097] init: sysctl: kernel.dmesg_restrict=1 [ 1.260698] init: sysctl: net.core.bpf_jit_harden=2 [ 1.261252] init: sysctl: kernel.nmi_watchdog=0 [ 1.261811] init: sysctl: kernel.panic_on_oops=1 [ 1.262361] init: sysctl: kernel.softlockup_panic=1 [ 1.262971] init: sysctl: kernel.hung_task_panic skipped (not found) [ 1.263659] init: sysctl: kernel.panic_on_io_nmi=1 [ 1.264249] init: sysctl: kernel.panic_on_unrecovered_nmi=1 [ 1.264911] init: sysctl: kernel.unknown_nmi_panic=1 [ 1.265553] init: sysctl: kernel.core_pattern=/dev/null [ 1.266222] init: sysctl: fs.suid_dumpable=0 [ 1.266806] init: sysctl: fs.protected_hardlinks=1 [ 1.267410] init: sysctl: fs.protected_symlinks=1 [ 1.268029] init: sysctl: fs.protected_fifos=2 [ 1.268617] init: sysctl: fs.protected_regular=2 [ 1.269197] init: sysctl: kernel.randomize_va_space=2 [ 1.269821] init: sysctl: vm.unprivileged_userfaultfd=0 [ 1.270440] init: sysctl: kernel.io_uring_disabled=2 [ 1.271059] init: sysctl: user.max_user_namespaces=0 [ 1.271650] init: sysctl: dev.tty.ldisc_autoload=0 [ 1.272242] init: sysctl: dev.tty.legacy_tiocsti=0 [ 1.272903] init: sysctl: vm.mmap_min_addr=65536 [ 1.273755] init: sysctl: kernel.ftrace_enabled=0 [ 1.274308] init: sysctl: kernel.stack_tracer_enabled=0 [ 1.274893] init: sysctl: net.ipv4.conf.all.rp_filter=1 [ 1.275560] init: sysctl: net.ipv4.conf.default.rp_filter=1 [ 1.276185] init: sysctl: net.ipv4.conf.all.accept_redirects=0 [ 1.276881] init: sysctl: net.ipv4.conf.default.accept_redirects=0 [ 1.277555] init: sysctl: net.ipv6.conf.all.accept_redirects=0 [ 1.278225] init: sysctl: net.ipv6.conf.default.accept_redirects=0 [ 1.278918] init: sysctl: net.ipv4.conf.all.send_redirects=0 [ 1.279582] init: sysctl: net.ipv4.conf.default.send_redirects=0 [ 1.280204] init: sysctl: net.ipv4.conf.all.accept_source_route=0 [ 1.280870] init: sysctl: net.ipv4.conf.default.accept_source_route=0 [ 1.281535] init: sysctl: net.ipv6.conf.all.accept_source_route=0 [ 1.282185] init: sysctl: net.ipv6.conf.default.accept_source_route=0 [ 1.282891] init: sysctl: net.ipv4.tcp_syncookies=1 [ 1.283497] init: sysctl: net.ipv6.conf.all.accept_ra=0 [ 1.284156] init: sysctl: net.ipv6.conf.default.accept_ra=0 [ 1.284778] init: sysctl: net.core.default_qdisc=fq_codel [ 1.285405] init: sysctl: kernel.pid_max=4194304 [ 1.286789] udhcpc: started, v1.37.0 [ 1.295541] udhcpc: broadcasting discover [ 4.310649] udhcpc: broadcasting discover [ 4.321580] udhcpc: broadcasting select for 10.0.2.18, server 10.0.2.1 [ 4.343564] udhcpc: lease of 10.0.2.18 obtained from 10.0.2.1, lease time 43200 [ 4.357920] init: exec c157c986c3b7e7a0c1980cc3d7244c74e2b7f6e5632f5889c474d5cb8bc93292 /bin/stage1 --- tools/build-uki/init | 193 ++++++++++++++++++++++++------------------- 1 file changed, 108 insertions(+), 85 deletions(-) diff --git a/tools/build-uki/init b/tools/build-uki/init index 11516c9..dd918a4 100644 --- a/tools/build-uki/init +++ b/tools/build-uki/init @@ -8,6 +8,35 @@ log() { kmsg "<6>init: $*"; } warn() { kmsg "<4>init: $*"; } die() { kmsg "<3>init: $*"; $BB poweroff -f; } +# sysctl path value — write or die. Pass -f to skip missing files with a warning. +sysctl() { + _opt=0; [ "${1:-}" = "-f" ] && { _opt=1; shift; } + _path="/proc/sys/$1"; _name=$(echo "$1" | $BB tr '/' '.') + if [ ! -f "$_path" ]; then + [ $_opt -eq 1 ] && { warn "sysctl: $_name skipped (not found)"; return 0; } + die "sysctl: $_name not found" + fi + if ! echo "$2" > "$_path" 2>/dev/null; then + die "sysctl: $_name=$2 failed" + fi + log "sysctl: $_name=$2" +} + +# mod name — load or die. Pass -q to skip silently, -w to warn. +mod() { + _mode=die + case "${1:-}" in -q) _mode=quiet; shift;; -w) _mode=warn; shift;; esac + if $BB modprobe "$1" 2>/dev/null; then + log "modprobe: $1" + elif [ "$_mode" = "quiet" ]; then + return 0 + elif [ "$_mode" = "warn" ]; then + warn "modprobe: $1 not available" + else + die "modprobe: $1 failed" + fi +} + ##################################################################### # Mount essential filesystems # @@ -24,70 +53,50 @@ log "Filesystems mounted" ##################################################################### # Load kernel modules # -log "Loading kernel modules" -$BB modprobe rng-core -$BB modprobe intel-rng 2>/dev/null || true -$BB modprobe amd-rng 2>/dev/null || true +mod rng-core +mod -q intel-rng +mod -q amd-rng -$BB modprobe failover -$BB modprobe net_failover -$BB modprobe virtio_pci -$BB modprobe virtio_net -$BB modprobe gve 2>/dev/null || warn "gve driver not available" -$BB modprobe ena 2>/dev/null || true -$BB modprobe vsock -$BB modprobe vhost -$BB modprobe vhost_vsock -$BB modprobe nitro_enclaves - -##################################################################### -# Setup networking -# -log "Setting up networking" -$BB ip link set lo up -# Find first non-loopback interface -ETH_IFACE=$($BB ip link show | $BB grep -v loopback | $BB grep -v 'lo:' | $BB head -1 | $BB awk '{print $2}' | $BB sed 's/://') -if [ -n "$ETH_IFACE" ]; then - $BB ip link set "$ETH_IFACE" up - $BB udhcpc -i "$ETH_IFACE" -n -s /bin/udhcpc.script 2>&1 | while IFS= read -r line; do kmsg "<6>$line"; done - # Pipeline swallows exit code (no pipefail in ash), so verify we got an IP - $BB ip addr show "$ETH_IFACE" | $BB grep -q 'inet ' || { log "DHCP failed on $ETH_IFACE"; $BB poweroff -f; } -else - log "No network interface found" - $BB poweroff -f - exit -fi +mod failover +mod net_failover +mod virtio_pci +mod virtio_net +mod -w gve +mod -q ena +mod vsock +mod vhost +mod vhost_vsock +mod nitro_enclaves ##################################################################### # Lock down kernel before handing off to stage1/stage2. -log "Locking down kernel" # # One-way toggles (irreversible, even by root): -echo 1 > /proc/sys/kernel/modules_disabled # No more module loading, even signed -echo 1 > /proc/sys/kernel/kexec_load_disabled # No kexec, defense-in-depth with lockdown -echo 1 > /proc/sys/kernel/unprivileged_bpf_disabled # Permanently disable unprivileged BPF (one-way) +sysctl kernel/modules_disabled 1 +sysctl kernel/kexec_load_disabled 1 +sysctl kernel/unprivileged_bpf_disabled 1 # SysRq: operator can send BREAK+key via serial console API to dump kernel state # (SysRq+l/m/p) or trigger panic (SysRq+c). lockdown does NOT block SysRq. # This is a sysctl, not a kernel cmdline param. /proc/sysrq-trigger still works # for root, but that's within stage2's existing privilege boundary. -echo 0 > /proc/sys/kernel/sysrq # Disable SysRq via keyboard/serial +sysctl kernel/sysrq 0 # Reversible but raises the bar: -echo 2 > /proc/sys/kernel/kptr_restrict # Hide kernel pointers from root -echo 3 > /proc/sys/kernel/perf_event_paranoid # Disallow all perf events -echo 3 > /proc/sys/kernel/yama/ptrace_scope # No ptrace at all -echo 1 > /proc/sys/kernel/dmesg_restrict # Restrict dmesg to CAP_SYSLOG -echo 2 > /proc/sys/net/core/bpf_jit_harden # Harden BPF JIT for all users +sysctl kernel/kptr_restrict 2 +sysctl kernel/perf_event_paranoid 3 +sysctl kernel/yama/ptrace_scope 3 +sysctl kernel/dmesg_restrict 1 +sysctl net/core/bpf_jit_harden 2 # Fail closed: crash immediately on abnormal conditions rather than continuing # in a potentially exploitable state. crashkernel=0 + lockdown ensures no # memory dump is produced on panic. -echo 0 > /proc/sys/kernel/nmi_watchdog # Disable NMI watchdog (hypervisor monitors liveness) -echo 1 > /proc/sys/kernel/panic_on_oops # Panic on oops (also set via cmdline oops=panic) -echo 1 > /proc/sys/kernel/softlockup_panic # Panic on soft lockup -[ -f /proc/sys/kernel/hung_task_panic ] && echo 1 > /proc/sys/kernel/hung_task_panic # Panic on hung task +sysctl kernel/nmi_watchdog 0 +sysctl kernel/panic_on_oops 1 +sysctl kernel/softlockup_panic 1 +sysctl -f kernel/hung_task_panic 1 # x86-only NMI sysctls (not present on aarch64): -[ -f /proc/sys/kernel/panic_on_io_nmi ] && echo 1 > /proc/sys/kernel/panic_on_io_nmi -[ -f /proc/sys/kernel/panic_on_unrecovered_nmi ] && echo 1 > /proc/sys/kernel/panic_on_unrecovered_nmi -[ -f /proc/sys/kernel/unknown_nmi_panic ] && echo 1 > /proc/sys/kernel/unknown_nmi_panic +sysctl -f kernel/panic_on_io_nmi 1 +sysctl -f kernel/panic_on_unrecovered_nmi 1 +sysctl -f kernel/unknown_nmi_panic 1 ##################################################################### # Prudent protection: standard hardening that systemd would normally @@ -95,54 +104,68 @@ echo 1 > /proc/sys/kernel/softlockup_panic # Panic on soft lockup # otherwise get an unhardened baseline. # # Core dumps: -echo /dev/null > /proc/sys/kernel/core_pattern # Discard all core dumps -echo 0 > /proc/sys/fs/suid_dumpable # No core dumps from setuid programs +sysctl kernel/core_pattern /dev/null +sysctl fs/suid_dumpable 0 # Filesystem protections: -echo 1 > /proc/sys/fs/protected_hardlinks # Block hardlink-based privesc -echo 1 > /proc/sys/fs/protected_symlinks # Block symlink attacks in sticky dirs -echo 2 > /proc/sys/fs/protected_fifos # Block FIFO attacks in sticky dirs -echo 2 > /proc/sys/fs/protected_regular # Block regular file attacks in sticky dirs +sysctl fs/protected_hardlinks 1 +sysctl fs/protected_symlinks 1 +sysctl fs/protected_fifos 2 +sysctl fs/protected_regular 2 # Address space: -echo 2 > /proc/sys/kernel/randomize_va_space # Full ASLR +sysctl kernel/randomize_va_space 2 # Attack surface reduction (stage2 can re-enable as root if needed): -echo 0 > /proc/sys/vm/unprivileged_userfaultfd # Disable unprivileged userfaultfd (heap race primitive) -echo 2 > /proc/sys/kernel/io_uring_disabled # Disable io_uring for all users (prolific CVE source) -echo 0 > /proc/sys/user/max_user_namespaces # Disable user namespaces (unlocks large attack surface) -echo 0 > /proc/sys/dev/tty/ldisc_autoload # No auto-loading TTY line disciplines (exploit target) -echo 0 > /proc/sys/dev/tty/legacy_tiocsti # Disable TIOCSTI terminal injection ioctl -echo 65536 > /proc/sys/vm/mmap_min_addr # Prevent NULL page mapping (null deref exploits) +sysctl vm/unprivileged_userfaultfd 0 +sysctl kernel/io_uring_disabled 2 +sysctl user/max_user_namespaces 0 +sysctl dev/tty/ldisc_autoload 0 +sysctl dev/tty/legacy_tiocsti 0 +sysctl vm/mmap_min_addr 65536 # Kernel instrumentation (no business running in production): -echo 0 > /proc/sys/kernel/ftrace_enabled # Disable function tracer -echo 0 > /proc/sys/kernel/stack_tracer_enabled # Disable stack depth tracer +sysctl kernel/ftrace_enabled 0 +sysctl kernel/stack_tracer_enabled 0 # Network hardening: -echo 1 > /proc/sys/net/ipv4/conf/all/rp_filter # Strict reverse path filtering -echo 1 > /proc/sys/net/ipv4/conf/default/rp_filter -echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects # Ignore ICMP redirects -echo 0 > /proc/sys/net/ipv4/conf/default/accept_redirects -echo 0 > /proc/sys/net/ipv6/conf/all/accept_redirects -echo 0 > /proc/sys/net/ipv6/conf/default/accept_redirects -echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects # Don't send ICMP redirects -echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects -echo 0 > /proc/sys/net/ipv4/conf/all/accept_source_route # Reject source-routed packets -echo 0 > /proc/sys/net/ipv4/conf/default/accept_source_route -echo 0 > /proc/sys/net/ipv6/conf/all/accept_source_route -echo 0 > /proc/sys/net/ipv6/conf/default/accept_source_route -echo 1 > /proc/sys/net/ipv4/tcp_syncookies # SYN flood protection -echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra # Ignore Router Advertisements -echo 0 > /proc/sys/net/ipv6/conf/default/accept_ra -echo fq_codel > /proc/sys/net/core/default_qdisc # Fair queuing + bufferbloat control (Fedora default) +sysctl net/ipv4/conf/all/rp_filter 1 +sysctl net/ipv4/conf/default/rp_filter 1 +sysctl net/ipv4/conf/all/accept_redirects 0 +sysctl net/ipv4/conf/default/accept_redirects 0 +sysctl net/ipv6/conf/all/accept_redirects 0 +sysctl net/ipv6/conf/default/accept_redirects 0 +sysctl net/ipv4/conf/all/send_redirects 0 +sysctl net/ipv4/conf/default/send_redirects 0 +sysctl net/ipv4/conf/all/accept_source_route 0 +sysctl net/ipv4/conf/default/accept_source_route 0 +sysctl net/ipv6/conf/all/accept_source_route 0 +sysctl net/ipv6/conf/default/accept_source_route 0 +sysctl net/ipv4/tcp_syncookies 1 +sysctl net/ipv6/conf/all/accept_ra 0 +sysctl net/ipv6/conf/default/accept_ra 0 +sysctl net/core/default_qdisc fq_codel # Fedora sysctl.d defaults we inherit: -echo 4194304 > /proc/sys/kernel/pid_max # Larger PID space (Fedora 50-pid-max.conf) +sysctl kernel/pid_max 4194304 + +##################################################################### +# Setup networking +# +$BB ip link set lo up +# Find first non-loopback interface +ETH_IFACE=$($BB ip link show | $BB grep -v loopback | $BB grep -v 'lo:' | $BB head -1 | $BB awk '{print $2}' | $BB sed 's/://') +if [ -n "$ETH_IFACE" ]; then + $BB ip link set "$ETH_IFACE" up + $BB udhcpc -i "$ETH_IFACE" -n -s /bin/udhcpc.script 2>&1 | while IFS= read -r line; do kmsg "<6>$line"; done + # Pipeline swallows exit code (no pipefail in ash), so verify we got an IP + $BB ip addr show "$ETH_IFACE" | $BB grep -q 'inet ' || { die "DHCP failed on $ETH_IFACE"; } +else + die "No network interface found" + exit +fi ##################################################################### # Finally, exec stage1 -log "Executing stage1" +log "exec $($BB sha256sum /bin/stage1)" exec /bin/stage1 ##################################################################### # Oops! FUBAR! Shutdown! # -echo "[ERROR] Failed to run stage1" -$BB sleep 2 -$BB poweroff -f +die "Failed to run stage1" exit