Plausible deniability suite for QubesOS
Find a file
2025-08-21 08:55:08 -04:00
extra extra configuration files and services 2025-08-21 08:47:05 -04:00
qubes-sflc update instructions 2025-08-21 08:55:08 -04:00
.gitmodules qubes-sflc build script and instructions 2025-06-30 01:34:35 -04:00
LICENSE Initial commit 2025-06-25 22:38:49 +02:00
README.md extra configuration files and services 2025-08-21 08:47:05 -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 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 possible. 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 sharing files between qubes.

Architecture

Bootloader

GRUB command line is modified to include systemd.volatile=overlay to make use of existing SystemD functionality and create a volatile root filesystem. This flag also acts as a toggle for further stages, and will enable Maintenance Mode if removed.

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 mounting an ext4 filesystem, even readonly, changes some metadata which in turn breaks the hash validation.

Main service

After /sysroot is mounted by systemd as an OverlayFS, reliant.service runs reliant.sh, a multi-stage script which is the core of the project. First, the security context is validated by considering several factors (no swap, volatile root, no writable on-disk filesystems, no network). Hashes are calculated for the base QubesOS system partitions. Currently, the verification is entirely manual. You need to ask yourself the question 'do I expect the hashes to change' and compare them with the previously recorded values on paper. If you have booted the system in Maintenance Mode by manually removing systemd.volatile=overlay, both root filesystem and /boot hashes will change, because mounting the ext4 filesystem on /boot without blockdev --setro alters the last mounted time metadata. If you have not booted the system in maintenance mode but your hashes still have changed, this suggests that either 1) you got the wrong paper 2) there is a bug in Reliant 3) something did --setrw your base system devices and write to them. In any case, you need to carefully consider you next actions. Worst case scenario, you can always carefully reinstall the base system without losing any data stored on the Shufflecake partition.

After the security context is confirmed as valid, the user is prompted for their Shufflecake password. Upon successful decryption, the surgeon-suture subroutine is started. This is what makes QubesOS actually work under such bizarre conditions. It deletes the original qubes.xml, and reassembles it from pieces of domains.xml.

Userspace

Upon login into the dom0 graphical interface, you will be greeted by the reliant.service script. It is currently debated whether it is necessary, since it essentially repeats the security checks performed by the initramfs stage. However, except for the second hash pass on drives it does not take much time.

There are several utility scripts inside userspace, namely reliant-seal, reliant-unseal and reliant-status. These operate on Shufflecake volumes. 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 number you enter may leak information via side channels3.

Installation

  1. Install a regular instance of QubesOS on your drive, making sure to leave most of the space for the Shufflecake partition
  2. Boot into the Qubes system and create a standalone "bootstrap" qube. Template doesn't matter. You will use it for installation and updates of Reliant.
  3. Inside the bootstrap qube, install git and docker.
  4. Copy /lib/modules/YOUR_KERNEL/build into the bootstrap qube.
  5. Enter the repository and cd qubes-sflc.
  6. Build Shufflecake targeted for dom0 inside a Docker container.
  7. Copy the Shufflecake binary into /usr/bin and dm-sflc into /lib/modules/YOUR_KERNEL/extra inside dom0.
  8. Copy the files under skel into your dom0 filesystem. In some cases you must rename directories, such as YOUR_USERNAME.
  9. Edit the files to accurately reflect your drive layout.
  10. Edit /etc/fstab to mount /boot and /boot/efi as ro,noatime,nodiratime and / as rw,noatime,nodiratime.
  11. sudo systemctl daemon-reload
  12. sudo sh -c "echo '. /etc/default/grub.systemd-volatile-overlay' >> /etc/default/grub"
  13. sudo dracut --force --regenerate-all
  14. sudo grub-mkconfig -o /boot/grub2/grub.cfg
  15. Now, reboot the system in volatile mode. Make Reliant skip the mount, since it has nothing to mount yet. Take note of the hashes.
  16. Format your Shufflecake partition, creating N ext4 volumes.
  17. (optional) Create an empty domains.xml under each volume.
  18. The installation is complete. You may now reboot and use your system as detailed below.

Usage

Upgrading or modifying the base system

  1. Boot the system in maintenance mode by removing systemd.volatile=overlay from the GRUB command line
  2. Proceed with the necessary modifications
  3. 'dissect' qubes.xml manually into the following files, all under /var/lib/qubes
  • Ensure revisions_to_keep="0" and rw="False" everywhere.
  • Ensure you keep the indentation of original qubes.xml. surgeon-suture is an extermely primitive script which simply concatenates files and has no way of ensuring the validiting of XML.

defaults.xml

    <pool name="varlibqubes"...>
    <pool name="linux-kernel"...>
    <pool name="volatile" dir_path="/run/volatile" driver="file" revisions_to_keep="0"/>
  </pools>
  <properties>
    <property name="...">
  </properties>
  <domains>

This is an example of defaults.xml extracted from qubes.xml. Make sure you add the <pool name="volatile"> line which is necessary to avoid overflowing the OverlayFS.

domains.xml

This is a simple list of domains (qubes), present both in /var/lib/qubes and in Shufflecake volumes. However, several modifications are needed for the normal functionality of system qubes under OverlayFS. Make sure you do not include the </domains></qubes> part.

  1. As mentioned above, set rw="False" and revisions_to_keep="0" everywhere. nano or vim are suggested for quick editing inside dom0.
  2. Find the entries for sys-net and sys-whonix (if you asked to create these during install). There, change <volume name="private" pool="varlibqubes" vid="appvms/sys-net/private" to <volume name="private" pool="varlibqubes" vid="appvms/sys-net/private-rw" and similarly for sys-whonix.
  3. Change all instances of <volume name="volatile" pool="varlibqubes" to <volume name="volatile" pool="volatile". This makes use of the /run/volatile pool created by Reliant. Otherwise, the OverlayFS on root will be overwhelmed due to large volatile.img-s created by system qubes.

This is only for the system domains.xml. In Shufflecake domains.xml, these modifications are not needed. But you need to manually extract each qube definition from qubes.xml, put into the domains.xml of the relevant pool and randomize the numerical qube ID. More details in Creating new deniable qubes.

Running a deniable qube

Unfortunately, you cannot simply run a deniable qube from the app menu like you usually would. This is both due to additional security measures and due to how shortcuts work in QubesOS.

  1. reliant-unseal the target volume
  2. Now you can use any qube from the volume like usual.
  3. After you no longer need them, it's recommended to reliant-seal the volume immediately.

Creating new deniable qubes

  1. reliant-unseal the target volume
  2. Create a new qube like usual, but make sure to select sflc_X_X as the storage pool
  3. Extract the qube defintion from qubes.xml into the target volume's domains.xml, making sure to randomize the numerical qube ID.

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 QUBE add ..., 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.

Known issues

  • All known issues of Shufflecake and QubesOS QubesOS

  • Installation and usage is the opposite of user-friendly. This will be gradually adjusted as the project is developed, and suggestions are always welcome.

  • 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 first (delaying surgeon-suture until root is available) 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.

  • 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. This may be combined with manual verification for Anti Evil Maid, still. Otherwise, the partition could be stored on a USB drive.

  • 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.

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

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

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

  • 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. In the future, an integration with OpenSnitch could be considered as an additional feature.

  • Side channels, see 3.

  • Update checks may also leak information, better to disable them except for dom0.

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

This happens due to how QubesOS handles overwriting 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. For now, the possible solutions are:

  1. Create a standalone qube

To template or not to template?

  • Templates WILL disclose your system packages to an adversary.
  • On the other hand, standalone qubes - long discussion ahead.

In development

  • Automated surgeon-dissect script which extracts the qubes from qubes.xml with all the necessary modifications and precautions.
  • reliant-bootstrap for automated system setup
  • reliant-shred for eliminating data leaks volume-wise

  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. Side channels can and will leak information about your system. Make sure you keep your phone away from your workplace, preferable turned off unless it has a secure operating system like GrapheneOS installed. Even in this case, it's best to hard switch-off the camera and microphone. Your keystrokes can absolutely be recorded and used to infer data about your passwords and the number of volumes. ↩︎