diff --git a/criu/cr-check.c b/criu/cr-check.c index 67a49ee28..881ce47b1 100644 --- a/criu/cr-check.c +++ b/criu/cr-check.c @@ -1360,6 +1360,16 @@ static int check_sockopt_buf_lock(void) return 0; } +static int check_so_passpidfd(void) +{ + if (!kdat.has_so_passpidfd) { + pr_warn("SO_PASSPIDFD is not supported\n"); + return -1; + } + + return 0; +} + static int check_move_mount_set_group(void) { if (!kdat.has_move_mount_set_group) @@ -1721,6 +1731,7 @@ int cr_check(void) ret |= check_overlayfs_maps(); ret |= check_timer_cr_ids(); ret |= check_pagemap_scan_guard_pages(); + ret |= check_so_passpidfd(); if (kdat.lsm == LSMTYPE__APPARMOR) ret |= check_apparmor_stacking(); @@ -1870,6 +1881,7 @@ static struct feature_list feature_list[] = { { "overlayfs_maps", check_overlayfs_maps }, { "pagemap_scan_guard_pages", check_pagemap_scan_guard_pages }, { "binfmt_misc_sandboxing", check_binfmt_misc_sandboxing }, + { "so_passpidfd", check_so_passpidfd }, { "compress", check_compress }, { NULL, NULL }, }; diff --git a/criu/include/kerndat.h b/criu/include/kerndat.h index 27bdfe557..f23b5a609 100644 --- a/criu/include/kerndat.h +++ b/criu/include/kerndat.h @@ -93,6 +93,7 @@ struct kerndat_s { bool has_madv_guard; bool has_pagemap_scan_guard_pages; bool has_binfmt_misc_sandboxing; + bool has_so_passpidfd; }; extern struct kerndat_s kdat; diff --git a/criu/include/pidfd.h b/criu/include/pidfd.h index bcc0fb45a..2ddce7786 100644 --- a/criu/include/pidfd.h +++ b/criu/include/pidfd.h @@ -8,6 +8,8 @@ extern const struct fdtype_ops pidfd_dump_ops; extern struct collect_image_info pidfd_cinfo; extern int is_pidfd_link(char *link); extern void init_dead_pidfd_hash(void); +extern int create_tmp_process(void); +extern int kill_helper(pid_t pid); struct pidfd_dump_info { PidfdEntry pidfe; pid_t pid; diff --git a/criu/include/sk-queue.h b/criu/include/sk-queue.h index 6723c6c7a..af5527d88 100644 --- a/criu/include/sk-queue.h +++ b/criu/include/sk-queue.h @@ -4,6 +4,7 @@ extern struct collect_image_info sk_queues_cinfo; #define SK_QUEUE_REAL_PID 0x1 /* scm creds contains a real pid */ +#define SK_QUEUE_ALLOW_STALE_PID 0x2 /* receiver mints pidfds from skb pids, keep packets of dead senders */ extern int dump_sk_queue(int sock_fd, int sock_id, int flags); extern int sk_queue_post_actions(void); extern int restore_sk_queue(int fd, unsigned int peer_id); diff --git a/criu/include/sockets.h b/criu/include/sockets.h index 764cb93b2..be6734c52 100644 --- a/criu/include/sockets.h +++ b/criu/include/sockets.h @@ -129,4 +129,16 @@ extern const char *socket_proto_name(unsigned int proto, char *nm, size_t size); #define SO_BUF_LOCK 72 #endif +#ifndef SO_PASSPIDFD +#define SO_PASSPIDFD 76 +#endif + +#ifndef SO_PEERPIDFD +#define SO_PEERPIDFD 77 +#endif + +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + #endif /* __CR_SOCKETS_H__ */ diff --git a/criu/kerndat.c b/criu/kerndat.c index 1e4863190..b9b7f0433 100644 --- a/criu/kerndat.c +++ b/criu/kerndat.c @@ -1072,6 +1072,35 @@ err: return exit_code; } +static int kerndat_has_so_passpidfd(void) +{ + int exit_code = -1; + socklen_t len; + int val; + int sock; + + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) { + pr_perror("Unable to create a unix socket"); + return -1; + } + + len = sizeof(val); + if (getsockopt(sock, SOL_SOCKET, SO_PASSPIDFD, &val, &len)) { + if (errno != ENOPROTOOPT) { + pr_perror("Unable to get SO_PASSPIDFD with getsockopt"); + goto err; + } + kdat.has_so_passpidfd = false; + } else + kdat.has_so_passpidfd = true; + + exit_code = 0; +err: + close(sock); + return exit_code; +} + static int kerndat_has_move_mount_set_group(void) { char tmpdir[] = "/tmp/.criu.move_mount_set_group.XXXXXX"; @@ -2090,6 +2119,10 @@ int kerndat_init(void) pr_err("kerndat_sockopt_buf_lock failed when initializing kerndat.\n"); ret = -1; } + if (!ret && kerndat_has_so_passpidfd()) { + pr_err("kerndat_has_so_passpidfd failed when initializing kerndat.\n"); + ret = -1; + } if (!ret && kerndat_has_openat2()) { pr_err("kerndat_has_openat2 failed when initializing kerndat.\n"); ret = -1; diff --git a/criu/pidfd.c b/criu/pidfd.c index ae32025b0..bf7449f60 100644 --- a/criu/pidfd.c +++ b/criu/pidfd.c @@ -121,7 +121,7 @@ static int pidfd_open(pid_t pid, int flags) return syscall(__NR_pidfd_open, pid, flags); } -static int create_tmp_process(void) +int create_tmp_process(void) { int tmp_process; tmp_process = fork(); @@ -135,7 +135,7 @@ static int create_tmp_process(void) return tmp_process; } -static int kill_helper(pid_t pid) +int kill_helper(pid_t pid) { int status; sigset_t blockmask, oldmask; diff --git a/criu/sk-queue.c b/criu/sk-queue.c index f18ee91a3..2fce4f596 100644 --- a/criu/sk-queue.c +++ b/criu/sk-queue.c @@ -11,6 +11,7 @@ #include "common/list.h" #include "imgset.h" +#include "kerndat.h" #include "image.h" #include "servicefd.h" #include "cr_options.h" @@ -23,6 +24,7 @@ #include "util.h" #include "sk-queue.h" +#include "pidfd.h" #include "files.h" #include "protobuf.h" #include "images/sk-packet.pb-c.h" @@ -39,6 +41,7 @@ struct sk_packet { }; unsigned scm_len; int *scm; + bool allow_stale_pid; }; static LIST_HEAD(packets_list); @@ -172,23 +175,43 @@ int sk_queue_post_actions(void) struct pstree_item *item, *found = NULL; SkUcredEntry *ue = pkt->entry->ucred; - for_each_pstree_item(item) { - if (item->pid->real == ue->pid) { - found = item; - break; + /* + * A zero pid means the kernel attached no struct pid + * to the skb (credless sender), there is nothing to + * fix up, write the packet as is. + */ + if (ue->pid != 0) { + for_each_pstree_item(item) { + if (item->pid->real == ue->pid) { + found = item; + break; + } + } + + if (found) { + pr_debug("ucred: Fixup ucred pids %d -> %d\n", + ue->pid, vpid(item)); + ue->pid = vpid(item); + } else if (pkt->allow_stale_pid) { + /* + * The sender is gone, but the receiver + * has SO_PASSPIDFD: a pidfd minted from + * the skb pid is stale both before and + * after C/R, so the exact pid doesn't + * matter. Mark the packet, restore will + * attach a short-lived helper pid. + */ + pr_debug("ucred: Sender pid %d is dead, marking packet stale on id_for %x\n", + ue->pid, pkt->entry->id_for); + ue->has_stale = true; + ue->stale = true; + } else { + pr_warn("ucred: Can't find process with pid %d, ignoring packet\n", + ue->pid); + goto next; } } - if (!found) { - pr_warn("ucred: Can't find process with pid %d, ignoring packet\n", - ue->pid); - goto next; - } - - pr_debug("ucred: Fixup ucred pids %d -> %d\n", - ue->pid, vpid(item)); - ue->pid = vpid(item); - ret = pb_write_one(img, pkt->entry, PB_SK_QUEUES); if (ret < 0) { ret = -EIO; @@ -211,7 +234,7 @@ next: return ret; } -static int queue_packet_entry(SkPacketEntry *entry, void *data, size_t len) +static int queue_packet_entry(SkPacketEntry *entry, void *data, size_t len, int flags) { SkPacketEntry *pe; SkUcredEntry *ue; @@ -238,6 +261,7 @@ static int queue_packet_entry(SkPacketEntry *entry, void *data, size_t len) pkt->entry = pe; pkt->data_off = p - (void *)pkt; + pkt->allow_stale_pid = !!(flags & SK_QUEUE_ALLOW_STALE_PID); pe->id_for = entry->id_for; pe->length = entry->length; @@ -381,6 +405,20 @@ static int dump_packet_cmsg(struct msghdr *mh, SkPacketEntry *pe, int flags) if (ret < 0) return -1; continue; + } else if (ch->cmsg_len == CMSG_LEN(sizeof(int)) && + ch->cmsg_type == SCM_PIDFD) { + int pidfd = *(int *)CMSG_DATA(ch); + + /* + * The skb pid the pidfd refers to is dumped + * from the SCM_CREDENTIALS cmsg (SO_PASSCRED + * is enabled for the peek when the socket has + * SO_PASSPIDFD), so just close the fd that + * recvmsg installed. + */ + pr_debug("Closing pidfd %d received on queue peek\n", pidfd); + close(pidfd); + continue; } else if (ch->cmsg_type == SCM_TIMESTAMP || ch->cmsg_type == SCM_TIMESTAMPNS || ch->cmsg_type == SCM_TIMESTAMPING) { @@ -448,7 +486,7 @@ static int dump_sk_queue_packet(int sock_fd, int sock_id, void *data, int size, if (ret > 0) { if (ret == 1) { - if (queue_packet_entry(&pe, data, pe.length)) + if (queue_packet_entry(&pe, data, pe.length, flags)) goto cleanup_packet; } exit_code = 0; @@ -475,6 +513,7 @@ int dump_sk_queue(int sock_fd, int sock_id, int flags) { int ret, size, orig_peek_off; int exit_code = -1; + int passcred_set = 0; void *data; socklen_t tmp; @@ -518,6 +557,47 @@ int dump_sk_queue(int sock_fd, int sock_id, int flags) goto err_free; } + /* + * If the socket has SO_PASSPIDFD but not SO_PASSCRED, peeked packets + * carry an SCM_PIDFD cmsg only, which loses the skb uid/gid and needs + * a pidfd -> pid conversion. Both PASS* options are just recvmsg-time + * views of the same pid/creds the kernel attached to the skb at send + * time, so temporarily enable SO_PASSCRED to make every packet yield + * a full SCM_CREDENTIALS cmsg instead. + */ + if (kdat.has_so_passpidfd) { + int passpidfd = 0, passcred = 0; + + tmp = sizeof(passpidfd); + if (getsockopt(sock_fd, SOL_SOCKET, SO_PASSPIDFD, &passpidfd, &tmp)) { + pr_perror("Unable to get SO_PASSPIDFD"); + goto err_set_sock; + } + + /* + * A pidfd minted from the pid of a dead sender is stale + * both before and after C/R, so such packets can be kept. + */ + if (passpidfd) + flags |= SK_QUEUE_ALLOW_STALE_PID; + + tmp = sizeof(passcred); + if (getsockopt(sock_fd, SOL_SOCKET, SO_PASSCRED, &passcred, &tmp)) { + pr_perror("Unable to get SO_PASSCRED"); + goto err_set_sock; + } + + if (passpidfd && !passcred) { + int one = 1; + + if (setsockopt(sock_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one))) { + pr_perror("Unable to set SO_PASSCRED"); + goto err_set_sock; + } + passcred_set = 1; + } + } + while (1) { ret = dump_sk_queue_packet(sock_fd, sock_id, data, size, flags); if (ret == 1) @@ -528,6 +608,14 @@ int dump_sk_queue(int sock_fd, int sock_id, int flags) exit_code = 0; err_set_sock: + if (passcred_set) { + int zero = 0; + + if (setsockopt(sock_fd, SOL_SOCKET, SO_PASSCRED, &zero, sizeof(zero))) { + pr_perror("Unable to drop SO_PASSCRED back"); + exit_code = -1; + } + } /* * Restore original peek offset. */ @@ -549,6 +637,7 @@ static int send_one_pkt(int fd, struct sk_packet *pkt) struct iovec iov; char cmsg[CMSG_MAX_SIZE]; struct cmsghdr *ch = NULL; + pid_t helper_pid = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; @@ -595,7 +684,7 @@ static int send_one_pkt(int fd, struct sk_packet *pkt) * boundaries messages should be saved. */ - if (entry->ucred && entry->ucred->pid) { + if (entry->ucred && (entry->ucred->pid || entry->ucred->stale)) { struct ucred *ucred; ch = ch ? CMSG_NXTHDR(&mh, ch) : CMSG_FIRSTHDR(&mh); @@ -609,15 +698,30 @@ static int send_one_pkt(int fd, struct sk_packet *pkt) CMSG_SPACE(sizeof(struct ucred)) >= sizeof(cmsg)); ucred = (struct ucred *)CMSG_DATA(ch); - ucred->pid = entry->ucred->pid; + if (entry->ucred->stale) { + /* + * The sender died before the dump. Attach the pid of + * a short-lived helper process to the skb; the helper + * is killed right after sendmsg(), so a receiver with + * SO_PASSPIDFD gets a stale pidfd, just as before the + * dump. + */ + helper_pid = create_tmp_process(); + if (helper_pid < 0) + return -1; + ucred->pid = helper_pid; + } else { + ucred->pid = entry->ucred->pid; + } ucred->uid = entry->ucred->uid; ucred->gid = entry->ucred->gid; msg_controllen += CMSG_SPACE(sizeof(struct ucred)); - pr_debug("\tsend creds pid %d uid %d gid %d\n", - entry->ucred->pid, - entry->ucred->uid, - entry->ucred->gid); + pr_debug("\tsend creds pid %d uid %d gid %d%s\n", + ucred->pid, + ucred->uid, + ucred->gid, + entry->ucred->stale ? " (stale pid helper)" : ""); } mh.msg_controllen = msg_controllen; @@ -625,6 +729,10 @@ static int send_one_pkt(int fd, struct sk_packet *pkt) ret = sendmsg(fd, &mh, 0); xfree(pkt->data); xfree(pkt->scm); + + if (helper_pid > 0 && kill_helper(helper_pid)) + return -1; + if (ret < 0) { pr_perror("Failed to send packet"); return -1; diff --git a/criu/sockets.c b/criu/sockets.c index 7778b4688..54cbc327e 100644 --- a/criu/sockets.c +++ b/criu/sockets.c @@ -567,6 +567,10 @@ int restore_socket_opts(int sk, SkOptsEntry *soe) pr_debug("\tset passcred for socket\n"); ret |= restore_opt(sk, SOL_SOCKET, SO_PASSCRED, &val); } + if (soe->has_so_passpidfd && soe->so_passpidfd) { + pr_debug("\tset passpidfd for socket\n"); + ret |= restore_opt(sk, SOL_SOCKET, SO_PASSPIDFD, &val); + } if (soe->has_so_passsec && soe->so_passsec) { pr_debug("\tset passsec for socket\n"); ret |= restore_opt(sk, SOL_SOCKET, SO_PASSSEC, &val); @@ -696,6 +700,17 @@ int dump_socket_opts(int sk, int family, SkOptsEntry *soe) ret |= dump_opt(sk, SOL_SOCKET, SO_PASSSEC, &val); soe->has_so_passsec = true; soe->so_passsec = val ? true : false; + + /* + * Since Linux 6.16 SO_PASSPIDFD is restricted to unix + * sockets and get/setsockopt fails with EOPNOTSUPP on + * netlink ones. + */ + if (family == AF_UNIX && kdat.has_so_passpidfd) { + ret |= dump_opt(sk, SOL_SOCKET, SO_PASSPIDFD, &val); + soe->has_so_passpidfd = true; + soe->so_passpidfd = val ? true : false; + } } ret |= dump_opt(sk, SOL_SOCKET, SO_DONTROUTE, &val); diff --git a/images/sk-opts.proto b/images/sk-opts.proto index 2f9d4e5c3..2913cc77c 100644 --- a/images/sk-opts.proto +++ b/images/sk-opts.proto @@ -36,6 +36,8 @@ message sk_opts_entry { optional uint32 so_linger = 24; optional uint32 so_buf_lock = 25; + + optional bool so_passpidfd = 26; } enum sk_shutdown { diff --git a/images/sk-packet.proto b/images/sk-packet.proto index 341892607..202859e75 100644 --- a/images/sk-packet.proto +++ b/images/sk-packet.proto @@ -11,6 +11,8 @@ message sk_ucred_entry { required uint32 uid = 1; required uint32 gid = 2; required uint32 pid = 3; + // The sender died before the dump, pid can't be restored + optional bool stale = 4; } message sk_packet_entry { diff --git a/test/zdtm/static/Makefile b/test/zdtm/static/Makefile index 83cc5bec0..6169b55f1 100644 --- a/test/zdtm/static/Makefile +++ b/test/zdtm/static/Makefile @@ -488,6 +488,9 @@ TST_DIR = \ sk-unix01 \ sk-unix01-seqpacket \ sk-unix-dgram-cred \ + sk-unix-dgram-pidfd \ + sk-unix-dgram-pidfd-cred \ + sk-unix-dgram-pidfd-stale \ sk-unix-dgram-ghost \ unsupported_children_collision \ shared_slave_mount_children \ diff --git a/test/zdtm/static/sk-unix-dgram-pidfd-cred.c b/test/zdtm/static/sk-unix-dgram-pidfd-cred.c new file mode 100644 index 000000000..6c19697ba --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd-cred.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zdtmtst.h" + +#ifndef SO_PASSPIDFD +#define SO_PASSPIDFD 76 +#endif + +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + +const char *test_doc = "Test SCM_CREDENTIALS + SCM_PIDFD checkpoint/restore in one unix socket queue\n"; +const char *test_author = "Ahmed Elaidy "; + +char *dirname; +TEST_OPTION(dirname, string, "directory name", 1); + +static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) +{ + return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags); +} + +static pid_t pidfd_get_pid(int pidfd) +{ + char path[64]; + char line[256]; + pid_t pid = -1; + FILE *f; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd); + f = fopen(path, "r"); + if (!f) { + pr_perror("fopen %s", path); + return -1; + } + + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "Pid: %d", &pid) == 1) + break; + } + + fclose(f); + return pid; +} + +int main(int argc, char *argv[]) +{ + int sk[2]; + struct msghdr msg = {}; + struct iovec iov; + char buf[64]; + char cmsg_buf[CMSG_SPACE(sizeof(struct ucred)) + CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + struct ucred send_cred; + struct ucred *cred = NULL; + int opt = 1; + int pidfd = -1; + pid_t pid; + + test_init(argc, argv); + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sk) < 0) + return pr_perror("socketpair"); + + if (setsockopt(sk[1], SOL_SOCKET, SO_PASSCRED, &opt, sizeof(opt)) < 0) + return pr_perror("setsockopt SO_PASSCRED"); + + if (setsockopt(sk[1], SOL_SOCKET, SO_PASSPIDFD, &opt, sizeof(opt)) < 0) + return pr_perror("setsockopt SO_PASSPIDFD"); + + /* Send a message with explicit SCM_CREDENTIALS */ + send_cred.pid = getpid(); + send_cred.uid = getuid(); + send_cred.gid = getgid(); + + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_SPACE(sizeof(struct ucred)); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + memcpy(CMSG_DATA(cmsg), &send_cred, sizeof(struct ucred)); + + strcpy(buf, "hello"); + if (sendmsg(sk[0], &msg, 0) < 0) + return pr_perror("sendmsg"); + + test_daemon(); + test_waitsig(); + + /* Both cmsgs must arrive in one recvmsg after restore */ + memset(buf, 0, sizeof(buf)); + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + if (recvmsg(sk[1], &msg, 0) < 0) + return pr_perror("recvmsg"); + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + if (cmsg->cmsg_type == SCM_CREDENTIALS) + cred = (struct ucred *)CMSG_DATA(cmsg); + else if (cmsg->cmsg_type == SCM_PIDFD) + memcpy(&pidfd, CMSG_DATA(cmsg), sizeof(pidfd)); + } + + if (!cred) { + fail("no SCM_CREDENTIALS after restore"); + return 1; + } + + if (pidfd < 0) { + fail("no SCM_PIDFD after restore"); + return 1; + } + + if (cred->uid != send_cred.uid || cred->gid != send_cred.gid) { + fail("credentials mismatch after restore: " + "uid %d/%d gid %d/%d", + cred->uid, send_cred.uid, + cred->gid, send_cred.gid); + return 1; + } + + if (pidfd_send_signal(pidfd, 0, NULL, 0)) + return pr_perror("pidfd_send_signal"); + + /* The pidfd and the creds are two views of the same skb pid */ + pid = pidfd_get_pid(pidfd); + if (pid != cred->pid || pid != getpid()) { + fail("pid mismatch after restore: pidfd %d cred %d self %d", + pid, cred->pid, getpid()); + return 1; + } + + close(pidfd); + close(sk[0]); + close(sk[1]); + + pass(); + return 0; +} diff --git a/test/zdtm/static/sk-unix-dgram-pidfd-cred.desc b/test/zdtm/static/sk-unix-dgram-pidfd-cred.desc new file mode 100644 index 000000000..8e9526ec4 --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd-cred.desc @@ -0,0 +1 @@ +{'flavor': 'h ns uns', 'feature': 'so_passpidfd'} diff --git a/test/zdtm/static/sk-unix-dgram-pidfd-stale.c b/test/zdtm/static/sk-unix-dgram-pidfd-stale.c new file mode 100644 index 000000000..54aac4d21 --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd-stale.c @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zdtmtst.h" + +#ifndef SO_PASSPIDFD +#define SO_PASSPIDFD 76 +#endif + +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + +const char *test_doc = "Test C/R of a stale pidfd in unix socket queue (sender died before dump)\n"; +const char *test_author = "Ahmed Elaidy "; + +char *dirname; +TEST_OPTION(dirname, string, "directory name", 1); + +static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) +{ + return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags); +} + +static pid_t pidfd_get_pid(int pidfd) +{ + char path[64]; + char line[256]; + pid_t pid = -2; + FILE *f; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd); + f = fopen(path, "r"); + if (!f) { + pr_perror("fopen %s", path); + return -2; + } + + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "Pid: %d", &pid) == 1) + break; + } + + fclose(f); + return pid; +} + +/* + * Queue one message into sk_snd from a child which exits and is + * reaped before we return, so the skb pid points to a dead process. + */ +static int queue_msg_from_dead_child(int sk_snd) +{ + int status; + pid_t child; + + child = fork(); + if (child < 0) + return pr_perror("fork"); + + if (child == 0) { + char buf[] = "hello"; + struct iovec iov = { + .iov_base = buf, + .iov_len = sizeof(buf), + }; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + if (sendmsg(sk_snd, &msg, 0) < 0) + _exit(1); + _exit(0); + } + + if (waitpid(child, &status, 0) != child) + return pr_perror("waitpid"); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + pr_err("child failed to send\n"); + return -1; + } + + return 0; +} + +static int recv_pidfd(int sk_rcv, int *pidfd) +{ + struct msghdr msg = {}; + struct iovec iov; + char buf[64]; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + + memset(buf, 0, sizeof(buf)); + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + if (recvmsg(sk_rcv, &msg, 0) < 0) + return pr_perror("recvmsg"); + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) { + pr_err("no cmsg\n"); + return -1; + } + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_PIDFD) { + pr_err("wrong cmsg: level %d type %d\n", + cmsg->cmsg_level, cmsg->cmsg_type); + return -1; + } + + memcpy(pidfd, CMSG_DATA(cmsg), sizeof(*pidfd)); + return 0; +} + +/* + * Only kernels >= 6.17 can mint a pidfd for an already reaped + * process; older ones put a negative error code into the cmsg + * payload instead. Probe which behavior this kernel has, so we can + * check that C/R preserves it. + */ +static int probe_reaped_pidfd(void) +{ + int sk[2]; + int opt = 1; + int pidfd; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sk) < 0) { + pr_perror("socketpair"); + return -1; + } + + if (setsockopt(sk[1], SOL_SOCKET, SO_PASSPIDFD, &opt, sizeof(opt)) < 0) { + pr_perror("setsockopt SO_PASSPIDFD"); + return -1; + } + + if (queue_msg_from_dead_child(sk[0])) + return -1; + + if (recv_pidfd(sk[1], &pidfd)) + return -1; + + close(sk[0]); + close(sk[1]); + + if (pidfd < 0) + return 0; + + close(pidfd); + return 1; +} + +int main(int argc, char *argv[]) +{ + int sk[2]; + int opt = 1; + int pidfd; + int reaped_pidfd_supported; + + test_init(argc, argv); + + reaped_pidfd_supported = probe_reaped_pidfd(); + if (reaped_pidfd_supported < 0) + return 1; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sk) < 0) + return pr_perror("socketpair"); + + if (setsockopt(sk[1], SOL_SOCKET, SO_PASSPIDFD, &opt, sizeof(opt)) < 0) + return pr_perror("setsockopt SO_PASSPIDFD"); + + if (queue_msg_from_dead_child(sk[0])) + return 1; + + test_daemon(); + test_waitsig(); + + /* The packet must survive C/R and yield a stale pidfd */ + if (recv_pidfd(sk[1], &pidfd)) { + fail("no SCM_PIDFD cmsg after restore"); + return 1; + } + + if (!reaped_pidfd_supported) { + /* + * Pre-dump recvmsg would have failed to mint a pidfd + * for the dead sender the same way. + */ + if (pidfd >= 0) { + close(pidfd); + fail("got pidfd %d, but kernel can't mint pidfds of reaped processes", pidfd); + return 1; + } + test_msg("kernel can't mint pidfds of reaped processes, got %d as before dump\n", pidfd); + } else { + if (pidfd < 0) { + fail("invalid pidfd %d after restore", pidfd); + return 1; + } + + /* The pidfd must reference a dead process, as before the dump */ + if (pidfd_send_signal(pidfd, 0, NULL, 0) == 0 || errno != ESRCH) { + fail("pidfd is not stale after restore"); + return 1; + } + + if (pidfd_get_pid(pidfd) != -1) { + fail("pidfd fdinfo Pid is not -1 after restore"); + return 1; + } + + close(pidfd); + } + + close(sk[0]); + close(sk[1]); + + pass(); + return 0; +} diff --git a/test/zdtm/static/sk-unix-dgram-pidfd-stale.desc b/test/zdtm/static/sk-unix-dgram-pidfd-stale.desc new file mode 100644 index 000000000..8e9526ec4 --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd-stale.desc @@ -0,0 +1 @@ +{'flavor': 'h ns uns', 'feature': 'so_passpidfd'} diff --git a/test/zdtm/static/sk-unix-dgram-pidfd.c b/test/zdtm/static/sk-unix-dgram-pidfd.c new file mode 100644 index 000000000..40109a778 --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zdtmtst.h" + +#ifndef SO_PASSPIDFD +#define SO_PASSPIDFD 76 +#endif + +#ifndef SCM_PIDFD +#define SCM_PIDFD 0x04 +#endif + +const char *test_doc = "Test SCM_PIDFD checkpoint/restore in unix socket queue\n"; +const char *test_author = "Ahmed Elaidy "; + +char *dirname; +TEST_OPTION(dirname, string, "directory name", 1); + +static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) +{ + return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags); +} + +static pid_t pidfd_get_pid(int pidfd) +{ + char path[64]; + char line[256]; + pid_t pid = -1; + FILE *f; + + snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd); + f = fopen(path, "r"); + if (!f) { + pr_perror("fopen %s", path); + return -1; + } + + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "Pid: %d", &pid) == 1) + break; + } + + fclose(f); + return pid; +} + +int main(int argc, char *argv[]) +{ + int sk[2]; + struct msghdr msg = {}; + struct iovec iov; + char buf[64]; + char cmsg_buf[CMSG_SPACE(sizeof(int))]; + struct cmsghdr *cmsg; + int opt = 1; + int pidfd; + pid_t pid; + socklen_t len; + + test_init(argc, argv); + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sk) < 0) + return pr_perror("socketpair"); + + if (setsockopt(sk[1], SOL_SOCKET, SO_PASSPIDFD, &opt, sizeof(opt)) < 0) + return pr_perror("setsockopt SO_PASSPIDFD"); + + /* + * A plain sendmsg: the kernel attaches the sender pid to the + * queued skb because the receiver has SO_PASSPIDFD set. + */ + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + strcpy(buf, "hello"); + if (sendmsg(sk[0], &msg, 0) < 0) + return pr_perror("sendmsg"); + + test_daemon(); + test_waitsig(); + + /* Check the socket option survived */ + opt = 0; + len = sizeof(opt); + if (getsockopt(sk[1], SOL_SOCKET, SO_PASSPIDFD, &opt, &len) < 0) + return pr_perror("getsockopt SO_PASSPIDFD"); + if (opt != 1) { + fail("SO_PASSPIDFD not restored"); + return 1; + } + + /* Receive and verify the pidfd after restore */ + memset(buf, 0, sizeof(buf)); + memset(cmsg_buf, 0, sizeof(cmsg_buf)); + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + + if (recvmsg(sk[1], &msg, 0) < 0) + return pr_perror("recvmsg"); + + cmsg = CMSG_FIRSTHDR(&msg); + if (!cmsg) { + fail("no cmsg after restore"); + return 1; + } + + if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_PIDFD) { + fail("wrong cmsg after restore: level %d type %d", + cmsg->cmsg_level, cmsg->cmsg_type); + return 1; + } + + memcpy(&pidfd, CMSG_DATA(cmsg), sizeof(pidfd)); + if (pidfd < 0) { + fail("invalid pidfd %d after restore", pidfd); + return 1; + } + + if (pidfd_send_signal(pidfd, 0, NULL, 0)) + return pr_perror("pidfd_send_signal"); + + pid = pidfd_get_pid(pidfd); + if (pid != getpid()) { + fail("pidfd pid mismatch after restore: %d/%d", pid, getpid()); + return 1; + } + + close(pidfd); + close(sk[0]); + close(sk[1]); + + pass(); + return 0; +} diff --git a/test/zdtm/static/sk-unix-dgram-pidfd.desc b/test/zdtm/static/sk-unix-dgram-pidfd.desc new file mode 100644 index 000000000..8e9526ec4 --- /dev/null +++ b/test/zdtm/static/sk-unix-dgram-pidfd.desc @@ -0,0 +1 @@ +{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}