reliant.profile and reliant.e2fsck commandline options; unseal and seal now mount and unmount instead of changing flags; displayed volume limit via reliant.dvl=VID

This commit is contained in:
Anderson Rosenberg 2025-10-22 14:34:13 -04:00
parent 8a3ae37678
commit 501d68bf7e
No known key found for this signature in database
GPG key ID: 7ACF448C0590AB9C
7 changed files with 130 additions and 20 deletions

View file

@ -94,6 +94,14 @@ action=accept dst4=1.1.1.1 dstports=80 proto=tcp
```
Due to potential security implications of arbitrary code execution (firewall.rules is not sanitized when a volume is unsealed), the user is asked for manual confirmation before the firewall configuration is executed.
### Commandline arguments
- `systemd.volatile=overlay` controls the switch between Protected Mode (when present) and Maintenance Mode (when omitted)
- `reliant.e2fsck` runs `e2fsck -p` on every volume before mount to avoid needing an extra reboot in case of accidental data corruption
- `reliant.profile` mounts the upperdir and workdir of OverlayFS in an accessible location under /run/reliant for debugging purposes
- `reliant.dvl=VOLUME_ID` limits the amount of volumes displayed in Qubes app menu, manager, and other places, useful for when you want to avoid showing your hidden qubes in public
- Displayed volume limit does **NOT** provide plausible deniability or actual device inspection
## Known issues
- All known issues of [Shufflecake](https://shufflecake.net/#faq) and [QubesOS](https://www.qubes-os.org/faq).
@ -154,4 +162,4 @@ This seems to be an issue caused by Qube IDs above 10000 (the default limit). Th
[^5]: In deniable systems, not only your password but the presence information (e.g. number of volumes in case of Shufflecake) is confidential. Side channels can and will leak information about your system. Make sure you keep your phone away from your workplace, preferably turned off unless you it has hardware switches for the camera and microphone. Your keystrokes can absolutely be recorded and used to infer data about your passwords and the number of volumes. For information about other potential side channel attacks you are encouraged to look up information on [TEMPEST](https://en.wikipedia.org/wiki/Tempest_%28codename%29) and [Van Eck phreaking](https://en.wikipedia.org/wiki/Van_Eck_phreaking) and relevant mitigation methods. Network access in deniable qubes is also a side channel which should be carefully worked around.
[^6]: 'Skipped-Mount Protected Mode' is not a normal operation mode and the system will be highly broken under such circumstances since `surgeon-suture` cannot run. Nevertheless, dom0 and keyboard should remain functional, and this is all you need - switch to a TTY by pressing Ctrl+Alt+F2, log in and run `shufflecake init $RELIANT_SECURE_DEVICE`, where `$RELIANT_SECURE_DEVICE` is your soon-to-be Shufflecake device.
[^6]: 'Skipped-Mount Protected Mode' is not a normal operation mode and the system will be highly broken under such circumstances since `surgeon-suture` cannot run. Nevertheless, dom0 and keyboard should remain functional, and this is all you need - switch to a TTY by pressing Ctrl+Alt+F2, log in and run `shufflecake init $RELIANT_SECURE_DEVICE`, where `$RELIANT_SECURE_DEVICE` is your soon-to-be Shufflecake device.

View file

@ -111,6 +111,12 @@ main() {
# Run the surgeon script to fix qubes.xml
surgeon-suture
# Seal all volumes
for path in '/run/shufflecake/'*; do
name="${path##*/}"
reliant-seal "$name"
done
}
main

View file

@ -8,11 +8,20 @@ if [ ! -d /run/shufflecake ]; then
exit 0
fi
# Shut down everything first
qvm-shutdown --force --all --wait --timeout 10 || true
# Detach loop devices
losetup -D || true
# Seal and close each Shufflecake volume
for path in /run/shufflecake/*; do
volume="${path##*/}"
reliant-seal "$volume"
umount "$path"
volume="${path##*/}" || true
reliant-seal "$volume" || true
# reliant-seal does unmount now, but leave this for good measure
# or if the user has old reliant-seal for some reason
umount "$path" || true
done
# Close the Shufflecake device

@ -1 +1 @@
Subproject commit 96c1557cdde6d92c299592cbfcd39af1e51b7c29
Subproject commit 635fcbd228dcd28b748ccc47bee14dcfc7434dd2

View file

@ -33,21 +33,69 @@ while true; do
fi
done
# Check for the displayed volume limit in commandline arguments
# This is only useful when booting in public to hide deniable qubes
dvl_required=$RELIANT_FALSE
for argument in $(cat /proc/cmdline); do
if [[ "$argument" == reliant.dvl=* ]]; then
dvl_id="${argument##*/}"
dvl_required=$RELIANT_TRUE
fi
done
# Set the displayed volume limit
if [ -n "$dvl_id" ]; then
dvl_device="/dev/mapper/sflc_0_$dvl_id"
# Notify the user so they know the limit has been detected
if [ -b "$dvl_device" ]; then
plymouth display-message --text="Displayed volumes limited"
sleep 1
plymouth hide-message --text="Displayed volumes limited"
else
while true; do
dvl_id=$(plymouth ask-question --prompt="Displayed volume limit is invalid. Provide a new one")
dvl_device="/dev/mapper/sflc_0_$dvl_id"
# Validate user input
if [ -b $dvl_device ]; then
plymouth display-message --text="Limit adjusted successfully"
sleep 1
plymouth hide-message --text="Limit adjusted successfully"
break
fi
done
fi
fi
# Create the volume root directory under /run
mkdir -m 750 /run/shufflecake
# Check if we need to e2fsck
reliant_e2fsck=$(grep -q reliant.e2fsck /proc/cmdline)
# Mount each volume
find /dev/mapper -maxdepth 1 -name 'sflc_*' | while read -r device; do
find /dev/mapper -maxdepth 1 -name 'sflc_0_*' | while read -r device; do
# IMPORTANT: Seal it
blockdev --setro "$device" || exit 1
# Determine the name and mountpoint
name="${device##*/}"
mountpoint="/run/shufflecake/$name"
# e2fsck if requested
if [ "$reliant_e2fsck" ]; then
e2fsck -p "$device" &> /dev/null || true # If the user has mixed filesystems, it will fail for some
fi
# Mount in /run/shufflecake
mkdir -m 750 "$mountpoint"
mount -o ro,noatime,nodiratime "$device" "$mountpoint" &> /dev/null || true # allow it to silently fail in case there's no filesystem or it is corrupted
mount -o ro,noatime,nodiratime "$device" "$mountpoint" &> /dev/null || true # Allow it to silently fail in case there's no filesystem or it is corrupted
# Apply the displayed volume limit if required
if [ $dvl_required = $RELIANT_TRUE ] && [ "$device" = "$dvl_device" ]; then
break
fi
done
# Set up the volatile image pool
@ -57,3 +105,17 @@ for path in '/sysroot/var/lib/qubes/appvms/'*; do
name="${path##*/}"
mkdir -m 750 "/run/volatile/appvms/$name"
done
# Check if we need to set up profiling
if grep -q reliant.profile /proc/cmdline; then
mkdir -m 750 /run/reliant
mkdir -m 750 /run/reliant/profile
# Upperdir
mkdir -m 750 /run/reliant/profile/upper
mount --bind /run/systemd/overlay-sysroot/upper /run/reliant/profile/upper
# Workdir
mkdir -m 750 /run/reliant/profile/work
mount --bind /run/systemd/overlay-sysroot/work /run/reliant/profile/work
fi

View file

@ -38,11 +38,15 @@ done
echo "Done."
echo -n "Sealing mountpoint... "
mount -o remount,ro,noatime,nodiratime /run/shufflecake/$name
mount -o remount,ro,noatime,nodiratime "/run/shufflecake/$name"
echo "Done."
echo -n "Sealing device... "
blockdev --setro /dev/mapper/$name
blockdev --setro "/dev/mapper/$name"
echo "Done."
echo -n "Unmounting device... "
umount "/run/shufflecake/$name" && rmdir "/run/shufflecake/$name"
echo "Done."
echo -e "${GREEN}Sealed.${ENDCOLOR} See reliant-status for more information."

View file

@ -5,8 +5,8 @@ RED="\e[31;1m"
GREEN="\e[32;1m"
ENDCOLOR="\e[0m"
if [ "$#" -ne 1 ]; then
echo "Expected 1 argument."
if [ "$#" -lt 1 ]; then
echo "Expected at least 1 argument."
exit 1
fi
@ -21,23 +21,44 @@ if [ ! -d /run/shufflecake ]; then
fi
name=$1
device="/dev/mapper/$name"
echo -n "Unsealing device... "
blockdev --setrw /dev/mapper/$name
blockdev --setrw "$device"
echo "Done."
echo -n "Unsealing mountpoint... "
mount -o remount,rw,noatime,nodiratime /run/shufflecake/$name
mkdir "/run/shufflecake/$name"
mount -o rw,noatime,nodiratime "$device" "/run/shufflecake/$name"
echo "Done."
# Check if we were given a qube list
allowed_qubes="${*:2}"
echo "Creating links... "
for appvm in /run/shufflecake/$name/appvms/*; do
for appvm in "/run/shufflecake/$name/appvms/"*; do
qube="${appvm##*/}"
# Filter if user provided a list of qubes
if [ ! -z "$allowed_qubes" ]; then
allowed=0
for allowed_qube in $allowed_qubes; do
if [ "$qube" = "$allowed_qube" ]; then
allowed=1
break
fi
done
# Only unseal explicitly requested qubes
if [ $allowed -ne 1 ]; then
continue
fi
fi
# Directory link
directory="/var/lib/qubes/appvms/$qube"
if [ ! -d $directory ]; then
install -d -o root -g qubes -m 0750 $directory
if [ ! -d "$directory" ]; then
install -d -o root -g qubes -m 0750 "$directory"
fi
# App menus
@ -46,9 +67,9 @@ for appvm in /run/shufflecake/$name/appvms/*; do
su user -c "qvm-shutdown $qube" &>/dev/null || true
# Firewall rules
if [ -f $appvm/firewall.rules ]; then
if [ -f "$appvm/firewall.rules" ]; then
echo "Found firewall.rules. Approve?"
cat $appvm/firewall.rules
cat "$appvm/firewall.rules"
read -p "[Y/N]: " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
@ -59,9 +80,9 @@ for appvm in /run/shufflecake/$name/appvms/*; do
echo "Approved."
su user -c "qvm-firewall $qube reset"
su user -c "qvm-firewall $qube del --rule-no 0"
while read; do
while read -r; do
su user -c "qvm-firewall $qube add $REPLY"
done < $appvm/firewall.rules
done < "$appvm/firewall.rules"
su user -c "qvm-firewall $qube add action=drop"
fi
done