Several preparations for supporting new coredump interface (#39350)

This commit is contained in:
Yu Watanabe
2025-10-20 20:44:22 +09:00
committed by GitHub
9 changed files with 334 additions and 8 deletions
+27 -1
View File
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
#include "alloc-util.h"
#include "iovec-util.h"
#include "iovec-wrapper.h"
@@ -59,7 +61,7 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
return 0;
}
int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) {
int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) {
_cleanup_free_ char *x = NULL;
int r;
@@ -69,6 +71,14 @@ int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const c
if (!x)
return -ENOMEM;
if (replace)
FOREACH_ARRAY(iovec, iovw->iovec, iovw->count)
if (memory_startswith(iovec->iov_base, iovec->iov_len, field)) {
iovec->iov_len = strlen(x);
free_and_replace(iovec->iov_base, x);
return 0;
}
r = iovw_put(iovw, x, strlen(x));
if (r >= 0)
TAKE_PTR(x);
@@ -76,6 +86,22 @@ int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const c
return r;
}
int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *format, ...) {
_cleanup_free_ char *value = NULL;
va_list ap;
int r;
assert(format);
va_start(ap, format);
r = vasprintf(&value, format, ap);
va_end(ap);
if (r < 0)
return -ENOMEM;
return iovw_put_string_field_full(iovw, replace, field, value);
}
int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value) {
_cleanup_free_ _unused_ char *free_ptr = value;
+10 -1
View File
@@ -33,7 +33,16 @@ static inline bool iovw_isempty(const struct iovec_wrapper *iovw) {
return !iovw || iovw->count == 0;
}
int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value);
int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value);
static inline int iovw_put_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) {
return iovw_put_string_field_full(iovw, false, field, value);
}
static inline int iovw_replace_string_field(struct iovec_wrapper *iovw, const char *field, const char *value) {
return iovw_put_string_field_full(iovw, true, field, value);
}
int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *format, ...) _printf_(4, 5);
#define iovw_put_string_fieldf(iovw, ...) iovw_put_string_fieldf_full(iovw, false, __VA_ARGS__)
#define iovw_replace_string_fieldf(iovw, ...) iovw_put_string_fieldf_full(iovw, true, __VA_ARGS__)
int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value);
void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new);
size_t iovw_size(const struct iovec_wrapper *iovw);
+1 -1
View File
@@ -73,7 +73,7 @@ int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) {
return nsfd;
}
static int pidfd_get_info(int fd, struct pidfd_info *info) {
int pidfd_get_info(int fd, struct pidfd_info *info) {
static bool cached_supported = true;
assert(fd >= 0);
+2
View File
@@ -7,6 +7,8 @@
int pidfd_get_namespace(int fd, unsigned long ns_type_cmd);
int pidfd_get_info(int fd, struct pidfd_info *info);
int pidfd_get_pid(int fd, pid_t *ret);
int pidfd_verify_pid(int pidfd, pid_t pid);
+52 -5
View File
@@ -42,8 +42,56 @@ int missing_pidfd_send_signal(int fd, int sig, siginfo_t *info, unsigned flags);
/* defined in linux/pidfd.h */
#ifndef PIDFD_GET_INFO
/* Flags for pidfd_info. */
#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */
#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */
#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_SIZE_VER0 64 /* sizeof first published struct */
/*
* Values for @coredump_mask in pidfd_info.
* Only valid if PIDFD_INFO_COREDUMP is set in @mask.
*
* Note, the @PIDFD_COREDUMP_ROOT flag indicates that the generated
* coredump should be treated as sensitive and access should only be
* granted to privileged users.
*/
#define PIDFD_COREDUMPED (1U << 0) /* Did crash and... */
#define PIDFD_COREDUMP_SKIP (1U << 1) /* coredumping generation was skipped. */
#define PIDFD_COREDUMP_USER (1U << 2) /* coredump was done as the user. */
#define PIDFD_COREDUMP_ROOT (1U << 3) /* coredump was done as root. */
struct pidfd_info {
/*
* This mask is similar to the request_mask in statx(2).
*
* Userspace indicates what extensions or expensive-to-calculate fields
* they want by setting the corresponding bits in mask. The kernel
* will ignore bits that it does not know about.
*
* When filling the structure, the kernel will only set bits
* corresponding to the fields that were actually filled by the kernel.
* This also includes any future extensions that might be automatically
* filled. If the structure size is too small to contain a field
* (requested or not), to avoid confusion the mask will not
* contain a bit for that field.
*
* As such, userspace MUST verify that mask contains the
* corresponding flags after the ioctl(2) returns to ensure that it is
* using valid data.
*/
__u64 mask;
/*
* The information contained in the following fields might be stale at the
* time it is received, as the target process might have exited as soon as
* the IOCTL was processed, and there is no way to avoid that. However, it
* is guaranteed that if the call was successful, then the information was
* correct and referred to the intended process at the time the work was
* performed. */
__u64 cgroupid;
__u32 pid;
__u32 tgid;
@@ -56,11 +104,10 @@ struct pidfd_info {
__u32 sgid;
__u32 fsuid;
__u32 fsgid;
__u32 spare0[1];
__s32 exit_code; /* since kernel v6.15 (7477d7dce48a996ae4e4f0b5f7bd82de7ec9131b) */
__u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */
__u32 __spare1;
};
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
#define PIDFD_INFO_PID (1UL << 0)
#define PIDFD_INFO_CREDS (1UL << 1)
#define PIDFD_INFO_CGROUPID (1UL << 2)
#endif
#endif /* PIDFD_GET_INFO */
+104
View File
@@ -0,0 +1,104 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_COREDUMP_H
#define _LINUX_COREDUMP_H
#include <linux/types.h>
/**
* coredump_{req,ack} flags
* @COREDUMP_KERNEL: kernel writes coredump
* @COREDUMP_USERSPACE: userspace writes coredump
* @COREDUMP_REJECT: don't generate coredump
* @COREDUMP_WAIT: wait for coredump server
*/
enum {
COREDUMP_KERNEL = (1ULL << 0),
COREDUMP_USERSPACE = (1ULL << 1),
COREDUMP_REJECT = (1ULL << 2),
COREDUMP_WAIT = (1ULL << 3),
};
/**
* struct coredump_req - message kernel sends to userspace
* @size: size of struct coredump_req
* @size_ack: known size of struct coredump_ack on this kernel
* @mask: supported features
*
* When a coredump happens the kernel will connect to the coredump
* socket and send a coredump request to the coredump server. The @size
* member is set to the size of struct coredump_req and provides a hint
* to userspace how much data can be read. Userspace may use MSG_PEEK to
* peek the size of struct coredump_req and then choose to consume it in
* one go. Userspace may also simply read a COREDUMP_ACK_SIZE_VER0
* request. If the size the kernel sends is larger userspace simply
* discards any remaining data.
*
* The coredump_req->mask member is set to the currently know features.
* Userspace may only set coredump_ack->mask to the bits raised by the
* kernel in coredump_req->mask.
*
* The coredump_req->size_ack member is set by the kernel to the size of
* struct coredump_ack the kernel knows. Userspace may only send up to
* coredump_req->size_ack bytes to the kernel and must set
* coredump_ack->size accordingly.
*/
struct coredump_req {
__u32 size;
__u32 size_ack;
__u64 mask;
};
enum {
COREDUMP_REQ_SIZE_VER0 = 16U, /* size of first published struct */
};
/**
* struct coredump_ack - message userspace sends to kernel
* @size: size of the struct
* @spare: unused
* @mask: features kernel is supposed to use
*
* The @size member must be set to the size of struct coredump_ack. It
* may never exceed what the kernel returned in coredump_req->size_ack
* but it may of course be smaller (>= COREDUMP_ACK_SIZE_VER0 and <=
* coredump_req->size_ack).
*
* The @mask member must be set to the features the coredump server
* wants the kernel to use. Only bits the kernel returned in
* coredump_req->mask may be set.
*/
struct coredump_ack {
__u32 size;
__u32 spare;
__u64 mask;
};
enum {
COREDUMP_ACK_SIZE_VER0 = 16U, /* size of first published struct */
};
/**
* enum coredump_mark - Markers for the coredump socket
*
* The kernel will place a single byte on the coredump socket. The
* markers notify userspace whether the coredump ack succeeded or
* failed.
*
* @COREDUMP_MARK_MINSIZE: the provided coredump_ack size was too small
* @COREDUMP_MARK_MAXSIZE: the provided coredump_ack size was too big
* @COREDUMP_MARK_UNSUPPORTED: the provided coredump_ack mask was invalid
* @COREDUMP_MARK_CONFLICTING: the provided coredump_ack mask has conflicting options
* @COREDUMP_MARK_REQACK: the coredump request and ack was successful
* @__COREDUMP_MARK_MAX: the maximum coredump mark value
*/
enum coredump_mark {
COREDUMP_MARK_REQACK = 0U,
COREDUMP_MARK_MINSIZE = 1U,
COREDUMP_MARK_MAXSIZE = 2U,
COREDUMP_MARK_UNSUPPORTED = 3U,
COREDUMP_MARK_CONFLICTING = 4U,
__COREDUMP_MARK_MAX = (1U << 31),
};
#endif /* _LINUX_COREDUMP_H */
+68
View File
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sched.h>
#include <stdio.h>
#include <sys/utsname.h>
#include <unistd.h>
@@ -15,8 +16,12 @@
#include "hostname-setup.h"
#include "hostname-util.h"
#include "initrd-util.h"
#include "io-util.h"
#include "log.h"
#include "namespace-util.h"
#include "pidref.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "siphash24.h"
#include "string-table.h"
#include "string-util.h"
@@ -344,3 +349,66 @@ int gethostname_full(GetHostnameFlags flags, char **ret) {
*ret = TAKE_PTR(buf);
return 0;
}
int pidref_gethostname_full(PidRef *pidref, GetHostnameFlags flags, char **ret) {
int r;
assert(pidref);
assert(ret);
r = pidref_in_same_namespace(pidref, NULL, NAMESPACE_UTS);
if (r < 0)
return r;
if (r > 0)
return gethostname_full(flags, ret);
_cleanup_close_ int utsns_fd = r = pidref_namespace_open_by_type(pidref, NAMESPACE_UTS);
if (r < 0)
return r;
_cleanup_close_pair_ int errno_pipe[2] = EBADF_PAIR;
r = pipe2(errno_pipe, O_CLOEXEC);
if (r < 0)
return -errno;
_cleanup_close_pair_ int result_pipe[2] = EBADF_PAIR;
r = pipe2(result_pipe, O_CLOEXEC);
if (r < 0)
return -errno;
_cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL;
r = pidref_safe_fork("(gethostname)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &child);
if (r < 0)
return r;
if (r == 0) {
errno_pipe[0] = safe_close(errno_pipe[0]);
result_pipe[0] = safe_close(result_pipe[0]);
if (setns(utsns_fd, CLONE_NEWUTS) < 0)
report_errno_and_exit(errno_pipe[1], -errno);
char *t;
r = gethostname_full(flags, &t);
if (r < 0)
report_errno_and_exit(errno_pipe[1], r);
r = loop_write(result_pipe[1], t, strlen(t) + 1);
report_errno_and_exit(errno_pipe[1], r);
}
errno_pipe[1] = safe_close(errno_pipe[1]);
result_pipe[1] = safe_close(result_pipe[1]);
r = read_errno(errno_pipe[0]);
if (r < 0)
return r;
char buf[HOST_NAME_MAX+1];
ssize_t n = loop_read(result_pipe[0], buf, sizeof(buf), /* do_poll = */ false);
if (n < 0)
return n;
if (n == 0 || buf[n - 1] != '\0')
return -EPROTO;
return strdup_to(ret, buf);
}
+2
View File
@@ -56,3 +56,5 @@ static inline char* gethostname_short_malloc(void) {
return s;
}
int pidref_gethostname_full(PidRef *pidref, GetHostnameFlags flags, char **ret);
+68
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fnmatch.h>
#include <sched.h>
#include <stdlib.h>
#include "alloc-util.h"
@@ -9,6 +10,8 @@
#include "hostname-setup.h"
#include "hostname-util.h"
#include "id128-util.h"
#include "pidref.h"
#include "process-util.h"
#include "tests.h"
#include "tmpfile-util.h"
@@ -132,4 +135,69 @@ TEST(default_hostname) {
ASSERT_TRUE(hostname_is_valid(m, VALID_HOSTNAME_QUESTION_MARK));
}
TEST(pidref_gethostname_full) {
int r;
if (geteuid() != 0)
return (void) log_tests_skipped("Not privileged");
_cleanup_free_ char *original = NULL, *original_short = NULL;
ASSERT_NOT_NULL(original = gethostname_malloc());
ASSERT_NOT_NULL(original_short = gethostname_short_malloc());
_cleanup_close_pair_ int fds[2] = EBADF_PAIR;
ASSERT_OK_ERRNO(pipe2(fds, O_CLOEXEC));
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
r = pidref_safe_fork("(test-pidref-gethostname)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, &pidref);
ASSERT_OK(r);
if (r == 0) {
fds[0] = safe_close(fds[0]);
ASSERT_OK_ERRNO(unshare(CLONE_NEWUTS));
ASSERT_OK(sethostname_idempotent("hogehoge.example.com"));
ASSERT_OK_EQ_ERRNO(write(fds[1], &(const char[]) { 'x' }, 1), 1);
freeze();
}
fds[1] = safe_close(fds[1]);
char x;
ASSERT_OK_EQ_ERRNO(read(fds[0], &x, 1), 1);
ASSERT_EQ(x, 'x');
_cleanup_free_ char *s = NULL;
ASSERT_OK(pidref_gethostname_full(&pidref, /* flags= */ 0, &s));
ASSERT_STREQ(s, "hogehoge.example.com");
s = mfree(s);
ASSERT_OK(pidref_gethostname_full(&pidref, GET_HOSTNAME_SHORT, &s));
ASSERT_STREQ(s, "hogehoge");
s = mfree(s);
_cleanup_(pidref_done) PidRef self = PIDREF_NULL;
ASSERT_OK(pidref_set_self(&self));
ASSERT_OK(pidref_gethostname_full(&self, /* flags= */ 0, &s));
ASSERT_STREQ(s, original);
s = mfree(s);
ASSERT_OK(pidref_gethostname_full(&self, GET_HOSTNAME_SHORT, &s));
ASSERT_STREQ(s, original_short);
s = mfree(s);
ASSERT_NOT_NULL(s = gethostname_malloc());
ASSERT_STREQ(s, original);
s = mfree(s);
ASSERT_NOT_NULL(s = gethostname_short_malloc());
ASSERT_STREQ(s, original_short);
}
DEFINE_TEST_MAIN(LOG_DEBUG);