Plausible deniability suite for QubesOS
Find a file
2025-10-23 14:47:47 -04:00
common dev release 2025-08-26 17:25:16 -04:00
dracut/99reliant idempotent seal-unseal; e2fsck works as intended; profiling patch applied reliably 2025-10-23 14:47:47 -04:00
extra remove nonexistent systemd service parameters 2025-10-22 19:36:03 -04:00
qubes-sflc 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 2025-10-22 14:34:13 -04:00
tools idempotent seal-unseal; e2fsck works as intended; profiling patch applied reliably 2025-10-23 14:47:47 -04:00
.gitmodules qubes-sflc build script and instructions 2025-06-30 01:34:35 -04:00
build.sh more fixes, add argon2 patch 2025-09-03 23:26:32 -04:00
install.sh idempotent seal-unseal; e2fsck works as intended; profiling patch applied reliably 2025-10-23 14:47:47 -04:00
LICENSE Initial commit 2025-06-25 22:38:49 +02:00
README.md idempotent seal-unseal; e2fsck works as intended; profiling patch applied reliably 2025-10-23 14:47:47 -04:00

reliant-system

Reliant provides a complete plausible deniability solution based on QubesOS and Shufflecake. The core principle of the system is to avoid leaking information about the hidden volumes via metadata, system logs, network and other means. This is addressed by effectively separating the OS into two parts, volatile core and persistent data. The base Qubes system is forced into an immutable state1 and verified on boot via a hashsum2 to ensure nothing has bypassed the readonly restrictions. System qubes are modified to work on an OverlayFS root, which is not normally possible3. A Shufflecake-encrypted partition stores an unknown number of deniable volumes, each attached dynamically as a storage pool during the initramfs phase. We rely on QubesOS to provide sufficient isolation of qubes from each other, however there are still certain caveats you need to be aware of, in particular when sharing files between qubes or exposing hidden qubes to the Internet.

How it works

Bootloader

Linux kernel command line is modified to include systemd.volatile=overlay to make use of existing SystemD functionality and create a volatile root environment. This flag also acts as a toggle for Reliant, and erasing it will enable Maintenance Mode.

Initramfs

Early hook

readonly.sh runs before any filesystem is mounted and executes blockdev --setro on every detected block device. This has to be done very early during boot because otherwise mounting most filesystems will inevitably change some metadata, such as last mount time, and consequently break the hash verification even if no changes have been made to the filesystem contents.

Main service

After SystemD creates the overlayed root filesystem on /sysroot, reliant.service runs reliant-initramfs.sh, a multi-stage script which validates the runtime security context, and, if successful, unlocks and mounts the Shufflecake volumes under /run/shufflecake. reliant-security ensures a fully volatile environment by scanning for any non-RAM filesystems or writable block devices. Networking should not be available in Qubes dom0 by default, but the script still ensures only the loopback interface is active. Volatility of the root filesystem, the most important factor, is also verified comprehensively by analyzing the mountpoint information and using canary files to signal about data leaks.

Hashes are calculated for all available block devices, except for RELIANT_SECURE_DEVICE and those listed in RELIANT_SKIP_CHECKSUM. It is most important to check the hashsums for /boot, /boot/efi and / partitions, while the rest can be reasonably excluded unless you want a greater degree of data authentication4. However, it is important to note that these hash checks are merely a 'canary' in case of accidental data changes bypassing other security measures. An 'Evil Maid' adversary aware of such a system being in place could easily bypass it by taking note of the hashes and altering the verification script. Therefore, you should still use Anti Evil Maid if such attacks are within your threat model. This also implies that if you believe the device hashing process to be too long, you can bypass it by adding more devices to RELIANT_SKIP_CHECKSUM without significant security implications.

When the environment is marked as secure, the user will be prompted for their Shufflecake password. Upon successful decryption, reliant-mount sets up the Shufflecake volumes, mounting them in a sealed state. Patch subroutine surgeon-suture, vital for operation of the system, is started subsequently to recreate qubes.xml from shards of domains.xml.

Installation

Reliant is installed on top of a regular instance of QubesOS. However, make sure to leave most of the space for the Shufflecake partition. You will not be able to run regular qubes from varlibqubes unless you add them into RELIANT_RW_DOMAINS. For this reason, you are advised to move them to the zeroth volume of your Shufflecake device.

