Qubes OS live mode. dom0 in RAM. Non-persistent Boot. Protection against forensics. Tails mode. Hardening dom0

This guide adds two new options to the GRUB menu for safely launching live modes. You will get two ways to launch dom0 in RAM for protection against forensics:

  1. Qubes Overlay-Live Mode
  2. Qubes Zram-Live Mode

:shield: Both modes significantly increase dom0 security - the default root runs in read‑only mode, and operations take place within a hardened copy of the system. This also works great for experiments in Qubes or for beginners who want to learn without fear of breaking anything - all changes disappear after a reboot. It will extend the lifespan of your SSD.

Overlay‑Live Mode is very fast and maximally (paranoid) secure.
Zram‑Live Mode starts slowly, uses more CPU power, but saves RAM dramatically (~ 2× compared to Overlay‑Live).

This guide solves the old problem of implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics.

You will need: at least 16 GB RAM for Zram-Live Mode and at least 24 GB RAM for Overlay-Live Mode for comfortably launching several qubes in live mode.

:loudspeaker: Don’t worry about installing these modes - doing so won’t affect the default Qubes boot. I created this scenario to be as safe as possible and isolated from the default Qubes boot. The new GRUB options are added to /etc/grub.d/40_custom (so it don’t modify your /etc/default/grub), and the new dracut modules only run when the additional GRUB parameters are supplied (these parameters aren’t present in the default boot). Default Qubes boot won’t be affected even if the new modes encounter problems in the future, such as after dom0 updates.

But Make a backup before you start working :slightly_smiling_face:

:green_circle: If you’re worried about entering something incorrectly, use the fully automated live‑mode setup via a simple script

Step 1. Make New Directorys for Dracut Automation Modules:

sudo mkdir /usr/lib/dracut/modules.d/90ramboot
sudo mkdir /usr/lib/dracut/modules.d/90overlayfs-root

Step 2. Make Two Dracut Script Files module-setup.sh:

sudo touch /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
chmod 755 /usr/lib/dracut/modules.d/90ramboot/module-setup.sh
sudo nano /usr/lib/dracut/modules.d/90ramboot/module-setup.sh

add:

#!/usr/bin/bash
check() {
return 0
}
depends() {
return 0
}
install() {
inst_simple "$moddir/zram-mount.sh"
inst_hook cleanup 00 "$moddir/zram-mount.sh"
}

Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.

sudo touch /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh
sudo nano /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh

add:

#!/bin/bash

check() {
    # do not add modules if the kernel does not have overlayfs support
    [ -d /lib/modules/$kernel/kernel/fs/overlayfs ] || return 1
}

depends() {
    # We do not depend on any modules - just some root
    return 0
}

# called by dracut
installkernel() {
    hostonly='' instmods overlay
}

install() {
    inst_hook pre-pivot 10 "$moddir/overlay-mount.sh"
}

Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.

Step 3. Make New Dracut Script File overlay-mount.sh:

sudo touch /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh
sudo nano /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh

add:

#!/bin/sh
. /lib/dracut-lib.sh

if ! getargbool 0 rootovl ; then
    return
fi

modprobe overlay
mount -o remount,nolock,noatime $NEWROOT
mkdir -p /live/image
mount --bind $NEWROOT /live/image
umount $NEWROOT
mkdir /cow
mount -n -t tmpfs -o mode=0755,size=70%,nr_inodes=500k,noexec,nodev,nosuid,noatime,nodiratime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT
mkdir -p $NEWROOT/live/cow
mkdir -p $NEWROOT/live/image
mount --bind /cow/rw $NEWROOT/live/cow
umount /cow
mount --bind /live/image $NEWROOT/live/image
umount /live/image

Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.

Step 4. Creating a script to automatically create zram-mount.sh and edit /etc/grub.d/40_custom.

sudo touch /usr/local/bin/grub-custom.sh
sudo chmod 755 /usr/local/bin/grub-custom.sh
sudo nano /usr/local/bin/grub-custom.sh

Add:

#!/bin/bash

#BOOT_UUID
BOOT_UUID=$(findmnt -n -o UUID /boot 2>/dev/null || echo "AUTO_BOOT_NOT_FOUND")
if [ "$BOOT_UUID" = "AUTO_BOOT_NOT_FOUND" ]; then
    BOOT_UUID=$(blkid -s UUID -o value -d $(findmnt -n -o SOURCE /boot 2>/dev/null))
fi

# LUKS_UUID
LUKS_DEVICE=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1 || echo "")
if [ -n "$LUKS_DEVICE" ]; then
    LUKS_UUID=$(sudo cryptsetup luksUUID "$LUKS_DEVICE" 2>/dev/null)
else
    LUKS_UUID="AUTO_LUKS_NOT_FOUND"
fi

# Latest XEN_PATH 
XEN_PATH=$(ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz")

# Latest kernel/initramfs
LATEST_KERNEL=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
LATEST_INITRAMFS=$(echo "/initramfs-${LATEST_KERNEL#vmlinuz-}.img")

# Max memory dom0
DOM0_MAX_KB=$(xenstore-read /local/domain/0/memory/hotplug-max 2>/dev/null \
           || xenstore-read /local/domain/0/memory/static-max 2>/dev/null \
           || echo 0)

if [ "$DOM0_MAX_KB" -gt 0 ]; then
    DOM0_MAX_MB=$(( (DOM0_MAX_KB * 70) / (1024 * 100) ))
    DOM0_MAX_GB=$(( DOM0_MAX_MB / 1024 ))
    DOM0_MAX_GBG="${DOM0_MAX_GB}G"
    DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"
else
    DOM0_MAX_RAM="dom0_mem=max:10240M"
    DOM0_MAX_GB="10"
fi

cat > /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh << EOF
#!/bin/sh

. /lib/dracut-lib.sh

if ! getargbool 0 rootzram ; then
    return
fi

mkdir /mnt
umount /sysroot
mount -o ro /dev/mapper/qubes_dom0-root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0
EOF

chmod 755 /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh

cat > /etc/dracut.conf.d/ramboot.conf << 'EOF'
add_drivers+=" zram "
add_dracutmodules+=" ramboot "
EOF

# Update INITRAMFS
dracut --verbose --force

cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0

menuentry 'Qubes Overlay-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
	insmod part_gpt
	insmod ext2
	search --no-floppy --fs-uuid --set=root $BOOT_UUID
	echo 'Loading Xen ...'
	if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
	    xen_rm_opts=
	else
	    xen_rm_opts="no-real-mode edd=off"
	fi
	insmod multiboot2
	multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
	echo 'Loading Linux $LATEST_KERNEL ...'
	module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootovl quiet usbcore.authorized_default=0
	echo 'Loading initial ramdisk ...'
	insmod multiboot2
	module2 --nounzip $LATEST_INITRAMFS
}

menuentry 'Qubes Zram-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
	insmod part_gpt
	insmod ext2
	search --no-floppy --fs-uuid --set=root $BOOT_UUID
	echo 'Loading Xen ...'
	if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
	    xen_rm_opts=
	else
	    xen_rm_opts="no-real-mode edd=off"
	fi
	insmod multiboot2
	multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
	echo 'Loading Linux $LATEST_KERNEL ...'
	module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootzram quiet usbcore.authorized_default=0
	echo 'Loading initial ramdisk ...'
	insmod multiboot2
	module2 --nounzip $LATEST_INITRAMFS
}
EOF

# Update GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg

# Disable Dom0 Swap:
sed -i '/[[:space:]]\+swap[[:space:]]\+/s/^/#/' "/etc/fstab"
swapoff -a

Press Ctrl + O to save file.
Press Ctrl + X to exit nano editor.

Run grub-custom.sh

sudo /usr/local/bin/grub-custom.sh

If you want to add your custom GRUB parameters for live modes, do so in /etc/grub.d/40_custom

Step 5. Clone dangerous qubes (dvm‑template, appVMs) into a pool in dom0

:exclamation:Since only dom0 runs in live mode, you have to start the VMs from the pool in dom0. You can use the default pool varlibqubes or create a new pool.

In the Qube Manager click clone qube, then in Advanced select a pool in dom0 (varlibqubes or your new pool. not vm‑pool). If you have a lot of memory, you can run all appVMs (sys, dvm, appVM) in live mode.

:exclamation:Don’t add templates to varlibqubes - templates don’t retain any session data in an appVM (that’s the point of Qubes’ isolation). So, in live mode it’s sufficient that only the private and volatile storages are active. You can inspect all template metadata yourself and verify that none of them contain artifacts from sessions in appVM.

Restart Qubes OS and Test Qubes live modes :wink:

Zram-Live Mode takes longer to start (about 30–40 seconds more).

:exclamation: You can update templates in live modes, but update dom0 in persistent mode! .

Now if dom0 updates kernels or If you’ve changed dom_mem=max in GRUB, just run sudo /usr/local/bin/grub-custom.sh and the live modes will work with the new kernels, and max memory settings.

Remember that the data will be erased only after a reboot of Qubes OS (like Tails).
If you create a qube in the vm‑pool while in live mode, this qube won’t be saved in the Qube Manager after a reboot.
You can make backups in live mode (I’ve done it many times using a Overlay-Live Mode).

:flashlight:
You can add a “System Monitor” widget to the XFCE panel and configure it to run command findmnt -n -o SOURCE /. This widget will display which mode you’re currently in:

You can also use this terminal theme so can see which mode you’re currently in:
Click CTRL + H in thunar of dom0 and add this code into .bashrc instead of the default code:

# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
	. /etc/bashrc
fi

# User specific environment
if ! [[ "$PATH" =~ "$HOME/.local/bin:$HOME/bin:" ]]
then
    PATH="$HOME/.local/bin:$HOME/bin:$PATH"
fi
export PATH

###########################


export VIRTUAL_ENV_DISABLE_PROMPT=true


__qubes_update_prompt_data() {
	local RETVAL=$?

	__qubes_venv=''
	[[ -n "$VIRTUAL_ENV" ]] && __qubes_venv=$(basename "$VIRTUAL_ENV")

	__qubes_git=''
	__qubes_git_color=$(tput setaf 10)  # clean
	local git_branch=$(git --no-optional-locks rev-parse --abbrev-ref HEAD 2> /dev/null)
	if [[ -n "$git_branch" ]]; then
		local git_status=$(git --no-optional-locks status --porcelain 2> /dev/null | tail -n 1)
		[[ -n "$git_status" ]] && __qubes_git_color=$(tput setaf 11)  # dirty
		__qubes_git="‹${git_branch}›"
	fi

	__qubes_prompt_symbol_color=$(tput sgr0)
	[[ "$RETVAL" -ne 0 ]] && __qubes_prompt_symbol_color=$(tput setaf 1)


	return $RETVAL  # to preserve retcode
}


if [[ -n "$git_branch" ]]; then
	PROMPT_COMMAND="$PROMPT_COMMAND; __qubes_update_prompt_data"
else
	PROMPT_COMMAND="__qubes_update_prompt_data"
fi


PS1=''
PS1+='\[$(tput setaf 7)\]$(echo -ne $__qubes_venv)\[$(tput sgr0)\]'
PS1+='\[$(tput setaf 14)\]\u'
PS1+='\[$(tput setaf 15)\] đź‘‘ '
PS1+='\[$(tput setaf 9)\]\h'
PS1+=" $(findmnt -n -o SOURCE /)"
PS1+='\[$(tput setaf 15)\]:'
PS1+='\[$(tput setaf 7)\]\w '
PS1+='\[$(echo -ne $__qubes_git_color)\]$(echo -ne $__qubes_git)\[$(tput sgr0)\] '
PS1+='\[$(tput setaf 8)\]\[$([[ -n "$QUBES_THEME_SHOW_TIME" ]] && echo -n "[\t]")\]\[$(tput sgr0)\]'
PS1+='\[$(tput sgr0)\]\n'
PS1+='\[$(echo -ne $__qubes_prompt_symbol_color)\]\$\[$(tput sgr0)\] '


If you have limited memory, use this guide: Really disposable (RAM based) qubes

:sunglasses: Also use these guides to hide traces of VPN/Tor connections and for external system shutdown and memory clearing for maximum paranoid security:
Installation of AmneziaVPN: effective circumvention of internet blocks via DPI
USB Kill Switch for Qubes OS

11 Likes

Simple script for automatically creating live‑modes.

This script performs all actions automatically; you just need to run it.
Run this script with sudo (for example, sudo ./live.sh).

#!/bin/bash

# Qubes Dom0 Live Boot Automation Script
# ⚠️ Make backup before running! Run as root: sudo ./live.sh

set -e  # Exit on any error

#BOOT_UUID
BOOT_UUID=$(findmnt -n -o UUID /boot 2>/dev/null || echo "AUTO_BOOT_NOT_FOUND")
if [ "$BOOT_UUID" = "AUTO_BOOT_NOT_FOUND" ]; then
    BOOT_UUID=$(blkid -s UUID -o value -d $(findmnt -n -o SOURCE /boot 2>/dev/null))
fi

# LUKS_UUID
LUKS_DEVICE=$(blkid -t TYPE="crypto_LUKS" -o device 2>/dev/null | head -n1 || echo "")
if [ -n "$LUKS_DEVICE" ]; then
    LUKS_UUID=$(sudo cryptsetup luksUUID "$LUKS_DEVICE" 2>/dev/null)
else
    LUKS_UUID="AUTO_LUKS_NOT_FOUND"
fi

# Latest XEN_PATH 
XEN_PATH=$(ls /boot/xen*.gz 2>/dev/null | sort -V | tail -1 | xargs basename 2>/dev/null || echo "/xen-4.19.4.gz")

# Latest kernel/initramfs
LATEST_KERNEL=$(ls /boot/vmlinuz-*qubes*.x86_64 2>/dev/null | grep -E 'qubes\.fc[0-9]+' | sort -V | tail -1 | xargs basename)
LATEST_INITRAMFS=$(echo "/initramfs-${LATEST_KERNEL#vmlinuz-}.img")

# Max memory dom0
DOM0_MAX_KB=$(xenstore-read /local/domain/0/memory/hotplug-max 2>/dev/null \
           || xenstore-read /local/domain/0/memory/static-max 2>/dev/null \
           || echo 0)

if [ "$DOM0_MAX_KB" -gt 0 ]; then
    DOM0_MAX_MB=$(( (DOM0_MAX_KB * 70) / (1024 * 100) ))
    DOM0_MAX_GB=$(( DOM0_MAX_MB / 1024 ))
    DOM0_MAX_GBG="${DOM0_MAX_GB}G"
    DOM0_MAX_RAM="dom0_mem=max:${DOM0_MAX_MB}M"
else
    DOM0_MAX_RAM="dom0_mem=max:10240M"
    DOM0_MAX_GB="10"
fi

echo "=== Qubes Dom0 Live Boot Setup ==="

# Disable Dom0 Swap:
sed -i '/[[:space:]]\+swap[[:space:]]\+/s/^/#/' "/etc/fstab"
swapoff -a

# Create Dracut directories
echo "Creating Dracut module directories..."
mkdir -p /usr/lib/dracut/modules.d/90ramboot
mkdir -p /usr/lib/dracut/modules.d/90overlayfs-root

# Create 90ramboot/module-setup.sh
echo "Creating 90ramboot module-setup.sh..."
cat > /usr/lib/dracut/modules.d/90ramboot/module-setup.sh << 'EOF'
#!/usr/bin/bash
check() {
    return 0
}
depends() {
    return 0
}
install() {
    inst_simple "$moddir/zram-mount.sh"
    inst_hook cleanup 00 "$moddir/zram-mount.sh"
}
EOF

chmod 755 /usr/lib/dracut/modules.d/90ramboot/module-setup.sh

# Create 90overlayfs-root/module-setup.sh
echo "Creating 90overlayfs-root module-setup.sh..."
cat > /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh << 'EOF'
#!/bin/bash

check() {
    [ -d /lib/modules/$kernel/kernel/fs/overlayfs ] || return 1
}

depends() {
    return 0
}

installkernel() {
    hostonly='' instmods overlay
}

install() {
    inst_hook pre-pivot 10 "$moddir/overlay-mount.sh"
}
EOF

chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/module-setup.sh

# Create overlay-mount.sh
echo "Creating overlay-mount.sh..."
cat > /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh << 'EOF'
#!/bin/sh
. /lib/dracut-lib.sh

if ! getargbool 0 rootovl ; then
    return
fi

