mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Merge tag 'vfs-6.19-rc1.coredump' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull pidfd and coredump updates from Christian Brauner:
"Features:
- Expose coredump signal via pidfd
Expose the signal that caused the coredump through the pidfd
interface. The recent changes to rework coredump handling to rely
on unix sockets are in the process of being used in systemd. The
previous systemd coredump container interface requires the coredump
file descriptor and basic information including the signal number
to be sent to the container. This means the signal number needs to
be available before sending the coredump to the container.
- Add supported_mask field to pidfd
Add a new supported_mask field to struct pidfd_info that indicates
which information fields are supported by the running kernel. This
allows userspace to detect feature availability without relying on
error codes or kernel version checks.
Cleanups:
- Drop struct pidfs_exit_info and prepare to drop exit_info pointer,
simplifying the internal publication mechanism for exit and
coredump information retrievable via the pidfd ioctl
- Use guard() for task_lock in pidfs
- Reduce wait_pidfd lock scope
- Add missing PIDFD_INFO_SIZE_VER1 constant
- Add missing BUILD_BUG_ON() assert on struct pidfd_info
Fixes:
- Fix PIDFD_INFO_COREDUMP handling
Selftests:
- Split out coredump socket tests and common helpers into separate
files for better organization
- Fix userspace coredump client detection issues
- Handle edge-triggered epoll correctly
- Ignore ENOSPC errors in tests
- Add debug logging to coredump socket tests, socket protocol tests,
and test helpers
- Add tests for PIDFD_INFO_COREDUMP_SIGNAL
- Add tests for supported_mask field
- Update pidfd header for selftests"
* tag 'vfs-6.19-rc1.coredump' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (23 commits)
pidfs: reduce wait_pidfd lock scope
selftests/coredump: add second PIDFD_INFO_COREDUMP_SIGNAL test
selftests/coredump: add first PIDFD_INFO_COREDUMP_SIGNAL test
selftests/coredump: ignore ENOSPC errors
selftests/coredump: add debug logging to coredump socket protocol tests
selftests/coredump: add debug logging to coredump socket tests
selftests/coredump: add debug logging to test helpers
selftests/coredump: handle edge-triggered epoll correctly
selftests/coredump: fix userspace coredump client detection
selftests/coredump: fix userspace client detection
selftests/coredump: split out coredump socket tests
selftests/coredump: split out common helpers
selftests/pidfd: add second supported_mask test
selftests/pidfd: add first supported_mask test
selftests/pidfd: update pidfd header
pidfs: expose coredump signal
pidfs: drop struct pidfs_exit_info
pidfs: prepare to drop exit_info pointer
pidfd: add a new supported_mask field
pidfs: add missing BUILD_BUG_ON() assert on struct pidfd_info
...
This commit is contained in:
113
fs/pidfs.c
113
fs/pidfs.c
@@ -39,20 +39,20 @@ void pidfs_get_root(struct path *path)
|
||||
path_get(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stashes information that userspace needs to access even after the
|
||||
* process has been reaped.
|
||||
*/
|
||||
struct pidfs_exit_info {
|
||||
__u64 cgroupid;
|
||||
__s32 exit_code;
|
||||
__u32 coredump_mask;
|
||||
enum pidfs_attr_mask_bits {
|
||||
PIDFS_ATTR_BIT_EXIT = 0,
|
||||
PIDFS_ATTR_BIT_COREDUMP = 1,
|
||||
};
|
||||
|
||||
struct pidfs_attr {
|
||||
unsigned long attr_mask;
|
||||
struct simple_xattrs *xattrs;
|
||||
struct pidfs_exit_info __pei;
|
||||
struct pidfs_exit_info *exit_info;
|
||||
struct /* exit info */ {
|
||||
__u64 cgroupid;
|
||||
__s32 exit_code;
|
||||
};
|
||||
__u32 coredump_mask;
|
||||
__u32 coredump_signal;
|
||||
};
|
||||
|
||||
static struct rb_root pidfs_ino_tree = RB_ROOT;
|
||||
@@ -293,6 +293,15 @@ static __u32 pidfs_coredump_mask(unsigned long mm_flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* This must be updated whenever a new flag is added */
|
||||
#define PIDFD_INFO_SUPPORTED (PIDFD_INFO_PID | \
|
||||
PIDFD_INFO_CREDS | \
|
||||
PIDFD_INFO_CGROUPID | \
|
||||
PIDFD_INFO_EXIT | \
|
||||
PIDFD_INFO_COREDUMP | \
|
||||
PIDFD_INFO_SUPPORTED_MASK | \
|
||||
PIDFD_INFO_COREDUMP_SIGNAL)
|
||||
|
||||
static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg;
|
||||
@@ -300,12 +309,13 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
struct pid *pid = pidfd_pid(file);
|
||||
size_t usize = _IOC_SIZE(cmd);
|
||||
struct pidfd_info kinfo = {};
|
||||
struct pidfs_exit_info *exit_info;
|
||||
struct user_namespace *user_ns;
|
||||
struct pidfs_attr *attr;
|
||||
const struct cred *c;
|
||||
__u64 mask;
|
||||
|
||||
BUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER2);
|
||||
|
||||
if (!uinfo)
|
||||
return -EINVAL;
|
||||
if (usize < PIDFD_INFO_SIZE_VER0)
|
||||
@@ -323,20 +333,24 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
|
||||
attr = READ_ONCE(pid->attr);
|
||||
if (mask & PIDFD_INFO_EXIT) {
|
||||
exit_info = READ_ONCE(attr->exit_info);
|
||||
if (exit_info) {
|
||||
if (test_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask)) {
|
||||
smp_rmb();
|
||||
kinfo.mask |= PIDFD_INFO_EXIT;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
kinfo.cgroupid = exit_info->cgroupid;
|
||||
kinfo.cgroupid = attr->cgroupid;
|
||||
kinfo.mask |= PIDFD_INFO_CGROUPID;
|
||||
#endif
|
||||
kinfo.exit_code = exit_info->exit_code;
|
||||
kinfo.exit_code = attr->exit_code;
|
||||
}
|
||||
}
|
||||
|
||||
if (mask & PIDFD_INFO_COREDUMP) {
|
||||
kinfo.mask |= PIDFD_INFO_COREDUMP;
|
||||
kinfo.coredump_mask = READ_ONCE(attr->__pei.coredump_mask);
|
||||
if (test_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask)) {
|
||||
smp_rmb();
|
||||
kinfo.mask |= PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL;
|
||||
kinfo.coredump_mask = attr->coredump_mask;
|
||||
kinfo.coredump_signal = attr->coredump_signal;
|
||||
}
|
||||
}
|
||||
|
||||
task = get_pid_task(pid, PIDTYPE_PID);
|
||||
@@ -355,14 +369,15 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
if (!c)
|
||||
return -ESRCH;
|
||||
|
||||
if ((kinfo.mask & PIDFD_INFO_COREDUMP) && !(kinfo.coredump_mask)) {
|
||||
task_lock(task);
|
||||
if ((mask & PIDFD_INFO_COREDUMP) && !kinfo.coredump_mask) {
|
||||
guard(task_lock)(task);
|
||||
if (task->mm) {
|
||||
unsigned long flags = __mm_flags_get_dumpable(task->mm);
|
||||
|
||||
kinfo.coredump_mask = pidfs_coredump_mask(flags);
|
||||
kinfo.mask |= PIDFD_INFO_COREDUMP;
|
||||
/* No coredump actually took place, so no coredump signal. */
|
||||
}
|
||||
task_unlock(task);
|
||||
}
|
||||
|
||||
/* Unconditionally return identifiers and credentials, the rest only on request */
|
||||
@@ -409,6 +424,13 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
return -ESRCH;
|
||||
|
||||
copy_out:
|
||||
if (mask & PIDFD_INFO_SUPPORTED_MASK) {
|
||||
kinfo.mask |= PIDFD_INFO_SUPPORTED_MASK;
|
||||
kinfo.supported_mask = PIDFD_INFO_SUPPORTED;
|
||||
}
|
||||
|
||||
/* Are there bits in the return mask not present in PIDFD_INFO_SUPPORTED? */
|
||||
WARN_ON_ONCE(~PIDFD_INFO_SUPPORTED & kinfo.mask);
|
||||
/*
|
||||
* If userspace and the kernel have the same struct size it can just
|
||||
* be copied. If userspace provides an older struct, only the bits that
|
||||
@@ -603,24 +625,25 @@ void pidfs_exit(struct task_struct *tsk)
|
||||
{
|
||||
struct pid *pid = task_pid(tsk);
|
||||
struct pidfs_attr *attr;
|
||||
struct pidfs_exit_info *exit_info;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
struct cgroup *cgrp;
|
||||
#endif
|
||||
|
||||
might_sleep();
|
||||
|
||||
guard(spinlock_irq)(&pid->wait_pidfd.lock);
|
||||
attr = pid->attr;
|
||||
if (!attr) {
|
||||
/*
|
||||
* No one ever held a pidfd for this struct pid.
|
||||
* Mark it as dead so no one can add a pidfs
|
||||
* entry anymore. We're about to be reaped and
|
||||
* so no exit information would be available.
|
||||
*/
|
||||
pid->attr = PIDFS_PID_DEAD;
|
||||
return;
|
||||
/* Synchronize with pidfs_register_pid(). */
|
||||
scoped_guard(spinlock_irq, &pid->wait_pidfd.lock) {
|
||||
attr = pid->attr;
|
||||
if (!attr) {
|
||||
/*
|
||||
* No one ever held a pidfd for this struct pid.
|
||||
* Mark it as dead so no one can add a pidfs
|
||||
* entry anymore. We're about to be reaped and
|
||||
* so no exit information would be available.
|
||||
*/
|
||||
pid->attr = PIDFS_PID_DEAD;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -631,41 +654,39 @@ void pidfs_exit(struct task_struct *tsk)
|
||||
* is put
|
||||
*/
|
||||
|
||||
exit_info = &attr->__pei;
|
||||
|
||||
#ifdef CONFIG_CGROUPS
|
||||
rcu_read_lock();
|
||||
cgrp = task_dfl_cgroup(tsk);
|
||||
exit_info->cgroupid = cgroup_id(cgrp);
|
||||
attr->cgroupid = cgroup_id(cgrp);
|
||||
rcu_read_unlock();
|
||||
#endif
|
||||
exit_info->exit_code = tsk->exit_code;
|
||||
attr->exit_code = tsk->exit_code;
|
||||
|
||||
/* Ensure that PIDFD_GET_INFO sees either all or nothing. */
|
||||
smp_store_release(&attr->exit_info, &attr->__pei);
|
||||
smp_wmb();
|
||||
set_bit(PIDFS_ATTR_BIT_EXIT, &attr->attr_mask);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COREDUMP
|
||||
void pidfs_coredump(const struct coredump_params *cprm)
|
||||
{
|
||||
struct pid *pid = cprm->pid;
|
||||
struct pidfs_exit_info *exit_info;
|
||||
struct pidfs_attr *attr;
|
||||
__u32 coredump_mask = 0;
|
||||
|
||||
attr = READ_ONCE(pid->attr);
|
||||
|
||||
VFS_WARN_ON_ONCE(!attr);
|
||||
VFS_WARN_ON_ONCE(attr == PIDFS_PID_DEAD);
|
||||
|
||||
exit_info = &attr->__pei;
|
||||
/* Note how we were coredumped. */
|
||||
coredump_mask = pidfs_coredump_mask(cprm->mm_flags);
|
||||
/* Note that we actually did coredump. */
|
||||
coredump_mask |= PIDFD_COREDUMPED;
|
||||
/* Note how we were coredumped and that we coredumped. */
|
||||
attr->coredump_mask = pidfs_coredump_mask(cprm->mm_flags) |
|
||||
PIDFD_COREDUMPED;
|
||||
/* If coredumping is set to skip we should never end up here. */
|
||||
VFS_WARN_ON_ONCE(coredump_mask & PIDFD_COREDUMP_SKIP);
|
||||
smp_store_release(&exit_info->coredump_mask, coredump_mask);
|
||||
VFS_WARN_ON_ONCE(attr->coredump_mask & PIDFD_COREDUMP_SKIP);
|
||||
/* Expose the signal number that caused the coredump. */
|
||||
attr->coredump_signal = cprm->siginfo->si_signo;
|
||||
smp_wmb();
|
||||
set_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -26,8 +26,12 @@
|
||||
#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */
|
||||
#define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */
|
||||
#define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */
|
||||
#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */
|
||||
#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */
|
||||
|
||||
#define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */
|
||||
#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */
|
||||
#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */
|
||||
|
||||
/*
|
||||
* Values for @coredump_mask in pidfd_info.
|
||||
@@ -91,8 +95,11 @@ struct pidfd_info {
|
||||
__u32 fsuid;
|
||||
__u32 fsgid;
|
||||
__s32 exit_code;
|
||||
__u32 coredump_mask;
|
||||
__u32 __spare1;
|
||||
struct /* coredump info */ {
|
||||
__u32 coredump_mask;
|
||||
__u32 coredump_signal;
|
||||
};
|
||||
__u64 supported_mask; /* Mask flags that this kernel supports */
|
||||
};
|
||||
|
||||
#define PIDFS_IOCTL_MAGIC 0xFF
|
||||
|
||||
4
tools/testing/selftests/coredump/.gitignore
vendored
Normal file
4
tools/testing/selftests/coredump/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
stackdump_test
|
||||
coredump_socket_test
|
||||
coredump_socket_protocol_test
|
||||
@@ -1,7 +1,13 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
CFLAGS += -Wall -O0 -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES)
|
||||
|
||||
TEST_GEN_PROGS := stackdump_test
|
||||
TEST_GEN_PROGS := stackdump_test \
|
||||
coredump_socket_test \
|
||||
coredump_socket_protocol_test
|
||||
TEST_FILES := stackdump
|
||||
|
||||
include ../lib.mk
|
||||
|
||||
$(OUTPUT)/stackdump_test: coredump_test_helpers.c
|
||||
$(OUTPUT)/coredump_socket_test: coredump_test_helpers.c
|
||||
$(OUTPUT)/coredump_socket_protocol_test: coredump_test_helpers.c
|
||||
|
||||
1568
tools/testing/selftests/coredump/coredump_socket_protocol_test.c
Normal file
1568
tools/testing/selftests/coredump/coredump_socket_protocol_test.c
Normal file
File diff suppressed because it is too large
Load Diff
742
tools/testing/selftests/coredump/coredump_socket_test.c
Normal file
742
tools/testing/selftests/coredump/coredump_socket_test.c
Normal file
@@ -0,0 +1,742 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include "coredump_test.h"
|
||||
|
||||
FIXTURE_SETUP(coredump)
|
||||
{
|
||||
FILE *file;
|
||||
int ret;
|
||||
|
||||
self->pid_coredump_server = -ESRCH;
|
||||
self->fd_tmpfs_detached = -1;
|
||||
file = fopen("/proc/sys/kernel/core_pattern", "r");
|
||||
ASSERT_NE(NULL, file);
|
||||
|
||||
ret = fread(self->original_core_pattern, 1, sizeof(self->original_core_pattern), file);
|
||||
ASSERT_TRUE(ret || feof(file));
|
||||
ASSERT_LT(ret, sizeof(self->original_core_pattern));
|
||||
|
||||
self->original_core_pattern[ret] = '\0';
|
||||
self->fd_tmpfs_detached = create_detached_tmpfs();
|
||||
ASSERT_GE(self->fd_tmpfs_detached, 0);
|
||||
|
||||
ret = fclose(file);
|
||||
ASSERT_EQ(0, ret);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(coredump)
|
||||
{
|
||||
const char *reason;
|
||||
FILE *file;
|
||||
int ret, status;
|
||||
|
||||
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";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = fprintf(file, "%s", self->original_core_pattern);
|
||||
if (ret < 0) {
|
||||
reason = "Unable to write to core_pattern";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = fclose(file);
|
||||
if (ret) {
|
||||
reason = "Unable to close core_pattern";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (self->fd_tmpfs_detached >= 0) {
|
||||
ret = close(self->fd_tmpfs_detached);
|
||||
if (ret < 0) {
|
||||
reason = "Unable to close detached tmpfs";
|
||||
goto fail;
|
||||
}
|
||||
self->fd_tmpfs_detached = -1;
|
||||
}
|
||||
|
||||
return;
|
||||
fail:
|
||||
/* This should never happen */
|
||||
fprintf(stderr, "Failed to cleanup coredump test: %s\n", reason);
|
||||
}
|
||||
|
||||
TEST_F(coredump, socket)
|
||||
{
|
||||
int pidfd, ret, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct stat st;
|
||||
struct pidfd_info info = {};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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 = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0) {
|
||||
fprintf(stderr, "socket test: create_and_listen_unix_socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
|
||||
fprintf(stderr, "socket test: write_nointr to ipc socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0) {
|
||||
fprintf(stderr, "socket test: accept4 failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0) {
|
||||
fprintf(stderr, "socket test: get_peer_pidfd failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
|
||||
fprintf(stderr, "socket test: get_pidfd_info failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
|
||||
fprintf(stderr, "socket test: PIDFD_INFO_COREDUMP not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
|
||||
fprintf(stderr, "socket test: PIDFD_COREDUMPED not set in coredump_mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_core_file = creat("/tmp/coredump.file", 0644);
|
||||
if (fd_core_file < 0) {
|
||||
fprintf(stderr, "socket test: creat coredump file failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes_read, bytes_write;
|
||||
|
||||
bytes_read = read(fd_coredump, buffer, sizeof(buffer));
|
||||
if (bytes_read < 0) {
|
||||
fprintf(stderr, "socket test: read from coredump socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (bytes_read == 0)
|
||||
break;
|
||||
|
||||
bytes_write = write(fd_core_file, buffer, bytes_read);
|
||||
if (bytes_read != bytes_write) {
|
||||
if (bytes_write < 0 && errno == ENOSPC)
|
||||
continue;
|
||||
fprintf(stderr, "socket test: write to core file failed (read=%zd, write=%zd): %m\n", bytes_read, bytes_write);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "socket test: completed successfully\n");
|
||||
out:
|
||||
if (fd_core_file >= 0)
|
||||
close(fd_core_file);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
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));
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
|
||||
ASSERT_GT((info.coredump_mask & PIDFD_COREDUMPED), 0);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
|
||||
ASSERT_EQ(stat("/tmp/coredump.file", &st), 0);
|
||||
ASSERT_GT(st.st_size, 0);
|
||||
}
|
||||
|
||||
TEST_F(coredump, socket_detect_userspace_client)
|
||||
{
|
||||
int pidfd, ret, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct stat st;
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_COREDUMP,
|
||||
};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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 = -1, fd_coredump = -1, fd_peer_pidfd = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: create_and_listen_unix_socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: write_nointr to ipc socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: accept4 failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: get_peer_pidfd failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: get_pidfd_info failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: PIDFD_INFO_COREDUMP not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info.coredump_mask & PIDFD_COREDUMPED) {
|
||||
fprintf(stderr, "socket_detect_userspace_client: PIDFD_COREDUMPED incorrectly set (should be userspace client)\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "socket_detect_userspace_client: completed successfully\n");
|
||||
out:
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
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;
|
||||
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");
|
||||
|
||||
fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd_socket < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client (client): socket failed: %m\n");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ret = connect(fd_socket, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "socket_detect_userspace_client (client): connect failed: %m\n");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
close(fd_socket);
|
||||
pause();
|
||||
fprintf(stderr, "socket_detect_userspace_client (client): completed successfully\n");
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
pidfd = sys_pidfd_open(pid, 0);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_GT((info.mask & PIDFD_INFO_COREDUMP), 0);
|
||||
ASSERT_EQ((info.coredump_mask & PIDFD_COREDUMPED), 0);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
|
||||
ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0), 0);
|
||||
ASSERT_EQ(close(pidfd), 0);
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGKILL);
|
||||
|
||||
ASSERT_NE(stat("/tmp/coredump.file", &st), 0);
|
||||
ASSERT_EQ(errno, ENOENT);
|
||||
}
|
||||
|
||||
TEST_F(coredump, socket_enoent)
|
||||
{
|
||||
int pidfd, status;
|
||||
pid_t pid;
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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;
|
||||
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");
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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 = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd_server < 0) {
|
||||
fprintf(stderr, "socket_no_listener: socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = bind(fd_server, (const struct sockaddr *)&coredump_sk, coredump_sk_len);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "socket_no_listener: bind failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
|
||||
fprintf(stderr, "socket_no_listener: write_nointr to ipc socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "socket_no_listener: completed successfully\n");
|
||||
out:
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
close(ipc_sockets[1]);
|
||||
_exit(exit_code);
|
||||
}
|
||||
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));
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump
|
||||
*
|
||||
* Verify that when using simple socket-based coredump (@ pattern),
|
||||
* the coredump_signal field is correctly exposed as SIGSEGV.
|
||||
*/
|
||||
TEST_F(coredump, socket_coredump_signal_sigsegv)
|
||||
{
|
||||
int pidfd, ret, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct pidfd_info info = {};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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 = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: create_and_listen_unix_socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: write_nointr to ipc socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: accept4 failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: get_peer_pidfd failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: get_pidfd_info failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_COREDUMPED not set in coredump_mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify coredump_signal is available and correct */
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info.coredump_signal != SIGSEGV) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: coredump_signal=%d, expected SIGSEGV=%d\n",
|
||||
info.coredump_signal, SIGSEGV);
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
|
||||
if (fd_core_file < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: open_coredump_tmpfile failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes_read, bytes_write;
|
||||
|
||||
bytes_read = read(fd_coredump, buffer, sizeof(buffer));
|
||||
if (bytes_read < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: read from coredump socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (bytes_read == 0)
|
||||
break;
|
||||
|
||||
bytes_write = write(fd_core_file, buffer, bytes_read);
|
||||
if (bytes_read != bytes_write) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: write to core file failed (read=%zd, write=%zd): %m\n",
|
||||
bytes_read, bytes_write);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "socket_coredump_signal_sigsegv: completed successfully\n");
|
||||
out:
|
||||
if (fd_core_file >= 0)
|
||||
close(fd_core_file);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
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_EQ(WTERMSIG(status), SIGSEGV);
|
||||
ASSERT_TRUE(WCOREDUMP(status));
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
|
||||
ASSERT_EQ(info.coredump_signal, SIGSEGV);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump with SIGABRT
|
||||
*
|
||||
* Verify that when using simple socket-based coredump (@ pattern),
|
||||
* the coredump_signal field is correctly exposed as SIGABRT.
|
||||
*/
|
||||
TEST_F(coredump, socket_coredump_signal_sigabrt)
|
||||
{
|
||||
int pidfd, ret, status;
|
||||
pid_t pid, pid_coredump_server;
|
||||
struct pidfd_info info = {};
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
|
||||
|
||||
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 = -1, fd_coredump = -1, fd_peer_pidfd = -1, fd_core_file = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
|
||||
if (fd_server < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: create_and_listen_unix_socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: write_nointr to ipc socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
|
||||
if (fd_coredump < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: accept4 failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_peer_pidfd = get_peer_pidfd(fd_coredump);
|
||||
if (fd_peer_pidfd < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: get_peer_pidfd failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!get_pidfd_info(fd_peer_pidfd, &info)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: get_pidfd_info failed\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(info.coredump_mask & PIDFD_COREDUMPED)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_COREDUMPED not set in coredump_mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify coredump_signal is available and correct */
|
||||
if (!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL)) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: PIDFD_INFO_COREDUMP_SIGNAL not set in mask\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (info.coredump_signal != SIGABRT) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: coredump_signal=%d, expected SIGABRT=%d\n",
|
||||
info.coredump_signal, SIGABRT);
|
||||
goto out;
|
||||
}
|
||||
|
||||
fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
|
||||
if (fd_core_file < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: open_coredump_tmpfile failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes_read, bytes_write;
|
||||
|
||||
bytes_read = read(fd_coredump, buffer, sizeof(buffer));
|
||||
if (bytes_read < 0) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: read from coredump socket failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (bytes_read == 0)
|
||||
break;
|
||||
|
||||
bytes_write = write(fd_core_file, buffer, bytes_read);
|
||||
if (bytes_read != bytes_write) {
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: write to core file failed (read=%zd, write=%zd): %m\n",
|
||||
bytes_read, bytes_write);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "socket_coredump_signal_sigabrt: completed successfully\n");
|
||||
out:
|
||||
if (fd_core_file >= 0)
|
||||
close(fd_core_file);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
if (fd_server >= 0)
|
||||
close(fd_server);
|
||||
_exit(exit_code);
|
||||
}
|
||||
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)
|
||||
abort();
|
||||
|
||||
pidfd = sys_pidfd_open(pid, 0);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
|
||||
waitpid(pid, &status, 0);
|
||||
ASSERT_TRUE(WIFSIGNALED(status));
|
||||
ASSERT_EQ(WTERMSIG(status), SIGABRT);
|
||||
ASSERT_TRUE(WCOREDUMP(status));
|
||||
|
||||
ASSERT_TRUE(get_pidfd_info(pidfd, &info));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP_SIGNAL));
|
||||
ASSERT_EQ(info.coredump_signal, SIGABRT);
|
||||
|
||||
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
|
||||
}
|
||||
|
||||
TEST_F(coredump, socket_invalid_paths)
|
||||
{
|
||||
ASSERT_FALSE(set_core_pattern("@ /tmp/coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@/tmp/../coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@../coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@/tmp/coredump.socket/.."));
|
||||
ASSERT_FALSE(set_core_pattern("@.."));
|
||||
|
||||
ASSERT_FALSE(set_core_pattern("@@ /tmp/coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@@/tmp/../coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@@../coredump.socket"));
|
||||
ASSERT_FALSE(set_core_pattern("@@/tmp/coredump.socket/.."));
|
||||
ASSERT_FALSE(set_core_pattern("@@.."));
|
||||
|
||||
ASSERT_FALSE(set_core_pattern("@@@/tmp/coredump.socket"));
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
59
tools/testing/selftests/coredump/coredump_test.h
Normal file
59
tools/testing/selftests/coredump/coredump_test.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef __COREDUMP_TEST_H
|
||||
#define __COREDUMP_TEST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include <linux/coredump.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
#include "../pidfd/pidfd.h"
|
||||
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
|
||||
#define NUM_THREAD_SPAWN 128
|
||||
|
||||
/* Coredump fixture */
|
||||
FIXTURE(coredump)
|
||||
{
|
||||
char original_core_pattern[256];
|
||||
pid_t pid_coredump_server;
|
||||
int fd_tmpfs_detached;
|
||||
};
|
||||
|
||||
/* Shared helper function declarations */
|
||||
void *do_nothing(void *arg);
|
||||
void crashing_child(void);
|
||||
int create_detached_tmpfs(void);
|
||||
int create_and_listen_unix_socket(const char *path);
|
||||
bool set_core_pattern(const char *pattern);
|
||||
int get_peer_pidfd(int fd);
|
||||
bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
|
||||
|
||||
/* Inline helper that uses harness types */
|
||||
static inline void wait_and_check_coredump_server(pid_t pid_coredump_server,
|
||||
struct __test_metadata *const _metadata,
|
||||
FIXTURE_DATA(coredump) *self)
|
||||
{
|
||||
int status;
|
||||
waitpid(pid_coredump_server, &status, 0);
|
||||
self->pid_coredump_server = -ESRCH;
|
||||
ASSERT_TRUE(WIFEXITED(status));
|
||||
ASSERT_EQ(WEXITSTATUS(status), 0);
|
||||
}
|
||||
|
||||
/* Protocol helper function declarations */
|
||||
ssize_t recv_marker(int fd);
|
||||
bool read_marker(int fd, enum coredump_mark mark);
|
||||
bool read_coredump_req(int fd, struct coredump_req *req);
|
||||
bool send_coredump_ack(int fd, const struct coredump_req *req,
|
||||
__u64 mask, size_t size_ack);
|
||||
bool check_coredump_req(const struct coredump_req *req, size_t min_size,
|
||||
__u64 required_mask);
|
||||
int open_coredump_tmpfile(int fd_tmpfs_detached);
|
||||
void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file);
|
||||
|
||||
#endif /* __COREDUMP_TEST_H */
|
||||
383
tools/testing/selftests/coredump/coredump_test_helpers.c
Normal file
383
tools/testing/selftests/coredump/coredump_test_helpers.c
Normal file
@@ -0,0 +1,383 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <linux/coredump.h>
|
||||
#include <linux/fs.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../filesystems/wrappers.h"
|
||||
#include "../pidfd/pidfd.h"
|
||||
|
||||
/* Forward declarations to avoid including harness header */
|
||||
struct __test_metadata;
|
||||
|
||||
/* Match the fixture definition from coredump_test.h */
|
||||
struct _fixture_coredump_data {
|
||||
char original_core_pattern[256];
|
||||
pid_t pid_coredump_server;
|
||||
int fd_tmpfs_detached;
|
||||
};
|
||||
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
|
||||
#define NUM_THREAD_SPAWN 128
|
||||
|
||||
void *do_nothing(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (1)
|
||||
pause();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void crashing_child(void)
|
||||
{
|
||||
pthread_t thread;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_THREAD_SPAWN; ++i)
|
||||
pthread_create(&thread, NULL, do_nothing, NULL);
|
||||
|
||||
/* crash on purpose */
|
||||
i = *(int *)NULL;
|
||||
}
|
||||
|
||||
int create_detached_tmpfs(void)
|
||||
{
|
||||
int fd_context, fd_tmpfs;
|
||||
|
||||
fd_context = sys_fsopen("tmpfs", 0);
|
||||
if (fd_context < 0)
|
||||
return -1;
|
||||
|
||||
if (sys_fsconfig(fd_context, FSCONFIG_CMD_CREATE, NULL, NULL, 0) < 0)
|
||||
return -1;
|
||||
|
||||
fd_tmpfs = sys_fsmount(fd_context, 0, 0);
|
||||
close(fd_context);
|
||||
return fd_tmpfs;
|
||||
}
|
||||
|
||||
int create_and_listen_unix_socket(const char *path)
|
||||
{
|
||||
struct sockaddr_un addr = {
|
||||
.sun_family = AF_UNIX,
|
||||
};
|
||||
assert(strlen(path) < sizeof(addr.sun_path) - 1);
|
||||
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
|
||||
size_t addr_len =
|
||||
offsetof(struct sockaddr_un, sun_path) + strlen(path) + 1;
|
||||
int fd, ret;
|
||||
|
||||
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
if (fd < 0)
|
||||
goto out;
|
||||
|
||||
ret = bind(fd, (const struct sockaddr *)&addr, addr_len);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = listen(fd, 128);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
return fd;
|
||||
|
||||
out:
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool set_core_pattern(const char *pattern)
|
||||
{
|
||||
int fd;
|
||||
ssize_t ret;
|
||||
|
||||
fd = open("/proc/sys/kernel/core_pattern", O_WRONLY | O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
ret = write(fd, pattern, strlen(pattern));
|
||||
close(fd);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Set core_pattern to '%s' | %zu == %zu\n", pattern, ret, strlen(pattern));
|
||||
return ret == strlen(pattern);
|
||||
}
|
||||
|
||||
int get_peer_pidfd(int fd)
|
||||
{
|
||||
int fd_peer_pidfd;
|
||||
socklen_t fd_peer_pidfd_len = sizeof(fd_peer_pidfd);
|
||||
int ret = getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &fd_peer_pidfd,
|
||||
&fd_peer_pidfd_len);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "get_peer_pidfd: getsockopt(SO_PEERPIDFD) failed: %m\n");
|
||||
return -1;
|
||||
}
|
||||
fprintf(stderr, "get_peer_pidfd: successfully retrieved pidfd %d\n", fd_peer_pidfd);
|
||||
return fd_peer_pidfd;
|
||||
}
|
||||
|
||||
bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info)
|
||||
{
|
||||
int ret;
|
||||
memset(info, 0, sizeof(*info));
|
||||
info->mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL;
|
||||
ret = ioctl(fd_peer_pidfd, PIDFD_GET_INFO, info);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "get_pidfd_info: ioctl(PIDFD_GET_INFO) failed: %m\n");
|
||||
return false;
|
||||
}
|
||||
fprintf(stderr, "get_pidfd_info: mask=0x%llx, coredump_mask=0x%x, coredump_signal=%d\n",
|
||||
(unsigned long long)info->mask, info->coredump_mask, info->coredump_signal);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Protocol helper functions */
|
||||
|
||||
ssize_t recv_marker(int fd)
|
||||
{
|
||||
enum coredump_mark mark = COREDUMP_MARK_REQACK;
|
||||
ssize_t ret;
|
||||
|
||||
ret = recv(fd, &mark, sizeof(mark), MSG_WAITALL);
|
||||
if (ret != sizeof(mark))
|
||||
return -1;
|
||||
|
||||
switch (mark) {
|
||||
case COREDUMP_MARK_REQACK:
|
||||
fprintf(stderr, "Received marker: ReqAck\n");
|
||||
return COREDUMP_MARK_REQACK;
|
||||
case COREDUMP_MARK_MINSIZE:
|
||||
fprintf(stderr, "Received marker: MinSize\n");
|
||||
return COREDUMP_MARK_MINSIZE;
|
||||
case COREDUMP_MARK_MAXSIZE:
|
||||
fprintf(stderr, "Received marker: MaxSize\n");
|
||||
return COREDUMP_MARK_MAXSIZE;
|
||||
case COREDUMP_MARK_UNSUPPORTED:
|
||||
fprintf(stderr, "Received marker: Unsupported\n");
|
||||
return COREDUMP_MARK_UNSUPPORTED;
|
||||
case COREDUMP_MARK_CONFLICTING:
|
||||
fprintf(stderr, "Received marker: Conflicting\n");
|
||||
return COREDUMP_MARK_CONFLICTING;
|
||||
default:
|
||||
fprintf(stderr, "Received unknown marker: %u\n", mark);
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool read_marker(int fd, enum coredump_mark mark)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
ret = recv_marker(fd);
|
||||
if (ret < 0)
|
||||
return false;
|
||||
return ret == mark;
|
||||
}
|
||||
|
||||
bool read_coredump_req(int fd, struct coredump_req *req)
|
||||
{
|
||||
ssize_t ret;
|
||||
size_t field_size, user_size, ack_size, kernel_size, remaining_size;
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
field_size = sizeof(req->size);
|
||||
|
||||
/* Peek the size of the coredump request. */
|
||||
ret = recv(fd, req, field_size, MSG_PEEK | MSG_WAITALL);
|
||||
if (ret != field_size) {
|
||||
fprintf(stderr, "read_coredump_req: peek failed (got %zd, expected %zu): %m\n",
|
||||
ret, field_size);
|
||||
return false;
|
||||
}
|
||||
kernel_size = req->size;
|
||||
|
||||
if (kernel_size < COREDUMP_ACK_SIZE_VER0) {
|
||||
fprintf(stderr, "read_coredump_req: kernel_size %zu < min %d\n",
|
||||
kernel_size, COREDUMP_ACK_SIZE_VER0);
|
||||
return false;
|
||||
}
|
||||
if (kernel_size >= PAGE_SIZE) {
|
||||
fprintf(stderr, "read_coredump_req: kernel_size %zu >= PAGE_SIZE %d\n",
|
||||
kernel_size, PAGE_SIZE);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Use the minimum of user and kernel size to read the full request. */
|
||||
user_size = sizeof(struct coredump_req);
|
||||
ack_size = user_size < kernel_size ? user_size : kernel_size;
|
||||
ret = recv(fd, req, ack_size, MSG_WAITALL);
|
||||
if (ret != ack_size)
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Read coredump request with size %u and mask 0x%llx\n",
|
||||
req->size, (unsigned long long)req->mask);
|
||||
|
||||
if (user_size > kernel_size)
|
||||
remaining_size = user_size - kernel_size;
|
||||
else
|
||||
remaining_size = kernel_size - user_size;
|
||||
|
||||
if (PAGE_SIZE <= remaining_size)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Discard any additional data if the kernel's request was larger than
|
||||
* what we knew about or cared about.
|
||||
*/
|
||||
if (remaining_size) {
|
||||
char buffer[PAGE_SIZE];
|
||||
|
||||
ret = recv(fd, buffer, sizeof(buffer), MSG_WAITALL);
|
||||
if (ret != remaining_size)
|
||||
return false;
|
||||
fprintf(stderr, "Discarded %zu bytes of data after coredump request\n", remaining_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool send_coredump_ack(int fd, const struct coredump_req *req,
|
||||
__u64 mask, size_t size_ack)
|
||||
{
|
||||
ssize_t ret;
|
||||
/*
|
||||
* Wrap struct coredump_ack in a larger struct so we can
|
||||
* simulate sending to much data to the kernel.
|
||||
*/
|
||||
struct large_ack_for_size_testing {
|
||||
struct coredump_ack ack;
|
||||
char buffer[PAGE_SIZE];
|
||||
} large_ack = {};
|
||||
|
||||
if (!size_ack)
|
||||
size_ack = sizeof(struct coredump_ack) < req->size_ack ?
|
||||
sizeof(struct coredump_ack) :
|
||||
req->size_ack;
|
||||
large_ack.ack.mask = mask;
|
||||
large_ack.ack.size = size_ack;
|
||||
ret = send(fd, &large_ack, size_ack, MSG_NOSIGNAL);
|
||||
if (ret != size_ack)
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Sent coredump ack with size %zu and mask 0x%llx\n",
|
||||
size_ack, (unsigned long long)mask);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool check_coredump_req(const struct coredump_req *req, size_t min_size,
|
||||
__u64 required_mask)
|
||||
{
|
||||
if (req->size < min_size)
|
||||
return false;
|
||||
if ((req->mask & required_mask) != required_mask)
|
||||
return false;
|
||||
if (req->mask & ~required_mask)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int open_coredump_tmpfile(int fd_tmpfs_detached)
|
||||
{
|
||||
return openat(fd_tmpfs_detached, ".", O_TMPFILE | O_RDWR | O_EXCL, 0600);
|
||||
}
|
||||
|
||||
void process_coredump_worker(int fd_coredump, int fd_peer_pidfd, int fd_core_file)
|
||||
{
|
||||
int epfd = -1;
|
||||
int exit_code = EXIT_FAILURE;
|
||||
struct epoll_event ev;
|
||||
int flags;
|
||||
|
||||
/* Set socket to non-blocking mode for edge-triggered epoll */
|
||||
flags = fcntl(fd_coredump, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
fprintf(stderr, "Worker: fcntl(F_GETFL) failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
if (fcntl(fd_coredump, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
fprintf(stderr, "Worker: fcntl(F_SETFL, O_NONBLOCK) failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
epfd = epoll_create1(0);
|
||||
if (epfd < 0) {
|
||||
fprintf(stderr, "Worker: epoll_create1() failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ev.events = EPOLLIN | EPOLLRDHUP | EPOLLET;
|
||||
ev.data.fd = fd_coredump;
|
||||
if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd_coredump, &ev) < 0) {
|
||||
fprintf(stderr, "Worker: epoll_ctl(EPOLL_CTL_ADD) failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
struct epoll_event events[1];
|
||||
int n = epoll_wait(epfd, events, 1, -1);
|
||||
if (n < 0) {
|
||||
fprintf(stderr, "Worker: epoll_wait() failed: %m\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (events[0].events & (EPOLLIN | EPOLLRDHUP)) {
|
||||
for (;;) {
|
||||
char buffer[4096];
|
||||
ssize_t bytes_read = read(fd_coredump, buffer, sizeof(buffer));
|
||||
if (bytes_read < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
break;
|
||||
fprintf(stderr, "Worker: read() failed: %m\n");
|
||||
goto out;
|
||||
}
|
||||
if (bytes_read == 0)
|
||||
goto done;
|
||||
ssize_t bytes_write = write(fd_core_file, buffer, bytes_read);
|
||||
if (bytes_write != bytes_read) {
|
||||
if (bytes_write < 0 && errno == ENOSPC)
|
||||
continue;
|
||||
fprintf(stderr, "Worker: write() failed (read=%zd, write=%zd): %m\n",
|
||||
bytes_read, bytes_write);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
exit_code = EXIT_SUCCESS;
|
||||
fprintf(stderr, "Worker: completed successfully\n");
|
||||
out:
|
||||
if (epfd >= 0)
|
||||
close(epfd);
|
||||
if (fd_core_file >= 0)
|
||||
close(fd_core_file);
|
||||
if (fd_peer_pidfd >= 0)
|
||||
close(fd_peer_pidfd);
|
||||
if (fd_coredump >= 0)
|
||||
close(fd_coredump);
|
||||
_exit(exit_code);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -148,6 +148,14 @@
|
||||
#define PIDFD_INFO_COREDUMP (1UL << 4)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_SUPPORTED_MASK
|
||||
#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_COREDUMP_SIGNAL
|
||||
#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_COREDUMPED
|
||||
#define PIDFD_COREDUMPED (1U << 0) /* Did crash and... */
|
||||
#endif
|
||||
@@ -183,8 +191,11 @@ struct pidfd_info {
|
||||
__u32 fsuid;
|
||||
__u32 fsgid;
|
||||
__s32 exit_code;
|
||||
__u32 coredump_mask;
|
||||
__u32 __spare1;
|
||||
struct {
|
||||
__u32 coredump_mask;
|
||||
__u32 coredump_signal;
|
||||
};
|
||||
__u64 supported_mask;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -690,4 +690,77 @@ TEST_F(pidfd_info, thread_group_exec_thread)
|
||||
EXPECT_EQ(close(pidfd_thread), 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: PIDFD_INFO_SUPPORTED_MASK field
|
||||
*
|
||||
* Verify that when PIDFD_INFO_SUPPORTED_MASK is requested, the kernel
|
||||
* returns the supported_mask field indicating which flags the kernel supports.
|
||||
*/
|
||||
TEST(supported_mask_field)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_SUPPORTED_MASK,
|
||||
};
|
||||
int pidfd;
|
||||
pid_t pid;
|
||||
|
||||
pid = create_child(&pidfd, 0);
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
pause();
|
||||
|
||||
/* Request supported_mask field */
|
||||
ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
|
||||
|
||||
/* Verify PIDFD_INFO_SUPPORTED_MASK is set in the reply */
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_SUPPORTED_MASK));
|
||||
|
||||
/* Verify supported_mask contains expected flags */
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_PID));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_CGROUPID));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_COREDUMP));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_SUPPORTED_MASK));
|
||||
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_COREDUMP_SIGNAL));
|
||||
|
||||
/* Clean up */
|
||||
sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
|
||||
sys_waitid(P_PIDFD, pidfd, NULL, WEXITED);
|
||||
close(pidfd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test: PIDFD_INFO_SUPPORTED_MASK always available
|
||||
*
|
||||
* Verify that supported_mask is returned even when other fields are requested.
|
||||
*/
|
||||
TEST(supported_mask_with_other_fields)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_SUPPORTED_MASK,
|
||||
};
|
||||
int pidfd;
|
||||
pid_t pid;
|
||||
|
||||
pid = create_child(&pidfd, 0);
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
pause();
|
||||
|
||||
ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
|
||||
|
||||
/* Both fields should be present */
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CGROUPID));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_SUPPORTED_MASK));
|
||||
ASSERT_NE(info.supported_mask, 0);
|
||||
|
||||
/* Clean up */
|
||||
sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
|
||||
sys_waitid(P_PIDFD, pidfd, NULL, WEXITED);
|
||||
close(pidfd);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
||||
Reference in New Issue
Block a user