A Laptop Kill Cord for QubesOS

This post will describe how to use BusKill as a dead man switch to trigger your laptop to self-destruct if it's physically separated from you. This guide is specific to QubesOS users.

What if someone literally steals your laptop while you're working with classified information inside a Whonix DispVM? They'd also be able to recover data from previous DispVMs--as Disposable VM's rootfs virtual files are not securely shredded after your DispVM is destroyed.

QubesOS Logo
QubesOS: A reasonably secure OS

This is part one of a two-part series. For part two, see Disarm BusKill in QubesOS (2/2)

  1. A Laptop Kill Cord for QubesOS (1/2)
  2. Disarm BusKill in QubesOS (2/2)

Are you a security researcher, journalist, or intelligence operative that works in QubesOS--exploiting Qubes' brilliant security-through-compartimentalizatio to keep your data safe? Do you make use of Whonix Disposable VMs for your work? Great! This post is for you.

I'm sure your QubesOS laptop has Full Disk Encryption and you're using a strong passphrase. But what if someone literally steals your laptop while you're working with classified information inside a Whonix DispVM? Not only will they get access to all of your AppVM's private data and the currently-running Whonix DispVM's data, but there's a high chance they'd be able to recover data from previous DispVMs--as Disposable VM's rootfs virtual files (volatile.img) are not securely shredded after your DispVM is destroyed by Qubes!

Let's say you're a journalist, activist, whistleblower, or a human rights worker in an oppressive regime. Or an intelligence operative behind enemy lines doing research or preparing a top-secret document behind a locked door. What do you do to protect your data, sources, or assets when the secret police suddenly batter down your door? How quickly can you actually act to shutdown your laptop and shred your RAM and/or FDE encryption keys?


BusKill utilizes a magnetic trip-wire that tethers your body to your laptop. If you suddenly jump to your feet or fall off your chair (in response to the battering ram crashing through your door) or your laptop is ripped off your table by a group of armed thugs, the data bus' magnetic connection will be severed. This event causes a configurable trigger to execute.

The BusKill trigger can be anything from:

  1. locking your screen or
  2. shutting down the computer or
  3. initiating a self-destruct sequence

This post will describe how to setup such a system in QubesOS with BusKill

Disclaimer

This guide contains experimental files, commands, and software. The information contained in this article may or may not lead to corruption or total permanent deletion of some or all of your data. We've done our best to carefully guide the user so they know the risks of each BusKill trigger, but we cannot be responsible for any data loss that has occurred as a result of following this guide.

The contents of this guide is provided openly and is licensed under the CC-BY-SA license. The software included in this guide is licensed under the GNU GPLv3 license. All content here is consistent with the limitations of liabilities outlined in its respective licenses.

We highly recommend that any experiments with the scripts included in this article are used exclusively on a disposable machine containing no valuable data.

If data loss is a concern for you, then leave now and do not proceed with following this guide. You have been warned.

Release Note

Also be aware that, due to the risks outlined above, BusKill will not be released with this "self-destruct" trigger.

If you purchase a BusKill cable, it will only ship with non-destructive triggers that lock the screen or shutdown the computer. Advanced users can follow guides to add additional destructive triggers, such as the one described in this post, but they should do so at their own risk--taking carefully into consideration all of the warnings outlined above and throughout this article.

Again, if you buy a BusKill cable, the worst that can happen is your computer will abruptly shutdown.

Assumptions

This guide necessarily makes several assumptions outlined below.

sys-usb

In this guide, we assume that your QubesOS install has a USB-Qube named 'sys-usb' for handling USB events on behalf of dom0.

If you decided to combine your USB and networking Qubes at install time, then replace all references in this guide for 'sys-usb' to 'sys-net'.

If you decided to run your 'sys-usb' VM as a DispoableVM at install time, then replace all references in this guide for 'sys-usb' its Disposable TemplateVM (eg 'fedora-36-dvm').

..And if you chose not to isolate your USB devices, then may god help you.

Udev Device Matching

BusKill in Linux uses udev to detect when the USB's cable is severed. The exact udev rule that you use in the files below will depend on the drive you choose to use in your BusKill cable.

In this guide, we identify our BusKill-specific drive with the 'ENV{ID_MODEL}=="Micromax_A74"' udev property. You should replace this property with one that matches your BusKill-specific drive.

