emerg-shutdown: fix the hang-on-shutdown bug, add autodetection of new keyboards, shutdown key configuration, and instant shutdown option

This commit is contained in:
Aaron Rainbolt 2025-07-28 20:42:14 -05:00
parent a1d1c56033
commit e42078e90d
No known key found for this signature in database
GPG key ID: A709160D73C79109
5 changed files with 153 additions and 20 deletions

View file

@ -0,0 +1,19 @@
## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## 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"

View file

@ -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

View file

@ -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"

View file

@ -3,25 +3,49 @@
# Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
# 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}"

View file

@ -94,6 +94,8 @@
#include <stdbool.h>
#include <poll.h>
#include <linux/input.h>
#include <linux/vt.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#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));