From f3d46ee56233c4ef0552c20304413d137e90acfe Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Fri, 9 May 2025 18:46:41 -0500 Subject: [PATCH 1/7] Add emergency shutdown feature, triggered by root device removal --- debian/control | 2 + ...rce-poweroff-on-boot-media-removal.service | 14 + .../force-poweroff-on-boot-media-removal | 24 ++ .../security-misc/security-misc-memlockd.cfg | 2 + .../force-shutdown-when-device-removed.c | 295 ++++++++++++++++++ 5 files changed, 337 insertions(+) create mode 100644 usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service create mode 100755 usr/libexec/security-misc/force-poweroff-on-boot-media-removal create mode 100644 usr/share/security-misc/security-misc-memlockd.cfg create mode 100644 usr/src/security-misc/force-shutdown-when-device-removed.c diff --git a/debian/control b/debian/control index fd56b5f..6235dad 100644 --- a/debian/control +++ b/debian/control @@ -20,6 +20,7 @@ Package: security-misc Architecture: all Depends: adduser, apparmor-profile-dist, + build-essential, dmsetup, helper-scripts, libcap2-bin, @@ -27,6 +28,7 @@ Depends: adduser, libpam-modules-bin, libpam-runtime, libpam-umask, + memlockd, python3, secure-delete, sudo, diff --git a/usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service b/usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service new file mode 100644 index 0000000..b99cf64 --- /dev/null +++ b/usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service @@ -0,0 +1,14 @@ +## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +[Unit] +Description=Emergency shutdown when boot media is removed +Documentation=https://github.com/Kicksecure/security-misc + +[Service] +Type=oneshot +ExecStart=/usr/libexec/security-misc/force-poweroff-on-boot-media-removal +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/usr/libexec/security-misc/force-poweroff-on-boot-media-removal b/usr/libexec/security-misc/force-poweroff-on-boot-media-removal new file mode 100755 index 0000000..0760ae1 --- /dev/null +++ b/usr/libexec/security-misc/force-poweroff-on-boot-media-removal @@ -0,0 +1,24 @@ +#!/bin/bash + +# Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC +# See the file COPYING for copying conditions. + +gcc \ + -o \ + /run/force-shutdown-when-device-removed \ + -static \ + /usr/src/security-misc/force-shutdown-when-device-removed.c \ + || { + printf "%s\n" 'Could not compile force-shutdown executable!' + exit 1; + } + +readarray -t root_devices < <(/usr/libexec/helper-scripts/get-backing-devices-for-mountpoint '/'); + +## memlockd daemonizes itself, so no need to background it +memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg + +/run/force-shutdown-when-device-removed "${root_devices}" & +sleep 1 +disown +exit 0 diff --git a/usr/share/security-misc/security-misc-memlockd.cfg b/usr/share/security-misc/security-misc-memlockd.cfg new file mode 100644 index 0000000..ebdc4c6 --- /dev/null +++ b/usr/share/security-misc/security-misc-memlockd.cfg @@ -0,0 +1,2 @@ +# Lock systemd and all of its library dependencies into memory ++/usr/bin/systemd diff --git a/usr/src/security-misc/force-shutdown-when-device-removed.c b/usr/src/security-misc/force-shutdown-when-device-removed.c new file mode 100644 index 0000000..c7ddd52 --- /dev/null +++ b/usr/src/security-misc/force-shutdown-when-device-removed.c @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC + * See the file COPYING for copying conditions. + */ + +/* + * This program is designed specifically to immediately and forcibly power off + * the system in the event the device providing the root filesystem is + * abruptly removed from the system. The idea is that a user can shut down + * a portable installation of Kicksecure by simply yanking the USB drive + * containing the installation from the computer. Tails provides essentially + * the same feature, however it is known for occasionally failing to do its + * job properly. + * + * The fact that we're triggering a shutdown when the device containing the + * root filesystem vanishes presents a number of significant challenges: + * + * - The device providing the entire operating system is gone. The only things + * we will still have left are the kernel, files loaded into RAM (for + * instance under /run), and anything that happens to still be in the + * system's disk cache. + * - Virtually any process on the system may abruptly crash at any time. This + * isn't just because applications may be unable to access files. The Linux + * kernel's virtual memory subsystem doesn't just page out RAM contents to a + * swap file, it will sometimes simply erase pages containing executable + * code from memory if it can reload that code from disk later when needed. + * If part of a program isn't present in memory, and then the root device + * vanishes, any attempt to use code in the absent part of the application + * will result in the application crashing. (Attempts to access data in RAM + * that happened to be paged out will result in a similar crash.) + * - We have no control over what is and isn't in the disk cache, which makes + * it unsafe to launch any dynamically linked executable. What happens if we + * need to load a missing part of libc? What if the dynamic linker itself + * needs loaded from disk? + * - Systemd could lock up at any time, since the init process isn't immune to + * having bits of it erased from RAM to free up memory. If systemd receives + * a SIGSEGV, rather than crashing (which would panic the kernel), it goes + * into an "emergency mode" that tries to keep the system as operational as + * possible even though PID 1 is now out of service. + * + * Circumventing this set of difficulties is not easy, and it might not even + * be entirely possible. To give our feature the highest chance of success: + * + * - We use memlockd to lock systemd and all libraries it depends on into + * memory. It can holds its own pretty well in the event of a segfault, but + * if its crash handler ends up re-segfaulting, that could get ugly. + * - We compile the utility at boot time, statically link it against all of + * its dependencies (really only one, glibc), and load it into /run. This + * allows for decent architecture independence while removing any dependency + * on anything that isn't in RAM, thus (hopefully!) making the process + * crash-immune. + * - Because we're static-linking against glibc, we cannot call anything + * defined in stdio.h. This is because glibc uses dlopen() to load iconv + * modules, which are used internally by glibc for locale support. Things + * defined in stdio.h may use iconv, so calling anything there will + * basically make our static-linked executable become dynamically linked, + * which could segfault it since the root filesystem is gone. We can't call + * anything that could touch Name Service Switch (NSS) either, but we have + * no need to do so, so we should be safe there. See + * https://stackoverflow.com/questions/57476533/why-is-statically-linking-glibc-discouraged + * - We can't use udev either because libudev is only available as a dynamic + * library. That means we have to listen to kernel uevents directly to + * determine when the root device vanishes. Thankfully this isn't as much of + * a pain as it might sound like. + * - We don't call out to any external process, since those external processes + * could segfault. + * + * This is likely superior to Tails' implementation, which uses udev (and thus + * dynamic linking), uses an interpreter-driven script to shut down the system + * when the root device vanishes, and calls out to external executables to + * actually shut the system down. These issues are likely why Tails' + * implementation of emergency shutdown occasionally fails. See + * https://www.reddit.com/r/tails/comments/xh8njn/tails_wont_shutdown_when_i_pull_usb_stick/ + * (there are other similar posts as well). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define fd_stdin 0 +#define fd_stdout 1 +#define fd_stderr 2 + +void print(int fd, char *str) { + size_t len = strlen(str) + 1; + while (true) { + ssize_t write_len = write(fd, str, len); + len -= write_len; + if (len == 0) { + return; + } + str += write_len; + } +} + +void print_usage() { + print(fd_stderr, "Usage:\n"); + print(fd_stderr, " force-shutdown-when-device-removed DEVICE1 [DEVICE2...]\n"); + print(fd_stderr, "Example:\n"); + print(fd_stderr, " force-shutdown-when-device-removed /dev/sda3\n"); +} + +int main(int argc, char **argv) { + if (getuid() != 0) { + print(fd_stderr, "This program must be run as root!\n"); + exit(1); + } + + if (argc < 2) { + print(fd_stderr, "Invalid number of arguments!\n"); + print_usage(); + exit(1); + } + + size_t target_dev_name_list_len = argc - 1; + char **target_dev_name_list = calloc(target_dev_name_list_len, + sizeof(char *)); + if (target_dev_name_list == NULL) { + print(fd_stderr, "Out of memory during early setup!\n"); + exit(1); + } + + for (int i = 1; i < argc; i++) { + char *target_dev_path = argv[i]; + if (access(target_dev_path, F_OK) != 0) { + print(fd_stderr, "One of the specified devices does not exist!\n"); + print_usage(); + exit(1); + } + + if (strncmp(target_dev_path, "/dev/sr", strlen("/dev/sr")) != 0 + && strncmp(target_dev_path, "/dev/nvme", strlen("/dev/nvme")) != 0 + && strncmp(target_dev_path, "/dev/sd", strlen("/dev/sd")) != 0 + && strncmp(target_dev_path, "/dev/mmc", strlen("/dev/mmc")) != 0 + && strncmp(target_dev_path, "/dev/vd", strlen("/dev/vd")) != 0 + && strncmp(target_dev_path, "/dev/xvd", strlen("/dev/xvd")) != 0 + && strncmp(target_dev_path, "/dev/hd", strlen("/dev/hd")) != 0) { + print(fd_stderr, "One of the specified devices is not supported!\n"); + print_usage(); + exit(1); + } + + size_t device_path_slash_count = 0; + for (size_t j = 0; j < strlen(target_dev_path); j++) { + if (target_dev_path[j] == '/') { + device_path_slash_count++; + } + } + if (device_path_slash_count != 2) { + print(fd_stderr, "One of the specified devices is not supported!\n"); + print_usage(); + exit(1); + } + + char *target_dev_parse = calloc(1, strlen(target_dev_path) + 1); + if (target_dev_parse == NULL) { + print(fd_stderr, "Out of memory during early setup!\n"); + exit(1); + } + memcpy(target_dev_parse, target_dev_path, strlen(target_dev_path) + 1); + + /* returns "dev" */ + char *target_dev_name = strtok(target_dev_parse, "/"); + /* returns the actual device name we want */ + target_dev_name = strtok(NULL, "/"); + if (target_dev_name == NULL) { + print(fd_stderr, "One of the specified devices is not supported!\n"); + print_usage(); + exit(1); + } + + target_dev_name_list[i - 1] = calloc(1, strlen(target_dev_name) + 1); + memcpy(target_dev_name_list[i - 1], target_dev_name, + strlen(target_dev_name) + 1); + free(target_dev_parse); + } + + struct sockaddr_nl sa = { + .nl_family = AF_NETLINK, + .nl_pad = 0, + .nl_pid = getpid(), + .nl_groups = NETLINK_KOBJECT_UEVENT, + }; + int ns = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); + if (ns < 0) { + print(fd_stderr, "Failed to create netlink socket!\n"); + exit(1); + } + int ret = bind(ns, (struct sockaddr *) &sa, sizeof(sa)); + if (ret < 0) { + print(fd_stderr, "Failed to bind netlink socket!\n"); + exit(1); + } + + while (true) { + /* + * So, you looked at `man 7 netlink`, then looked at this code, and can't + * figure out how on earth any of this makes sense? Well guess what, turns + * out NETLINK_KOBJECT_UEVENT messages break all of the rules about how + * netlink messages work specified in that manpage. What you actually + * get... well, depends. + * + * - The messages we actually want are just NUL-separated string lists. + * These are the actual kernel uevents. + * - Mixed in with those will be uevents generated by systemd-udevd, which + * use a different format and are unsuitable for our purposes. We have + * to ignore those. Thankfully those messages start with the + * NUL-terminated string "libudev" so they're easy to filter out. + */ + + int len; + char buf[16384]; + struct iovec iov = { buf, sizeof(buf) }; + struct sockaddr_nl sa2; + struct msghdr msg = { &sa2, sizeof(sa2), &iov, 1, NULL, 0, 0 }; + len = recvmsg(ns, &msg, 0); + if (len == -1) { + reboot(RB_POWER_OFF); + //print(fd_stderr, "SHUTDOWN!!!\n"); + exit(0); + } + + if (len < 8) { + /* There aren't any super-short messages we're interested in, discard + * them */ + continue; + } + if (memcmp(buf, "libudev", 8) == 0) { + /* udevd message, ignore */ + continue; + } + + char *tmpbuf = buf; + bool device_removed = false; + while (len > 0) { + if (strcmp(tmpbuf, "ACTION=remove") == 0) { + device_removed = true; + goto next_str; + } + + if (strncmp(tmpbuf, "DEVNAME=", strlen("DEVNAME=")) == 0) { + if (device_removed) { + char *rem_devname_line; + /* + * Try to allocate the memory needed to check DEVNAME in a loop. We + * really do not want to simply abort here due to an out of memory + * condition, because that would result in the shutdown never + * occurring. We also don't want to force a shutdown when memory + * runs out, as that could result in the user losing work because + * they opened too many browser tabs. + */ + while(true) { + rem_devname_line = calloc(1, strlen(tmpbuf) + 1); + if (rem_devname_line == NULL) { + print(fd_stderr, "Out of memory while parsing devname, retrying in one second\n"); + sleep(1); + continue; + } else { + break; + } + } + + memcpy(rem_devname_line, tmpbuf, strlen(tmpbuf) + 1); + /* returns DEVNAME */ + char *rem_dev_name = strtok(rem_devname_line, "="); + /* returns the actual device name */ + rem_dev_name = strtok(NULL, "="); + if (rem_dev_name == NULL) { + continue; + } + + for (int i = 0; i < target_dev_name_list_len; i++) { + if (strcmp(rem_dev_name, target_dev_name_list[i]) == 0) { + reboot(RB_POWER_OFF); + //print(fd_stderr, "SHUTDOWN!!!\n"); + exit(0); + } + } + free(rem_devname_line); + } + } + +next_str: + len -= strlen(tmpbuf) + 1; + tmpbuf += strlen(tmpbuf) + 1; + } + } +} From dfb6f143f0324d0903ae2dd106bc0fb6907c1cb0 Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Sun, 13 Jul 2025 20:53:29 -0500 Subject: [PATCH 2/7] Add panic key handling to emergency shutdown utility --- .../force-shutdown-when-device-removed.c | 630 +++++++++++++++--- 1 file changed, 529 insertions(+), 101 deletions(-) diff --git a/usr/src/security-misc/force-shutdown-when-device-removed.c b/usr/src/security-misc/force-shutdown-when-device-removed.c index c7ddd52..e7b887f 100644 --- a/usr/src/security-misc/force-shutdown-when-device-removed.c +++ b/usr/src/security-misc/force-shutdown-when-device-removed.c @@ -74,6 +74,13 @@ * (there are other similar posts as well). */ +/* + * TODO: Consider handling signals more gracefully (perhaps use ppoll instead + * of poll, handle things like EINTR, etc.). Right now the plan is to simply + * terminate when a signal is received and let systemd restart the process, + * but it might be better to just be signal-resilient. + */ + #include #include #include @@ -83,11 +90,183 @@ #include #include #include +#include +#include +#include +#include #define fd_stdin 0 #define fd_stdout 1 #define fd_stderr 2 +#define max_inputs 255 +#define input_path_size 20 +#define key_flags_len 12 + +/* Adapted from kloak/src/keycodes.c */ +struct name_value { + const char *name; + const int value; +}; +static struct name_value key_table[] = { + {"KEY_ESC", KEY_ESC}, + {"KEY_1", KEY_1}, + {"KEY_2", KEY_2}, + {"KEY_3", KEY_3}, + {"KEY_4", KEY_4}, + {"KEY_5", KEY_5}, + {"KEY_6", KEY_6}, + {"KEY_7", KEY_7}, + {"KEY_8", KEY_8}, + {"KEY_9", KEY_9}, + {"KEY_0", KEY_0}, + {"KEY_MINUS", KEY_MINUS}, + {"KEY_EQUAL", KEY_EQUAL}, + {"KEY_BACKSPACE", KEY_BACKSPACE}, + {"KEY_TAB", KEY_TAB}, + {"KEY_Q", KEY_Q}, + {"KEY_W", KEY_W}, + {"KEY_E", KEY_E}, + {"KEY_R", KEY_R}, + {"KEY_T", KEY_T}, + {"KEY_Y", KEY_Y}, + {"KEY_U", KEY_U}, + {"KEY_I", KEY_I}, + {"KEY_O", KEY_O}, + {"KEY_P", KEY_P}, + {"KEY_LEFTBRACE", KEY_LEFTBRACE}, + {"KEY_RIGHTBRACE", KEY_RIGHTBRACE}, + {"KEY_ENTER", KEY_ENTER}, + {"KEY_LEFTCTRL", KEY_LEFTCTRL}, + {"KEY_A", KEY_A}, + {"KEY_S", KEY_S}, + {"KEY_D", KEY_D}, + {"KEY_F", KEY_F}, + {"KEY_G", KEY_G}, + {"KEY_H", KEY_H}, + {"KEY_J", KEY_J}, + {"KEY_K", KEY_K}, + {"KEY_L", KEY_L}, + {"KEY_SEMICOLON", KEY_SEMICOLON}, + {"KEY_APOSTROPHE", KEY_APOSTROPHE}, + {"KEY_GRAVE", KEY_GRAVE}, + {"KEY_LEFTSHIFT", KEY_LEFTSHIFT}, + {"KEY_BACKSLASH", KEY_BACKSLASH}, + {"KEY_Z", KEY_Z}, + {"KEY_X", KEY_X}, + {"KEY_C", KEY_C}, + {"KEY_V", KEY_V}, + {"KEY_B", KEY_B}, + {"KEY_N", KEY_N}, + {"KEY_M", KEY_M}, + {"KEY_COMMA", KEY_COMMA}, + {"KEY_DOT", KEY_DOT}, + {"KEY_SLASH", KEY_SLASH}, + {"KEY_RIGHTSHIFT", KEY_RIGHTSHIFT}, + {"KEY_KPASTERISK", KEY_KPASTERISK}, + {"KEY_LEFTALT", KEY_LEFTALT}, + {"KEY_SPACE", KEY_SPACE}, + {"KEY_CAPSLOCK", KEY_CAPSLOCK}, + {"KEY_F1", KEY_F1}, + {"KEY_F2", KEY_F2}, + {"KEY_F3", KEY_F3}, + {"KEY_F4", KEY_F4}, + {"KEY_F5", KEY_F5}, + {"KEY_F6", KEY_F6}, + {"KEY_F7", KEY_F7}, + {"KEY_F8", KEY_F8}, + {"KEY_F9", KEY_F9}, + {"KEY_F10", KEY_F10}, + {"KEY_NUMLOCK", KEY_NUMLOCK}, + {"KEY_SCROLLLOCK", KEY_SCROLLLOCK}, + {"KEY_KP7", KEY_KP7}, + {"KEY_KP8", KEY_KP8}, + {"KEY_KP9", KEY_KP9}, + {"KEY_KPMINUS", KEY_KPMINUS}, + {"KEY_KP4", KEY_KP4}, + {"KEY_KP5", KEY_KP5}, + {"KEY_KP6", KEY_KP6}, + {"KEY_KPPLUS", KEY_KPPLUS}, + {"KEY_KP1", KEY_KP1}, + {"KEY_KP2", KEY_KP2}, + {"KEY_KP3", KEY_KP3}, + {"KEY_KP0", KEY_KP0}, + {"KEY_KPDOT", KEY_KPDOT}, + {"KEY_ZENKAKUHANKAKU", KEY_ZENKAKUHANKAKU}, + {"KEY_102ND", KEY_102ND}, + {"KEY_F11", KEY_F11}, + {"KEY_F12", KEY_F12}, + {"KEY_RO", KEY_RO}, + {"KEY_KATAKANA", KEY_KATAKANA}, + {"KEY_HIRAGANA", KEY_HIRAGANA}, + {"KEY_HENKAN", KEY_HENKAN}, + {"KEY_KATAKANAHIRAGANA", KEY_KATAKANAHIRAGANA}, + {"KEY_MUHENKAN", KEY_MUHENKAN}, + {"KEY_KPJPCOMMA", KEY_KPJPCOMMA}, + {"KEY_KPENTER", KEY_KPENTER}, + {"KEY_RIGHTCTRL", KEY_RIGHTCTRL}, + {"KEY_KPSLASH", KEY_KPSLASH}, + {"KEY_SYSRQ", KEY_SYSRQ}, + {"KEY_RIGHTALT", KEY_RIGHTALT}, + {"KEY_LINEFEED", KEY_LINEFEED}, + {"KEY_HOME", KEY_HOME}, + {"KEY_UP", KEY_UP}, + {"KEY_PAGEUP", KEY_PAGEUP}, + {"KEY_LEFT", KEY_LEFT}, + {"KEY_RIGHT", KEY_RIGHT}, + {"KEY_END", KEY_END}, + {"KEY_DOWN", KEY_DOWN}, + {"KEY_PAGEDOWN", KEY_PAGEDOWN}, + {"KEY_INSERT", KEY_INSERT}, + {"KEY_DELETE", KEY_DELETE}, + {"KEY_MACRO", KEY_MACRO}, + {"KEY_MUTE", KEY_MUTE}, + {"KEY_VOLUMEDOWN", KEY_VOLUMEDOWN}, + {"KEY_VOLUMEUP", KEY_VOLUMEUP}, + {"KEY_POWER", KEY_POWER}, + {"KEY_POWER2", KEY_POWER2}, + {"KEY_KPEQUAL", KEY_KPEQUAL}, + {"KEY_KPPLUSMINUS", KEY_KPPLUSMINUS}, + {"KEY_PAUSE", KEY_PAUSE}, + {"KEY_SCALE", KEY_SCALE}, + {"KEY_KPCOMMA", KEY_KPCOMMA}, + {"KEY_HANGEUL", KEY_HANGEUL}, + {"KEY_HANGUEL", KEY_HANGUEL}, + {"KEY_HANJA", KEY_HANJA}, + {"KEY_YEN", KEY_YEN}, + {"KEY_LEFTMETA", KEY_LEFTMETA}, + {"KEY_RIGHTMETA", KEY_RIGHTMETA}, + {"KEY_COMPOSE", KEY_COMPOSE}, + {"KEY_F13", KEY_F13}, + {"KEY_F14", KEY_F14}, + {"KEY_F15", KEY_F15}, + {"KEY_F16", KEY_F16}, + {"KEY_F17", KEY_F17}, + {"KEY_F18", KEY_F18}, + {"KEY_F19", KEY_F19}, + {"KEY_F20", KEY_F20}, + {"KEY_F21", KEY_F21}, + {"KEY_F22", KEY_F22}, + {"KEY_F23", KEY_F23}, + {"KEY_F24", KEY_F24}, + {"KEY_UNKNOWN", KEY_UNKNOWN}, + {NULL, 0} +}; +int lookup_keycode(const char *name) { + struct name_value *p; + for (p = key_table; p->name != NULL; ++p) { + if (strcmp(p->name, name) == 0) { + return p->value; + } + } + return -1; +} + +/* Adapted from systemd/src/login/logind-button.c */ +bool bitset_get(const uint64_t *bits, uint32_t i) { + return (bits[i / 64] >> (i % 64)) & 1UL; +} + void print(int fd, char *str) { size_t len = strlen(str) + 1; while (true) { @@ -102,35 +281,151 @@ void print(int fd, char *str) { void print_usage() { print(fd_stderr, "Usage:\n"); - print(fd_stderr, " force-shutdown-when-device-removed DEVICE1 [DEVICE2...]\n"); + print(fd_stderr, " emerg-shutdown --devices=DEVICE1[,DEVICE2...] --keys=KEY_1[,KEY_2...]\n"); print(fd_stderr, "Example:\n"); - print(fd_stderr, " force-shutdown-when-device-removed /dev/sda3\n"); + print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n"); +} + +void *safe_calloc(size_t nmemb, size_t size) { + void *ret_buf = calloc(nmemb, size); + if (ret_buf == NULL) { + print(fd_stderr, "Out of memory!\n"); + exit(1); + } + return ret_buf; +} + +void *safe_reallocarray(void *ptr, size_t nmemb, size_t size) { + void *ret_buf = reallocarray(ptr, nmemb, size); + if (ret_buf == NULL) { + print(fd_stderr, "Out of memory!\n"); + exit(1); + } + return ret_buf; +} + +/* Inspired by https://www.strudel.org.uk/itoa/ */ +char *int_to_str(uint32_t val) { + static char buf[11]; + int8_t i; + char *rslt = NULL; + const char *digits = "0123456789"; + + buf[10] = '\0'; + + for (i = 9; i >= 0; i--) { + buf[i] = digits[val % 10]; + val /= 10; + if (val == 0) { + break; + } + } + + rslt = safe_calloc(1, 11 - i); + memcpy(rslt, buf + i, 11 - i); + return rslt; +} + +void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list_ref) { + char **result_list = NULL; + size_t result_list_len = 0; + int arg_copy_len = strlen(arg) + 1; + char *arg_copy = safe_calloc(1, arg_copy_len); + char *arg_val; + char *arg_part; + + memcpy(arg_copy, arg, arg_copy_len); + /* returns "--whatever" */ + arg_val = strtok(arg_copy, "="); + /* returns everything after the = sign */ + arg_val = strtok(NULL, ""); + + arg_part = strtok(arg_val, ","); + if (arg_part == NULL) { + return; + } + + do { + result_list_len++; + result_list = safe_reallocarray(result_list, result_list_len, sizeof(char *)); + result_list[result_list_len - 1] = safe_calloc(1, strlen(arg_part) + 1); + strcpy(result_list[result_list_len - 1], arg_part); + } while ((arg_part = strtok(NULL, ",")) != NULL); + + *result_list_len_ref = result_list_len; + *result_list_ref = result_list; + free(arg_copy); } int main(int argc, char **argv) { + /* Working variables */ + size_t target_dev_list_len = 0; + char **target_dev_name_raw_list = NULL; + size_t panic_key_list_len = 0; + char **panic_key_str_list = NULL; + char **target_dev_list = NULL; + int *panic_key_list = NULL; + bool *panic_key_active_list = NULL; + size_t event_fd_list_len = 0; + int *event_fd_list = NULL; + char input_path_buf[input_path_size]; + struct pollfd *pollfd_list = NULL; + struct input_event ie_buf[64]; + + /* Index variables */ + int arg_idx = 0; + size_t tdl_idx = 0; + size_t tdp_char_idx = 0; + size_t pkl_idx = 0; + int input_idx = 0; + size_t efl_idx = 0; + int ie_idx = 0; + + /* Prerequisite check */ if (getuid() != 0) { print(fd_stderr, "This program must be run as root!\n"); exit(1); } + /* Argument parsing */ if (argc < 2) { print(fd_stderr, "Invalid number of arguments!\n"); print_usage(); exit(1); } - size_t target_dev_name_list_len = argc - 1; - char **target_dev_name_list = calloc(target_dev_name_list_len, - sizeof(char *)); - if (target_dev_name_list == NULL) { - print(fd_stderr, "Out of memory during early setup!\n"); - exit(1); + for (arg_idx = 1; arg_idx < argc; arg_idx++) { + 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"); + print_usage(); + exit(1); + } + load_list(argv[arg_idx], &target_dev_list_len, &target_dev_name_raw_list); + } else if (strncmp(argv[arg_idx], "--keys=", strlen("--keys=")) == 0) { + if (panic_key_str_list != NULL) { + print(fd_stderr, "--keys cannot be passed more than once!\n"); + print_usage(); + exit(1); + } + load_list(argv[arg_idx], &panic_key_list_len, &panic_key_str_list); + } } - for (int i = 1; i < argc; i++) { - char *target_dev_path = argv[i]; + 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)); + + for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { + char *target_dev_path = target_dev_name_raw_list[tdl_idx]; + size_t device_path_slash_count = 0; + char *target_dev_parse = safe_calloc(1, strlen(target_dev_path) + 1); + char *target_dev_name = NULL; + if (access(target_dev_path, F_OK) != 0) { - print(fd_stderr, "One of the specified devices does not exist!\n"); + print(fd_stderr, "The device '"); + print(fd_stderr, target_dev_path); + print(fd_stderr, "' does not exist!\n"); print_usage(); exit(1); } @@ -142,46 +437,58 @@ int main(int argc, char **argv) { && strncmp(target_dev_path, "/dev/vd", strlen("/dev/vd")) != 0 && strncmp(target_dev_path, "/dev/xvd", strlen("/dev/xvd")) != 0 && strncmp(target_dev_path, "/dev/hd", strlen("/dev/hd")) != 0) { - print(fd_stderr, "One of the specified devices is not supported!\n"); + print(fd_stderr, "The device '"); + print(fd_stderr, target_dev_path); + print(fd_stderr, "' is not supported!\n"); print_usage(); exit(1); } - size_t device_path_slash_count = 0; - for (size_t j = 0; j < strlen(target_dev_path); j++) { - if (target_dev_path[j] == '/') { + for (tdp_char_idx = 0; tdp_char_idx < strlen(target_dev_path); tdp_char_idx++) { + if (target_dev_path[tdp_char_idx] == '/') { device_path_slash_count++; } } if (device_path_slash_count != 2) { - print(fd_stderr, "One of the specified devices is not supported!\n"); + print(fd_stderr, "The device '"); + print(fd_stderr, target_dev_path); + print(fd_stderr, "' is not supported!\n"); print_usage(); exit(1); } - char *target_dev_parse = calloc(1, strlen(target_dev_path) + 1); - if (target_dev_parse == NULL) { - print(fd_stderr, "Out of memory during early setup!\n"); - exit(1); - } memcpy(target_dev_parse, target_dev_path, strlen(target_dev_path) + 1); /* returns "dev" */ - char *target_dev_name = strtok(target_dev_parse, "/"); + target_dev_name = strtok(target_dev_parse, "/"); /* returns the actual device name we want */ target_dev_name = strtok(NULL, "/"); if (target_dev_name == NULL) { - print(fd_stderr, "One of the specified devices is not supported!\n"); + print(fd_stderr, "The device '"); + print(fd_stderr, target_dev_path); + print(fd_stderr, "' is not supported!\n"); print_usage(); exit(1); } - target_dev_name_list[i - 1] = calloc(1, strlen(target_dev_name) + 1); - memcpy(target_dev_name_list[i - 1], target_dev_name, - strlen(target_dev_name) + 1); + target_dev_list[tdl_idx] = calloc(1, strlen(target_dev_name) + 1); + memcpy(target_dev_list[tdl_idx], target_dev_name, strlen(target_dev_name) + 1); free(target_dev_parse); } + for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { + int keycode = lookup_keycode(panic_key_str_list[pkl_idx]); + if (keycode < 0) { + print(fd_stderr, "Invalid key code '"); + print(fd_stderr, panic_key_str_list[pkl_idx]); + print(fd_stderr, "'!\n"); + print_usage(); + exit(1); + } + panic_key_list[pkl_idx] = keycode; + } + + /* Device event listener setup */ struct sockaddr_nl sa = { .nl_family = AF_NETLINK, .nl_pad = 0, @@ -199,97 +506,218 @@ int main(int argc, char **argv) { exit(1); } - while (true) { - /* - * So, you looked at `man 7 netlink`, then looked at this code, and can't - * figure out how on earth any of this makes sense? Well guess what, turns - * out NETLINK_KOBJECT_UEVENT messages break all of the rules about how - * netlink messages work specified in that manpage. What you actually - * get... well, depends. - * - * - The messages we actually want are just NUL-separated string lists. - * These are the actual kernel uevents. - * - Mixed in with those will be uevents generated by systemd-udevd, which - * use a different format and are unsuitable for our purposes. We have - * to ignore those. Thankfully those messages start with the - * NUL-terminated string "libudev" so they're easy to filter out. - */ + /* Keyboard event listener setup + * Heavily inspired by systemd/src/login/logind-button.c and + * kloak/src/main.c */ + for (input_idx = 0; input_idx <= max_inputs; input_idx++) { + int tmp_fd = 0; + uint64_t key_flags[key_flags_len]; + bool supports_panic = true; + char *loop_str = NULL; - int len; - char buf[16384]; - struct iovec iov = { buf, sizeof(buf) }; - struct sockaddr_nl sa2; - struct msghdr msg = { &sa2, sizeof(sa2), &iov, 1, NULL, 0, 0 }; - len = recvmsg(ns, &msg, 0); - if (len == -1) { - reboot(RB_POWER_OFF); - //print(fd_stderr, "SHUTDOWN!!!\n"); - exit(0); - } + strcpy(input_path_buf, "/dev/input/event"); + loop_str = int_to_str(input_idx); + strcat(input_path_buf, loop_str); + free(loop_str); - if (len < 8) { - /* There aren't any super-short messages we're interested in, discard - * them */ - continue; - } - if (memcmp(buf, "libudev", 8) == 0) { - /* udevd message, ignore */ + tmp_fd = open(input_path_buf, O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK); + if (tmp_fd < 0) { continue; } - char *tmpbuf = buf; - bool device_removed = false; - while (len > 0) { - if (strcmp(tmpbuf, "ACTION=remove") == 0) { - device_removed = true; - goto next_str; + if (ioctl(tmp_fd, EVIOCGBIT(EV_SYN, sizeof(key_flags)), key_flags) < 0) { + print(fd_stderr, "Failed to query properties of input device '"); + print(fd_stderr, input_path_buf); + print(fd_stderr, "'!\n"); + exit(1); + } + + if (!bitset_get(key_flags, EV_KEY)) { + continue; + } + + if (ioctl(tmp_fd, EVIOCGBIT(EV_KEY, sizeof(key_flags)), key_flags) < 0) { + print(fd_stderr, "Failed to query keys available on input device '"); + print(fd_stderr, input_path_buf); + print(fd_stderr, "'!\n"); + exit(1); + } + + for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { + if (!bitset_get(key_flags, panic_key_list[pkl_idx])) { + supports_panic = false; + break; + } + } + if (!supports_panic) { + continue; + } + + event_fd_list_len++; + event_fd_list = safe_reallocarray(event_fd_list, event_fd_list_len, sizeof(int)); + event_fd_list[event_fd_list_len - 1] = tmp_fd; + } + + if (event_fd_list_len == 0) { + print(fd_stderr, "Failed to find any input device supporting panic keys!\n"); + exit(1); + } + + /* Poll setup */ + pollfd_list = safe_calloc(event_fd_list_len + 1, sizeof(struct pollfd)); + for (efl_idx = 0; efl_idx < event_fd_list_len; efl_idx++) { + pollfd_list[efl_idx].fd = event_fd_list[efl_idx]; + pollfd_list[efl_idx].events = POLLIN; + } + pollfd_list[event_fd_list_len].fd = ns; + pollfd_list[event_fd_list_len].events = POLLIN; + + /* Event loop */ + while (poll(pollfd_list, event_fd_list_len + 1, -1) != -1) { + /* Panic key handler */ + for (efl_idx = 0; efl_idx < event_fd_list_len; efl_idx++) { + if (!(pollfd_list[efl_idx].revents & POLLIN)) { + continue; } - if (strncmp(tmpbuf, "DEVNAME=", strlen("DEVNAME=")) == 0) { - if (device_removed) { - char *rem_devname_line; - /* - * Try to allocate the memory needed to check DEVNAME in a loop. We - * really do not want to simply abort here due to an out of memory - * condition, because that would result in the shutdown never - * occurring. We also don't want to force a shutdown when memory - * runs out, as that could result in the user losing work because - * they opened too many browser tabs. - */ - while(true) { - rem_devname_line = calloc(1, strlen(tmpbuf) + 1); - if (rem_devname_line == NULL) { - print(fd_stderr, "Out of memory while parsing devname, retrying in one second\n"); - sleep(1); - continue; + size_t ieread_bytes = read(event_fd_list[efl_idx], ie_buf, sizeof(struct input_event) * 64); + + if (ieread_bytes == -1 + || ieread_bytes == 0 + || (ieread_bytes % sizeof(struct input_event)) != 0) { + /* This will probably terminate the service if the user unplugs a + * keyboard or similar, however systemd can start it again. The + * alternative is to handle device hotplug, which sounds like a + * recipe for bugs. */ + print(fd_stderr, "Error reading from input device!\n"); + exit(1); + } + + for (ie_idx = 0; ie_idx < ieread_bytes / sizeof(struct input_event); ie_idx++) { + if (ie_buf[ie_idx].type != EV_KEY) { + continue; + } + + for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { + if (ie_buf[ie_idx].code == panic_key_list[pkl_idx]) { + if (ie_buf[ie_idx].value == 0) { + panic_key_active_list[pkl_idx] = false; } else { - break; + panic_key_active_list[pkl_idx] = true; } } + } - memcpy(rem_devname_line, tmpbuf, strlen(tmpbuf) + 1); - /* returns DEVNAME */ - char *rem_dev_name = strtok(rem_devname_line, "="); - /* returns the actual device name */ - rem_dev_name = strtok(NULL, "="); - if (rem_dev_name == NULL) { - continue; + for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { + if (!panic_key_active_list[pkl_idx]) { + break; } - - for (int i = 0; i < target_dev_name_list_len; i++) { - if (strcmp(rem_dev_name, target_dev_name_list[i]) == 0) { - reboot(RB_POWER_OFF); - //print(fd_stderr, "SHUTDOWN!!!\n"); - exit(0); - } + if (pkl_idx == (panic_key_list_len - 1)) { + print(fd_stderr, "SHUTDOWN!!!\n"); + exit(0); } - free(rem_devname_line); } } + } -next_str: - len -= strlen(tmpbuf) + 1; - tmpbuf += strlen(tmpbuf) + 1; + /* Netlink socket handler */ + if (pollfd_list[event_fd_list_len].revents & POLLIN) { + /* + * So, you looked at `man 7 netlink`, then looked at this code, and can't + * figure out how on earth any of this makes sense? Well guess what, turns + * out NETLINK_KOBJECT_UEVENT messages break all of the rules about how + * netlink messages work specified in that manpage. What you actually + * get... well, depends. + * + * - The messages we actually want are just NUL-separated string lists. + * These are the actual kernel uevents. + * - Mixed in with those will be uevents generated by systemd-udevd, which + * use a different format and are unsuitable for our purposes. We have + * to ignore those. Thankfully those messages start with the + * NUL-terminated string "libudev" so they're easy to filter out. + */ + + int len; + char buf[16384]; + struct iovec iov = { buf, sizeof(buf) }; + struct sockaddr_nl sa2; + struct msghdr msg = { &sa2, sizeof(sa2), &iov, 1, NULL, 0, 0 }; + char *tmpbuf = NULL; + bool device_removed = false; + + len = recvmsg(ns, &msg, 0); + if (len == -1) { + //reboot(RB_POWER_OFF); + print(fd_stderr, "SHUTDOWN!!!\n"); + exit(0); + } + + if (len < 8) { + /* There aren't any super-short messages we're interested in, discard + * them */ + continue; + } + if (memcmp(buf, "libudev", 8) == 0) { + /* udevd message, ignore */ + continue; + } + + tmpbuf = buf; + while (len > 0) { + if (strcmp(tmpbuf, "ACTION=remove") == 0) { + device_removed = true; + goto next_str; + } + + if (strncmp(tmpbuf, "DEVNAME=", strlen("DEVNAME=")) == 0) { + if (device_removed) { + char *rem_devname_line = NULL; + char *rem_dev_name = NULL; + + /* + * Try to allocate the memory needed to check DEVNAME in a loop. We + * really do not want to simply abort here due to an out of memory + * condition, because that would result in the shutdown never + * occurring. We also don't want to force a shutdown when memory + * runs out, as that could result in the user losing work because + * they opened too many browser tabs. + */ + while(true) { + rem_devname_line = calloc(1, strlen(tmpbuf) + 1); + if (rem_devname_line == NULL) { + print(fd_stderr, "Out of memory while parsing devname, retrying in one second\n"); + sleep(1); + continue; + } else { + break; + } + } + + memcpy(rem_devname_line, tmpbuf, strlen(tmpbuf) + 1); + /* returns DEVNAME */ + rem_dev_name = strtok(rem_devname_line, "="); + /* returns the actual device name */ + rem_dev_name = strtok(NULL, "="); + if (rem_dev_name == NULL) { + free(rem_devname_line); + continue; + } + + for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { + if (strcmp(rem_dev_name, target_dev_list[tdl_idx]) == 0) { + //reboot(RB_POWER_OFF); + print(fd_stderr, "SHUTDOWN!!!\n"); + exit(0); + } + } + free(rem_devname_line); + } + } + + next_str: + len -= strlen(tmpbuf) + 1; + tmpbuf += strlen(tmpbuf) + 1; + } } } } From e387086de4b6e6b90b23d4c32ddf8a566beb858c Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Mon, 14 Jul 2025 21:05:16 -0500 Subject: [PATCH 3/7] Allow specifying alternative keys in panic key combo, fix optical disk eject handling --- ...removal.service => emerg-shutdown.service} | 2 +- ...f-on-boot-media-removal => emerg-shutdown} | 9 +- ...when-device-removed.c => emerg-shutdown.c} | 90 +++++++++++++------ 3 files changed, 70 insertions(+), 31 deletions(-) rename usr/lib/systemd/system/{force-poweroff-on-boot-media-removal.service => emerg-shutdown.service} (81%) rename usr/libexec/security-misc/{force-poweroff-on-boot-media-removal => emerg-shutdown} (68%) rename usr/src/security-misc/{force-shutdown-when-device-removed.c => emerg-shutdown.c} (91%) diff --git a/usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service b/usr/lib/systemd/system/emerg-shutdown.service similarity index 81% rename from usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service rename to usr/lib/systemd/system/emerg-shutdown.service index b99cf64..af887f4 100644 --- a/usr/lib/systemd/system/force-poweroff-on-boot-media-removal.service +++ b/usr/lib/systemd/system/emerg-shutdown.service @@ -7,7 +7,7 @@ Documentation=https://github.com/Kicksecure/security-misc [Service] Type=oneshot -ExecStart=/usr/libexec/security-misc/force-poweroff-on-boot-media-removal +ExecStart=/usr/libexec/security-misc/emerg-shutdown RemainAfterExit=true [Install] diff --git a/usr/libexec/security-misc/force-poweroff-on-boot-media-removal b/usr/libexec/security-misc/emerg-shutdown similarity index 68% rename from usr/libexec/security-misc/force-poweroff-on-boot-media-removal rename to usr/libexec/security-misc/emerg-shutdown index 0760ae1..b60d3f9 100755 --- a/usr/libexec/security-misc/force-poweroff-on-boot-media-removal +++ b/usr/libexec/security-misc/emerg-shutdown @@ -5,9 +5,9 @@ gcc \ -o \ - /run/force-shutdown-when-device-removed \ + /run/emerg-shutdown \ -static \ - /usr/src/security-misc/force-shutdown-when-device-removed.c \ + /usr/src/security-misc/emerg-shutdown.c \ || { printf "%s\n" 'Could not compile force-shutdown executable!' exit 1; @@ -18,7 +18,10 @@ readarray -t root_devices < <(/usr/libexec/helper-scripts/get-backing-devices-fo ## memlockd daemonizes itself, so no need to background it memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg -/run/force-shutdown-when-device-removed "${root_devices}" & +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 diff --git a/usr/src/security-misc/force-shutdown-when-device-removed.c b/usr/src/security-misc/emerg-shutdown.c similarity index 91% rename from usr/src/security-misc/force-shutdown-when-device-removed.c rename to usr/src/security-misc/emerg-shutdown.c index e7b887f..bc5f95d 100644 --- a/usr/src/security-misc/force-shutdown-when-device-removed.c +++ b/usr/src/security-misc/emerg-shutdown.c @@ -281,7 +281,7 @@ 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...]\n"); + print(fd_stderr, " emerg-shutdown --devices=DEVICE1[,DEVICE2...] --keys=KEY_1[,KEY_2|KEY_3...]\n"); print(fd_stderr, "Example:\n"); print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n"); } @@ -326,7 +326,7 @@ char *int_to_str(uint32_t val) { return rslt; } -void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list_ref) { +void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list_ref, const char *sep, bool parse_opt) { char **result_list = NULL; size_t result_list_len = 0; int arg_copy_len = strlen(arg) + 1; @@ -335,12 +335,16 @@ void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list char *arg_part; memcpy(arg_copy, arg, arg_copy_len); - /* returns "--whatever" */ - arg_val = strtok(arg_copy, "="); - /* returns everything after the = sign */ - arg_val = strtok(NULL, ""); + if (parse_opt) { + /* returns "--whatever" */ + arg_val = strtok(arg_copy, "="); + /* returns everything after the = sign */ + arg_val = strtok(NULL, ""); + } else { + arg_val = arg_copy; + } - arg_part = strtok(arg_val, ","); + arg_part = strtok(arg_val, sep); if (arg_part == NULL) { return; } @@ -364,7 +368,7 @@ int main(int argc, char **argv) { size_t panic_key_list_len = 0; char **panic_key_str_list = NULL; char **target_dev_list = NULL; - int *panic_key_list = NULL; + int **panic_key_list = NULL; bool *panic_key_active_list = NULL; size_t event_fd_list_len = 0; int *event_fd_list = NULL; @@ -380,6 +384,7 @@ int main(int argc, char **argv) { int input_idx = 0; size_t efl_idx = 0; int ie_idx = 0; + size_t kg_idx = 0; /* Prerequisite check */ if (getuid() != 0) { @@ -401,19 +406,19 @@ int main(int argc, char **argv) { print_usage(); exit(1); } - load_list(argv[arg_idx], &target_dev_list_len, &target_dev_name_raw_list); + load_list(argv[arg_idx], &target_dev_list_len, &target_dev_name_raw_list, ",", true); } else if (strncmp(argv[arg_idx], "--keys=", strlen("--keys=")) == 0) { if (panic_key_str_list != NULL) { print(fd_stderr, "--keys cannot be passed more than once!\n"); print_usage(); exit(1); } - load_list(argv[arg_idx], &panic_key_list_len, &panic_key_str_list); + load_list(argv[arg_idx], &panic_key_list_len, &panic_key_str_list, ",", true); } } target_dev_list = safe_calloc(target_dev_list_len, sizeof(char *)); - panic_key_list = safe_calloc(panic_key_list_len, sizeof(int)); + panic_key_list = safe_calloc(panic_key_list_len, sizeof(int *)); panic_key_active_list = safe_calloc(panic_key_list_len, sizeof(bool)); for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { @@ -477,15 +482,27 @@ int main(int argc, char **argv) { } for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { - int keycode = lookup_keycode(panic_key_str_list[pkl_idx]); - if (keycode < 0) { - print(fd_stderr, "Invalid key code '"); - print(fd_stderr, panic_key_str_list[pkl_idx]); - print(fd_stderr, "'!\n"); - print_usage(); - exit(1); + size_t keygroup_str_list_len = 0; + char **keygroup_str_list = NULL; + load_list(panic_key_str_list[pkl_idx], &keygroup_str_list_len, &keygroup_str_list, "|", false); + int *pkl_element = safe_calloc(keygroup_str_list_len + 1, sizeof(int)); + + pkl_element[keygroup_str_list_len] = 0; + for (kg_idx = 0; kg_idx < keygroup_str_list_len; kg_idx++) { + int keycode = lookup_keycode(keygroup_str_list[kg_idx]); + if (keycode < 0) { + print(fd_stderr, "Invalid key code '"); + print(fd_stderr, keygroup_str_list[kg_idx]); + print(fd_stderr, "'!\n"); + print_usage(); + exit(1); + } + pkl_element[kg_idx] = keycode; + free(keygroup_str_list[kg_idx]); } - panic_key_list[pkl_idx] = keycode; + + free(keygroup_str_list); + panic_key_list[pkl_idx] = pkl_element; } /* Device event listener setup */ @@ -544,8 +561,13 @@ int main(int argc, char **argv) { } for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { - if (!bitset_get(key_flags, panic_key_list[pkl_idx])) { - supports_panic = false; + for (kg_idx = 0; panic_key_list[pkl_idx][kg_idx] != 0; kg_idx++) { + if (!bitset_get(key_flags, panic_key_list[pkl_idx][kg_idx])) { + supports_panic = false; + break; + } + } + if (!supports_panic) { break; } } @@ -599,11 +621,14 @@ int main(int argc, char **argv) { } for (pkl_idx = 0; pkl_idx < panic_key_list_len; pkl_idx++) { - if (ie_buf[ie_idx].code == panic_key_list[pkl_idx]) { - if (ie_buf[ie_idx].value == 0) { - panic_key_active_list[pkl_idx] = false; - } else { - panic_key_active_list[pkl_idx] = true; + for (kg_idx = 0; panic_key_list[pkl_idx][kg_idx] != 0; kg_idx++) { + if (ie_buf[ie_idx].code == panic_key_list[pkl_idx][kg_idx]) { + if (ie_buf[ie_idx].value == 0) { + panic_key_active_list[pkl_idx] = false; + } else { + panic_key_active_list[pkl_idx] = true; + } + break; /* only breaks inner loop */ } } } @@ -644,6 +669,7 @@ int main(int argc, char **argv) { struct msghdr msg = { &sa2, sizeof(sa2), &iov, 1, NULL, 0, 0 }; char *tmpbuf = NULL; bool device_removed = false; + bool device_changed = false; len = recvmsg(ns, &msg, 0); if (len == -1) { @@ -668,9 +694,13 @@ int main(int argc, char **argv) { device_removed = true; goto next_str; } + if (strcmp(tmpbuf, "ACTION=change") == 0) { + device_changed = true; + goto next_str; + } if (strncmp(tmpbuf, "DEVNAME=", strlen("DEVNAME=")) == 0) { - if (device_removed) { + if (device_removed || device_changed) { char *rem_devname_line = NULL; char *rem_dev_name = NULL; @@ -703,6 +733,11 @@ int main(int argc, char **argv) { continue; } + if (device_changed && strncmp(rem_dev_name, "sr", 2) != 0) { + free(rem_devname_line); + continue; + } + for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { if (strcmp(rem_dev_name, target_dev_list[tdl_idx]) == 0) { //reboot(RB_POWER_OFF); @@ -710,6 +745,7 @@ int main(int argc, char **argv) { exit(0); } } + free(rem_devname_line); } } From b745c8ddae74d5e1684919442fa74d64e95263b8 Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Mon, 14 Jul 2025 21:51:52 -0500 Subject: [PATCH 4/7] emerg-shutdown: Enable actual shutdown code, fix infinite loop when started too early --- usr/src/security-misc/emerg-shutdown.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/usr/src/security-misc/emerg-shutdown.c b/usr/src/security-misc/emerg-shutdown.c index bc5f95d..41f3d7c 100644 --- a/usr/src/security-misc/emerg-shutdown.c +++ b/usr/src/security-misc/emerg-shutdown.c @@ -638,7 +638,8 @@ int main(int argc, char **argv) { break; } if (pkl_idx == (panic_key_list_len - 1)) { - print(fd_stderr, "SHUTDOWN!!!\n"); + reboot(RB_POWER_OFF); + /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } } @@ -673,8 +674,8 @@ int main(int argc, char **argv) { len = recvmsg(ns, &msg, 0); if (len == -1) { - //reboot(RB_POWER_OFF); - print(fd_stderr, "SHUTDOWN!!!\n"); + reboot(RB_POWER_OFF); + /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } @@ -730,18 +731,18 @@ int main(int argc, char **argv) { rem_dev_name = strtok(NULL, "="); if (rem_dev_name == NULL) { free(rem_devname_line); - continue; + goto next_str; } if (device_changed && strncmp(rem_dev_name, "sr", 2) != 0) { free(rem_devname_line); - continue; + goto next_str; } for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { if (strcmp(rem_dev_name, target_dev_list[tdl_idx]) == 0) { - //reboot(RB_POWER_OFF); - print(fd_stderr, "SHUTDOWN!!!\n"); + reboot(RB_POWER_OFF); + /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } } @@ -750,7 +751,7 @@ int main(int argc, char **argv) { } } - next_str: +next_str: len -= strlen(tmpbuf) + 1; tmpbuf += strlen(tmpbuf) + 1; } From 5889d134a23b3d4f8db5d81171ea12907bb10d4d Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Sun, 20 Jul 2025 14:14:09 -0500 Subject: [PATCH 5/7] emerg-shutdow: Improve recvmsg handling, call reboot syscall directly --- usr/src/security-misc/emerg-shutdown.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/usr/src/security-misc/emerg-shutdown.c b/usr/src/security-misc/emerg-shutdown.c index 41f3d7c..8d2e1cd 100644 --- a/usr/src/security-misc/emerg-shutdown.c +++ b/usr/src/security-misc/emerg-shutdown.c @@ -82,7 +82,7 @@ */ #include -#include +#include #include #include #include @@ -94,6 +94,7 @@ #include #include #include +#include #define fd_stdin 0 #define fd_stdout 1 @@ -361,6 +362,10 @@ void load_list(const char *arg, size_t *result_list_len_ref, char ***result_list free(arg_copy); } +int kill_system() { + return syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_POWER_OFF, NULL); +} + int main(int argc, char **argv) { /* Working variables */ size_t target_dev_list_len = 0; @@ -638,7 +643,7 @@ int main(int argc, char **argv) { break; } if (pkl_idx == (panic_key_list_len - 1)) { - reboot(RB_POWER_OFF); + kill_system(); /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } @@ -667,14 +672,21 @@ int main(int argc, char **argv) { char buf[16384]; struct iovec iov = { buf, sizeof(buf) }; struct sockaddr_nl sa2; - struct msghdr msg = { &sa2, sizeof(sa2), &iov, 1, NULL, 0, 0 }; + struct msghdr msg = { 0 }; + msg.msg_name = &sa2; + msg.msg_namelen = sizeof(sa2); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; char *tmpbuf = NULL; bool device_removed = false; bool device_changed = false; len = recvmsg(ns, &msg, 0); if (len == -1) { - reboot(RB_POWER_OFF); + kill_system(); /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } @@ -741,7 +753,7 @@ int main(int argc, char **argv) { for (tdl_idx = 0; tdl_idx < target_dev_list_len; tdl_idx++) { if (strcmp(rem_dev_name, target_dev_list[tdl_idx]) == 0) { - reboot(RB_POWER_OFF); + kill_system(); /*print(fd_stderr, "SHUTDOWN!!!\n");*/ exit(0); } From e42078e90d7d7e5339a7c4682eb93c844fd38580 Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Mon, 28 Jul 2025 20:42:14 -0500 Subject: [PATCH 6/7] emerg-shutdown: fix the hang-on-shutdown bug, add autodetection of new keyboards, shutdown key configuration, and instant shutdown option --- .../emerg-shutdown/30_security_misc.conf | 19 +++++ usr/lib/systemd/system/emerg-shutdown.service | 3 +- usr/lib/udev/rules.d/95-emerg-shutdown.rules | 9 ++ usr/libexec/security-misc/emerg-shutdown | 58 +++++++++---- usr/src/security-misc/emerg-shutdown.c | 84 ++++++++++++++++++- 5 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 etc/security-misc/emerg-shutdown/30_security_misc.conf create mode 100644 usr/lib/udev/rules.d/95-emerg-shutdown.rules 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)); From 1a60da71eddfcc6fb72a34596c770cd754146887 Mon Sep 17 00:00:00 2001 From: Aaron Rainbolt Date: Tue, 29 Jul 2025 21:16:51 -0500 Subject: [PATCH 7/7] emerg-shutdown: Add shutdown timeout for preventing stuck shutdowns, briefly document feature set and usage --- README.md | 13 ++ .../emerg-shutdown/30_security_misc.conf | 14 ++ .../system-preset/50-security-misc.preset | 4 + usr/lib/systemd/system/emerg-shutdown.service | 3 +- .../systemd/system/ensure-shutdown.service | 18 ++ usr/libexec/security-misc/emerg-shutdown | 3 +- usr/libexec/security-misc/ensure-shutdown | 28 +++ usr/src/security-misc/emerg-shutdown.c | 200 +++++++++++++++--- 8 files changed, 257 insertions(+), 26 deletions(-) create mode 100644 usr/lib/systemd/system/ensure-shutdown.service create mode 100755 usr/libexec/security-misc/ensure-shutdown diff --git a/README.md b/README.md index cf3ea62..ac12886 100644 --- a/README.md +++ b/README.md @@ -712,6 +712,19 @@ See: * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860040 * https://forums.whonix.org/t/cannot-use-pkexec/8129 +## Emergency shutdown + +- Forcibly powers off the system if the drive the system booted from is + removed from the system. +- Forcibly powers off the system if a user-configurable "panic key sequence" + is pressed (Ctrl+Alt+Delete by default). +- Forcibly powers off the system if + `sudo /run/emerg-shutdown --instant-shutdown` is called. +- Optional - Forcibly powers off the system if shutdown gets stuck for longer + than a user-configurable number of seconds (30 by default). Requires tuning + by the user to function properly, see notes in + `/etc/security-misc/emerg-shutdown/30_security_misc.conf`. + ## Application-specific hardening - Enables "`apt-get --error-on=any`" which makes apt exit non-zero for diff --git a/etc/security-misc/emerg-shutdown/30_security_misc.conf b/etc/security-misc/emerg-shutdown/30_security_misc.conf index a4bb394..e844374 100644 --- a/etc/security-misc/emerg-shutdown/30_security_misc.conf +++ b/etc/security-misc/emerg-shutdown/30_security_misc.conf @@ -17,3 +17,17 @@ ## 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" + +## Set the maximum number of seconds shutdown can take. If shutdown gets stuck +## for longer than this, the system will forcibly power down. +## +## NOTE: This requires ensure-shutdown.service to be enabled, which is not +## done by default. Enabling ensure-shutdown.service will cause shutdown to +## always take at least as long as systemd's DefaultTimeoutStopSec (which by +## default is 90 seconds). If you are going to enable ensure-shutdown.service, +## it is highly recommended to set DefaultTimeoutStopSec to a much smaller +## value, such as 5 seconds. The maximum shutdown time set here should be at +## least 10 seconds *longer* than DefaultTimeoutStopSec, to give normal +## shutdown a chance to actually succeed before forcibly shutting down the +## system. +ENSURE_SHUTDOWN_TIMEOUT=30 diff --git a/usr/lib/systemd/system-preset/50-security-misc.preset b/usr/lib/systemd/system-preset/50-security-misc.preset index 1895526..004563c 100644 --- a/usr/lib/systemd/system-preset/50-security-misc.preset +++ b/usr/lib/systemd/system-preset/50-security-misc.preset @@ -17,3 +17,7 @@ disable proc-hidepid.service ## Disable due to issues. See: ## https://github.com/Kicksecure/security-misc/issues/159 disable harden-module-loading.service + +## Disable due to timing difficulties. See: +## https://github.com/systemd/systemd/issues/38261#issuecomment-3134580852 +disable ensure-shutdown.service diff --git a/usr/lib/systemd/system/emerg-shutdown.service b/usr/lib/systemd/system/emerg-shutdown.service index c1fca25..0eb4258 100644 --- a/usr/lib/systemd/system/emerg-shutdown.service +++ b/usr/lib/systemd/system/emerg-shutdown.service @@ -6,8 +6,9 @@ Description=Emergency shutdown when boot media is removed Documentation=https://github.com/Kicksecure/security-misc [Service] -Type=exec +Type=notify ExecStart=/usr/libexec/security-misc/emerg-shutdown +NotifyAccess=main [Install] WantedBy=multi-user.target diff --git a/usr/lib/systemd/system/ensure-shutdown.service b/usr/lib/systemd/system/ensure-shutdown.service new file mode 100644 index 0000000..30bcc23 --- /dev/null +++ b/usr/lib/systemd/system/ensure-shutdown.service @@ -0,0 +1,18 @@ +## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC +## See the file COPYING for copying conditions. + +[Unit] +Description=Forcibly shut down the system if normal shutdown gets stuck +Documentation=https://github.com/Kicksecure/security-misc +Wants=emerg-shutdown.service +After=emerg-shutdown.service + +[Service] +Type=oneshot +RemainAfterExit=true +ExecStart=/usr/libexec/security-misc/ensure-shutdown +ExecStop=bash -c -- 'echo "d" > /run/emerg-shutdown-trigger' +KillMode=process + +[Install] +WantedBy=multi-user.target diff --git a/usr/libexec/security-misc/emerg-shutdown b/usr/libexec/security-misc/emerg-shutdown index d8dc7f9..81dc9c1 100755 --- a/usr/libexec/security-misc/emerg-shutdown +++ b/usr/libexec/security-misc/emerg-shutdown @@ -39,9 +39,10 @@ if [ ! -f '/run/emerg-shutdown' ]; then printf "%s\n" 'Could not compile force-shutdown executable!' exit 1; } - fi +systemd-notify --ready + ## memlockd daemonizes itself, so no need to background it. memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg || true diff --git a/usr/libexec/security-misc/ensure-shutdown b/usr/libexec/security-misc/ensure-shutdown new file mode 100755 index 0000000..8eb663f --- /dev/null +++ b/usr/libexec/security-misc/ensure-shutdown @@ -0,0 +1,28 @@ +#!/bin/bash + +# Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC +# See the file COPYING for copying conditions. + +set -o errexit +set -o nounset +set -o errtrace +set -o pipefail + +source /usr/libexec/helper-scripts/strings.bsh + +## Make sure globs sort in a predictable, reproducible fashion +export LC_ALL=C + +## Read emergency shutdown key configuration +for config_file in /etc/security-misc/emerg-shutdown/*.conf; do + source "${config_file}" +done +if [ -z "${ENSURE_SHUTDOWN_TIMEOUT}" ] \ + || ! is_whole_number "${ENSURE_SHUTDOWN_TIMEOUT}"; then + ENSURE_SHUTDOWN_TIMEOUT=30; +fi + +/run/emerg-shutdown --monitor-fifo "--timeout=${ENSURE_SHUTDOWN_TIMEOUT}" & +sleep 1 +disown +exit 0 diff --git a/usr/src/security-misc/emerg-shutdown.c b/usr/src/security-misc/emerg-shutdown.c index 5a01e17..83cb6de 100644 --- a/usr/src/security-misc/emerg-shutdown.c +++ b/usr/src/security-misc/emerg-shutdown.c @@ -74,13 +74,6 @@ * (there are other similar posts as well). */ -/* - * TODO: Consider handling signals more gracefully (perhaps use ppoll instead - * of poll, handle things like EINTR, etc.). Right now the plan is to simply - * terminate when a signal is received and let systemd restart the process, - * but it might be better to just be signal-resilient. - */ - #include #include #include @@ -97,6 +90,10 @@ #include #include #include +#include +#include +#include +#include #define fd_stdin 0 #define fd_stdout 1 @@ -106,6 +103,11 @@ #define input_path_size 20 #define key_flags_len 12 +#define hw_monitor_val 1 +#define fifo_monitor_val 2 + +#define max_sig_num 31 + int console_fd = 0; /* Adapted from kloak/src/keycodes.c */ @@ -289,6 +291,8 @@ void print_usage() { 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, "Or:\n"); + print(fd_stderr, " emerg-shutdown --monitor-fifo --timeout=TIMEOUT\n"); print(fd_stderr, "Example:\n"); print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n"); } @@ -439,7 +443,8 @@ trykill: LINUX_REBOOT_CMD_POWER_OFF, NULL); } -int main(int argc, char **argv) { +/* Monitor for device removal and emergency shutdown key combos. */ +void hw_monitor(int argc, char **argv) { /* Working variables */ size_t target_dev_list_len = 0; char **target_dev_name_raw_list = NULL; @@ -464,23 +469,7 @@ int main(int argc, char **argv) { int ie_idx = 0; size_t kg_idx = 0; - /* Prerequisite check */ - if (getuid() != 0) { - print(fd_stderr, "This program must be run as root!\n"); - exit(1); - } - - /* Argument parsing */ - if (argc < 2) { - print(fd_stderr, "Invalid number of arguments!\n"); - print_usage(); - exit(1); - } - 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"); @@ -495,6 +484,12 @@ int main(int argc, char **argv) { exit(1); } load_list(argv[arg_idx], &panic_key_list_len, &panic_key_str_list, ",", true); + } else { + print(fd_stderr, "Unrecognized argument '"); + print(fd_stderr, argv[arg_idx]); + print(fd_stderr, "' passed!\n"); + print_usage(); + exit(1); } } @@ -851,4 +846,161 @@ next_str: } } } + + print(fd_stderr, "Hardware monitor poll gave up!\n"); + exit(1); +} + +/* + * Monitor for a kill command on a fifo. Two commands are recognized: + * + * - 'k': Instantly kill the system. + * - 'd': Wait 15 seconds, then kill the system. This is used to keep systemd + * from delaying shutdown excessively. + */ +void fifo_monitor(int argc, char **argv) { + long monitor_fifo_timeout = 0; + char *arg_copy = NULL; + char *arg_part = NULL; + char *arg_num_end = NULL; + const char *trigger_fifo_path = "/run/emerg-shutdown-trigger"; + int trigger_fifo_fd = 0; + struct pollfd trigger_fifo_poll = { 0 }; + char trigger_fifo_charbuf = '\0'; + ssize_t trigger_fifo_readlen = 0; + int sig_idx = 0; + struct sigaction sigact_swallow = { 0 }; + + if (strncmp(argv[2], "--timeout=", strlen("--timeout=")) != 0) { + print(fd_stderr, "Timeout not passed for --monitor-fifo!\n"); + print_usage(); + exit(1); + } + + arg_copy = safe_calloc(1, strlen(argv[2]) + 1); + memcpy(arg_copy, argv[2], strlen(argv[2]) + 1); + /* returns "--timeout" */ + arg_part = strtok(arg_copy, "="); + /* returns everything after the = sign */ + arg_part = strtok(NULL, ""); + monitor_fifo_timeout = strtol(arg_part, &arg_num_end, 10); + if (errno == ERANGE) { + print(fd_stderr, "Timeout out of range!\n"); + print_usage(); + exit(1); + } + if (*arg_num_end != '\0') { + print(fd_stderr, "Timeout is not purely numeric!\n"); + print_usage(); + exit(1); + } + if (monitor_fifo_timeout < 1) { + print(fd_stderr, "Timeout is less than one!\n"); + print_usage(); + exit(1); + } + + free(arg_copy); + arg_copy = NULL; + arg_part = NULL; + arg_num_end = NULL; + + if (mkfifo(trigger_fifo_path, 0777) == -1) { + print(fd_stderr, "Cannot create trigger fifo!\n"); + exit(1); + } + + trigger_fifo_fd = open(trigger_fifo_path, O_RDONLY | O_NONBLOCK); + if (trigger_fifo_fd == -1) { + print(fd_stderr, "Cannot open trigger fifo for reading!\n"); + exit(1); + } + + trigger_fifo_poll.fd = trigger_fifo_fd; + trigger_fifo_poll.events = POLLIN; + + /* Swallow all signals that we can. */ + sigact_swallow.sa_handler = SIG_IGN; + for (sig_idx = 1; sig_idx < max_sig_num; sig_idx++) { + if (sig_idx == SIGSTOP) { + continue; + } + if (sig_idx == SIGKILL) { + continue; + } + if (sigaction(sig_idx, &sigact_swallow, NULL) == -1) { + print(fd_stderr, "Failed to set up signal ignores!\n"); + exit(1); + } + } + for (sig_idx = SIGRTMIN; sig_idx <= SIGRTMAX; sig_idx++) { + if (sigaction(sig_idx, &sigact_swallow, NULL) == -1) { + print(fd_stderr, "Failed to set up real-time signal ignores!\n"); + exit(1); + } + } + + while (poll(&trigger_fifo_poll, 1, -1) != -1) { + trigger_fifo_readlen = read(trigger_fifo_fd, &trigger_fifo_charbuf, 1); + if (trigger_fifo_readlen != 1) { + print(fd_stderr, "Error reading from trigger fifo!\n"); + exit(1); + } + if (trigger_fifo_charbuf == 'k') { + kill_system(); + } else if (trigger_fifo_charbuf == 'd') { + sleep(monitor_fifo_timeout); + kill_system(); + } + } + + print(fd_stderr, "Trigger fifo poll gave up!\n"); + exit(1); +} + +int main(int argc, char **argv) { + int monitor_mode = hw_monitor_val; + + /* Prerequisite check */ + if (getuid() != 0) { + print(fd_stderr, "This program must be run as root!\n"); + exit(1); + } + + if (argc < 2) { + print(fd_stderr, "Not enough arguments!\n"); + print_usage(); + exit(1); + } + + if (strcmp(argv[1], "--instant-shutdown") == 0) { + if (argc != 2) { + print(fd_stderr, "Too many arguments, --instant-shutdown must be passed alone!\n"); + print_usage(); + exit(1); + } + + kill_system(); + } + if (strcmp(argv[1], "--monitor-fifo") == 0) { + if (argc != 3) { + print(fd_stderr, "Wrong number of arguments for --monitor-fifo!\n"); + print_usage(); + exit(1); + } + + monitor_mode = fifo_monitor_val; + } + + if (monitor_mode == hw_monitor_val) { + /* hw_monitor handles its own argument parsing */ + hw_monitor(argc, argv); + } else if (monitor_mode == fifo_monitor_val) { + /* fifo_monitor handles its own argument parsing */ + fifo_monitor(argc, argv); + } else { + print(fd_stderr, "Unknown monitor mode chosen!\n"); + print_usage(); + exit(1); + } }