coredump: support kernel coredump socket (#43330)
This adds kernel coredump socket support added in v6.17. See https://lore.kernel.org/all/20250603-work-coredump-socket-protocol-v2-0-05a5f0c18ecc@kernel.org/
This commit is contained in:
@@ -74,13 +74,18 @@ REQUIREMENTS:
|
||||
≥ 6.6 for quota support on tmpfs
|
||||
≥ 6.7 for cgroup2fs memory_hugetlb_accounting option
|
||||
≥ 6.8 for STATX_MNT_ID_UNIQUE
|
||||
≥ 6.9 for pidfs and MPOL_WEIGHTED_INTERLEAVE NUMA policy
|
||||
≥ 6.9 for pidfs, PIDFD_THREAD flag for pidfd_open(), and
|
||||
MPOL_WEIGHTED_INTERLEAVE NUMA policy
|
||||
≥ 6.10 for fcntl(F_DUPFD_QUERY), unprivileged linkat(AT_EMPTY_PATH),
|
||||
and block device 'partscan' sysfs attribute
|
||||
≥ 6.12 for AT_HANDLE_MNT_ID_UNIQUE
|
||||
≥ 6.13 for PIDFD_GET_INFO, {set,remove}xattrat(), and
|
||||
FSCONFIG_SET_FD support for overlayfs layers
|
||||
≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS
|
||||
≥ 6.16 for coredump pattern '%F' (pidfd) specifier and SO_PASSRIGHTS,
|
||||
PIDFD_INFO_COREDUMP by ioctl(PIDFD_GET_INFO)
|
||||
≥ 6.17 for coredump socket pattern ('@@' prefix)
|
||||
≥ 6.19 for PIDFD_INFO_SUPPORTED_MASK and PIDFD_INFO_COREDUMP_SIGNAL
|
||||
≥ 7.1 for PIDFD_INFO_COREDUMP_CODE
|
||||
|
||||
✅ systemd utilizes several new kernel APIs, but will fall back gracefully
|
||||
when unavailable.
|
||||
|
||||
+14
-2
@@ -47,6 +47,18 @@
|
||||
and
|
||||
<citerefentry project='man-pages'><refentrytitle>core</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
pages for the details.</para>
|
||||
|
||||
<para>On systems where core dumps are processed by the long-running
|
||||
<filename>systemd-coredumpd.service</filename> (rather than a fresh
|
||||
<filename>systemd-coredump@.service</filename> instance started for every crash, see
|
||||
<citerefentry><refentrytitle>systemd-coredump</refentrytitle><manvolnum>8</manvolnum></citerefentry> for
|
||||
details on when each applies), these configuration files are only read once, when the service starts.
|
||||
If <filename>systemd-coredumpd.service</filename> is running, changes to
|
||||
<filename>coredump.conf</filename> or its drop-ins will <emphasis>not</emphasis> take effect until the
|
||||
service is reloaded:
|
||||
<programlisting>systemctl reload systemd-coredumpd.service</programlisting>
|
||||
This causes the running service to reparse its configuration without restarting or interrupting any core
|
||||
dump currently being processed.</para>
|
||||
</refsect1>
|
||||
|
||||
<xi:include href="standard-conf.xml" xpointer="main-conf" />
|
||||
@@ -168,9 +180,9 @@
|
||||
<option>MaxUse=</option> makes
|
||||
sure that old core dumps are removed as soon as the total disk
|
||||
space taken up by core dumps grows beyond this limit (defaults
|
||||
to 10% of the total disk size, capped at 4GiB, minimum 1MiB).
|
||||
to 10% of the total disk size, capped at 4GiB, minimum 1MiB).
|
||||
<option>KeepFree=</option> controls how much disk space to keep
|
||||
free at least (defaults to 15% of the total disk size, limited
|
||||
free at least (defaults to 15% of the total disk size, limited
|
||||
to a maximum of 4GiB). Note that the disk space used
|
||||
by core dumps might temporarily exceed these limits while
|
||||
core dumps are processed. <option>MaxUse=</option> takes precedence.
|
||||
|
||||
@@ -1041,7 +1041,10 @@ manpages = [
|
||||
['systemd-clonesetup', '8', ['systemd-clonesetup@.service'], ''],
|
||||
['systemd-coredump',
|
||||
'8',
|
||||
['systemd-coredump.socket', 'systemd-coredump@.service'],
|
||||
['systemd-coredump-register.service',
|
||||
'systemd-coredump.socket',
|
||||
'systemd-coredump@.service',
|
||||
'systemd-coredumpd.service'],
|
||||
'ENABLE_COREDUMP'],
|
||||
['systemd-creds', '1', [], ''],
|
||||
['systemd-cryptenroll', '1', [], 'HAVE_LIBCRYPTSETUP'],
|
||||
|
||||
+209
-19
@@ -20,14 +20,20 @@
|
||||
<refname>systemd-coredump</refname>
|
||||
<refname>systemd-coredump.socket</refname>
|
||||
<refname>systemd-coredump@.service</refname>
|
||||
<refname>systemd-coredump-register.service</refname>
|
||||
<refname>systemd-coredumpd.service</refname>
|
||||
<refpurpose>Acquire, save and process core dumps</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>/usr/lib/systemd/systemd-coredump</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-coredump</filename> <option>--backtrace</option></para>
|
||||
<para><filename>systemd-coredump@.service</filename></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-coredump</filename> <option>--manager</option></para>
|
||||
<para><filename>/usr/lib/systemd/systemd-coredump</filename> <option>--check-requirements</option></para>
|
||||
<para><filename>systemd-coredump.socket</filename></para>
|
||||
<para><filename>systemd-coredump@.service</filename></para>
|
||||
<para><filename>systemd-coredump-register.service</filename></para>
|
||||
<para><filename>systemd-coredumpd.service</filename></para>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<refsect1>
|
||||
@@ -64,29 +70,193 @@
|
||||
Handling</ulink>.</para>
|
||||
|
||||
<refsect2>
|
||||
<title>Invocation of <command>systemd-coredump</command></title>
|
||||
<title>Overview</title>
|
||||
|
||||
<para>The <command>systemd-coredump</command> executable does the actual work. It is invoked twice:
|
||||
once as the handler by the kernel, and the second time in the
|
||||
<filename>systemd-coredump@.service</filename> to actually write the data to the journal and process
|
||||
and save the core file.</para>
|
||||
<para>The <command>systemd-coredump</command> executable does the actual work, but is invoked
|
||||
differently depending on the capabilities of the running kernel.</para>
|
||||
|
||||
<para>When the kernel invokes <command>systemd-coredump</command> to handle a core dump, it runs in
|
||||
privileged mode, and will connect to the socket created by the
|
||||
<para>When the kernel supports the kernel coredump socket protocol (see "Kernel Requirements" below,
|
||||
generally kernel v6.19 or newer), <filename>systemd-coredumpd.service</filename> creates and listens on
|
||||
a Unix socket, and <filename>systemd-coredump-register.service</filename> writes the socket path to
|
||||
<filename>/proc/sys/kernel/core_pattern</filename>. When a process crashes, the kernel connects to the
|
||||
socket, and sends the core dump of the process through the socket. Then,
|
||||
<filename>systemd-coredumpd.service</filename> transfers the core to the corresponding container if the
|
||||
crashed process is in a container environment, otherwise saves the core in the local storage and writes
|
||||
information about the crashed process to journal.</para>
|
||||
|
||||
<para>When the running kernel does not meet those requirements, the executable
|
||||
<command>systemd-coredump</command> is invoked twice: once as the handler by the kernel, and the second
|
||||
time in the <filename>systemd-coredump@.service</filename> to actually write the data to the journal and
|
||||
process and save the core file.</para>
|
||||
|
||||
<para>When the kernel invokes <command>systemd-coredump</command> to handle a core dump this way, it
|
||||
runs in privileged mode, and will connect to the socket created by the
|
||||
<filename>systemd-coredump.socket</filename> unit, which in turn will spawn an unprivileged
|
||||
<filename>systemd-coredump@.service</filename> instance to process the core dump. Hence
|
||||
<filename>systemd-coredump.socket</filename> and <filename>systemd-coredump@.service</filename> are
|
||||
helper units which do the actual processing of core dumps and are subject to normal service
|
||||
management.</para>
|
||||
|
||||
<para>It is also possible to invoke <command>systemd-coredump</command> with
|
||||
<option>--backtrace</option> option. In this case, <command>systemd-coredump</command> expects a
|
||||
journal entry in the journal
|
||||
<ulink url="https://systemd.io/JOURNAL_EXPORT_FORMATS#journal-export-format">Journal Export Format</ulink>
|
||||
on standard input. The entry should contain a <varname>MESSAGE=</varname> field and any additional
|
||||
metadata fields the caller deems reasonable. <command>systemd-coredump</command> will append additional
|
||||
metadata fields in the same way it does for core dumps received from the kernel. In this mode, no core
|
||||
dump is stored in the journal.</para>
|
||||
<para>See "Units" below for details on the individual units involved in either mode.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Command-line Options</title>
|
||||
|
||||
<para><command>systemd-coredump</command> is normally not invoked directly by the user, but by the
|
||||
kernel, or by the units described below. It supports the following modes of invocation:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>(no option)</term>
|
||||
|
||||
<listitem><para>Handles a single core dump. This is the mode the kernel uses to invoke
|
||||
<command>systemd-coredump</command> directly (via <varname>kernel.core_pattern</varname>) on
|
||||
kernels that do not support the kernel coredump socket protocol, as well as the mode used by
|
||||
<filename>systemd-coredump@.service</filename> to actually process and save a core dump that was
|
||||
handed to it via <filename>systemd-coredump.socket</filename>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--backtrace</option></term>
|
||||
|
||||
<listitem><para><command>systemd-coredump</command> expects a journal entry in the
|
||||
<ulink url="https://systemd.io/JOURNAL_EXPORT_FORMATS#journal-export-format">Journal Export
|
||||
Format</ulink> on standard input. The entry should contain a <varname>MESSAGE=</varname> field and
|
||||
any additional metadata fields the caller deems reasonable.
|
||||
<command>systemd-coredump</command> will append additional metadata fields in the same way it does
|
||||
for core dumps received from the kernel. In this mode, no core dump is stored in the
|
||||
journal.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--manager</option></term>
|
||||
|
||||
<listitem><para>Runs <command>systemd-coredump</command> as the long-running manager process that
|
||||
listens on the kernel coredump socket, accepts connections from the kernel, and forks off worker
|
||||
processes to process each individual core dump. This is the mode used internally by
|
||||
<filename>systemd-coredumpd.service</filename> and is not intended to be invoked interactively.
|
||||
See "Units" below for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--check-requirements</option></term>
|
||||
|
||||
<listitem><para>Checks whether the running kernel and execution environment fulfil the
|
||||
requirements for the kernel coredump socket protocol (see "Kernel Requirements" below), and exits
|
||||
with a non-zero status if they are not met. This is used internally as the
|
||||
<varname>ExecCondition=</varname> of <filename>systemd-coredumpd.service</filename> and
|
||||
<filename>systemd-coredump-register.service</filename>, so that those units are skipped, without
|
||||
being marked as failed, on systems where the feature is unavailable. It is not intended to be
|
||||
invoked interactively.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Kernel Requirements for the Coredump Socket</title>
|
||||
|
||||
<para>The kernel coredump socket protocol, used by <filename>systemd-coredumpd.service</filename> and
|
||||
<filename>systemd-coredump-register.service</filename>, requires:</para>
|
||||
|
||||
<itemizedlist>
|
||||
<listitem><para>A kernel that supports the <constant>PIDFD_INFO_COREDUMP</constant> (since kernel
|
||||
v6.16) and <constant>PIDFD_INFO_COREDUMP_SIGNAL</constant> (since kernel v6.19) flags for the
|
||||
<constant>PIDFD_GET_INFO</constant>
|
||||
<citerefentry project='man-pages'><refentrytitle>ioctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>
|
||||
operation on a PID file descriptor (see
|
||||
<citerefentry project='man-pages'><refentrytitle>pidfd_open</refentrytitle><manvolnum>2</manvolnum></citerefentry>).
|
||||
Support for these flags is used as an indicator that the kernel is also new enough to support the
|
||||
request-mode (<literal>@@</literal>-prefixed) kernel coredump socket
|
||||
<varname>kernel.core_pattern</varname>, which was introduced in kernel v6.17.</para></listitem>
|
||||
|
||||
<listitem><para><command>systemd-coredump</command> must be running in the initial PID, USER, and
|
||||
TIME namespaces, i.e. on the host, not inside a container.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>These conditions are checked with <command>systemd-coredump</command>
|
||||
<option>--check-requirements</option> before <filename>systemd-coredumpd.service</filename> and
|
||||
<filename>systemd-coredump-register.service</filename> are started. If the requirements are not met,
|
||||
<command>systemd-coredump</command> instead falls back to the legacy invocation via
|
||||
<filename>systemd-coredump.socket</filename> and <filename>systemd-coredump@.service</filename>.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>Units</title>
|
||||
|
||||
<para>The following units are involved in processing core dumps. Which of them are active depends on
|
||||
whether the kernel coredump socket protocol is available, see "Kernel Requirements" above.</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><filename>systemd-coredumpd.service</filename></term>
|
||||
|
||||
<listitem><para>A long-running manager service, started early at boot on kernels that support the
|
||||
kernel coredump socket protocol. It creates and listens on the <constant>AF_UNIX</constant> socket
|
||||
<filename>/run/systemd/coredumpd/kernel</filename> (this path is also passed to the kernel via
|
||||
<varname>kernel.core_pattern</varname> by <filename>systemd-coredump-register.service</filename>,
|
||||
see below). When the kernel connects to this socket to submit a core dump,
|
||||
<filename>systemd-coredumpd.service</filename> accepts the connection and forks off a worker
|
||||
process to receive and process that specific core dump, up to a limit of 8 concurrently running
|
||||
workers (once this limit is reached, no further connections are accepted until a worker finishes).
|
||||
Workers that run for longer than 5 minutes are killed automatically.</para>
|
||||
|
||||
<para>The configuration in
|
||||
<citerefentry><refentrytitle>coredump.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
is read once when <filename>systemd-coredumpd.service</filename> starts. Since the service keeps
|
||||
running afterwards (rather than being invoked anew for every core dump, as
|
||||
<filename>systemd-coredump@.service</filename> is), changes to
|
||||
<filename>coredump.conf</filename> and its drop-ins are <emphasis>not</emphasis> picked up
|
||||
automatically. Reload the service to apply configuration changes, see
|
||||
<citerefentry><refentrytitle>coredump.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
for details.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><filename>systemd-coredump-register.service</filename></term>
|
||||
|
||||
<listitem><para>A oneshot unit, automatically pulled in and bound to the lifetime of
|
||||
<filename>systemd-coredumpd.service</filename> (it is not meant to be started or stopped manually).
|
||||
Once <filename>systemd-coredumpd.service</filename> is up, this unit points the kernel at it by
|
||||
setting <varname>kernel.core_pattern</varname> to the request-mode (<literal>@@</literal>-prefixed)
|
||||
socket path <filename>/run/systemd/coredumpd/kernel</filename>, and persists this setting to
|
||||
<filename>/run/sysctl.d/60-coredump.conf</filename> via
|
||||
<citerefentry><refentrytitle>systemd-sysctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||
When <filename>systemd-coredumpd.service</filename> is stopped,
|
||||
<filename>systemd-coredump-register.service</filename> is stopped along with it and reverts
|
||||
<varname>kernel.core_pattern</varname> to its previous value.</para>
|
||||
|
||||
<para>Because <filename>/run/sysctl.d/60-coredump.conf</filename> takes precedence, per the usual
|
||||
<citerefentry><refentrytitle>sysctl.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
ordering rules, over the <filename>/usr/lib/sysctl.d/50-coredump.conf</filename> file described in
|
||||
"Configuration" below, masking or overriding <filename>50-coredump.conf</filename> alone is
|
||||
<emphasis>not</emphasis> sufficient to install a custom <varname>kernel.core_pattern</varname> on a
|
||||
system where <filename>systemd-coredumpd.service</filename> is active. See "Configuration" below
|
||||
for how to prevent this.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><filename>systemd-coredump.socket</filename></term>
|
||||
<term><filename>systemd-coredump@.service</filename></term>
|
||||
|
||||
<listitem><para>Legacy handler units, used on kernels that do not support the kernel coredump
|
||||
socket protocol (see "Kernel Requirements" above). The kernel invokes
|
||||
<command>systemd-coredump</command> directly and in privileged mode for each crash; that instance
|
||||
connects to <filename>systemd-coredump.socket</filename>, which in turn spawns an unprivileged
|
||||
<filename>systemd-coredump@.service</filename> instance to actually process and save the core
|
||||
dump. Unlike <filename>systemd-coredumpd.service</filename>, a fresh
|
||||
<filename>systemd-coredump@.service</filename> instance is started for every core dump, so it
|
||||
always reads the current <filename>coredump.conf</filename> configuration.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
@@ -150,6 +320,21 @@
|
||||
<citerefentry><refentrytitle>systemd-sysctl</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||
</para>
|
||||
|
||||
<para>On systems where <filename>systemd-coredumpd.service</filename> is active (see "Kernel
|
||||
Requirements" and "Units" above), <filename>systemd-coredump-register.service</filename> writes its own
|
||||
<varname>kernel.core_pattern</varname> setting to <filename>/run/sysctl.d/60-coredump.conf</filename>
|
||||
whenever <filename>systemd-coredumpd.service</filename> is running, and this file takes precedence over
|
||||
<filename>/usr/lib/sysctl.d/50-coredump.conf</filename>. As a result, masking or overriding
|
||||
<filename>50-coredump.conf</filename> alone does <emphasis>not</emphasis> override the default
|
||||
<varname>kernel.core_pattern</varname> on such systems, since <filename>60-coredump.conf</filename> will
|
||||
still take effect. To set a custom <varname>kernel.core_pattern</varname> of your own on a system where
|
||||
<filename>systemd-coredumpd.service</filename> is available, you must additionally stop (if currently
|
||||
running) and mask <filename>systemd-coredumpd.service</filename>:
|
||||
<programlisting>systemctl mask --now systemd-coredumpd.service</programlisting>
|
||||
Masking <filename>systemd-coredumpd.service</filename> also prevents
|
||||
<filename>systemd-coredump-register.service</filename>, which is bound to it, from running and writing
|
||||
<filename>60-coredump.conf</filename>.</para>
|
||||
|
||||
<para>In order to be used in the <option>--backtrace</option> mode, an appropriate backtrace
|
||||
handler must be installed on the sender side. For example, in case of
|
||||
<citerefentry project='die-net'><refentrytitle>python</refentrytitle><manvolnum>1</manvolnum></citerefentry>, this
|
||||
@@ -160,9 +345,14 @@
|
||||
<para>The behavior of <command>systemd-coredump</command> itself is configured through the configuration file
|
||||
<filename>/etc/systemd/coredump.conf</filename> and corresponding snippets
|
||||
<filename>/etc/systemd/coredump.conf.d/*.conf</filename>, see
|
||||
<citerefentry><refentrytitle>coredump.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. A new
|
||||
instance of <command>systemd-coredump</command> is invoked upon receiving every core dump. Therefore, changes
|
||||
in these files will take effect the next time a core dump is received.</para>
|
||||
<citerefentry><refentrytitle>coredump.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>. When
|
||||
core dumps are processed via <filename>systemd-coredump@.service</filename> (see "Units" above), a new
|
||||
instance of <command>systemd-coredump</command> is invoked upon receiving every core dump, and changes in
|
||||
these files will take effect the next time a core dump is received. When core dumps are instead processed
|
||||
by the long-running <filename>systemd-coredumpd.service</filename>, configuration changes require an
|
||||
explicit reload of that service to take effect; see
|
||||
<citerefentry><refentrytitle>coredump.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para>
|
||||
|
||||
<para>Resources used by core dump files are restricted in two ways. Parameters like maximum size of acquired
|
||||
core dumps and files can be set in files <filename>/etc/systemd/coredump.conf</filename> and snippets mentioned
|
||||
|
||||
@@ -350,17 +350,17 @@ int pidref_get_cmdline_strv(const PidRef *pid, ProcessCmdlineFlags flags, char *
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pid_is_kernel_thread(pid_t pid) {
|
||||
int procfs_get_stat_flags(const char *path, unsigned long long *ret) {
|
||||
int r;
|
||||
|
||||
if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
|
||||
return 0;
|
||||
if (!pid_is_valid(pid))
|
||||
return -EINVAL;
|
||||
assert(path);
|
||||
assert(ret);
|
||||
|
||||
/* Read the 'flags' field (9th field, task->flags) from the given /proc/[pid]/stat or
|
||||
* /proc/[pid]/task/[tid]/stat file. */
|
||||
|
||||
const char *p = procfs_file_alloca(pid, "stat");
|
||||
_cleanup_free_ char *line = NULL;
|
||||
r = read_one_line_file(p, &line);
|
||||
r = read_one_line_file(path, &line);
|
||||
if (r == -ENOENT)
|
||||
return -ESRCH;
|
||||
if (r < 0)
|
||||
@@ -397,8 +397,21 @@ int pid_is_kernel_thread(pid_t pid) {
|
||||
return -EINVAL;
|
||||
q[l] = 0;
|
||||
|
||||
return safe_atollu(q, ret);
|
||||
}
|
||||
|
||||
int pid_is_kernel_thread(pid_t pid) {
|
||||
int r;
|
||||
|
||||
if (IN_SET(pid, 0, 1) || pid == getpid_cached()) /* pid 1, and we ourselves certainly aren't a kernel thread */
|
||||
return 0;
|
||||
if (!pid_is_valid(pid))
|
||||
return -EINVAL;
|
||||
|
||||
const char *p = procfs_file_alloca(pid, "stat");
|
||||
|
||||
unsigned long long flags;
|
||||
r = safe_atollu(q, &flags);
|
||||
r = procfs_get_stat_flags(p, &flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ int pidref_wait_for_terminate_and_check(const char *name, PidRef *pidref, WaitFl
|
||||
|
||||
int kill_and_sigcont(pid_t pid, int sig);
|
||||
|
||||
int procfs_get_stat_flags(const char *path, unsigned long long *ret);
|
||||
|
||||
int pid_is_kernel_thread(pid_t pid);
|
||||
int pidref_is_kernel_thread(const PidRef *pid);
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -181,7 +186,7 @@ static int get_process_container_parent_cmdline(PidRef *pid, char** ret_cmdline)
|
||||
|
||||
/* The kernel passes si_signo through core_pattern (%s). Starting with v7.1,
|
||||
* si_code is reported as well via pidfd. */
|
||||
static int coredump_context_read_pidfd_info(CoredumpContext *context) {
|
||||
static int coredump_context_read_code(CoredumpContext *context) {
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_COREDUMP,
|
||||
};
|
||||
@@ -190,19 +195,23 @@ static int coredump_context_read_pidfd_info(CoredumpContext *context) {
|
||||
assert(context);
|
||||
assert(pidref_is_set(&context->pidref));
|
||||
|
||||
if (!context->got_pidfd || context->pidref.fd < 0)
|
||||
if (!context->got_pidfd || context->pidref.fd < 0 || context->got_code >= 0)
|
||||
return 0;
|
||||
|
||||
r = pidfd_get_info(context->pidref.fd, &info);
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return log_debug_errno(r, "PIDFD_INFO_COREDUMP not supported, ignoring: %m");
|
||||
if (r < 0)
|
||||
if (r < 0) {
|
||||
context->got_code = false;
|
||||
|
||||
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
|
||||
return log_debug_errno(r, "PIDFD_INFO_COREDUMP not supported, ignoring: %m");
|
||||
return log_debug_errno(r, "Failed to get pidfd coredump info, ignoring: %m");
|
||||
}
|
||||
|
||||
if (FLAGS_SET(info.mask, PIDFD_INFO_COREDUMP_CODE)) {
|
||||
context->code = (int) info.coredump_code;
|
||||
context->got_code = true;
|
||||
}
|
||||
} else
|
||||
context->got_code = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -246,7 +255,7 @@ int coredump_context_build_iovw(CoredumpContext *context) {
|
||||
(void) iovw_put_string_field(&context->iovw, "COREDUMP_SIGNAL_NAME=SIG", signal_to_string(context->signo));
|
||||
|
||||
/* Emit si_code if we learned it from pidfd_info */
|
||||
if (context->got_code)
|
||||
if (context->got_code > 0)
|
||||
(void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_CODE=", "%i", context->code);
|
||||
}
|
||||
|
||||
@@ -402,7 +411,7 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to get auxv, ignoring: %m");
|
||||
|
||||
(void) coredump_context_read_pidfd_info(context);
|
||||
(void) coredump_context_read_code(context);
|
||||
|
||||
r = pidref_verify(&context->pidref);
|
||||
if (r < 0)
|
||||
@@ -645,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);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ struct CoredumpContext {
|
||||
size_t auxv_size; /* META_PROC_AUXV */
|
||||
char *thread_name; /* META_THREAD_NAME */
|
||||
bool got_pidfd; /* META_ARGV_PIDFD */
|
||||
bool got_code; /* META_CODE */
|
||||
int got_code; /* META_CODE, tristate */
|
||||
bool same_pidns;
|
||||
bool forwarded;
|
||||
int input_fd;
|
||||
@@ -79,6 +79,7 @@ struct CoredumpContext {
|
||||
.uid = UID_INVALID, \
|
||||
.gid = GID_INVALID, \
|
||||
.tidref = PIDREF_NULL, \
|
||||
.got_code = -1, \
|
||||
.mount_tree_fd = -EBADF, \
|
||||
.input_fd = -EBADF, \
|
||||
}
|
||||
@@ -89,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);
|
||||
+12
-3
@@ -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,14 +26,22 @@ 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();
|
||||
log_set_target_and_open(LOG_TARGET_KMSG);
|
||||
|
||||
/* Make sure we never enter a loop. */
|
||||
(void) set_dumpable(SUID_DUMP_DISABLE);
|
||||
|
||||
r = sd_listen_fds(false);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine the number of file descriptors: %m");
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -19,6 +19,13 @@
|
||||
static_assert(CLONE_NEWTIME == 0x00000080, "");
|
||||
#endif
|
||||
|
||||
/* Not exposed yet. Defined at include/linux/sched.h */
|
||||
#ifndef PF_DUMPCORE
|
||||
# define PF_DUMPCORE 0x00000200
|
||||
#else
|
||||
static_assert(PF_DUMPCORE == 0x00000200, "");
|
||||
#endif
|
||||
|
||||
/* Not exposed yet. Defined at include/linux/sched.h */
|
||||
#ifndef PF_KTHREAD
|
||||
# define PF_KTHREAD 0x00200000
|
||||
|
||||
@@ -377,7 +377,9 @@ static size_t entry_size_max_by_ucred(Manager *m, const struct ucred *ucred, con
|
||||
log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT,
|
||||
"Failed to retrieve credentials for PID " PID_FMT ", ignoring: %m",
|
||||
ucred->pid);
|
||||
else if (context->unit && startswith(context->unit, "systemd-coredump@"))
|
||||
else if (context->unit &&
|
||||
(startswith(context->unit, "systemd-coredump@") ||
|
||||
streq(context->unit, "systemd-coredumpd.service")))
|
||||
return ENTRY_SIZE_MAX;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,12 @@ at_exit() {
|
||||
rm -fv -- "$CORE_TEST_BIN" "$CORE_TEST_UNPRIV_BIN" "$MAKE_DUMP_SCRIPT"
|
||||
}
|
||||
|
||||
maybe_reload_coredumpd() {
|
||||
if systemctl -q is-active systemd-coredumpd.service; then
|
||||
systemctl reload systemd-coredumpd.service
|
||||
fi
|
||||
}
|
||||
|
||||
(! systemd-detect-virt -cq)
|
||||
|
||||
trap at_exit EXIT
|
||||
@@ -25,8 +31,31 @@ trap at_exit EXIT
|
||||
journalctl --sync
|
||||
journalctl --rotate
|
||||
|
||||
if /usr/lib/systemd/systemd-coredump --check-requirements && systemctl is-enabled systemd-coredumpd.service; then
|
||||
systemctl is-active systemd-coredumpd.service
|
||||
fi
|
||||
|
||||
# Check that we're the ones to receive coredumps
|
||||
sysctl kernel.core_pattern | grep systemd-coredump
|
||||
if systemctl -q is-active systemd-coredumpd.service; then
|
||||
systemctl is-active systemd-coredump-register.service
|
||||
[[ -e /run/sysctl.d/60-coredump.conf ]]
|
||||
sysctl kernel.core_pattern | grep '@@/run/systemd/coredumpd/kernel'
|
||||
|
||||
systemctl stop systemd-coredumpd.service
|
||||
(! systemctl is-active systemd-coredump-register.service)
|
||||
[[ ! -e /run/sysctl.d/60-coredump.conf ]]
|
||||
sysctl kernel.core_pattern | grep 'systemd-coredump'
|
||||
|
||||
systemctl start systemd-coredumpd.service
|
||||
timeout 30 bash -xec 'until systemctl -q is-active systemd-coredump-register.service; do sleep 1; done'
|
||||
[[ -e /run/sysctl.d/60-coredump.conf ]]
|
||||
sysctl kernel.core_pattern | grep '@@/run/systemd/coredumpd/kernel'
|
||||
else
|
||||
assert_eq "$(systemctl is-active systemd-coredumpd.service ||:)" "inactive"
|
||||
assert_eq "$(systemctl is-active systemd-coredump-register.service ||:)" "inactive"
|
||||
[[ ! -e /run/sysctl.d/60-coredump.conf ]]
|
||||
sysctl kernel.core_pattern | grep 'systemd-coredump'
|
||||
fi
|
||||
|
||||
# Prepare "fake" binaries for coredumps, so we can properly exercise
|
||||
# the matching stuff too
|
||||
@@ -70,11 +99,13 @@ chmod +x "$MAKE_DUMP_SCRIPT"
|
||||
# with Storage=external as well
|
||||
mkdir -p /run/systemd/coredump.conf.d/
|
||||
printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
|
||||
maybe_reload_coredumpd
|
||||
"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGTRAP"
|
||||
"$MAKE_DUMP_SCRIPT" "$CORE_TEST_BIN" "SIGABRT"
|
||||
rm -fv /run/systemd/coredump.conf.d/99-external.conf
|
||||
# Wait a bit for the coredumps to get processed
|
||||
timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
|
||||
rm -fv /run/systemd/coredump.conf.d/99-external.conf
|
||||
maybe_reload_coredumpd
|
||||
|
||||
if cgroupfs_supports_user_xattrs; then
|
||||
# Make sure we can forward crashes back to containers
|
||||
@@ -192,11 +223,13 @@ UNPRIV_CMD=(systemd-run --user --wait --pipe -M "testuser@.host" -E SYSTEMD_PAGE
|
||||
# with Storage=external as well
|
||||
mkdir -p /run/systemd/coredump.conf.d/
|
||||
printf '[Coredump]\nStorage=external' >/run/systemd/coredump.conf.d/99-external.conf
|
||||
maybe_reload_coredumpd
|
||||
"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGTRAP"
|
||||
"${UNPRIV_CMD[@]}" "$MAKE_DUMP_SCRIPT" "$CORE_TEST_UNPRIV_BIN" "SIGABRT"
|
||||
rm -fv /run/systemd/coredump.conf.d/99-external.conf
|
||||
# Wait a bit for the coredumps to get processed
|
||||
timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $CORE_TEST_UNPRIV_BIN | wc -l) -lt 4 ]]; do sleep 1; done"
|
||||
rm -fv /run/systemd/coredump.conf.d/99-external.conf
|
||||
maybe_reload_coredumpd
|
||||
|
||||
# Sync and rotate journal again to make the coredump stored in an archived journal.
|
||||
journalctl --sync
|
||||
@@ -317,6 +350,7 @@ EOF
|
||||
|
||||
mkdir -p /run/systemd/coredump.conf.d/
|
||||
printf '[Coredump]\nEnterNamespace=no' >/run/systemd/coredump.conf.d/99-enter-namespace.conf
|
||||
maybe_reload_coredumpd
|
||||
|
||||
unshare --pid --fork --mount-proc --mount --uts --ipc --net "$MAKE_STACKTRACE_DUMP" "test-stacktrace-not-symbolized"
|
||||
timeout 30 bash -c "until coredumpctl list -q --no-legend /tmp/test-stacktrace-not-symbolized; do sleep .2; done"
|
||||
@@ -327,6 +361,7 @@ EOF
|
||||
(! grep -E "#[0-9]+ .* baz " /tmp/not-symbolized.log)
|
||||
|
||||
printf '[Coredump]\nEnterNamespace=yes' >/run/systemd/coredump.conf.d/99-enter-namespace.conf
|
||||
maybe_reload_coredumpd
|
||||
unshare --pid --fork --mount-proc --mount --uts --ipc --net "$MAKE_STACKTRACE_DUMP" "test-stacktrace-symbolized"
|
||||
timeout 30 bash -c "until coredumpctl list -q --no-legend /tmp/test-stacktrace-symbolized; do sleep .2; done"
|
||||
coredumpctl info /tmp/test-stacktrace-symbolized | tee /tmp/symbolized.log
|
||||
@@ -337,6 +372,7 @@ EOF
|
||||
|
||||
test -d /usr/lib/debug/ && umount /usr/lib/debug/
|
||||
rm -f "$MAKE_STACKTRACE_DUMP" /run/systemd/coredump.conf.d/99-enter-namespace.conf /tmp/{not-,}symbolized.log
|
||||
maybe_reload_coredumpd
|
||||
else
|
||||
echo "libdw doesn't not support setting sysroot, skipping EnterNamespace= test"
|
||||
fi
|
||||
|
||||
@@ -157,6 +157,9 @@ systemd-cgtop.xml ./refsect1[title="Keys"]/variablelist/varlistentry[term="h"]
|
||||
systemd-cgtop.xml ./refsect1[title="Keys"]/variablelist/varlistentry[term="q"]
|
||||
systemd-cgtop.xml ./refsect1[title="Keys"]/variablelist/varlistentry[term="p"]
|
||||
systemd-cgtop.xml ./refsect1[title="Keys"]/variablelist/varlistentry[term="+"]
|
||||
systemd-coredump.xml ./refsect1[title="Description"]/refsect2[title="Command-line Options"]/variablelist/varlistentry[term="(no option)"]
|
||||
systemd-coredump.xml ./refsect1[title="Description"]/refsect2[title="Command-line Options"]/variablelist/varlistentry[term="--backtrace"]
|
||||
systemd-coredump.xml ./refsect1[title="Description"]/refsect2[title="Units"]/variablelist/varlistentry[term="systemd-coredump.socket"]
|
||||
systemd-delta.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="-t"]
|
||||
systemd-delta.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="--diff="]
|
||||
systemd-delta.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="-t"]/listitem/para/variablelist/varlistentry[term="masked"]
|
||||
|
||||
@@ -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