This guide will help you configure a hardened Debian template for QubesOS.
What version of Debian would you like to target? The latest is 12.
To get started, simply open the Q menu and type dom0 Xfce Terminal
. I recommend adding the dom0 terminal to favorites (Right click > Add to favorites) since it's our main entry point for performing advanced customization of your system. Nevertheless, we'll try to avoid the dom0 terminal as much as possible in these guides, to keep things easy and friendly.
Now go ahead and type following code into your dom0 terminal (note that you can't copy-and-paste into dom0, for security reasons). This will download a minimal TemplateVM called debian-XX-minimal
.
# ==================== # Note that for security reasons, you # dom0 > xterm # can't paste text into dom0. You therefore # ==================== # have to type each command manually. qubes-dom0-update qubes-template-debian-XX-minimal
To avoid modifying the base template, let's create a clone called dXX
. This clone will serve as the basis of most of our virtual machines.
# ==================== # Note that for security reasons, you # dom0 > xterm # can't paste text into dom0. You therefore # ==================== # have to type each command manually. qvm-clone debian-XX-minimal dXX
Now open a terminal in dXX
with root user permissions.
# ==================== # Note that for security reasons, you # dom0 > xterm # can't paste text into dom0. You therefore # ==================== # have to type each command manually. qvm-run -u root dXX xterm
The following code should be executed in dXX
. Its purpose is to reroute Debian-supplied updates through a random Swiss mirror (since Switzerland has very good privacy laws), and to avoid installing proprietary blobs (which represent a security risk). Re-enabling proprietary blobs is straightforward, and sometimes necessary. Functions to do this are therefore also included. After the reroute it then installs a few critical utilities, and does a full upgrade of all installed software.
The most controversial utility this installs is passwordless sudo. Many security experts would argue that installing passwordless sudo here reduces our overall security posture. However, they're objectively wrong. This is because passwordless sudo allows us to run our TemplateVMs with non-root permissions, which means that potentially dangerous commands will fail unless used with sudo
. This improves the transparency and auditability of the overall process of setting up our TemplateVMs.
There are certain high-assurance VMs for which we'll ultimately uninstall passwordless sudo. This is to ensure potential attackers have the fewest possible tools available to them. But we only uninstall it AFTER setting up our system, not before, to avoid running as the root user.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # echo 'Defining functions for modifying apt sources' until tee -a /etc/bash.bashrc > /dev/null <<'EOF_OUTER' # h20-register-debian-sources # ---------------------------- # Creates a Debian APT sources list file at a specified path, using a random # Switzerland-based mirror and a given set of components (e.g. 'main', 'contrib non-free-firmware'). # The function tests mirrors until one works by running 'apt update'. # Arguments: # 1 - Path to the file to create (e.g. /etc/apt/sources.list.d/nonfree.list) # 2 - Components string (e.g. "contrib non-free-firmware") h20-register-debian-sources() { local TARGET_PATH="\$1" local COMPONENTS="\$2" if [ -z "\$VERSION_CODENAME" ]; then echo "Getting codename for Debian version" until source /etc/os-release; do sleep 1; done fi local CODENAME="\$VERSION_CODENAME" local MIRRORS=( 'debian.ethz.ch' 'linuxsoft.cern.ch' 'mirror.iway.ch' 'mirror.metanet.ch' 'mirror.sinavps.ch' 'pkg.adfinis-on-exoscale.ch' ) echo "Trying random Swiss mirrors to find a working one for components: \$COMPONENTS" while true; do local MIRROR="\${MIRRORS[\$RANDOM % \${#MIRRORS[@]}]}" echo "Trying mirror: \$MIRROR" until sudo tee "\$TARGET_PATH" > /dev/null <<EOF_INNER # Debian sources deb https://\$MIRROR/debian \$CODENAME \$COMPONENTS deb-src https://\$MIRROR/debian \$CODENAME \$COMPONENTS deb https://\$MIRROR/debian-security \$CODENAME-security \$COMPONENTS deb-src https://\$MIRROR/debian-security \$CODENAME-security \$COMPONENTS EOF_INNER do sleep 1; done echo 'Running apt update...' if sudo apt update; then echo "Success with \$MIRROR" break else echo "apt update failed with \$MIRROR - trying another mirror in 1s..." sleep 1 fi done echo "APT repository file created at \$TARGET_PATH using mirror: \$MIRROR" echo "Run 'sudo apt update' to refresh your APT repositories." } # h20-enable-nonfree-debian-sources # ----------------------------------- # Enables access to Debian's contrib and non-free-firmware components # by creating a new APT sources list file using a Swiss mirror. # This is useful for systems that need non-free firmware or extra driver support. h20-enable-nonfree-debian-sources() { h20-register-debian-sources /etc/apt/sources.list.d/nonfree.list "contrib non-free-firmware" } # h20-disable-nonfree-debian-sources # ------------------------------------ # Disables contrib and non-free-firmware support by removing the # previously created sources list file. This helps enforce a fully # free and auditable system. Reminder: run 'sudo apt update' after disabling. h20-disable-nonfree-debian-sources() { local TARGET_PATH="/etc/apt/sources.list.d/nonfree.list" echo "Disabling contrib and non-free-firmware by removing: $TARGET_PATH" if sudo rm -f "$TARGET_PATH"; then echo "Removed $TARGET_PATH" echo "Run 'sudo apt update' to refresh your APT sources." else echo "Failed to remove $TARGET_PATH" return 1 fi } EOF_OUTER do sleep 1; done echo 'Ensuring newly defined functions are immediately available' until source /etc/bash.bashrc; do sleep 1; done echo 'Changing to random Swiss mirror and disabling proprietary blobs.' h20-register-debian-sources /etc/apt/sources.list "main" echo 'Installing key packages to ensure updates are over HTTPS' until sudo apt install apt-transport-https ca-certificates; do sleep 1; done echo 'Installing passwordless sudo so we can avoid running as root user' until sudo apt install qubes-core-agent-passwordless-root; do sleep 1; done
Begin by closing the terminal in dXX
with root user status. You don't have to restart the VM. Afterwards, reopen xterm in dXX
via the Q menu. This has the effect of opening it without root user status, and forces potentially dangerous commands to go via the sudo escape hatch.
Once the new terminal has been opened, go ahead and paste in the following code. It updates all of your software, and then cleans up afterwards.
echo 'Performing full upgrade via the new mirror' until sudo apt update; do sleep 1; done until sudo apt full-upgrade; do sleep 1; done until sudo apt autoremove; do sleep 1; done until sudo apt clean; do sleep 1; done
For the most part, security and privacy are complementary. Unfortunately, they're actually occassionally in conflict. Logs are a common example of this. Keeping extensive logs is good for security, because they facilitate automated (and manual) intrusion detection, and assist users in reacting appropriately if there's a breach. On the other hand, they can be stolen by hackers, and recovered from hard drives. Logs are therefore a common pain point for security and privacy setups.
At Hero to Zero, we advocate using the following rules to address the tension:
logaudit
qube.logaudit
qube runs automated log analysis and intrusion detection software.logaudit
qube does not hold onto your logs for more than 30 minutes, unless anomalous behaviour is detected.logaudit
qube chooses a random password, and begins to encrypt the logs with that password, and stores them on disk for future reference.Additionally, we need to make sure there's fundamentally an asymmetry between enabling logging (which should be easy) and disabling logging (which should require a password). This ensures that a malicious script cannot easily disable logging.
To achieve this, the following script makes four functions available.
h20-enable-journald
turns system logging on, and can be run without root/sudo privileges.h20-disable-journald
turns most system logging off, but can only be run with root/sudo privileges.h20-enable-log-exfil
turns on exfiltration to the dedicated AuditVM, and can be run without root/sudo privileges.h20-disable-log-exfil
turns off exfiltration to the dedicated AuditVM, and cannot be run without root/sudo privileges.NOTE: To gain this asymmetry, we have to strategically uninstall passwordless root where appropriate. We'll do that later.
For now, please run the following code in dXX
.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # echo 'Ensuring logs are redirected to RAM and purged on shutdown' until sudo mount -t tmpfs -o size=100M,mode=0755 tmpfs /var/log; do sleep 1; done echo 'Recreating essential /var/log files and directories (tmpfiles.d)' until sudo tee /etc/tmpfiles.d/h20-var-log.conf > /dev/null <<'EOF' # h20-var-log.conf — recreate essentials when /var/log is tmpfs d /var/log 0755 root root - d /var/log/apt 0755 root root - d /var/log/dpkg 0755 root root - d /var/log/fsck 0755 root root - d /var/log/rsyslog 0755 root root - d /var/spool/rsyslog 0755 root root - # classic login/accounting files f /var/log/wtmp 0664 root utmp - f /var/log/btmp 0600 root utmp - f /var/log/lastlog 0644 root root - f /var/log/faillog 0644 root root - EOF do sleep 1; done until sudo systemd-tmpfiles --create /etc/tmpfiles.d/h20-var-log.conf; do sleep 1; done echo 'Persist the mount in /etc/fstab (no idempotency, just append)' until sudo tee -a /etc/fstab > /dev/null <<'EOF' tmpfs /var/log tmpfs defaults,size=100m,mode=0755 0 0 EOF do sleep 1; done echo 'Configure systemd-journald to use volatile (RAM-only) logging' until sudo mkdir -p /etc/systemd/journald.conf.d; do sleep 1; done until sudo tee /etc/systemd/journald.conf.d/volatile.conf > /dev/null <<'EOF' [Journal] Storage=volatile EOF do sleep 1; done echo 'Restarting journald to apply the volatile logging config' until sudo systemctl restart systemd-journald; do sleep 1; done if systemctl list-unit-files --no-legend --type=service 2>/dev/null | awk '{print $1}' | grep -Fxq rsyslog.service; then echo 'Restarting rsyslog to resync with journald and /var/log' until sudo systemctl restart rsyslog; do sleep 1; done fi echo 'Commenting out HIST or shopt lines in /etc/skel/.bashrc' until sudo awk -v h="#" -f - /etc/skel/.bashrc > "/tmp/.h20.bashrc.new" <<'EOF' /^[[:space:]]*(HIST|shopt)/ { sub(/^[[:space:]]*/, "&" h " ") } { print } EOF do sleep 1; done until sudo mv /tmp/.h20.bashrc.new /etc/skel/.bashrc; do sleep 1; done echo 'Ensuring bash history is limited to 10 commands, stored in RAM, and purged on shutdown' until sudo tee -a /etc/bash.bashrc > /dev/null <<'EOF' export HISTSIZE=10 export HISTFILESIZE=0 export HISTFILE=/dev/null EOF do sleep 1; done echo 'Creating root helper for h20-enable-journald' until sudo tee /usr/sbin/h20-enable-journald-root > /dev/null <<'EOF' #!/bin/sh # h20-enable-journald-root # ------------------------ # Unmasks, enables, and starts journald sockets/services, and rsyslog if present. echo "[h20] enabling journald sockets/services" for u in systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket systemd-journald.service; do if systemctl list-unit-files --no-legend --type=service --type=socket 2>/dev/null | awk '{print $1}' | grep -Fxq "$u"; then if systemctl is-enabled "$u" 2>&1 | grep -q masked; then echo "[h20] unmask $u" until systemctl unmask "$u"; do sleep 1; done fi echo "[h20] enable $u" until systemctl enable "$u"; do sleep 1; done echo "[h20] start $u" until systemctl start "$u" || systemctl restart "$u"; do sleep 1; done else echo "[h20] skip missing $u" fi done if command -v rsyslogd >/dev/null 2>&1 || [ -f /usr/sbin/rsyslogd ] || [ -f /usr/bin/rsyslogd ]; then echo "[h20] ensuring rsyslog.service is up" if systemctl is-enabled rsyslog.service 2>&1 | grep -q masked; then until systemctl unmask rsyslog.service; do sleep 1; done fi until systemctl enable rsyslog.service; do sleep 1; done until systemctl start rsyslog.service || systemctl restart rsyslog.service; do sleep 1; done else echo "[h20] rsyslog not installed; skipping" fi echo "[h20] journald enable complete" EOF do sleep 1; done until sudo chmod 0755 /usr/sbin/h20-enable-journald-root; do sleep 1; done until sudo chown root:root /usr/sbin/h20-enable-journald-root; do sleep 1; done echo 'Creating root helper for h20-disable-journald' until sudo tee /usr/sbin/h20-disable-journald-root > /dev/null <<'EOF' #!/bin/sh # h20-disable-journald-root # ------------------------- # Stops and disables journald sockets/services; stops/disables rsyslog if present. echo "[h20] stopping journald sockets/services" for u in systemd-journald.service systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket; do if systemctl list-unit-files --no-legend --type=service --type=socket 2>/dev/null | awk '{print $1}' | grep -Fxq "$u"; then until systemctl stop "$u" || systemctl try-restart "$u"; do sleep 1; done until systemctl disable "$u"; do sleep 1; done fi done if systemctl list-unit-files --no-legend --type=service 2>/dev/null | awk '{print $1}' | grep -Fxq rsyslog.service; then until systemctl stop rsyslog.service || systemctl try-restart rsyslog.service; do sleep 1; done until systemctl disable rsyslog.service; do sleep 1; done fi echo "[h20] journald mostly disabled" EOF do sleep 1; done until sudo chmod 0755 /usr/sbin/h20-disable-journald-root; do sleep 1; done until sudo chown root:root /usr/sbin/h20-disable-journald-root; do sleep 1; done echo 'Creating root helper for h20-enable-log-exfil' until sudo tee /usr/sbin/h20-enable-log-exfil-root > /dev/null <<'EOF' #!/bin/sh # h20-enable-log-exfil-root # ------------------------- # Configures rsyslog to forward all logs to host "logaudit" over UDP 514. AUDIT_HOST="logaudit" AUDIT_PORT="514" # install rsyslog if missing if ! command -v rsyslogd >/dev/null 2>&1 && [ ! -f /usr/sbin/rsyslogd ] && [ ! -f /usr/bin/rsyslogd ]; then echo "[h20] installing rsyslog" until apt-get update; do sleep 1; done until DEBIAN_FRONTEND=noninteractive apt-get install -y rsyslog; do sleep 1; done fi # ensure imjournal follows journald if [ -f /etc/rsyslog.conf ]; then if ! grep -q '^module(load="imjournal")' /etc/rsyslog.conf; then echo '[h20] enabling imjournal in /etc/rsyslog.conf' until sed -i '1imodule(load="imjournal" StateFile="/var/spool/rsyslog/imjournal.state")' /etc/rsyslog.conf; do sleep 1; done fi fi # forward everything until mkdir -p /etc/rsyslog.d; do sleep 1; done until tee /etc/rsyslog.d/99-h20-forward-all.conf >/dev/null <<EOC # h20 forward all logs to logaudit *.* @${AUDIT_HOST}:${AUDIT_PORT} EOC do sleep 1; done # start/enable rsyslog if systemctl is-enabled rsyslog.service 2>&1 | grep -q masked; then until systemctl unmask rsyslog.service; do sleep 1; done fi until systemctl enable rsyslog.service; do sleep 1; done until systemctl restart rsyslog.service; do sleep 1; done echo "[h20] log exfil enabled to ${AUDIT_HOST}:${AUDIT_PORT}" EOF do sleep 1; done until sudo chmod 0755 /usr/sbin/h20-enable-log-exfil-root; do sleep 1; done until sudo chown root:root /usr/sbin/h20-enable-log-exfil-root; do sleep 1; done echo 'Creating root helper for h20-disable-log-exfil' until sudo tee /usr/sbin/h20-disable-log-exfil-root > /dev/null <<'EOF' #!/bin/sh # h20-disable-log-exfil-root # -------------------------- # Removes the rsyslog forwarding config and restarts rsyslog. if [ -f /etc/rsyslog.d/99-h20-forward-all.conf ]; then until rm -f /etc/rsyslog.d/99-h20-forward-all.conf; do sleep 1; done fi if systemctl list-unit-files --no-legend --type=service 2>/dev/null | awk '{print $1}' | grep -Fxq rsyslog.service; then until systemctl restart rsyslog.service; do sleep 1; done fi echo "[h20] log exfil disabled" EOF do sleep 1; done until sudo chmod 0755 /usr/sbin/h20-disable-log-exfil-root; do sleep 1; done until sudo chown root:root /usr/sbin/h20-disable-log-exfil-root; do sleep 1; done echo 'Allowing passwordless enablement of log creation and log exfil' until sudo tee /etc/sudoers.d/h20-logs > /dev/null <<'EOF' Cmnd_Alias H20_ENABLES = /usr/sbin/h20-enable-journald-root, /usr/sbin/h20-enable-log-exfil-root user ALL=(root) NOPASSWD: H20_ENABLES Defaults!H20_ENABLES env_reset,secure_path="/usr/sbin:/usr/bin:/sbin:/bin" EOF do sleep 1; done until sudo chmod 0440 /etc/sudoers.d/h20-logs; do sleep 1; done until sudo visudo -c > /dev/null; do sleep 1; done echo 'Creating bash wrappers for exposure to end-user' until sudo tee -a /etc/bash.bashrc > /dev/null <<'EOF' # h20-enable-journald # ------------------- # Turns system logging on. Unmasks/enables/starts journald sockets/services, # and ensures rsyslog is running if present. Can be run by non-root user # due to a limited sudoers rule. h20-enable-journald() { until sudo /usr/sbin/h20-enable-journald-root; do sleep 1; done } # h20-disable-journald # -------------------- # Turns most system logging off. Stops/disables journald sockets/services # and stops/disables rsyslog if present. Requires root/sudo privileges. h20-disable-journald() { until sudo /usr/sbin/h20-disable-journald-root; do sleep 1; done } # h20-enable-log-exfil # -------------------- # Forwards all logs to the "logaudit" host over UDP 514 via rsyslog. # Installs rsyslog if missing. Can be run without a password due to # a limited sudoers rule. h20-enable-log-exfil() { until sudo /usr/sbin/h20-enable-log-exfil-root; do sleep 1; done } # h20-disable-log-exfil # --------------------- # Disables forwarding by removing the rsyslog snippet and restarting rsyslog. # Requires root/sudo privileges. h20-disable-log-exfil() { until sudo /usr/sbin/h20-disable-log-exfil-root; do sleep 1; done } # h20-watch-logs # -------------- # Shows the last 200 journal lines and then follows new log entries in realtime. # Must be run as root or with sudo. h20-watch-logs() { sudo journalctl -n 200 -f --no-hostname --output=short-iso } EOF do sleep 1; done echo 'Installing 1-minute journal vacuum service and timer' until sudo tee /etc/systemd/system/h20-journal-vacuum.service > /dev/null <<'EOF' [Unit] Description=H2O - vacuum systemd journal to 1 minute [Service] Type=oneshot ExecStart=/bin/sh -c 'journalctl --vacuum-time=1m || true' EOF do sleep 1; done until sudo tee /etc/systemd/system/h20-journal-vacuum.timer > /dev/null <<'EOF' [Unit] Description=H2O - run journal vacuum every minute [Timer] OnBootSec=30s OnUnitActiveSec=60s Unit=h20-journal-vacuum.service AccuracySec=10s [Install] WantedBy=timers.target EOF do sleep 1; done echo 'Enabling and starting the 1-minute journal vacuum timer' until sudo systemctl daemon-reload; do sleep 1; done until sudo systemctl enable h20-journal-vacuum.timer; do sleep 1; done until sudo systemctl start h20-journal-vacuum.timer; do sleep 1; done
Now run the following code. It reconfigure the overall operating system with a view towards security and the creation of verbose and maximally helpful logs.
Remember: if logs are fundamentally incompatible with your use case, the h20-disable-journald
command provided by the previous script can be used to eliminate most of them.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # echo 'Enable extra canaries/checks for binaries using glibc malloc' until echo 'MALLOC_CHECK_=3' | sudo tee -a /etc/environment > /dev/null; do sleep 1; done until echo 'MALLOC_PERTURB_=153' | sudo tee -a /etc/environment > /dev/null; do sleep 1; done echo 'Hardening sysctl for privacy and security' until sudo tee -a /etc/sysctl.d/97-hardening.conf > /dev/null <<'EOF' ################################ #### MEMORY/DEBUG/FORENSICS #### ################################ kernel.kptr_restrict = 2 # Hide kernel pointers everywhere kernel.dmesg_restrict = 1 # Only root can read dmesg kernel.yama.ptrace_scope = 3 # Completely disable ptrace except direct child kernel.sysrq = 0 # No magic sysrq kernel.ftrace_enabled = 0 # Block kernel tracing (anti-forensics) kernel.perf_event_paranoid = 3 # No perf monitoring for non-root kernel.perf_event_mlock_kb = 1 # Minimal perf event buffer kernel.kexec_load_disabled = 1 # No kexec (rootkits/forensics) kernel.kprobes_allow_uds = 0 # No unprivileged kprobes (if supported) kernel.core_pattern = "|/bin/false" # Never dump core to disk kernel.core_pipe_limit = 0 # No core dump pipes kernel.randomize_va_space = 2 # Full ASLR vm.mmap_min_addr = 65536 # Block low-address mmap attacks kernel.numa_balancing = 0 # Disable page migration/auto NUMA ####################### #### SWAP BEHAVIOR #### ####################### vm.swappiness = 10 # Do not use swap except under a lot of ram pressure vm.overcommit_memory = 2 # Strict overcommit restrictions (reduce DoS/fuzz) vm.overcommit_ratio = 400 # Gives a 400% overcommit ratio vm.dirty_background_ratio = 7 # Reduced from a default of 10 vm.dirty_ratio = 15 # Reduced from a default of 20 vm.page-cluster = 4 # 64KiB swap clusters (be sure to use ephemeral swap) vm.min_free_kbytes = 65536 # Extra RAM for kernel (resist memory starvation) ########################### #### HIDE PROCESS INFO #### ########################### kernel.pid_max = 4194304 kernel.ngroups_max = 65536 kernel.threads-max = 32768 kernel.sched_child_runs_first = 1 ####################################### #### ANTI-SURVEILLANCE / ANTI-LEAK #### ####################################### kernel.printk = 3 3 3 3 # Reduces kernel logging verbosity - good for minimizing leakage into dmesg, journald, or serial consoles kernel.nmi_watchdog = 0 # Disables kernel NMI watchdog - reduces noise and can prevent info leakage via debugging/traps kernel.acpi_video_flags = 0 # Minimal ACPI logs kernel.acpi_rsdp = 0 # Minimal ACPI root system desc kernel.domainname = "" # Removes legacy NIS/YP domain - safe, modern systems do not need it # kernel.hostname = "" # If the environment was a bare-metal setup debian, you would uncomment this. But within Qubes it creates problems, and should remain commented ########################### #### FILESYSTEM / MISC #### ########################### fs.suid_dumpable = 0 # No setuid core dumps fs.protected_regular = 2 # Block regular file hardening attacks fs.protected_symlinks = 1 # Block symlink privilege attacks fs.protected_hardlinks = 1 # Block hardlink privilege attacks fs.protected_fifos = 1 # Block FIFO tricks fs.protected_readdir = 1 # Block dangerous readdir tricks (newer kernels) fs.inode_readahead_blks = 8 # Minimal readahead fs.pipe-user-pages-soft = 0 # Harden pipe attacks ################################### #### USERNAMESPACES/CONTAINERS #### ################################### kernel.unprivileged_userns_clone = 0 # Block userns (most privesc) kernel.unprivileged_bpf_disabled = 1 # Block unprivileged BPF everywhere user.max_user_namespaces = 0 # No user namespaces for any process user.max_mnt_namespaces = 8 user.max_pid_namespaces = 8 user.max_net_namespaces = 8 user.max_uts_namespaces = 8 user.max_ipc_namespaces = 8 user.max_cgroup_namespaces = 8 user.max_time_namespaces = 8 #################################### #### AUDIT/LOGGING MINIMIZATION #### #################################### kernel.audit_enabled = 0 # No kernel audit (if using forensics resistance) kernel.random.trust_cpu = 0 # Do not trust CPU for randomness ###################### #### MISC PRIVACY #### ###################### dev.tty0.autoclose = 1 # Autoclose on logout (no stray processes) dev.tty.autoclose = 1 #################################### #### MAXIMIZE MEMORY RANDOMNESS #### #################################### vm.mmap_rnd_bits = 32 # Maximum mmap entropy (if arch supports) vm.mmap_rnd_compat_bits = 16 # Same for compat mode ######################################## #### ACCOMODATE HARDENED ALLOCATORS #### ######################################## vm.max_map_count = 1048576 # Multiply default value by 16x to support hardened_malloc ################################# #### NETWORK STACK HARDENING #### ################################# # IPv4 net.ipv4.tcp_timestamps = 0 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 1 net.ipv4.conf.default.secure_redirects = 1 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 net.ipv4.tcp_syncookies = 1 net.ipv4.conf.all.shared_media = 0 net.ipv4.conf.default.shared_media = 0 net.ipv4.conf.all.proxy_arp = 0 net.ipv4.conf.default.proxy_arp = 0 net.ipv4.ip_forward = 0 net.ipv4.conf.all.arp_filter = 1 net.ipv4.conf.default.arp_filter = 1 net.ipv4.conf.all.arp_announce = 2 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.all.arp_ignore = 2 net.ipv4.conf.default.arp_ignore = 2 # IPv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 net.ipv6.conf.lo.disable_ipv6 = 1 net.ipv6.conf.all.accept_ra = 0 net.ipv6.conf.default.accept_ra = 0 net.ipv6.conf.all.accept_redirects = 0 net.ipv6.conf.default.accept_redirects = 0 net.ipv6.conf.all.accept_source_route = 0 net.ipv6.conf.default.accept_source_route = 0 net.ipv6.conf.all.drop_unsolicited_na = 1 net.ipv6.conf.default.drop_unsolicited_na = 1 net.ipv6.conf.lo.drop_unsolicited_na = 1 ############################################## #### PREVENT COMPACTION OF SENSITIVE DATA #### ############################################## vm.compact_unevictable_allowed = 0 EOF do sleep 1; done echo 'Reapplying .conf files' until sysctl --system; do sleep 1; done
The majority of software vulnerabilities arise from memory management errors. We therefore need a better and more secure memory allocator.
Without closing your dXX terminal, go ahead and open another terminal within a disposable qube. It should be called dispYYYY. Please enter the YYYY value below.
In this step, we download and build the memory allocator, and then transfer it to dXX. Feel free to close dispYYYY after performing this step.
# ======================== # You can use SHIFT+INSERT to paste into # dispYYYY > xterm # xterm, or use the middle mouse button. # ======================== # # Install download and build prerequisites until sudo apt update; do sleep 1; done until sudo apt install -y git build-essential cmake pkg-config; do sleep 1; done # Clone repository rm -rf hardened_malloc || true until git clone https://github.com/GrapheneOS/hardened_malloc.git; do sleep 1; done # Move into a subshell ( # Enter the hardened_malloc directory until cd hardened_malloc; do sleep 1; done # Build default until make clean; do sleep 1; done until make RELEASE=1; do sleep 1; done until cp out/libhardened_malloc.so ../libhardened_malloc_default.so; do sleep 1; done # Build non-clearing version export CONFIG_ZERO_ON_FREE=0 export CONFIG_WRITE_AFTER_FREE_CHECK=0 until make clean; do sleep 1; done until make RELEASE=1; do sleep 1; done until cp out/libhardened_malloc.so ../libhardened_malloc_softened.so; do sleep 1; done ) # Package both versions rm -f libhardened_malloc.tar.gz || true until tar -czf libhardened_malloc.tar.gz libhardened_malloc_default.so libhardened_malloc_softened.so; do sleep 1; done # Qubes copy to VM until sync; do sleep 1; done until qvm-copy libhardened_malloc.tar.gz; do sleep 1; done
Now go back to your dXX
terminal. If you closed it, open a dom0 terminal, and hit the up button until you find the appropriate command. If you can't find it, use the back button on this webpage to find the appropriate dom0 command.
Go ahead and run the following script in dXX
. It put libhardened malloc into the appropriate directory, and sets up some functions to make activating it straightforward.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # ( # Extract the tarball from the incoming Qubes directory echo '[INFO] Extracting libhardened_malloc.tar.gz from QubesIncoming...' until cd /home/user/QubesIncoming/dispYYYY; do sleep 1; done until sudo tar -xzf libhardened_malloc.tar.gz; do sleep 1; done # Set correct permissions until sudo chmod 755 libhardened_malloc_default.so; do sleep 1; done until sudo chmod 755 libhardened_malloc_softened.so; do sleep 1; done # Move both .so files to /usr/lib until sudo mv libhardened_malloc_default.so /usr/lib/; do sleep 1; done until sudo mv libhardened_malloc_softened.so /usr/lib/; do sleep 1; done # Clean up by removing the directory that has the tarball cd .. until rm -rf dispYYYY; do sleep 1; done ) # Add allocator switching and inspection functions to /etc/bash.bashrc echo '[INFO] Adding allocator control functions to /etc/bash.bashrc' until sudo tee -a /etc/bash.bashrc > /dev/null <<'EOF' # h20-libhardened-malloc-harden # ----------------------------------- # Enables the default version of libhardened_malloc system-wide # by adding it to /etc/ld.so.preload. This version includes # zero-on-free behavior for maximum memory hygiene. h20-libhardened-malloc-harden() { sudo sed -i "\|/usr/lib/libhardened_malloc_.*\.so|d" /etc/ld.so.preload 2>/dev/null || true echo "/usr/lib/libhardened_malloc_default.so" | sudo tee -a /etc/ld.so.preload > /dev/null # Ensure only the correct line is present sudo grep -Fxq "/usr/lib/libhardened_malloc_default.so" /etc/ld.so.preload && \ ! sudo grep -Fq "/usr/lib/libhardened_malloc_softened.so" /etc/ld.so.preload && return 0 || return 1 echo "Hardened malloc entries written to /etc/ld.so.preload." } # h20-libhardened-malloc-soften # ------------------------------------ # Enables a softened version of libhardened_malloc system-wide # by adding it to /etc/ld.so.preload. This version disables # zero-on-free for better compatibility with some programs. h20-libhardened-malloc-soften() { sudo sed -i "\|/usr/lib/libhardened_malloc_.*\.so|d" /etc/ld.so.preload 2>/dev/null || true echo "/usr/lib/libhardened_malloc_softened.so" | sudo tee -a /etc/ld.so.preload > /dev/null # Ensure only the correct line is present sudo grep -Fxq "/usr/lib/libhardened_malloc_softened.so" /etc/ld.so.preload && \ ! sudo grep -Fq "/usr/lib/libhardened_malloc_default.so" /etc/ld.so.preload && return 0 || return 1 echo "Softened hardened malloc entries written to /etc/ld.so.preload." } # h20-libhardened-malloc-disable # ------------------------------- # Disables all versions of libhardened_malloc by removing them # from /etc/ld.so.preload. Use this if you encounter crashes # in software that is not compatible with custom allocators. h20-libhardened-malloc-disable() { echo "Disabling libhardened_malloc by filtering /etc/ld.so.preload..." if [ ! -f /etc/ld.so.preload ]; then echo "No preload file exists - nothing to do." return 0 fi if ! grep -qE "/usr/lib/libhardened_malloc_.*\.so" /etc/ld.so.preload; then echo "No libhardened_malloc entries found in /etc/ld.so.preload" return 0 fi # Remove lines matching libhardened_malloc and overwrite the file until sudo grep -vE "/usr/lib/libhardened_malloc_.*\.so" /etc/ld.so.preload | sudo tee /etc/ld.so.preload.tmp > /dev/null; do sleep 1; done until sudo mv /etc/ld.so.preload.tmp /etc/ld.so.preload; do sleep 1; done echo "Hardened malloc entries removed from /etc/ld.so.preload." } # h20-libhardened-malloc-inspect # ---------------------- # Prints any lines in /etc/ld.so.preload that contain # the string "alloc". Use this to check which allocator # is currently active. h20-libhardened-malloc-inspect() { echo "The lines in /etc/ld.so.preload matching .*alloc.* are as follows:" sudo grep ".*alloc.*" /etc/ld.so.preload 2>/dev/null } EOF do sleep 1; done
Close your terminal, and open it again (note that source
won't work, since ShellGuard disabled it). You don't have to shutdown the VM, just close and reopen the terminal.
Now type the following into your terminal dXX
terminal. It'll activate libhardened malloc for any future programs you run.
Congratulations-you just configured a hardened Debian template!
Let us now install some small utilities that will be helpful later.
In the wonderful world of the Linux desktop experience, there's at least four library clusters that are very hard to get away from. I have in mind, in particular, the following clusters:
The vast majority of applications you install will depend on one or two of the aforementioned clusters. Unfortunately, since these libraries are very full-featured, they represent enormous attack surfaces, and have a long history of exploitable vulnerabilities. It's therefore quite handy to be able blacklist and un-blacklist these clusters.
The following script installs functions we can use later to do exactly that. It doesn't actually perform the blacklisting; rather, it provides the commands that will allow us to quickly and easily do so later.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # sudo tee -a /etc/bash.bashrc > /dev/null <<'OUTER_EOF' # h20-blacklist-gtk # ------------------ # Blacklists the GTK Toolkit in apt. h20-blacklist-gtk() { sudo tee /etc/apt/preferences.d/h20-blacklist-gtk > /dev/null <<'INNER_EOF' Package: libgtk* Pin: release * Pin-Priority: -1 Package: gtk* Pin: release * Pin-Priority: -1 Package: libgail* Pin: release * Pin-Priority: -1 Package: gail* Pin: release * Pin-Priority: -1 Package: gtk-update-icon-cache Pin: release * Pin-Priority: -1 INNER_EOF echo "Blacklisted all GTK (except gdk and existing core stack)." } # h20-blacklist-qt # ------------------ # Blacklists the Qt Toolkit in apt. h20-blacklist-qt() { sudo tee /etc/apt/preferences.d/h20-blacklist-qt > /dev/null <<'INNER_EOF' Package: libqt* Pin: release * Pin-Priority: -1 Package: qt5-* Pin: release * Pin-Priority: -1 Package: qt6-* Pin: release * Pin-Priority: -1 Package: qtbase* Pin: release * Pin-Priority: -1 Package: qttools* Pin: release * Pin-Priority: -1 Package: qtwayland* Pin: release * Pin-Priority: -1 Package: qtchooser Pin: release * Pin-Priority: -1 INNER_EOF echo "Blacklisted all Qt-related packages." } # h20-blacklist-ffmpeg # --------------------- # Blacklists the FFmpeg Multimedia Framework in apt. h20-blacklist-ffmpeg() { sudo tee /etc/apt/preferences.d/h20-blacklist-ffmpeg > /dev/null <<'INNER_EOF' Package: libavcodec* Pin: release * Pin-Priority: -1 Package: avcodec* Pin: release * Pin-Priority: -1 Package: libavformat* Pin: release * Pin-Priority: -1 Package: avformat* Pin: release * Pin-Priority: -1 Package: libavutil* Pin: release * Pin-Priority: -1 Package: avutil* Pin: release * Pin-Priority: -1 Package: libavfilter* Pin: release * Pin-Priority: -1 Package: avfilter* Pin: release * Pin-Priority: -1 Package: libavdevice* Pin: release * Pin-Priority: -1 Package: avdevice* Pin: release * Pin-Priority: -1 Package: libpostproc* Pin: release * Pin-Priority: -1 Package: postproc* Pin: release * Pin-Priority: -1 Package: libswscale* Pin: release * Pin-Priority: -1 Package: swscale* Pin: release * Pin-Priority: -1 Package: libswresample* Pin: release * Pin-Priority: -1 Package: swresample* Pin: release * Pin-Priority: -1 Package: ffmpeg Pin: release * Pin-Priority: -1 INNER_EOF echo "Blacklisted all FFmpeg/libav-related packages." } # h20-blacklist-gstreamer # ------------------------ # Blacklists the GStreamer Multimedia Framework in apt. h20-blacklist-gstreamer() { sudo tee /etc/apt/preferences.d/h20-blacklist-gstreamer > /dev/null <<'INNER_EOF' Package: libgstreamer* Pin: release * Pin-Priority: -1 Package: gstreamer* Pin: release * Pin-Priority: -1 Package: libgst* Pin: release * Pin-Priority: -1 Package: gst* Pin: release * Pin-Priority: -1 Package: gir1.2-gstreamer* Pin: release * Pin-Priority: -1 INNER_EOF echo "Blacklisted all GStreamer-related packages." } # h20-unblacklist-gtk # -------------------- # Deletes the file which blacklists the GTK Toolkit. h20-unblacklist-gtk() { sudo rm -f /etc/apt/preferences.d/h20-blacklist-gtk echo "GTK blacklist removed." } # h20-unblacklist-gtk # -------------------- # Deletes the file which blacklists the Qt Toolkit. h20-unblacklist-qt() { sudo rm -f /etc/apt/preferences.d/h20-blacklist-qt echo "Qt blacklist removed." } # h20-unblacklist-ffmpeg # ----------------------- # Deletes the file which blacklists the FFmpeg Toolkit. h20-unblacklist-ffmpeg() { sudo rm -f /etc/apt/preferences.d/h20-blacklist-ffmpeg echo "FFmpeg blacklist removed." } # h20-unblacklist-ffmpeg # ----------------------- # Deletes the file which blacklists the FFmpeg Toolkit. h20-unblacklist-gstreamer() { sudo rm -f /etc/apt/preferences.d/h20-blacklist-gstreamer echo "GStreamer blacklist removed." } OUTER_EOF do sleep 1; done
We've been using XTerminal because it's very minimal and battle-hardened compared with other terminal software. Unfortunately, it doesn't provide the standard copy-and-paste niceties that people have come to expect from their desktop applications. To address this, I recommend running the following code, which defines a custom terminal command h20-copy
to assist with copying data out of XTerminal (or any other terminal).
Security note: the function h20-copy
depends on a small utility called xclip
, which provides the underlying clipboard functionality. Installing xclip
introduces minimal attack surface, and is widely considered safe. However, if you're feeling ultra-paranoid, feel free to skip this step.
# =================== # You can use SHIFT+INSERT to paste into # dXX > xterm # xterm, or use the middle mouse button. # =================== # # Install xclip for clipboard bridging until sudo apt update; do sleep 1; done until sudo apt install --no-install-recommends -y xclip; do sleep 1; done # Append h20-copy function to system-wide bashrc until sudo tee -a /etc/bash.bashrc > /dev/null <<'EOF' # h20-copy # --------- # A basic clipboard helper. # - No args: copy X11 primary selection to clipboard # - With args: run command, copy its output to clipboard # Text can only be pasted once (or twice, on some systems) before it is cleared # NOTE: h20-copy has known issues with pasting into Mullvad Browser (?!). Long-term # solution might be to write an xclip alternative from scratch. h20-copy() { if [[ $# -eq 0 ]]; then xclip -o -selection primary | xclip -i -selection clipboard -loops 2 else "$@" | xclip -selection clipboard -loops 2 fi } EOF do sleep 1; done # Make the new function available immediately until source /etc/bash.bashrc; do sleep 1; done
Go ahead and try out the new copying functionality.
h20-copy
into your terminal.You can also use h20-copy to get the output from a command into the clipboard. For example:
# Copy file contents to clipboard. h20-copy cat FILENAME
Known issues:
That last dot point can be quite frustrating, as Qubes doesn't currently allow the pasting of clipboard text back into the original VM from whence it came. This is a known issue, and I'm working on it.
When you're done, be sure to shutdown dXX.