diff options
author | Lennart Poettering <lennart@poettering.net> | 2016-02-10 16:50:21 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2016-02-10 16:50:21 +0100 |
commit | 059adb5ac035fec290ca3a9f2dc08a19b20b4d67 (patch) | |
tree | de2291d03ba2397bb72b34c65a4fe3deec54da2f /src | |
parent | 16a798deb3b560f8b27848fe292a76b362c0b581 (diff) | |
parent | a7c723c0c00a1b8ee64fe360a5d3caf2c89cb25c (diff) |
Merge pull request #2555 from poettering/coredump-fixes
Coredump fixes and more
Diffstat (limited to 'src')
-rw-r--r-- | src/activate/activate.c | 193 | ||||
-rw-r--r-- | src/basic/socket-util.c | 3 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.m4 | 8 | ||||
-rw-r--r-- | src/core/load-fragment.c | 86 | ||||
-rw-r--r-- | src/core/load-fragment.h | 1 | ||||
-rw-r--r-- | src/core/main.c | 21 | ||||
l--------- | src/coredump/Makefile | 1 | ||||
-rw-r--r-- | src/coredump/coredump-vacuum.c (renamed from src/journal/coredump-vacuum.c) | 0 | ||||
-rw-r--r-- | src/coredump/coredump-vacuum.h (renamed from src/journal/coredump-vacuum.h) | 0 | ||||
-rw-r--r-- | src/coredump/coredump.c (renamed from src/journal/coredump.c) | 730 | ||||
-rw-r--r-- | src/coredump/coredump.conf (renamed from src/journal/coredump.conf) | 0 | ||||
-rw-r--r-- | src/coredump/coredumpctl.c (renamed from src/journal/coredumpctl.c) | 0 | ||||
-rw-r--r-- | src/coredump/stacktrace.c (renamed from src/journal/stacktrace.c) | 0 | ||||
-rw-r--r-- | src/coredump/stacktrace.h (renamed from src/journal/stacktrace.h) | 0 | ||||
-rw-r--r-- | src/coredump/test-coredump-vacuum.c (renamed from src/journal/test-coredump-vacuum.c) | 0 | ||||
-rw-r--r-- | src/resolve/resolved-def.h | 2 |
16 files changed, 699 insertions, 346 deletions
diff --git a/src/activate/activate.c b/src/activate/activate.c index d50566c97b..0db4967edb 100644 --- a/src/activate/activate.c +++ b/src/activate/activate.c @@ -37,10 +37,11 @@ static char** arg_listen = NULL; static bool arg_accept = false; -static bool arg_datagram = false; +static int arg_socket_type = SOCK_STREAM; static char** arg_args = NULL; static char** arg_setenv = NULL; static const char *arg_fdname = NULL; +static bool arg_inetd = false; static int add_epoll(int epoll_fd, int fd) { struct epoll_event ev = { @@ -96,12 +97,7 @@ static int open_sockets(int *epoll_fd, bool accept) { */ STRV_FOREACH(address, arg_listen) { - - if (arg_datagram) - fd = make_socket_fd(LOG_DEBUG, *address, SOCK_DGRAM, SOCK_CLOEXEC); - else - fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM, (arg_accept*SOCK_CLOEXEC)); - + fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC)); if (fd < 0) { log_open(); return log_error_errno(fd, "Failed to open '%s': %m", *address); @@ -132,14 +128,20 @@ static int open_sockets(int *epoll_fd, bool accept) { return count; } -static int launch(char* name, char **argv, char **env, int fds) { +static int exec_process(const char* name, char **argv, char **env, int start_fd, int n_fds) { - static const char* tocopy[] = {"TERM=", "PATH=", "USER=", "HOME="}; _cleanup_strv_free_ char **envp = NULL; - _cleanup_free_ char *tmp = NULL; + _cleanup_free_ char *joined = NULL; unsigned n_env = 0, length; - char **s; + const char *tocopy; unsigned i; + char **s; + int r; + + if (arg_inetd && n_fds != 1) { + log_error("--inetd only supported for single file descriptors."); + return -EINVAL; + } length = strv_length(arg_setenv); @@ -149,6 +151,7 @@ static int launch(char* name, char **argv, char **env, int fds) { return log_oom(); STRV_FOREACH(s, arg_setenv) { + if (strchr(*s, '=')) { char *k; @@ -172,13 +175,15 @@ static int launch(char* name, char **argv, char **env, int fds) { envp[n_env] = strdup(n); if (!envp[n_env]) return log_oom(); + + n_env ++; } } - for (i = 0; i < ELEMENTSOF(tocopy); i++) { + FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { const char *n; - n = strv_find_prefix(env, tocopy[i]); + n = strv_find_prefix(env, tocopy); if (!n) continue; @@ -189,50 +194,76 @@ static int launch(char* name, char **argv, char **env, int fds) { n_env ++; } - if ((asprintf((char**)(envp + n_env++), "LISTEN_FDS=%d", fds) < 0) || - (asprintf((char**)(envp + n_env++), "LISTEN_PID=%d", getpid()) < 0)) - return log_oom(); + if (arg_inetd) { + assert(n_fds == 1); + + r = dup2(start_fd, STDIN_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdin: %m"); + + r = dup2(start_fd, STDOUT_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdout: %m"); - if (arg_fdname) { - char *e; + start_fd = safe_close(start_fd); + } else { + if (start_fd != SD_LISTEN_FDS_START) { + assert(n_fds == 1); - e = strappend("LISTEN_FDNAMES=", arg_fdname); - if (!e) + r = dup2(start_fd, SD_LISTEN_FDS_START); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection: %m"); + + safe_close(start_fd); + start_fd = SD_LISTEN_FDS_START; + } + + if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%i", n_fds) < 0) return log_oom(); - for (i = 1; i < (unsigned) fds; i++) { - char *c; + if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid()) < 0) + return log_oom(); - c = strjoin(e, ":", arg_fdname, NULL); - if (!c) { - free(e); + if (arg_fdname) { + char *e; + + e = strappend("LISTEN_FDNAMES=", arg_fdname); + if (!e) return log_oom(); + + for (i = 1; i < (unsigned) n_fds; i++) { + char *c; + + c = strjoin(e, ":", arg_fdname, NULL); + if (!c) { + free(e); + return log_oom(); + } + + free(e); + e = c; } - free(e); - e = c; + envp[n_env++] = e; } - - envp[n_env++] = e; } - tmp = strv_join(argv, " "); - if (!tmp) + joined = strv_join(argv, " "); + if (!joined) return log_oom(); - log_info("Execing %s (%s)", name, tmp); + log_info("Execing %s (%s)", name, joined); execvpe(name, argv, envp); - return log_error_errno(errno, "Failed to execp %s (%s): %m", name, tmp); + return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); } -static int launch1(const char* child, char** argv, char **env, int fd) { - _cleanup_free_ char *tmp = NULL; +static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) { + _cleanup_free_ char *joined = NULL; pid_t parent_pid, child_pid; - int r; - tmp = strv_join(argv, " "); - if (!tmp) + joined = strv_join(argv, " "); + if (!joined) return log_oom(); parent_pid = getpid(); @@ -247,24 +278,6 @@ static int launch1(const char* child, char** argv, char **env, int fd) { (void) reset_all_signal_handlers(); (void) reset_signal_mask(); - r = dup2(fd, STDIN_FILENO); - if (r < 0) { - log_error_errno(errno, "Failed to dup connection to stdin: %m"); - _exit(EXIT_FAILURE); - } - - r = dup2(fd, STDOUT_FILENO); - if (r < 0) { - log_error_errno(errno, "Failed to dup connection to stdout: %m"); - _exit(EXIT_FAILURE); - } - - r = close(fd); - if (r < 0) { - log_error_errno(errno, "Failed to close dupped connection: %m"); - _exit(EXIT_FAILURE); - } - /* Make sure the child goes away when the parent dies */ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) _exit(EXIT_FAILURE); @@ -274,31 +287,27 @@ static int launch1(const char* child, char** argv, char **env, int fd) { if (getppid() != parent_pid) _exit(EXIT_SUCCESS); - execvp(child, argv); - log_error_errno(errno, "Failed to exec child %s: %m", child); + exec_process(child, argv, env, fd, 1); _exit(EXIT_FAILURE); } - log_info("Spawned %s (%s) as PID %d", child, tmp, child_pid); - + log_info("Spawned %s (%s) as PID %d", child, joined, child_pid); return 0; } static int do_accept(const char* name, char **argv, char **envp, int fd) { _cleanup_free_ char *local = NULL, *peer = NULL; - _cleanup_close_ int fd2 = -1; + _cleanup_close_ int fd_accepted = -1; - fd2 = accept(fd, NULL, NULL); - if (fd2 < 0) { - log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); - return fd2; - } + fd_accepted = accept4(fd, NULL, NULL, 0); + if (fd_accepted < 0) + return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); - getsockname_pretty(fd2, &local); - getpeername_pretty(fd2, true, &peer); + getsockname_pretty(fd_accepted, &local); + getpeername_pretty(fd_accepted, true, &peer); log_info("Connection from %s to %s", strna(peer), strna(local)); - return launch1(name, argv, envp, fd2); + return fork_and_exec_process(name, argv, envp, fd_accepted); } /* SIGCHLD handler. */ @@ -306,21 +315,24 @@ static void sigchld_hdl(int sig, siginfo_t *t, void *data) { PROTECT_ERRNO; log_info("Child %d died with code %d", t->si_pid, t->si_status); + /* Wait for a dead child. */ - waitpid(t->si_pid, NULL, 0); + (void) waitpid(t->si_pid, NULL, 0); } static int install_chld_handler(void) { - int r; - struct sigaction act = { + static const struct sigaction act = { .sa_flags = SA_SIGINFO, .sa_sigaction = sigchld_hdl, }; + int r; + r = sigaction(SIGCHLD, &act, 0); if (r < 0) - log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - return r; + return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + + return 0; } static void help(void) { @@ -331,8 +343,10 @@ static void help(void) { " --version Print version string and exit\n" " -l --listen=ADDR Listen for raw connections at ADDR\n" " -d --datagram Listen on datagram instead of stream socket\n" + " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" " -a --accept Spawn separate child for each connection\n" " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" + " --inetd Enable inetd file descriptor passing protocol\n" "\n" "Note: file descriptors from sd_listen_fds() will be passed through.\n" , program_invocation_short_name); @@ -342,17 +356,21 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FDNAME, + ARG_SEQPACKET, + ARG_INETD, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "datagram", no_argument, NULL, 'd' }, + { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, { "listen", required_argument, NULL, 'l' }, { "accept", no_argument, NULL, 'a' }, { "setenv", required_argument, NULL, 'E' }, { "environment", required_argument, NULL, 'E' }, /* legacy alias */ { "fdname", required_argument, NULL, ARG_FDNAME }, + { "inetd", no_argument, NULL, ARG_INETD }, {} }; @@ -378,7 +396,21 @@ static int parse_argv(int argc, char *argv[]) { break; case 'd': - arg_datagram = true; + if (arg_socket_type == SOCK_SEQPACKET) { + log_error("--datagram may not be combined with --seqpacket."); + return -EINVAL; + } + + arg_socket_type = SOCK_DGRAM; + break; + + case ARG_SEQPACKET: + if (arg_socket_type == SOCK_DGRAM) { + log_error("--seqpacket may not be combined with --datagram."); + return -EINVAL; + } + + arg_socket_type = SOCK_SEQPACKET; break; case 'a': @@ -401,6 +433,10 @@ static int parse_argv(int argc, char *argv[]) { arg_fdname = optarg; break; + case ARG_INETD: + arg_inetd = true; + break; + case '?': return -EINVAL; @@ -414,7 +450,7 @@ static int parse_argv(int argc, char *argv[]) { return -EINVAL; } - if (arg_datagram && arg_accept) { + if (arg_socket_type == SOCK_DGRAM && arg_accept) { log_error("Datagram sockets do not accept connections. " "The --datagram and --accept options may not be combined."); return -EINVAL; @@ -462,15 +498,14 @@ int main(int argc, char **argv, char **envp) { log_info("Communication attempt on fd %i.", event.data.fd); if (arg_accept) { - r = do_accept(argv[optind], argv + optind, envp, - event.data.fd); + r = do_accept(argv[optind], argv + optind, envp, event.data.fd); if (r < 0) return EXIT_FAILURE; } else break; } - launch(argv[optind], argv + optind, envp, n); + exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, n); return EXIT_SUCCESS; } diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 7a866f2e23..49e5f5b125 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -871,14 +871,13 @@ int send_one_fd_sa( struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(int))]; } control = {}; - struct cmsghdr *cmsg; - struct msghdr mh = { .msg_name = (struct sockaddr*) sa, .msg_namelen = len, .msg_control = &control, .msg_controllen = sizeof(control), }; + struct cmsghdr *cmsg; assert(transport_fd >= 0); assert(fd >= 0); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index c2a47c7b11..5b99398307 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -161,7 +161,7 @@ Unit.OnFailureJobMode, config_parse_job_mode, 0, Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 -Unit.JobTimeoutSec, config_parse_sec, 0, offsetof(Unit, job_timeout) +Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) @@ -215,9 +215,9 @@ Service.ExecReload, config_parse_exec, SERVICE_EXE Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) -Service.TimeoutSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec) -Service.TimeoutStartSec, config_parse_service_timeout, 0, offsetof(Service, timeout_start_usec) -Service.TimeoutStopSec, config_parse_service_timeout, 0, offsetof(Service, timeout_stop_usec) +Service.TimeoutSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 8e5ef935f7..e0c318c110 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1711,18 +1711,20 @@ int config_parse_bus_name( return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); } -int config_parse_service_timeout(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_service_timeout( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { Service *s = userdata; + usec_t usec; int r; assert(filename); @@ -1730,25 +1732,63 @@ int config_parse_service_timeout(const char *unit, assert(rvalue); assert(s); - r = config_parse_sec(unit, filename, line, section, section_line, lvalue, ltype, - rvalue, data, userdata); - if (r < 0) - return r; + /* This is called for three cases: TimeoutSec=, TimeoutStopSec= and TimeoutStartSec=. */ - if (streq(lvalue, "TimeoutSec")) { - s->start_timeout_defined = true; - s->timeout_stop_usec = s->timeout_start_usec; - } else if (streq(lvalue, "TimeoutStartSec")) - s->start_timeout_defined = true; + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle * all other timeouts. */ + if (usec <= 0) + usec = USEC_INFINITY; + + if (!streq(lvalue, "TimeoutStopSec")) { + s->start_timeout_defined = true; + s->timeout_start_usec = usec; + } + + if (!streq(lvalue, "TimeoutStartSec")) + s->timeout_stop_usec = usec; + + return 0; +} + +int config_parse_sec_fix_0( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + usec_t *usec = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(usec); + + /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for + * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a + * timeout. */ + + r = parse_sec(rvalue, usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } - if (s->timeout_start_usec <= 0) - s->timeout_start_usec = USEC_INFINITY; - if (s->timeout_stop_usec <= 0) - s->timeout_stop_usec = USEC_INFINITY; + if (*usec <= 0) + *usec = USEC_INFINITY; return 0; } diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index da215195cb..5fb5910919 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -109,6 +109,7 @@ int config_parse_bus_name(const char* unit, const char *filename, unsigned line, int config_parse_exec_utmp_mode(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_working_directory(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_fdname(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_sec_fix_0(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/core/main.c b/src/core/main.c index 0e6832c122..e2088574c0 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -172,19 +172,15 @@ noreturn static void crash(int sig) { if (pid < 0) log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); else if (pid == 0) { - struct rlimit rl = { - .rlim_cur = RLIM_INFINITY, - .rlim_max = RLIM_INFINITY, - }; - /* Enable default signal handler for core dump */ + sa = (struct sigaction) { .sa_handler = SIG_DFL, }; (void) sigaction(sig, &sa, NULL); - /* Don't limit the core dump size */ - (void) setrlimit(RLIMIT_CORE, &rl); + /* Don't limit the coredump size */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); /* Just to be sure... */ (void) chdir("/"); @@ -1466,6 +1462,17 @@ int main(int argc, char *argv[]) { kernel_timestamp = DUAL_TIMESTAMP_NULL; } + if (getpid() == 1) { + /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) + * will process core dumps for system services by default. */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored + * until the systemd-coredump tool is enabled via sysctl. */ + if (!skip_setup) + (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); + } + /* Initialize default unit */ r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); if (r < 0) { diff --git a/src/coredump/Makefile b/src/coredump/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/coredump/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/journal/coredump-vacuum.c b/src/coredump/coredump-vacuum.c index f02b6dbd87..f02b6dbd87 100644 --- a/src/journal/coredump-vacuum.c +++ b/src/coredump/coredump-vacuum.c diff --git a/src/journal/coredump-vacuum.h b/src/coredump/coredump-vacuum.h index 4b7b9f2d98..4b7b9f2d98 100644 --- a/src/journal/coredump-vacuum.h +++ b/src/coredump/coredump-vacuum.h diff --git a/src/journal/coredump.c b/src/coredump/coredump.c index 8b1c670cc6..085909c20c 100644 --- a/src/journal/coredump.c +++ b/src/coredump/coredump.c @@ -24,12 +24,13 @@ #include <unistd.h> #ifdef HAVE_ELFUTILS -# include <dwarf.h> -# include <elfutils/libdwfl.h> +#include <dwarf.h> +#include <elfutils/libdwfl.h> #endif #include "sd-journal.h" #include "sd-login.h" +#include "sd-daemon.h" #include "acl-util.h" #include "alloc-util.h" @@ -51,6 +52,7 @@ #include "mkdir.h" #include "parse-util.h" #include "process-util.h" +#include "socket-util.h" #include "special.h" #include "stacktrace.h" #include "string-table.h" @@ -62,12 +64,10 @@ /* The maximum size up to which we process coredumps */ #define PROCESS_SIZE_MAX ((uint64_t) (2LLU*1024LLU*1024LLU*1024LLU)) -/* The maximum size up to which we leave the coredump around on - * disk */ +/* The maximum size up to which we leave the coredump around on disk */ #define EXTERNAL_SIZE_MAX PROCESS_SIZE_MAX -/* The maximum size up to which we store the coredump in the - * journal */ +/* The maximum size up to which we store the coredump in the journal */ #define JOURNAL_SIZE_MAX ((size_t) (767LU*1024LU*1024LU)) /* Make sure to not make this larger than the maximum journal entry @@ -75,14 +75,17 @@ assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX); enum { - INFO_PID, - INFO_UID, - INFO_GID, - INFO_SIGNAL, - INFO_TIMESTAMP, - INFO_COMM, - INFO_EXE, - _INFO_LEN + /* We use this as array indexes for a couple of special fields we use for naming coredumping files, and + * attaching xattrs */ + CONTEXT_PID, + CONTEXT_UID, + CONTEXT_GID, + CONTEXT_SIGNAL, + CONTEXT_TIMESTAMP, + CONTEXT_RLIMIT, + CONTEXT_COMM, + CONTEXT_EXE, + _CONTEXT_MAX }; typedef enum CoredumpStorage { @@ -173,16 +176,16 @@ static int fix_acl(int fd, uid_t uid) { return 0; } -static int fix_xattr(int fd, const char *info[_INFO_LEN]) { +static int fix_xattr(int fd, const char *context[_CONTEXT_MAX]) { - static const char * const xattrs[_INFO_LEN] = { - [INFO_PID] = "user.coredump.pid", - [INFO_UID] = "user.coredump.uid", - [INFO_GID] = "user.coredump.gid", - [INFO_SIGNAL] = "user.coredump.signal", - [INFO_TIMESTAMP] = "user.coredump.timestamp", - [INFO_COMM] = "user.coredump.comm", - [INFO_EXE] = "user.coredump.exe", + static const char * const xattrs[_CONTEXT_MAX] = { + [CONTEXT_PID] = "user.coredump.pid", + [CONTEXT_UID] = "user.coredump.uid", + [CONTEXT_GID] = "user.coredump.gid", + [CONTEXT_SIGNAL] = "user.coredump.signal", + [CONTEXT_TIMESTAMP] = "user.coredump.timestamp", + [CONTEXT_COMM] = "user.coredump.comm", + [CONTEXT_EXE] = "user.coredump.exe", }; int r = 0; @@ -193,13 +196,13 @@ static int fix_xattr(int fd, const char *info[_INFO_LEN]) { /* Attach some metadata to coredumps via extended * attributes. Just because we can. */ - for (i = 0; i < _INFO_LEN; i++) { + for (i = 0; i < _CONTEXT_MAX; i++) { int k; - if (isempty(info[i]) || !xattrs[i]) + if (isempty(context[i]) || !xattrs[i]) continue; - k = fsetxattr(fd, xattrs[i], info[i], strlen(info[i]), XATTR_CREATE); + k = fsetxattr(fd, xattrs[i], context[i], strlen(context[i]), XATTR_CREATE); if (k < 0 && r == 0) r = -errno; } @@ -213,18 +216,18 @@ static int fix_permissions( int fd, const char *filename, const char *target, - const char *info[_INFO_LEN], + const char *context[_CONTEXT_MAX], uid_t uid) { assert(fd >= 0); assert(filename); assert(target); - assert(info); + assert(context); /* Ignore errors on these */ - fchmod(fd, 0640); - fix_acl(fd, uid); - fix_xattr(fd, info); + (void) fchmod(fd, 0640); + (void) fix_acl(fd, uid); + (void) fix_xattr(fd, context); if (fsync(fd) < 0) return log_error_errno(errno, "Failed to sync coredump %s: %m", filename); @@ -252,18 +255,18 @@ static int maybe_remove_external_coredump(const char *filename, uint64_t size) { return 1; } -static int make_filename(const char *info[_INFO_LEN], char **ret) { +static int make_filename(const char *context[_CONTEXT_MAX], char **ret) { _cleanup_free_ char *c = NULL, *u = NULL, *p = NULL, *t = NULL; sd_id128_t boot = {}; int r; - assert(info); + assert(context); - c = filename_escape(info[INFO_COMM]); + c = filename_escape(context[CONTEXT_COMM]); if (!c) return -ENOMEM; - u = filename_escape(info[INFO_UID]); + u = filename_escape(context[CONTEXT_UID]); if (!u) return -ENOMEM; @@ -271,11 +274,11 @@ static int make_filename(const char *info[_INFO_LEN], char **ret) { if (r < 0) return r; - p = filename_escape(info[INFO_PID]); + p = filename_escape(context[CONTEXT_PID]); if (!p) return -ENOMEM; - t = filename_escape(info[INFO_TIMESTAMP]); + t = filename_escape(context[CONTEXT_TIMESTAMP]); if (!t) return -ENOMEM; @@ -292,8 +295,8 @@ static int make_filename(const char *info[_INFO_LEN], char **ret) { } static int save_external_coredump( - const char *info[_INFO_LEN], - uid_t uid, + const char *context[_CONTEXT_MAX], + int input_fd, char **ret_filename, int *ret_node_fd, int *ret_data_fd, @@ -301,16 +304,34 @@ static int save_external_coredump( _cleanup_free_ char *fn = NULL, *tmp = NULL; _cleanup_close_ int fd = -1; + uint64_t rlimit, max_size; struct stat st; + uid_t uid; int r; - assert(info); + assert(context); assert(ret_filename); assert(ret_node_fd); assert(ret_data_fd); assert(ret_size); - r = make_filename(info, &fn); + r = parse_uid(context[CONTEXT_UID], &uid); + if (r < 0) + return log_error_errno(r, "Failed to parse UID: %m"); + + r = safe_atou64(context[CONTEXT_RLIMIT], &rlimit); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", context[CONTEXT_RLIMIT]); + if (rlimit <= 0) { + /* Is coredumping disabled? Then don't bother saving/processing the coredump */ + log_info("Core Dumping has been disabled for process %s (%s).", context[CONTEXT_PID], context[CONTEXT_COMM]); + return -EBADSLT; + } + + /* Never store more than the process configured, or than we actually shall keep or process */ + max_size = MIN(rlimit, MAX(arg_process_size_max, arg_external_size_max)); + + r = make_filename(context, &fn); if (r < 0) return log_error_errno(r, "Failed to determine coredump file name: %m"); @@ -324,12 +345,12 @@ static int save_external_coredump( if (fd < 0) return log_error_errno(errno, "Failed to create coredump file %s: %m", tmp); - r = copy_bytes(STDIN_FILENO, fd, arg_process_size_max, false); + r = copy_bytes(input_fd, fd, max_size, false); if (r == -EFBIG) { - log_error("Coredump of %s (%s) is larger than configured processing limit, refusing.", info[INFO_PID], info[INFO_COMM]); + log_error("Coredump of %s (%s) is larger than configured processing limit, refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); goto fail; } else if (IN_SET(r, -EDQUOT, -ENOSPC)) { - log_error("Not enough disk space for coredump of %s (%s), refusing.", info[INFO_PID], info[INFO_COMM]); + log_error("Not enough disk space for coredump of %s (%s), refusing.", context[CONTEXT_PID], context[CONTEXT_COMM]); goto fail; } else if (r < 0) { log_error_errno(r, "Failed to dump coredump to file: %m"); @@ -378,7 +399,7 @@ static int save_external_coredump( goto fail_compressed; } - r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, info, uid); + r = fix_permissions(fd_compressed, tmp_compressed, fn_compressed, context, uid); if (r < 0) goto fail_compressed; @@ -396,13 +417,13 @@ static int save_external_coredump( return 0; fail_compressed: - unlink_noerrno(tmp_compressed); + (void) unlink(tmp_compressed); } uncompressed: #endif - r = fix_permissions(fd, tmp, fn, info, uid); + r = fix_permissions(fd, tmp, fn, context, uid); if (r < 0) goto fail; @@ -417,7 +438,7 @@ uncompressed: return 0; fail: - unlink_noerrno(tmp); + (void) unlink(tmp); return r; } @@ -539,186 +560,473 @@ static int compose_open_fds(pid_t pid, char **open_fds) { return 0; } -int main(int argc, char* argv[]) { +static int change_uid_gid(const char *context[]) { + uid_t uid; + gid_t gid; + int r; - /* The small core field we allocate on the stack, to keep things simple */ - char - *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, - *core_session = NULL, *core_exe = NULL, *core_comm = NULL, *core_cmdline = NULL, - *core_cgroup = NULL, *core_cwd = NULL, *core_root = NULL, *core_unit = NULL, - *core_slice = NULL; + r = parse_uid(context[CONTEXT_UID], &uid); + if (r < 0) + return r; - /* The larger ones we allocate on the heap */ - _cleanup_free_ char - *core_timestamp = NULL, *core_message = NULL, *coredump_data = NULL, *core_owner_uid = NULL, - *core_open_fds = NULL, *core_proc_status = NULL, *core_proc_maps = NULL, *core_proc_limits = NULL, - *core_proc_cgroup = NULL, *core_environ = NULL; + if (uid <= SYSTEM_UID_MAX) { + const char *user = "systemd-coredump"; - _cleanup_free_ char *exe = NULL, *comm = NULL, *filename = NULL; - const char *info[_INFO_LEN]; + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_warning_errno(r, "Cannot resolve %s user. Proceeding to dump core as root: %m", user); + uid = gid = 0; + } + } else { + r = parse_gid(context[CONTEXT_GID], &gid); + if (r < 0) + return r; + } - _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; + return drop_privileges(uid, gid, 0); +} + +static int submit_coredump( + const char *context[_CONTEXT_MAX], + struct iovec *iovec, + size_t n_iovec_allocated, + size_t n_iovec, + int input_fd) { - struct iovec iovec[26]; + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; + _cleanup_free_ char *core_message = NULL, *filename = NULL, *coredump_data = NULL; uint64_t coredump_size; - int r, j = 0; - uid_t uid, owner_uid; - gid_t gid; - pid_t pid; - char *t; - const char *p; + int r; - /* Make sure we never enter a loop */ - prctl(PR_SET_DUMPABLE, 0); + assert(context); + assert(iovec); + assert(n_iovec_allocated >= n_iovec + 3); + assert(input_fd >= 0); - /* 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_set_target(LOG_TARGET_KMSG); - log_open(); + /* Vacuum before we write anything again */ + (void) coredump_vacuum(-1, arg_keep_free, arg_max_use); - if (argc < INFO_COMM + 1) { - log_error("Not enough arguments passed from kernel (%d, expected %d).", - argc - 1, INFO_COMM + 1 - 1); - r = -EINVAL; - goto finish; + /* Always stream the coredump to disk, if that's possible */ + r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); + if (r < 0) + /* Skip whole core dumping part */ + goto log; + + /* If we don't want to keep the coredump on disk, remove it now, as later on we will lack the privileges for + * it. However, we keep the fd to it, so that we can still process it and log it. */ + r = maybe_remove_external_coredump(filename, coredump_size); + if (r < 0) + return r; + if (r == 0) { + const char *coredump_filename; + + coredump_filename = strjoina("COREDUMP_FILENAME=", filename); + IOVEC_SET_STRING(iovec[n_iovec++], coredump_filename); } - /* Ignore all parse errors */ - parse_config(); + /* Vacuum again, but exclude the coredump we just created */ + (void) coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); - log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage)); - log_debug("Selected compression %s.", yes_no(arg_compress)); + /* Now, let's drop privileges to become the user who owns the segfaulted process and allocate the coredump + * memory under the user's uid. This also ensures that the credentials journald will see are the ones of the + * coredumping user, thus making sure the user gets access to the core dump. Let's also get rid of all + * capabilities, if we run as root, we won't need them anymore. */ + r = change_uid_gid(context); + if (r < 0) + return log_error_errno(r, "Failed to drop privileges: %m"); - r = parse_uid(argv[INFO_UID + 1], &uid); - if (r < 0) { - log_error("Failed to parse UID."); - goto finish; +#ifdef HAVE_ELFUTILS + /* Try to get a strack trace if we can */ + if (coredump_size <= arg_process_size_max) { + _cleanup_free_ char *stacktrace = NULL; + + r = coredump_make_stack_trace(coredump_fd, context[CONTEXT_EXE], &stacktrace); + if (r >= 0) + core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.\n\n", stacktrace, NULL); + else if (r == -EINVAL) + log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno())); + else + log_warning_errno(r, "Failed to generate stack trace: %m"); } - r = parse_pid(argv[INFO_PID + 1], &pid); - if (r < 0) { - log_error("Failed to parse PID."); - goto finish; + if (!core_message) +#endif +log: + core_message = strjoin("MESSAGE=Process ", context[CONTEXT_PID], " (", context[CONTEXT_COMM], ") of user ", context[CONTEXT_UID], " dumped core.", NULL); + if (core_message) + IOVEC_SET_STRING(iovec[n_iovec++], core_message); + + /* Optionally store the entire coredump in the journal */ + if (IN_SET(arg_storage, COREDUMP_STORAGE_JOURNAL, COREDUMP_STORAGE_BOTH) && + coredump_size <= arg_journal_size_max) { + size_t sz = 0; + + /* Store the coredump itself in the journal */ + + r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); + if (r >= 0) { + iovec[n_iovec].iov_base = coredump_data; + iovec[n_iovec].iov_len = sz; + n_iovec++; + } } - r = parse_gid(argv[INFO_GID + 1], &gid); - if (r < 0) { - log_error("Failed to parse GID."); + assert(n_iovec <= n_iovec_allocated); + + r = sd_journal_sendv(iovec, n_iovec); + if (r < 0) + return log_error_errno(r, "Failed to log coredump: %m"); + + return 0; +} + +static void map_context_fields(const struct iovec *iovec, const char *context[]) { + + static const char * const context_field_names[_CONTEXT_MAX] = { + [CONTEXT_PID] = "COREDUMP_PID=", + [CONTEXT_UID] = "COREDUMP_UID=", + [CONTEXT_GID] = "COREDUMP_GID=", + [CONTEXT_SIGNAL] = "COREDUMP_SIGNAL=", + [CONTEXT_TIMESTAMP] = "COREDUMP_TIMESTAMP=", + [CONTEXT_COMM] = "COREDUMP_COMM=", + [CONTEXT_EXE] = "COREDUMP_EXE=", + [CONTEXT_RLIMIT] = "COREDUMP_RLIMIT=", + }; + + unsigned i; + + assert(iovec); + assert(context); + + for (i = 0; i < _CONTEXT_MAX; i++) { + size_t l; + + l = strlen(context_field_names[i]); + if (iovec->iov_len < l) + continue; + + if (memcmp(iovec->iov_base, context_field_names[i], l) != 0) + continue; + + /* Note that these strings are NUL terminated, because we made sure that a trailing NUL byte is in the + * buffer, though not included in the iov_len count. (see below) */ + context[i] = (char*) iovec->iov_base + l; + break; + } +} + +static int process_socket(int fd) { + _cleanup_close_ int coredump_fd = -1; + struct iovec *iovec = NULL; + size_t n_iovec = 0, n_iovec_allocated = 0, i; + const char *context[_CONTEXT_MAX] = {}; + int r; + + assert(fd >= 0); + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + for (;;) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iovlen = 1, + }; + ssize_t n; + int l; + + if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { + r = log_oom(); + goto finish; + } + + if (ioctl(fd, FIONREAD, &l) < 0) { + r = log_error_errno(errno, "FIONREAD failed: %m"); + goto finish; + } + + assert(l >= 0); + + iovec[n_iovec].iov_len = l; + iovec[n_iovec].iov_base = malloc(l + 1); + + if (!iovec[n_iovec].iov_base) { + r = log_oom(); + goto finish; + } + + mh.msg_iov = iovec + n_iovec; + + n = recvmsg(fd, &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (n < 0) { + free(iovec[n_iovec].iov_base); + r = log_error_errno(errno, "Failed to receive datagram: %m"); + goto finish; + } + + if (n == 0) { + struct cmsghdr *cmsg, *found = NULL; + /* The final zero-length datagram carries the file descriptor and tells us that we're done. */ + + free(iovec[n_iovec].iov_base); + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + assert(!found); + found = cmsg; + } + } + + if (!found) { + log_error("Coredump file descriptor missing."); + r = -EBADMSG; + goto finish; + } + + assert(coredump_fd < 0); + coredump_fd = *(int*) CMSG_DATA(found); + break; + } + + /* Add trailing NUL byte, in case these are strings */ + ((char*) iovec[n_iovec].iov_base)[n] = 0; + iovec[n_iovec].iov_len = (size_t) n; + + cmsg_close_all(&mh); + map_context_fields(iovec + n_iovec, context); + n_iovec++; + } + + if (!GREEDY_REALLOC(iovec, n_iovec_allocated, n_iovec + 3)) { + r = log_oom(); goto finish; } - if (get_process_comm(pid, &comm) < 0) { - log_warning("Failed to get COMM, falling back to the command line."); - comm = strv_join(argv + INFO_COMM + 1, " "); + /* Make sure we we got all data we really need */ + assert(context[CONTEXT_PID]); + assert(context[CONTEXT_UID]); + assert(context[CONTEXT_GID]); + assert(context[CONTEXT_SIGNAL]); + assert(context[CONTEXT_TIMESTAMP]); + assert(context[CONTEXT_RLIMIT]); + assert(context[CONTEXT_COMM]); + assert(coredump_fd >= 0); + + r = submit_coredump(context, iovec, n_iovec_allocated, n_iovec, coredump_fd); + +finish: + for (i = 0; i < n_iovec; i++) + free(iovec[i].iov_base); + free(iovec); + + return r; +} + +static int send_iovec(const struct iovec iovec[], size_t n_iovec, int input_fd) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/coredump", + }; + _cleanup_close_ int fd = -1; + size_t i; + int r; + + assert(iovec || n_iovec <= 0); + assert(input_fd >= 0); + + fd = socket(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to create coredump socket: %m"); + + if (connect(fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path)) < 0) + return log_error_errno(errno, "Failed to connect to coredump service: %m"); + + for (i = 0; i < n_iovec; i++) { + ssize_t n; + assert(iovec[i].iov_len > 0); + + n = send(fd, iovec[i].iov_base, iovec[i].iov_len, MSG_NOSIGNAL); + if (n < 0) + return log_error_errno(errno, "Failed to send coredump datagram: %m"); } - if (get_process_exe(pid, &exe) < 0) - log_warning("Failed to get EXE."); + r = send_one_fd(fd, input_fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send coredump fd: %m"); - info[INFO_PID] = argv[INFO_PID + 1]; - info[INFO_UID] = argv[INFO_UID + 1]; - info[INFO_GID] = argv[INFO_GID + 1]; - info[INFO_SIGNAL] = argv[INFO_SIGNAL + 1]; - info[INFO_TIMESTAMP] = argv[INFO_TIMESTAMP + 1]; - info[INFO_COMM] = comm; - info[INFO_EXE] = exe; + return 0; +} - if (cg_pid_get_unit(pid, &t) >= 0) { +static int process_journald_crash(const char *context[], int input_fd) { + _cleanup_close_ int coredump_fd = -1, coredump_node_fd = -1; + _cleanup_free_ char *filename = NULL; + uint64_t coredump_size; + int r; - if (streq(t, SPECIAL_JOURNALD_SERVICE)) { - free(t); + assert(context); + assert(input_fd >= 0); - /* If we are journald, we cut things short, - * don't write to the journal, but still - * create a coredump. */ + /* If we are journald, we cut things short, don't write to the journal, but still create a coredump. */ - if (arg_storage != COREDUMP_STORAGE_NONE) - arg_storage = COREDUMP_STORAGE_EXTERNAL; + if (arg_storage != COREDUMP_STORAGE_NONE) + arg_storage = COREDUMP_STORAGE_EXTERNAL; - r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); - if (r < 0) - goto finish; + r = save_external_coredump(context, input_fd, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); + if (r < 0) + return r; - r = maybe_remove_external_coredump(filename, coredump_size); - if (r < 0) - goto finish; + r = maybe_remove_external_coredump(filename, coredump_size); + if (r < 0) + return r; - log_info("Detected coredump of the journal daemon itself, diverted to %s.", filename); - goto finish; + log_info("Detected coredump of the journal daemon itself, diverted to %s.", filename); + return 0; +} + +static int process_kernel(int argc, char* argv[]) { + + /* The small core field we allocate on the stack, to keep things simple */ + char + *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, + *core_session = NULL, *core_exe = NULL, *core_comm = NULL, *core_cmdline = NULL, + *core_cgroup = NULL, *core_cwd = NULL, *core_root = NULL, *core_unit = NULL, + *core_user_unit = NULL, *core_slice = NULL, *core_timestamp = NULL, *core_rlimit = NULL; + + /* The larger ones we allocate on the heap */ + _cleanup_free_ char + *core_owner_uid = NULL, *core_open_fds = NULL, *core_proc_status = NULL, + *core_proc_maps = NULL, *core_proc_limits = NULL, *core_proc_cgroup = NULL, *core_environ = NULL; + + _cleanup_free_ char *exe = NULL, *comm = NULL; + const char *context[_CONTEXT_MAX]; + struct iovec iovec[25]; + size_t n_iovec = 0; + uid_t owner_uid; + const char *p; + pid_t pid; + char *t; + int r; + + if (argc < CONTEXT_COMM + 1) { + log_error("Not enough arguments passed from kernel (%i, expected %i).", argc - 1, CONTEXT_COMM + 1 - 1); + return -EINVAL; + } + + r = parse_pid(argv[CONTEXT_PID + 1], &pid); + if (r < 0) + return log_error_errno(r, "Failed to parse PID."); + + r = get_process_comm(pid, &comm); + if (r < 0) { + log_warning_errno(r, "Failed to get COMM, falling back to the command line: %m"); + comm = strv_join(argv + CONTEXT_COMM + 1, " "); + if (!comm) + return log_oom(); + } + + r = get_process_exe(pid, &exe); + if (r < 0) + log_warning_errno(r, "Failed to get EXE, ignoring: %m"); + + context[CONTEXT_PID] = argv[CONTEXT_PID + 1]; + context[CONTEXT_UID] = argv[CONTEXT_UID + 1]; + context[CONTEXT_GID] = argv[CONTEXT_GID + 1]; + context[CONTEXT_SIGNAL] = argv[CONTEXT_SIGNAL + 1]; + context[CONTEXT_TIMESTAMP] = argv[CONTEXT_TIMESTAMP + 1]; + context[CONTEXT_RLIMIT] = argv[CONTEXT_RLIMIT + 1]; + context[CONTEXT_COMM] = comm; + context[CONTEXT_EXE] = exe; + + if (cg_pid_get_unit(pid, &t) >= 0) { + + if (streq(t, SPECIAL_JOURNALD_SERVICE)) { + free(t); + return process_journald_crash(context, STDIN_FILENO); } core_unit = strjoina("COREDUMP_UNIT=", t); free(t); - } else if (cg_pid_get_user_unit(pid, &t) >= 0) { - core_unit = strjoina("COREDUMP_USER_UNIT=", t); - free(t); + IOVEC_SET_STRING(iovec[n_iovec++], core_unit); } - if (core_unit) - IOVEC_SET_STRING(iovec[j++], core_unit); - - /* OK, now we know it's not the journal, hence we can make use - * of it now. */ + /* OK, now we know it's not the journal, hence we can make use of it now. */ log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); log_open(); - core_pid = strjoina("COREDUMP_PID=", info[INFO_PID]); - IOVEC_SET_STRING(iovec[j++], core_pid); + if (cg_pid_get_user_unit(pid, &t) >= 0) { + core_user_unit = strjoina("COREDUMP_USER_UNIT=", t); + free(t); + + IOVEC_SET_STRING(iovec[n_iovec++], core_user_unit); + } + + core_pid = strjoina("COREDUMP_PID=", context[CONTEXT_PID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_pid); + + core_uid = strjoina("COREDUMP_UID=", context[CONTEXT_UID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_uid); - core_uid = strjoina("COREDUMP_UID=", info[INFO_UID]); - IOVEC_SET_STRING(iovec[j++], core_uid); + core_gid = strjoina("COREDUMP_GID=", context[CONTEXT_GID]); + IOVEC_SET_STRING(iovec[n_iovec++], core_gid); - core_gid = strjoina("COREDUMP_GID=", info[INFO_GID]); - IOVEC_SET_STRING(iovec[j++], core_gid); + core_signal = strjoina("COREDUMP_SIGNAL=", context[CONTEXT_SIGNAL]); + IOVEC_SET_STRING(iovec[n_iovec++], core_signal); - core_signal = strjoina("COREDUMP_SIGNAL=", info[INFO_SIGNAL]); - IOVEC_SET_STRING(iovec[j++], core_signal); + core_rlimit = strjoina("COREDUMP_RLIMIT=", context[CONTEXT_RLIMIT]); + IOVEC_SET_STRING(iovec[n_iovec++], core_rlimit); if (sd_pid_get_session(pid, &t) >= 0) { core_session = strjoina("COREDUMP_SESSION=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_session); + IOVEC_SET_STRING(iovec[n_iovec++], core_session); } if (sd_pid_get_owner_uid(pid, &owner_uid) >= 0) { - r = asprintf(&core_owner_uid, - "COREDUMP_OWNER_UID=" UID_FMT, owner_uid); + r = asprintf(&core_owner_uid, "COREDUMP_OWNER_UID=" UID_FMT, owner_uid); if (r > 0) - IOVEC_SET_STRING(iovec[j++], core_owner_uid); + IOVEC_SET_STRING(iovec[n_iovec++], core_owner_uid); } if (sd_pid_get_slice(pid, &t) >= 0) { core_slice = strjoina("COREDUMP_SLICE=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_slice); + IOVEC_SET_STRING(iovec[n_iovec++], core_slice); } if (comm) { core_comm = strjoina("COREDUMP_COMM=", comm); - IOVEC_SET_STRING(iovec[j++], core_comm); + IOVEC_SET_STRING(iovec[n_iovec++], core_comm); } if (exe) { core_exe = strjoina("COREDUMP_EXE=", exe); - IOVEC_SET_STRING(iovec[j++], core_exe); + IOVEC_SET_STRING(iovec[n_iovec++], core_exe); } if (get_process_cmdline(pid, 0, false, &t) >= 0) { core_cmdline = strjoina("COREDUMP_CMDLINE=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_cmdline); + IOVEC_SET_STRING(iovec[n_iovec++], core_cmdline); } if (cg_pid_get_path_shifted(pid, NULL, &t) >= 0) { core_cgroup = strjoina("COREDUMP_CGROUP=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_cgroup); + IOVEC_SET_STRING(iovec[n_iovec++], core_cgroup); } if (compose_open_fds(pid, &t) >= 0) { @@ -726,7 +1034,7 @@ int main(int argc, char* argv[]) { free(t); if (core_open_fds) - IOVEC_SET_STRING(iovec[j++], core_open_fds); + IOVEC_SET_STRING(iovec[n_iovec++], core_open_fds); } p = procfs_file_alloca(pid, "status"); @@ -735,7 +1043,7 @@ int main(int argc, char* argv[]) { free(t); if (core_proc_status) - IOVEC_SET_STRING(iovec[j++], core_proc_status); + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_status); } p = procfs_file_alloca(pid, "maps"); @@ -744,7 +1052,7 @@ int main(int argc, char* argv[]) { free(t); if (core_proc_maps) - IOVEC_SET_STRING(iovec[j++], core_proc_maps); + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_maps); } p = procfs_file_alloca(pid, "limits"); @@ -753,7 +1061,7 @@ int main(int argc, char* argv[]) { free(t); if (core_proc_limits) - IOVEC_SET_STRING(iovec[j++], core_proc_limits); + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_limits); } p = procfs_file_alloca(pid, "cgroup"); @@ -762,21 +1070,21 @@ int main(int argc, char* argv[]) { free(t); if (core_proc_cgroup) - IOVEC_SET_STRING(iovec[j++], core_proc_cgroup); + IOVEC_SET_STRING(iovec[n_iovec++], core_proc_cgroup); } if (get_process_cwd(pid, &t) >= 0) { core_cwd = strjoina("COREDUMP_CWD=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_cwd); + IOVEC_SET_STRING(iovec[n_iovec++], core_cwd); } if (get_process_root(pid, &t) >= 0) { core_root = strjoina("COREDUMP_ROOT=", t); free(t); - IOVEC_SET_STRING(iovec[j++], core_root); + IOVEC_SET_STRING(iovec[n_iovec++], core_root); } if (get_process_environ(pid, &t) >= 0) { @@ -784,96 +1092,56 @@ int main(int argc, char* argv[]) { free(t); if (core_environ) - IOVEC_SET_STRING(iovec[j++], core_environ); + IOVEC_SET_STRING(iovec[n_iovec++], core_environ); } - core_timestamp = strjoin("COREDUMP_TIMESTAMP=", info[INFO_TIMESTAMP], "000000", NULL); - if (core_timestamp) - IOVEC_SET_STRING(iovec[j++], core_timestamp); + core_timestamp = strjoina("COREDUMP_TIMESTAMP=", context[CONTEXT_TIMESTAMP], "000000", NULL); + IOVEC_SET_STRING(iovec[n_iovec++], core_timestamp); - IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); + IOVEC_SET_STRING(iovec[n_iovec++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); assert_cc(2 == LOG_CRIT); - IOVEC_SET_STRING(iovec[j++], "PRIORITY=2"); - - /* Vacuum before we write anything again */ - coredump_vacuum(-1, arg_keep_free, arg_max_use); - - /* Always stream the coredump to disk, if that's possible */ - r = save_external_coredump(info, uid, &filename, &coredump_node_fd, &coredump_fd, &coredump_size); - if (r < 0) - /* skip whole core dumping part */ - goto log; + IOVEC_SET_STRING(iovec[n_iovec++], "PRIORITY=2"); - /* If we don't want to keep the coredump on disk, remove it - * now, as later on we will lack the privileges for - * it. However, we keep the fd to it, so that we can still - * process it and log it. */ - r = maybe_remove_external_coredump(filename, coredump_size); - if (r < 0) - goto finish; - if (r == 0) { - const char *coredump_filename; + assert(n_iovec <= ELEMENTSOF(iovec)); - coredump_filename = strjoina("COREDUMP_FILENAME=", filename); - IOVEC_SET_STRING(iovec[j++], coredump_filename); - } + return send_iovec(iovec, n_iovec, STDIN_FILENO); +} - /* Vacuum again, but exclude the coredump we just created */ - coredump_vacuum(coredump_node_fd >= 0 ? coredump_node_fd : coredump_fd, arg_keep_free, arg_max_use); - - /* Now, let's drop privileges to become the user who owns the - * segfaulted process and allocate the coredump memory under - * the user's uid. This also ensures that the credentials - * journald will see are the ones of the coredumping user, - * thus making sure the user gets access to the core - * dump. Let's also get rid of all capabilities, if we run as - * root, we won't need them anymore. */ - r = drop_privileges(uid, gid, 0); - if (r < 0) { - log_error_errno(r, "Failed to drop privileges: %m"); - goto finish; - } +int main(int argc, char *argv[]) { + int r; -#ifdef HAVE_ELFUTILS - /* Try to get a strack trace if we can */ - if (coredump_size <= arg_process_size_max) { - _cleanup_free_ char *stacktrace = NULL; + /* 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. */ - r = coredump_make_stack_trace(coredump_fd, exe, &stacktrace); - if (r >= 0) - core_message = strjoin("MESSAGE=Process ", info[INFO_PID], " (", comm, ") of user ", info[INFO_UID], " dumped core.\n\n", stacktrace, NULL); - else if (r == -EINVAL) - log_warning("Failed to generate stack trace: %s", dwfl_errmsg(dwfl_errno())); - else - log_warning_errno(r, "Failed to generate stack trace: %m"); - } + log_set_target(LOG_TARGET_KMSG); + log_open(); - if (!core_message) -#endif -log: - core_message = strjoin("MESSAGE=Process ", info[INFO_PID], " (", comm, ") of user ", info[INFO_UID], " dumped core.", NULL); - if (core_message) - IOVEC_SET_STRING(iovec[j++], core_message); + /* Make sure we never enter a loop */ + (void) prctl(PR_SET_DUMPABLE, 0); - /* Optionally store the entire coredump in the journal */ - if (IN_SET(arg_storage, COREDUMP_STORAGE_JOURNAL, COREDUMP_STORAGE_BOTH) && - coredump_size <= arg_journal_size_max) { - size_t sz = 0; + /* Ignore all parse errors */ + (void) parse_config(); - /* Store the coredump itself in the journal */ + log_debug("Selected storage '%s'.", coredump_storage_to_string(arg_storage)); + log_debug("Selected compression %s.", yes_no(arg_compress)); - r = allocate_journal_field(coredump_fd, (size_t) coredump_size, &coredump_data, &sz); - if (r >= 0) { - iovec[j].iov_base = coredump_data; - iovec[j].iov_len = sz; - j++; - } + r = sd_listen_fds(false); + if (r < 0) { + log_error_errno(r, "Failed to determine number of file descriptor: %m"); + goto finish; } - r = sd_journal_sendv(iovec, j); - if (r < 0) - log_error_errno(r, "Failed to log coredump: %m"); + /* If we got an fd passed, we are running in coredumpd mode. Otherwise we are invoked from the kernel as + * coredump handler */ + if (r == 0) + r = process_kernel(argc, argv); + else if (r == 1) + r = process_socket(SD_LISTEN_FDS_START); + else { + log_error("Received unexpected number of file descriptors."); + r = -EINVAL; + } finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/src/journal/coredump.conf b/src/coredump/coredump.conf index c2f0643e03..c2f0643e03 100644 --- a/src/journal/coredump.conf +++ b/src/coredump/coredump.conf diff --git a/src/journal/coredumpctl.c b/src/coredump/coredumpctl.c index 0034a1a0ac..0034a1a0ac 100644 --- a/src/journal/coredumpctl.c +++ b/src/coredump/coredumpctl.c diff --git a/src/journal/stacktrace.c b/src/coredump/stacktrace.c index 68806992fc..68806992fc 100644 --- a/src/journal/stacktrace.c +++ b/src/coredump/stacktrace.c diff --git a/src/journal/stacktrace.h b/src/coredump/stacktrace.h index 15e9c04465..15e9c04465 100644 --- a/src/journal/stacktrace.h +++ b/src/coredump/stacktrace.h diff --git a/src/journal/test-coredump-vacuum.c b/src/coredump/test-coredump-vacuum.c index 70a57f183f..70a57f183f 100644 --- a/src/journal/test-coredump-vacuum.c +++ b/src/coredump/test-coredump-vacuum.c diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index d43a1fe25e..c4c1915b18 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -19,6 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <inttypes.h> + #define SD_RESOLVED_DNS (UINT64_C(1) << 0) #define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) #define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) |