mirror of
https://github.com/Kicksecure/security-misc.git
synced 2025-08-06 16:34:18 -04:00
emerg-shutdown: Add shutdown timeout for preventing stuck shutdowns, briefly document feature set and usage
This commit is contained in:
parent
e42078e90d
commit
1a60da71ed
8 changed files with 257 additions and 26 deletions
13
README.md
13
README.md
|
@ -712,6 +712,19 @@ See:
|
||||||
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860040
|
* https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860040
|
||||||
* https://forums.whonix.org/t/cannot-use-pkexec/8129
|
* 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
|
## Application-specific hardening
|
||||||
|
|
||||||
- Enables "`apt-get --error-on=any`" which makes apt exit non-zero for
|
- Enables "`apt-get --error-on=any`" which makes apt exit non-zero for
|
||||||
|
|
|
@ -17,3 +17,17 @@
|
||||||
## The default key sequence triggers a shutdown when Ctrl+Alt+Delete is
|
## 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.
|
## 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"
|
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
|
||||||
|
|
|
@ -17,3 +17,7 @@ disable proc-hidepid.service
|
||||||
## Disable due to issues. See:
|
## Disable due to issues. See:
|
||||||
## https://github.com/Kicksecure/security-misc/issues/159
|
## https://github.com/Kicksecure/security-misc/issues/159
|
||||||
disable harden-module-loading.service
|
disable harden-module-loading.service
|
||||||
|
|
||||||
|
## Disable due to timing difficulties. See:
|
||||||
|
## https://github.com/systemd/systemd/issues/38261#issuecomment-3134580852
|
||||||
|
disable ensure-shutdown.service
|
||||||
|
|
|
@ -6,8 +6,9 @@ Description=Emergency shutdown when boot media is removed
|
||||||
Documentation=https://github.com/Kicksecure/security-misc
|
Documentation=https://github.com/Kicksecure/security-misc
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=exec
|
Type=notify
|
||||||
ExecStart=/usr/libexec/security-misc/emerg-shutdown
|
ExecStart=/usr/libexec/security-misc/emerg-shutdown
|
||||||
|
NotifyAccess=main
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
|
18
usr/lib/systemd/system/ensure-shutdown.service
Normal file
18
usr/lib/systemd/system/ensure-shutdown.service
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
|
||||||
|
## 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
|
|
@ -39,9 +39,10 @@ if [ ! -f '/run/emerg-shutdown' ]; then
|
||||||
printf "%s\n" 'Could not compile force-shutdown executable!'
|
printf "%s\n" 'Could not compile force-shutdown executable!'
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
systemd-notify --ready
|
||||||
|
|
||||||
## memlockd daemonizes itself, so no need to background it.
|
## memlockd daemonizes itself, so no need to background it.
|
||||||
memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg || true
|
memlockd -c /usr/share/security-misc/security-misc-memlockd.cfg || true
|
||||||
|
|
||||||
|
|
28
usr/libexec/security-misc/ensure-shutdown
Executable file
28
usr/libexec/security-misc/ensure-shutdown
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
|
||||||
|
# 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
|
|
@ -74,13 +74,6 @@
|
||||||
* (there are other similar posts as well).
|
* (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 <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <linux/reboot.h>
|
#include <linux/reboot.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
@ -97,6 +90,10 @@
|
||||||
#include <linux/vt.h>
|
#include <linux/vt.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#define fd_stdin 0
|
#define fd_stdin 0
|
||||||
#define fd_stdout 1
|
#define fd_stdout 1
|
||||||
|
@ -106,6 +103,11 @@
|
||||||
#define input_path_size 20
|
#define input_path_size 20
|
||||||
#define key_flags_len 12
|
#define key_flags_len 12
|
||||||
|
|
||||||
|
#define hw_monitor_val 1
|
||||||
|
#define fifo_monitor_val 2
|
||||||
|
|
||||||
|
#define max_sig_num 31
|
||||||
|
|
||||||
int console_fd = 0;
|
int console_fd = 0;
|
||||||
|
|
||||||
/* Adapted from kloak/src/keycodes.c */
|
/* 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, " emerg-shutdown --devices=DEVICE1[,DEVICE2...] --keys=KEY_1[,KEY_2|KEY_3...]\n");
|
||||||
print(fd_stderr, "Or:\n");
|
print(fd_stderr, "Or:\n");
|
||||||
print(fd_stderr, " emerg-shutdown --instant-shutdown\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, "Example:\n");
|
||||||
print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n");
|
print(fd_stderr, " emerg-shutdown --devices=/dev/sda3 --keys=KEY_POWER\n");
|
||||||
}
|
}
|
||||||
|
@ -439,7 +443,8 @@ trykill:
|
||||||
LINUX_REBOOT_CMD_POWER_OFF, NULL);
|
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 */
|
/* Working variables */
|
||||||
size_t target_dev_list_len = 0;
|
size_t target_dev_list_len = 0;
|
||||||
char **target_dev_name_raw_list = NULL;
|
char **target_dev_name_raw_list = NULL;
|
||||||
|
@ -464,23 +469,7 @@ int main(int argc, char **argv) {
|
||||||
int ie_idx = 0;
|
int ie_idx = 0;
|
||||||
size_t kg_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++) {
|
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 (strncmp(argv[arg_idx], "--devices=", strlen("--devices=")) == 0) {
|
||||||
if (target_dev_name_raw_list != NULL) {
|
if (target_dev_name_raw_list != NULL) {
|
||||||
print(fd_stderr, "--devices cannot be passed more than once!\n");
|
print(fd_stderr, "--devices cannot be passed more than once!\n");
|
||||||
|
@ -495,6 +484,12 @@ int main(int argc, char **argv) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
load_list(argv[arg_idx], &panic_key_list_len, &panic_key_str_list, ",", true);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue