Re: [PATCH v7 9/9] selftests/coredump: add tests for AF_UNIX coredumps

Alexander Mikhalitsyn
2025-05-15T14:37:53Z

Am Do., 15. Mai 2025 um 00:04 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Add a simple test for generating coredumps via AF_UNIX sockets.
>
> Signed-off-by: Christian Brauner <brauner@kernel.org>

Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@canonical.com>

> ---
> tools/testing/selftests/coredump/stackdump_test.c | 514 +++++++++++++++++++++-
> 1 file changed, 513 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/coredump/stackdump_test.c b/tools/testing/selftests/coredump/stackdump_test.c
> index fe3c728cd6be..42ddcf0bdaf2 100644
> --- a/tools/testing/selftests/coredump/stackdump_test.c
> +++ b/tools/testing/selftests/coredump/stackdump_test.c
> @@ -1,14 +1,20 @@
> // SPDX-License-Identifier: GPL-2.0
>
> #include <fcntl.h>
> +#include <inttypes.h>
> #include <libgen.h>
> #include <linux/limits.h>
> #include <pthread.h>
> #include <string.h>
> +#include <sys/mount.h>
> #include <sys/resource.h>
> +#include <sys/stat.h>
> +#include <sys/socket.h>
> +#include <sys/un.h>
> #include <unistd.h>
>
> #include "../kselftest_harness.h"
> +#include "../pidfd/pidfd.h"
>
> #define STACKDUMP_FILE "stack_values"
> #define STACKDUMP_SCRIPT "stackdump"
> @@ -35,6 +41,7 @@ static void crashing_child(void)
> FIXTURE(coredump)
> {
> char original_core_pattern[256];
> + pid_t pid_coredump_server;
> };
>
> FIXTURE_SETUP(coredump)
> @@ -44,6 +51,7 @@ FIXTURE_SETUP(coredump)
> char *dir;
> int ret;
>
> + self->pid_coredump_server = -ESRCH;
> file = fopen("/proc/sys/kernel/core_pattern", "r");
> ASSERT_NE(NULL, file);
>
> @@ -61,10 +69,17 @@ FIXTURE_TEARDOWN(coredump)
> {
> const char *reason;
> FILE *file;
> - int ret;
> + int ret, status;
>
> unlink(STACKDUMP_FILE);
>
> + if (self->pid_coredump_server > 0) {
> + kill(self->pid_coredump_server, SIGTERM);
> + waitpid(self->pid_coredump_server, &status, 0);
> + }
> + unlink("/tmp/coredump.file");
> + unlink("/tmp/coredump.socket");
> +
> file = fopen("/proc/sys/kernel/core_pattern", "w");
> if (!file) {
> reason = "Unable to open core_pattern";
> @@ -154,4 +169,501 @@ TEST_F_TIMEOUT(coredump, stackdump, 120)
> fclose(file);
> }
>
> +TEST_F(coredump, socket)
> +{
> + int fd, pidfd, ret, status;
> + FILE *file;
> + pid_t pid, pid_coredump_server;
> + struct stat st;
> + char core_file[PATH_MAX];
> + struct pidfd_info info = {};
> + int ipc_sockets[2];
> + char c;
> + const struct sockaddr_un coredump_sk = {
> + .sun_family = AF_UNIX,
> + .sun_path = "/tmp/coredump.socket",
> + };
> + size_t coredump_sk_len = offsetof(struct sockaddr_un, sun_path) +
> + sizeof("/tmp/coredump.socket");
> +
> + ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
> + ASSERT_EQ(ret, 0);
> +
> + file = fopen("/proc/sys/kernel/core_pattern", "w");
> + ASSERT_NE(file, NULL);
> +
> + ret = fprintf(file, "@/tmp/coredump.socket");
> + ASSERT_EQ(ret, strlen("@/tmp/coredump.socket"));
> + ASSERT_EQ(fclose(file), 0);
> +
> + pid_coredump_server = fork();
> + ASSERT_GE(pid_coredump_server, 0);
> + if (pid_coredump_server == 0) {
> + int fd_server, fd_coredump, fd_peer_pidfd, fd_core_file;
> + __u64 peer_cookie;
> + socklen_t fd_peer_pidfd_len, peer_cookie_len;
> +
> + close(ipc_sockets[0]);
> +
> + fd_server = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
> + if (fd_server < 0)
> + _exit(EXIT_FAILURE);
> +
> + ret = bind(fd_server, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to bind coredump socket\n");
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + ret = listen(fd_server, 1);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to listen on coredump socket\n");
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + close(ipc_sockets[1]);
> +
> + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
> + if (fd_coredump < 0) {
> + fprintf(stderr, "Failed to accept coredump socket connection\n");
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + peer_cookie_len = sizeof(peer_cookie);
> + ret = getsockopt(fd_coredump, SOL_SOCKET, SO_COOKIE,
> + &peer_cookie, &peer_cookie_len);
> + if (ret < 0) {
> + fprintf(stderr, "%m - Failed to retrieve cookie for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + fd_peer_pidfd_len = sizeof(fd_peer_pidfd);
> + ret = getsockopt(fd_coredump, SOL_SOCKET, SO_PEERPIDFD,
> + &fd_peer_pidfd, &fd_peer_pidfd_len);
> + if (ret < 0) {
> + fprintf(stderr, "%m - Failed to retrieve peer pidfd for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + memset(&info, 0, sizeof(info));
> + info.mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP;
> + ret = ioctl(fd_peer_pidfd, PIDFD_GET_INFO, &info);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to retrieve pidfd info from peer pidfd for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (!(info.mask & PIDFD_INFO_COREDUMP)) {
> + fprintf(stderr, "Missing coredump information from coredumping task\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
> + fprintf(stderr, "Received connection from non-coredumping task\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (!info.coredump_cookie) {
> + fprintf(stderr, "Missing coredump cookie\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (info.coredump_cookie != peer_cookie) {
> + fprintf(stderr, "Mismatching coredump cookies\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + fd_core_file = creat("/tmp/coredump.file", 0644);
> + if (fd_core_file < 0) {
> + fprintf(stderr, "Failed to create coredump file\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + for (;;) {
> + char buffer[4096];
> + ssize_t bytes_read, bytes_write;
> +
> + bytes_read = read(fd_coredump, buffer, sizeof(buffer));
> + if (bytes_read < 0) {
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + close(fd_core_file);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (bytes_read == 0)
> + break;
> +
> + bytes_write = write(fd_core_file, buffer, bytes_read);
> + if (bytes_read != bytes_write) {
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + close(fd_core_file);
> + _exit(EXIT_FAILURE);
> + }
> + }
> +
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + close(fd_core_file);
> + _exit(EXIT_SUCCESS);
> + }
> + self->pid_coredump_server = pid_coredump_server;
> +
> + EXPECT_EQ(close(ipc_sockets[1]), 0);
> + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
> + EXPECT_EQ(close(ipc_sockets[0]), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0)
> + crashing_child();
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFSIGNALED(status));
> + ASSERT_TRUE(WCOREDUMP(status));
> +
> + info.mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP;
> + ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
> + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
> + ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
> +
> + waitpid(pid_coredump_server, &status, 0);
> + self->pid_coredump_server = -ESRCH;
> + ASSERT_TRUE(WIFEXITED(status));
> + ASSERT_EQ(WEXITSTATUS(status), 0);
> +
> + ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
> + ASSERT_GT(st.st_size, 0);
> + /*
> + * We should somehow validate the produced core file.
> + * For now just allow for visual inspection
> + */
> + system("file /tmp/coredump.file");
> +}
> +
> +TEST_F(coredump, socket_detect_userspace_client)
> +{
> + int fd, pidfd, ret, status;
> + FILE *file;
> + pid_t pid, pid_coredump_server;
> + struct stat st;
> + char core_file[PATH_MAX];
> + struct pidfd_info info = {};
> + int ipc_sockets[2];
> + char c;
> + const struct sockaddr_un coredump_sk = {
> + .sun_family = AF_UNIX,
> + .sun_path = "/tmp/coredump.socket",
> + };
> + size_t coredump_sk_len = offsetof(struct sockaddr_un, sun_path) +
> + sizeof("/tmp/coredump.socket");
> +
> + file = fopen("/proc/sys/kernel/core_pattern", "w");
> + ASSERT_NE(file, NULL);
> +
> + ret = fprintf(file, "@/tmp/coredump.socket");
> + ASSERT_EQ(ret, strlen("@/tmp/coredump.socket"));
> + ASSERT_EQ(fclose(file), 0);
> +
> + ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
> + ASSERT_EQ(ret, 0);
> +
> + pid_coredump_server = fork();
> + ASSERT_GE(pid_coredump_server, 0);
> + if (pid_coredump_server == 0) {
> + int fd_server, fd_coredump, fd_peer_pidfd, fd_core_file;
> + __u64 peer_cookie;
> + socklen_t fd_peer_pidfd_len, peer_cookie_len;
> +
> + close(ipc_sockets[0]);
> +
> + fd_server = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
> + if (fd_server < 0)
> + _exit(EXIT_FAILURE);
> +
> + ret = bind(fd_server, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to bind coredump socket\n");
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + ret = listen(fd_server, 1);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to listen on coredump socket\n");
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + close(ipc_sockets[1]);
> +
> + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
> + if (fd_coredump < 0) {
> + fprintf(stderr, "Failed to accept coredump socket connection\n");
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + peer_cookie_len = sizeof(peer_cookie);
> + ret = getsockopt(fd_coredump, SOL_SOCKET, SO_COOKIE,
> + &peer_cookie, &peer_cookie_len);
> + if (ret < 0) {
> + fprintf(stderr, "%m - Failed to retrieve cookie for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + fd_peer_pidfd_len = sizeof(fd_peer_pidfd);
> + ret = getsockopt(fd_coredump, SOL_SOCKET, SO_PEERPIDFD,
> + &fd_peer_pidfd, &fd_peer_pidfd_len);
> + if (ret < 0) {
> + fprintf(stderr, "%m - Failed to retrieve peer pidfd for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + _exit(EXIT_FAILURE);
> + }
> +
> + memset(&info, 0, sizeof(info));
> + info.mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP;
> + ret = ioctl(fd_peer_pidfd, PIDFD_GET_INFO, &info);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to retrieve pidfd info from peer pidfd for coredump socket connection\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (!(info.mask & PIDFD_INFO_COREDUMP)) {
> + fprintf(stderr, "Missing coredump information from coredumping task\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (info.coredump_mask & PIDFD_COREDUMPED) {
> + fprintf(stderr, "Received unexpected connection from coredumping task\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (info.coredump_cookie) {
> + fprintf(stderr, "Received unexpected coredump cookie\n");
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + _exit(EXIT_FAILURE);
> + }
> +
> + close(fd_coredump);
> + close(fd_server);
> + close(fd_peer_pidfd);
> + close(fd_core_file);
> + _exit(EXIT_SUCCESS);
> + }
> + self->pid_coredump_server = pid_coredump_server;
> +
> + EXPECT_EQ(close(ipc_sockets[1]), 0);
> + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
> + EXPECT_EQ(close(ipc_sockets[0]), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0) {
> + int fd_socket;
> + ssize_t ret;
> +
> + fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
> + if (fd_socket < 0)
> + _exit(EXIT_FAILURE);
> +
> +
> + ret = connect(fd_socket, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
> + if (ret < 0)
> + _exit(EXIT_FAILURE);
> +
> + (void *)write(fd_socket, &(char){ 0 }, 1);
> + close(fd_socket);
> + _exit(EXIT_SUCCESS);
> + }
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFEXITED(status));
> + ASSERT_EQ(WEXITSTATUS(status), 0);
> +
> + info.mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP;
> + ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
> + ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
> + ASSERT_EQ((info.coredump_mask & PIDFD_COREDUMPED), 0);
> +
> + waitpid(pid_coredump_server, &status, 0);
> + self->pid_coredump_server = -ESRCH;
> + ASSERT_TRUE(WIFEXITED(status));
> + ASSERT_EQ(WEXITSTATUS(status), 0);
> +
> + ASSERT_NE(stat("/tmp/coredump.file", &st), 0);
> + ASSERT_EQ(errno, ENOENT);
> +}
> +
> +TEST_F(coredump, socket_enoent)
> +{
> + int pidfd, ret, status;
> + FILE *file;
> + pid_t pid;
> + char core_file[PATH_MAX];
> +
> + file = fopen("/proc/sys/kernel/core_pattern", "w");
> + ASSERT_NE(file, NULL);
> +
> + ret = fprintf(file, "@/tmp/coredump.socket");
> + ASSERT_EQ(ret, strlen("@/tmp/coredump.socket"));
> + ASSERT_EQ(fclose(file), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0)
> + crashing_child();
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFSIGNALED(status));
> + ASSERT_FALSE(WCOREDUMP(status));
> +}
> +
> +TEST_F(coredump, socket_no_listener)
> +{
> + int pidfd, ret, status;
> + FILE *file;
> + pid_t pid, pid_coredump_server;
> + int ipc_sockets[2];
> + char c;
> + const struct sockaddr_un coredump_sk = {
> + .sun_family = AF_UNIX,
> + .sun_path = "/tmp/coredump.socket",
> + };
> + size_t coredump_sk_len = offsetof(struct sockaddr_un, sun_path) +
> + sizeof("/tmp/coredump.socket");
> +
> + ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
> + ASSERT_EQ(ret, 0);
> +
> + file = fopen("/proc/sys/kernel/core_pattern", "w");
> + ASSERT_NE(file, NULL);
> +
> + ret = fprintf(file, "@/tmp/coredump.socket");
> + ASSERT_EQ(ret, strlen("@/tmp/coredump.socket"));
> + ASSERT_EQ(fclose(file), 0);
> +
> + pid_coredump_server = fork();
> + ASSERT_GE(pid_coredump_server, 0);
> + if (pid_coredump_server == 0) {
> + int fd_server, fd_coredump, fd_peer_pidfd, fd_core_file;
> + __u64 peer_cookie;
> + socklen_t fd_peer_pidfd_len, peer_cookie_len;
> +
> + close(ipc_sockets[0]);
> +
> + fd_server = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
> + if (fd_server < 0)
> + _exit(EXIT_FAILURE);
> +
> + ret = bind(fd_server, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
> + if (ret < 0) {
> + fprintf(stderr, "Failed to bind coredump socket\n");
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_FAILURE);
> + }
> +
> + close(fd_server);
> + close(ipc_sockets[1]);
> + _exit(EXIT_SUCCESS);
> + }
> + self->pid_coredump_server = pid_coredump_server;
> +
> + EXPECT_EQ(close(ipc_sockets[1]), 0);
> + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
> + EXPECT_EQ(close(ipc_sockets[0]), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0)
> + crashing_child();
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFSIGNALED(status));
> + ASSERT_FALSE(WCOREDUMP(status));
> +
> + waitpid(pid_coredump_server, &status, 0);
> + self->pid_coredump_server = -ESRCH;
> + ASSERT_TRUE(WIFEXITED(status));
> + ASSERT_EQ(WEXITSTATUS(status), 0);
> +}
> +
> TEST_HARNESS_MAIN
>
> --
> 2.47.2
>