diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 index a9529a4..125096f --- a/build.sh +++ b/build.sh @@ -2,5 +2,7 @@ set -euo pipefail # Build Shufflecake +echo "[INFO]: Building qubes-sflc..." cd qubes-sflc ./build.sh +echo "[INFO]: Build complete." diff --git a/dracut/99reliant/module-setup.sh b/dracut/99reliant/module-setup.sh index 3206f2a..5a7b29b 100755 --- a/dracut/99reliant/module-setup.sh +++ b/dracut/99reliant/module-setup.sh @@ -10,10 +10,10 @@ depends() { install() { # Interactive script - inst_simple "$moddir/scripts/reliant-initramfs.sh" "/usr/local/share/scripts/reliant-initramfs.sh" + inst_script "$moddir/scripts/reliant-initramfs.sh" "/usr/local/share/scripts/reliant-initramfs.sh" # Main service - inst_simple "$moddir/reliant.service" "/etc/systemd/system/reliant.service" + inst "$moddir/reliant.service" "/etc/systemd/system/reliant.service" systemctl --root="$initdir" enable reliant.service # Shufflecake @@ -28,10 +28,12 @@ install() { # TODO: Do we need udev rules to --setro newly attached devices? # Reliant - for file in "/usr/local/share/scripts/reliant-*" "/usr/local/sbin/reliant-*" "/usr/local/bin/surgeon-*"; do - inst_binary "$file" - done - inst_simple "/etc/reliant.conf" + inst_script /usr/local/share/scripts/reliant-common.sh + inst_script /usr/local/sbin/reliant-security + inst_script /usr/local/sbin/surgeon-suture + inst_script /usr/local/sbin/reliant-mount + inst_script /usr/local/sbin/reliant-hash + inst /etc/reliant.conf # Other binaries inst_multiple dmesg lsblk blockdev mount dd wc sed sleep md5sum find modprobe cut grep mkdir rm tail diff --git a/dracut/99reliant/patches/modified-create-snapshot.sh b/dracut/99reliant/patches/create-snapshot.sh similarity index 100% rename from dracut/99reliant/patches/modified-create-snapshot.sh rename to dracut/99reliant/patches/create-snapshot.sh diff --git a/dracut/99reliant/scripts/reliant-initramfs.sh b/dracut/99reliant/scripts/reliant-initramfs.sh index 8a15caa..e94afd0 100755 --- a/dracut/99reliant/scripts/reliant-initramfs.sh +++ b/dracut/99reliant/scripts/reliant-initramfs.sh @@ -6,7 +6,7 @@ export RELIANT_INITRAMFS=$RELIANT_TRUE # Upon encountering issues, prompt the user for the next course of action reliant_prompt_user() { while true; do - REPLY=$(plymouth ask-question --prompt="System might be compromised. Proceed? [Y(es)/N(o)/S(kip)]: ") + REPLY=$(plymouth ask-question --prompt="System might be compromised. Proceed? [Y(es)/N(o)/S(kip)]") case $REPLY in [Yy]*) # User confirmed the system is safe to boot @@ -29,16 +29,16 @@ reliant_prompt_user() { exit 0 ;; *) - plymouth display-message --text="Invalid response. [Y(es)/N(o)/S(kip)]: " + plymouth display-message --text="Invalid response." sleep 1 - plymouth hide-message --text="Invalid response. [Y(es)/N(o)/S(kip)]: " + plymouth hide-message --text="Invalid response." ;; esac done } # Ensures the canary files have been wiped by previous shutdown -reliant_initramfs_check_canaries() { +reliant_security_initramfs_check_canaries() { # /rootfs.canary if [ -f /rootfs.canary ]; then reliant_error "reliant_initramfs_check_canaries: /rootfs.canary exists!" @@ -73,14 +73,25 @@ reliant_initramfs_check_canaries() { # Verifies that the initramfs cannot leak information via non-volatile media, logs or network reliant_security_check_initramfs() { - # Run the common checks - reliant-security /sysroot || return $RELIANT_SECURITY_FAIL - + # Run the common checks first + status_common=$RELIANT_OK + reliant-security /sysroot || status_common=$? + # No need to report, already done + # Check canaries - reliant_security_initramfs_check_canaries || return $RELIANT_SECURITY_FAIL - - # Pass - return 0 + status_canaries=$RELIANT_OK + reliant_security_initramfs_check_canaries || status_canaries=$? + echo "[CANARIES]: $(reliant_err2str $status_canaries)" + + # Calculate and return the verdict + echo -n "[VERDICT]: " + if [ $status_common -eq $RELIANT_OK ] && [ $status_canaries -eq $RELIANT_OK ]; then + echo "SAFE TO BOOT" + return $RELIANT_OK + else + echo "POTENTIALLY COMPROMISED" + return $RELIANT_SECURITY_FAIL + fi } # Main function diff --git a/install.sh b/install.sh index 7176283..35e8363 100755 --- a/install.sh +++ b/install.sh @@ -22,6 +22,7 @@ fi : "${RELIANT_SKIP_CHECKSUM:=}" : "${RELIANT_SPARSE_SAMPLES:=512}" : "${RELIANT_BOOTSTRAP_QUBE:=bootstrap}" +: "${RELIANT_KERNEL_VERSION:=$(qvm-run --pass-io "$RELIANT_BOOTSTRAP_QUBE" 'uname -r')}" # Validate configuration values if [ -z "$RELIANT_SECURE_DEVICE" ]; then @@ -86,6 +87,10 @@ reliant_install_dracut() { reliant_install_file "$1" "$RELIANT_DRACUT_DIR/$2" "$3" "$4" "$5" } +# Run the build script inside of the bootstrap qube +echo "[INFO]: Building $RELIANT_BOOTSTRAP_QUBE:$RELIANT_SYSTEM_ROOT for kernel $RELIANT_KERNEL_VERSION..." +qvm-run --pass-io "$RELIANT_BOOTSTRAP_QUBE" "sh -c 'cd $RELIANT_SYSTEM_ROOT && ./build.sh'" + # Begin the installation process echo "[INFO]: Installing reliant-system from $RELIANT_BOOTSTRAP_QUBE:$RELIANT_SYSTEM_ROOT..." @@ -118,7 +123,7 @@ reliant_install_dracut dracut/99reliant/scripts/reliant-initramfs.sh scripts/rel reliant_install_dracut dracut/99reliant/patches/create-snapshot.sh patches/create-snapshot.sh 0755 root root # reliant-system/qubes-sflc -reliant_install_file qubes-sflc/dm-sflc.ko "/usr/lib/modules/$(uname -r)/extra" 0644 root root +reliant_install_file qubes-sflc/dm-sflc.ko "/usr/lib/modules/$RELIANT_KERNEL_VERSION/extra/dm-sflc.ko" 0644 root root reliant_install_sbin qubes-sflc/shufflecake shufflecake 0744 root root echo "[INFO]: Successfully copied files to dom0." @@ -158,11 +163,11 @@ systemctl enable shufflecake-close.service surgeon-dissect -t varlibqubes reliant-snapshot-rw +# reliant-system/qubes-sflc +depmod -a "$RELIANT_KERNEL_VERSION" + # reliant-system/dracut dracut --force --regenerate-all -# reliant-system/qubes-sflc -depmod - # Report successful installation echo "[INFO]: Installation complete. Reboot to enter protected mode." diff --git a/qubes-sflc/build.sh b/qubes-sflc/build.sh index 95b0507..bd5540c 100755 --- a/qubes-sflc/build.sh +++ b/qubes-sflc/build.sh @@ -2,9 +2,29 @@ set -euo pipefail # Download the submodules -git submodule init --update --recursive +echo "[INFO]: Updating git submodules..." +git submodule update --init --recursive + +# Apply the Argon2 memory parameter patch +echo "[INFO]: Applying Argon2 memory patch to improve KDF resilience..." + +# Change the working directory +OLD_PWD=$PWD +cd shufflecake-c + +# Apply the patch +CRYPTO_H=shufflecake-userland/include/utils/crypto.h +git restore $CRYPTO_H +patch $CRYPTO_H "$OLD_PWD/crypto.h.patch" + +# Return back to qubes-sflc +cd "$OLD_PWD" + +# Notify user of a breaking change +echo "[WARN]: Your new Argon2 memory parameter is 2097152 KiB, INCOMPATIBLE with default Shufflecake." # Ensure Docker is running +echo "[INFO]: Starting the Docker container..." sudo systemctl start docker # Build and execute the container @@ -12,5 +32,9 @@ sudo docker build -t qubes-sflc . sudo docker run --rm -v /usr/lib/modules:/usr/lib/modules:ro -v $PWD/shufflecake-c:/root/shufflecake-c qubes-sflc # Copy artifacts -cp shufflecake-c/dm-sflc.ko $PWD/dm-sflc.ko -cp shufflecake-c/shufflecake $PWD/shufflecake +echo "[INFO]: Copying build artifacts..." +cp shufflecake-c/dm-sflc.ko "$PWD/dm-sflc.ko" +cp shufflecake-c/shufflecake "$PWD/shufflecake" + +# Restore the original crypto.h to avoid interference with future updates +git restore "shufflecake-c/$CRYPTO_H" diff --git a/qubes-sflc/crypto.h.patch b/qubes-sflc/crypto.h.patch new file mode 100644 index 0000000..f8fea8b --- /dev/null +++ b/qubes-sflc/crypto.h.patch @@ -0,0 +1,13 @@ +diff --git a/shufflecake-userland/include/utils/crypto.h b/shufflecake-userland/include/utils/crypto.h +index 8366134..303b2ae 100644 +--- a/shufflecake-userland/include/utils/crypto.h ++++ b/shufflecake-userland/include/utils/crypto.h +@@ -67,7 +67,7 @@ + + // Argon memory parameter + // We assume machines with at least 128 MiB available RAM, so 2^17 kiB +-#define SFLC_ARGON_M (1 << 17) /* kibibytes */ ++#define SFLC_ARGON_M 2097152 /* kibibytes */ + + // Argon iterations count + // We aim for 1-2 seconds on a low-end laptop or mobile (it's a one-time operation) diff --git a/tools/reliant-mount b/tools/reliant-mount index 9da0146..4dab1b3 100755 --- a/tools/reliant-mount +++ b/tools/reliant-mount @@ -23,8 +23,9 @@ modprobe dm-sflc # Prompt the user for their password while true; do - if plymouth ask-for-password --prompt="Auxillary password" | shufflecake open "$SECURE_DEVICE" &>/dev/null; then + if plymouth ask-for-password --prompt="Auxillary password" | shufflecake open "$RELIANT_SECURE_DEVICE" &>/dev/null; then plymouth display-message --text="Success" + break else plymouth display-message --text="Invalid password" sleep 2 diff --git a/tools/reliant-seal b/tools/reliant-seal index 9c1901d..6124fe8 100755 --- a/tools/reliant-seal +++ b/tools/reliant-seal @@ -22,7 +22,7 @@ fi name=$1 echo -n "Destroying links... " -for appvm in /run/shufflecake/$name/appvms/*; do +for appvm in "/run/shufflecake/$name/appvms/"*; do qube="${appvm##*/}" # Shutdown @@ -31,8 +31,8 @@ for appvm in /run/shufflecake/$name/appvms/*; do # Remove directory directory="/var/lib/qubes/appvms/$qube" - if [ -d $directory ]; then - rm -rf $directory + if [ -d "$directory" ]; then + rm -rf "$directory" fi done echo "Done." diff --git a/tools/reliant-security b/tools/reliant-security index ca20336..4fc09a7 100755 --- a/tools/reliant-security +++ b/tools/reliant-security @@ -15,6 +15,11 @@ fi # No more variable checks needed set -u +# Require superuser permissions +if [ "$EUID" -ne 0 ]; then + reliant_fail "must be superuser" +fi + # Kernel command line check reliant_security_check_cmdline() { # Check if /proc/cmdline exists @@ -94,7 +99,7 @@ reliant_security_check_network() { # Persistent, real filesystems must be readonly reliant_security_check_filesystems() { # Check each mountpoint - mount | while IFS= read -r line; do + mount | while read -r line; do # Determine the filesystem type filesystem_type=$(echo "$line" | awk '{ print $5 }' ) @@ -115,7 +120,7 @@ reliant_security_check_filesystems() { esac done - # Pass from subshell + # Pass status from subshell return $? } @@ -139,68 +144,64 @@ reliant_security_check_overlay() { fi # Find the mountpoint - mount | while IFS= read -r line; do - # Determine the mountpoint - filesystem_mountpoint=$(echo "$line" | awk '{ print $3 }') + mount | { + found=$RELIANT_FALSE + while read -r line; do + # Filter by mountpoint + filesystem_mountpoint=$(echo "$line" | awk '{ print $3 }') + if [ "$filesystem_mountpoint" != "$1" ]; then continue; fi - # Skip if not matching - if [ "$filesystem_mountpoint" != "$1" ]; then continue; fi - - # Determine the filesystem flags - filesystem_flags=$(echo "$line" | awk '{ print $6 }') - - # Iterate the flags - upper_found=$RELIANT_FALSE - work_found=$RELIANT_FALSE - IFS=',' - for flag in $filesystem_flags; do - case $flag in - 'upperdir=/run/systemd/overlay-sysroot/upper') upper_found=0 ;; - 'workdir=/run/systemd/overlay-sysroot/work' ) work_found=0 ;; - esac + # Filter by filesystem type + filesystem_type=$(echo "$line" | awk '{ print $5 }' ) + if [ "$filesystem_type" != "overlay" ]; then continue; fi + + # Determine the filesystem flags + filesystem_flags=$(echo "$line" | awk '{ print $6 }') + + # Iterate the flags + upper_found=$RELIANT_FALSE + work_found=$RELIANT_FALSE + IFS=',' + for flag in $filesystem_flags; do + case $flag in + 'upperdir=/run/systemd/overlay-sysroot/upper') upper_found=$RELIANT_TRUE ;; + 'workdir=/run/systemd/overlay-sysroot/work' ) work_found=$RELIANT_TRUE ;; + esac + done + + # Check if they have been validated + if [ $work_found -ne $RELIANT_TRUE ]; then + reliant_error "reliant_security_check_overlay: workdir for $1 could not be validated" + exit $RELIANT_SECURITY_FAIL + fi + if [ $upper_found -ne $RELIANT_TRUE ]; then + reliant_error "reliant_security_check_overlay: upperdir for $1 could not be validated" + exit $RELIANT_SECURITY_FAIL + fi + + # Indicate that the mountpoint has been found + found=$RELIANT_TRUE + break done - # Check if they have been validated - if [ $work_found -ne $RELIANT_TRUE ]; then - reliant_error "reliant_security_check_overlay: workdir for $1 could not be validated" - exit $RELIANT_SECURITY_FAIL - fi - if [ $upper_found -ne $RELIANT_TRUE ]; then - reliant_error "reliant_security_check_overlay: upperdir for $1 could not be validated" + # Check if the mountpoint has been found within the loop + if [ $found -ne $RELIANT_TRUE ]; then + reliant_error "reliant_security_check_overlay: mountpoint for overlay root on $1 not found" exit $RELIANT_SECURITY_FAIL + else + exit $RELIANT_OK fi + } - # Indicate that the mountpoint has been found - exit 255 - done - status=$? - - # Check the subshell return code - case "$status" in - 0) - # Not found - reliant_error "reliant_security_check_overlay: mountpoint $1 not found" - return $RELIANT_FAIL - ;; - - 255) - # Found - return $RELIANT_OK - ;; - - *) - # Other issues - reliant_error "reliant_security_check_overlay: mountpoint $1 verification failed" - return $status - ;; - esac + # Forward from subshell + return $? } # Checksum verification reliant_security_check_devices() { IFS=$'\n' - for device in $(lsblk -rno name); do - device_path="/dev/$device" + for device_path in $(lsblk -rnpo name); do + device="${device_path##*/}" # Check if it is a valid block device if [ ! -b "$device_path" ]; then return $RELIANT_BUG; fi @@ -214,6 +215,7 @@ reliant_security_check_devices() { # User-specified devices will be excluded should_skip=$RELIANT_FALSE + IFS=' ' for skip_device in $RELIANT_SKIP_CHECKSUM; do if [ "$skip_device" = "$device_path" ]; then should_skip=$RELIANT_TRUE @@ -232,7 +234,7 @@ reliant_security_check_devices() { # Inside initramfs, use Plymouth, otherwise, use read prompt="$device_path matches $hash? [Y/N]" - if [ $RELIANT_INITRAMFS -eq $RELIANT_TRUE ]; then + if [ "$RELIANT_INITRAMFS" -eq $RELIANT_TRUE ]; then REPLY=$(plymouth ask-question --prompt="$prompt") else read -r -p "$prompt: " @@ -244,6 +246,9 @@ reliant_security_check_devices() { return $RELIANT_SECURITY_FAIL fi done + + # Forward from subshell + return $? } # Main function @@ -272,7 +277,7 @@ main() { reliant_security_check_network || status_network=$? reliant_security_check_filesystems || status_filesystems=$? reliant_security_check_overlay "$1" || status_overlay=$? - reliant_security_check_devices || status_devices=$? + reliant_security_check_devices || status_devices=$? # Print the report echo "[REPORT]" diff --git a/tools/surgeon-dissect b/tools/surgeon-dissect index f369337..c16245f 100755 --- a/tools/surgeon-dissect +++ b/tools/surgeon-dissect @@ -106,7 +106,7 @@ def parse_qubes_xml(target_pools, qubes_xml): for domain in root.find('domains'): volume_config = domain.find('volume-config') if volume_config is None: - continue + volume_config = [] # Detect sys-net and sys-whonix # TODO: use reliant.conf RELIANT_RW_DOMAINS @@ -118,8 +118,6 @@ def parse_qubes_xml(target_pools, qubes_xml): # Check VM type if domain.get('class') == 'AdminVM': - # dom0 - # Disable update checking features = domain.find('features') for feature in features: @@ -127,6 +125,9 @@ def parse_qubes_xml(target_pools, qubes_xml): features.remove(feature) ElementTree.SubElement(features, 'feature', name='config.default.qubes-update-check').text = '' + # dom0 does not have volumes, so add it immediately + domains['varlibqubes'].append(domain) + else: # Not dom0, qube ID must be randomized qid = random.randint(1, qubes.config.max_qid)