Files
systemd/src/core/crash-handler.c
Yu Watanabe 4f18ff2e29 tree-wide: include unistd.h where necessary
We use symbols provided by unistd.h without including it. E.g.
open(), close(), read(), write(), access(), symlink(), unlink(), rmdir(),
fsync(), syncfs(), lseek(), ftruncate(), fchown(), dup2(), pipe2(),
getuid(), getgid(), gettid(), getppid(), pipe2(), execv(), _exit(),
environ, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, F_OK, and their
friends and variants, so on.

Currently, unistd.h is indirectly included mainly in the following two paths:
- through missing_syscall.h, which is planned to covert to .c file.
- through signal.h -> bits/sigstksz.h, which is new since glibc-2.34.
  Note, signal.h is included by sd-eevent.h. So, many source files
  indirectly include unistd.h if newer glibc is used.

Currently, our baseline on glibc is 2.31. We need to support glibc older
than 2.34, but unfortunately, we do not have any CI environments with
such old glibc. CIFuzz uses glibc-2.31, but it builds only fuzzers, and
many files are even not compiled.
2025-06-30 09:19:15 +02:00

207 lines
9.6 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/reboot.h>
#include <unistd.h>
#include "sd-messages.h"
#include "constants.h"
#include "crash-handler.h"
#include "exit-status.h"
#include "format-util.h"
#include "log.h"
#include "main.h"
#include "process-util.h"
#include "raw-clone.h"
#include "rlimit-util.h"
#include "signal-util.h"
#include "string-table.h"
#include "string-util.h"
#include "terminal-util.h"
#include "virt.h"
_noreturn_ void freeze_or_exit_or_reboot(void) {
/* If we are running in a container, let's prefer exiting, after all we can propagate an exit code to
* the container manager, and thus inform it that something went wrong. */
if (detect_container() > 0) {
log_struct(LOG_EMERG,
LOG_MESSAGE("Exiting PID 1..."),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_EXIT_STR));
_exit(EXIT_EXCEPTION);
}
if (arg_crash_action == CRASH_POWEROFF) {
log_notice("Shutting down...");
(void) reboot(RB_POWER_OFF);
log_struct_errno(LOG_EMERG, errno,
LOG_MESSAGE("Failed to power off: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_FAILED_STR));
} else if (arg_crash_action == CRASH_REBOOT) {
log_notice("Rebooting in 10s...");
(void) sleep(10);
log_notice("Rebooting now...");
(void) reboot(RB_AUTOBOOT);
log_struct_errno(LOG_EMERG, errno,
LOG_MESSAGE("Failed to reboot: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_FAILED_STR));
}
log_struct(LOG_EMERG,
LOG_MESSAGE("Freezing execution."),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_FREEZE_STR));
sync();
freeze();
}
_noreturn_ static void crash(int sig, siginfo_t *siginfo, void *context) {
static const struct sigaction sa_nocldwait = {
.sa_handler = SIG_IGN,
.sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART,
};
pid_t pid;
int r;
/* NB: 💣 💣 💣 This is a signal handler, most likely executed in a situation where we have corrupted
* memory. Thus: please avoid any libc memory allocation here, or any functions that internally use
* memory allocation, as we cannot rely on memory allocation still working at this point! (Note that
* memory allocation is not async-signal-safe anyway — see signal-safety(7) for details —, and thus
* is not permissible in signal handlers.) */
if (getpid_cached() != 1)
/* Pass this on immediately, if this is not PID 1 */
propagate_signal(sig, siginfo);
else if (!arg_dump_core)
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>, not dumping core.", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_NO_COREDUMP_STR));
else {
/* We want to wait for the core process, hence let's enable SIGCHLD */
(void) sigaction(SIGCHLD, &sigaction_nop_nocldstop, NULL);
pid = raw_clone(SIGCHLD);
if (pid < 0)
log_struct_errno(LOG_EMERG, errno,
LOG_MESSAGE("Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_NO_FORK_STR));
else if (pid == 0) {
/* Enable default signal handler for core dump */
(void) sigaction(sig, &sigaction_default, NULL);
/* Don't limit the coredump size */
(void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY));
/* Just to be sure... */
(void) chdir("/");
/* Raise the signal again */
propagate_signal(sig, siginfo);
assert_not_reached();
_exit(EXIT_EXCEPTION);
} else {
siginfo_t status;
if (siginfo) {
if (!si_code_from_process(siginfo->si_code))
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>.", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_UNKNOWN_SIGNAL_STR));
else if (siginfo->si_pid == 0)
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>, from unknown sender process.", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_UNKNOWN_SIGNAL_STR));
else if (siginfo->si_pid == 1)
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>, from our own process.", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_SYSTEMD_SIGNAL_STR));
else
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s> from PID "PID_FMT".", signal_to_string(sig), siginfo->si_pid),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_PROCESS_SIGNAL_STR));
}
/* Order things nicely. */
r = wait_for_terminate(pid, &status);
if (r < 0)
log_struct_errno(LOG_EMERG, r,
LOG_MESSAGE("Caught <%s>, waitpid() failed: %m", signal_to_string(sig)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_WAITPID_FAILED_STR));
else if (status.si_code != CLD_DUMPED) {
const char *s = status.si_code == CLD_EXITED ?
exit_status_to_string(status.si_status, EXIT_STATUS_LIBC) :
signal_to_string(status.si_status);
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).",
signal_to_string(sig),
pid,
sigchld_code_to_string(status.si_code),
status.si_status,
strna(s)),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_COREDUMP_FAILED_STR));
} else
log_struct(LOG_EMERG,
LOG_MESSAGE("Caught <%s>, dumped core as pid "PID_FMT".",
signal_to_string(sig), pid),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_COREDUMP_PID_STR));
}
}
if (arg_crash_chvt >= 0)
(void) chvt(arg_crash_chvt);
/* Let the kernel reap children for us */
(void) sigaction(SIGCHLD, &sa_nocldwait, NULL);
if (arg_crash_shell) {
log_notice("Executing crash shell...");
pid = raw_clone(SIGCHLD);
if (pid < 0)
log_struct_errno(LOG_EMERG, errno,
LOG_MESSAGE("Failed to fork off crash shell: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_SHELL_FORK_FAILED_STR));
else if (pid == 0) {
(void) setsid();
(void) terminal_vhangup("/dev/console");
(void) make_console_stdio();
(void) rlimit_nofile_safe();
(void) execle("/bin/sh", "/bin/sh", NULL, environ);
log_struct_errno(LOG_EMERG, errno,
LOG_MESSAGE("execle() failed: %m"),
LOG_MESSAGE_ID(SD_MESSAGE_CRASH_EXECLE_FAILED_STR));
_exit(EXIT_EXCEPTION);
} else {
log_info("Spawned crash shell as PID "PID_FMT".", pid);
(void) wait_for_terminate(pid, NULL);
}
}
freeze_or_exit_or_reboot();
}
void install_crash_handler(void) {
static const struct sigaction sa = {
.sa_sigaction = crash,
.sa_flags = SA_NODEFER | SA_SIGINFO, /* So that we can raise the signal again from the signal handler */
};
int r;
/* We ignore the return value here, since, we don't mind if we cannot set up a crash handler */
r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER);
if (r < 0)
log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m");
}
static const char* const crash_action_table[_CRASH_ACTION_MAX] = {
[CRASH_FREEZE] = "freeze",
[CRASH_REBOOT] = "reboot",
[CRASH_POWEROFF] = "poweroff",
};
DEFINE_STRING_TABLE_LOOKUP(crash_action, CrashAction);