Bootstrap

Boot into the Qubes system and create a standalone qube named bootstrap, or otherwise indicate the name in the environment variable RELIANT_BOOTSTRAP_QUBE. You will use it for installation and subsequent updates of Reliant. Install Git and Docker in the bootstrap qube and clone this repository into /home/$USER/reliant-system. In dom0, run qvm-run --pass-io bootstrap 'cat /home/$USER/reliant-system/install.sh' | install -D -m 0755 /dev/stdin install.sh to copy the installation script. You are encouraged to read it before running, as it is well-documented. Optionally, edit /etc/fstab to mount /boot and /boot/efi as ro,noatime,nodiratime and / as rw,noatime,nodiratime.

Configuration

Several environment variables can be configured and will be saved into /etc/reliant.conf by the installation script.

  • RELIANT_PARANOID=true|false - currently unused, in the future will be used as a flag for stricter security measures
  • RELIANT_RW_DOMAINS=sys-net sys-whonix... - used in conjunction with reliant-snapshot-rw to allow specified qubes to run from an OverlayFS pool3
  • RELIANT_SKIP_CHECKSUM=/dev/sdX /dev/sdY1 /dev/sdY2... - excludes the block devices from checksum verification during boot
  • RELIANT_SECURE_DEVICE=/dev/sdZ - required, the Shufflecake encrypted device
  • RELIANT_SPARSE_SAMPLES=512 - the number of samples for sparse hashing, more samples have higher coverage but are slower to compute and vice versa

Creating a Shufflecake device

After install.sh tells you to reboot into Protected Mode, follow the instructions, but skip the mount phase during boot. This can be achieved by deliberately reporting a hash mismatch. If there are other security issues reported by the script, do NOT proceed and attempt to investigate and fix them first. Most likely, this is a bug and has to be reported. Otherwise, you will enter Protected Mode but without access to any Shufflecake volumes5. Format RELIANT_SECURE_DEVICE into a Shufflecake device, and create a new ext4 filesystem on each volume. Optionally, you may create an empty appvms directory and a blank domains.xml file under each volume root.

Importantly, Shufflecake devices created by the shufflecake binary from Reliant are not compatible with other Shufflecake devices, since Reliant patches Argon2 KDF parameters for higher security. In particular, the Argon2 memory parameter is changed to 2 GiB.

Post-installation

Your system should be operational by now, and feel free to report any bugs and issues in this repository. The software is highly experimental, and it is possible you will need to tweak the scripts at some point, so experience with Linux or QubesOS in particular is vital.

Usage

Updating the system

Reliant significantly changes the normal operation of QubesOS, effectively separating the system into two modes of operation, designated Protected Mode and Maintenance Mode. No permanent changes, such as system configuration or upgrade, can be made under Protected Mode. To enter Maintenance Mode, erase systemd.volatile=overlay from the kernel command line during boot. You may also opt to create a custom GRUB entry for this purpose. Then, you can freely update and alter your base system. Take care not to touch the Shufflecake device, since opening it will inevitably compromise your system. During the next boot your device hashes will change4 - this is normal and expected, since you have made changes to the physical data on drives.

Deniable qubes

Creating a deniable qube

First, reliant-unseal the target volume. Then, create a qube like usual, taking care to specify sflc_X_X as the target storage pool. Afterwards, run surgeon-dissect -t sflc_X_X to update domains.xml in the volume.

To Template or Not To Template

Template qubes are much more convenient than standalones, since they allow for the system to be upgraded in a centralized way. However, there are extra considerations for plausible deniability systems. Having certain software installed in a template and no qubes actually using it could tip an adversary onto the existence of a hidden qube. But this is by no means a compelling argument, since the requirement to reboot every time you install new software could lead the user to decide to install a lot of things 'just in case'. On the other hand, an adversary with network control will clearly see when you update your qubes, and if you're updating a bunch of standalones, it could be suspicious. This is, of course, addressable by updating through Tor, but that is not always a viable option. Standalones also have an additional benefit of not needing a physical reboot into Maintenance Mode to install new software. In the end, both options are viable depending on your threat model.

