mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 14:33:24 -04:00
Merge branch 'net-af_unix-useful-handling-of-lsm-denials-on-scm_rights'
Jori Koolstra says: ==================== net: af_unix: useful handling of LSM denials on SCM_RIGHTS Right now if some LSM denies an AF_UNIX socket peer to receive a SCM_RIGHTS fd, the SCM_RIGHTS fd array will be cut short at that point, and MSG_CTRUNC is set on return of recvmsg(2). This is highly problematic behaviour, because it leaves the receiver wondering what happened. As per man page MSG_CTRUNC is supposed to indicate that the control buffer was sized too short, but suddenly a permission error might result in the exact same flag being set. Moreover, the receiver has no chance to determine how many fds got originally sent and how many were suppressed.[1] Add a SO_RIGHTS_NOTRUNC option to UNIX sockets to enable more useful handling of LSM denials when receiving SCM_RIGHTS messages: instead of truncating the message at the first blocked fd, keep every fd slot and store the LSM errno in the blocked slot. This option is inherited by the accept()-ed socket when set on the listen() socket. [1]: https://github.com/uapi-group/kernel-features#useful-handling-of-lsm-denials-on-scm_rights ==================== Link: https://patch.msgid.link/20260813162818.149248-1-jkoolstra@xs4all.nl Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -155,6 +155,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -166,6 +166,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -147,6 +147,8 @@
|
||||
#define SO_INQ 0x4052
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 0x4053
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64
|
||||
|
||||
@@ -148,6 +148,8 @@
|
||||
#define SO_INQ 0x005d
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 0x005e
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ struct unix_sock {
|
||||
struct scm_stat scm_stat;
|
||||
int inq_len;
|
||||
bool recvmsg_inq;
|
||||
bool scm_rights_notrunc;
|
||||
#if IS_ENABLED(CONFIG_AF_UNIX_OOB)
|
||||
struct sk_buff *oob_skb;
|
||||
#endif
|
||||
|
||||
@@ -50,8 +50,8 @@ struct scm_cookie {
|
||||
#endif
|
||||
};
|
||||
|
||||
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm);
|
||||
void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm);
|
||||
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
|
||||
void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc);
|
||||
int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *scm);
|
||||
void __scm_destroy(struct scm_cookie *scm);
|
||||
struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl);
|
||||
@@ -107,13 +107,8 @@ void scm_recv(struct socket *sock, struct msghdr *msg,
|
||||
void scm_recv_unix(struct socket *sock, struct msghdr *msg,
|
||||
struct scm_cookie *scm, int flags);
|
||||
|
||||
static inline int scm_recv_one_fd(struct file *f, int __user *ufd,
|
||||
unsigned int flags)
|
||||
{
|
||||
if (!ufd)
|
||||
return -EFAULT;
|
||||
return receive_fd(f, ufd, flags);
|
||||
}
|
||||
int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
|
||||
bool notrunc);
|
||||
|
||||
#endif /* __LINUX_NET_SCM_H */
|
||||
|
||||
|
||||
@@ -150,6 +150,8 @@
|
||||
#define SO_INQ 84
|
||||
#define SCM_INQ SO_INQ
|
||||
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
|
||||
#if !defined(__KERNEL__)
|
||||
|
||||
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
|
||||
|
||||
@@ -286,7 +286,7 @@ static int scm_max_fds_compat(struct msghdr *msg)
|
||||
return (msg->msg_controllen - sizeof(struct compat_cmsghdr)) / sizeof(int);
|
||||
}
|
||||
|
||||
void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
|
||||
void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
|
||||
{
|
||||
struct compat_cmsghdr __user *cm =
|
||||
(struct compat_cmsghdr __user *)msg->msg_control_user;
|
||||
@@ -296,7 +296,7 @@ void scm_detach_fds_compat(struct msghdr *msg, struct scm_cookie *scm)
|
||||
int err = 0, i;
|
||||
|
||||
for (i = 0; i < fdmax; i++) {
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -351,7 +351,31 @@ static int scm_max_fds(struct msghdr *msg)
|
||||
return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int);
|
||||
}
|
||||
|
||||
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
|
||||
int scm_recv_one_fd(struct file *f, int __user *ufd, unsigned int flags,
|
||||
bool notrunc)
|
||||
{
|
||||
int error;
|
||||
|
||||
if (!ufd)
|
||||
return -EFAULT;
|
||||
|
||||
error = security_file_receive(f);
|
||||
if (error)
|
||||
return notrunc ? put_user(error, ufd) : error;
|
||||
|
||||
FD_PREPARE(fdf, flags, get_file(f));
|
||||
if (fdf.err)
|
||||
return fdf.err;
|
||||
|
||||
error = put_user(fd_prepare_fd(fdf), ufd);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
__receive_sock(fd_prepare_file(fdf));
|
||||
return fd_publish(fdf);
|
||||
}
|
||||
|
||||
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm, bool notrunc)
|
||||
{
|
||||
struct cmsghdr __user *cm =
|
||||
(__force struct cmsghdr __user *)msg->msg_control_user;
|
||||
@@ -365,12 +389,12 @@ void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
|
||||
return;
|
||||
|
||||
if (msg->msg_flags & MSG_CMSG_COMPAT) {
|
||||
scm_detach_fds_compat(msg, scm);
|
||||
scm_detach_fds_compat(msg, scm, notrunc);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < fdmax; i++) {
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags);
|
||||
err = scm_recv_one_fd(scm->fp->fp[i], cmsg_data + i, o_flags, notrunc);
|
||||
if (err < 0)
|
||||
break;
|
||||
}
|
||||
@@ -523,9 +547,6 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
|
||||
|
||||
scm_passec(sk, msg, scm);
|
||||
|
||||
if (scm->fp)
|
||||
scm_detach_fds(msg, scm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -545,6 +566,13 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
|
||||
if (!__scm_recv_common(sock->sk, msg, scm, flags))
|
||||
return;
|
||||
|
||||
if (scm->fp) {
|
||||
struct unix_sock *u;
|
||||
|
||||
u = unix_sk(sock->sk);
|
||||
scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc));
|
||||
}
|
||||
|
||||
if (sock->sk->sk_scm_pidfd)
|
||||
scm_pidfd_recv(msg, scm);
|
||||
|
||||
|
||||
@@ -922,6 +922,7 @@ static bool unix_custom_sockopt(int optname)
|
||||
{
|
||||
switch (optname) {
|
||||
case SO_INQ:
|
||||
case SO_RIGHTS_NOTRUNC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -950,13 +951,21 @@ static int unix_setsockopt(struct socket *sock, int level, int optname,
|
||||
switch (optname) {
|
||||
case SO_INQ:
|
||||
if (sk->sk_type != SOCK_STREAM)
|
||||
return -EINVAL;
|
||||
return -ENOPROTOOPT;
|
||||
|
||||
if (val > 1 || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
WRITE_ONCE(u->recvmsg_inq, val);
|
||||
break;
|
||||
|
||||
case SO_RIGHTS_NOTRUNC:
|
||||
if (val > 1 || val < 0)
|
||||
return -EINVAL;
|
||||
|
||||
WRITE_ONCE(u->scm_rights_notrunc, val);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -ENOPROTOOPT;
|
||||
}
|
||||
@@ -1006,6 +1015,7 @@ static const struct proto_ops unix_dgram_ops = {
|
||||
#endif
|
||||
.listen = sock_no_listen,
|
||||
.shutdown = unix_shutdown,
|
||||
.setsockopt = unix_setsockopt,
|
||||
.sendmsg = unix_dgram_sendmsg,
|
||||
.read_skb = unix_read_skb,
|
||||
.recvmsg = unix_dgram_recvmsg,
|
||||
@@ -1030,6 +1040,7 @@ static const struct proto_ops unix_seqpacket_ops = {
|
||||
#endif
|
||||
.listen = unix_listen,
|
||||
.shutdown = unix_shutdown,
|
||||
.setsockopt = unix_setsockopt,
|
||||
.sendmsg = unix_seqpacket_sendmsg,
|
||||
.recvmsg = unix_seqpacket_recvmsg,
|
||||
.mmap = sock_no_mmap,
|
||||
@@ -1143,9 +1154,10 @@ static int unix_create(struct net *net, struct socket *sock, int protocol,
|
||||
if (protocol && protocol != PF_UNIX)
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
|
||||
|
||||
switch (sock->type) {
|
||||
case SOCK_STREAM:
|
||||
set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags);
|
||||
sock->ops = &unix_stream_ops;
|
||||
break;
|
||||
/*
|
||||
@@ -1743,9 +1755,10 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uad
|
||||
init_peercred(newsk, &peercred);
|
||||
|
||||
newu = unix_sk(newsk);
|
||||
newu->listener = other;
|
||||
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
|
||||
otheru = unix_sk(other);
|
||||
newu->listener = other;
|
||||
newu->scm_rights_notrunc = READ_ONCE(otheru->scm_rights_notrunc);
|
||||
RCU_INIT_POINTER(newsk->sk_wq, &newu->peer_wq);
|
||||
|
||||
/* copy address information from listening to new sock
|
||||
*
|
||||
@@ -1865,8 +1878,7 @@ static int unix_accept(struct socket *sock, struct socket *newsock,
|
||||
skb_free_datagram(sk, skb);
|
||||
wake_up_interruptible(&unix_sk(sk)->peer_wait);
|
||||
|
||||
if (tsk->sk_type == SOCK_STREAM)
|
||||
set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags);
|
||||
set_bit(SOCK_CUSTOM_SOCKOPT, &newsock->flags);
|
||||
|
||||
/* attach accepted sock to socket */
|
||||
unix_state_lock(tsk);
|
||||
|
||||
@@ -3,6 +3,8 @@ msg_oob
|
||||
scm_inq
|
||||
scm_pidfd
|
||||
scm_rights
|
||||
scm_rights_denial_lsm
|
||||
scm_rights_denial_lsm.bpf.o
|
||||
so_peek_off
|
||||
unix_connect
|
||||
unix_connreset
|
||||
|
||||
@@ -11,10 +11,18 @@ TEST_GEN_PROGS := \
|
||||
scm_inq \
|
||||
scm_pidfd \
|
||||
scm_rights \
|
||||
scm_rights_denial_lsm \
|
||||
so_peek_off \
|
||||
unix_connect \
|
||||
unix_connreset \
|
||||
unix_listen \
|
||||
# end of TEST_GEN_PROGS
|
||||
|
||||
TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
|
||||
|
||||
include ../../lib.mk
|
||||
include ../bpf.mk
|
||||
|
||||
$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
|
||||
$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
|
||||
$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
CONFIG_AF_UNIX_OOB=y
|
||||
CONFIG_BPF=y
|
||||
CONFIG_BPF_EVENTS=y
|
||||
CONFIG_BPF_JIT=y
|
||||
CONFIG_BPF_LSM=y
|
||||
CONFIG_BPF_SYSCALL=y
|
||||
CONFIG_DEBUG_INFO_BTF=y
|
||||
CONFIG_SECURITY=y
|
||||
CONFIG_UNIX=y
|
||||
CONFIG_UNIX_DIAG=m
|
||||
CONFIG_USER_NS=y
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/errno.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
||||
struct inode {
|
||||
unsigned long i_ino;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct file {
|
||||
struct inode *f_inode;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__uint(max_entries, 16);
|
||||
__type(key, __u64); /* inode number */
|
||||
__type(value, __u32); /* tgid of the receiver being tested */
|
||||
} denied_inodes SEC(".maps");
|
||||
|
||||
SEC("lsm/file_receive")
|
||||
int BPF_PROG(scm_rights_deny, struct file *file)
|
||||
{
|
||||
__u32 tgid = bpf_get_current_pid_tgid() >> 32;
|
||||
__u64 ino = file->f_inode->i_ino;
|
||||
__u32 *owner;
|
||||
|
||||
owner = bpf_map_lookup_elem(&denied_inodes, &ino);
|
||||
if (owner && *owner == tgid)
|
||||
return -EPERM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
292
tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
Normal file
292
tools/testing/selftests/net/af_unix/scm_rights_denial_lsm.c
Normal file
@@ -0,0 +1,292 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
#ifndef SO_RIGHTS_NOTRUNC
|
||||
#define SO_RIGHTS_NOTRUNC 85
|
||||
#endif
|
||||
|
||||
#define NR_FILES 2
|
||||
|
||||
/* Per-file content, so a received fd can be matched to the file sent */
|
||||
#define SECRET(n) "secret %d", (n)
|
||||
|
||||
/* Indices into the socketpair */
|
||||
#define SK_SENDER 0
|
||||
#define SK_RECEIVER 1
|
||||
|
||||
FIXTURE(scm_rights_denial_bpf)
|
||||
{
|
||||
struct bpf_object *obj;
|
||||
struct bpf_link *link;
|
||||
int map_fd;
|
||||
int sk[2];
|
||||
int files[NR_FILES];
|
||||
__u64 inos[NR_FILES];
|
||||
char paths[NR_FILES][64];
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT(scm_rights_denial_bpf)
|
||||
{
|
||||
int sock_type;
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream)
|
||||
{
|
||||
.sock_type = SOCK_STREAM,
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram)
|
||||
{
|
||||
.sock_type = SOCK_DGRAM,
|
||||
};
|
||||
|
||||
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket)
|
||||
{
|
||||
.sock_type = SOCK_SEQPACKET,
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(scm_rights_denial_bpf)
|
||||
{
|
||||
struct bpf_program *prog;
|
||||
char lsms[256] = {};
|
||||
int i, fd;
|
||||
|
||||
if (geteuid() != 0)
|
||||
SKIP(return, "requires root");
|
||||
|
||||
fd = open("/sys/kernel/security/lsm", O_RDONLY);
|
||||
ASSERT_GE(fd, 0);
|
||||
ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));
|
||||
close(fd);
|
||||
|
||||
if (!strstr(lsms, "bpf"))
|
||||
SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)");
|
||||
|
||||
self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL);
|
||||
ASSERT_NE(NULL, self->obj);
|
||||
ASSERT_EQ(0, bpf_object__load(self->obj));
|
||||
|
||||
prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny");
|
||||
ASSERT_NE(NULL, prog);
|
||||
|
||||
self->link = bpf_program__attach_lsm(prog);
|
||||
ASSERT_NE(NULL, self->link);
|
||||
|
||||
self->map_fd = bpf_object__find_map_fd_by_name(self->obj,
|
||||
"denied_inodes");
|
||||
ASSERT_GE(self->map_fd, 0);
|
||||
|
||||
ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk));
|
||||
|
||||
for (i = 0; i < NR_FILES; i++) {
|
||||
struct stat st;
|
||||
|
||||
snprintf(self->paths[i], sizeof(self->paths[i]),
|
||||
"/tmp/scm_rights_denial_bpf.%d.XXXXXX", i);
|
||||
self->files[i] = mkstemp(self->paths[i]);
|
||||
ASSERT_GE(self->files[i], 0);
|
||||
|
||||
ASSERT_LT(0, dprintf(self->files[i], SECRET(i)));
|
||||
|
||||
ASSERT_EQ(0, fstat(self->files[i], &st));
|
||||
self->inos[i] = st.st_ino;
|
||||
}
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(scm_rights_denial_bpf)
|
||||
{
|
||||
bpf_link__destroy(self->link);
|
||||
bpf_object__close(self->obj);
|
||||
|
||||
for (int i = 0; i < NR_FILES; i++) {
|
||||
if (self->files[i] >= 0) {
|
||||
close(self->files[i]);
|
||||
unlink(self->paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
close(self->sk[SK_SENDER]);
|
||||
close(self->sk[SK_RECEIVER]);
|
||||
}
|
||||
|
||||
static int deny_inode(int map_fd, __u64 ino)
|
||||
{
|
||||
__u32 tgid = getpid();
|
||||
|
||||
return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY);
|
||||
}
|
||||
|
||||
static int set_notrunc(int sk)
|
||||
{
|
||||
int one = 1;
|
||||
|
||||
return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
|
||||
&one, sizeof(one));
|
||||
}
|
||||
|
||||
static int send_fds(int sk, int *fds, int n)
|
||||
{
|
||||
char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};
|
||||
char data = 'x';
|
||||
struct iovec iov = {
|
||||
.iov_base = &data,
|
||||
.iov_len = sizeof(data),
|
||||
};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = ctrl,
|
||||
.msg_controllen = CMSG_SPACE(n * sizeof(int)),
|
||||
};
|
||||
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
|
||||
int ret;
|
||||
|
||||
cmsg->cmsg_level = SOL_SOCKET;
|
||||
cmsg->cmsg_type = SCM_RIGHTS;
|
||||
cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
|
||||
memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));
|
||||
|
||||
ret = sendmsg(sk, &msg, 0);
|
||||
if (ret != 1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int recv_fd_slots(int sk, int *slots, int *msg_flags)
|
||||
{
|
||||
int nr_slots;
|
||||
char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];
|
||||
char data;
|
||||
struct iovec iov = {
|
||||
.iov_base = &data,
|
||||
.iov_len = sizeof(data),
|
||||
};
|
||||
struct msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = ctrl,
|
||||
.msg_controllen = sizeof(ctrl),
|
||||
};
|
||||
struct cmsghdr *cmsg;
|
||||
|
||||
if (recvmsg(sk, &msg, 0) < 0)
|
||||
return -1;
|
||||
|
||||
*msg_flags = msg.msg_flags;
|
||||
|
||||
cmsg = CMSG_FIRSTHDR(&msg);
|
||||
if (!cmsg)
|
||||
return 0;
|
||||
|
||||
nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
||||
memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));
|
||||
|
||||
return nr_slots;
|
||||
}
|
||||
|
||||
/* Prove a received fd works by reading back the file's content. */
|
||||
static int check_secret(int fd, int idx)
|
||||
{
|
||||
char want[32], got[32] = {};
|
||||
|
||||
snprintf(want, sizeof(want), SECRET(idx));
|
||||
if (pread(fd, got, sizeof(got) - 1, 0) < 0)
|
||||
return -1;
|
||||
|
||||
return strcmp(want, got);
|
||||
}
|
||||
|
||||
TEST_F(scm_rights_denial_bpf, all_allowed)
|
||||
{
|
||||
int slots[NR_FILES], nr_slots, flags;
|
||||
|
||||
ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
|
||||
ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
|
||||
nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
|
||||
|
||||
ASSERT_EQ(NR_FILES, nr_slots);
|
||||
EXPECT_EQ(0, flags & MSG_CTRUNC);
|
||||
|
||||
for (int i = 0; i < nr_slots; i++) {
|
||||
ASSERT_GE(slots[i], 0);
|
||||
EXPECT_EQ(0, check_secret(slots[i], i));
|
||||
close(slots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(scm_rights_denial_bpf, first_denied)
|
||||
{
|
||||
int slots[NR_FILES], nr_slots, flags;
|
||||
|
||||
ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0]));
|
||||
|
||||
ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
|
||||
ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
|
||||
nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
|
||||
|
||||
ASSERT_EQ(NR_FILES, nr_slots);
|
||||
EXPECT_EQ(0, flags & MSG_CTRUNC);
|
||||
|
||||
EXPECT_EQ(-EPERM, slots[0]);
|
||||
for (int i = 1; i < nr_slots; i++) {
|
||||
ASSERT_GE(slots[i], 0);
|
||||
EXPECT_EQ(0, check_secret(slots[i], i));
|
||||
close(slots[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(scm_rights_denial_bpf, all_denied)
|
||||
{
|
||||
int slots[NR_FILES], nr_slots, flags, i;
|
||||
|
||||
for (i = 0; i < NR_FILES; i++)
|
||||
ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i]));
|
||||
|
||||
ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
|
||||
ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
|
||||
nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
|
||||
|
||||
ASSERT_EQ(NR_FILES, nr_slots);
|
||||
EXPECT_EQ(0, flags & MSG_CTRUNC);
|
||||
|
||||
for (i = 0; i < nr_slots; i++)
|
||||
EXPECT_EQ(-EPERM, slots[i]);
|
||||
}
|
||||
|
||||
TEST_F(scm_rights_denial_bpf, denied_without_notrunc)
|
||||
{
|
||||
int slots[NR_FILES], nr_slots, flags;
|
||||
|
||||
/*
|
||||
* Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is
|
||||
* truncated at the first denied fd and MSG_CTRUNC is set.
|
||||
*/
|
||||
ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1]));
|
||||
|
||||
ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
|
||||
nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
|
||||
|
||||
ASSERT_EQ(1, nr_slots);
|
||||
EXPECT_NE(0, flags & MSG_CTRUNC);
|
||||
|
||||
ASSERT_GE(slots[0], 0);
|
||||
EXPECT_EQ(0, check_secret(slots[0], 0));
|
||||
close(slots[0]);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
Reference in New Issue
Block a user