This commit is contained in:
Ahmed khalid Elaidy 2026-07-16 22:53:58 +03:00 committed by GitHub
commit 8d64e170a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 762 additions and 24 deletions

View file

@ -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 },
};

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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__ */

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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 \

View file

@ -0,0 +1,162 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#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 <elaidya225@gmail.com>";
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;
}

View file

@ -0,0 +1 @@
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}

View file

@ -0,0 +1,236 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#include <sys/wait.h>
#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 <elaidya225@gmail.com>";
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;
}

View file

@ -0,0 +1 @@
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}

View file

@ -0,0 +1,146 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/un.h>
#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 <elaidya225@gmail.com>";
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;
}

View file

@ -0,0 +1 @@
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}