Running a deniable qube

reliant-unseal the target volumes, then run the qube like usual. It is considered good practice to keep all volumes sealed except the ones you need at the current moment. Be careful when running the commands, because the volume number you enter may leak information via side channels6. reliant-seal the volume after you no longer need it.

Modifying a deniable qube

After making any modifications to qubes.xml, either manually or via Qube Manager configuration GUI, ensure to run surgeon-dissect -t sflc_X_X to update the corresponding domains.xml entry of the deniable qube.

Networking in deniable qubes

Internet access inside a qube, the existence of which you should be able to deny, is a difficult situation. For example, if you have a deniable qube running software which 'phones home', and this software is not present in any of your unlocked qubes, it will be suspicious to an adversary. There are two solutions to this problem.

Option A: No exclusive software

Do not have any software in higher-layer qubes that is not present in lower-layer qubes.

Option B: Network security

Of course, all of these concerns can be avoided if you use a reasonable network security solution, such as Tor or trusted VPN. It is unlikely, but possible, that traffic analysis could be employed to deanonymize Tor or VPN users, in which case stronger measures are necessary such as using a mixnet.

firewall.rules

Firewall protection is paramount for isolation of qubes from the outside network. Therefore, an extension was implemented to allow this in deniable qubes. This is achieved by placing a simple file firewall.rules in the root directory of your qube. The contents of the file are the arguments to be passed to qvm-firewall, for example

action=accept dst4=1.1.1.1 dstports=443 proto=tcp
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 argumentop

  • systemd.volatile=overlay controls the switch between Protected Mode (when present) and Maintenance Mode (when omitted)
  • reliant.e2fsck attempts to run e2fsck -p on volumes in case mount fails
  • 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 and QubesOS.

  • reliant-unseal is too slow and starts every qube in the volume when unsealing. This might be significant if the qubes are networked and send outgoing traffic upon being started - further investigation needed.

  • qvm-copy-to-vm and qvm-copy WILL break your plausible deniability if you copy from a higher-layer qube to a lower-layer hidden qube. Mitigated by using a proxy disposable qube.

  • Manual verification of hashes is error-prone. Possible solution is to have a small partition that stores the hashes, and write to that partition automatically when the system is powered off in maintenance mode.

  • Sparse checksums. Possibly by minimizing the size of root filesystem, dense hashing could be made feasible.

  • GPT/MBR might need to be hashed too. However, this is more of an Anti Evil Maid countermeasure.

  • Password must be entered twice on each boot if you decide to encrypt your root filesystem with LUKS (highly advisable, although technically there should be no personal or sensitive data there). Possible workaround is to decrypt the Shufflecake partition before the root partition, and storing a LUKS keyfile in each volume. However, this is not considered a high priority issue since the boot time will be long anyway due to the hashing process.

  • An adversary capable of monitoring your network connection would be able to infer some conclusions about the kind of applications installed on your system. This can be mitigated by 1) using sys-whonix for everything, which is slow 2) carefully planning the usage of your system to avoid raising any red flags which would hint at the existence of hidden volumes.

  • Firewall configuration is very rigid, which makes it difficult to properly restrict network usage by qubes like an application-level firewall. This is a Qubes issue, rather that a Reliant issue, but we can address it in the future.

  • Side channels6.

  • RAM is not wiped on shutdown.

  • QubesOS has a policy to distrust the infrastructure. Currently there's no consideration for supply chain attacks on our side, and should this repository be compromised, the consequences could be fatal. All source code commits are signed, however there's no established secondary mechanism to distribute the key like in Qubes.

  • Update checks may also leak information, better to disable them except for dom0 - already done by surgeon-dissect.

  • Qube IDs, if ordered, leak information - mitigated by randomizing them in surgeon-dissect.

/var (or other directory) fills up in template qubes!

This happens due to how QubesOS handles overwriting the root directory in templates. All the changes go into the root-cow.img image which unfortunately is stored under the varlibqubes pool. There might be a way to enforce template-local CoW cache, but it hasn't been investigated yet. You are advised to either redirect the data into your home directory via an application-specific method (e.g. specifying TMPDIR or cache directory as an environment variable), convert to a standalone qube, or in case you're installing software, doing it permanently through Maintenance Mode.

