coredump: support kernel coredump socket protocol

Since kernel v6.17, we can set a path to unix socket to core_pattern
(with request mode). See also:
https://lore.kernel.org/all/20250603-work-coredump-socket-protocol-v2-0-05a5f0c18ecc@kernel.org/

This introduces a tiny service that prepares and listens on a UNIX
socket to be passed to the kernel core pattern. When a process is
crashed, the kernel connects to the socket, and sends information
about the crashed process and passes coredump through the socket.
The service accepts the connection, and forks a worker process that
receives coredump from the kernel.
This commit is contained in:
Yu Watanabe
2026-08-27 05:52:49 +09:00
parent 0351920ac7
commit 264ca8f827
11 changed files with 1124 additions and 0 deletions
+115
View File
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */ /* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sched.h>
#include "sd-login.h" #include "sd-login.h"
#include "sd-messages.h" #include "sd-messages.h"
@@ -8,6 +10,7 @@
#include "dirent-util.h" #include "dirent-util.h"
#include "fd-util.h" #include "fd-util.h"
#include "fs-util.h" #include "fs-util.h"
#include "hostname-setup.h"
#include "hostname-util.h" #include "hostname-util.h"
#include "iovec-wrapper.h" #include "iovec-wrapper.h"
#include "log.h" #include "log.h"
@@ -16,7 +19,9 @@
#include "parse-util.h" #include "parse-util.h"
#include "pidfd-util.h" #include "pidfd-util.h"
#include "process-util.h" #include "process-util.h"
#include "rlimit-util.h"
#include "signal-util.h" #include "signal-util.h"
#include "socket-util.h"
#include "special.h" #include "special.h"
#include "string-table.h" #include "string-table.h"
#include "string-util.h" #include "string-util.h"
@@ -649,3 +654,113 @@ int coredump_context_parse_from_argv(CoredumpContext *context, int argc, char **
coredump_context_check_pidns(context); coredump_context_check_pidns(context);
return coredump_context_parse_from_procfs(context); return coredump_context_parse_from_procfs(context);
} }
static int get_crashing_tid(pid_t pid, PidRef *ret) {
int r;
assert(pid > 0);
assert(ret);
/* Read /proc/[PID]/task/[TID]/stat then find a thread that has the PF_DUMPCORE flag. */
const char *p = procfs_file_alloca(pid, "task");
_cleanup_closedir_ DIR *d = opendir(p);
if (!d)
return errno == ENOENT ? -ESRCH : -errno;
FOREACH_DIRENT_ALL(de, d, return -errno) {
if (!in_charset(de->d_name, DIGITS))
continue;
_cleanup_free_ char *q = path_join(p, de->d_name, "stat");
if (!q)
return -ENOMEM;
unsigned long long flags;
r = procfs_get_stat_flags(q, &flags);
if (r == -ENOMEM)
return r;
if (r < 0) {
log_debug_errno(r, "Failed to read '%s', ignoring: %m", q);
continue;
}
if (!FLAGS_SET(flags, PF_DUMPCORE))
continue;
return pidref_set_pidstr_full(ret, de->d_name, PIDFD_THREAD);
}
return -ESRCH;
}
int coredump_context_parse_from_peer(CoredumpContext *context) {
int r;
assert(context);
assert(context->input_fd >= 0);
r = getpeerpidref(context->input_fd, &context->pidref);
if (r < 0)
return log_error_errno(r, "Failed to get peer pidref: %m");
if (context->pidref.fd < 0)
return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM), "We do not have pidfd of the crashed process.");
context->got_pidfd = true;
struct pidfd_info info = {
.mask = PIDFD_INFO_COREDUMP,
};
r = pidfd_get_info(context->pidref.fd, &info);
if (r < 0)
return log_error_errno(r, "ioctl(PIDFD_GET_INFO) failed: %m");
if (!FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL))
return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
"Failed to get coredump information by ioctl(PIDFD_GET_INFO).");
if (!FLAGS_SET(info.coredump_mask, PIDFD_COREDUMPED))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"The process ["PID_FMT"] did not crash.", context->pidref.pid);
if (FLAGS_SET(info.coredump_mask, PIDFD_COREDUMP_SKIP))
return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
"Coredump generation for process ["PID_FMT"] is skipped.", context->pidref.pid);
context->uid = info.ruid;
context->gid = info.rgid;
context->dumpable = FLAGS_SET(info.coredump_mask, PIDFD_COREDUMP_USER) ? SUID_DUMP_USER : SUID_DUMP_SAFE;
context->signo = info.coredump_signal;
/* This is relatively new (since kernel v7.1), thus optional. */
if (FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP_CODE)) {
context->code = (int) info.coredump_code;
context->got_code = true;
} else
context->got_code = false;
r = get_crashing_tid(context->pidref.pid, &context->tidref);
if (r < 0)
log_warning_errno(r, "Failed to get crashing TID of process ["PID_FMT"], ignoring: %m", context->pidref.pid);
struct rlimit rl;
r = pid_getrlimit(context->pidref.pid, RLIMIT_CORE, &rl);
if (r < 0)
log_warning_errno(r, "Failed to get coredump size limit of process ["PID_FMT"], ignoring: %m", context->pidref.pid);
else
context->rlimit = rl.rlim_cur;
_cleanup_free_ char *h = NULL;
r = pidref_gethostname_full(&context->pidref, GET_HOSTNAME_ALLOW_LOCALHOST | GET_HOSTNAME_FALLBACK_DEFAULT, &h);
if (r < 0)
log_warning_errno(r, "Failed to get hostname, ignoring: %m");
else if (!hostname_is_valid(h, /* flags= */ 0))
log_warning("Obtained an invalid hostname, ignoring: %s", h);
else
context->hostname = TAKE_PTR(h);
coredump_context_check_pidns(context);
return coredump_context_parse_from_procfs(context);
}
+1
View File
@@ -90,3 +90,4 @@ bool coredump_context_is_journald(CoredumpContext *context);
int coredump_context_build_iovw(CoredumpContext *context); int coredump_context_build_iovw(CoredumpContext *context);
int coredump_context_parse_iovw(CoredumpContext *context); int coredump_context_parse_iovw(CoredumpContext *context);
int coredump_context_parse_from_argv(CoredumpContext *context, int argc, char **argv); int coredump_context_parse_from_argv(CoredumpContext *context, int argc, char **argv);
int coredump_context_parse_from_peer(CoredumpContext *context);
+600
View File
@@ -0,0 +1,600 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "acl-util.h"
#include "alloc-util.h"
#include "compress.h"
#include "coredump-config.h"
#include "coredump-manager.h"
#include "coredump-worker.h"
#include "daemon-util.h"
#include "elf-util.h"
#include "errno-util.h"
#include "event-util.h"
#include "fd-util.h"
#include "hashmap.h"
#include "label-util.h"
#include "log.h"
#include "namespace-util.h"
#include "pidfd-util.h"
#include "pidref.h"
#include "process-util.h"
#include "signal-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "umask-util.h"
#define COREDUMP_SOCKET_PATH "/run/systemd/coredumpd/kernel"
#define COREDUMP_SOCKET_MAX_WORKERS 8u
#define WORKER_TIMEOUT_USEC (5 * USEC_PER_MINUTE)
enum {
/* This should have a higher priority than the others, to prevent accepting new connections. */
EVENT_PRIORITY_SIGTERM_SIGINT = SD_EVENT_PRIORITY_NORMAL - 4,
/* This should have a higher priority than timer events about killing long running worker processes. */
EVENT_PRIORITY_WORKER_SIGCHLD = SD_EVENT_PRIORITY_NORMAL - 3,
/* As said in the above, this should have a lower priority than the SIGCHLD event source. */
EVENT_PRIORITY_WORKER_TIMER = SD_EVENT_PRIORITY_NORMAL - 2,
/* This should have a lower priority than the events for workers. */
EVENT_PRIORITY_COREDUMP_SOCKET = SD_EVENT_PRIORITY_NORMAL - 1,
/* This should have a lower priority than other event sources. */
EVENT_PRIORITY_SIGHUP = SD_EVENT_PRIORITY_NORMAL + 1,
};
typedef struct Manager Manager;
typedef struct Worker {
Manager *manager;
sd_event_source *timer_event_source;
sd_event_source *child_event_source;
PidRef pidref;
bool dead;
} Worker;
struct Manager {
CoredumpConfig config;
sd_event *event;
sd_event_source *coredump_socket_event_source;
int coredump_socket;
Hashmap *workers_by_pidref;
int exit_code;
bool exit;
};
static Worker* worker_free(Worker *worker) {
if (!worker)
return NULL;
if (pidref_is_set(&worker->pidref)) {
if (worker->manager)
hashmap_remove(worker->manager->workers_by_pidref, &worker->pidref);
if (worker->dead)
pidref_done(&worker->pidref);
else
pidref_done_sigkill_wait(&worker->pidref);
}
sd_event_source_unref(worker->child_event_source);
sd_event_source_unref(worker->timer_event_source);
return mfree(worker);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(Worker*, worker_free);
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
worker_hash_ops,
PidRef,
pidref_hash_func,
pidref_compare_func,
Worker,
worker_free);
static Worker* worker_new(void) {
Worker *worker = new(Worker, 1);
if (!worker)
return NULL;
*worker = (Worker) {
.pidref = PIDREF_NULL,
};
return worker;
}
static Manager* manager_free(Manager *manager) {
if (!manager)
return NULL;
sd_event_source_unref(manager->coredump_socket_event_source);
safe_close(manager->coredump_socket);
hashmap_free(manager->workers_by_pidref);
sd_event_unref(manager->event);
return mfree(manager);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
static Manager* manager_new(void) {
Manager *manager = new(Manager, 1);
if (!manager)
return NULL;
*manager = (Manager) {
.config = COREDUMP_CONFIG_NULL,
.coredump_socket = -EBADF,
};
return manager;
}
static int on_worker_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) {
_cleanup_(worker_freep) Worker *worker = ASSERT_PTR(userdata);
assert(si);
/* We know the worker is dead. Let's avoid calling pidref_done_sigkill_wait() in worker_free(). */
worker->dead = true;
switch (si->si_code) {
case CLD_EXITED:
if (si->si_status == 0)
log_debug("Worker ["PID_FMT"] exited.", si->si_pid);
else
log_warning("Worker ["PID_FMT"] exited with code %i.", si->si_pid, si->si_status);
break;
case CLD_KILLED:
case CLD_DUMPED:
log_warning("Worker ["PID_FMT"] terminated by signal %i (%s).",
si->si_pid, si->si_status, signal_to_string(si->si_status));
break;
default:
assert_not_reached();
}
return 0;
}
static int on_worker_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
Worker *worker = ASSERT_PTR(userdata);
log_warning("Worker ["PID_FMT"] is running longer than %s, killing the worker.",
worker->pidref.pid, FORMAT_TIMESPAN(WORKER_TIMEOUT_USEC, USEC_PER_SEC));
(void) pidref_kill(&worker->pidref, SIGKILL);
return 0;
}
static int manager_spawn_worker(Manager *manager, int coredump_fd) {
int r;
assert(manager);
assert(coredump_fd >= 0);
_cleanup_(worker_freep) Worker *worker = worker_new();
if (!worker)
return -ENOMEM;
/* On socket mode, the kernel does not provide any timestamp of the crash. Let's use the timestamp
* that the socket accept the connection. */
usec_t timestamp;
r = sd_event_now(manager->event, CLOCK_REALTIME, &timestamp);
if (r < 0)
return r;
r = pidref_safe_fork_full(
"(coredump-worker)",
/* stdio_fds= */ NULL,
(int[]) { coredump_fd }, 1,
FORK_DEATHSIG_SIGTERM |
FORK_RESET_SIGNALS |
FORK_CLOSE_ALL_FDS |
FORK_REOPEN_LOG |
FORK_LOG,
&worker->pidref);
if (r < 0)
return r;
if (r == 0) {
/* Worker process */
r = coredump_worker(&manager->config, TAKE_FD(coredump_fd), timestamp);
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
r = event_add_child_pidref(manager->event, &worker->child_event_source,
&worker->pidref, WEXITED, on_worker_sigchld, worker);
if (r < 0)
return r;
r = sd_event_source_set_priority(worker->child_event_source, EVENT_PRIORITY_WORKER_SIGCHLD);
if (r < 0)
return r;
(void) sd_event_source_set_description(worker->child_event_source, "worker-child-event");
r = sd_event_add_time_relative(manager->event, &worker->timer_event_source,
CLOCK_MONOTONIC, WORKER_TIMEOUT_USEC, USEC_PER_SEC,
on_worker_timeout, worker);
if (r < 0)
return r;
r = sd_event_source_set_priority(worker->timer_event_source, EVENT_PRIORITY_WORKER_TIMER);
if (r < 0)
return r;
(void) sd_event_source_set_description(worker->timer_event_source, "worker-timeout");
r = hashmap_ensure_put(&manager->workers_by_pidref, &worker_hash_ops, &worker->pidref, worker);
if (r < 0)
return r;
worker->manager = manager;
TAKE_PTR(worker);
return 0;
}
static int on_connect(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
int r;
assert(fd >= 0);
assert(!manager->exit);
_cleanup_close_ int coredump_fd = accept4(fd, /* addr= */ NULL, /* addrlen= */ NULL, SOCK_CLOEXEC | SOCK_NONBLOCK);
if (coredump_fd < 0) {
if (!ERRNO_IS_ACCEPT_AGAIN(errno))
log_warning_errno(errno, "Failed to accept coredump socket connection, ignoring: %m");
return 0;
}
r = manager_spawn_worker(manager, coredump_fd);
if (r < 0)
log_warning_errno(r, "Failed to spawn worker process, ignoring: %m");
if (hashmap_size(manager->workers_by_pidref) >= COREDUMP_SOCKET_MAX_WORKERS) {
r = sd_event_source_set_enabled(s, SD_EVENT_OFF);
if (r < 0)
log_warning_errno(r, "Failed to stop listening on the kernel coredump socket, ignoring: %m");
}
return 0;
}
static int manager_exit(Manager *manager, int exit_code) {
assert(manager);
if (manager->exit)
return 0;
manager->exit_code = exit_code;
manager->exit = true;
(void) sd_notify(/* unset_environment= */ false, NOTIFY_STOPPING_MESSAGE);
/* Do not accept any new connections. */
manager->coredump_socket_event_source = sd_event_source_disable_unref(manager->coredump_socket_event_source);
manager->coredump_socket = safe_close(manager->coredump_socket);
return 0;
}
static int on_post(sd_event_source *s, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
int r;
if (!manager->exit &&
hashmap_size(manager->workers_by_pidref) < COREDUMP_SOCKET_MAX_WORKERS) {
r = sd_event_source_set_enabled(manager->coredump_socket_event_source, SD_EVENT_ON);
if (r < 0) {
log_error_errno(r, "Failed to re-enable listening on the kernel coredump socket, terminating: %m");
(void) manager_exit(manager, r);
}
}
if (manager->exit) {
if (!hashmap_isempty(manager->workers_by_pidref))
return 0; /* There still exist workers. */
return sd_event_exit(manager->event, manager->exit_code);
}
return 0;
}
static int on_sigterm(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
return manager_exit(ASSERT_PTR(userdata), /* exit_code= */ 0);
}
static int on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
Manager *manager = ASSERT_PTR(userdata);
int r;
if (manager->exit)
return 0;
(void) notify_reloading();
manager->config = COREDUMP_CONFIG_NULL;
(void) coredump_parse_config(&manager->config);
r = sd_notify(/* unset_environment= */ false, NOTIFY_READY_MESSAGE);
if (r < 0)
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
return 0;
}
static int manager_setup_signal(
Manager *manager,
sd_event *event,
int signal,
sd_event_signal_handler_t handler,
int64_t priority,
const char *description) {
int r;
assert(manager);
assert(event);
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_signal(event, &s, signal | SD_EVENT_SIGNAL_PROCMASK, handler, manager);
if (r < 0)
return r;
r = sd_event_source_set_priority(s, priority);
if (r < 0)
return r;
(void) sd_event_source_set_description(s, description);
r = sd_event_source_set_floating(s, true);
if (r < 0)
return r;
return 0;
}
static int manager_init_event(Manager *manager) {
int r;
assert(manager);
/* block SIGCHLD for listening child events. */
assert_se(sigprocmask_many(SIG_BLOCK, /* ret_old_mask= */ NULL, SIGCHLD) >= 0);
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to allocate event loop: %m");
r = sd_event_set_watchdog(event, true);
if (r < 0)
return log_error_errno(r, "Failed to enable watchdog: %m");
r = manager_setup_signal(manager, event, SIGINT, on_sigterm,
EVENT_PRIORITY_SIGTERM_SIGINT, "sigint-event-source");
if (r < 0)
return log_error_errno(r, "Failed to create SIGINT event source: %m");
r = manager_setup_signal(manager, event, SIGTERM, on_sigterm,
EVENT_PRIORITY_SIGTERM_SIGINT, "sigterm-event-source");
if (r < 0)
return log_error_errno(r, "Failed to create SIGTERM event source: %m");
r = manager_setup_signal(manager, event, SIGHUP, on_sighup,
EVENT_PRIORITY_SIGHUP, "sighup-event-source");
if (r < 0)
return log_error_errno(r, "Failed to create SIGHUP event source: %m");
r = sd_event_add_post(event, /* ret= */ NULL, on_post, manager);
if (r < 0)
return log_error_errno(r, "Failed to create post event source: %m");
_cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
r = sd_event_add_io(event, &s, manager->coredump_socket, EPOLLIN, on_connect, manager);
if (r < 0)
return log_error_errno(r, "Failed to allocate IO event source: %m");
(void) sd_event_source_set_description(s, "coredump-socket-event");
r = sd_event_source_set_priority(s, EVENT_PRIORITY_COREDUMP_SOCKET);
if (r < 0)
return log_error_errno(r, "Failed to set priority of IO event source: %m");
manager->coredump_socket_event_source = TAKE_PTR(s);
manager->event = TAKE_PTR(event);
return 0;
}
static int manager_open_coredump_socket(Manager *manager) {
int r;
assert(manager);
assert(manager->coredump_socket < 0);
_cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (fd < 0)
return log_error_errno(errno, "Failed to create AF_UNIX socket: %m");
union sockaddr_union sa;
r = sockaddr_un_set_path(&sa.un, COREDUMP_SOCKET_PATH);
if (r < 0)
return log_error_errno(r, "Failed to set path for AF_UNIX socket: %m");
socklen_t sa_len = r;
(void) sockaddr_un_unlink(&sa.un);
WITH_UMASK(0077)
r = RET_NERRNO(bind(fd, &sa.sa, sa_len));
if (r < 0)
return log_error_errno(r, "Failed to bind AF_UNIX socket at %s: %m", sa.un.sun_path);
if (listen(fd, SOMAXCONN_DELUXE) < 0)
return log_error_errno(errno, "Failed to listen AF_UNIX socket at %s: %m", sa.un.sun_path);
manager->coredump_socket = TAKE_FD(fd);
return 0;
}
static int manager_push_coredump_socket(Manager *manager) {
int r;
assert(manager);
assert(manager->coredump_socket >= 0);
r = notify_push_fd(manager->coredump_socket, "coredump-socket");
if (r < 0)
return log_warning_errno(r, "Failed to push coredump socket to service manager, ignoring: %m");
log_debug("Pushed coredump socket to service manager.");
return 0;
}
static int manager_set_coredump_socket(Manager *manager, int fd) {
int r;
assert(manager);
assert(fd >= 0);
/* This takes passed fd on success. */
if (manager->coredump_socket >= 0)
return log_warning_errno(SYNTHETIC_ERRNO(EALREADY),
"Received multiple coredump socket (%i), ignoring.", fd);
r = sd_is_socket_unix(fd, SOCK_STREAM, /* listening= */ true, COREDUMP_SOCKET_PATH, /* length= */ 0);
if (r < 0)
return log_warning_errno(r, "Failed to check if fd (%i) is a valid unix socket, ignoring: %m", fd);
if (r == 0)
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
"Received invalid coredump socket (%i), ignoring.", fd);
(void) fd_nonblock(fd, true);
manager->coredump_socket = fd;
return 0;
}
static int manager_listen_fds(Manager *manager) {
int r;
assert(manager);
_cleanup_strv_free_ char **names = NULL;
int n = sd_listen_fds_with_names(/* unset_environment= */ false, &names);
if (n < 0)
return log_error_errno(n, "Failed to determine the number of file descriptors: %m");
for (int i = 0; i < n; i++) {
int fd = SD_LISTEN_FDS_START + i;
if (streq_ptr(names[i], "coredump-socket"))
r = manager_set_coredump_socket(manager, fd);
else
r = log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
"Received unexpected fd (%i: %s), ignoring.", fd, names[i]);
if (r < 0)
close_and_notify_warn(fd, names[i]);
}
return 0;
}
int coredump_manager_check_requirements(int log_level) {
int r;
/* This checks the following flags are supported by the running kernel:
* PIDFD_INFO_COREDUMP : 1d8db6fd698de1f73b1a7d72aea578fdd18d9a87 (v6.16),
* PIDFD_INFO_COREDUMP_SIGNAL : 036375522be8425874e9e0f907c7127e315c7a52 (v6.19).
*
* These flags are required for using the kernel coredump socket feature. Note that the
* request mode coredump socket pattern (@@ prefixed) is supported since kernel v6.17. Old
* kernels do not refuse the new core patterns (moreover, any strings are accepted), hence we
* need to check kernel version in some ways other than reading/writing core patterns. This
* method can be also used for checking that. If the required flags are supported, we assume
* the kernel is new enough, and the kernel coredump socket is also supported. */
r = pidfd_info_mask_is_supported(PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL);
if (IN_SET(r, 0, -EOPNOTSUPP))
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"ioctl(PIDFD_GET_INFO) does not support PIDFD_INFO_COREDUMP and/or PIDFD_INFO_COREDUMP_SIGNAL.");
if (r < 0)
return log_error_errno(r, "Failed to check if PIDFD_INFO_COREDUMP and PIDFD_INFO_COREDUMP_SIGNAL flags are supported by ioctl(PIDFD_GET_INFO): %m");
log_debug("The kernel supports both PIDFD_INFO_COREDUMP and PIDFD_INFO_COREDUMP_SIGNAL, assuming the kernel coredump socket is also supported.");
/* Let's check if we are in the initial PID, USER, TIME namespace. */
NamespaceType type;
FOREACH_ARGUMENT(type, NAMESPACE_PID, NAMESPACE_USER, NAMESPACE_TIME) {
r = namespace_is_init(type);
if (r < 0)
return log_error_errno(r, "Failed to check if we are in the initial %s namespace: %m",
namespace_info[type].proc_name);
if (r == 0)
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
"Running in a non-initial %s namespace.",
namespace_info[type].proc_name);
}
return 0;
}
int coredump_manager(void) {
int r;
log_setup();
r = coredump_manager_check_requirements(LOG_ERR);
if (r < 0)
return r;
r = mac_init();
if (r < 0)
return r;
/* Load some shared libraries before we fork any workers */
(void) dlopen_compress_default(LOG_DEBUG);
(void) dlopen_dw(LOG_DEBUG);
(void) dlopen_elf(LOG_DEBUG);
(void) dlopen_libacl(LOG_DEBUG);
_cleanup_(manager_freep) Manager *manager = manager_new();
if (!manager)
return log_oom();
/* Ignore all parse errors. */
(void) coredump_parse_config(&manager->config);
r = manager_listen_fds(manager);
if (r < 0)
return r;
if (manager->coredump_socket < 0) {
r = manager_open_coredump_socket(manager);
if (r < 0)
return r;
(void) manager_push_coredump_socket(manager);
}
r = manager_init_event(manager);
if (r < 0)
return r;
_unused_ _cleanup_(notify_on_cleanup) const char *notify_message =
notify_start(NOTIFY_READY_MESSAGE, NOTIFY_STOPPING_MESSAGE);
r = sd_event_loop(manager->event);
if (r < 0)
return log_error_errno(r, "Event loop failed: %m");
return 0;
}
+5
View File
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int coredump_manager_check_requirements(int log_level);
int coredump_manager(void);
+289
View File
@@ -0,0 +1,289 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/coredump.h>
#include "sd-event.h"
#include "coredump-context.h"
#include "coredump-send.h"
#include "coredump-submit.h"
#include "coredump-worker.h"
#include "errno-util.h"
#include "fd-util.h"
#include "format-util.h"
#include "log.h"
#include "process-util.h"
#include "signal-util.h"
#include "socket-util.h"
#define COREDUMP_REQ_SIZE_MAX 4096u
typedef enum CoredumpHandshakeState {
COREDUMP_HANDSHAKE_WAITING_REQUEST,
COREDUMP_HANDSHAKE_SENDING_ACK,
COREDUMP_HANDSHAKE_WAITING_MARKER,
COREDUMP_HANDSHAKE_DONE,
_COREDUMP_HANDSHAKE_STATE_MAX,
_COREDUMP_HANDSHAKE_STATE_INVALID = -EINVAL,
} CoredumpHandshakeState;
typedef struct HandshakeContext {
CoredumpHandshakeState state;
struct coredump_req req;
} HandshakeContext;
static int handshake_process_request(HandshakeContext *hc, sd_event_source *s, int fd, uint32_t revents) {
int r;
assert(hc);
assert(hc->state == COREDUMP_HANDSHAKE_WAITING_REQUEST);
assert(s);
assert(fd >= 0);
assert(FLAGS_SET(revents, EPOLLIN));
ssize_t n = next_datagram_size_fd(fd);
if (n < 0) {
if (ERRNO_IS_NEG_TRANSIENT(n))
return 0;
return log_debug_errno(n, "Failed to determine coredump request size: %m");
}
/* Verify the acquired request size. */
if (n < COREDUMP_REQ_SIZE_VER0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Acquired coredump request size is too small (%zi < %i).",
n, COREDUMP_REQ_SIZE_VER0);
if ((size_t) n > COREDUMP_REQ_SIZE_MAX)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Acquired coredump request size is too large (%zi > %u).",
n, COREDUMP_REQ_SIZE_MAX);
union coredump_req_union {
struct coredump_req req;
uint8_t buf[COREDUMP_REQ_SIZE_MAX];
} req = {};
n = recv(fd, &req, n, /* flags= */ 0);
if (n < 0) {
if (ERRNO_IS_TRANSIENT(errno))
return 0;
return log_debug_errno(errno, "Failed to receive coredump request: %m");
}
assert((size_t) n <= sizeof(req));
/* Verify the received coredump request. */
if (n < COREDUMP_REQ_SIZE_VER0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received coredump request size is too small (%zi < %i).",
n, COREDUMP_REQ_SIZE_VER0);
if ((size_t) n != req.req.size)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received coredump request size does not match with the size specified in the request (%zi != %"PRIu32").",
n, req.req.size);
if (req.req.size_ack < COREDUMP_ACK_SIZE_VER0)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received coredump request with too small ack size (%"PRIu32" < %i).",
req.req.size_ack, COREDUMP_ACK_SIZE_VER0);
if (!FLAGS_SET(req.req.mask, COREDUMP_KERNEL | COREDUMP_USERSPACE | COREDUMP_REJECT | COREDUMP_WAIT))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Received coredump request with insufficient flags (%"PRIx64").",
(uint64_t) req.req.mask);
hc->req = req.req;
log_debug("Received coredump request, sending coredump ack.");
hc->state = COREDUMP_HANDSHAKE_SENDING_ACK;
r = sd_event_source_set_io_events(s, EPOLLOUT);
if (r < 0)
return log_debug_errno(r, "Failed to set IO events: %m");
return 0;
}
static int handshake_send_ack(HandshakeContext *hc, sd_event_source *s, int fd, uint32_t revents) {
int r;
assert(hc);
assert(hc->state == COREDUMP_HANDSHAKE_SENDING_ACK);
assert(s);
assert(fd >= 0);
assert(FLAGS_SET(revents, EPOLLOUT));
struct coredump_ack ack = {
/* send the full coredump_ack structure if we can, but never more than what the kernel actually supports */
.size = MIN(sizeof(struct coredump_ack), hc->req.size_ack),
.mask = COREDUMP_KERNEL | COREDUMP_WAIT,
};
ssize_t n = send(fd, &ack, ack.size, MSG_NOSIGNAL);
if (n < 0) {
if (ERRNO_IS_TRANSIENT(errno))
return 0;
return log_debug_errno(errno, "Failed to send coredump ack: %m");
}
if ((size_t) n != ack.size)
return log_debug_errno(SYNTHETIC_ERRNO(ESTALE),
"Sent size does not match with the size of coredump ack (%zi != %"PRIu32").",
n, ack.size);
log_debug("Sent coredump ack, waiting for marker.");
hc->state = COREDUMP_HANDSHAKE_WAITING_MARKER;
r = sd_event_source_set_io_events(s, EPOLLIN);
if (r < 0)
return log_debug_errno(r, "Failed to set IO events: %m");
return 0;
}
static int handshake_process_marker(HandshakeContext *hc, sd_event_source *s, int fd, uint32_t revents) {
assert(hc);
assert(hc->state == COREDUMP_HANDSHAKE_WAITING_MARKER);
assert(s);
assert(fd >= 0);
assert(FLAGS_SET(revents, EPOLLIN));
enum coredump_mark mark;
ssize_t n = recv(fd, &mark, sizeof(mark), /* flags= */ 0);
if (n < 0) {
if (ERRNO_IS_TRANSIENT(errno))
return 0;
return log_debug_errno(errno, "Failed to receive marker: %m");
}
if ((size_t) n != sizeof(mark))
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Received marker with invalid size (%zi).", n);
switch (mark) {
case COREDUMP_MARK_REQACK:
log_debug("Sent coredump ack message is accepted, reading coredump data.");
hc->state = COREDUMP_HANDSHAKE_DONE;
return 1;
case COREDUMP_MARK_MINSIZE:
return log_debug_errno(SYNTHETIC_ERRNO(ENOBUFS),
"Sent coredump ack message is refused as its size is too small.");
case COREDUMP_MARK_MAXSIZE:
return log_debug_errno(SYNTHETIC_ERRNO(EMSGSIZE),
"Sent coredump ack message is refused as its size is too large.");
case COREDUMP_MARK_UNSUPPORTED:
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Sent coredump ack message is refused as it contains unsupported flags.");
case COREDUMP_MARK_CONFLICTING:
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Sent coredump ack message is refused as it contains conflicting flags.");
default:
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
"Sent coredump ack message is refused with unknown reason (%u).", mark);
}
}
static int on_handshake_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
HandshakeContext *hc = ASSERT_PTR(userdata);
int r;
assert(s);
assert(fd >= 0);
if (revents & (EPOLLHUP|EPOLLERR)) {
log_debug("Received EPOLLHUP and/or EPOLLERR.");
return sd_event_exit(sd_event_source_get_event(s), -EIO);
}
switch (hc->state) {
case COREDUMP_HANDSHAKE_WAITING_REQUEST:
r = handshake_process_request(hc, s, fd, revents);
break;
case COREDUMP_HANDSHAKE_SENDING_ACK:
r = handshake_send_ack(hc, s, fd, revents);
break;
case COREDUMP_HANDSHAKE_WAITING_MARKER:
r = handshake_process_marker(hc, s, fd, revents);
break;
default:
assert_not_reached();
}
if (r != 0)
return sd_event_exit(sd_event_source_get_event(s), r < 0 ? r : 0);
return 0;
}
static int coredump_handshake(CoredumpContext *context) {
int r;
assert(context);
assert(context->input_fd >= 0);
_cleanup_(sd_event_unrefp) sd_event *e = NULL;
r = sd_event_new(&e);
if (r < 0)
return log_error_errno(r, "Failed to allocate sd-event object: %m");
HandshakeContext hc = {};
r = sd_event_add_io(e, /* ret= */ NULL, context->input_fd, EPOLLIN, on_handshake_io, &hc);
if (r < 0)
return log_error_errno(r, "Failed to add IO event source for kernel coredump socket: %m");
r = sd_event_set_signal_exit(e, true);
if (r < 0)
return log_error_errno(r, "Failed to enable signal event sources: %m");
log_debug("Processing coredump request.");
r = sd_event_loop(e);
if (r < 0)
return log_error_errno(r, "Failed to process coredump request: %m");
if (hc.state != COREDUMP_HANDSHAKE_DONE) /* Maybe received SIGTERM/SIGINT. */
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Processing coredump request is cancelled.");
return 0;
}
int coredump_worker(const CoredumpConfig *config, int coredump_fd, usec_t timestamp) {
int r;
assert(config);
assert(coredump_fd >= 0);
/* This invalidates the input file descriptor even on failure. */
LogTarget saved_target = _LOG_TARGET_INVALID;
if (!log_on_console()) {
saved_target = log_get_target();
log_set_target_and_open(LOG_TARGET_KMSG);
}
/* Set higher OOM score, we only protect the manager process. */
r = set_oom_score_adjust(500);
if (r < 0)
log_debug_errno(r, "Failed to reset OOM score, ignoring: %m");
_cleanup_(coredump_context_done) CoredumpContext context = COREDUMP_CONTEXT_NULL;
context.input_fd = TAKE_FD(coredump_fd);
context.timestamp = timestamp;
r = coredump_context_parse_from_peer(&context);
if (r < 0)
return r;
if (!coredump_context_is_journald(&context) && saved_target >= 0)
log_set_target_and_open(saved_target);
/* Log minimal metadata now, so it is not lost if the system is about to shut down. */
log_info("Process "PID_FMT" (%s) of user "UID_FMT" terminated abnormally with signal %i/%s, processing...",
context.pidref.pid, context.comm, context.uid, context.signo,
signal_to_string(context.signo));
r = coredump_handshake(&context);
if (r < 0)
return r;
(void) fd_nonblock(context.input_fd, false);
if (coredump_send_to_container(&context) > 0)
return 0;
return coredump_submit(config, &context);
}
+6
View File
@@ -0,0 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include "coredump-forward.h"
int coredump_worker(const CoredumpConfig *config, int coredump_fd, usec_t timestamp);
+9
View File
@@ -4,6 +4,7 @@
#include "coredump-backtrace.h" #include "coredump-backtrace.h"
#include "coredump-kernel-helper.h" #include "coredump-kernel-helper.h"
#include "coredump-manager.h"
#include "coredump-receive.h" #include "coredump-receive.h"
#include "coredump-util.h" #include "coredump-util.h"
#include "dlopen-note.h" #include "dlopen-note.h"
@@ -25,9 +26,17 @@ static int run(int argc, char *argv[]) {
if (streq_ptr(argv[1], "--backtrace")) if (streq_ptr(argv[1], "--backtrace"))
return coredump_backtrace(argc, argv); return coredump_backtrace(argc, argv);
if (streq_ptr(argv[1], "--check-requirements")) {
log_setup();
return coredump_manager_check_requirements(LOG_DEBUG);
}
/* Make sure we never enter a loop. */ /* Make sure we never enter a loop. */
(void) set_dumpable(SUID_DUMP_DISABLE); (void) set_dumpable(SUID_DUMP_DISABLE);
if (streq_ptr(argv[1], "--manager"))
return coredump_manager();
/* First, log to a safe place, since we don't know what crashed and it might be journald which we'd /* First, log to a safe place, since we don't know what crashed and it might be journald which we'd
* rather not log to then. */ * rather not log to then. */
log_parse_environment(); log_parse_environment();
+2
View File
@@ -10,9 +10,11 @@ systemd_coredump_sources = files(
'coredump-config.c', 'coredump-config.c',
'coredump-context.c', 'coredump-context.c',
'coredump-kernel-helper.c', 'coredump-kernel-helper.c',
'coredump-manager.c',
'coredump-receive.c', 'coredump-receive.c',
'coredump-send.c', 'coredump-send.c',
'coredump-submit.c', 'coredump-submit.c',
'coredump-worker.c',
) )
systemd_coredump_export_sources = files( systemd_coredump_export_sources = files(
'coredump-vacuum.c', 'coredump-vacuum.c',
+9
View File
@@ -342,6 +342,15 @@ units = [
'file' : 'systemd-coredump@.service.in', 'file' : 'systemd-coredump@.service.in',
'conditions' : ['ENABLE_COREDUMP'], 'conditions' : ['ENABLE_COREDUMP'],
}, },
{
'file' : 'systemd-coredump-register.service.in',
'conditions' : ['ENABLE_COREDUMP'],
},
{
'file' : 'systemd-coredumpd.service.in',
'conditions' : ['ENABLE_COREDUMP'],
'symlinks' : ['sysinit.target.wants/'],
},
{ {
'file' : 'systemd-creds.socket', 'file' : 'systemd-creds.socket',
'symlinks' : ['sockets.target.wants/'], 'symlinks' : ['sockets.target.wants/'],
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Kernel Core Pattern Register
Documentation=man:systemd-coredump(8)
DefaultDependencies=no
After=systemd-coredumpd.service systemd-sysctl.service
BindsTo=systemd-coredumpd.service
Before=sysinit.target shutdown.target
Conflicts=shutdown.target
ConditionVirtualization=!container
RefuseManualStart=yes
RefuseManualStop=yes
[Service]
Type=oneshot
ExecCondition={{LIBEXECDIR}}/systemd-coredump --check-requirements
ExecStart={{LIBEXECDIR}}/systemd-sysctl --strict --verify --save=60-coredump --inline "kernel.core_pattern=@@/run/systemd/coredumpd/kernel"
ExecStop={{LIBEXECDIR}}/systemd-sysctl --revert=60-coredump --prefix=/kernel/core_pattern
RemainAfterExit=yes
+59
View File
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Listen Kernel Core Dump Socket
Documentation=man:systemd-coredump(8)
DefaultDependencies=no
After=local-fs.target systemd-journald.socket
Before=shutdown.target sysinit.target systemd-coredump-register.service
Conflicts=shutdown.target
Requires=systemd-journald.socket
Upholds=systemd-coredump-register.service
ConditionVirtualization=!container
[Service]
Type=notify-reload
ExecCondition={{LIBEXECDIR}}/systemd-coredump --check-requirements
ExecStart={{LIBEXECDIR}}/systemd-coredump --manager
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETGID CAP_SETPCAP CAP_SETUID CAP_SYS_ADMIN CAP_SYS_CHROOT CAP_SYS_PTRACE
FileDescriptorStoreMax=512
FileDescriptorStorePreserve=yes
IPAddressDeny=any
KillMode=mixed
LockPersonality=yes
MemoryDenyWriteExecute=yes
Nice=9
NoNewPrivileges=yes
OOMScoreAdjust=-1000
PrivateDevices=yes
PrivateNetwork=yes
PrivateTmp=disconnected
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=read-only
ProtectHostname=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
Restart=on-failure
RestartSec=0
RestrictAddressFamilies=AF_UNIX
RestrictRealtime=yes
RuntimeDirectory=systemd/coredumpd
RuntimeDirectoryPreserve=yes
StateDirectory=systemd/coredump
SystemCallArchitectures=native
SystemCallErrorNumber=EPERM
SystemCallFilter=@system-service @mount
TimeoutStopSec=6min
{{SERVICE_WATCHDOG}}