diff --git a/etc/security-misc/emerg-shutdown/30_security_misc.conf b/etc/security-misc/emerg-shutdown/30_security_misc.conf new file mode 100644 index 0000000..a4bb394 --- /dev/null +++ b/etc/security-misc/emerg-shutdown/30_security_misc.conf @@ -0,0 +1,19 @@ +## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +## Please use "/etc/security-misc/emerg-shutdown/50_user.conf" for your custom +## configuration, which will override the defaults found here. +## When Kicksecure is updated, this file may be overwritten. + +## Set the key combo for forcing immediate shutdown. See the "Keys and +## buttons" section of "/usr/include/linux/input-event-codes.h" for possibly +## supported values. Not all keys are supported. +## +## All specified keys must be depressed at the same time to trigger a +## shutdown. Use a comma (",") to separate keys. If you want to alias certain +## keys to each other from emerg-shutdown's standpoint, use a pipe +## character("|"). +## +## The default key sequence triggers a shutdown when Ctrl+Alt+Delete is +## pressed, allowing the use of either the left or right Ctrl and Alt keys. +EMERG_SHUTDOWN_KEYS="KEY_LEFTCTRL|KEY_RIGHTCTRL,KEY_LEFTALT|KEY_RIGHTALT,KEY_DELETE" diff --git a/usr/lib/systemd/system/emerg-shutdown.service b/usr/lib/systemd/system/emerg-shutdown.service index af887f4..c1fca25 100644 --- a/usr/lib/systemd/system/emerg-shutdown.service +++ b/usr/lib/systemd/system/emerg-shutdown.service @@ -6,9 +6,8 @@ Description=Emergency shutdown when boot media is removed Documentation=https://github.com/Kicksecure/security-misc [Service] -Type=oneshot +Type=exec ExecStart=/usr/libexec/security-misc/emerg-shutdown -RemainAfterExit=true [Install] WantedBy=multi-user.target diff --git a/usr/lib/udev/rules.d/95-emerg-shutdown.rules b/usr/lib/udev/rules.d/95-emerg-shutdown.rules new file mode 100644 index 0000000..051af9f --- /dev/null +++ b/usr/lib/udev/rules.d/95-emerg-shutdown.rules @@ -0,0 +1,9 @@ +SUBSYSTEM!="input", GOTO="end" + +# new keyboard or mouse attached or removed, restart emerg-shutdown +KERNEL=="event*", ACTION=="add", ENV{ID_INPUT_KEYBOARD}=="1", RUN+="/usr/bin/systemctl restart emerg-shutdown.service" +KERNEL=="event*", ACTION=="add", ENV{ID_INPUT_KEYBOARD}=="1", GOTO="end" +KERNEL=="event*", ACTION=="remove", ENV{ID_INPUT_KEYBOARD}=="1", RUN+="/usr/bin/systemctl restart emerg-shutdown.service" +KERNEL=="event*", ACTION=="remove", ENV{ID_INPUT_KEYBOARD}=="1", GOTO="end" + +LABEL="end" diff --git a/usr/libexec/security-misc/emerg-shutdown b/usr/libexec/security-misc/emerg-shutdown index b60d3f9..d8dc7f9 100755 --- a/usr/libexec/security-misc/emerg-shutdown +++ b/usr/libexec/security-misc/emerg-shutdown @@ -3,25 +3,49 @@ # Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC # See the file COPYING for copying conditions. -gcc \ - -o \ - /run/emerg-shutdown \ - -static \ - /usr/src/security-misc/emerg-shutdown.c \ - || { - printf "%s\n" 'Could not compile force-shutdown executable!' - exit 1; - } +set -o errexit +set -o nounset +set -o errtrace +set -o pipefail -readarray -t root_devices < <(/usr/libexec/helper-scripts/get-backing-devices-for-mountpoint '/'); +## Make sure globs sort in a predictable, reproducible fashion +export LC_ALL=C -## memlockd daemonizes itself, so no need to background it -memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg +## Read emergency shutdown key configuration +for config_file in /etc/security-misc/emerg-shutdown/*.conf; do + source "${config_file}" +done +if [ -z "${EMERG_SHUTDOWN_KEYS}" ]; then + ## Default to Ctrl+Alt+Delete if nothing else is set + EMERG_SHUTDOWN_KEYS="KEY_LEFTCTRL|KEY_RIGHTCTRL,KEY_LEFTALT|KEY_RIGHTALT,KEY_DELETE" +fi +## Find the devices that make up the root device +readarray -t root_devices < <(/usr/libexec/helper-scripts/get-backing-devices-for-mountpoint '/') || true; +if [ "${#root_devices[@]}" = '0' ] \ + || [ "${root_devices[0]}" == '' ]; then + ## /dev/sda1 might be the right one... + root_devices[0]='/dev/sda1' +fi + +## Build the actual emerg-shutdown executable +if [ ! -f '/run/emerg-shutdown' ]; then + gcc \ + -o \ + /run/emerg-shutdown \ + -static \ + /usr/src/security-misc/emerg-shutdown.c \ + || { + printf "%s\n" 'Could not compile force-shutdown executable!' + exit 1; + } + +fi + +## memlockd daemonizes itself, so no need to background it. +memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg || true + +## Launch emerg-shutdown OLDIFS="$IFS" IFS=',' -/run/emerg-shutdown "--devices=${root_devices[*]}" '--keys=KEY_LEFTCTRL|KEY_RIGHTCTRL,KEY_LEFTALT|KEY_RIGHTALT,KEY_DELETE' & -IFS="$OLDIFS" -sleep 1 -disown -exit 0 +/run/emerg-shutdown "--devices=${root_devices[*]}" "--keys=${EMERG_SHUTDOWN_KEYS}" diff --git a/usr/src/security-misc/emerg-shutdown.c b/usr/src/security-misc/emerg-shutdown.c index 8d2e1cd..5a01e17 100644 --- a/usr/src/security-misc/emerg-shutdown.c +++ b/usr/src/security-misc/emerg-shutdown.c @@ -94,6 +94,8 @@ #include #include #include +#include +#include #include #define fd_stdin 0 @@ -104,6 +106,8 @@ #define input_path_size 20 #define key_flags_len 12 +int console_fd = 0; + /* Adapted from kloak/src/keycodes.c */ struct name_value { const char *name; @@ -283,6 +287,8 @@ void print(int fd, char *str) { void print_usage() { print(fd_stderr, "Usage:\n"); print(fd_stderr, " emerg-shutdown --devices=DEVICE1[,DEVICE2...] --keys=KEY_1[,KEY_2|KEY_3...]\n"); + print(fd_stderr, "Or:\n"); + print(fd_stderr, " emerg-shutdown --instant-shutdown\n"); print(fd_stderr, "Example:\n"); print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n"); } @@ -363,7 +369,74 @@ void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list } int kill_system() { - return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL); + /* + * It isn't safe to simply call the reboot syscall here - there is a + * graphics driver bug in the i915 driver on Bookworm that will throw a + * kernel warning during shutdown. Kicksecure sets panic_on_oops and + * panic_on_warn to 1 during bootup, which means this bug will cause a + * kernel panic and thus hang the system rather than shutting down. + * + * To mitigate this, we do two things: + * + * - We disable the panic_on_oops and panic_on_warn kernel settings before + * calling the reboot syscall. This way if a warn or oops does occur, at + * least it isn't as likely to block shutdown. + * - We switch virtual terminals before initiating the shutdown. This should + * hopefully keep whatever is going wrong from going wrong in the first + * place. + * + * This is probably a good idea for any system, because switching TTYs is a + * rather basic operation that is likely to work, while forcibly shutting + * down while X11 or Wayland still has control of the display is probably + * not as well tested (if it's been tested at all). + * + * Above all else though, we want to at least *try* to shutdown. Even if all + * our attempts to switch VTs fail and /proc isn't available for us to tweak + * kernel settings, we still need to try. Therefore we absolutely do not + * crash or block, except when waiting for a VT to become activated. (If VT + * activation blocks forever, the kernel is probably horribly broken and + * would probably panic imminently anyway.) + */ + + const char *panic_on_oops_path = "/proc/sys/kernel/panic_on_oops"; + const char *panic_on_warn_path = "/proc/sys/kernel/panic_on_warn"; + + int ret = 0; + int tgt_vt = 0; + int sysctl_fd = 0; + + /* Turn off panic_on_oops. */ + sysctl_fd = open(panic_on_oops_path, O_WRONLY); + if (sysctl_fd != -1) { + write(sysctl_fd, "0", 1); + close(sysctl_fd); + } + + /* Turn off panic_on_warn. */ + sysctl_fd = open(panic_on_warn_path, O_WRONLY); + if (sysctl_fd != -1) { + write(sysctl_fd, "0", 1); + close(sysctl_fd); + } + + /* Determine which VT to switch to. Anything that isn't open yet will do. */ + ret = ioctl(console_fd, VT_OPENQRY, &tgt_vt); + if (ret == -1) { + goto trykill; + } + + /* Try to switch to it. */ + ret = ioctl(console_fd, VT_ACTIVATE, tgt_vt); + if (ret == -1) { + goto trykill; + } + + /* Wait for it to become active. */ + ioctl(console_fd, VT_WAITACTIVE, tgt_vt); + +trykill: + return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, + LINUX_REBOOT_CMD_POWER_OFF, NULL); } int main(int argc, char **argv) { @@ -405,6 +478,9 @@ int main(int argc, char **argv) { } for (arg_idx = 1; arg_idx < argc; arg_idx++) { + if (strncmp(argv[arg_idx], "--instant-shutdown", strlen("--instant-shutdown")) == 0) { + kill_system(); + } if (strncmp(argv[arg_idx], "--devices=", strlen("--devices=")) == 0) { if (target_dev_name_raw_list != NULL) { print(fd_stderr, "--devices cannot be passed more than once!\n"); @@ -422,6 +498,12 @@ int main(int argc, char **argv) { } } + console_fd = open("/dev/console", O_RDWR); + if (console_fd == -1) { + print(fd_stderr, "Could not open /dev/console!\n"); + exit(1); + } + target_dev_list = safe_calloc(target_dev_list_len, sizeof(char *)); panic_key_list = safe_calloc(panic_key_list_len, sizeof(int *)); panic_key_active_list = safe_calloc(panic_key_list_len, sizeof(bool));