Qubes get assigned an invalid IP address, e.g. 10.X.X.353

This seems to be an issue caused by Qube IDs above 10000 (the default limit). The offending change has already been reverted, so this is left here for purely informational purposes. Changing the QID to below 10000 or making the qube non-networked both resolve the problem.

Planned features

  • Improved seal-unseal mechanism which respects RELIANT_PARANOID, is faster, does not require volume number and can be integrated into the Qubes app menu in the future as reliant-start QUBE. Possibly keep directories of unused volumes under /run/shufflecake unmounted and removed when not in use. Decrease granularity of the seal-unseal mechanism down to a single qube.

  • Network security integration to facilitate usage of VPNs via sys-vpn and OpenSnitch via sys-snitch. The latter could host the GUI and display prompts, decoupled from opensnitchd inside individual app qubes. Potentially, a proxy will be needed to aggregate messages from daemons in multiple domains.

  • Thorough scrubbing of metadata inside images. It is currently up to consideration whether changing otherwise fixed metadata such as ctime via an invocation of debugfs is worth the risk. Shell history might also be automatically scrubbed upon sealing. This could be a feature exclusive to RELIANT_PARANOID=true.

  • reliant-shred for sanitizing partitions or Shufflecake volumes in case of information leaks. Due to the architecture of Shufflecake simply doing a dd if=/dev/urandom of=/dev/mapper/sflc_X_X will exhaust the free blocks pool for ALL volumes, permanently assigning it to the volume that has been filled with junk data. In future releases, you should be able to blkdiscard the volume afterwards to recollect its blocks and return them to the free pool. However, this still does a lot of unnecessary disk writes while we know exactly which blocks are used and which aren't. There's no need to overwrite blocks that are not used and have never been assigned to the volume.

  • Tighter physical security measures. Emergency shutdown on various conditions (e.g. USB), integration with Anti Evil Maid, wiping RAM on shutdown. Better handling of hashes which does not require manual confirmation (potentially via TPM).


  1. Using a combination of systemd.volatile=overlay, blockdev --setro and mount -o ro. ↩︎

  2. Sparse checksums with a configurable sampling interval are used for large drives to avoid stalling the boot. This does not significantly compromise security, since the filesystem superblocks are always included in the hashsum and any read-write mounts will therefore be detected (unless using a patched filesystem driver that does not change last mount time? but changing files should be visible anyway). In any case the hashing is technically an optional feature that provides a degree of Anti Evil Maid resistance and will alert if you if there has been any (with the abovementioned exception) modification to the filesystems. ↩︎

  3. QubesOS makes use of loop devices to load virtual disk images from the storage pool. When a sufficiently large (>1 GiB) image is loaded with write permissions, OverlayFS copy-on-write mechanism is overwhelmed, causing losetup to fail. This is addressed by enforcing all images from the varlibqubes pool to be loaded read-only save for a select few specified in RELIANT_RW_DOMAINS via a runtime patch of /usr/lib/qubes/create-snapshot. The exceptions are necessary for non-disposable system qubes, such as sys-net and sys-whonix, to function correctly. ↩︎

  4. Currently, the verification is entirely manual. If you have booted the system in Maintenance Mode, expect both / and /boot fingerprints to change, because mounting an ext4 filesystem on /boot without blockdev --setro alters the last mounted time metadata. /boot/efi, being a FAT filesystem, is not affected. If you have not booted the system in Maintenance Mode but your hashes still changed, this could be a sign of a compromised system, either deliberately by malicious software or Evil Maid, or accidentally by yourself altering these partitions. You should absolutely avoid executing dracut in Protected Mode, even though technically possible, this is very likely to leave traces of your Shufflecake volumes in the unprotected initramfs image. If that is something you have done, it is advised to 1) boot in Maintenance Mode 2) shred or otherwise securely wipe the /boot partition 3) reformat and run dracut again. Worst case scenario, you can always carefully reinstall the base Qubes system without losing any data stored on the Shufflecake partition. ↩︎

  5. '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. 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 and Van Eck phreaking and relevant mitigation methods. Network access in deniable qubes is also a side channel which should be carefully worked around. ↩︎