* [LTP] [PATCH v7 1/2] coredump01: New core_pattern specifiers test
2026-09-04 11:03 [LTP] [PATCH v7 0/2] coredump testing suite Andrea Cervesato
@ 2026-09-04 11:03 ` Andrea Cervesato
2026-09-04 12:20 ` [LTP] " linuxtestproject.agent
2026-09-08 6:18 ` [LTP] [PATCH v7 1/2] " Li Wang
2026-09-04 11:03 ` [LTP] [PATCH v7 2/2] coredump02: Verify ELF structure and notes Andrea Cervesato
1 sibling, 2 replies; 8+ messages in thread
From: Andrea Cervesato @ 2026-09-04 11:03 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
LTP has no coverage for the core_pattern specifiers expansion, neither
for the pipe flavor of core_pattern, where the kernel spawns a user
space helper and writes the core dump into its standard input.
The new test verifies that %e, %p and %s are expanded both in the core
file name and in the arguments of a piped helper, checking also that
the helper really receives an ELF core dump.
Root is required to rewrite the system wide core_pattern, which is
restored by the test library on all exit paths.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/kernel_misc | 1 +
testcases/kernel/Makefile | 1 +
testcases/kernel/coredump/.gitignore | 2 +
testcases/kernel/coredump/Makefile | 7 ++
testcases/kernel/coredump/coredump01.c | 158 ++++++++++++++++++++++++++
testcases/kernel/coredump/coredump01_helper.c | 73 ++++++++++++
testcases/kernel/coredump/coredump_common.h | 69 +++++++++++
7 files changed, 311 insertions(+)
diff --git a/runtest/kernel_misc b/runtest/kernel_misc
index cc3562cb7..ecf9ee2a2 100644
--- a/runtest/kernel_misc
+++ b/runtest/kernel_misc
@@ -17,3 +17,4 @@ zram02 zram02.sh
zram03 zram03
umip_basic_test umip_basic_test
aslr01 aslr01
+coredump01 coredump01
diff --git a/testcases/kernel/Makefile b/testcases/kernel/Makefile
index ac816e4e8..6b303c14b 100644
--- a/testcases/kernel/Makefile
+++ b/testcases/kernel/Makefile
@@ -19,6 +19,7 @@ SUBDIRS += lib
SUBDIRS += connectors \
containers \
controllers \
+ coredump \
crypto \
device-drivers \
firmware \
diff --git a/testcases/kernel/coredump/.gitignore b/testcases/kernel/coredump/.gitignore
new file mode 100644
index 000000000..cd0b51700
--- /dev/null
+++ b/testcases/kernel/coredump/.gitignore
@@ -0,0 +1,2 @@
+/coredump01
+/coredump01_helper
diff --git a/testcases/kernel/coredump/Makefile b/testcases/kernel/coredump/Makefile
new file mode 100644
index 000000000..951a3e976
--- /dev/null
+++ b/testcases/kernel/coredump/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (c) 2026 Linux Test Project
+
+top_srcdir ?= ../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
diff --git a/testcases/kernel/coredump/coredump01.c b/testcases/kernel/coredump/coredump01.c
new file mode 100644
index 000000000..ad690e95c
--- /dev/null
+++ b/testcases/kernel/coredump/coredump01.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Linux Test Project
+ */
+
+/*\
+ * Verify that the kernel expands the :manpage:`core(5)` specifiers %e
+ * (executable name), %p (PID) and %s (signal number) when it creates a
+ * core dump.
+ *
+ * Both flavors of ``/proc/sys/kernel/core_pattern`` are tested:
+ *
+ * - a plain file name, where the specifiers become part of the core file
+ * name
+ * - a pipe to a user space helper, where the specifiers are expanded in
+ * the helper arguments and the core dump is written to the helper
+ * standard input
+ *
+ * The test needs root because it rewrites the system wide core_pattern.
+ * The original value is saved and restored by the test library on all
+ * exit paths.
+ *
+ * [Algorithm]
+ *
+ * - Point core_pattern into the test temporary directory
+ * - Fork a child which aborts itself to produce a core dump
+ * - For the file pattern check that ``core.<exe>.<pid>.<signal>`` was created
+ * - For the pipe pattern check the arguments the helper was called with
+ * and that it received an ELF core dump on its standard input
+ */
+
+#include "coredump_common.h"
+#include "tst_kconfig.h"
+
+#define HELPER "coredump01_helper"
+#define HELPER_TIMEOUT 10
+
+static char helper_path[PATH_MAX];
+static int static_usermodehelper;
+
+/*
+ * e_ident[] and e_type live at the same file offset in ELF32 and ELF64,
+ * so we can validate both without picking a class.
+ */
+static void verify_elf_core(const char *path)
+{
+ unsigned char hdr[EI_NIDENT + sizeof(Elf32_Half)];
+ Elf32_Half e_type;
+ int fd;
+
+ fd = SAFE_OPEN(path, O_RDONLY);
+ SAFE_READ(1, fd, hdr, sizeof(hdr));
+ SAFE_CLOSE(fd);
+
+ memcpy(&e_type, hdr + EI_NIDENT, sizeof(e_type));
+
+ TST_EXP_EXPR(!memcmp(hdr, ELFMAG, SELFMAG), "%s starts with ELF magic", path);
+ TST_EXP_EQ_LI(e_type, ET_CORE);
+}
+
+static void verify_file_pattern(void)
+{
+ char dump[PATH_MAX + 32];
+ pid_t pid;
+
+ tst_res(TINFO, "Testing file core_pattern");
+
+ set_pattern("%s/core.%%e.%%p.%%s", cwd);
+
+ pid = crash_child();
+
+ snprintf(dump, sizeof(dump), "%s/core.coredump01.%d.%d", cwd, pid, SIGABRT);
+
+ TST_EXP_PASS(access(dump, F_OK), "core.%%e.%%p.%%s expanded to core.coredump01.%d.%d",
+ pid, SIGABRT);
+ if (!TST_PASS)
+ return;
+
+ verify_elf_core(dump);
+}
+
+static void verify_pipe_pattern(void)
+{
+ char res[PATH_MAX + 32], exe[PATH_MAX];
+ int pid_seen, sig_seen, elf, et_core;
+ long long bytes;
+ pid_t pid;
+
+ if (static_usermodehelper) {
+ tst_res(TCONF, "CONFIG_STATIC_USERMODEHELPER is enabled, skipping pipe core_pattern");
+ return;
+ }
+
+ tst_res(TINFO, "Testing pipe core_pattern");
+
+ set_pattern("|%s %%e %%p %%s %s/res.%%p", helper_path, cwd);
+
+ pid = crash_child();
+
+ snprintf(res, sizeof(res), "%s/res.%d", cwd, pid);
+
+ /* the kernel spawns the helper asynchronously */
+ if (TST_RETRY_FN_EXP_BACKOFF(access(res, F_OK), TST_RETVAL_EQ0, HELPER_TIMEOUT)) {
+ tst_res(TFAIL, "%s did not report any core dump", HELPER);
+ return;
+ }
+
+ SAFE_FILE_SCANF(res, "exe=%15s pid=%d sig=%d bytes=%lld elf=%d et_core=%d",
+ exe, &pid_seen, &sig_seen, &bytes, &elf, &et_core);
+
+ TST_EXP_EQ_STR(exe, "coredump01");
+ TST_EXP_EQ_LI(pid_seen, pid);
+ TST_EXP_EQ_LI(sig_seen, SIGABRT);
+ TST_EXP_EXPR(elf && bytes > 0, "%s read %lli bytes of ELF core dump", HELPER, bytes);
+ TST_EXP_EQ_LI(et_core, 1);
+}
+
+static void run(unsigned int n)
+{
+ if (n)
+ verify_pipe_pattern();
+ else
+ verify_file_pattern();
+}
+
+static void setup(void)
+{
+ char path[PATH_MAX];
+ struct tst_kconfig_var kconfig = TST_KCONFIG_INIT("CONFIG_STATIC_USERMODEHELPER");
+
+ coredump_setup();
+
+ tst_kconfig_read(&kconfig, 1);
+ static_usermodehelper = (kconfig.choice == 'y');
+
+ if (tst_get_path(HELPER, path, sizeof(path)))
+ tst_brk(TCONF, "'%s' not found in $PATH", HELPER);
+
+ if (!realpath(path, helper_path))
+ tst_brk(TBROK | TERRNO, "realpath(%s) failed", path);
+}
+
+static struct tst_test test = {
+ .test = run,
+ .tcnt = 2,
+ .setup = setup,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+ .needs_kconfigs = (const char* []) {
+ "CONFIG_COREDUMP=y",
+ NULL,
+ },
+ .save_restore = (const struct tst_path_val[]) {
+ {PATH_KERN_CORE_PATTERN, NULL, TST_SR_TCONF},
+ {}
+ },
+};
diff --git a/testcases/kernel/coredump/coredump01_helper.c b/testcases/kernel/coredump/coredump01_helper.c
new file mode 100644
index 000000000..b454de476
--- /dev/null
+++ b/testcases/kernel/coredump/coredump01_helper.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Linux Test Project
+ */
+
+/*
+ * Core dump collector for the piped core_pattern tested by coredump01.
+ *
+ * Avoiding the LTP API here is correct, since the kernel spawns the helper
+ * through ``call_usermodehelper()`` without the LTP IPC environment.
+ *
+ * The helper is called as:
+ *
+ * coredump01_helper <exe> <pid> <signal> <result file>
+ *
+ * and stores what it received in the result file, which is published with
+ * :manpage:`rename()` so that the polling test never reads a partial line.
+ */
+
+#include <elf.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+int main(int argc, char *argv[])
+{
+ unsigned char buf[4096], hdr[EI_NIDENT + sizeof(Elf32_Half)];
+ char tmp[PATH_MAX];
+ unsigned int hdr_len = 0;
+ long long bytes = 0;
+ ssize_t rval, i;
+ int fd, elf = 0, et_core = 0;
+ Elf32_Half e_type;
+
+ if (argc < 5)
+ return 1;
+
+ /*
+ * The ELF header is only meaningful at the very beginning of the
+ * stream. Collect enough bytes to read e_ident and e_type.
+ */
+ while ((rval = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
+ for (i = 0; i < rval && hdr_len < sizeof(hdr); i++)
+ hdr[hdr_len++] = buf[i];
+
+ bytes += rval;
+ }
+
+ if (rval < 0)
+ return 1;
+
+ if (hdr_len >= sizeof(hdr)) {
+ if (!memcmp(hdr, ELFMAG, SELFMAG))
+ elf = 1;
+ memcpy(&e_type, hdr + EI_NIDENT, sizeof(e_type));
+ et_core = (e_type == ET_CORE);
+ }
+
+ snprintf(tmp, sizeof(tmp), "%s.tmp", argv[4]);
+
+ fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ return 1;
+
+ dprintf(fd, "exe=%s pid=%s sig=%s bytes=%lld elf=%d et_core=%d\n",
+ argv[1], argv[2], argv[3], bytes, elf, et_core);
+
+ close(fd);
+
+ return rename(tmp, argv[4]) ? 1 : 0;
+}
diff --git a/testcases/kernel/coredump/coredump_common.h b/testcases/kernel/coredump/coredump_common.h
new file mode 100644
index 000000000..7c04a659c
--- /dev/null
+++ b/testcases/kernel/coredump/coredump_common.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Linux Test Project
+ */
+
+#ifndef COREDUMP_COMMON_H
+#define COREDUMP_COMMON_H
+
+#include <elf.h>
+#include <sys/wait.h>
+
+#include "tst_test.h"
+#include "lapi/prctl.h"
+
+#define PATTERN_MAX 128
+
+static char cwd[PATH_MAX];
+
+static void set_pattern(const char *fmt, ...)
+{
+ char pattern[PATTERN_MAX];
+ char readback[PATTERN_MAX];
+ va_list va;
+ int len;
+
+ va_start(va, fmt);
+ len = vsnprintf(pattern, sizeof(pattern), fmt, va);
+ va_end(va);
+
+ if (len >= PATTERN_MAX)
+ tst_brk(TCONF, "core_pattern does not fit into %i bytes", PATTERN_MAX - 1);
+
+ SAFE_FILE_PRINTF(PATH_KERN_CORE_PATTERN, "%s", pattern);
+ SAFE_FILE_LINES_SCANF(PATH_KERN_CORE_PATTERN, "%127[^\n]", readback);
+
+ if (strcmp(pattern, readback))
+ tst_brk(TBROK, "core_pattern readback mismatch: wrote '%s', read '%s'",
+ pattern, readback);
+
+ tst_res(TINFO, "core_pattern is '%s'", pattern);
+}
+
+static pid_t crash_child(void)
+{
+ int status;
+ pid_t pid;
+
+ pid = SAFE_FORK();
+ if (!pid)
+ abort();
+
+ SAFE_WAITPID(pid, &status, 0);
+
+ if (!WIFSIGNALED(status) || !WCOREDUMP(status))
+ tst_brk(TFAIL, "Child did not dump core");
+
+ return pid;
+}
+
+static void coredump_setup(void)
+{
+ struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
+
+ SAFE_SETRLIMIT(RLIMIT_CORE, &rl);
+ SAFE_PRCTL(PR_SET_DUMPABLE, 1, 0, 0, 0);
+ SAFE_GETCWD(cwd, sizeof(cwd));
+}
+
+#endif /* COREDUMP_COMMON_H */
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread* [LTP] [PATCH v7 2/2] coredump02: Verify ELF structure and notes
2026-09-04 11:03 [LTP] [PATCH v7 0/2] coredump testing suite Andrea Cervesato
2026-09-04 11:03 ` [LTP] [PATCH v7 1/2] coredump01: New core_pattern specifiers test Andrea Cervesato
@ 2026-09-04 11:03 ` Andrea Cervesato
2026-09-08 6:20 ` Li Wang
1 sibling, 1 reply; 8+ messages in thread
From: Andrea Cervesato @ 2026-09-04 11:03 UTC (permalink / raw)
To: Linux Test Project
From: Andrea Cervesato <andrea.cervesato@suse.com>
LTP currently only tests core_pattern specifier expansion and basic
ELF magic in coredump01, leaving the internal ELF structure and notes
generated by the kernel unverified.
Add a test to verify that the kernel produces a valid ET_CORE ELF file
with the expected PT_NOTE and PT_LOAD segments, and that the
NT_PRPSINFO and NT_PRSTATUS notes contain the expected process name,
PID, and terminating signal.
Root is required to set the system-wide core_pattern into the test's
temporary directory.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
runtest/kernel_misc | 1 +
testcases/kernel/coredump/.gitignore | 1 +
testcases/kernel/coredump/coredump02.c | 198 +++++++++++++++++++++++++++++++++
3 files changed, 200 insertions(+)
diff --git a/runtest/kernel_misc b/runtest/kernel_misc
index ecf9ee2a2..3311e1929 100644
--- a/runtest/kernel_misc
+++ b/runtest/kernel_misc
@@ -18,3 +18,4 @@ zram03 zram03
umip_basic_test umip_basic_test
aslr01 aslr01
coredump01 coredump01
+coredump02 coredump02
diff --git a/testcases/kernel/coredump/.gitignore b/testcases/kernel/coredump/.gitignore
index cd0b51700..e241a112e 100644
--- a/testcases/kernel/coredump/.gitignore
+++ b/testcases/kernel/coredump/.gitignore
@@ -1,2 +1,3 @@
/coredump01
/coredump01_helper
+/coredump02
diff --git a/testcases/kernel/coredump/coredump02.c b/testcases/kernel/coredump/coredump02.c
new file mode 100644
index 000000000..2980bc8ca
--- /dev/null
+++ b/testcases/kernel/coredump/coredump02.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Linux Test Project
+ */
+
+/*\
+ * Verify the ELF structure and note content of a kernel-generated core
+ * dump.
+ *
+ * A core dump written by the kernel is an ELF file of type ET_CORE. It
+ * contains:
+ *
+ * - one PT_NOTE program header holding process metadata
+ * - one or more PT_LOAD program headers for the mapped memory segments
+ *
+ * The PT_NOTE segment carries a stream of notes. Two of them describe
+ * the crashed process:
+ *
+ * - NT_PRPSINFO, whose descriptor is a struct elf_prpsinfo. The pr_fname
+ * field holds the executable name.
+ * - NT_PRSTATUS, whose descriptor is a struct elf_prstatus. The pr_pid
+ * field holds the PID and pr_cursig holds the terminating signal.
+ *
+ * The crashed child is a fork of the test binary, so the core dump has
+ * the same ELF class as the test. ElfW() is therefore safe here.
+ *
+ * The test needs root because it rewrites the system-wide core_pattern.
+ * The original value is saved and restored by the test library on all
+ * exit paths.
+ *
+ * [Algorithm]
+ *
+ * - Point core_pattern into the test temporary directory
+ * - Fork a child which aborts itself to produce a core dump
+ * - Read the core file into memory and parse the ELF header
+ * - Walk the program headers and verify PT_NOTE and PT_LOAD are present
+ * - Walk the notes in every PT_NOTE segment
+ * - Check that NT_PRPSINFO carries the expected executable name
+ * - Check that NT_PRSTATUS carries the expected PID and signal
+ */
+
+#include <link.h>
+#include <sys/procfs.h>
+
+#include "coredump_common.h"
+
+#define NOTE_ALIGN(x) (((x) + 3) & ~3U)
+
+static char *core_buf;
+static size_t core_len;
+static int prpsinfo_seen, prstatus_seen;
+
+static void load_core(const char *path)
+{
+ struct stat st;
+ int fd;
+
+ SAFE_STAT(path, &st);
+ if (st.st_size <= 0)
+ tst_brk(TFAIL, "core file %s is empty", path);
+
+ core_len = st.st_size;
+ core_buf = SAFE_MALLOC(core_len);
+
+ fd = SAFE_OPEN(path, O_RDONLY);
+ SAFE_READ(1, fd, core_buf, core_len);
+ SAFE_CLOSE(fd);
+}
+
+static void unload_core(void)
+{
+ free(core_buf);
+ core_buf = NULL;
+ core_len = 0;
+}
+
+static const void *core_at(size_t off, size_t need)
+{
+ if (off > core_len || need > core_len - off)
+ tst_brk(TFAIL, "core file truncated at %zu (need %zu, have %zu)",
+ off, need, core_len);
+
+ return core_buf + off;
+}
+
+static void handle_note(uint32_t type, const void *desc, size_t descsz, pid_t pid)
+{
+ if (type == NT_PRPSINFO && descsz >= sizeof(struct elf_prpsinfo)) {
+ struct elf_prpsinfo info;
+ char name[sizeof(info.pr_fname) + 1] = {0};
+
+ memcpy(&info, desc, sizeof(info));
+ memcpy(name, info.pr_fname, sizeof(info.pr_fname));
+
+ TST_EXP_EQ_STR(name, "coredump02");
+
+ prpsinfo_seen = 1;
+ } else if (type == NT_PRSTATUS && descsz >= sizeof(struct elf_prstatus)) {
+ struct elf_prstatus st;
+
+ memcpy(&st, desc, sizeof(st));
+
+ TST_EXP_EQ_LI(st.pr_pid, pid);
+ TST_EXP_EQ_LI(st.pr_cursig, SIGABRT);
+
+ prstatus_seen = 1;
+ }
+}
+
+static void walk_notes(size_t off, size_t size, pid_t pid)
+{
+ size_t pos = 0;
+
+ while (pos + sizeof(ElfW(Nhdr)) <= size) {
+ const ElfW(Nhdr) *nh = core_at(off + pos, sizeof(*nh));
+ size_t np = NOTE_ALIGN(nh->n_namesz);
+ size_t dp = NOTE_ALIGN(nh->n_descsz);
+ size_t total = sizeof(*nh) + np + dp;
+
+ if (pos + total > size)
+ tst_brk(TFAIL, "note extends past PT_NOTE (pos=%zu total=%zu size=%zu)",
+ pos, total, size);
+
+ handle_note(nh->n_type,
+ core_at(off + pos + sizeof(*nh) + np, nh->n_descsz),
+ nh->n_descsz, pid);
+
+ pos += total;
+ }
+}
+
+static void run(void)
+{
+ char dump[PATH_MAX + 32];
+ int pt_note_cnt = 0, have_load = 0;
+ const ElfW(Ehdr) *eh;
+ const ElfW(Phdr) *ph;
+ pid_t pid;
+ size_t i;
+
+ prpsinfo_seen = prstatus_seen = 0;
+
+ set_pattern("%s/core.%%p", cwd);
+
+ pid = crash_child();
+
+ snprintf(dump, sizeof(dump), "%s/core.%d", cwd, pid);
+ load_core(dump);
+
+ eh = core_at(0, sizeof(*eh));
+
+ TST_EXP_EXPR(!memcmp(eh->e_ident, ELFMAG, SELFMAG), "core has ELF magic");
+ TST_EXP_EQ_LI(eh->e_type, ET_CORE);
+
+ if (!eh->e_phnum)
+ tst_brk(TFAIL, "core has no program headers");
+
+ ph = core_at(eh->e_phoff, (size_t)eh->e_phnum * sizeof(*ph));
+
+ for (i = 0; i < eh->e_phnum; i++) {
+ if (ph[i].p_type == PT_NOTE) {
+ pt_note_cnt++;
+ walk_notes(ph[i].p_offset, ph[i].p_filesz, pid);
+ } else if (ph[i].p_type == PT_LOAD) {
+ have_load = 1;
+ }
+ }
+
+ TST_EXP_EQ_LI(pt_note_cnt, 1);
+ TST_EXP_EQ_LI(have_load, 1);
+ TST_EXP_EQ_LI(prpsinfo_seen, 1);
+ TST_EXP_EQ_LI(prstatus_seen, 1);
+
+ SAFE_UNLINK(dump);
+ unload_core();
+}
+
+static void cleanup(void)
+{
+ unload_core();
+}
+
+static struct tst_test test = {
+ .test_all = run,
+ .setup = coredump_setup,
+ .cleanup = cleanup,
+ .needs_root = 1,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+ .needs_kconfigs = (const char *[]) {
+ "CONFIG_COREDUMP=y",
+ NULL,
+ },
+ .save_restore = (const struct tst_path_val[]) {
+ {PATH_KERN_CORE_PATTERN, NULL, TST_SR_TCONF},
+ {}
+ },
+};
--
2.51.0
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 8+ messages in thread