modprobe overlay
mount -o remount,nolock,noatime $NEWROOT
mkdir -p /live/image
mount --bind $NEWROOT /live/image
umount $NEWROOT
mkdir /cow
mount -n -t tmpfs -o mode=0755,size=70%,nr_inodes=500k,noexec,nodev,nosuid,noatime,nodiratime tmpfs /cow
mkdir /cow/work /cow/rw
mount -t overlay -o noatime,nodiratime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT
mkdir -p $NEWROOT/live/cow
mkdir -p $NEWROOT/live/image
mount --bind /cow/rw $NEWROOT/live/cow
umount /cow
mount --bind /live/image $NEWROOT/live/image
umount /live/image
EOF

chmod 755 /usr/lib/dracut/modules.d/90overlayfs-root/overlay-mount.sh

cat > /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh << EOF
#!/bin/sh

. /lib/dracut-lib.sh

if ! getargbool 0 rootzram ; then
    return
fi

mkdir /mnt
umount /sysroot
mount -o ro /dev/mapper/qubes_dom0-root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0
EOF

chmod 755 /usr/lib/dracut/modules.d/90ramboot/zram-mount.sh

# Create dracut.conf
echo "Creating dracut configuration..."
cat > /etc/dracut.conf.d/ramboot.conf << 'EOF'
add_drivers+=" zram "
add_dracutmodules+=" ramboot "
EOF

# Update INITRAMFS
dracut --verbose --force

# Create GRUB custom
echo "Creating GRUB custom ..."

cat > /etc/grub.d/40_custom << EOF
#!/usr/bin/sh
exec tail -n +3 \$0

menuentry 'Qubes Overlay-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
	insmod part_gpt
	insmod ext2
	search --no-floppy --fs-uuid --set=root $BOOT_UUID
	echo 'Loading Xen ...'
	if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
	    xen_rm_opts=
	else
	    xen_rm_opts="no-real-mode edd=off"
	fi
	insmod multiboot2
	multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
	echo 'Loading Linux $LATEST_KERNEL ...'
	module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootovl quiet usbcore.authorized_default=0
	echo 'Loading initial ramdisk ...'
	insmod multiboot2
	module2 --nounzip $LATEST_INITRAMFS
}

menuentry 'Qubes Zram-Live Mode (latest kernel)' --class qubes --class gnu-linux --class gnu --class os --class xen \$menuentry_id_option 'xen-gnulinux-simple-/dev/mapper/qubes_dom0-root' {
	insmod part_gpt
	insmod ext2
	search --no-floppy --fs-uuid --set=root $BOOT_UUID
	echo 'Loading Xen ...'
	if [ "\$grub_platform" = "pc" -o "\$grub_platform" = "" ]; then
	    xen_rm_opts=
	else
	    xen_rm_opts="no-real-mode edd=off"
	fi
	insmod multiboot2
	multiboot2 /$XEN_PATH placeholder console=none dom0_mem=min:1024M $DOM0_MAX_RAM ucode=scan smt=off gnttab_max_frames=2048 gnttab_max_maptrack_frames=4096 \${xen_rm_opts}
	echo 'Loading Linux $LATEST_KERNEL ...'
	module2 /$LATEST_KERNEL placeholder root=/dev/mapper/qubes_dom0-root ro rd.luks.uuid=$LUKS_UUID rd.lvm.lv=qubes_dom0/root rd.lvm.lv=qubes_dom0/swap plymouth.ignore-serial-consoles rhgb rootzram quiet usbcore.authorized_default=0
	echo 'Loading initial ramdisk ...'
	insmod multiboot2
	module2 --nounzip $LATEST_INITRAMFS
}
EOF

chmod 755  /etc/grub.d/40_custom

# Update GRUB
grub2-mkconfig -o /boot/grub2/grub.cfg

echo "âś… ALL STEPS COMPLETED SUCCESSFULLY!"

Now copy/create the necessary qubes (for example, dvm) into varlibqubes pool and start live mode after reboot :wink:

2 Likes

Update: Hardening dom0 live mode!

:sunglasses: Now launching dom0 in live mode provides additional protection.
New flags have been added to the overlay‑tmpfs live mode, enhancing security and slightly improving performance by minimizing data.:

