From b31a571849a164023e53937c31f7c2e67543aaa7 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Oct 2025 18:00:31 +0900 Subject: [PATCH 1/6] include: import linux/coredump.h from kernel v6.17.1 --- src/include/uapi/linux/coredump.h | 104 ++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/include/uapi/linux/coredump.h diff --git a/src/include/uapi/linux/coredump.h b/src/include/uapi/linux/coredump.h new file mode 100644 index 0000000000..8d6fe6f289 --- /dev/null +++ b/src/include/uapi/linux/coredump.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _LINUX_COREDUMP_H +#define _LINUX_COREDUMP_H + +#include + +/** + * 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 */ From 208f88def373dafb970e4c4fd627b813d0e80162 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Oct 2025 18:02:44 +0900 Subject: [PATCH 2/6] include: update struct pidfd_info and relevant constants --- src/include/override/sys/pidfd.h | 57 +++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index 9632afc95f..7f136eb62e 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -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 */ From 0c3750849429f0e203eaee5b3ca7c5a92c43ed85 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Tue, 7 Oct 2025 18:03:32 +0900 Subject: [PATCH 3/6] pidfd-util: expose pidfd_get_info() --- src/basic/pidfd-util.c | 2 +- src/basic/pidfd-util.h | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/basic/pidfd-util.c b/src/basic/pidfd-util.c index b6614ffd3a..4b4caf5c9c 100644 --- a/src/basic/pidfd-util.c +++ b/src/basic/pidfd-util.c @@ -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); diff --git a/src/basic/pidfd-util.h b/src/basic/pidfd-util.h index 2050734ab2..74dd1d96a2 100644 --- a/src/basic/pidfd-util.h +++ b/src/basic/pidfd-util.h @@ -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); From a5cdc2be707c6fb4b269f824bceb4c9ea5746244 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Wed, 15 Oct 2025 09:29:18 +0900 Subject: [PATCH 4/6] iovec-wrapper: introduce iovw_replace_string_field() --- src/basic/iovec-wrapper.c | 10 +++++++++- src/basic/iovec-wrapper.h | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 5cc3cc2f93..0071893411 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -59,7 +59,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 +69,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); diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 6bc4d3905a..33a91df1ca 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -33,7 +33,13 @@ 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_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); From d10596a6e02299ad86008fd5230a1420677c4c6e Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Thu, 9 Oct 2025 04:10:20 +0900 Subject: [PATCH 5/6] iovec-wrapper: introduce iovw_put_string_fieldf() --- src/basic/iovec-wrapper.c | 18 ++++++++++++++++++ src/basic/iovec-wrapper.h | 3 +++ 2 files changed, 21 insertions(+) diff --git a/src/basic/iovec-wrapper.c b/src/basic/iovec-wrapper.c index 0071893411..b4519f808d 100644 --- a/src/basic/iovec-wrapper.c +++ b/src/basic/iovec-wrapper.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "iovec-util.h" #include "iovec-wrapper.h" @@ -84,6 +86,22 @@ int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, 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; diff --git a/src/basic/iovec-wrapper.h b/src/basic/iovec-wrapper.h index 33a91df1ca..94b39feb15 100644 --- a/src/basic/iovec-wrapper.h +++ b/src/basic/iovec-wrapper.h @@ -40,6 +40,9 @@ static inline int iovw_put_string_field(struct iovec_wrapper *iovw, const char * 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); From 64595f1cb34a2d237063d827dc480fce44fc4235 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 11 Oct 2025 12:30:05 +0900 Subject: [PATCH 6/6] hostname-setup: introduce pidref_gethostname_full() --- src/shared/hostname-setup.c | 68 ++++++++++++++++++++++++++++++++++ src/shared/hostname-setup.h | 2 + src/test/test-hostname-setup.c | 68 ++++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index ac13d06fb2..4e977eed12 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include #include @@ -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); +} diff --git a/src/shared/hostname-setup.h b/src/shared/hostname-setup.h index 9af57fad56..7f6216ce6e 100644 --- a/src/shared/hostname-setup.h +++ b/src/shared/hostname-setup.h @@ -56,3 +56,5 @@ static inline char* gethostname_short_malloc(void) { return s; } + +int pidref_gethostname_full(PidRef *pidref, GetHostnameFlags flags, char **ret); diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c index 87c9f7d751..e3fc8a220b 100644 --- a/src/test/test-hostname-setup.c +++ b/src/test/test-hostname-setup.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #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);