To determine how to query your USB drive for device-specific identifiers, see Introducing BusKill: A Kill Cord for your Laptop. Note that the `udevadm monitor --environment --udev` command should be run in the 'sys-usb' Qube.

ⓘ Note: If you'd prefer to buy a BusKill cable than make your own, you can buy one fully assembled here.

QubesOS Version

This guide was written for QubesOS v4.0.

[user@dom0 ~]$ cat /etc/redhat-release
Qubes release 4.0 (R4.0)
[user@dom0 ~]$ 

BusKill Files

This section will describe what files should be created and where.

sys-usb

To protect dom0 from USB peripherals (ie: vulnerable drivers parsing a malicious partition table), our udev rule to detect when the BusKill cable's connection is severed will live on 'sys-usb' in the '/etc/udev/rules.d/' directory. But to set this up the Qubes way, we'll put them in '/rw/config/' and use 'rc.local' to put them in-place when 'sys-usb' boots.

First, we create our udev rule file 'sys-usb:/rw/config/buskill.rules'.

Execute the following on your 'sys-usb' Qube:

cat << EOF | sudo tee /rw/config/buskill.rules
################################################################################
# File:    sys-usb:/etc/udev/rules.d/buskill.rules -> /rw/config/buskill.rules
# Purpose: Add buskill rules. For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.lock"
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.softShutdown"
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.hardReboot"
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.selfDestruct+--yes"
EOF
sudo ln -s /rw/config/buskill.rules /etc/udev/rules.d/
sudo udevadm control --reload

The above file defines a number of udev rules that execute distinct triggers when the BusKill device is removed from the system. All are commented-out except the top one, which tells 'sys-usb' to execute the command `/usr/bin/qrexec-client-vm dom0 buskill.lock`.

The `qrexec-client-vm` command allows one VM to execute a command on another VM (assuming dom0 permits it--which we'll setup below). In this case, we're executing the command on 'dom0' herself. And the command we're executing is 'buskill.lock'

Now let's add the following lines to 'sys-usb:/rw/config/rc.local'.

Execute the following on your 'sys-usb' Qube:

grep 'buskill' /rw/config/rc.local || cat << EOF | sudo tee --append /rw/config/rc.local

# Add buskill rules. For more info, see: https://buskill.in/qubes-os/‎
sudo ln -s /rw/config/buskill.rules /etc/udev/rules.d/
sudo udevadm control --reload
EOF
sync

dom0

All of our triggers (lock screen, shutdown, self-destruct, etc) can actually only be executed from dom0. However, because QubesOS makes it intentionally difficult to put new files in dom0, we'll be putting them in '/tmp/buskill/qubes-rpc' in your AppVM, creating a tarball, copying the tarball to dom0, and then extracting it to '/etc/qubes-rpc' inside dom0.

First, let's create a staging dir where we'll be adding files that will be later copied to dom0.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

mkdir -p /tmp/buskill/qubes-rpc/policy

Now let's define our screen lock trigger. This one is the lowest paranoia level. If you accidentally trip BusKill when going to get a cup of coffee, the worst-case with this trigger is that you'll have to unlock your screen again.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/buskill.lock
################################################################################
# File:    dom0:/etc/qubes-rpc/buskill.lock
# Purpose: Locks the screen. For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
DISPLAY=:0 xscreensaver-command -lock
EOF
sudo chmod 0755 /tmp/buskill/qubes-rpc/buskill.lock

Now let's define a soft shutdown trigger. This one's a bit more paranoid. You'll probably loose any unsaved work and anything in a DispVM, but hopefully you won't actually get data corruption.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/buskill.softShutdown
################################################################################
# File:    dom0:/etc/qubes-rpc/buskill.softShutdown
# Purpose: Locks the screen. For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sudo shutdown -h now
EOF
sudo chmod 0755 /tmp/buskill/qubes-rpc/buskill.softShutdown

And now let's define a hard reboot trigger. This effectively yanks the power immediately to reboot as fast as possible. Here you're definitely risking data corruption.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/buskill.hardReboot
################################################################################
# File:    dom0:/etc/qubes-rpc/buskill.hardReboot
# Purpose: Immediate reboot. For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sudo bash -c "echo b > /proc/sysrq-trigger"
EOF
sudo chmod 0755 /tmp/buskill/qubes-rpc/buskill.hardReboot

Finally, the most paranoid and most destructive if you accidentally trigger it is the 'selfDestruct' trigger that will overwrite your LUKS header with random data (including all your key slots containing your FDE master key and all LUKS metadata). In a matter of seconds, this will render all of the data on your machine permanently useless--even against rubber hose cryptanalysis.

Comic describing rubber-hose cryptanalysis: An attacker tells another attacker to beat the victim with a wrench until the victim reveals the passphrase
Not even rubber-hose cryptanalysis is a threat after a BusKill self-destruct trigger is run (source)

If you only need BusKill for locking your screen or triggering a shutdown and don't want to risk a false-positive wiping all your data, feel free to skip creating this script.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

cat > /tmp/buskill/qubes-rpc/buskill.selfDestruct <<'EOWF'
#!/bin/bash
#set -x
################################################################################
# File:    buskill.selfDestruct
# Purpose: Self-destruct trigger script for BusKill Kill Cord
#          For more info, see: https://buskill.in/
# WARNING: THIS IS EXPERIMENTAL SOFTWARE THAT IS DESIGNED TO CAUSE PERMANENT,
#          COMPLETE AND IRREVERSIBLE DATA LOSS!
# Note   : This script will *not* execute unless it's passed the '--yes'
#          argument. Be sure to test this trigger before depending on it!
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-03-11
# Updated: 2020-03-11
# Version: 0.1
################################################################################

############
# SETTINGS #
############

BUSKILL_LOCK='/etc/qubes-rpc/buskill.lock'
[ -f ${BUSKILL_LOCK} ] || echo "ERROR: Unable to find buskill.lock"

WHICH="/usr/bin/which"
CRYPTSETUP=`${WHICH} --skip-alias cryptsetup` || echo "ERROR: Unable to find cryptsetup"
LS=`${WHICH} --skip-alias ls` || echo "ERROR: Unable to find ls"
CP=`${WHICH} --skip-alias cp` || echo "ERROR: Unable to find cp"
MV=`${WHICH} --skip-alias mv` || echo "ERROR: Unable to find mv"
LN=`${WHICH} --skip-alias ln` || echo "ERROR: Unable to find ln"
MKDIR=`${WHICH} --skip-alias mkdir` || echo "ERROR: Unable to find mkdir"
MOUNT=`${WHICH} --skip-alias mount` || echo "ERROR: Unable to find mount"
CAT=`${WHICH} --skip-alias cat` || echo "ERROR: Unable to find cat"
GREP=`${WHICH} --skip-alias grep` || echo "ERROR: Unable to find grep"
ECHO=`${WHICH} --skip-alias echo` || echo "ERROR: Unable to find echo"
AWK=`${WHICH} --skip-alias awk` || echo "ERROR: Unable to find awk"
HEAD=`${WHICH} --skip-alias head` || echo "ERROR: Unable to find head"
LSBLK=`${WHICH} --skip-alias lsblk` || echo "ERROR: Unable to find lsblk"
OD=`${WHICH} --skip-alias od` || echo "ERROR: Unable to find od"
SUDO=`${WHICH} --skip-alias sudo` || echo "ERROR: Unable to find sudo"
CHMOD=`${WHICH} --skip-alias chmod` || echo "ERROR: Unable to find chmod"
BASH=`${WHICH} --skip-alias bash` || echo "ERROR: Unable to find bash"
NOHUP=`${WHICH} --skip-alias nohup` || echo "ERROR: Unable to find nohup"
SLEEP=`${WHICH} --skip-alias sleep` || echo "ERROR: Unable to find sleep"
QVM_KILL=`${WHICH} --skip-alias qvm-kill` || echo "ERROR: Unable to find qvm-kill"
POWEROFF=`${WHICH} --skip-alias poweroff` || echo "ERROR: Unable to find poweroff"
CHROOT=`${WHICH} --skip-alias chroot` || echo "ERROR: Unable to find chroot"
SYNC=`${WHICH} --skip-alias sync` || echo "ERROR: Unable to find sync"
LDD=`${WHICH} --skip-alias ldd` || echo "ERROR: Unable to find ldd"
XARGS=`${WHICH} --skip-alias xargs` || echo "ERROR: Unable to find xargs"

CHROOT_PATH='/dev/shm/buskill/chroot'
DIE_SCRIPT='/usr/bin/die.sh'

##############
# ROOT CHECK #
##############

# re-run as root
if [[ $EUID -ne 0 ]]; then
    exec ${SUDO} ${BASH} "$0" "$@"
fi

###########
# CONFIRM #
###########

# for safety, exit if this script is executed without a '--yes' argument
${ECHO} "${@}" | ${GREP} '\--yes' &> /dev/null
if [ $? -ne 0 ]; then
	${ECHO} "WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
	${ECHO} "================================================================================"
	${ECHO} "WARNING: THIS IS EXPERIMENTAL SOFTWARE THAT IS DESIGNED TO CAUSE PERMANENT,  COMPLETE AND IRREVERSIBLE DATA LOSS!"
	${ECHO} "================================================================================"
	${ECHO} "WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING"
	${ECHO}
	${ECHO} "cowardly refusing to execute without the '--yes' argument for your protection. If really you want to proceed with damaging your system, retry with the '--yes' argument"
	exit 1
fi

###########################
# (DELAYED) HARD SHUTDOWN #
###########################

# The most secure encrypted computer is an encrypted computer that is *off*
# This is our highest priority; initiate a hard-shutdown to occur in 1
# minute regardless of what happens later in this script

${NOHUP} ${SLEEP} 60 && ${ECHO} o > /proc/sysrq-trigger &
${NOHUP} ${SLEEP} 61 && ${POWEROFF} --poweroff --no-sync --no-wall --force &
${NOHUP} ${SLEEP} 62 && shutdown -h now &

###############
# LOCK SCREEN #
###############

# first action: lock the screen!
${SUDO} -u "${SUDO_USER}" ${BASH} -c "${BUSKILL_LOCK}" &

################
# KILL ALL VMs #
################

# doing this sooner will decrease the time needed to hard shutdown later
# See https://github.com/QubesOS/qubes-issues/issues/5887
${QVM_KILL} --all &

#####################
# WIPE LUKS VOLUMES #
#####################

# overwrite luks headers
${ECHO} "INFO: shredding LUKS header (plaintext metadata and keyslots with encrypted master decryption key)"
writes=''
oldIFS="${IFS}"
IFS=$'\n'
for line in $( ${LSBLK} --list --output 'UUID,FSTYPE' | ${GREP} 'crypt' ); do

	device="/dev/disk/by-uuid/`${ECHO} \"${line}\" | ${AWK} '{print \$1}'`"
	${ECHO} -e "\t${device}"

	###########################
	# OVERWRITE LUKS KEYSLOTS #
	###########################

	# erases all keyslots, making the LUKS container "permanently inaccessible"
	${CRYPTSETUP} luksErase --batch-mode "${device}" || ${HEAD} --bytes 20M /dev/urandom > ${device} &

	# store the pid of the above write tasks so we can try to wait for it to
	# flush to disk later -- before triggering a brutal hard-shutdown
	writes="${writes} $!"

	#####################################
	# OVERWRITE LUKS PLAINTEXT METADATA #
	#####################################

	luksVersion=`${OD} --skip-bytes 6 --read-bytes 2 --format d2 --endian=big --address-radix "n" "${device}"`

	# get the end byte to overwrite. For more info, see:
	# https://security.stackexchange.com/questions/227359/how-to-determine-start-and-end-bytes-of-luks-header
	if [[ $luksVersion -eq 1 ]]; then
		# LUKS1: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf

	 	# in LUKS1, the whole header ends at 512 * the `payload-offset`
		# this is actually more than we need (includes keyslots), but
		# it's the fastest/easiest to bound to fetch in LUKS1
		payloadOffset=`${OD} --skip-bytes 104 --read-bytes 4 --format d4 --endian=big --address-radix "n" "${device}"`
		luksEndByte=$(( 512 * ${payloadOffset} ))

	elif [[ $luksVersion -eq 2 ]]; then
		# LUKS2: https://gitlab.com/cryptsetup/LUKS2-docs/blob/master/luks2_doc_wip.pdf

		# in LUKS2, the end of the plaintext metadata area is twice the
		# size of the `hdr_size` field
		hdr_size=`${OD} --skip-bytes 8 --read-bytes 8 --format d8 --endian=big --address-radix "n" "${device}"`
		luksEndByte=$(( 2 * ${hdr_size} ))

	else
		# version unclear; just overwrite 20 MiB
		luksEndByte=20971520

	fi
		
	# finally, shred that plaintext metadata; we do this in a new file descriptor
	# to prevent bash from truncating if ${device} is a file
	exec 5<> "${device}"
	${HEAD} --bytes "${luksEndByte}" /dev/urandom >&5 &
	writes="${writes} $!"
	exec 5>&-
done
IFS="${oldIFS}"

#######################
# WAIT ON DISK WRITES #
#######################

# xargs is our leading/trailing whitespace trim() function
writes=`${ECHO} "${writes}" | ${XARGS}`

# wait until all the write tasks above have completed
# note: do *not* put quotes around this arg or the whitespace will break wait
# note: without the conditional, wait will wait indefinitely when $writes is empty
if [[ ${writes} ]]; then wait ${writes}; fi

# clear write buffer to ensure headers overwrites are actually synced to disks
sync; echo 3 > /proc/sys/vm/drop_caches

#################
# CREATE CHROOT #
#################

# we have to create a ramfs chroot with cryptsetup, poweroff, and a few other
# basic depends for after we luksSuspend our rootfs from under our feet

${MKDIR} -p "${CHROOT_PATH}/proc"
${MKDIR} -p "${CHROOT_PATH}/sys"
${MKDIR} -p "${CHROOT_PATH}/dev"
${MKDIR} -p "${CHROOT_PATH}/pts"
${MKDIR} -p "${CHROOT_PATH}/bin"
${MKDIR} -p "${CHROOT_PATH}/lib64"
${MKDIR} -p "${CHROOT_PATH}/lib/systemd"
${MKDIR} -p "${CHROOT_PATH}/usr/sbin"
${MKDIR} -p "${CHROOT_PATH}/usr/bin"

${MOUNT} -t proc none "${CHROOT_PATH}/proc"
${MOUNT} -t sysfs none "${CHROOT_PATH}/sys"
${MOUNT} -o bind /dev "${CHROOT_PATH}/dev"
${MOUNT} -o bind /dev/pts "${CHROOT_PATH}/dev/pts"

# copy-in our binaries and their depends
binaries="${BASH} ${CRYPTSETUP} ${ECHO} ${LS} ${NOHUP} ${SLEEP} ${XARGS} ${SYNC} ${POWEROFF}"
for binary in ${binaries}; do
	${CP} -vf "${binary}" "${CHROOT_PATH}/${binary}"
	libs="$( ${LDD} "${binary}" | ${GREP} -Eo '/lib.*\.so(\.[0-9]*)*' )"
	for lib in ${libs}; do
		${CP} -vf "${lib}" "${CHROOT_PATH}/${lib}"
	done
done

# fix around some libs and symlink issues
${MV} "${CHROOT_PATH}/lib" "${CHROOT_PATH}/usr/lib"
${LN} -s "usr/lib" "${CHROOT_PATH}/lib"

${CAT} << EOF > "${CHROOT_PATH}/${DIE_SCRIPT}"
#!${BASH}

# let's give ourselves 10 more seconds to wipe luks keys from RAM
${NOHUP} ${SLEEP} 10 && ${ECHO} o > /proc/sysrq-trigger &
${NOHUP} ${SLEEP} 11 && ${POWEROFF} --poweroff --no-sync --no-wall --force &

#################################
# WIPE DECRYPTION KEYS FROM RAM #
#################################

# suspend each currently-decrypted LUKS volume
${ECHO} "INFO: removing decryption keys from memory"
waits=''
for device in \$( ${LS} -1 "/dev/mapper" ); do

	${ECHO} -e "\t\${device}";
	${CRYPTSETUP} luksSuspend "\${device}" &

	waits="\${waits} $!"

done
${ECHO} "INFO: finished luksSuspend calls"

# xargs is our leading/trailing whitespace trim() function
waits=`${ECHO} "\${waits}" | ${XARGS}`

if [[ \${waits} ]]; then wait \${waits}; fi
${ECHO} "INFO: finished waiting"

# clear page caches in memory (again)
sync; echo 3 > /proc/sys/vm/drop_caches
${ECHO} "INFO: finished syncing"

#############################
# (IMMEDIATE) HARD SHUTDOWN #
#############################
${ECHO} "INFO: Powering Down"

# do whatever works; this is important.
${ECHO} o > /proc/sysrq-trigger &
${SLEEP} 1
${POWEROFF} --poweroff --no-sync --no-wall --force &
EOF
${CHMOD} +x "${CHROOT_PATH}/${DIE_SCRIPT}"

# finally, enter the chroot and call the above die.sh script inside it
${CHROOT} "${CHROOT_PATH}" "${DIE_SCRIPT}"

EOWF
sudo chmod 0755 /tmp/buskill/qubes-rpc/buskill.selfDestruct
sudo chown root:root /tmp/buskill/qubes-rpc/buskill.selfDestruct

Finally, we define policy files in dom0 that permit our 'sys-usb' VM to be able to execute the above triggers. Note that 'sys-usb' is necessarily a high-risk and untrusted VM that could easily be compromised. Therefore, it's critically important that any buskill triggers defined on dom0 are self-contained and don't accept any input telling them them what to execute on dom0.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/policy/buskill.lock
################################################################################
# File:    dom0:/etc/qubes-rpc/policy/buskill.lock
# Purpose: Permits sys-usb VM to execute the lock screen BusKill trigger.
#          For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sys-usb dom0 allow
EOF
sudo chmod 0644 /tmp/buskill/qubes-rpc/policy/buskill.lock

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/policy/buskill.softShutdown
################################################################################
# File:    dom0:/etc/qubes-rpc/policy/buskill.softShutdown
# Purpose: Permits sys-usb VM to execute the soft shutdown BusKill trigger.
#          For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sys-usb dom0 allow
EOF
sudo chmod 0644 /tmp/buskill/qubes-rpc/policy/buskill.softShutdown

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/policy/buskill.hardReboot
################################################################################
# File:    dom0:/etc/qubes-rpc/policy/buskill.hardReboot
# Purpose: Permits sys-usb VM to execute the hard reboot BusKill trigger.
#          For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sys-usb dom0 allow
EOF
sudo chmod 0644 /tmp/buskill/qubes-rpc/policy/buskill.hardReboot

cat << EOF | sudo tee /tmp/buskill/qubes-rpc/policy/buskill.selfDestruct
################################################################################
# File:    dom0:/etc/qubes-rpc/policy/buskill.selfDestruct
# Purpose: Permits sys-usb VM to execute the wipe data BusKill trigger.
#          For more info, see: https://buskill.in/qubes-os/
# Authors: Michael Altfield <michael@buskill.in>
# Created: 2020-01-02
# License: GNU GPLv3
################################################################################
sys-usb dom0 allow
EOF
sudo chmod 0644 /tmp/buskill/qubes-rpc/policy/buskill.selfDestruct

Now that all our files destined for dom0 are prepared in our AppVM, let's create a tarball of them.

Execute the following in a reasonably trustworthy AppVM with internet access (eg 'personal'):

pushd /tmp/buskill
tar -czvf buskill.qubes-rpc.tar.gz qubes-rpc/*
sync

Finally, we copy the tarball to dom0 and extract it. Note that you'll have to type this manually as copy-paste to dom0 isn't allowed--this is why we did the tarball/copy approach. Don't worry, it's only 4 lines ;P. Be sure to replace 'personal' with whatever AppVM you ran the above commmands on (where the tarball created above lives).

Execute the following in Dom0:

sudo su -
cd /etc
qvm-run --pass-io personal 'cat /tmp/buskill/buskill.qubes-rpc.tar.gz' > buskill.qubes-rpc.tar.gz
tar -xzvf buskill.qubes-rpc.tar.gz
sync

That's it! BusKill is now installed.

Customization

If you want to customize what is triggered when your BusKill USB device is removed from your system, you want to edit your '/etc/udev/rules.d/buskill.rules' file on your 'sys-usb' Qube.

The defaults described above enable the screen lock trigger. For example, if you want BusKill to trigger a hard shutdown instead of locking the screen, comment-out the 'buskill.lock' line and uncomment the 'hardReboot' line.

...
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.lock"
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.softShutdown"
ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.hardReboot"
#ACTION=="remove", SUBSYSTEM=="usb", ENV{ID_MODEL}=="Micromax_A74", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.selfDestruct+--yes"
EOF
sudo udevadm control --reload

Note: Changes to the buskill.rules file require a restart or udev rule reload via `sudo udevadm control --reload`

Troubleshooting

Is unplugging your USB device doing nothing? Try removing the udev property from your '/etc/udev/rules.d/buskill.rules' file on your 'sys-usb' Qube.

...
ACTION=="remove", SUBSYSTEM=="usb", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.lock"
#ACTION=="remove", SUBSYSTEM=="usb", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.softShutdown"
#ACTION=="remove", SUBSYSTEM=="usb", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.hardReboot"
#ACTION=="remove", SUBSYSTEM=="usb", RUN+="/usr/bin/qrexec-client-vm dom0 buskill.selfDestruct+--yes"
EOF
sudo udevadm control --reload

The above example should trigger the screen to lock when any USB device is removed from your system. If you want to be more specific, learn how to query your USB drive for device-specific identifiers in our original article Introducing BusKill: A Kill Cord for your Laptop. Note that the `udevadm monitor --environment --udev` command should be run in the 'sys-usb' Qube.

Note: Changes to the buskill.rules file require a restart or udev rule reload via `sudo udevadm control --reload`

Limitations/Improvements

Security is porous. All software has bugs. Nothing is 100% secure.

This section will explain limitations to the information outlined in this article, as well as suggest potential improvements.

Not a suitable replacement for TAILS

TAILS Logo
TAILS is by far the best OS to use for security-critical situations. Learn more about using BusKill with TAILS.

If you are an investigative journalist, activist, or political dissident operating in an oppressive regime where an adversary having access to your Internet activity could cause pain, suffering, or loss-of-life, then you should consider using TAILS.


Don't trust sys-usb

This implementation of BusKill in QubesOS necessarily grants the 'sys-usb' Qube permission to execute scripts in dom0 with immense destructive power, such as the ability to make all of the data on your computer permanently inaccessible.

The 'sys-usb' Qube is also a particularly exposed Qube that should not be trusted, as it could become infected by a malicious USB peripheral. Please take this into consideration when designing your BusKill triggers and granting permission to the 'sys-usb' Qube to execute them on dom0 via your dom0:/etc/qubes-rpc/policy/ files.

IANA QubesOS Dev

Disclaimer: I am a QubesOS user; I am not a QubesOS developer. The way BusKill was implemented in QubesOS here was done to the best of my ability to make it functional and secure, but I did not consult the Qubes dev team for a review prior to publication. If you are a QubesOS developer and have noticed a flaw or have a recommendation on how this was implemented, please let me know in the comments or by email

No Hard-Shutdown

You may have noticed the absence of a `buskill.hardShutdown` trigger. Indeed, a "hard shutdown" is preferred to a "hard reboot" as it stops power from flowing to your RAM (which holds your FDE master key) ASAP.

Unfortunatley, there's a bug in QubesOS that causes would-be 1-second shutdowns via the SysRq Power Off signal take over a minute 🙁

Self-Destruct Implementation Details

LUKS Header Shredder
See our article on our LUKS Header Shredder script for self-destruct implementation details

It's important to note what the self-destruct trigger does and does not do. First of all, it was designed to prioritize [a] wiping the LUKS header and [b] shutting down as fast as possible. Therefore, it renders your encryped partition totally useless (even when the decryption passphrase is known). However, it does not attempt to wipe your boot partition; this will not give you plausible deniability.

For more details on the self-destruct trigger--including post-destruct forensic details, see LUKS Header Shredder.


QubesOS Trigger Integration

In July 2020, I created a feature request and PR for the QubesOS team to integrate a lockscreen qrexec service directly into their release, which would simplifiy these installation instructions.

If you would like to make BusKill more accessible on QubesOS, please consider commenting on the QubesOS #5893 requesting the QubesOS team to incorporate lockscreen and shutdown qrexec services into the next QubesOS release.

Bathroom Breaks

The installation guide described in this article does not have any way to easy way to "pause" BusKill before stepping away from your computer (eg to go to the bathroom).

We've published an updated article describing how to setup a simple keyboard shortcut that disarms BusKill so you can step away from your computer without it shutting-down or self-destructing.


If you'd like to purchase a BusKill cable, click here.

Michael Altfield

View posts by Michael Altfield
Hi, I'm Michael Altfield. I write articles about opsec, privacy, and devops About Michael

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to top