nr_inodes=500k (tmpfs) – limits the number of inodes (file descriptors) that the tmpfs can create to 500 000. Protection of temporary‑file DoS and fork‑bomb.
noexec (tmpfs) – disables execution of binaries located on the mounted filesystem. Even if a malicious script is placed there, it cannot be run.
nodev (tmpfs) – prevents the interpretation of device special files (e.g., /dev/null) inside the mount, blocking attacks that rely on creating device nodes.
nosuid (tmpfs) – ignores set‑UID and set‑GID bits on files, so privileged executables cannot gain elevated rights.
size=70% (tmpfs) – restricts the total memory that the tmpfs may occupy to 95 percent of the available RAM, ensuring the temporary filesystem never exhausts all system memory.
noatime,nodiratime (tmpfs, overlay) – disables updating a file’s “last‑access” timestamp on each read, eliminating unnecessary write operations. It reduces frequent metadata writes, saving I/O and RAM.
volatile (overlay) – improves performance by completely disabling sync/fsync operations. It reduces frequent metadata writes, saving I/O and RAM.

It was added to these lines:

mount -n -t tmpfs -o mode=0755,size=70%,nr_inodes=500k,noexec,nodev,nosuid,relatime tmpfs /cow
mount -t overlay -o noatime,volatile,lowerdir=/live/image,upperdir=/cow/rw,workdir=/cow/work,default_permissions,relatime overlay $NEWROOT

It won’t affect your daily work. My tests show that everything works perfectly, apVMs launch correctly and apps install fine in the appVMs.

1 Like

This doesn’t look very robust, have you tried using something like separate grub menu entries with different settings? Alternatively remove wildcards from your script to detect one character, not the whole string, but I’m not sure if this works with the way it receives input

This is the simplest way. I haven’t worked with grub. I’ve been studying the topic of porting grub‑live from Kicksecure, adding these two modules, but I couldn’t get it to work implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics · Issue #4982 · QubesOS/qubes-issues · GitHub
So any help improving the launch of live modes in grub is welcome

1 Like

The guide has been updated and heavily reworked! Now the live‑mode boot options are added to the GRUB menu.

I studied the GRUB documentation and found that it isn’t as complicated as I thought.

Launching live modes from GRUB is simpler and safer than starting them via initramfs!
I removed the script that creates the boot‑mode menu in dracut (Enter Boot Mode / Boot to RAM?) that was created by a forum user from an old topic. It was inconvenient and unsafe - for example, if the first letter of the password matched the letter used to launch the live mode, the live mode would always start automatically, and user would have to edit GRUB each time the system boots.

It completely solves the problem: implement live boot by porting grub-live to Qubes - amnesia / non-persistent boot / anti-forensics

Kernel parameter rootzram is added (activated when Zram‑Live is added to GRUB)

if ! getargbool 0 rootzram ; then
    return
fi

Overlay‑Live Mode have rootovl parameter (it’s used in native Kicksecure/Whonix to launch Live Mode).

if ! getargbool 0 rootovl ; then
    return
fi

A simple script has been created to automatically update /etc/grub.d/40_custom file - it adds new menu entries to GRUB and upgrades the kernels to the latest version.

The script for launching Zram‑Live dracut module has been changed - now this line will automatically insert the maximum dom0 memory:
old echo 10G > /sys/block/zram0/disksize > new echo $DOM0_MAX_GBG > /sys/block/zram0/disksize

Any suggestions for even better optimization and automation are welcome.

3 Likes

Zram versus the hardening used by Kicksecure or Whonix – does it provide the same security as running dom0 in Qubes with persistent mode?
If dom0 is secure because it is completely offline and resides within Qubes’ architecture, does that make it ultra‑secure, right?
If we run dom0 the way it normally works but mount it in RAM, will it become insecure just because it is 100 % in RAM? That doesn’t make sense!
Using Whonix’s hardening based on Overlay‑Live Mode inside operating systems that don’t share Qubes’ architecture—such as Tails and Whonix—clearly makes sense and is even mandatory to achieve more security than the Zram mode. However, mounting dom0 in RAM will not change Qubes’ security; it merely runs 100 % in memory, and everything that runs inside dom0 is “anti‑forensic” just like Tails and Whonix failsafe!Overlay‑Live Mode makes dom0 more secure, but is it really necessary? What proof exists that using dom0 in Zram mode would make Qubes insecure? Zram is unquestionably insecure for normal systems, but for dom0 in Qubes it seems that it does not lose any security by operating entirely in RAM…
Another point: if an attacker compromises a dom0 that is amnesic thanks to Zram or Overlay‑Live Mode, the original dom0 still resides on one of the SSD partitions. Thus, if the attacker has full control over the system, they could mount that original dom0 partition on the SSD and install something permanent. Then, when the user runs Qubes in persistent mode or in the next session with Qubes in live mode the attacker would regain access perhaps!
In Overlay‑Live Mode, does the hardening employed by Kicksecure prevent the attacker from mounting the original dom0 partition on the SSD and Trojan‑injecting it for later access in subsequent live or persistent sessions? In Zram mode it seems that it does, but what about Overlay‑Live Mode? If in Overlay‑Live Mode the attacker cannot modify the live dom0, will they instead modify the SSD partition where dom0 resides? All of this appears possible to me, which is why Zram would only be insecure if used on regular OSes such as Tails or Whonix that are monolithic…

