[LTP] [PATCH v3] coredump01: New core_pattern specifiers test
Cyril Hrubis
chrubis@suse.cz
Mon Aug 31 16:52:06 CEST 2026
Hi!
> 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>
> ---
> Add a simple coredump01 test that simulate a coredump and verify that it
> actually works. This is a small coverage that is missing inside LTP
> testing suite.
> ---
> Changes in v3:
> - check for CONFIG_COREDUMP=y
> - Link to v2: https://lore.kernel.org/20260824-coredump-v2-1-42428acf5216@suse.com
>
> Changes in v2:
> - fix CI agent spotted errors
> - Link to v1: https://lore.kernel.org/20260820-coredump-v1-1-7ce51603cefb@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 | 225 ++++++++++++++++++++++++++
> testcases/kernel/coredump/coredump01_helper.c | 66 ++++++++
> 6 files changed, 302 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..96a7252da
> --- /dev/null
> +++ b/testcases/kernel/coredump/coredump01.c
> @@ -0,0 +1,225 @@
> +// 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 <sys/wait.h>
> +#include <sys/stat.h>
> +#include <fcntl.h>
> +
> +#include "tst_test.h"
> +#include "tst_kconfig.h"
> +#include "lapi/prctl.h"
> +
> +#define PATTERN_MAX 128
> +#define HELPER "coredump01_helper"
> +#define HELPER_TIMEOUT 10
> +
> +static char helper_dir[PATH_MAX];
> +static char cwd[PATH_MAX];
> +
> +static void set_pattern(const char *fmt, ...)
> +{
> + char pattern[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);
> +
> + tst_res(TINFO, "core_pattern is '%s'", pattern);
Shouldn't we read the pattern back and make sure it's the same?
> +}
> +
> +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 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);
> +}
> +
> +/* PID 2 is kthreadd only if we can see the initial PID namespace */
> +static int have_init_pid_ns(void)
> +{
> + char comm[16] = {0};
> + int fd, len;
> +
> + fd = open("/proc/2/comm", O_RDONLY);
> + if (fd < 0)
> + return 0;
> +
> + len = SAFE_READ(0, fd, comm, sizeof(comm) - 1);
> + SAFE_CLOSE(fd);
> +
> + return len > 0 && !strncmp(comm, "kthreadd\n", 9);
> +}
> +
> +/*
> + * The kernel resolves @path the way /proc/2/root (kthreadd lives in the
> + * initial namespaces) resolves it.
> + */
> +static int same_in_init_mnt_ns(const char *path)
> +{
> + char init_path[PATH_MAX + 32];
> + struct stat our_st, init_st;
> +
> + SAFE_STAT(path, &our_st);
> +
> + snprintf(init_path, sizeof(init_path), "/proc/2/root%s", path);
> +
> + if (stat(init_path, &init_st))
> + return 0;
> +
> + return init_st.st_dev == our_st.st_dev && init_st.st_ino == our_st.st_ino;
> +}
> +
> +static void verify_pipe_pattern(void)
> +{
> + char res[PATH_MAX + 32], exe[PATH_MAX], helper[PATH_MAX + 32];
> + struct tst_kconfig_var kconfig = TST_KCONFIG_INIT("CONFIG_STATIC_USERMODEHELPER");
> + int pid_seen, sig_seen, elf;
> + long long bytes;
> + pid_t pid;
> +
> + tst_kconfig_read(&kconfig, 1);
> + if (kconfig.choice == 'y') {
> + tst_res(TCONF, "CONFIG_STATIC_USERMODEHELPER is enabled, skipping pipe core_pattern");
> + return;
> + }
This should be read once in the test setup so that we do not parse
.config on each -i iteration.
> + snprintf(helper, sizeof(helper), "%s/%s", helper_dir, HELPER);
> +
> + /*
> + * The kernel always execs the pipe helper in the initial mount
> + * namespace (core(5)), no matter where the crashing process lives.
> + * Skip unless both the helper and the result directory are the very
> + * same objects there, otherwise the kernel would not find them and
> + * the test would fail spuriously, e.g. inside a container.
> + */
> + if (!have_init_pid_ns() || !same_in_init_mnt_ns(helper) ||
> + !same_in_init_mnt_ns(cwd)) {
> + tst_res(TCONF, "Pipe helper or result dir not visible in the initial mount namespace");
> + return;
> + }
I'm not sure if these extensive tests aren't out of scope for the test.
Is it common for tests to be executed in a mount namespace?
> + tst_res(TINFO, "Testing pipe core_pattern");
> +
> + set_pattern("|%s/%s %%e %%p %%s %s/res.%%p", helper_dir, HELPER, 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",
> + exe, &pid_seen, &sig_seen, &bytes, &elf);
> +
> + 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);
> +}
> +
> +static void run(unsigned int n)
> +{
> + if (n)
> + verify_pipe_pattern();
> + else
> + verify_file_pattern();
> +}
> +
> +static void setup(void)
> +{
> + struct rlimit rl = {RLIM_INFINITY, RLIM_INFINITY};
> + ssize_t len;
> +
> + SAFE_SETRLIMIT(RLIMIT_CORE, &rl);
> +
> + SAFE_PRCTL(PR_SET_DUMPABLE, 1, 0, 0, 0);
> +
> + SAFE_GETCWD(cwd, sizeof(cwd));
> +
> + len = SAFE_READLINK("/proc/self/exe", helper_dir, sizeof(helper_dir) - 1);
> + helper_dir[len] = '\0';
> + *strrchr(helper_dir, '/') = '\0';
This looks like a hack. We do have tst_get_path() that could be used to
resolve the helper absolute path instead.
> +}
> +
> +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..41091b428
> --- /dev/null
> +++ b/testcases/kernel/coredump/coredump01_helper.c
> @@ -0,0 +1,66 @@
> +// 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 <fcntl.h>
> +#include <limits.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <unistd.h>
> +
> +int main(int argc, char *argv[])
> +{
> + char buf[4096], tmp[PATH_MAX], magic[4];
> + long long bytes = 0;
> + ssize_t rval, i;
> + int fd, elf = 0;
> +
> + if (argc < 5)
> + return 1;
> +
> + /*
> + * A core dump embeds the ELF images it has mapped, so the magic is
> + * only meaningful at the very beginning of the stream. Collect it
> + * across reads, since a pipe read can return less than 4 bytes.
> + */
> + while ((rval = read(STDIN_FILENO, buf, sizeof(buf))) > 0) {
> + for (i = 0; i < rval && bytes + i < 4; i++)
> + magic[bytes + i] = buf[i];
> +
> + bytes += rval;
> + }
> +
> + if (rval < 0)
> + return 1;
> +
> + if (bytes >= 4 && !memcmp(magic, "\177ELF", 4))
> + elf = 1;
> +
> + 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\n",
> + argv[1], argv[2], argv[3], bytes, elf);
> +
> + close(fd);
> +
> + return rename(tmp, argv[4]) ? 1 : 0;
> +}
>
> ---
> base-commit: e62b1a4731f9c458d65c2659d2f8bb58b93e6d6d
> change-id: 20260820-coredump-22fc731bde08
>
> Best regards,
> --
> Andrea Cervesato <andrea.cervesato@suse.com>
>
>
> --
> Mailing list info: https://lists.linux.it/listinfo/ltp
--
Cyril Hrubis
chrubis@suse.cz
More information about the ltp
mailing list