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:
@@ -1,5 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sched.h>
|
||||
|
||||
#include "sd-login.h"
|
||||
#include "sd-messages.h"
|
||||
|
||||
@@ -8,6 +10,7 @@
|
||||
#include "dirent-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hostname-setup.h"
|
||||
#include "hostname-util.h"
|
||||
#include "iovec-wrapper.h"
|
||||
#include "log.h"
|
||||
@@ -16,7 +19,9 @@
|
||||
#include "parse-util.h"
|
||||
#include "pidfd-util.h"
|
||||
#include "process-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "special.h"
|
||||
#include "string-table.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);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -90,3 +90,4 @@ bool coredump_context_is_journald(CoredumpContext *context);
|
||||
int coredump_context_build_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_peer(CoredumpContext *context);
|
||||
|
||||
@@ -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, ×tamp);
|
||||
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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "coredump-backtrace.h"
|
||||
#include "coredump-kernel-helper.h"
|
||||
#include "coredump-manager.h"
|
||||
#include "coredump-receive.h"
|
||||
#include "coredump-util.h"
|
||||
#include "dlopen-note.h"
|
||||
@@ -25,9 +26,17 @@ static int run(int argc, char *argv[]) {
|
||||
if (streq_ptr(argv[1], "--backtrace"))
|
||||
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. */
|
||||
(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
|
||||
* rather not log to then. */
|
||||
log_parse_environment();
|
||||
|
||||
@@ -10,9 +10,11 @@ systemd_coredump_sources = files(
|
||||
'coredump-config.c',
|
||||
'coredump-context.c',
|
||||
'coredump-kernel-helper.c',
|
||||
'coredump-manager.c',
|
||||
'coredump-receive.c',
|
||||
'coredump-send.c',
|
||||
'coredump-submit.c',
|
||||
'coredump-worker.c',
|
||||
)
|
||||
systemd_coredump_export_sources = files(
|
||||
'coredump-vacuum.c',
|
||||
|
||||
@@ -342,6 +342,15 @@ units = [
|
||||
'file' : 'systemd-coredump@.service.in',
|
||||
'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',
|
||||
'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
|
||||
@@ -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}}
|
||||
Reference in New Issue
Block a user