Of course, zram won’t break the basic isolation of dom0. I’ve written a theory - copying the root filesystem into zram has many more potential vulnerabilities in theory than the battle‑tested overlay. The theory is useful in case someone decides to experiment and run something questionable in dom0. In fact, zram is dangerous for dom0 not because of viruses or hackers, but because it can break the entire system - for example, a random updating of a dracut or grub could corrupt the base system (the system simply won’t boot after a reboot). A live mode is a good test environment, for instance. It’s handy for studying advanced guides from the Qubes forum (so that a reboot wipes out any possible damage if something goes wrong), but some guides won’t work on zram mode; certain guides can break the system after a reboot - that happened to me and my friends once.
Therefore, my comment is based not only on theory but also on experience. With overlay, breaking the system is virtually impossible because you’re working in the upper layer. I can’t even imagine what action could cause a failure when the base system is read‑only in overlay. So, in theory, if we take the default persistent dom0, a dom0 running in zram, and a dom0 running in overlay, the overlay mode would be the safest. But yes, it does require more physical memory. Then comes the zram mode, and then default dom0. Therefore, zram mode is still safer than default dom0 (it’s easiest to break something in default dom0)

The guide has been updated! Now Zram‑Mode also offers very high security and protection!

The root filesystem is now mounted read‑only (-o ro).

mount -o ro /dev/mapper/qubes_dom0-root /mnt

Additional parameters have been added for hardening and to reduce RAM/CPU load:

mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot

nodev – prevents the interpretation of device special files (e.g., /dev/null) inside the mount, blocking attacks that rely on creating device nodes.
nosuid – ignores set‑UID and set‑GID bits on files, so privileged executables cannot gain elevated rights.
noatime,nodiratime – reduces frequent metadata writes, saving I/O and RAM/CPU.

All code now:

#!/bin/sh

. /lib/dracut-lib.sh

if ! getargbool 0 rootzram ; then
    return
fi

mkdir /mnt
umount /sysroot
mount -o ro /dev/mapper/qubes_dom0-root /mnt
modprobe zram
echo $DOM0_MAX_GBG > /sys/block/zram0/disksize
/mnt/usr/sbin/mkfs.ext2 /dev/zram0
mount -o nodev,nosuid,noatime,nodiratime /dev/zram0 /sysroot
cp -a /mnt/* /sysroot
exit 0

:shield: Now both live modes have very high security!

2 Likes

It’s an amazing! I’m currently using the old version (which asks a question after entering the password) – how can I change it to the new version with GRUB menu?

@newqube Remove these modules:

sudo rm /usr/lib/dracut/modules.d/01ramboot
sudo rm /usr/lib/dracut/modules.d/90overlayfs-root

and start the guide again

2 Likes

Thanks!

The guide has been updated!

Now this scenario automatically sets the optimal amount of RAM for dom0 in live modes. My tests showed that 70 % of RAM is the best balance for performance and security.

Now you no longer need to edit /etc/default/grub - the memory size is changed only in custom GRUB configurations for dom0, and /etc/default/grub is never edited!
This makes the live‑mode launch script very safe, because you won’t break the default Qubes boot!

A command has also been added to automatically edit /etc/fstab to remove swap. The script will now prepend a # to any line containing the word “swap”!

Now this script will perform all actions automatically - just run it and it will create dracut modules, disable swap, custom GRUB entries, and set 70 % of memory for dom0‑live!

Additionally, Zram module has been moved from 01 to 90 - since it starts later, there’s less chance that other modules will interfere with this script.

2 Likes