mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-22 01:31:34 +00:00
Merge b782ad993b into b7bdc14736
This commit is contained in:
commit
8d64e170a8
18 changed files with 762 additions and 24 deletions
|
|
@ -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 },
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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__ */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
152
criu/sk-queue.c
152
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
162
test/zdtm/static/sk-unix-dgram-pidfd-cred.c
Normal file
162
test/zdtm/static/sk-unix-dgram-pidfd-cred.c
Normal 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;
|
||||
}
|
||||
1
test/zdtm/static/sk-unix-dgram-pidfd-cred.desc
Normal file
1
test/zdtm/static/sk-unix-dgram-pidfd-cred.desc
Normal file
|
|
@ -0,0 +1 @@
|
|||
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}
|
||||
236
test/zdtm/static/sk-unix-dgram-pidfd-stale.c
Normal file
236
test/zdtm/static/sk-unix-dgram-pidfd-stale.c
Normal 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;
|
||||
}
|
||||
1
test/zdtm/static/sk-unix-dgram-pidfd-stale.desc
Normal file
1
test/zdtm/static/sk-unix-dgram-pidfd-stale.desc
Normal file
|
|
@ -0,0 +1 @@
|
|||
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}
|
||||
146
test/zdtm/static/sk-unix-dgram-pidfd.c
Normal file
146
test/zdtm/static/sk-unix-dgram-pidfd.c
Normal 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;
|
||||
}
|
||||
1
test/zdtm/static/sk-unix-dgram-pidfd.desc
Normal file
1
test/zdtm/static/sk-unix-dgram-pidfd.desc
Normal file
|
|
@ -0,0 +1 @@
|
|||
{'flavor': 'h ns uns', 'feature': 'so_passpidfd'}
|
||||
Loading…
Add table
Add a link
Reference in a new issue