core: reorder systemd arguments on reexe
Debrand for AlmaLinux
This commit is contained in:
commit
8f75db73d0
@ -0,0 +1,167 @@
|
||||
From a8c056f2847f080e8ceb5d43fe0d36b5c5ee8655 Mon Sep 17 00:00:00 2001
|
||||
From: Ondrej Kozina <okozina@redhat.com>
|
||||
Date: Wed, 31 Jan 2024 13:11:21 +0100
|
||||
Subject: [PATCH] cryptsetup: Add optional support for linking volume key in
|
||||
keyring.
|
||||
|
||||
cryptsetup 2.7.0 adds feature to link effective volume key in custom
|
||||
kernel keyring during device activation. It can be used later to pass
|
||||
linked volume key to other services.
|
||||
|
||||
For example: kdump enabled systems installed on LUKS2 device.
|
||||
This feature allows it to store volume key linked in a kernel keyring
|
||||
to the kdump reserved memory and reuse it to reactivate LUKS2 device
|
||||
in case of kernel crash.
|
||||
|
||||
(cherry picked from commit c5daf14c88ba44cefabe052de93a29d28b6b0175)
|
||||
|
||||
Resolves: RHEL-118294
|
||||
---
|
||||
man/crypttab.xml | 21 ++++++++++++
|
||||
meson.build | 3 +-
|
||||
src/cryptsetup/cryptsetup.c | 65 +++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 88 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/man/crypttab.xml b/man/crypttab.xml
|
||||
index 1dd9bb1bb6..bd49e025fa 100644
|
||||
--- a/man/crypttab.xml
|
||||
+++ b/man/crypttab.xml
|
||||
@@ -239,6 +239,27 @@
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
+ <varlistentry>
|
||||
+ <term><option>link-volume-key=</option></term>
|
||||
+
|
||||
+ <listitem><para>Specifies the kernel keyring and key description
|
||||
+ (see <citerefentry project='man-pages'><refentrytitle>keyrings</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
|
||||
+ where LUKS2 volume key gets linked during device activation. The kernel keyring
|
||||
+ description and key description must be separated by <literal>::</literal>.</para>
|
||||
+
|
||||
+ <para>The kernel keyring part can be a string description or a predefined
|
||||
+ kernel keyring prefixed with <literal>@</literal> (e.g.: to use <literal>@s</literal> session or
|
||||
+ <literal>@u</literal> user keyring directly). The type prefix text in the kernel keyring description
|
||||
+ is not required. The specified kernel keyring must already exist at the time of device activation.</para>
|
||||
+
|
||||
+ <para>The key part is a string description optionally prefixed by a <literal>%key_type:</literal>.
|
||||
+ If no type is specified, the <literal>user</literal> type key is linked by default. See
|
||||
+ <citerefentry project='man-pages'><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
+ for more information on key descriptions (KEY IDENTIFIERS section).</para>
|
||||
+
|
||||
+ <para>Note that the linked volume key is not cleaned up automatically when the device is detached.</para></listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
<varlistentry>
|
||||
<term><option>luks</option></term>
|
||||
|
||||
diff --git a/meson.build b/meson.build
|
||||
index cbde702211..684324c6d7 100644
|
||||
--- a/meson.build
|
||||
+++ b/meson.build
|
||||
@@ -1316,7 +1316,8 @@ if want_libcryptsetup != 'false' and not skip_deps
|
||||
|
||||
foreach ident : ['crypt_set_metadata_size',
|
||||
'crypt_activate_by_signed_key',
|
||||
- 'crypt_token_max']
|
||||
+ 'crypt_token_max',
|
||||
+ 'crypt_set_keyring_to_link']
|
||||
have_ident = have and cc.has_function(
|
||||
ident,
|
||||
prefix : '#include <libcryptsetup.h>',
|
||||
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
|
||||
index 3f2cab1e41..f9130e2568 100644
|
||||
--- a/src/cryptsetup/cryptsetup.c
|
||||
+++ b/src/cryptsetup/cryptsetup.c
|
||||
@@ -101,6 +101,9 @@ static bool arg_headless = false;
|
||||
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
|
||||
static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
|
||||
static char **arg_tpm2_measure_banks = NULL;
|
||||
+static char *arg_link_keyring = NULL;
|
||||
+static char *arg_link_key_type = NULL;
|
||||
+static char *arg_link_key_description = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
|
||||
@@ -113,6 +116,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
|
||||
+STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep);
|
||||
+STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep);
|
||||
+STATIC_DESTRUCTOR_REGISTER(arg_link_key_description, freep);
|
||||
|
||||
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
|
||||
[PASSPHRASE_REGULAR] = "passphrase",
|
||||
@@ -486,6 +492,56 @@ static int parse_one_option(const char *option) {
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to parse %s, ignoring: %m", option);
|
||||
|
||||
+ } else if ((val = startswith(option, "link-volume-key="))) {
|
||||
+#ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK
|
||||
+ const char *sep, *c;
|
||||
+ _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL;
|
||||
+
|
||||
+ /* Stick with cryptsetup --link-vk-to-keyring format
|
||||
+ * <keyring_description>::%<key_type>:<key_description>,
|
||||
+ * where %<key_type> is optional and defaults to 'user'.
|
||||
+ */
|
||||
+ if (!(sep = strstr(val, "::")))
|
||||
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %m");
|
||||
+
|
||||
+ /* cryptsetup (cli) supports <keyring_description> passed in various formats:
|
||||
+ * - well-known keyrings prefixed with '@' (@u user, @s session, etc)
|
||||
+ * - text descriptions prefixed with "%:" or "%keyring:".
|
||||
+ * - text desription with no prefix.
|
||||
+ * - numeric keyring id (ignored in current patch set). */
|
||||
+ if (*val == '@' || *val == '%')
|
||||
+ keyring = strndup(val, sep - val);
|
||||
+ else
|
||||
+ /* add type prefix if missing (crypt_set_keyring_to_link() expects it) */
|
||||
+ keyring = strnappend("%:", val, sep - val);
|
||||
+ if (!keyring)
|
||||
+ return log_oom();
|
||||
+
|
||||
+ sep += 2;
|
||||
+
|
||||
+ /* %<key_type> is optional (and defaults to 'user') */
|
||||
+ if (*sep == '%') {
|
||||
+ /* must be separated by colon */
|
||||
+ if (!(c = strchr(sep, ':')))
|
||||
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %m");
|
||||
+
|
||||
+ key_type = strndup(sep + 1, c - sep - 1);
|
||||
+ if (!key_type)
|
||||
+ return log_oom();
|
||||
+
|
||||
+ sep = c + 1;
|
||||
+ }
|
||||
+
|
||||
+ key_description = strdup(sep);
|
||||
+ if (!key_description)
|
||||
+ return log_oom();
|
||||
+
|
||||
+ free_and_replace(arg_link_keyring, keyring);
|
||||
+ free_and_replace(arg_link_key_type, key_type);
|
||||
+ free_and_replace(arg_link_key_description, key_description);
|
||||
+#else
|
||||
+ log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option);
|
||||
+#endif
|
||||
} else if (!streq(option, "x-initrd.attach"))
|
||||
log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option);
|
||||
|
||||
@@ -2207,6 +2263,15 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd));
|
||||
|
||||
+/* since cryptsetup 2.7.0 (Jan 2024) */
|
||||
+#if HAVE_CRYPT_SET_KEYRING_TO_LINK
|
||||
+ if (arg_link_key_description) {
|
||||
+ r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring);
|
||||
+ if (r < 0)
|
||||
+ log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m");
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
if (arg_header) {
|
||||
r = crypt_set_data_device(cd, source);
|
||||
if (r < 0)
|
||||
27
SOURCES/1252-cryptsetup-fix-typo.patch
Normal file
27
SOURCES/1252-cryptsetup-fix-typo.patch
Normal file
@ -0,0 +1,27 @@
|
||||
From 9807259b544cbf4f7a0a05bd426b95c788a89bd4 Mon Sep 17 00:00:00 2001
|
||||
From: Yu Watanabe <watanabe.yu+github@gmail.com>
|
||||
Date: Wed, 14 Feb 2024 04:01:36 +0900
|
||||
Subject: [PATCH] cryptsetup: fix typo
|
||||
|
||||
Follow-up for c5daf14c88ba44cefabe052de93a29d28b6b0175.
|
||||
|
||||
(cherry picked from commit a14d3b48f7647676a0c43bceaecd56d9a77e3de6)
|
||||
|
||||
Resolves: RHEL-118294
|
||||
---
|
||||
src/cryptsetup/cryptsetup.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
|
||||
index f9130e2568..1f672f19f1 100644
|
||||
--- a/src/cryptsetup/cryptsetup.c
|
||||
+++ b/src/cryptsetup/cryptsetup.c
|
||||
@@ -507,7 +507,7 @@ static int parse_one_option(const char *option) {
|
||||
/* cryptsetup (cli) supports <keyring_description> passed in various formats:
|
||||
* - well-known keyrings prefixed with '@' (@u user, @s session, etc)
|
||||
* - text descriptions prefixed with "%:" or "%keyring:".
|
||||
- * - text desription with no prefix.
|
||||
+ * - text description with no prefix.
|
||||
* - numeric keyring id (ignored in current patch set). */
|
||||
if (*val == '@' || *val == '%')
|
||||
keyring = strndup(val, sep - val);
|
||||
@ -0,0 +1,27 @@
|
||||
From 4920d1ca6823abe29e8cf2eceea497d9b677ee95 Mon Sep 17 00:00:00 2001
|
||||
From: Yu Watanabe <watanabe.yu+github@gmail.com>
|
||||
Date: Sun, 17 Aug 2025 21:05:24 +0900
|
||||
Subject: [PATCH] cryptsetup: HAVE_CRYPT_SET_KEYRING_TO_LINK is always defined
|
||||
|
||||
Follow-up for c5daf14c88ba44cefabe052de93a29d28b6b0175 (v256).
|
||||
|
||||
(cherry picked from commit fb4aabf4432d523b97376099ce4353b5c268ae82)
|
||||
|
||||
Resolves: RHEL-118294
|
||||
---
|
||||
src/cryptsetup/cryptsetup.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
|
||||
index 1f672f19f1..4dc315e810 100644
|
||||
--- a/src/cryptsetup/cryptsetup.c
|
||||
+++ b/src/cryptsetup/cryptsetup.c
|
||||
@@ -493,7 +493,7 @@ static int parse_one_option(const char *option) {
|
||||
log_warning_errno(r, "Failed to parse %s, ignoring: %m", option);
|
||||
|
||||
} else if ((val = startswith(option, "link-volume-key="))) {
|
||||
-#ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK
|
||||
+#if HAVE_CRYPT_SET_KEYRING_TO_LINK
|
||||
const char *sep, *c;
|
||||
_cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL;
|
||||
|
||||
51
SOURCES/1254-basic-add-PIDFS-magic-31709.patch
Normal file
51
SOURCES/1254-basic-add-PIDFS-magic-31709.patch
Normal file
@ -0,0 +1,51 @@
|
||||
From 4d9bb355df2ba60d734a73a62bf489abbb8ec54c Mon Sep 17 00:00:00 2001
|
||||
From: cpackham-atlnz <85916201+cpackham-atlnz@users.noreply.github.com>
|
||||
Date: Tue, 12 Mar 2024 00:55:36 +1300
|
||||
Subject: [PATCH] basic: add PIDFS magic (#31709)
|
||||
|
||||
Kernel commit cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b added pidfs.
|
||||
Update filesystems-gperf.gperf and missing_magic.h accordingly.
|
||||
|
||||
This fixes the following error building against a bleeding edge kernel.
|
||||
```
|
||||
../src/basic/meson.build:234:8: ERROR: Problem encountered: Unknown filesystems defined in kernel headers:
|
||||
|
||||
Filesystem found in kernel header but not in filesystems-gperf.gperf: PID_FS_MAGIC
|
||||
```
|
||||
|
||||
(cherry picked from commit ed01b92e1c92871bbd92711f280e2b2d15753f0e)
|
||||
|
||||
Resolves: RHEL-118294
|
||||
---
|
||||
src/basic/filesystems-gperf.gperf | 1 +
|
||||
src/basic/missing_magic.h | 5 +++++
|
||||
2 files changed, 6 insertions(+)
|
||||
|
||||
diff --git a/src/basic/filesystems-gperf.gperf b/src/basic/filesystems-gperf.gperf
|
||||
index e8c5357f91..1cd66b5a5f 100644
|
||||
--- a/src/basic/filesystems-gperf.gperf
|
||||
+++ b/src/basic/filesystems-gperf.gperf
|
||||
@@ -91,6 +91,7 @@ ocfs2, {OCFS2_SUPER_MAGIC}
|
||||
openpromfs, {OPENPROM_SUPER_MAGIC}
|
||||
orangefs, {ORANGEFS_DEVREQ_MAGIC}
|
||||
overlay, {OVERLAYFS_SUPER_MAGIC}
|
||||
+pidfs, {PID_FS_MAGIC}
|
||||
pipefs, {PIPEFS_MAGIC}
|
||||
ppc-cmm, {PPC_CMM_MAGIC}
|
||||
proc, {PROC_SUPER_MAGIC}
|
||||
diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h
|
||||
index c104fcfba3..82ede1873e 100644
|
||||
--- a/src/basic/missing_magic.h
|
||||
+++ b/src/basic/missing_magic.h
|
||||
@@ -128,6 +128,11 @@
|
||||
#define DEVMEM_MAGIC 0x454d444d
|
||||
#endif
|
||||
|
||||
+/* cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b (6.8) */
|
||||
+#ifndef PID_FS_MAGIC
|
||||
+#define PID_FS_MAGIC 0x50494446
|
||||
+#endif
|
||||
+
|
||||
/* Not in mainline but included in Ubuntu */
|
||||
#ifndef SHIFTFS_MAGIC
|
||||
#define SHIFTFS_MAGIC 0x6a656a62
|
||||
@ -0,0 +1,74 @@
|
||||
From a8ef307f05961e142b99cbbdda8855a24a44d4a1 Mon Sep 17 00:00:00 2001
|
||||
From: Yu Watanabe <watanabe.yu+github@gmail.com>
|
||||
Date: Sun, 12 Mar 2023 20:57:16 +0900
|
||||
Subject: [PATCH] time-util: make USEC_TIMESTAMP_FORMATTABLE_MAX for 32bit
|
||||
system off by one day
|
||||
|
||||
As the same reason why we take one day off for 64bit case.
|
||||
|
||||
This also makes both upper bounds always defined for testing.
|
||||
|
||||
(cherry picked from commit bd5770da76ee157d3b31323ed2d22f5d9082bb36)
|
||||
|
||||
Related: RHEL-118294
|
||||
---
|
||||
src/basic/time-util.h | 14 +++++++++-----
|
||||
src/test/test-date.c | 4 ++--
|
||||
src/test/test-time-util.c | 2 +-
|
||||
3 files changed, 12 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/basic/time-util.h b/src/basic/time-util.h
|
||||
index 9d44cac747..7f8bda0948 100644
|
||||
--- a/src/basic/time-util.h
|
||||
+++ b/src/basic/time-util.h
|
||||
@@ -200,13 +200,17 @@ static inline usec_t usec_sub_signed(usec_t timestamp, int64_t delta) {
|
||||
return usec_sub_unsigned(timestamp, (usec_t) delta);
|
||||
}
|
||||
|
||||
+/* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
|
||||
+ * year territory. However, since we want to stay away from this in all timezones we take one day off. */
|
||||
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT ((usec_t) 253402214399000000) /* Thu 9999-12-30 23:59:59 UTC */
|
||||
+/* With a 32bit time_t we can't go beyond 2038...
|
||||
+ * We parse timestamp with RFC-822/ISO 8601 (e.g. +06, or -03:00) as UTC, hence the upper bound must be off
|
||||
+ * by USEC_PER_DAY. See parse_timestamp() for more details. */
|
||||
+#define USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT (((usec_t) INT32_MAX) * USEC_PER_SEC - USEC_PER_DAY)
|
||||
#if SIZEOF_TIME_T == 8
|
||||
- /* The last second we can format is 31. Dec 9999, 1s before midnight, because otherwise we'd enter 5 digit
|
||||
- * year territory. However, since we want to stay away from this in all timezones we take one day off. */
|
||||
-# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 253402214399000000)
|
||||
+# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_64BIT
|
||||
#elif SIZEOF_TIME_T == 4
|
||||
-/* With a 32bit time_t we can't go beyond 2038... */
|
||||
-# define USEC_TIMESTAMP_FORMATTABLE_MAX ((usec_t) 2147483647000000)
|
||||
+# define USEC_TIMESTAMP_FORMATTABLE_MAX USEC_TIMESTAMP_FORMATTABLE_MAX_32BIT
|
||||
#else
|
||||
# error "Yuck, time_t is neither 4 nor 8 bytes wide?"
|
||||
#endif
|
||||
diff --git a/src/test/test-date.c b/src/test/test-date.c
|
||||
index cc11bd999e..ef316eeca7 100644
|
||||
--- a/src/test/test-date.c
|
||||
+++ b/src/test/test-date.c
|
||||
@@ -104,8 +104,8 @@ int main(int argc, char *argv[]) {
|
||||
test_should_fail("9999-12-31 00:00:00 UTC");
|
||||
test_should_fail("10000-01-01 00:00:00 UTC");
|
||||
#elif SIZEOF_TIME_T == 4
|
||||
- test_should_pass("2038-01-19 03:14:07 UTC");
|
||||
- test_should_fail("2038-01-19 03:14:08 UTC");
|
||||
+ test_should_pass("2038-01-18 03:14:07 UTC");
|
||||
+ test_should_fail("2038-01-18 03:14:08 UTC");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c
|
||||
index 21b05a3010..71ef5906ba 100644
|
||||
--- a/src/test/test-time-util.c
|
||||
+++ b/src/test/test-time-util.c
|
||||
@@ -550,7 +550,7 @@ TEST(format_timestamp_utc) {
|
||||
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Thu 9999-12-30 23:59:59 UTC");
|
||||
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
|
||||
#elif SIZEOF_TIME_T == 4
|
||||
- test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Tue 2038-01-19 03:14:07 UTC");
|
||||
+ test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX, "Mon 2038-01-18 03:14:07 UTC");
|
||||
test_format_timestamp_utc_one(USEC_TIMESTAMP_FORMATTABLE_MAX + 1, "--- XXXX-XX-XX XX:XX:XX");
|
||||
#endif
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
From a257453e7febc1c984b180e14095c2e6092dc850 Mon Sep 17 00:00:00 2001
|
||||
From: Lennart Poettering <lennart@poettering.net>
|
||||
Date: Thu, 31 Oct 2024 17:02:59 +0100
|
||||
Subject: [PATCH] coredump: make check that all argv[] meta data fields are
|
||||
passed strict
|
||||
|
||||
Otherwise, if some field is not supplied we might end up parsing a NULL
|
||||
string later. Let's catch that early.
|
||||
|
||||
(cherry picked from commit 098c3975acb3df61eedfe471fca27c21f13cf04c)
|
||||
|
||||
Related: RHEL-104138
|
||||
---
|
||||
src/coredump/coredump.c | 7 ++++---
|
||||
1 file changed, 4 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index dca78fa72c..b24f4c8cc3 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -1067,9 +1067,10 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
|
||||
}
|
||||
}
|
||||
|
||||
- if (!context->meta[META_ARGV_PID])
|
||||
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
- "Failed to find the PID of crashing process");
|
||||
+ /* The basic fields from argv[] should always be there, refuse early if not */
|
||||
+ for (int i = 0; i < _META_ARGV_MAX; i++)
|
||||
+ if (!context->meta[i])
|
||||
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A required (%s) has not been sent, aborting.", meta_field_names[i]);
|
||||
|
||||
r = parse_pid(context->meta[META_ARGV_PID], &context->pid);
|
||||
if (r < 0)
|
||||
@ -0,0 +1,122 @@
|
||||
From de05f30a12a0f1ce03dc21a29a606c70e42d1de4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Tue, 29 Apr 2025 14:47:59 +0200
|
||||
Subject: [PATCH] coredump: restore compatibility with older patterns
|
||||
|
||||
This was broken in f45b8015513d38ee5f7cc361db9c5b88c9aae704. Unfortunately
|
||||
the review does not talk about backward compatibility at all. There are
|
||||
two places where it matters:
|
||||
- During upgrades, the replacement of kernel.core_pattern is asynchronous.
|
||||
For example, during rpm upgrades, it would be updated a post-transaction
|
||||
file trigger. In other scenarios, the update might only happen after
|
||||
reboot. We have a potentially long window where the old pattern is in
|
||||
place. We need to capture coredumps during upgrades too.
|
||||
- With --backtrace. The interface of --backtrace, in hindsight, is not
|
||||
great. But there are users of --backtrace which were written to use
|
||||
a specific set of arguments, and we can't just break compatiblity.
|
||||
One example is systemd-coredump-python, but there are also reports of
|
||||
users using --backtrace to generate coredump logs.
|
||||
|
||||
Thus, we require the original set of args, and will use the additional args if
|
||||
found.
|
||||
|
||||
A test is added to verify that --backtrace works with and without the optional
|
||||
args.
|
||||
|
||||
(cherry picked from commit ded0aac389e647d35bce7ec4a48e718d77c0435b)
|
||||
|
||||
Related: RHEL-104138
|
||||
---
|
||||
src/coredump/coredump.c | 23 +++++++++++++++--------
|
||||
test/units/testsuite-74.coredump.sh | 18 +++++++++++-------
|
||||
2 files changed, 26 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index b24f4c8cc3..458857ffb2 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -94,8 +94,12 @@ enum {
|
||||
META_ARGV_SIGNAL, /* %s: number of signal causing dump */
|
||||
META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to µs granularity) */
|
||||
META_ARGV_RLIMIT, /* %c: core file size soft resource limit */
|
||||
- META_ARGV_HOSTNAME, /* %h: hostname */
|
||||
+ _META_ARGV_REQUIRED,
|
||||
+ /* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
|
||||
+ META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
||||
_META_ARGV_MAX,
|
||||
+ /* If new fields are added, they should be added here, to maintain compatibility
|
||||
+ * with callers which don't know about the new fields. */
|
||||
|
||||
/* The following indexes are cached for a couple of special fields we use (and
|
||||
* thereby need to be retrieved quickly) for naming coredump files, and attaching
|
||||
@@ -106,7 +110,7 @@ enum {
|
||||
_META_MANDATORY_MAX,
|
||||
|
||||
/* The rest are similar to the previous ones except that we won't fail if one of
|
||||
- * them is missing. */
|
||||
+ * them is missing in a message sent over the socket. */
|
||||
|
||||
META_EXE = _META_MANDATORY_MAX,
|
||||
META_UNIT,
|
||||
@@ -1068,7 +1072,7 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
|
||||
}
|
||||
|
||||
/* The basic fields from argv[] should always be there, refuse early if not */
|
||||
- for (int i = 0; i < _META_ARGV_MAX; i++)
|
||||
+ for (int i = 0; i < _META_ARGV_REQUIRED; i++)
|
||||
if (!context->meta[i])
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A required (%s) has not been sent, aborting.", meta_field_names[i]);
|
||||
|
||||
@@ -1286,14 +1290,17 @@ static int gather_pid_metadata_from_argv(
|
||||
char *t;
|
||||
|
||||
/* We gather all metadata that were passed via argv[] into an array of iovecs that
|
||||
- * we'll forward to the socket unit */
|
||||
+ * we'll forward to the socket unit.
|
||||
+ *
|
||||
+ * We require at least _META_ARGV_REQUIRED args, but will accept more.
|
||||
+ * We know how to parse _META_ARGV_MAX args. The rest will be ignored. */
|
||||
|
||||
- if (argc < _META_ARGV_MAX)
|
||||
+ if (argc < _META_ARGV_REQUIRED)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
- "Not enough arguments passed by the kernel (%i, expected %i).",
|
||||
- argc, _META_ARGV_MAX);
|
||||
+ "Not enough arguments passed by the kernel (%i, expected between %i and %i).",
|
||||
+ argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
|
||||
|
||||
- for (int i = 0; i < _META_ARGV_MAX; i++) {
|
||||
+ for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
|
||||
|
||||
t = argv[i];
|
||||
|
||||
diff --git a/test/units/testsuite-74.coredump.sh b/test/units/testsuite-74.coredump.sh
|
||||
index 1093cad8a9..0163131096 100755
|
||||
--- a/test/units/testsuite-74.coredump.sh
|
||||
+++ b/test/units/testsuite-74.coredump.sh
|
||||
@@ -218,14 +218,18 @@ rm -f /tmp/core.{output,redirected}
|
||||
(! "${UNPRIV_CMD[@]}" coredumpctl dump "$CORE_TEST_BIN" >/dev/null)
|
||||
|
||||
# --backtrace mode
|
||||
-# Pass one of the existing journal coredump records to systemd-coredump and
|
||||
-# use our PID as the source to make matching the coredump later easier
|
||||
-# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT HOSTNAME
|
||||
+# Pass one of the existing journal coredump records to systemd-coredump.
|
||||
+# Use our PID as the source to be able to create a PIDFD and to make matching easier.
|
||||
+# systemd-coredump args: PID UID GID SIGNUM TIMESTAMP CORE_SOFT_RLIMIT [HOSTNAME]
|
||||
journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
|
||||
- /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509994 12345 mymachine
|
||||
-# Wait a bit for the coredump to get processed
|
||||
-timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -eq 0 ]]; do sleep 1; done"
|
||||
-coredumpctl info "$$"
|
||||
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509900 12345
|
||||
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
|
||||
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509901 12345 mymachine
|
||||
+# Wait a bit for the coredumps to get processed
|
||||
+timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -lt 2 ]]; do sleep 1; done"
|
||||
+coredumpctl info $$
|
||||
+coredumpctl info COREDUMP_TIMESTAMP=1679509900000000
|
||||
+coredumpctl info COREDUMP_TIMESTAMP=1679509901000000
|
||||
coredumpctl info COREDUMP_HOSTNAME="mymachine"
|
||||
|
||||
# This used to cause a stack overflow
|
||||
158
SOURCES/1258-coredump-use-d-in-kernel-core-pattern.patch
Normal file
158
SOURCES/1258-coredump-use-d-in-kernel-core-pattern.patch
Normal file
@ -0,0 +1,158 @@
|
||||
From 58550ccfee8cdf87e623aa70318a45a5e7c19901 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Tue, 29 Apr 2025 14:47:59 +0200
|
||||
Subject: [PATCH] coredump: use %d in kernel core pattern
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The kernel provides %d which is documented as
|
||||
"dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE".
|
||||
|
||||
We already query /proc/pid/auxv for this information, but unfortunately this
|
||||
check is subject to a race, because the crashed process may be replaced by an
|
||||
attacker before we read this data, for example replacing a SUID process that
|
||||
was killed by a signal with another process that is not SUID, tricking us into
|
||||
making the coredump of the original process readable by the attacker.
|
||||
|
||||
With this patch, we effectively add one more check to the list of conditions
|
||||
that need be satisfied if we are to make the coredump accessible to the user.
|
||||
|
||||
Reportedy-by: Qualys Security Advisory <qsa@qualys.com>
|
||||
|
||||
In principle, %d might return a value other than 0, 1, or 2 in the future.
|
||||
Thus, we accept those, but emit a notice.
|
||||
|
||||
(cherry picked from commit 0c49e0049b7665bb7769a13ef346fef92e1ad4d6)
|
||||
|
||||
Related: RHEL-104138
|
||||
---
|
||||
man/systemd-coredump.xml | 10 ++++++++++
|
||||
src/coredump/coredump.c | 22 +++++++++++++++++++---
|
||||
sysctl.d/50-coredump.conf.in | 2 +-
|
||||
test/units/testsuite-74.coredump.sh | 5 +++++
|
||||
4 files changed, 35 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml
|
||||
index cb9f47745b..6cfa04f466 100644
|
||||
--- a/man/systemd-coredump.xml
|
||||
+++ b/man/systemd-coredump.xml
|
||||
@@ -259,6 +259,16 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
+ <varlistentry>
|
||||
+ <term><varname>COREDUMP_DUMPABLE=</varname></term>
|
||||
+
|
||||
+ <listitem><para>The <constant>PR_GET_DUMPABLE</constant> field as reported by the kernel, see
|
||||
+ <citerefentry
|
||||
+ project='man-pages'><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
|
||||
+ </para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
<varlistentry>
|
||||
<term><varname>COREDUMP_OPEN_FDS=</varname></term>
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index 458857ffb2..cd10678c43 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -97,7 +97,9 @@ enum {
|
||||
_META_ARGV_REQUIRED,
|
||||
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
|
||||
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
||||
+ META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
||||
_META_ARGV_MAX,
|
||||
+
|
||||
/* If new fields are added, they should be added here, to maintain compatibility
|
||||
* with callers which don't know about the new fields. */
|
||||
|
||||
@@ -126,6 +128,7 @@ static const char * const meta_field_names[_META_MAX] = {
|
||||
[META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=",
|
||||
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
|
||||
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
||||
+ [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
||||
[META_COMM] = "COREDUMP_COMM=",
|
||||
[META_EXE] = "COREDUMP_EXE=",
|
||||
[META_UNIT] = "COREDUMP_UNIT=",
|
||||
@@ -138,6 +141,7 @@ typedef struct Context {
|
||||
pid_t pid;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
+ unsigned dumpable;
|
||||
bool is_pid1;
|
||||
bool is_journald;
|
||||
} Context;
|
||||
@@ -453,14 +457,16 @@ static int grant_user_access(int core_fd, const Context *context) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
- /* We allow access if we got all the data and at_secure is not set and
|
||||
- * the uid/gid matches euid/egid. */
|
||||
+ /* We allow access if %d/dumpable on the command line was exactly 1, we got all the data,
|
||||
+ * at_secure is not set, and the uid/gid match euid/egid. */
|
||||
bool ret =
|
||||
+ context->dumpable == 1 &&
|
||||
at_secure == 0 &&
|
||||
uid != UID_INVALID && euid != UID_INVALID && uid == euid &&
|
||||
gid != GID_INVALID && egid != GID_INVALID && gid == egid;
|
||||
- log_debug("Will %s access (uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
|
||||
+ log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
|
||||
ret ? "permit" : "restrict",
|
||||
+ context->dumpable,
|
||||
uid, euid, gid, egid, yes_no(at_secure));
|
||||
return ret;
|
||||
}
|
||||
@@ -1089,6 +1095,16 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
|
||||
return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]);
|
||||
|
||||
|
||||
+ /* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to 2,
|
||||
+ * if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */
|
||||
+ if (context->meta[META_ARGV_DUMPABLE]) {
|
||||
+ r = safe_atou(context->meta[META_ARGV_DUMPABLE], &context->dumpable);
|
||||
+ if (r < 0)
|
||||
+ return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]);
|
||||
+ if (context->dumpable > 2)
|
||||
+ log_notice("Got unexpected %%d/dumpable value %u.", context->dumpable);
|
||||
+ }
|
||||
+
|
||||
unit = context->meta[META_UNIT];
|
||||
context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE);
|
||||
context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE);
|
||||
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
|
||||
index 5fb551a8cf..9c10a89828 100644
|
||||
--- a/sysctl.d/50-coredump.conf.in
|
||||
+++ b/sysctl.d/50-coredump.conf.in
|
||||
@@ -13,7 +13,7 @@
|
||||
# the core dump.
|
||||
#
|
||||
# See systemd-coredump(8) and core(5).
|
||||
-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h
|
||||
+kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d
|
||||
|
||||
# Allow 16 coredumps to be dispatched in parallel by the kernel.
|
||||
# We collect metadata from /proc/%P/, and thus need to make sure the crashed
|
||||
diff --git a/test/units/testsuite-74.coredump.sh b/test/units/testsuite-74.coredump.sh
|
||||
index 0163131096..b72313672c 100755
|
||||
--- a/test/units/testsuite-74.coredump.sh
|
||||
+++ b/test/units/testsuite-74.coredump.sh
|
||||
@@ -225,12 +225,17 @@ journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE
|
||||
/usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509900 12345
|
||||
journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
|
||||
/usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509901 12345 mymachine
|
||||
+journalctl -b -n 1 --output=export --output-fields=MESSAGE,COREDUMP COREDUMP_EXE="/usr/bin/test-dump" |
|
||||
+ /usr/lib/systemd/systemd-coredump --backtrace $$ 0 0 6 1679509902 12345 youmachine 1
|
||||
# Wait a bit for the coredumps to get processed
|
||||
timeout 30 bash -c "while [[ \$(coredumpctl list -q --no-legend $$ | wc -l) -lt 2 ]]; do sleep 1; done"
|
||||
coredumpctl info $$
|
||||
coredumpctl info COREDUMP_TIMESTAMP=1679509900000000
|
||||
coredumpctl info COREDUMP_TIMESTAMP=1679509901000000
|
||||
coredumpctl info COREDUMP_HOSTNAME="mymachine"
|
||||
+coredumpctl info COREDUMP_TIMESTAMP=1679509902000000
|
||||
+coredumpctl info COREDUMP_HOSTNAME="youmachine"
|
||||
+coredumpctl info COREDUMP_DUMPABLE="1"
|
||||
|
||||
# This used to cause a stack overflow
|
||||
systemd-run -t --property CoredumpFilter=all ls /tmp
|
||||
@ -0,0 +1,230 @@
|
||||
From c762a9a8764f8c6ce6640579ae8cf04f80360d16 Mon Sep 17 00:00:00 2001
|
||||
From: Lennart Poettering <lennart@poettering.net>
|
||||
Date: Sat, 9 Sep 2023 09:29:27 +0200
|
||||
Subject: [PATCH] pidref: add structure that can reference a pid via both pidfd
|
||||
and pid_t
|
||||
|
||||
Let's start with the conversion of PID 1 to pidfds. Let's add a simple
|
||||
structure with just two fields that can be used to maintain a reference
|
||||
to arbitrary processes via both pid_t and pidfd.
|
||||
|
||||
This is an embeddable struct, to keep it in line with where we
|
||||
previously used a pid_t directly to track a process.
|
||||
|
||||
Of course, since this might contain an fd on systems where we have pidfd
|
||||
this structure has a proper lifecycle.
|
||||
|
||||
(Note that this is quite different from sd_event_add_child() event
|
||||
source objects as that one is only for child processes and collects
|
||||
process results, while this infra is much simpler and more generic and
|
||||
can be used to reference any process, anywhere in the tree.)
|
||||
|
||||
(cherry picked from commit 3bda3f17fa84557eeb28fa7c330cbd3a3f876d47)
|
||||
|
||||
Related: RHEL-104138
|
||||
---
|
||||
src/basic/meson.build | 1 +
|
||||
src/basic/pidref.c | 145 ++++++++++++++++++++++++++++++++++++++++++
|
||||
src/basic/pidref.h | 29 +++++++++
|
||||
3 files changed, 175 insertions(+)
|
||||
create mode 100644 src/basic/pidref.c
|
||||
create mode 100644 src/basic/pidref.h
|
||||
|
||||
diff --git a/src/basic/meson.build b/src/basic/meson.build
|
||||
index 11053a5ecd..b8b4213c70 100644
|
||||
--- a/src/basic/meson.build
|
||||
+++ b/src/basic/meson.build
|
||||
@@ -182,6 +182,7 @@ basic_sources = files(
|
||||
'path-util.h',
|
||||
'percent-util.c',
|
||||
'percent-util.h',
|
||||
+ 'pidref.c',
|
||||
'prioq.c',
|
||||
'prioq.h',
|
||||
'proc-cmdline.c',
|
||||
diff --git a/src/basic/pidref.c b/src/basic/pidref.c
|
||||
new file mode 100644
|
||||
index 0000000000..f41460938c
|
||||
--- /dev/null
|
||||
+++ b/src/basic/pidref.c
|
||||
@@ -0,0 +1,145 @@
|
||||
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
+
|
||||
+#include "errno-util.h"
|
||||
+#include "fd-util.h"
|
||||
+#include "missing_syscall.h"
|
||||
+#include "parse-util.h"
|
||||
+#include "pidref.h"
|
||||
+#include "process-util.h"
|
||||
+
|
||||
+int pidref_set_pid(PidRef *pidref, pid_t pid) {
|
||||
+ int fd;
|
||||
+
|
||||
+ assert(pidref);
|
||||
+
|
||||
+ if (pid < 0)
|
||||
+ return -ESRCH;
|
||||
+ if (pid == 0)
|
||||
+ pid = getpid_cached();
|
||||
+
|
||||
+ fd = pidfd_open(pid, 0);
|
||||
+ if (fd < 0) {
|
||||
+ /* Graceful fallback in case the kernel doesn't support pidfds or is out of fds */
|
||||
+ if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && !ERRNO_IS_RESOURCE(errno))
|
||||
+ return -errno;
|
||||
+
|
||||
+ fd = -EBADF;
|
||||
+ }
|
||||
+
|
||||
+ *pidref = (PidRef) {
|
||||
+ .fd = fd,
|
||||
+ .pid = pid,
|
||||
+ };
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+int pidref_set_pidstr(PidRef *pidref, const char *pid) {
|
||||
+ pid_t nr;
|
||||
+ int r;
|
||||
+
|
||||
+ assert(pidref);
|
||||
+
|
||||
+ r = parse_pid(pid, &nr);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ return pidref_set_pid(pidref, nr);
|
||||
+}
|
||||
+
|
||||
+int pidref_set_pidfd(PidRef *pidref, int fd) {
|
||||
+ int r;
|
||||
+
|
||||
+ assert(pidref);
|
||||
+
|
||||
+ if (fd < 0)
|
||||
+ return -EBADF;
|
||||
+
|
||||
+ int fd_copy = fcntl(fd, F_DUPFD_CLOEXEC, 3);
|
||||
+ if (fd_copy < 0) {
|
||||
+ pid_t pid;
|
||||
+
|
||||
+ if (!ERRNO_IS_RESOURCE(errno))
|
||||
+ return -errno;
|
||||
+
|
||||
+ /* Graceful fallback if we are out of fds */
|
||||
+ r = pidfd_get_pid(fd, &pid);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ *pidref = (PidRef) {
|
||||
+ .fd = -EBADF,
|
||||
+ .pid = pid,
|
||||
+ };
|
||||
+
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ return pidref_set_pidfd_consume(pidref, fd_copy);
|
||||
+}
|
||||
+
|
||||
+int pidref_set_pidfd_take(PidRef *pidref, int fd) {
|
||||
+ pid_t pid;
|
||||
+ int r;
|
||||
+
|
||||
+ assert(pidref);
|
||||
+
|
||||
+ if (fd < 0)
|
||||
+ return -EBADF;
|
||||
+
|
||||
+ r = pidfd_get_pid(fd, &pid);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ *pidref = (PidRef) {
|
||||
+ .fd = fd,
|
||||
+ .pid = pid,
|
||||
+ };
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+int pidref_set_pidfd_consume(PidRef *pidref, int fd) {
|
||||
+ int r;
|
||||
+
|
||||
+ r = pidref_set_pidfd_take(pidref, fd);
|
||||
+ if (r < 0)
|
||||
+ safe_close(fd);
|
||||
+
|
||||
+ return r;
|
||||
+}
|
||||
+
|
||||
+void pidref_done(PidRef *pidref) {
|
||||
+ assert(pidref);
|
||||
+
|
||||
+ *pidref = (PidRef) {
|
||||
+ .fd = safe_close(pidref->fd),
|
||||
+ };
|
||||
+}
|
||||
+
|
||||
+int pidref_kill(PidRef *pidref, int sig) {
|
||||
+
|
||||
+ if (!pidref)
|
||||
+ return -ESRCH;
|
||||
+
|
||||
+ if (pidref->fd >= 0)
|
||||
+ return RET_NERRNO(pidfd_send_signal(pidref->fd, sig, NULL, 0));
|
||||
+
|
||||
+ if (pidref->pid > 0)
|
||||
+ return RET_NERRNO(kill(pidref->pid, sig));
|
||||
+
|
||||
+ return -ESRCH;
|
||||
+}
|
||||
+
|
||||
+int pidref_kill_and_sigcont(PidRef *pidref, int sig) {
|
||||
+ int r;
|
||||
+
|
||||
+ r = pidref_kill(pidref, sig);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ if (!IN_SET(sig, SIGCONT, SIGKILL))
|
||||
+ (void) pidref_kill(pidref, SIGCONT);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
diff --git a/src/basic/pidref.h b/src/basic/pidref.h
|
||||
new file mode 100644
|
||||
index 0000000000..2411e510f1
|
||||
--- /dev/null
|
||||
+++ b/src/basic/pidref.h
|
||||
@@ -0,0 +1,29 @@
|
||||
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
+#pragma once
|
||||
+
|
||||
+#include "macro.h"
|
||||
+
|
||||
+/* An embeddable structure carrying a reference to a process. Supposed to be used when tracking processes continously. */
|
||||
+typedef struct PidRef {
|
||||
+ pid_t pid; /* always valid */
|
||||
+ int fd; /* only valid if pidfd are available in the kernel, and we manage to get an fd */
|
||||
+} PidRef;
|
||||
+
|
||||
+#define PIDREF_NULL (PidRef) { .fd = -EBADF }
|
||||
+
|
||||
+static inline bool pidref_is_set(const PidRef *pidref) {
|
||||
+ return pidref && pidref->pid > 0;
|
||||
+}
|
||||
+
|
||||
+int pidref_set_pid(PidRef *pidref, pid_t pid);
|
||||
+int pidref_set_pidstr(PidRef *pidref, const char *pid);
|
||||
+int pidref_set_pidfd(PidRef *pidref, int fd);
|
||||
+int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/
|
||||
+int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */
|
||||
+
|
||||
+void pidref_done(PidRef *pidref);
|
||||
+
|
||||
+int pidref_kill(PidRef *pidref, int sig);
|
||||
+int pidref_kill_and_sigcont(PidRef *pidref, int sig);
|
||||
+
|
||||
+#define TAKE_PIDREF(p) TAKE_GENERIC((p), PidRef, PIDREF_NULL)
|
||||
54
SOURCES/1260-fd-util-introduce-parse_fd.patch
Normal file
54
SOURCES/1260-fd-util-introduce-parse_fd.patch
Normal file
@ -0,0 +1,54 @@
|
||||
From 9cf24f219925df6e449eb0838af6e309d84cbb0c Mon Sep 17 00:00:00 2001
|
||||
From: David Tardon <dtardon@redhat.com>
|
||||
Date: Fri, 5 May 2023 08:09:14 +0200
|
||||
Subject: [PATCH] fd-util: introduce parse_fd()
|
||||
|
||||
It's a simple wrapper for safe_atoi() that returns error if the parsed
|
||||
fd is < 0 .
|
||||
|
||||
(cherry picked from commit b8f83d7f0c35dca6ca3a23c42215d566e2815ca5)
|
||||
|
||||
Related: RHEL-104138
|
||||
---
|
||||
src/basic/parse-util.c | 15 +++++++++++++++
|
||||
src/basic/parse-util.h | 1 +
|
||||
2 files changed, 16 insertions(+)
|
||||
|
||||
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
|
||||
index 3b3efb0ab8..4161211c49 100644
|
||||
--- a/src/basic/parse-util.c
|
||||
+++ b/src/basic/parse-util.c
|
||||
@@ -313,6 +313,21 @@ int parse_errno(const char *t) {
|
||||
return e;
|
||||
}
|
||||
|
||||
+int parse_fd(const char *t) {
|
||||
+ int r, fd;
|
||||
+
|
||||
+ assert(t);
|
||||
+
|
||||
+ r = safe_atoi(t, &fd);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ if (fd < 0)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ return fd;
|
||||
+}
|
||||
+
|
||||
static const char *mangle_base(const char *s, unsigned *base) {
|
||||
const char *k;
|
||||
|
||||
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
|
||||
index 8d8d52327b..5c012d702a 100644
|
||||
--- a/src/basic/parse-util.h
|
||||
+++ b/src/basic/parse-util.h
|
||||
@@ -20,6 +20,7 @@ int parse_mtu(int family, const char *s, uint32_t *ret);
|
||||
int parse_size(const char *t, uint64_t base, uint64_t *size);
|
||||
int parse_range(const char *t, unsigned *lower, unsigned *upper);
|
||||
int parse_errno(const char *t);
|
||||
+int parse_fd(const char *t);
|
||||
|
||||
#define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30)
|
||||
#define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29)
|
||||
@ -0,0 +1,245 @@
|
||||
From bbd5aec472b90aa55e184f58691085142b7e3aaa Mon Sep 17 00:00:00 2001
|
||||
From: Luca Boccassi <luca.boccassi@gmail.com>
|
||||
Date: Sun, 13 Apr 2025 22:10:36 +0100
|
||||
Subject: [PATCH] coredump: add support for new %F PIDFD specifier
|
||||
|
||||
A new core_pattern specifier was added, %F, to provide a PIDFD
|
||||
to the usermode helper process referring to the crashed process.
|
||||
This removes all possible race conditions, ensuring only the
|
||||
crashed process gets inspected by systemd-coredump.
|
||||
|
||||
(cherry picked from commit 868d95577ec9f862580ad365726515459be582fc)
|
||||
|
||||
Resolves: RHEL-104138
|
||||
---
|
||||
man/systemd-coredump.xml | 9 ++++
|
||||
src/coredump/coredump.c | 89 ++++++++++++++++++++++++++++++++----
|
||||
sysctl.d/50-coredump.conf.in | 2 +-
|
||||
3 files changed, 89 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/man/systemd-coredump.xml b/man/systemd-coredump.xml
|
||||
index 6cfa04f466..b3d81d838a 100644
|
||||
--- a/man/systemd-coredump.xml
|
||||
+++ b/man/systemd-coredump.xml
|
||||
@@ -186,6 +186,15 @@ COREDUMP_FILENAME=/var/lib/systemd/coredump/core.Web….552351.….zst
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
+ <varlistentry>
|
||||
+ <term><varname>COREDUMP_BY_PIDFD=</varname></term>
|
||||
+ <listitem><para>If the crashed process was analyzed using a PIDFD provided by the kernel (requires
|
||||
+ kernel v6.16) then this field will be present and set to <literal>1</literal>. If this field is
|
||||
+ not set, then the crashed process was analyzed via a PID, which is known to be subject to race
|
||||
+ conditions.</para>
|
||||
+ </listitem>
|
||||
+ </varlistentry>
|
||||
+
|
||||
<varlistentry>
|
||||
<term><varname>COREDUMP_TIMESTAMP=</varname></term>
|
||||
<listitem><para>The time of the crash as reported by the kernel (in µs since the epoch).</para>
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index cd10678c43..e0aac3c8d0 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "mkdir-label.h"
|
||||
#include "namespace-util.h"
|
||||
#include "parse-util.h"
|
||||
+#include "pidref.h"
|
||||
#include "process-util.h"
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
@@ -98,8 +99,8 @@ enum {
|
||||
/* The fields below were added to kernel/core_pattern at later points, so they might be missing. */
|
||||
META_ARGV_HOSTNAME = _META_ARGV_REQUIRED, /* %h: hostname */
|
||||
META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
||||
+ META_ARGV_PIDFD, /* %F: pidfd of the process, since v6.16 */
|
||||
_META_ARGV_MAX,
|
||||
-
|
||||
/* If new fields are added, they should be added here, to maintain compatibility
|
||||
* with callers which don't know about the new fields. */
|
||||
|
||||
@@ -129,6 +130,7 @@ static const char * const meta_field_names[_META_MAX] = {
|
||||
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
|
||||
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
||||
[META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
||||
+ [META_ARGV_PIDFD] = "COREDUMP_BY_PIDFD=",
|
||||
[META_COMM] = "COREDUMP_COMM=",
|
||||
[META_EXE] = "COREDUMP_EXE=",
|
||||
[META_UNIT] = "COREDUMP_UNIT=",
|
||||
@@ -136,6 +138,7 @@ static const char * const meta_field_names[_META_MAX] = {
|
||||
};
|
||||
|
||||
typedef struct Context {
|
||||
+ PidRef pidref;
|
||||
const char *meta[_META_MAX];
|
||||
size_t meta_size[_META_MAX];
|
||||
pid_t pid;
|
||||
@@ -146,6 +149,14 @@ typedef struct Context {
|
||||
bool is_journald;
|
||||
} Context;
|
||||
|
||||
+#define CONTEXT_NULL \
|
||||
+ (Context) { \
|
||||
+ .pidref = PIDREF_NULL, \
|
||||
+ .uid = UID_INVALID, \
|
||||
+ .gid = GID_INVALID, \
|
||||
+ }
|
||||
+
|
||||
+
|
||||
typedef enum CoredumpStorage {
|
||||
COREDUMP_STORAGE_NONE,
|
||||
COREDUMP_STORAGE_EXTERNAL,
|
||||
@@ -171,6 +182,12 @@ static uint64_t arg_journal_size_max = JOURNAL_SIZE_MAX;
|
||||
static uint64_t arg_keep_free = UINT64_MAX;
|
||||
static uint64_t arg_max_use = UINT64_MAX;
|
||||
|
||||
+static void context_done(Context *c) {
|
||||
+ assert(c);
|
||||
+
|
||||
+ pidref_done(&c->pidref);
|
||||
+}
|
||||
+
|
||||
static int parse_config(void) {
|
||||
static const ConfigTableItem items[] = {
|
||||
{ "Coredump", "Storage", config_parse_coredump_storage, 0, &arg_storage },
|
||||
@@ -1114,7 +1131,7 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
|
||||
|
||||
static int process_socket(int fd) {
|
||||
_cleanup_close_ int input_fd = -EBADF, mntns_fd = -EBADF;
|
||||
- Context context = {};
|
||||
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
struct iovec_wrapper iovw = {};
|
||||
struct iovec iovec;
|
||||
int iterations = 0, r;
|
||||
@@ -1215,7 +1232,7 @@ static int process_socket(int fd) {
|
||||
goto finish;
|
||||
|
||||
/* Make sure we received at least all fields we need. */
|
||||
- for (int i = 0; i < _META_MANDATORY_MAX; i++)
|
||||
+ for (int i = 0; i < _META_ARGV_REQUIRED; i++)
|
||||
if (!context.meta[i]) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"A mandatory argument (%i) has not been sent, aborting.",
|
||||
@@ -1301,9 +1318,9 @@ static int gather_pid_metadata_from_argv(
|
||||
Context *context,
|
||||
int argc, char **argv) {
|
||||
|
||||
+ _cleanup_(pidref_done) PidRef local_pidref = PIDREF_NULL;
|
||||
_cleanup_free_ char *free_timestamp = NULL;
|
||||
- int r, signo;
|
||||
- char *t;
|
||||
+ int r, signo, kernel_fd = -EBADF;
|
||||
|
||||
/* We gather all metadata that were passed via argv[] into an array of iovecs that
|
||||
* we'll forward to the socket unit.
|
||||
@@ -1317,8 +1334,7 @@ static int gather_pid_metadata_from_argv(
|
||||
argc, _META_ARGV_REQUIRED, _META_ARGV_MAX);
|
||||
|
||||
for (int i = 0; i < MIN(argc, _META_ARGV_MAX); i++) {
|
||||
-
|
||||
- t = argv[i];
|
||||
+ const char *t = argv[i];
|
||||
|
||||
switch (i) {
|
||||
|
||||
@@ -1343,6 +1359,47 @@ static int gather_pid_metadata_from_argv(
|
||||
break;
|
||||
}
|
||||
|
||||
+ if (i == META_ARGV_PID) {
|
||||
+ /* Store this so that we can check whether the core will be forwarded to a container
|
||||
+ * even when the kernel doesn't provide a pidfd. Can be dropped once baseline is
|
||||
+ * >= v6.16. */
|
||||
+ r = pidref_set_pidstr(&local_pidref, t);
|
||||
+ if (r < 0)
|
||||
+ return log_error_errno(r, "Failed to initialize pidref from pid %s: %m", t);
|
||||
+ }
|
||||
+
|
||||
+ if (i == META_ARGV_PIDFD) {
|
||||
+ /* If the current kernel doesn't support the %F specifier (which resolves to a
|
||||
+ * pidfd), but we included it in the core_pattern expression, we'll receive an empty
|
||||
+ * string here. Deal with that gracefully. */
|
||||
+ if (isempty(t))
|
||||
+ continue;
|
||||
+
|
||||
+ assert(!pidref_is_set(&context->pidref));
|
||||
+ assert(kernel_fd < 0);
|
||||
+
|
||||
+ kernel_fd = parse_fd(t);
|
||||
+ if (kernel_fd < 0)
|
||||
+ return log_error_errno(kernel_fd, "Failed to parse pidfd \"%s\": %m", t);
|
||||
+
|
||||
+ r = pidref_set_pidfd(&context->pidref, kernel_fd);
|
||||
+ if (r < 0)
|
||||
+ return log_error_errno(r, "Failed to initialize pidref from pidfd %d: %m", kernel_fd);
|
||||
+
|
||||
+ /* If there are containers involved with different versions of the code they might
|
||||
+ * not be using pidfds, so it would be wrong to set the metadata, skip it. */
|
||||
+ r = in_same_namespace(getpid_cached(), context->pidref.pid, NAMESPACE_PID);
|
||||
+ if (r < 0)
|
||||
+ log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m");
|
||||
+ if (r <= 0)
|
||||
+ continue;
|
||||
+
|
||||
+ /* We don't print the fd number in the journal as it's meaningless, but we still
|
||||
+ * record that the parsing was done with a kernel-provided fd as it means it's safe
|
||||
+ * from races, which is valuable information to provide in the journal record. */
|
||||
+ t = "1";
|
||||
+ }
|
||||
+
|
||||
r = iovw_put_string_field(iovw, meta_field_names[i], t);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1350,7 +1407,19 @@ static int gather_pid_metadata_from_argv(
|
||||
|
||||
/* Cache some of the process metadata we collected so far and that we'll need to
|
||||
* access soon */
|
||||
- return save_context(context, iovw);
|
||||
+ r = save_context(context, iovw);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ /* If the kernel didn't give us a PIDFD, then use the one derived from the
|
||||
+ * PID immediately, given we have it. */
|
||||
+ if (!pidref_is_set(&context->pidref))
|
||||
+ context->pidref = TAKE_PIDREF(local_pidref);
|
||||
+
|
||||
+ /* Close the kernel-provided FD as the last thing after everything else succeeded. */
|
||||
+ kernel_fd = safe_close(kernel_fd);
|
||||
+
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) {
|
||||
@@ -1466,7 +1535,7 @@ static int gather_pid_metadata(struct iovec_wrapper *iovw, Context *context) {
|
||||
}
|
||||
|
||||
static int process_kernel(int argc, char* argv[]) {
|
||||
- Context context = {};
|
||||
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
struct iovec_wrapper *iovw;
|
||||
int r, mntns_fd = -EBADF;
|
||||
|
||||
@@ -1543,7 +1612,7 @@ static int process_kernel(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
static int process_backtrace(int argc, char *argv[]) {
|
||||
- Context context = {};
|
||||
+ _cleanup_(context_done) Context context = CONTEXT_NULL;
|
||||
struct iovec_wrapper *iovw;
|
||||
char *message;
|
||||
int r;
|
||||
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
|
||||
index 9c10a89828..1c6230ad93 100644
|
||||
--- a/sysctl.d/50-coredump.conf.in
|
||||
+++ b/sysctl.d/50-coredump.conf.in
|
||||
@@ -13,7 +13,7 @@
|
||||
# the core dump.
|
||||
#
|
||||
# See systemd-coredump(8) and core(5).
|
||||
-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d
|
||||
+kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d %F
|
||||
|
||||
# Allow 16 coredumps to be dispatched in parallel by the kernel.
|
||||
# We collect metadata from /proc/%P/, and thus need to make sure the crashed
|
||||
@ -0,0 +1,99 @@
|
||||
From 1e97dc426766eef1590d9054952aa0d7d5882b79 Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Tue, 23 Sep 2025 14:28:33 +0200
|
||||
Subject: [PATCH] test: rename TEST-53-ISSUE-16347 to TEST-53-TIMER
|
||||
|
||||
And split the existing test into a separate subtest.
|
||||
|
||||
(cherry picked from commit 953c347fb6f293acbd6da009646bfc071b68ddd7)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
.../Makefile | 0
|
||||
.../test.sh | 0
|
||||
test/units/testsuite-53.issue-16347.sh | 27 ++++++++++++++++++
|
||||
test/units/testsuite-53.sh | 28 +++----------------
|
||||
4 files changed, 31 insertions(+), 24 deletions(-)
|
||||
rename test/{TEST-53-ISSUE-16347 => TEST-53-TIMER}/Makefile (100%)
|
||||
rename test/{TEST-53-ISSUE-16347 => TEST-53-TIMER}/test.sh (100%)
|
||||
create mode 100755 test/units/testsuite-53.issue-16347.sh
|
||||
|
||||
diff --git a/test/TEST-53-ISSUE-16347/Makefile b/test/TEST-53-TIMER/Makefile
|
||||
similarity index 100%
|
||||
rename from test/TEST-53-ISSUE-16347/Makefile
|
||||
rename to test/TEST-53-TIMER/Makefile
|
||||
diff --git a/test/TEST-53-ISSUE-16347/test.sh b/test/TEST-53-TIMER/test.sh
|
||||
similarity index 100%
|
||||
rename from test/TEST-53-ISSUE-16347/test.sh
|
||||
rename to test/TEST-53-TIMER/test.sh
|
||||
diff --git a/test/units/testsuite-53.issue-16347.sh b/test/units/testsuite-53.issue-16347.sh
|
||||
new file mode 100755
|
||||
index 0000000000..8b266145cd
|
||||
--- /dev/null
|
||||
+++ b/test/units/testsuite-53.issue-16347.sh
|
||||
@@ -0,0 +1,27 @@
|
||||
+#!/usr/bin/env bash
|
||||
+# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
+set -eux
|
||||
+set -o pipefail
|
||||
+
|
||||
+# Reset host date to current time, 3 days in the past.
|
||||
+date -s "-3 days"
|
||||
+trap 'date -s "+3 days"' EXIT
|
||||
+
|
||||
+# Run a timer for every 15 minutes.
|
||||
+systemd-run --unit test-timer --on-calendar "*:0/15:0" true
|
||||
+
|
||||
+next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
|
||||
+next_elapsed=$(date -d "${next_elapsed}" +%s)
|
||||
+now=$(date +%s)
|
||||
+time_delta=$((next_elapsed - now))
|
||||
+
|
||||
+# Check that the timer will elapse in less than 20 minutes.
|
||||
+if [[ "$time_delta" -lt 0 || "$time_delta" -gt 1200 ]]; then
|
||||
+ echo 'Timer elapse outside of the expected 20 minute window.'
|
||||
+ echo " next_elapsed=${next_elapsed}"
|
||||
+ echo " now=${now}"
|
||||
+ echo " time_delta=${time_delta}"
|
||||
+ echo
|
||||
+
|
||||
+ exit 1
|
||||
+fi
|
||||
diff --git a/test/units/testsuite-53.sh b/test/units/testsuite-53.sh
|
||||
index 84cd66129d..9c2a033aa9 100755
|
||||
--- a/test/units/testsuite-53.sh
|
||||
+++ b/test/units/testsuite-53.sh
|
||||
@@ -3,29 +3,9 @@
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
-: >/failed
|
||||
+# shellcheck source=test/units/test-control.sh
|
||||
+. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
-# Reset host date to current time, 3 days in the past.
|
||||
-date -s "-3 days"
|
||||
+run_subtests
|
||||
|
||||
-# Run a timer for every 15 minutes.
|
||||
-systemd-run --unit test-timer --on-calendar "*:0/15:0" true
|
||||
-
|
||||
-next_elapsed=$(systemctl show test-timer.timer -p NextElapseUSecRealtime --value)
|
||||
-next_elapsed=$(date -d "${next_elapsed}" +%s)
|
||||
-now=$(date +%s)
|
||||
-time_delta=$((next_elapsed - now))
|
||||
-
|
||||
-# Check that the timer will elapse in less than 20 minutes.
|
||||
-((0 < time_delta && time_delta < 1200)) || {
|
||||
- echo 'Timer elapse outside of the expected 20 minute window.'
|
||||
- echo " next_elapsed=${next_elapsed}"
|
||||
- echo " now=${now}"
|
||||
- echo " time_delta=${time_delta}"
|
||||
- echo ''
|
||||
-} >>/failed
|
||||
-
|
||||
-if test ! -s /failed ; then
|
||||
- rm -f /failed
|
||||
- touch /testok
|
||||
-fi
|
||||
+touch /testok
|
||||
@ -0,0 +1,101 @@
|
||||
From 9fbd356a6453822d0472d8f1488adfc902a8d241 Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Tue, 23 Sep 2025 17:42:01 +0200
|
||||
Subject: [PATCH] test: restarting elapsed timer shouldn't trigger the
|
||||
corresponding service
|
||||
|
||||
Provides coverage for:
|
||||
- https://github.com/systemd/systemd/issues/31231
|
||||
- https://github.com/systemd/systemd/issues/35805
|
||||
|
||||
(cherry picked from commit 5730a400fd5ee82566fe03eb832121a0d4bc26b6)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
test/units/testsuite-53.restart-trigger.sh | 77 ++++++++++++++++++++++
|
||||
1 file changed, 77 insertions(+)
|
||||
create mode 100755 test/units/testsuite-53.restart-trigger.sh
|
||||
|
||||
diff --git a/test/units/testsuite-53.restart-trigger.sh b/test/units/testsuite-53.restart-trigger.sh
|
||||
new file mode 100755
|
||||
index 0000000000..057f379ddc
|
||||
--- /dev/null
|
||||
+++ b/test/units/testsuite-53.restart-trigger.sh
|
||||
@@ -0,0 +1,77 @@
|
||||
+#!/usr/bin/env bash
|
||||
+# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
+#
|
||||
+# Restarting an already elapsed timer shouldn't immediately trigger the corresponding service unit.
|
||||
+#
|
||||
+# Provides coverage for:
|
||||
+# - https://github.com/systemd/systemd/issues/31231
|
||||
+# - https://github.com/systemd/systemd/issues/35805
|
||||
+set -eux
|
||||
+set -o pipefail
|
||||
+
|
||||
+# shellcheck source=test/units/test-control.sh
|
||||
+. "$(dirname "$0")"/util.sh
|
||||
+
|
||||
+UNIT_NAME="timer-restart-$RANDOM"
|
||||
+TEST_MESSAGE="Hello from timer $RANDOM"
|
||||
+
|
||||
+# Setup
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
|
||||
+[Timer]
|
||||
+OnCalendar=$(date --date="+1 hour" "+%Y-%m-%d %H:%M:%S")
|
||||
+AccuracySec=1s
|
||||
+EOF
|
||||
+
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.service" <<EOF
|
||||
+[Service]
|
||||
+ExecStart=echo "$TEST_MESSAGE"
|
||||
+EOF
|
||||
+
|
||||
+systemctl daemon-reload
|
||||
+
|
||||
+JOURNAL_TS="$(date "+%s")"
|
||||
+# Paranoia check that the test message is not already in the logs
|
||||
+(! journalctl -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE")
|
||||
+
|
||||
+# Restart time timer and move time forward by 2 hours to trigger the timer
|
||||
+systemctl restart "$UNIT_NAME.timer"
|
||||
+systemctl status "$UNIT_NAME.timer"
|
||||
+
|
||||
+date -s '+2 hours'
|
||||
+trap 'date -s "-2 hours"' EXIT
|
||||
+sleep 1
|
||||
+systemctl status "$UNIT_NAME.timer"
|
||||
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
|
||||
+
|
||||
+# Restarting the timer unit shouldn't trigger neither the timer nor the service, so these
|
||||
+# fields should remain constant through the following tests
|
||||
+SERVICE_INV_ID="$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
+TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+
|
||||
+# Now restart the timer and check if the timer and the service weren't triggered again
|
||||
+systemctl restart "$UNIT_NAME.timer"
|
||||
+sleep 5
|
||||
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
|
||||
+assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+
|
||||
+# Set the timer into the past, restart it, and again check if it wasn't triggered
|
||||
+TIMER_TS="$(date --date="-1 day" "+%Y-%m-%d %H:%M:%S")"
|
||||
+mkdir "/run/systemd/system/$UNIT_NAME.timer.d/"
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.timer.d/99-override.conf" <<EOF
|
||||
+[Timer]
|
||||
+OnCalendar=$TIMER_TS
|
||||
+EOF
|
||||
+systemctl daemon-reload
|
||||
+systemctl status "$UNIT_NAME.timer"
|
||||
+assert_in "OnCalendar=$TIMER_TS" "$(systemctl show -P TimersCalendar "$UNIT_NAME".timer)"
|
||||
+systemctl restart "$UNIT_NAME.timer"
|
||||
+sleep 5
|
||||
+assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
|
||||
+assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+
|
||||
+# Cleanup
|
||||
+systemctl stop "$UNIT_NAME".{timer,service}
|
||||
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
|
||||
+systemctl daemon-reload
|
||||
@ -0,0 +1,156 @@
|
||||
From c282dc4e58279b4db6f735228e210a6e46ec5638 Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Tue, 23 Sep 2025 21:04:12 +0200
|
||||
Subject: [PATCH] test: check the next elapse timer timestamp after
|
||||
deserialization
|
||||
|
||||
When deserializing a serialized timer unit with RandomizedDelaySec= set,
|
||||
systemd should use the last inactive exit timestamp instead of current
|
||||
realtime to calculate the new next elapse, so the timer unit actually
|
||||
runs in the given calendar window.
|
||||
|
||||
Provides coverage for:
|
||||
- https://github.com/systemd/systemd/issues/18678
|
||||
- https://github.com/systemd/systemd/pull/27752
|
||||
|
||||
(cherry picked from commit f4c3c107d9be4e922a080fc292ed3889c4e0f4a5)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
.../testsuite-53.RandomizedDelaySec-reload.sh | 97 +++++++++++++++++++
|
||||
test/units/util.sh | 18 ++++
|
||||
2 files changed, 115 insertions(+)
|
||||
create mode 100755 test/units/testsuite-53.RandomizedDelaySec-reload.sh
|
||||
|
||||
diff --git a/test/units/testsuite-53.RandomizedDelaySec-reload.sh b/test/units/testsuite-53.RandomizedDelaySec-reload.sh
|
||||
new file mode 100755
|
||||
index 0000000000..08f4f469d6
|
||||
--- /dev/null
|
||||
+++ b/test/units/testsuite-53.RandomizedDelaySec-reload.sh
|
||||
@@ -0,0 +1,97 @@
|
||||
+#!/usr/bin/env bash
|
||||
+# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
+#
|
||||
+# When deserializing a serialized timer unit with RandomizedDelaySec= set, systemd should use the last
|
||||
+# inactive exit timestamp instead of current realtime to calculate the new next elapse, so the timer unit
|
||||
+# actually runs in the given calendar window.
|
||||
+#
|
||||
+# Provides coverage for:
|
||||
+# - https://github.com/systemd/systemd/issues/18678
|
||||
+# - https://github.com/systemd/systemd/pull/27752
|
||||
+set -eux
|
||||
+set -o pipefail
|
||||
+
|
||||
+# shellcheck source=test/units/test-control.sh
|
||||
+. "$(dirname "$0")"/util.sh
|
||||
+
|
||||
+UNIT_NAME="timer-RandomizedDelaySec-$RANDOM"
|
||||
+TARGET_TS="$(date --date="tomorrow 00:10")"
|
||||
+TARGET_TS_S="$(date --date="$TARGET_TS" "+%s")"
|
||||
+# Maximum possible next elapse timestamp: $TARGET_TS (OnCalendar=) + 22 hours (RandomizedDelaySec=)
|
||||
+MAX_NEXT_ELAPSE_REALTIME_S="$((TARGET_TS_S + 22 * 60 * 60))"
|
||||
+MAX_NEXT_ELAPSE_REALTIME="$(date --date="@$MAX_NEXT_ELAPSE_REALTIME_S")"
|
||||
+
|
||||
+# Let's make sure to return the date & time back to the original state once we're done with our time
|
||||
+# shenigans. One way to do this would be to use hwclock, but the RTC in VMs can be unreliable or slow to
|
||||
+# respond, causing unexpected test fails/timeouts.
|
||||
+#
|
||||
+# Instead, let's save the realtime timestamp before we start with the test together with a current monotonic
|
||||
+# timestamp, after the test ends take the difference between the current monotonic timestamp and the "start"
|
||||
+# one, add it to the originally saved realtime timestamp, and finally use that timestamp to set the system
|
||||
+# time. This should advance the system time by the amount of time the test actually ran, and hence restore it
|
||||
+# to some sane state after the time jumps performed by the test. It won't be perfect, but it should be close
|
||||
+# enough for our needs.
|
||||
+START_REALTIME="$(date "+%s")"
|
||||
+START_MONOTONIC="$(cut -d . -f 1 /proc/uptime)"
|
||||
+at_exit() {
|
||||
+ : "Restore the system date to a sane state"
|
||||
+ END_MONOTONIC="$(cut -d . -f 1 /proc/uptime)"
|
||||
+ date --set="@$((START_REALTIME + END_MONOTONIC - START_MONOTONIC))"
|
||||
+}
|
||||
+trap at_exit EXIT
|
||||
+
|
||||
+# Set some predictable time so we can schedule the first timer elapse in a deterministic-ish way
|
||||
+date --set="23:00"
|
||||
+
|
||||
+# Setup
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
|
||||
+[Timer]
|
||||
+# Run this timer daily, ten minutes after midnight
|
||||
+OnCalendar=*-*-* 00:10:00
|
||||
+RandomizedDelaySec=22h
|
||||
+AccuracySec=1ms
|
||||
+EOF
|
||||
+
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.service" <<EOF
|
||||
+[Service]
|
||||
+ExecStart=echo "Hello world"
|
||||
+EOF
|
||||
+
|
||||
+systemctl daemon-reload
|
||||
+
|
||||
+check_elapse_timestamp() {
|
||||
+ systemctl status "$UNIT_NAME.timer"
|
||||
+ systemctl show -p InactiveExitTimestamp "$UNIT_NAME.timer"
|
||||
+
|
||||
+ NEXT_ELAPSE_REALTIME="$(systemctl show -P NextElapseUSecRealtime "$UNIT_NAME.timer")"
|
||||
+ NEXT_ELAPSE_REALTIME_S="$(date --date="$NEXT_ELAPSE_REALTIME" "+%s")"
|
||||
+ : "Next elapse timestamp should be $TARGET_TS <= $NEXT_ELAPSE_REALTIME <= $MAX_NEXT_ELAPSE_REALTIME"
|
||||
+ assert_ge "$NEXT_ELAPSE_REALTIME_S" "$TARGET_TS_S"
|
||||
+ assert_le "$NEXT_ELAPSE_REALTIME_S" "$MAX_NEXT_ELAPSE_REALTIME_S"
|
||||
+}
|
||||
+
|
||||
+# Restart the timer unit and check the initial next elapse timestamp
|
||||
+: "Initial next elapse timestamp"
|
||||
+systemctl restart "$UNIT_NAME.timer"
|
||||
+check_elapse_timestamp
|
||||
+
|
||||
+# Bump the system date to 1 minute after the original calendar timer would've expired (without any random
|
||||
+# delay!) - systemd should recalculate the next elapse timestamp with a new randomized delay, but it should
|
||||
+# use the original inactive exit timestamp as a "base", so the final timestamp should not end up beyond the
|
||||
+# original calendar timestamp + randomized delay range.
|
||||
+#
|
||||
+# Similarly, do the same check after doing daemon-reload, as that also forces systemd to recalculate the next
|
||||
+# elapse timestamp (this goes through a slightly different codepath that actually contained the original
|
||||
+# issue).
|
||||
+: "Next elapse timestamp after time jump"
|
||||
+date -s "tomorrow 00:11"
|
||||
+check_elapse_timestamp
|
||||
+
|
||||
+: "Next elapse timestamp after daemon-reload"
|
||||
+systemctl daemon-reload
|
||||
+check_elapse_timestamp
|
||||
+
|
||||
+# Cleanup
|
||||
+systemctl stop "$UNIT_NAME".{timer,service}
|
||||
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
|
||||
+systemctl daemon-reload
|
||||
diff --git a/test/units/util.sh b/test/units/util.sh
|
||||
index 00b8c5e393..5728b324a9 100755
|
||||
--- a/test/units/util.sh
|
||||
+++ b/test/units/util.sh
|
||||
@@ -26,6 +26,24 @@ assert_eq() {(
|
||||
fi
|
||||
)}
|
||||
|
||||
+assert_le() {(
|
||||
+ set +ex
|
||||
+
|
||||
+ if [[ "${1:?}" -gt "${2:?}" ]]; then
|
||||
+ echo "FAIL: '$1' > '$2'" >&2
|
||||
+ exit 1
|
||||
+ fi
|
||||
+)}
|
||||
+
|
||||
+assert_ge() {(
|
||||
+ set +ex
|
||||
+
|
||||
+ if [[ "${1:?}" -lt "${2:?}" ]]; then
|
||||
+ echo "FAIL: '$1' < '$2'" >&2
|
||||
+ exit 1
|
||||
+ fi
|
||||
+)}
|
||||
+
|
||||
assert_in() {(
|
||||
set +ex
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
From 7dc588429e48ba6017fd75f6a266288768230025 Mon Sep 17 00:00:00 2001
|
||||
From: Lukas Nykryn <lnykryn@redhat.com>
|
||||
Date: Tue, 9 Sep 2025 15:24:22 +0200
|
||||
Subject: [PATCH] timer: don't run service immediately after restart of a timer
|
||||
|
||||
When a timer is restarted, don't reset the last_trigger field.
|
||||
This prevents the timer from triggering immediately.
|
||||
|
||||
Fixes: #31231
|
||||
(cherry picked from commit 3fc44a0f68412b649e16f12ff2f97a36c615457d)
|
||||
|
||||
Resolves: RHEL-127022
|
||||
---
|
||||
src/core/timer.c | 2 --
|
||||
1 file changed, 2 deletions(-)
|
||||
|
||||
diff --git a/src/core/timer.c b/src/core/timer.c
|
||||
index 60e8fea79f..2eadca4f1a 100644
|
||||
--- a/src/core/timer.c
|
||||
+++ b/src/core/timer.c
|
||||
@@ -635,8 +635,6 @@ static int timer_start(Unit *u) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
- t->last_trigger = DUAL_TIMESTAMP_NULL;
|
||||
-
|
||||
/* Reenable all timers that depend on unit activation time */
|
||||
LIST_FOREACH(value, v, t->values)
|
||||
if (v->base == TIMER_ACTIVE)
|
||||
@ -0,0 +1,49 @@
|
||||
From 5f0eda80175f04929c34cdb46cd85e3f4d64b6d1 Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Mon, 29 Sep 2025 16:11:27 +0200
|
||||
Subject: [PATCH] test: store and compare just the property value
|
||||
|
||||
Follow-up for 5730a400fd5ee82566fe03eb832121a0d4bc26b6.
|
||||
|
||||
(cherry picked from commit 0cb252d50f35256bff569fa6213784f2d45ad6a1)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
test/units/testsuite-53.restart-trigger.sh | 12 ++++++------
|
||||
1 file changed, 6 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/test/units/testsuite-53.restart-trigger.sh b/test/units/testsuite-53.restart-trigger.sh
|
||||
index 057f379ddc..e5cd575d66 100755
|
||||
--- a/test/units/testsuite-53.restart-trigger.sh
|
||||
+++ b/test/units/testsuite-53.restart-trigger.sh
|
||||
@@ -45,15 +45,15 @@ assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --
|
||||
|
||||
# Restarting the timer unit shouldn't trigger neither the timer nor the service, so these
|
||||
# fields should remain constant through the following tests
|
||||
-SERVICE_INV_ID="$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
-TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+SERVICE_INV_ID="$(systemctl show -P InvocationID "$UNIT_NAME.service")"
|
||||
+TIMER_LAST_TRIGGER="$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
|
||||
# Now restart the timer and check if the timer and the service weren't triggered again
|
||||
systemctl restart "$UNIT_NAME.timer"
|
||||
sleep 5
|
||||
assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
|
||||
-assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
-assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+assert_eq "$SERVICE_INV_ID" "$(systemctl show -P InvocationID "$UNIT_NAME.service")"
|
||||
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
|
||||
# Set the timer into the past, restart it, and again check if it wasn't triggered
|
||||
TIMER_TS="$(date --date="-1 day" "+%Y-%m-%d %H:%M:%S")"
|
||||
@@ -68,8 +68,8 @@ assert_in "OnCalendar=$TIMER_TS" "$(systemctl show -P TimersCalendar "$UNIT_NAME
|
||||
systemctl restart "$UNIT_NAME.timer"
|
||||
sleep 5
|
||||
assert_eq "$(journalctl -q -p info --since="@$JOURNAL_TS" --unit="$UNIT_NAME" --grep="$TEST_MESSAGE" | wc -l)" "1"
|
||||
-assert_eq "$SERVICE_INV_ID" "$(systemctl show --property=InvocationID "$UNIT_NAME.service")"
|
||||
-assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show --property=LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
+assert_eq "$SERVICE_INV_ID" "$(systemctl show -P InvocationID "$UNIT_NAME.service")"
|
||||
+assert_eq "$TIMER_LAST_TRIGGER" "$(systemctl show -P LastTriggerUSec "$UNIT_NAME.timer")"
|
||||
|
||||
# Cleanup
|
||||
systemctl stop "$UNIT_NAME".{timer,service}
|
||||
@ -0,0 +1,131 @@
|
||||
From e71140d217b3a2a75a52b488da39568d6ea942bd Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Mon, 13 Oct 2025 17:36:55 +0200
|
||||
Subject: [PATCH] timer: rebase the next elapse timestamp only if timer didn't
|
||||
already run
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The test added in f4c3c107d9be4e922a080fc292ed3889c4e0f4a5 uncovered a
|
||||
corner case while recalculating the next elapse timestamp of a timer unit
|
||||
that uses RandomizedDelaySec= during deserialization.
|
||||
|
||||
If the scheduled time (without RandomizedDelaySec=) already elapsed,
|
||||
systemd "rebases" the next elapse timestamp to the time when systemd
|
||||
first started, to make the RandomizedDelaySec= feature work even at
|
||||
boot. However, since it was done unconditionally, it always overrode the
|
||||
next elapse timestamp, which could then cause the final next elapse
|
||||
timestamp to fall out of the expected window.
|
||||
|
||||
With a couple of additional debug logs one of the test fail looks like
|
||||
this:
|
||||
|
||||
[ 132.129815] TEST-53-TIMER.sh[384]: + : 'Next elapse timestamp after daemon-reload, try #328'
|
||||
[ 132.129815] TEST-53-TIMER.sh[384]: + systemctl daemon-reload
|
||||
[ 132.136352] systemd[1]: Reload requested from client PID 16399 ('systemctl') (unit TEST-53-TIMER.service)...
|
||||
[ 132.136636] systemd[1]: Reloading...
|
||||
[ 132.446160] systemd[1]: Rebasing next elapse timestamp
|
||||
[ 132.446168] systemd[1]: v->next_elapse: Tue 2025-10-14 00:10:00 CEST
|
||||
[ 132.446170] systemd[1]: rebased: Tue 2025-10-14 00:10:56 CEST
|
||||
[ 132.446172] systemd[1]: v->next_elapse after rebase: Tue 2025-10-14 00:10:56 CEST
|
||||
[ 132.447361] systemd[1]: Reloading finished in 310 ms.
|
||||
[ 132.484041] TEST-53-TIMER.sh[384]: + check_elapse_timestamp
|
||||
[ 132.484041] TEST-53-TIMER.sh[384]: + systemctl status timer-RandomizedDelaySec-16377.timer
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: ● timer-RandomizedDelaySec-16377.timer
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Loaded: loaded (/run/systemd/system/timer-RandomizedDelaySec-16377.timer; static)
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Active: active (waiting) since Mon 2025-10-13 23:00:00 CEST; 1h 13min ago
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Invocation: 5555d4f060114a5493ff228013830d17
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Trigger: Tue 2025-10-14 22:10:04 CEST; 21h left
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Triggers: ● timer-RandomizedDelaySec-16377.service
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 15h 35min 1.230173s random time.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 15:45:58 CEST.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:07 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 16h 29min 44.084409s random time.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 16:40:41 CEST.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Adding 21h 59min 7.955828s random time.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Realtime timer elapses at Tue 2025-10-14 22:10:04 CEST.
|
||||
[ 132.533657] TEST-53-TIMER.sh[16440]: Oct 14 00:13:08 H systemd[1]: timer-RandomizedDelaySec-16377.timer: Changed dead -> waiting
|
||||
[ 132.535386] TEST-53-TIMER.sh[384]: + systemctl show -p InactiveExitTimestamp timer-RandomizedDelaySec-16377.timer
|
||||
[ 132.537727] TEST-53-TIMER.sh[16442]: InactiveExitTimestamp=Mon 2025-10-13 23:00:00 CEST
|
||||
[ 132.540317] TEST-53-TIMER.sh[16444]: ++ systemctl show -P NextElapseUSecRealtime timer-RandomizedDelaySec-16377.timer
|
||||
[ 132.547745] TEST-53-TIMER.sh[384]: + NEXT_ELAPSE_REALTIME='Tue 2025-10-14 22:10:04 CEST'
|
||||
[ 132.548020] TEST-53-TIMER.sh[16445]: ++ date '--date=Tue 2025-10-14 22:10:04 CEST' +%s
|
||||
[ 132.550218] TEST-53-TIMER.sh[384]: + NEXT_ELAPSE_REALTIME_S=1760472604
|
||||
[ 132.550218] TEST-53-TIMER.sh[384]: + : 'Next elapse timestamp should be Tue 2025-10-14 00:10:00 CEST <= Tue 2025-10-14 22:10:04 CEST <= Tue 2025-10-14 22:10:00 CEST'
|
||||
[ 132.550218] TEST-53-TIMER.sh[384]: + assert_ge 1760472604 1760393400
|
||||
[ 132.550555] TEST-53-TIMER.sh[16446]: + set +ex
|
||||
[ 132.550702] TEST-53-TIMER.sh[384]: + assert_le 1760472604 1760472600
|
||||
[ 132.550832] TEST-53-TIMER.sh[16447]: + set +ex
|
||||
[ 132.551091] TEST-53-TIMER.sh[16447]: FAIL: '1760472604' > '1760472600'
|
||||
|
||||
Here the original next elapse timestamp was Tue 2025-10-14 00:10:00 CEST
|
||||
as expected, but it was overridden by the rebased timestamp:
|
||||
Tue 2025-10-14 00:10:56 CEST. And when a new randomized delay was added
|
||||
to it (21h 59min 7.955828s) the final next elapse timestamp fell out of
|
||||
the expected window, i.e. Tue 2025-10-14 00:10:00 (scheduled time) <
|
||||
Tue 2025-10-14 22:10:04 CEST (rebased elapse timestamp + randomized
|
||||
delay) < Tue 2025-10-14 22:10:00 CEST (scheduled time + maximum from
|
||||
RandomizedDelaySec=, i.e. 22h).
|
||||
|
||||
By limiting the timestamp rebase only the case where the unit hasn't
|
||||
already run should prevent this from happening during daemon-reload.
|
||||
|
||||
(cherry picked from commit bdb8e584f4509de0daebbe2357d23156160c3a90)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
src/core/timer.c | 25 +++++++++++++++----------
|
||||
1 file changed, 15 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/src/core/timer.c b/src/core/timer.c
|
||||
index 2eadca4f1a..4b0266bc68 100644
|
||||
--- a/src/core/timer.c
|
||||
+++ b/src/core/timer.c
|
||||
@@ -392,7 +392,8 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
|
||||
continue;
|
||||
|
||||
if (v->base == TIMER_CALENDAR) {
|
||||
- usec_t b, rebased;
|
||||
+ bool rebase_after_boot_time = false;
|
||||
+ usec_t b;
|
||||
|
||||
/* If we know the last time this was
|
||||
* triggered, schedule the job based relative
|
||||
@@ -403,21 +404,25 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
|
||||
b = t->last_trigger.realtime;
|
||||
else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
|
||||
b = UNIT(t)->inactive_exit_timestamp.realtime;
|
||||
- else
|
||||
+ else {
|
||||
b = ts.realtime;
|
||||
+ rebase_after_boot_time = true;
|
||||
+ }
|
||||
|
||||
r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse);
|
||||
if (r < 0)
|
||||
continue;
|
||||
|
||||
- /* To make the delay due to RandomizedDelaySec= work even at boot, if the scheduled
|
||||
- * time has already passed, set the time when systemd first started as the scheduled
|
||||
- * time. Note that we base this on the monotonic timestamp of the boot, not the
|
||||
- * realtime one, since the wallclock might have been off during boot. */
|
||||
- rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
|
||||
- CLOCK_MONOTONIC, CLOCK_REALTIME);
|
||||
- if (v->next_elapse < rebased)
|
||||
- v->next_elapse = rebased;
|
||||
+ if (rebase_after_boot_time) {
|
||||
+ /* To make the delay due to RandomizedDelaySec= work even at boot, if the scheduled
|
||||
+ * time has already passed, set the time when systemd first started as the scheduled
|
||||
+ * time. Note that we base this on the monotonic timestamp of the boot, not the
|
||||
+ * realtime one, since the wallclock might have been off during boot. */
|
||||
+ usec_t rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
|
||||
+ CLOCK_MONOTONIC, CLOCK_REALTIME);
|
||||
+ if (v->next_elapse < rebased)
|
||||
+ v->next_elapse = rebased;
|
||||
+ }
|
||||
|
||||
if (!found_realtime)
|
||||
t->next_elapse_realtime = v->next_elapse;
|
||||
@ -0,0 +1,34 @@
|
||||
From 6ae4c19c78b0daac097b4a7d88566f95daa9e60b Mon Sep 17 00:00:00 2001
|
||||
From: Michal Sekletar <msekleta@redhat.com>
|
||||
Date: Fri, 24 Oct 2025 12:55:20 +0200
|
||||
Subject: [PATCH] coredump: handle ENOBUFS and EMSGSIZE the same way
|
||||
|
||||
Depending on the runtime configuration, e.g. sysctls
|
||||
net.core.wmem_default= and net.core.rmem_default and on the actual
|
||||
message size, sendmsg() can fail also with ENOBUFS. E.g. alloc_skb()
|
||||
failure caused by net.core.[rw]mem_default=64MiB and huge fdinfo list
|
||||
from process that has 90k opened FDs.
|
||||
|
||||
We should handle this case in the same way as EMSGSIZE and drop part of
|
||||
the message.
|
||||
|
||||
(cherry picked from commit 28e62e684b631f928f1d857b04f45f0d34441675)
|
||||
|
||||
Resolves: RHEL-126114
|
||||
---
|
||||
src/coredump/coredump.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index e0aac3c8d0..28dabf017b 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -1273,7 +1273,7 @@ static int send_iovec(const struct iovec_wrapper *iovw, int input_fd, int mntns_
|
||||
if (sendmsg(fd, &mh, MSG_NOSIGNAL) >= 0)
|
||||
break;
|
||||
|
||||
- if (errno == EMSGSIZE && mh.msg_iov[0].iov_len > 0) {
|
||||
+ if (IN_SET(errno, EMSGSIZE, ENOBUFS) && mh.msg_iov[0].iov_len > 0) {
|
||||
/* This field didn't fit? That's a pity. Given that this is
|
||||
* just metadata, let's truncate the field at half, and try
|
||||
* again. We append three dots, in order to show that this is
|
||||
143
SOURCES/1269-timer-rebase-last_trigger-timestamp-if-needed.patch
Normal file
143
SOURCES/1269-timer-rebase-last_trigger-timestamp-if-needed.patch
Normal file
@ -0,0 +1,143 @@
|
||||
From 6c05a35ce4d03cf25220de5e950970ef23417415 Mon Sep 17 00:00:00 2001
|
||||
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
||||
Date: Wed, 19 Nov 2025 14:44:13 +0100
|
||||
Subject: [PATCH] timer: rebase last_trigger timestamp if needed
|
||||
|
||||
After bdb8e584f4509de0daebbe2357d23156160c3a90 we stopped rebasing the
|
||||
next elapse timestamp unconditionally and the only case where we'd do
|
||||
that was when both last trigger and last inactive timestamps were empty.
|
||||
This covered timer units during boot just fine, since they would have
|
||||
neither of those timestamps set. However, persistent timers
|
||||
(Persistent=yes) store their last trigger timestamp on a persistent
|
||||
storage and load it back after reboot, so the rebasing was skipped in
|
||||
this case.
|
||||
|
||||
To mitigate this, check the last_trigger timestamp is older than the
|
||||
current machine boot - if so, that means that it came from a stamp file
|
||||
of a persistent timer unit and we need to rebase it to make
|
||||
RandomizedDelaySec= work properly.
|
||||
|
||||
Follow-up for bdb8e584f4509de0daebbe2357d23156160c3a90.
|
||||
|
||||
(cherry picked from commit 3605b3ba87833a9919bfde05952a7d9de10499a2)
|
||||
|
||||
Related: RHEL-127022
|
||||
---
|
||||
src/core/timer.c | 15 +++--
|
||||
...tsuite-53.RandomizedDelaySec-persistent.sh | 67 +++++++++++++++++++
|
||||
2 files changed, 78 insertions(+), 4 deletions(-)
|
||||
create mode 100755 test/units/testsuite-53.RandomizedDelaySec-persistent.sh
|
||||
|
||||
diff --git a/src/core/timer.c b/src/core/timer.c
|
||||
index 4b0266bc68..8fb79bc0cb 100644
|
||||
--- a/src/core/timer.c
|
||||
+++ b/src/core/timer.c
|
||||
@@ -394,15 +394,23 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
|
||||
if (v->base == TIMER_CALENDAR) {
|
||||
bool rebase_after_boot_time = false;
|
||||
usec_t b;
|
||||
+ usec_t boot_monotonic = UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic;
|
||||
|
||||
/* If we know the last time this was
|
||||
* triggered, schedule the job based relative
|
||||
* to that. If we don't, just start from
|
||||
* the activation time. */
|
||||
|
||||
- if (dual_timestamp_is_set(&t->last_trigger))
|
||||
+ if (dual_timestamp_is_set(&t->last_trigger)) {
|
||||
b = t->last_trigger.realtime;
|
||||
- else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
|
||||
+
|
||||
+ /* Check if the last_trigger timestamp is older than the current machine
|
||||
+ * boot. If so, this means the timestamp came from a stamp file of a
|
||||
+ * persistent timer and we need to rebase it to make RandomizedDelaySec=
|
||||
+ * work (see below). */
|
||||
+ if (t->last_trigger.monotonic < boot_monotonic)
|
||||
+ rebase_after_boot_time = true;
|
||||
+ } else if (dual_timestamp_is_set(&UNIT(t)->inactive_exit_timestamp))
|
||||
b = UNIT(t)->inactive_exit_timestamp.realtime;
|
||||
else {
|
||||
b = ts.realtime;
|
||||
@@ -418,8 +426,7 @@ static void timer_enter_waiting(Timer *t, bool time_change) {
|
||||
* time has already passed, set the time when systemd first started as the scheduled
|
||||
* time. Note that we base this on the monotonic timestamp of the boot, not the
|
||||
* realtime one, since the wallclock might have been off during boot. */
|
||||
- usec_t rebased = map_clock_usec(UNIT(t)->manager->timestamps[MANAGER_TIMESTAMP_USERSPACE].monotonic,
|
||||
- CLOCK_MONOTONIC, CLOCK_REALTIME);
|
||||
+ usec_t rebased = map_clock_usec(boot_monotonic, CLOCK_MONOTONIC, CLOCK_REALTIME);
|
||||
if (v->next_elapse < rebased)
|
||||
v->next_elapse = rebased;
|
||||
}
|
||||
diff --git a/test/units/testsuite-53.RandomizedDelaySec-persistent.sh b/test/units/testsuite-53.RandomizedDelaySec-persistent.sh
|
||||
new file mode 100755
|
||||
index 0000000000..af22daecc7
|
||||
--- /dev/null
|
||||
+++ b/test/units/testsuite-53.RandomizedDelaySec-persistent.sh
|
||||
@@ -0,0 +1,67 @@
|
||||
+#!/usr/bin/env bash
|
||||
+# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
+#
|
||||
+# Persistent timers (i.e. timers with Persitent=yes) save their last trigger timestamp to a persistent
|
||||
+# storage (a stamp file), which is loaded during subsequent boots. As mentioned in the man page, such timers
|
||||
+# should be still affected by RandomizedDelaySec= during boot even if they already elapsed and would be then
|
||||
+# triggered immediately.
|
||||
+#
|
||||
+# This behavior was, however, broken by [0], which stopped rebasing the to-be next elapse timestamps
|
||||
+# unconditionally and left that only for timers that have neither last trigger nor inactive exit timestamps
|
||||
+# set, since rebasing is needed only during boot. This holds for regular timers during boot, but not for
|
||||
+# persistent ones, since the last trigger timestamp is loaded from a persistent storage.
|
||||
+#
|
||||
+# Provides coverage for:
|
||||
+# - https://github.com/systemd/systemd/issues/39739
|
||||
+#
|
||||
+# [0] bdb8e584f4509de0daebbe2357d23156160c3a90
|
||||
+#
|
||||
+set -eux
|
||||
+set -o pipefail
|
||||
+
|
||||
+# shellcheck source=test/units/test-control.sh
|
||||
+. "$(dirname "$0")"/util.sh
|
||||
+
|
||||
+UNIT_NAME="timer-RandomizedDelaySec-persistent-$RANDOM"
|
||||
+STAMP_FILE="/var/lib/systemd/timers/stamp-$UNIT_NAME.timer"
|
||||
+
|
||||
+# Setup
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.timer" <<EOF
|
||||
+[Timer]
|
||||
+OnCalendar=daily
|
||||
+Persistent=true
|
||||
+RandomizedDelaySec=12h
|
||||
+EOF
|
||||
+
|
||||
+cat >"/run/systemd/system/$UNIT_NAME.service" <<\EOF
|
||||
+[Service]
|
||||
+ExecStart=echo "Service ran at $(date)"
|
||||
+EOF
|
||||
+
|
||||
+systemctl daemon-reload
|
||||
+
|
||||
+# Create timer's state file with an old-enough timestamp (~2 days ago), so it'd definitely elapse if the next
|
||||
+# elapse timestamp wouldn't get rebased
|
||||
+mkdir -p "$(dirname "$STAMP_FILE")"
|
||||
+touch -d "2 days ago" "$STAMP_FILE"
|
||||
+stat "$STAMP_FILE"
|
||||
+SAVED_LAST_TRIGGER_S="$(stat --format="%Y" "$STAMP_FILE")"
|
||||
+
|
||||
+# Start the timer and verify that its last trigger timestamp didn't change
|
||||
+#
|
||||
+# The last trigger timestamp should get rebased before it gets used as a base for the next elapse timestamp
|
||||
+# (since it pre-dates the machine boot time). This should then add a RandomizedDelaySec= to the rebased
|
||||
+# timestamp and the timer unit should not get triggered immediately after starting.
|
||||
+systemctl start "$UNIT_NAME.timer"
|
||||
+systemctl status "$UNIT_NAME.timer"
|
||||
+
|
||||
+TIMER_LAST_TRIGGER="$(systemctl show --property=LastTriggerUSec --value "$UNIT_NAME.timer")"
|
||||
+TIMER_LAST_TRIGGER_S="$(date --date="$TIMER_LAST_TRIGGER" "+%s")"
|
||||
+: "The timer should not be triggered immediately, hence the last trigger timestamp should not change"
|
||||
+assert_eq "$SAVED_LAST_TRIGGER_S" "$TIMER_LAST_TRIGGER_S"
|
||||
+
|
||||
+# Cleanup
|
||||
+systemctl stop "$UNIT_NAME".{timer,service}
|
||||
+systemctl clean --what=state "$UNIT_NAME.timer"
|
||||
+rm -f "/run/systemd/system/$UNIT_NAME".{timer,service}
|
||||
+systemctl daemon-reload
|
||||
36
SOURCES/1270-core-fix-array-size-in-unit_log_resources.patch
Normal file
36
SOURCES/1270-core-fix-array-size-in-unit_log_resources.patch
Normal file
@ -0,0 +1,36 @@
|
||||
From ca32ed0b2451453eaff579d08f9e188f9c79ab0c Mon Sep 17 00:00:00 2001
|
||||
From: Florian Schmaus <flo@geekplace.eu>
|
||||
Date: Thu, 9 Nov 2023 08:59:59 +0100
|
||||
Subject: [PATCH] core: fix array size in unit_log_resources()
|
||||
|
||||
In 0531bded79dc ("core: include peak memory in unit_log_resources()") new log
|
||||
messages where added, however the size of the according arrays to hold the
|
||||
messages was not adjusted.
|
||||
|
||||
Fixes: 0531bded79dc ("core: include peak memory in unit_log_resources()")
|
||||
(cherry picked from commit 893028523469b3ec459388428ddc466942cdaf4d)
|
||||
|
||||
Resolves: RHEL-132120
|
||||
---
|
||||
src/core/unit.c | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/core/unit.c b/src/core/unit.c
|
||||
index 9e349402ff..3e04b12951 100644
|
||||
--- a/src/core/unit.c
|
||||
+++ b/src/core/unit.c
|
||||
@@ -2233,12 +2233,12 @@ static int raise_level(int log_level, bool condition_info, bool condition_notice
|
||||
}
|
||||
|
||||
static int unit_log_resources(Unit *u) {
|
||||
- struct iovec iovec[1 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4];
|
||||
+ struct iovec iovec[1 + 1 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4];
|
||||
bool any_traffic = false, have_ip_accounting = false, any_io = false, have_io_accounting = false;
|
||||
_cleanup_free_ char *igress = NULL, *egress = NULL, *rr = NULL, *wr = NULL;
|
||||
int log_level = LOG_DEBUG; /* May be raised if resources consumed over a threshold */
|
||||
size_t n_message_parts = 0, n_iovec = 0;
|
||||
- char* message_parts[1 + 2 + 2 + 1], *t;
|
||||
+ char* message_parts[1 + 1 + 2 + 2 + 1], *t;
|
||||
nsec_t nsec = NSEC_INFINITY;
|
||||
uint64_t memory_peak = UINT64_MAX;
|
||||
int r;
|
||||
@ -1,97 +0,0 @@
|
||||
From d9281e6450d2cca3ea5e7eed61d95b8ff0fcca0b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
|
||||
Date: Sat, 16 Sep 2023 10:08:12 +0200
|
||||
Subject: [PATCH] basic/parse-util: add helper to parse bounded unsigned values
|
||||
|
||||
"parse_range" is already used for stuff like "a-b", so use "bounded" here to
|
||||
avoid confusion.
|
||||
---
|
||||
src/basic/parse-util.c | 15 +++++++++++++++
|
||||
src/basic/parse-util.h | 3 ++-
|
||||
src/test/test-parse-util.c | 26 ++++++++++++++++++++++++++
|
||||
3 files changed, 43 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
|
||||
index 3b3efb0..c1c25fd 100644
|
||||
--- a/src/basic/parse-util.c
|
||||
+++ b/src/basic/parse-util.c
|
||||
@@ -390,6 +390,21 @@ int safe_atou_full(const char *s, unsigned base, unsigned *ret_u) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
+int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret) {
|
||||
+ unsigned v;
|
||||
+ int r;
|
||||
+
|
||||
+ r = safe_atou(s, &v);
|
||||
+ if (r < 0)
|
||||
+ return r;
|
||||
+
|
||||
+ if (v < min || v > max)
|
||||
+ return -ERANGE;
|
||||
+
|
||||
+ *ret = v;
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
int safe_atoi(const char *s, int *ret_i) {
|
||||
unsigned base = 0;
|
||||
char *x = NULL;
|
||||
diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h
|
||||
index 8d8d523..7e1517c 100644
|
||||
--- a/src/basic/parse-util.h
|
||||
+++ b/src/basic/parse-util.h
|
||||
@@ -28,11 +28,12 @@ int parse_errno(const char *t);
|
||||
#define SAFE_ATO_MASK_FLAGS(base) ((base) & ~SAFE_ATO_ALL_FLAGS)
|
||||
|
||||
int safe_atou_full(const char *s, unsigned base, unsigned *ret_u);
|
||||
-
|
||||
static inline int safe_atou(const char *s, unsigned *ret_u) {
|
||||
return safe_atou_full(s, 0, ret_u);
|
||||
}
|
||||
|
||||
+int safe_atou_bounded(const char *s, unsigned min, unsigned max, unsigned *ret);
|
||||
+
|
||||
int safe_atoi(const char *s, int *ret_i);
|
||||
int safe_atolli(const char *s, long long int *ret_i);
|
||||
|
||||
diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c
|
||||
index 388d0fe..3bf237b 100644
|
||||
--- a/src/test/test-parse-util.c
|
||||
+++ b/src/test/test-parse-util.c
|
||||
@@ -417,6 +417,32 @@ TEST(parse_range) {
|
||||
assert_se(upper == 9999);
|
||||
}
|
||||
|
||||
+TEST(safe_atou_bounded) {
|
||||
+ int r;
|
||||
+ unsigned x;
|
||||
+
|
||||
+ r = safe_atou_bounded("12345", 12, 20000, &x);
|
||||
+ assert_se(r == 0);
|
||||
+ assert_se(x == 12345);
|
||||
+
|
||||
+ r = safe_atou_bounded("12", 12, 20000, &x);
|
||||
+ assert_se(r == 0);
|
||||
+ assert_se(x == 12);
|
||||
+
|
||||
+ r = safe_atou_bounded("20000", 12, 20000, &x);
|
||||
+ assert_se(r == 0);
|
||||
+ assert_se(x == 20000);
|
||||
+
|
||||
+ r = safe_atou_bounded("-1", 12, 20000, &x);
|
||||
+ assert_se(r == -ERANGE);
|
||||
+
|
||||
+ r = safe_atou_bounded("11", 12, 20000, &x);
|
||||
+ assert_se(r == -ERANGE);
|
||||
+
|
||||
+ r = safe_atou_bounded("20001", 12, 20000, &x);
|
||||
+ assert_se(r == -ERANGE);
|
||||
+}
|
||||
+
|
||||
TEST(safe_atolli) {
|
||||
int r;
|
||||
long long l;
|
||||
--
|
||||
2.47.1
|
||||
|
||||
@ -1,88 +0,0 @@
|
||||
From a18061cee0313d9425cad46c3b7c771cb74a22d0 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
Date: Tue, 27 May 2025 10:44:20 +0000
|
||||
Subject: [PATCH 1/2] coredump: get rid of _META_MANDATORY_MAX
|
||||
|
||||
No functional change. This change is done in preparation for future changes.
|
||||
Currently, the list of fields which are received on the command line is a
|
||||
strict subset of the fields which are always expected to be received on a
|
||||
socket. But when we add new kernel args in the future, we'll have two
|
||||
non-overlapping sets and this approach will not work. Get rid of the variable
|
||||
and enumerate the required fields. This set will never change, so this is
|
||||
actually more maintainable.
|
||||
|
||||
The message with the hint where to add new fields is switched with
|
||||
_META_ARGV_MAX. The new order is more correct.
|
||||
|
||||
CVE-2025-4598
|
||||
|
||||
Modified-by: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
Signed-off-by: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
---
|
||||
src/coredump/coredump.c | 28 +++++++++++++++++++---------
|
||||
1 file changed, 19 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index dca78fa..539bc83 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -81,7 +81,7 @@
|
||||
* size. See DATA_SIZE_MAX in journal-importer.h. */
|
||||
assert_cc(JOURNAL_SIZE_MAX <= DATA_SIZE_MAX);
|
||||
|
||||
-enum {
|
||||
+typedef enum {
|
||||
/* We use these as array indexes for our process metadata cache.
|
||||
*
|
||||
* The first indices of the cache stores the same metadata as the ones passed by
|
||||
@@ -103,16 +103,14 @@ enum {
|
||||
* environment. */
|
||||
|
||||
META_COMM = _META_ARGV_MAX,
|
||||
- _META_MANDATORY_MAX,
|
||||
|
||||
/* The rest are similar to the previous ones except that we won't fail if one of
|
||||
* them is missing. */
|
||||
-
|
||||
- META_EXE = _META_MANDATORY_MAX,
|
||||
+ META_EXE,
|
||||
META_UNIT,
|
||||
META_PROC_AUXV,
|
||||
_META_MAX
|
||||
-};
|
||||
+} meta_argv_t;
|
||||
|
||||
static const char * const meta_field_names[_META_MAX] = {
|
||||
[META_ARGV_PID] = "COREDUMP_PID=",
|
||||
@@ -1193,12 +1191,24 @@ static int process_socket(int fd) {
|
||||
if (r < 0)
|
||||
goto finish;
|
||||
|
||||
- /* Make sure we received at least all fields we need. */
|
||||
- for (int i = 0; i < _META_MANDATORY_MAX; i++)
|
||||
+ /* Make sure we received all the expected fields. We support being called by an *older*
|
||||
+ * systemd-coredump from the outside, so we require only the basic set of fields that
|
||||
+ * was being sent when the support for sending to containers over a socket was added
|
||||
+ * in a108c43e36d3ceb6e34efe37c014fc2cda856000. */
|
||||
+ meta_argv_t i;
|
||||
+ VA_ARGS_FOREACH(i,
|
||||
+ META_ARGV_PID,
|
||||
+ META_ARGV_UID,
|
||||
+ META_ARGV_GID,
|
||||
+ META_ARGV_SIGNAL,
|
||||
+ META_ARGV_TIMESTAMP,
|
||||
+ META_ARGV_RLIMIT,
|
||||
+ META_ARGV_HOSTNAME,
|
||||
+ META_COMM)
|
||||
if (!context.meta[i]) {
|
||||
r = log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
- "A mandatory argument (%i) has not been sent, aborting.",
|
||||
- i);
|
||||
+ "Mandatory argument %s not received on socket, aborting.",
|
||||
+ meta_field_names[i]);
|
||||
goto finish;
|
||||
}
|
||||
|
||||
--
|
||||
2.47.1
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
From 71f9f67970bde09f73812d59cbeafb7093e31f24 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
Date: Tue, 27 May 2025 10:47:46 +0000
|
||||
Subject: [PATCH 2/2] coredump: use %d in kernel core pattern
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The kernel provides %d which is documented as
|
||||
"dump mode—same as value returned by prctl(2) PR_GET_DUMPABLE".
|
||||
|
||||
We already query /proc/pid/auxv for this information, but unfortunately this
|
||||
check is subject to a race, because the crashed process may be replaced by an
|
||||
attacker before we read this data, for example replacing a SUID process that
|
||||
was killed by a signal with another process that is not SUID, tricking us into
|
||||
making the coredump of the original process readable by the attacker.
|
||||
|
||||
With this patch, we effectively add one more check to the list of conditions
|
||||
that need be satisfied if we are to make the coredump accessible to the user.
|
||||
|
||||
Reportedy-by: Qualys Security Advisory <qsa@qualys.com>
|
||||
|
||||
CVE-2025-4598
|
||||
|
||||
Modified-by: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
Signed-off-by: Alex Burmashev <alexander.burmashev@oracle.com>
|
||||
---
|
||||
src/coredump/coredump.c | 20 +++++++++++++++++---
|
||||
sysctl.d/50-coredump.conf.in | 2 +-
|
||||
2 files changed, 30 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c
|
||||
index 539bc83..f0b13a5 100644
|
||||
--- a/src/coredump/coredump.c
|
||||
+++ b/src/coredump/coredump.c
|
||||
@@ -95,6 +95,7 @@ typedef enum {
|
||||
META_ARGV_TIMESTAMP, /* %t: time of dump, expressed as seconds since the Epoch (we expand this to µs granularity) */
|
||||
META_ARGV_RLIMIT, /* %c: core file size soft resource limit */
|
||||
META_ARGV_HOSTNAME, /* %h: hostname */
|
||||
+ META_ARGV_DUMPABLE, /* %d: as set by the kernel */
|
||||
_META_ARGV_MAX,
|
||||
|
||||
/* The following indexes are cached for a couple of special fields we use (and
|
||||
@@ -120,6 +121,7 @@ static const char * const meta_field_names[_META_MAX] = {
|
||||
[META_ARGV_TIMESTAMP] = "COREDUMP_TIMESTAMP=",
|
||||
[META_ARGV_RLIMIT] = "COREDUMP_RLIMIT=",
|
||||
[META_ARGV_HOSTNAME] = "COREDUMP_HOSTNAME=",
|
||||
+ [META_ARGV_DUMPABLE] = "COREDUMP_DUMPABLE=",
|
||||
[META_COMM] = "COREDUMP_COMM=",
|
||||
[META_EXE] = "COREDUMP_EXE=",
|
||||
[META_UNIT] = "COREDUMP_UNIT=",
|
||||
@@ -132,6 +134,7 @@ typedef struct Context {
|
||||
pid_t pid;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
+ unsigned dumpable;
|
||||
bool is_pid1;
|
||||
bool is_journald;
|
||||
} Context;
|
||||
@@ -447,14 +450,16 @@ static int grant_user_access(int core_fd, const Context *context) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
- /* We allow access if we got all the data and at_secure is not set and
|
||||
- * the uid/gid matches euid/egid. */
|
||||
+ /* We allow access if dumpable on the command line was exactly 1, we got all the data,
|
||||
+ * at_secure is not set, and the uid/gid match euid/egid. */
|
||||
bool ret =
|
||||
+ context->dumpable == 1 &&
|
||||
at_secure == 0 &&
|
||||
uid != UID_INVALID && euid != UID_INVALID && uid == euid &&
|
||||
gid != GID_INVALID && egid != GID_INVALID && gid == egid;
|
||||
- log_debug("Will %s access (uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
|
||||
+ log_debug("Will %s access (dumpable=%u uid="UID_FMT " euid="UID_FMT " gid="GID_FMT " egid="GID_FMT " at_secure=%s)",
|
||||
ret ? "permit" : "restrict",
|
||||
+ context->dumpable,
|
||||
uid, euid, gid, egid, yes_no(at_secure));
|
||||
return ret;
|
||||
}
|
||||
@@ -1082,6 +1087,15 @@ static int save_context(Context *context, const struct iovec_wrapper *iovw) {
|
||||
return log_error_errno(r, "Failed to parse GID \"%s\": %m", context->meta[META_ARGV_GID]);
|
||||
|
||||
|
||||
+ /* The value is set to contents of /proc/sys/fs/suid_dumpable, which we set to 2,
|
||||
+ * if the process is marked as not dumpable, see PR_SET_DUMPABLE(2const). */
|
||||
+ if (context->meta[META_ARGV_DUMPABLE]) {
|
||||
+ r = safe_atou_bounded(context->meta[META_ARGV_DUMPABLE], 0, 2, &context->dumpable);
|
||||
+ if (r < 0)
|
||||
+ return log_error_errno(r, "Failed to parse dumpable field \"%s\": %m", context->meta[META_ARGV_DUMPABLE]);
|
||||
+ assert(context->dumpable <= 2);
|
||||
+ }
|
||||
+
|
||||
unit = context->meta[META_UNIT];
|
||||
context->is_pid1 = streq(context->meta[META_ARGV_PID], "1") || streq_ptr(unit, SPECIAL_INIT_SCOPE);
|
||||
context->is_journald = streq_ptr(unit, SPECIAL_JOURNALD_SERVICE);
|
||||
diff --git a/sysctl.d/50-coredump.conf.in b/sysctl.d/50-coredump.conf.in
|
||||
index 5fb551a..9c10a89 100644
|
||||
--- a/sysctl.d/50-coredump.conf.in
|
||||
+++ b/sysctl.d/50-coredump.conf.in
|
||||
@@ -13,7 +13,7 @@
|
||||
# the core dump.
|
||||
#
|
||||
# See systemd-coredump(8) and core(5).
|
||||
-kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h
|
||||
+kernel.core_pattern=|{{ROOTLIBEXECDIR}}/systemd-coredump %P %u %g %s %t %c %h %d
|
||||
|
||||
# Allow 16 coredumps to be dispatched in parallel by the kernel.
|
||||
# We collect metadata from /proc/%P/, and thus need to make sure the crashed
|
||||
--
|
||||
2.47.1
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
Name: systemd
|
||||
Url: https://systemd.io
|
||||
Version: 252
|
||||
Release: 55%{?dist}.2.alma.2
|
||||
Release: 55%{?dist}.7.alma.1
|
||||
# For a breakdown of the licensing, see README
|
||||
License: LGPLv2+ and MIT and GPLv2+
|
||||
Summary: System and Service Manager
|
||||
@ -1333,12 +1333,29 @@ Patch1247: 1247-time-util-drop-unnecessary-assignment-of-timezone-na.patch
|
||||
Patch1248: 1248-time-util-make-parse_timestamp-use-the-RFC-822-ISO-8.patch
|
||||
Patch1249: 1249-time-util-fix-typo.patch
|
||||
Patch1250: 1250-ci-bump-the-tools-tree-to-F42.patch
|
||||
Patch1251: 1251-cryptsetup-Add-optional-support-for-linking-volume-k.patch
|
||||
Patch1252: 1252-cryptsetup-fix-typo.patch
|
||||
Patch1253: 1253-cryptsetup-HAVE_CRYPT_SET_KEYRING_TO_LINK-is-always-.patch
|
||||
Patch1254: 1254-basic-add-PIDFS-magic-31709.patch
|
||||
Patch1255: 1255-time-util-make-USEC_TIMESTAMP_FORMATTABLE_MAX-for-32.patch
|
||||
Patch1256: 1256-coredump-make-check-that-all-argv-meta-data-fields-a.patch
|
||||
Patch1257: 1257-coredump-restore-compatibility-with-older-patterns.patch
|
||||
Patch1258: 1258-coredump-use-d-in-kernel-core-pattern.patch
|
||||
Patch1259: 1259-pidref-add-structure-that-can-reference-a-pid-via-bo.patch
|
||||
Patch1260: 1260-fd-util-introduce-parse_fd.patch
|
||||
Patch1261: 1261-coredump-add-support-for-new-F-PIDFD-specifier.patch
|
||||
Patch1262: 1262-test-rename-TEST-53-ISSUE-16347-to-TEST-53-TIMER.patch
|
||||
Patch1263: 1263-test-restarting-elapsed-timer-shouldn-t-trigger-the-.patch
|
||||
Patch1264: 1264-test-check-the-next-elapse-timer-timestamp-after-des.patch
|
||||
Patch1265: 1265-timer-don-t-run-service-immediately-after-restart-of.patch
|
||||
Patch1266: 1266-test-store-and-compare-just-the-property-value.patch
|
||||
Patch1267: 1267-timer-rebase-the-next-elapse-timestamp-only-if-timer.patch
|
||||
Patch1268: 1268-coredump-handle-ENOBUFS-and-EMSGSIZE-the-same-way.patch
|
||||
Patch1269: 1269-timer-rebase-last_trigger-timestamp-if-needed.patch
|
||||
Patch1270: 1270-core-fix-array-size-in-unit_log_resources.patch
|
||||
|
||||
# AlmaLinux Patch
|
||||
Patch9000: 9000-core-reorder-systemd-arguments-on-reexec.patch
|
||||
Patch9001: CVE-2025-4598-0001-basic-parse-util-add-helper-to-parse-bounded-unsigne.patch
|
||||
Patch9002: CVE-2025-4598-0002-coredump-get-rid-of-_META_MANDATORY_MAX.patch
|
||||
Patch9003: CVE-2025-4598-0003-coredump-use-d-in-kernel-core-pattern.patch
|
||||
|
||||
# Downstream-only patches (9000–9999)
|
||||
|
||||
@ -2216,11 +2233,38 @@ systemd-hwdb update &>/dev/null || :
|
||||
%{_prefix}/lib/dracut/modules.d/70rhel-net-naming-sysattrs/*
|
||||
|
||||
%changelog
|
||||
* Tue Nov 11 2025 Andrew Lukoshko <alukoshko@almalinux.org> - 252-55.2.alma.2
|
||||
* Thu Dec 04 2025 Andrew Lukoshko <alukoshko@almalinux.org> - 252-55.7.alma.1
|
||||
- core: reorder systemd arguments on reexe
|
||||
- coredump: use %d in kernel core pattern - CVE-2025-4598
|
||||
- Debrand for AlmaLinux
|
||||
|
||||
* Mon Dec 01 2025 systemd maintenance team <systemd-maint@redhat.com> - 252-55.7
|
||||
- core: fix array size in unit_log_resources() (RHEL-132120)
|
||||
|
||||
* Mon Nov 24 2025 systemd maintenance team <systemd-maint@redhat.com> - 252-55.6
|
||||
- timer: rebase last_trigger timestamp if needed (RHEL-127022)
|
||||
|
||||
* Tue Nov 18 2025 systemd maintenance team <systemd-maint@redhat.com> - 252-55.5
|
||||
- test: rename TEST-53-ISSUE-16347 to TEST-53-TIMER (RHEL-127022)
|
||||
- test: restarting elapsed timer shouldn't trigger the corresponding service (RHEL-127022)
|
||||
- test: check the next elapse timer timestamp after deserialization (RHEL-127022)
|
||||
- timer: don't run service immediately after restart of a timer (RHEL-127022)
|
||||
- test: store and compare just the property value (RHEL-127022)
|
||||
- timer: rebase the next elapse timestamp only if timer didn't already run (RHEL-127022)
|
||||
- coredump: handle ENOBUFS and EMSGSIZE the same way (RHEL-126114)
|
||||
|
||||
* Thu Nov 06 2025 systemd maintenance team <systemd-maint@redhat.com> - 252-55.4
|
||||
- cryptsetup: Add optional support for linking volume key in keyring. (RHEL-118294)
|
||||
- cryptsetup: fix typo (RHEL-118294)
|
||||
- cryptsetup: HAVE_CRYPT_SET_KEYRING_TO_LINK is always defined (RHEL-118294)
|
||||
- basic: add PIDFS magic (#31709) (RHEL-118294)
|
||||
- time-util: make USEC_TIMESTAMP_FORMATTABLE_MAX for 32bit system off by one day (RHEL-118294)
|
||||
- coredump: make check that all argv[] meta data fields are passed strict (RHEL-104138)
|
||||
- coredump: restore compatibility with older patterns (RHEL-104138)
|
||||
- coredump: use %d in kernel core pattern (RHEL-104138)
|
||||
- pidref: add structure that can reference a pid via both pidfd and pid_t (RHEL-104138)
|
||||
- fd-util: introduce parse_fd() (RHEL-104138)
|
||||
- coredump: add support for new %F PIDFD specifier (RHEL-104138)
|
||||
|
||||
* Fri Sep 12 2025 systemd maintenance team <systemd-maint@redhat.com> - 252-55.2
|
||||
- Revert "test-time-util: disable failing tests" (RHEL-110954)
|
||||
- test: use get_timezones() to iterate all known timezones (RHEL-110954)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user