Introduce configuration file: /etc/systemd/coredump.conf with configurable core dumps backend storages (journal/file/both/none), per storage size limits and preprocessing.
Default filestorage choosed as /run/log/coredump or /var/log/coredump with next reason: 1. These files produced with systemd component 2. These files registered with journal Main differences between v7 and v6: 1. As Colin suggested, create-then-drop policy used when storing to files. 2. {/var/,/run}/log/coredump/MACHINE-ID folder will be created with root:systemd-journal owner and drwxr-s-- permissions. 3. Trivial preprocessing support added. For example: PreprocessFile=gzip -9 <%i>%o PreprocessJournal=gdb -nw --batch --quiet --silent --ex 'thread \ apply all bt' --core %i %e > %o This will save to file storage compressed cores and backtraces to journal COREDUMP= section. 4. Copying "optimizations" removed for now. Now core always stored to temporary folder, then optionaly preprocessed to temporary files, then temporary files copied to storage. Weak places: coredump.c:495 To prevent copying of large memory segments, I contruct virtual space manually. Probably it will not work as expected in some cases, so recomendations and comments needed.. --- Makefile-man.am | 1 + Makefile.am | 14 +- man/coredump.conf.xml | 217 +++++++++++ src/core/manager.c | 1 + src/journal/coredump-gperf.gperf | 24 ++ src/journal/coredump.c | 764 ++++++++++++++++++++++++++++++++------- src/journal/coredump.conf | 18 + src/journal/coredump.h | 89 +++++ src/journal/journald-server.h | 2 +- 9 files changed, 992 insertions(+), 138 deletions(-) create mode 100644 man/coredump.conf.xml create mode 100644 src/journal/coredump-gperf.gperf create mode 100644 src/journal/coredump.conf create mode 100644 src/journal/coredump.h diff --git a/Makefile-man.am b/Makefile-man.am index 481423a..e3ff8dd 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -8,6 +8,7 @@ MANPAGES += \ man/hostname.5 \ man/journalctl.1 \ man/journald.conf.5 \ + man/coredump.conf.5 \ man/kernel-command-line.7 \ man/kernel-install.8 \ man/locale.conf.5 \ diff --git a/Makefile.am b/Makefile.am index eb85c8d..96e5bc3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2908,7 +2908,8 @@ nodist_systemunit_DATA += \ units/systemd-journal-flush.service dist_pkgsysconf_DATA += \ - src/journal/journald.conf + src/journal/journald.conf \ + src/journal/coredump.conf pkgconfiglib_DATA += \ src/journal/libsystemd-journal.pc @@ -2927,10 +2928,12 @@ EXTRA_DIST += \ src/journal/libsystemd-journal.sym \ units/systemd-journald.service.in \ units/systemd-journal-flush.service.in \ - src/journal/journald-gperf.gperf + src/journal/journald-gperf.gperf \ + src/journal/coredump-gperf.gperf CLEANFILES += \ - src/journal/journald-gperf.c + src/journal/journald-gperf.c \ + src/journal/coredump-gperf.c # ------------------------------------------------------------------------------ if HAVE_MICROHTTPD @@ -2975,10 +2978,13 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ if ENABLE_COREDUMP systemd_coredump_SOURCES = \ - src/journal/coredump.c + src/shared/specifier.c \ + src/journal/coredump.c \ + src/journal/coredump-gperf.c systemd_coredump_LDADD = \ libsystemd-journal-internal.la \ + libsystemd-id128-internal.la \ libsystemd-label.la \ libsystemd-shared.la diff --git a/man/coredump.conf.xml b/man/coredump.conf.xml new file mode 100644 index 0000000..491b9d0 --- /dev/null +++ b/man/coredump.conf.xml @@ -0,0 +1,217 @@ +<?xml version='1.0'?> <!--*-nxml-*--> +<?xml-stylesheet type="text/xsl" href="http://docbook.sourceforge.net/release/xsl/current/xhtml/docbook.xsl"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2013 Lennart Poettering + Oleksii Shevchuk + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="coredump.conf"> + <refentryinfo> + <title>coredump.conf</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Lennart</firstname> + <surname>Poettering</surname> + <email>lenn...@poettering.net</email> + </author> + <author> + <contrib>Developer</contrib> + <firstname>Oleksii</firstname> + <surname>Shevchuk</surname> + <email>alx...@gmail.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>coredump.conf</refentrytitle> + <manvolnum>5</manvolnum> + </refmeta> + + <refnamediv> + <refname>coredump.conf</refname> + <refpurpose>Core dump utility configuration file</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/etc/systemd/coredump.conf</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>This files configures several parameters of the + systemd-coredump utility.</para> + + </refsect1> + + <refsect1> + <title>Options</title> + + <para>All options are configured in the + <literal>[Coredump]</literal> section:</para> + + <variablelist> + + <varlistentry> + <term><varname>FileStorage=</varname></term> + + <listitem><para>One of + <literal>volatile</literal> or, + <literal>persistent</literal> + value. If <literal>volatile</literal>, core dump will be + stored as file to /run/log/coredump/MACHINE-ID/COMM-TMPSUFFIX location; + if <literal>persistent</literal> -- to + /var/log/coredump/MACHINE-ID/COMM-TMPSUFFIX location. + Storage directory will be created if not exists. + COMM will be escaped. + Field COREDUMP_FILE= will be added to log + message with COMM-TMPSUFFIX value.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>MaxJournalCoreSize=</varname></term> + <term><varname>MaxFileCoreSize=</varname></term> + + <listitem><para>Enforce size limits on the core + dumps stored. The option prefixed with <literal>Journal</literal> + apply to the COREDUMP= journal message field. The option prefixed + with <literal>File</literal> apply to the files when stored on a + persistent file system, more specifically + <filename>/var/log/coredump/MACHINE-ID</filename>. If value equals + to zero, then backend will be ommited. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>PreprocessUser=</varname></term> + + <listitem><para>If unspecified <literal>systemd-coredump</literal> will drop privileges to nobody user + to store core to temporary folder and process it. When value is unset, <literal>systemd-coredump</literal> + will drop privileges to process owner uid/gid. When specified, privileges will be changed to that user. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>PreprocessJournal=</varname></term> + <term><varname>PreprocessFile=</varname></term> + + <listitem><para>Core dumps can be preprocessed before storing to storages via command specified + by this option. Processing done by calling specified tools via system() call. Next specifiers can be + used:</para> + + <table> + <title>Specifiers available in processing commands</title> + <tgroup cols='3' align='left' colsep='1' rowsep='1'> + <colspec colname="spec" /> + <colspec colname="mean" /> + <colspec colname="detail" /> + <thead> + <row> + <entry>Specifier</entry> + <entry>Meaning</entry> + <entry>Details</entry> + </row> + </thead> + <tbody> + <row> + <entry><literal>%p</literal></entry> + <entry>Crashed process PID</entry> + <entry></entry> + </row> + <row> + <entry><literal>%u</literal></entry> + <entry>Crashed process UID</entry> + <entry></entry> + </row> + <row> + <entry><literal>%g</literal></entry> + <entry>Crashed process GID</entry> + <entry></entry> + </row> + <row> + <entry><literal>%s</literal></entry> + <entry>Signal, which caused dumping the core</entry> + <entry></entry> + </row> + <row> + <entry><literal>%t</literal></entry> + <entry>Timestamp of crash</entry> + <entry></entry> + </row> + <row> + <entry><literal>%e</literal></entry> + <entry>Crashed process EXE path</entry> + <entry></entry> + </row> + <row> + <entry><literal>%U</literal></entry> + <entry>Crashed process unit name</entry> + <entry></entry> + </row> + <row> + <entry><literal>%S</literal></entry> + <entry>Crashed process session</entry> + <entry>Available only when builded with logind</entry> + </row> + <row> + <entry><literal>%C</literal></entry> + <entry>Crashed process command line</entry> + <entry></entry> + </row> + <row> + <entry><literal>%i</literal></entry> + <entry>Temporary file with stored core</entry> + <entry>This file shouldn't be removed or modified</entry> + </row> + <row> + <entry><literal>%o</literal></entry> + <entry>Temporary file that should contain result of processing</entry> + <entry>If this file doesn't exists, then input file will be used</entry> + </row> + </tbody> + </tgroup> + </table> + </listitem> + </varlistentry> + + <varlistentry><term><varname>PreprocessMaxSize=</varname></term> + + <listitem><para>This will limit stored and processed core size.</para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-coredumpctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + </para> + </refsect1> + +</refentry> diff --git a/src/core/manager.c b/src/core/manager.c index 0508628..efce9ac 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -51,6 +51,7 @@ #include "hashmap.h" #include "macro.h" #include "strv.h" +#include "env-util.h" #include "log.h" #include "util.h" #include "mkdir.h" diff --git a/src/journal/coredump-gperf.gperf b/src/journal/coredump-gperf.gperf new file mode 100644 index 0000000..1bc599c --- /dev/null +++ b/src/journal/coredump-gperf.gperf @@ -0,0 +1,24 @@ +%{ +#include <stddef.h> +#include "coredump.h" +#include "conf-parser.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name coredump_gperf_hash +%define lookup-function-name coredump_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Coredump.MaxJournalCoreSize, config_parse_bytes_off, 0, offsetof(Coredump, journal_max_size) +Coredump.MaxFileCoreSize, config_parse_bytes_off, 0, offsetof(Coredump, file_max_size) +Coredump.FileStorage, config_parse_storage, 0, offsetof(Coredump, storage) +Coredump.PreprocessUser, config_parse_string, 0, offsetof(Coredump, preprocess_user) +Coredump.PreprocessJournal, config_parse_string, 0, offsetof(Coredump, preprocess_journal) +Coredump.PreprocessFile, config_parse_string, 0, offsetof(Coredump, preprocess_file) +Coredump.PreprocessMaxSize, config_parse_bytes_off, 0, offsetof(Coredump, preprocess_max_size) +Coredump.PreprocessDirectoryPath, config_parse_string, 0, offsetof(Coredump, preprocess_directory_path) diff --git a/src/journal/coredump.c b/src/journal/coredump.c index fd03e38..473499d 100644 --- a/src/journal/coredump.c +++ b/src/journal/coredump.c @@ -3,7 +3,8 @@ /*** This file is part of systemd. - Copyright 2012 Lennart Poettering + Copyright 2012-2013 Lennart Poettering + Oleksii Shevchuk 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 @@ -22,7 +23,12 @@ #include <errno.h> #include <unistd.h> #include <stdio.h> +#include <stdint.h> +#include <fcntl.h> +#include <sys/mman.h> #include <sys/prctl.h> +#include <sys/wait.h> +#include <sys/ptrace.h> #include <systemd/sd-journal.h> @@ -35,13 +41,13 @@ #include "macro.h" #include "mkdir.h" #include "special.h" +#include "sd-id128.h" +#include "coredump.h" #include "cgroup-util.h" +#include "specifier.h" -/* Few programs have less than 3MiB resident */ -#define COREDUMP_MIN_START (3*1024*1024) -/* Make sure to not make this larger than the maximum journal entry - * size. See ENTRY_SIZE_MAX in journald-native.c. */ -#define COREDUMP_MAX (768*1024*1024) +#define COREDUMP_MAX_DEFAULT ( 24 * 1024 * 1024 ) +#define COREDUMP_CONFIG "/etc/systemd/coredump.conf" enum { ARG_PID = 1, @@ -53,26 +59,84 @@ enum { _ARG_MAX }; -static int divert_coredump(void) { - _cleanup_fclose_ FILE *f = NULL; +static const char* const storage_table[] = { + [STORAGE_VOLATILE] = "volatile", + [STORAGE_PERSISTENT] = "persistent", +}; - log_info("Detected coredump of the journal daemon itself, diverting coredump to /var/lib/systemd/coredump/."); +DEFINE_STRING_TABLE_LOOKUP(storage, Storage); +DEFINE_CONFIG_PARSE_ENUM(config_parse_storage, storage, Storage, "Failed to parse storage setting"); - mkdir_p_label("/var/lib/systemd/coredump", 0755); +static int coredump_drop_creds(uid_t uid, uid_t gid) { - f = fopen("/var/lib/systemd/coredump/core.systemd-journald", "we"); - if (!f) { - log_error("Failed to create coredump file: %m"); + + if (setresgid(gid, gid, gid) < 0 || + setresuid(uid, uid, uid) < 0) { + log_error("Failed to drop privileges: %m"); return -errno; } + umask(0337); + + return 0; +} + +static char * coredump_tmp_directory(Coredump *s) { + assert(s); + + return strdup(s->preprocess_directory_path); +} + +static char * coredump_directory(Coredump *s) { + sd_id128_t machineid; + char buffer[33]; + + int r = sd_id128_get_machine(&machineid); + if (r) + return NULL; + + return strjoin(s->storage == STORAGE_VOLATILE ? + "/run/log/coredump/": + "/var/log/coredump/", + sd_id128_to_string(machineid, buffer), + NULL); +} + +static int coredump_ensure_directory_exists(Coredump *s) { + static const mode_t mode = S_ISGID + | S_IRUSR | S_IWUSR | S_IXUSR + | S_IRGRP | S_IXGRP + ; + int r; + _cleanup_free_ char *d = NULL; + + assert(s); + + d = coredump_directory(s); + if (!d) + return log_oom(); + + r = mkdir_parents_label(d, 0755); + if (r) + return -errno; + + r = mkdir_safe_label(d, mode, 0, s->journal); + if (r && (errno != EEXIST)) + return -errno; + + return 0; +} + +static int coredump_stdio_to_file(FILE *corefile, size_t max_size) { + size_t offset = 0; for (;;) { uint8_t buffer[4096]; size_t l, q; - l = fread(buffer, 1, sizeof(buffer), stdin); + l = fread(buffer, 1, MIN(sizeof(buffer), max_size - offset), + stdin); if (l <= 0) { - if (ferror(f)) { + if (ferror(corefile)) { log_error("Failed to read coredump: %m"); return -errno; } @@ -80,16 +144,19 @@ static int divert_coredump(void) { break; } - q = fwrite(buffer, 1, l, f); + q = fwrite(buffer, 1, l, corefile); if (q != l) { log_error("Failed to write coredump: %m"); return -errno; } + + if ((offset += q) >= max_size) + break; } - fflush(f); + fflush(corefile); - if (ferror(f)) { + if (ferror(corefile)) { log_error("Failed to write coredump: %m"); return -errno; } @@ -97,181 +164,612 @@ static int divert_coredump(void) { return 0; } -int main(int argc, char* argv[]) { - int r, j = 0; - char *t; - ssize_t n; - pid_t pid; - uid_t uid; - gid_t gid; - struct iovec iovec[14]; - size_t coredump_bufsize, coredump_size; - _cleanup_free_ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, - *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL, - *core_session = NULL, *core_message = NULL, *core_cmdline = NULL, *coredump_data = NULL; +static int coredump_create_file(Coredump *s, char ** name, bool shared) { + _cleanup_free_ char *template = NULL, *escaped_comm = NULL, *directory = NULL; + int fd; - prctl(PR_SET_DUMPABLE, 0); + assert(s); + assert(name); + assert(s->core_comm); - if (argc != _ARG_MAX) { - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_open(); + directory = shared + ? coredump_tmp_directory(s) + : coredump_directory(s) + ; - log_error("Invalid number of arguments passed from kernel."); - r = -EINVAL; - goto finish; + if (! directory) + return log_oom(); + + escaped_comm = xescape(s->core_comm, "/ "); + if (! escaped_comm) + return log_oom(); + + template = strjoin(directory, "/", escaped_comm, "-XXXXXX", NULL); + if (! template) + return log_oom(); + + fd = mkostemp(template, O_CREAT | O_EXCL | O_APPEND | O_RDWR | shared ? 0 : O_CLOEXEC); + + if (fd < 0) + return -errno; + + if (fchmod(fd, S_IRUSR | S_IRGRP)) + return -errno; + + *name = strdup(template); + if (! *name) { + close(fd); + return log_oom(); } - r = parse_pid(argv[ARG_PID], &pid); + return fd; +} + +static void coredump_cleanup_str(char *mem, size_t size) { + (void)size; + free(mem); +} + +static void coredump_cleanup_mem(char *mem, size_t size) { + munmap(mem, size); +} + +static char* specifier_quoted_string(char specifier, void *data, void *userdata) { + return strjoin("'", strempty(data), "'", NULL); +} + +static char* coredump_printf(Coredump *s, + char *input, + char *output, + char *format) { + + const Specifier table[] = { + { 'p', specifier_string, s->core_pid }, + { 'u', specifier_string, s->core_uid }, + { 'g', specifier_string, s->core_gid }, + { 's', specifier_string, s->core_signal }, + { 't', specifier_string, s->core_timestamp }, + { 'e', specifier_quoted_string, s->core_exe }, + { 'U', specifier_quoted_string, s->core_unit }, +#ifdef HAVE_LOGIND + { 'S', specifier_quoted_string, s->core_session }, +#endif + { 'C', specifier_quoted_string, s->core_cmdline }, + { 'i', specifier_quoted_string, input }, + { 'o', specifier_quoted_string, output }, + { 0, NULL, NULL } + }; + + return specifier_printf(format, table, s); +} + +static int coredump_mmap_file(const char *path, char *addr, char **mem, size_t *size) +{ + struct stat stat; + int fd; + + assert(path); + assert(mem); + assert(size); + + fd = open(path, O_NOFOLLOW | O_RDONLY); + + if (fd != -1 && !fstat(fd, &stat) && + (*mem = mmap(addr, stat.st_size, + PROT_READ, MAP_SHARED, + fd, + 0)) != MAP_FAILED) { + *size = stat.st_size; + close(fd); + return 0; + } else { + *mem = NULL; + *size = 0; + log_error("Couldn't mmap file %s: %m", path); + return -errno; + } +} + + +static int coredump_load_state(char ** argv, Coredump *s) { + int r; + const char * journal_group = "systemd-journal"; + const char * preprocess_user = "nobody"; + _cleanup_fclose_ FILE *f = NULL; + + assert(s); + + zero(*s); + + s->journal_max_size = COREDUMP_MAX_DEFAULT; + s->file_max_size = 0; + s->preprocess_max_size = SIZE_MAX; + s->storage = STORAGE_VOLATILE; + s->file_fd = -1; + s->journal = 0; + s->core_user_unit = false; + s->preprocess_directory_path = strdup("/tmp"); + s->preprocess_user = strdup("nobody"); + + s->core_pid = argv[ARG_PID]; + s->core_uid = argv[ARG_UID]; + s->core_gid = argv[ARG_GID]; + s->core_signal = argv[ARG_SIGNAL]; + s->core_comm = argv[ARG_COMM]; + s->core_timestamp = argv[ARG_TIMESTAMP]; + + r = parse_pid(s->core_pid, &s->process_pid); if (r < 0) { log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); log_open(); - log_error("Failed to parse PID."); - goto finish; + return r; } - if (cg_pid_get_unit(pid, &t) >= 0) { + if (cg_pid_get_unit(s->process_pid, &s->core_unit) >= 0) { + char *t = NULL; + s->core_user_unit = false; - if (streq(t, SPECIAL_JOURNALD_SERVICE)) { - /* Make sure we don't make use of the journal, - * if it's the journal which is crashing */ + if(streq(t, SPECIAL_JOURNALD_SERVICE)) { + s->journal_core_dump = true; log_set_target(LOG_TARGET_KMSG); - log_open(); - - r = divert_coredump(); - goto finish; - } - - core_unit = strappend("COREDUMP_UNIT=", t); - } else if (cg_pid_get_user_unit(pid, &t) >= 0) - core_unit = strappend("COREDUMP_USER_UNIT=", t); + } else + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + free(t); + } else { + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + if (cg_pid_get_user_unit(s->process_pid, &s->core_unit) >= 0) + s->core_user_unit = true; + } - if (core_unit) - IOVEC_SET_STRING(iovec[j++], core_unit); + if (s->journal_core_dump) { + s->file_max_size = SIZE_MAX; + s->journal_max_size = 0; + if (! s->storage) + s->storage = STORAGE_VOLATILE; + log_info(SPECIAL_JOURNALD_SERVICE " failed. Dumping to directory"); + } - /* OK, now we know it's not the journal, hence make use of - * it */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); log_open(); - r = parse_uid(argv[ARG_UID], &uid); + r = parse_uid(argv[ARG_UID], &s->process_uid); if (r < 0) { log_error("Failed to parse UID."); - goto finish; + return r; } - r = parse_gid(argv[ARG_GID], &gid); + r = parse_gid(argv[ARG_GID], &s->process_gid); if (r < 0) { log_error("Failed to parse GID."); - goto finish; + return r; } - core_pid = strappend("COREDUMP_PID=", argv[ARG_PID]); - if (core_pid) - IOVEC_SET_STRING(iovec[j++], core_pid); + if (get_group_creds(&journal_group, &s->journal)) + log_warning("Failed to get group %s creds", journal_group); - core_uid = strappend("COREDUMP_UID=", argv[ARG_UID]); - if (core_uid) - IOVEC_SET_STRING(iovec[j++], core_uid); + s->preprocess_uid = s->process_uid; + s->preprocess_gid = s->process_gid; - core_gid = strappend("COREDUMP_GID=", argv[ARG_GID]); - if (core_gid) - IOVEC_SET_STRING(iovec[j++], core_gid); + if (s->preprocess_user) { + preprocess_user = s->preprocess_user; + if (get_user_creds(&preprocess_user, &s->preprocess_uid, &s->preprocess_gid, NULL, NULL)) + log_warning("Failed to get user %s creds", preprocess_user); + } - core_signal = strappend("COREDUMP_SIGNAL=", argv[ARG_SIGNAL]); - if (core_signal) - IOVEC_SET_STRING(iovec[j++], core_signal); + f = fopen(COREDUMP_CONFIG, "re"); + if (!f) { + if (errno == ENOENT) + return 0; - core_comm = strappend("COREDUMP_COMM=", argv[ARG_COMM]); - if (core_comm) - IOVEC_SET_STRING(iovec[j++], core_comm); + log_warning("Failed to open configuration file " COREDUMP_CONFIG ": %m"); + return -errno; + } + + r = config_parse(NULL, COREDUMP_CONFIG, f, "Coredump\0", config_item_perf_lookup, + (void*) coredump_gperf_lookup, false, false, s); + if (r < 0) + log_warning("Failed to parse configuration file: %s", strerror(-r)); + + if (s->file_max_size) { + r = coredump_ensure_directory_exists(s); + if (r) { + log_error("Couldn't create coredump directory: %m"); + return r; + } + + s->file_fd = coredump_create_file(s, &s->file_path, false); + if (s->file_fd < 0) { + log_error("Couldn't create core dump file: %m"); + return s->file_fd; + } + } #ifdef HAVE_LOGIND - if (sd_pid_get_session(pid, &t) >= 0) { - core_session = strappend("COREDUMP_SESSION=", t); - free(t); + sd_pid_get_session(s->process_pid, &s->core_session); +#endif + get_process_exe(s->process_pid, &s->core_exe); + get_process_cmdline(s->process_pid, 0, false, &s->core_cmdline); - if (core_session) - IOVEC_SET_STRING(iovec[j++], core_session); + return 0; +} + +static void coredump_release_state(Coredump * s) { + assert(s); + + if (s->file_max_size) { + close(s->file_fd); + free(s->file_path); } + if (s->processed_journal_data) + munmap(s->processed_journal_data, + s->processed_journal_data_size); + if (s->processed_file_data) + munmap(s->processed_file_data, + s->processed_file_data_size); +#ifdef HAVE_LOGIND + free(s->core_session); #endif + free(s->core_cmdline); + free(s->core_unit); + free(s->core_exe); + free(s->preprocess_user); + free(s->preprocess_journal); + free(s->preprocess_file); + free(s->preprocess_directory_path); +} - if (get_process_exe(pid, &t) >= 0) { - core_exe = strappend("COREDUMP_EXE=", t); - free(t); +static int coredump_preprocess(Coredump *s) { + int r; + + _cleanup_fclose_ FILE *tmp_file = NULL; + _cleanup_close_ int tmp_fd = -1, journal_tmp_fd = -1, file_tmp_fd = -1; + _cleanup_free_ char *tmp_filename = NULL, *journal_tmp_filename = NULL, + *file_tmp_filename = NULL, *preprocess_journal = NULL, + *preprocess_file = NULL; + + struct stat stat; + + int child; + int status; + + assert(s); + + tmp_fd = coredump_create_file(s, &tmp_filename, true); + if (tmp_fd < 0) { + log_error("Couldn't create temporary storage file: %m"); + return -errno; + } + + if (fchown(tmp_fd, s->preprocess_uid, s->preprocess_gid)) + return -errno; + + if (s->preprocess_file) { + file_tmp_fd = coredump_create_file(s, &file_tmp_filename, true); + if (file_tmp_fd < 0) + return file_tmp_fd; + if (fchmod(file_tmp_fd, 0600)) + return -errno; + if (fchown(file_tmp_fd, s->preprocess_uid, s->preprocess_gid)) + return -errno; + close(file_tmp_fd); + file_tmp_fd = -1; + } + + if (s->preprocess_journal) { + journal_tmp_fd = coredump_create_file(s, &journal_tmp_filename, true); + if (journal_tmp_fd < 0) + return file_tmp_fd; + if (fchmod(journal_tmp_fd, 0600)) + return -errno; + if (fchown(journal_tmp_fd, s->preprocess_uid, s->preprocess_gid)) + return -errno; + close(journal_tmp_fd); + journal_tmp_fd = -1; + } + + child = fork(); + if (child == -1) + return -errno; + + if (child) { + static const char COREDUMP[] = "COREDUMP="; + int rj, rf; + int header_size = (strlen(COREDUMP)/(sysconf(_SC_PAGE_SIZE)) + 1) * sysconf(_SC_PAGE_SIZE); + + if (waitpid(child, &status, 0) == -1) + return -errno; + if (! (WIFEXITED(status) && WEXITSTATUS(status) == 0)) + return -1; + + rj = coredump_mmap_file(s->preprocess_journal ? journal_tmp_filename : tmp_filename, + NULL, + &s->processed_journal_data, + &s->processed_journal_data_size); + + if (!rj) { + /* Ok. sd_journal_sendv doesn't support scatter/gatter. + * Let's workaround it with dark mmap magic. If you know how to fix that without + * moving unknown size of data - let me know */ + + s->journal_message_header = mmap(s->processed_journal_data-header_size, + header_size, + PROT_WRITE | PROT_READ, + MAP_ANONYMOUS | MAP_PRIVATE, + -1, + 0); + + if (s->journal_message_header == MAP_FAILED) { + r = -errno; + log_error("Couldn't create padding for journal output: %m"); + return r; + } + + assert(s->processed_journal_data - s->journal_message_header == header_size); + memcpy(s->processed_journal_data-strlen(COREDUMP), COREDUMP, strlen(COREDUMP)); + } + + rf = coredump_mmap_file(s->preprocess_file ? file_tmp_filename : tmp_filename, + NULL, + &s->processed_file_data, + &s->processed_file_data_size); + + unlink(tmp_filename); + + if (s->preprocess_journal) + unlink(journal_tmp_filename); + + if (s->preprocess_file) + unlink(file_tmp_filename); + + return - (rj && rf); + } + + coredump_drop_creds(s->preprocess_uid, s->preprocess_gid); + + tmp_file = fdopen(tmp_fd, "rew+"); + if (! tmp_file) + return -errno; + + r = coredump_stdio_to_file(tmp_file, s->preprocess_max_size); + if (r) + return r; + + r = fstat(fileno(tmp_file), &stat); + if (r) + return -errno; + + fflush(tmp_file); + + if (s->preprocess_journal) { + preprocess_journal = coredump_printf(s, tmp_filename, journal_tmp_filename, s->preprocess_journal); + unlink(journal_tmp_filename); + if (system(preprocess_journal)) + unlink(journal_tmp_filename); + } + + if (s->preprocess_file) { + preprocess_file = coredump_printf(s, tmp_filename, file_tmp_filename, s->preprocess_file); + unlink(file_tmp_filename); + if (system(preprocess_file)) + unlink(file_tmp_filename); + } + + exit(0); +} + +static int coredump_store_memory_to_fd(const char * memory, + size_t memory_size, + int fd, + const char * fd_path, + size_t max_size, + char ** message, + size_t *message_size) { + static const char COREDUMP[] = "COREDUMP_FILE="; + _cleanup_fclose_ FILE *core = NULL; + char * m = NULL; + + int r; + + assert_se(fd >= 0); + assert_se(message); + assert_se(message_size); + + *message = NULL; + memory_size = MIN(memory_size, max_size); + + r = ftruncate(fd, memory_size); + if (r) { + log_error("Couldn't truncate backed file: %m"); + close(fd); + return -errno; + } + + m = mmap(NULL, memory_size, PROT_WRITE, MAP_SHARED, fd, 0); + if (m == MAP_FAILED) { + log_error("Couldn't mmap backed file: %m"); + close(fd); + return -errno; + } + + memcpy(m, memory, memory_size); + munmap(m, memory_size); + close(fd); + + assert(strrchr(fd_path, '/')); + + *message = strjoin(COREDUMP, strrchr(fd_path, '/') + 1, NULL); + + if (! *message) { + *message = NULL; + return log_oom(); + } + + *message_size = strlen(*message); + + return 0; +} + +static int coredump_submit_message(const Coredump * s) { + int r, j = 0; + _cleanup_free_ char *p = NULL; + struct iovec iovec[16]; + _cleanup_free_ char *core_pid = NULL, *core_uid = NULL, *core_gid = NULL, *core_signal = NULL, + *core_timestamp = NULL, *core_comm = NULL, *core_exe = NULL, *core_unit = NULL, + *core_session = NULL, *core_message = NULL, *core_cmdline = NULL; + char *journal_message = NULL, *file_message = NULL; + size_t journal_message_size = 0, file_message_size = 0; + + if (s->core_unit) { + if (s->core_user_unit) + core_unit = strappend("COREDUMP_USER_UNIT=", s->core_unit); + else + core_unit = strappend("COREDUMP_UNIT=", s->core_unit); + + if (core_unit) + IOVEC_SET_STRING(iovec[j++], core_unit); + } + + if (s->core_pid) { + core_pid = strappend("COREDUMP_PID=", s->core_pid); + if (core_pid) + IOVEC_SET_STRING(iovec[j++], core_pid); + } + + if (s->core_uid) { + core_uid = strappend("COREDUMP_UID=", s->core_uid); + if (core_uid) + IOVEC_SET_STRING(iovec[j++], core_uid); + } + + if (s->core_gid) { + core_gid = strappend("COREDUMP_GID=", s->core_gid); + if (core_gid) + IOVEC_SET_STRING(iovec[j++], core_gid); + } + + if (s->core_signal) { + core_signal = strappend("COREDUMP_SIGNAL=", s->core_signal); + if (core_signal) + IOVEC_SET_STRING(iovec[j++], core_signal); + } + + if (s->core_comm) { + core_comm = strappend("COREDUMP_COMM=", s->core_comm); + if (core_comm) + IOVEC_SET_STRING(iovec[j++], core_comm); + } + +#ifdef HAVE_LOGIND + if (s->core_session) { + core_session = strappend("COREDUMP_SESSION=", s->core_session); + if (core_session) + IOVEC_SET_STRING(iovec[j++], core_session); + } + +#endif + if (s->core_exe) { + core_exe = strappend("COREDUMP_EXE=", s->core_exe); if (core_exe) IOVEC_SET_STRING(iovec[j++], core_exe); } - if (get_process_cmdline(pid, 0, false, &t) >= 0) { - core_cmdline = strappend("COREDUMP_CMDLINE=", t); - free(t); - + if (s->core_cmdline) { + core_cmdline = strappend("COREDUMP_CMDLINE=", s->core_cmdline); if (core_cmdline) IOVEC_SET_STRING(iovec[j++], core_cmdline); } - core_timestamp = strjoin("COREDUMP_TIMESTAMP=", argv[ARG_TIMESTAMP], "000000", NULL); - if (core_timestamp) - IOVEC_SET_STRING(iovec[j++], core_timestamp); + if (s->core_timestamp) { + core_timestamp = strjoin("COREDUMP_TIMESTAMP=", s->core_timestamp, "000000", NULL); + if (core_timestamp) + IOVEC_SET_STRING(iovec[j++], core_timestamp); + } - IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"); IOVEC_SET_STRING(iovec[j++], "PRIORITY=2"); - core_message = strjoin("MESSAGE=Process ", argv[ARG_PID], " (", argv[ARG_COMM], ") dumped core.", NULL); + core_message = strjoin("MESSAGE=Process ", s->core_pid, " (", s->core_exe, ") dumped core.", NULL); if (core_message) IOVEC_SET_STRING(iovec[j++], core_message); - /* Now, let's drop privileges to become the user who owns the - * segfaulted process and allocate the coredump memory under - * his uid. This also ensures that the credentials journald - * will see are the ones of the coredumping user, thus making - * sure the user himself gets access to the core dump. */ + if (s->journal_max_size && s->processed_journal_data) { + iovec[j].iov_len = MIN(s->journal_max_size, + s->processed_journal_data_size + 9); + iovec[j].iov_base = s->processed_journal_data - 9; - if (setresgid(gid, gid, gid) < 0 || - setresuid(uid, uid, uid) < 0) { - log_error("Failed to drop privileges: %m"); - r = -errno; - goto finish; + j ++; } - coredump_bufsize = COREDUMP_MIN_START; - coredump_data = malloc(coredump_bufsize); - if (!coredump_data) { - r = log_oom(); - goto finish; - } - - memcpy(coredump_data, "COREDUMP=", 9); - coredump_size = 9; - - for (;;) { - n = loop_read(STDIN_FILENO, coredump_data + coredump_size, - coredump_bufsize - coredump_size, false); - if (n < 0) { - log_error("Failed to read core dump data: %s", strerror(-n)); - r = (int) n; - goto finish; - } else if (n == 0) - break; - - coredump_size += n; - if (!GREEDY_REALLOC(coredump_data, coredump_bufsize, coredump_size + 1)) { - r = log_oom(); - goto finish; + if (s->file_max_size && s->processed_file_data) { + r = coredump_store_memory_to_fd(s->processed_file_data, + s->processed_file_data_size, + s->file_fd, s->file_path, + s->file_max_size, + &file_message, &file_message_size); + + if (! r) { + iovec[j].iov_len = file_message_size; + iovec[j].iov_base = file_message; + + j ++; + } else { + if (r == -EACCES) + log_warning("User uid=%s gid=%s is not allowed to store coredump to file", + s->core_uid, s->core_gid); + else { + log_error("Failed to store coredump to file: %s", strerror(-r)); + goto finish; + } } } - iovec[j].iov_base = coredump_data; - iovec[j].iov_len = coredump_size; - j++; - r = sd_journal_sendv(iovec, j); if (r < 0) log_error("Failed to send coredump: %s", strerror(-r)); -finish: + finish: + if (journal_message) + coredump_cleanup_mem(journal_message, journal_message_size); + + if (file_message) + coredump_cleanup_str(file_message, file_message_size); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } + +int main(int argc, char ** argv) { + Coredump s; + int r = 0; + + prctl(PR_SET_DUMPABLE, 0); + + if (argc != _ARG_MAX) { + log_error("Invalid number of arguments passed from kernel."); + return -EINVAL; + } + + r = coredump_load_state(argv, &s); + if (r) { + log_error("State loading failed"); + return r; + } + + r = coredump_preprocess(&s); + if (r) { + log_error("Coredump preprocessing failed"); + return r; + } + + r = coredump_drop_creds(s.process_uid, s.process_gid); + if (r) { + coredump_release_state(&s); + return r; + } + + r = coredump_submit_message(&s); + + coredump_release_state(&s); + return r; + +} diff --git a/src/journal/coredump.conf b/src/journal/coredump.conf new file mode 100644 index 0000000..001a2b5 --- /dev/null +++ b/src/journal/coredump.conf @@ -0,0 +1,18 @@ +# 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. +# +# See coredump.conf(5) for details + +[Coredump] +#MaxJournalCoreSize=25M +#MaxFileCoreSize=0 +#FileStorage=volatile +#JournalStoragePreprocess=gdb --quiet --nx --batch -ex "thread apply all bt" --core %c %e +#PreprocessMaxSize=INT_MAX +#PreprocessUser=nobody +#PreprocessFile= +#PreprocessJournal= diff --git a/src/journal/coredump.h b/src/journal/coredump.h new file mode 100644 index 0000000..ec7c388 --- /dev/null +++ b/src/journal/coredump.h @@ -0,0 +1,89 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012-2013 Lennart Poettering + Oleksii Shevchuk + + 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. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "conf-parser.h" + +#define COREDUMP_MESSAGE_ID SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1 + +typedef enum Storage { + STORAGE_VOLATILE, + STORAGE_PERSISTENT, + _STORAGE_MAX, + _STORAGE_INVALID = -1 +} Storage; + +typedef struct Coredump { + size_t journal_max_size; + size_t file_max_size; + size_t preprocess_max_size; + + bool journal_core_dump; + + Storage storage; + + int file_fd; + char *file_path; + + gid_t journal; + + pid_t process_pid; + uid_t process_uid; + gid_t process_gid; + + bool core_user_unit; + char *core_pid; + char *core_uid; + char *core_gid; + char *core_signal; + char *core_timestamp; + char *core_comm; + char *core_exe; + char *core_unit; +#ifdef HAVE_LOGIND + char *core_session; +#endif + char *core_cmdline; + + uid_t preprocess_uid; + gid_t preprocess_gid; + + char *preprocess_user; + + char *preprocess_journal; + char *preprocess_file; + char *preprocess_directory_path; + + char *journal_message_header; + char *processed_journal_data; + size_t processed_journal_data_size; + char *processed_file_data; + size_t processed_file_data_size; +} Coredump; + +const struct ConfigPerfItem* coredump_gperf_lookup(const char *key, unsigned length); + +int config_parse_storage(const char *unit, const char *filename, unsigned line, const char *section, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); + +const char *storage_to_string(Storage s); +Storage storage_from_string(const char *s); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 129f7e8..31ff6b4 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -125,7 +125,7 @@ typedef struct Server { bool sync_scheduled; } Server; -#define N_IOVEC_META_FIELDS 17 +#define N_IOVEC_META_FIELDS 19 #define N_IOVEC_KERNEL_FIELDS 64 #define N_IOVEC_UDEV_FIELDS 32 -- 1.8.1.2 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel