Skip to content

Commit f01ee44

Browse files
q2venintel-lab-lkp
authored andcommitted
selftest: bpf: Add test for BPF LSM on unix_may_send().
This test performs the following for all AF_UNIX socket types to demonstrate how we can inspect each struct file passed via SCM_RIGHTS. 1. Create a socket pair (sender and receiver) 2. Send the receiver's fd from the sender to the receiver 3. Receive the fd 4. Attach a BPF LSM prog that forbids self-reference SCM_RIGHTS 5. Send the receiver's fd from the sender to the receiver 6. Check if sendmsg() fails with -EPERM 7. Detach the LSM prog How to run: # make -C tools/testing/selftests/bpf/ # ./tools/testing/selftests/bpf/test_progs -t lsm_unix_may_send ... torvalds#182/1 lsm_unix_may_send/SOCK_STREAM:OK torvalds#182/2 lsm_unix_may_send/SOCK_DGRAM:OK torvalds#182/3 lsm_unix_may_send/SOCK_SEQPACKET:OK torvalds#182 lsm_unix_may_send:OK Summary: 1/3 PASSED, 0 SKIPPED, 0 FAILED Signed-off-by: Kuniyuki Iwashima <[email protected]>
1 parent a5cd85e commit f01ee44

File tree

2 files changed

+251
-0
lines changed

2 files changed

+251
-0
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright 2025 Google LLC */
3+
4+
#include "test_progs.h"
5+
#include "lsm_unix_may_send.skel.h"
6+
7+
#define MSG_HELLO "Hello"
8+
#define MSG_WORLD "World"
9+
#define MSG_LEN 5
10+
11+
struct scm_rights {
12+
struct cmsghdr cmsghdr;
13+
int fd;
14+
};
15+
16+
static int send_fd(int sender_fd, int receiver_fd, bool lsm_attached)
17+
{
18+
struct scm_rights cmsg = {};
19+
struct msghdr msg = {};
20+
struct iovec iov = {};
21+
int ret;
22+
23+
msg.msg_iov = &iov;
24+
msg.msg_iovlen = 1;
25+
msg.msg_control = &cmsg;
26+
msg.msg_controllen = CMSG_SPACE(sizeof(cmsg.fd));
27+
28+
iov.iov_base = MSG_HELLO;
29+
iov.iov_len = MSG_LEN;
30+
31+
cmsg.cmsghdr.cmsg_len = CMSG_LEN(sizeof(cmsg.fd));
32+
cmsg.cmsghdr.cmsg_level = SOL_SOCKET;
33+
cmsg.cmsghdr.cmsg_type = SCM_RIGHTS;
34+
cmsg.fd = receiver_fd;
35+
36+
/* sending "Hello" with the receiver's fd. */
37+
ret = sendmsg(sender_fd, &msg, 0);
38+
39+
if (lsm_attached) {
40+
if (!ASSERT_EQ(ret, -1, "sendmsg(Hello)") ||
41+
!ASSERT_EQ(errno, EPERM, "sendmsg(Hello) errno"))
42+
return -EINVAL;
43+
} else {
44+
if (!ASSERT_EQ(ret, MSG_LEN, "sendmsg(Hello)"))
45+
return -EINVAL;
46+
}
47+
48+
/* sending "World" without SCM_RIGHTS. */
49+
ret = send(sender_fd, MSG_WORLD, MSG_LEN, 0);
50+
if (!ASSERT_EQ(ret, MSG_LEN, "sendmsg(World)"))
51+
return -EINVAL;
52+
53+
return 0;
54+
}
55+
56+
static int recv_fd(int receiver_fd, bool lsm_attached)
57+
{
58+
struct scm_rights cmsg = {};
59+
struct msghdr msg = {};
60+
char buf[MSG_LEN] = {};
61+
struct iovec iov = {};
62+
int ret;
63+
64+
msg.msg_iov = &iov;
65+
msg.msg_iovlen = 1;
66+
msg.msg_control = &cmsg;
67+
msg.msg_controllen = CMSG_SPACE(sizeof(cmsg.fd));
68+
69+
iov.iov_base = buf;
70+
iov.iov_len = sizeof(buf);
71+
72+
/* LSM is expected to drop "Hello" with the receiver's fd */
73+
if (lsm_attached)
74+
goto no_hello;
75+
76+
ret = recvmsg(receiver_fd, &msg, 0);
77+
if (!ASSERT_EQ(ret, MSG_LEN, "recvmsg(Hello) length") ||
78+
!ASSERT_STRNEQ(buf, MSG_HELLO, MSG_LEN, "recvmsg(Hello) data"))
79+
return -EINVAL;
80+
81+
if (!ASSERT_OK_PTR(CMSG_FIRSTHDR(&msg), "cmsg sent") ||
82+
!ASSERT_EQ(cmsg.cmsghdr.cmsg_len, CMSG_LEN(sizeof(cmsg.fd)), "cmsg_len") ||
83+
!ASSERT_EQ(cmsg.cmsghdr.cmsg_level, SOL_SOCKET, "cmsg_level") ||
84+
!ASSERT_EQ(cmsg.cmsghdr.cmsg_type, SCM_RIGHTS, "cmsg_type"))
85+
return -EINVAL;
86+
87+
/* Double-check if the fd is of the receiver itself. */
88+
receiver_fd = cmsg.fd;
89+
90+
memset(buf, 0, sizeof(buf));
91+
92+
no_hello:
93+
ret = recv(receiver_fd, buf, sizeof(buf), 0);
94+
if (!ASSERT_EQ(ret, MSG_LEN, "recvmsg(World) length") ||
95+
!ASSERT_STRNEQ(buf, MSG_WORLD, MSG_LEN, "recvmsg(World) data"))
96+
return -EINVAL;
97+
98+
return 0;
99+
}
100+
101+
static void test_scm_rights(struct lsm_unix_may_send *skel, int type)
102+
{
103+
struct bpf_link *link;
104+
int socket_fds[2];
105+
int err;
106+
107+
err = socketpair(AF_UNIX, type, 0, socket_fds);
108+
if (!ASSERT_EQ(err, 0, "socketpair"))
109+
return;
110+
111+
err = send_fd(socket_fds[0], socket_fds[1], false);
112+
if (err)
113+
goto close;
114+
115+
err = recv_fd(socket_fds[1], false);
116+
if (err)
117+
goto close;
118+
119+
link = bpf_program__attach_lsm(skel->progs.unix_may_send_filter);
120+
if (!ASSERT_OK_PTR(link, "attach lsm"))
121+
goto close;
122+
123+
err = send_fd(socket_fds[0], socket_fds[1], true);
124+
if (err)
125+
goto detach;
126+
127+
recv_fd(socket_fds[1], true);
128+
detach:
129+
err = bpf_link__destroy(link);
130+
ASSERT_EQ(err, 0, "detach lsm");
131+
close:
132+
close(socket_fds[0]);
133+
close(socket_fds[1]);
134+
}
135+
136+
struct sk_type {
137+
char name[16];
138+
int type;
139+
} sk_types[] = {
140+
{
141+
.name = "SOCK_STREAM",
142+
.type = SOCK_STREAM,
143+
},
144+
{
145+
.name = "SOCK_DGRAM",
146+
.type = SOCK_DGRAM,
147+
},
148+
{
149+
.name = "SOCK_SEQPACKET",
150+
.type = SOCK_SEQPACKET,
151+
},
152+
};
153+
154+
void test_lsm_unix_may_send(void)
155+
{
156+
struct lsm_unix_may_send *skel;
157+
int i;
158+
159+
skel = lsm_unix_may_send__open_and_load();
160+
if (!ASSERT_OK_PTR(skel, "load skel"))
161+
return;
162+
163+
for (i = 0; i < ARRAY_SIZE(sk_types); i++)
164+
if (test__start_subtest(sk_types[i].name))
165+
test_scm_rights(skel, sk_types[i].type);
166+
167+
lsm_unix_may_send__destroy(skel);
168+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright 2025 Google LLC */
3+
4+
#include <vmlinux.h>
5+
#include <bpf/bpf_core_read.h>
6+
#include <bpf/bpf_tracing.h>
7+
8+
#include "bpf_misc.h"
9+
10+
#define EPERM 1
11+
12+
#define FMODE_PATH (1 << 14)
13+
#define S_IFMT 00170000
14+
#define S_IFSOCK 0140000
15+
#define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK)
16+
17+
#define AF_UNIX 1
18+
19+
static struct inode *file_inode(struct file *filp)
20+
{
21+
return bpf_core_cast(filp->f_inode, struct inode);
22+
}
23+
24+
static struct socket *SOCKET_I(struct inode *inode)
25+
{
26+
return bpf_core_cast(&container_of(inode, struct socket_alloc, vfs_inode)->socket,
27+
struct socket);
28+
}
29+
30+
/* mostly same with unix_get_socket() in net/unix/garbage.c */
31+
static struct sock *unix_get_socket(struct file *filp)
32+
{
33+
struct socket *sock;
34+
struct inode *inode;
35+
36+
if (filp->f_mode & FMODE_PATH)
37+
return NULL;
38+
39+
inode = file_inode(filp);
40+
if (!inode)
41+
return NULL;
42+
43+
if (!S_ISSOCK(inode->i_mode))
44+
return NULL;
45+
46+
sock = SOCKET_I(inode);
47+
if (!sock || !sock->ops || sock->ops->family != AF_UNIX)
48+
return NULL;
49+
50+
return sock->sk;
51+
}
52+
53+
SEC("lsm/unix_may_send")
54+
int BPF_PROG(unix_may_send_filter,
55+
struct sock *sk, struct sock *other, struct sk_buff *skb)
56+
{
57+
struct unix_skb_parms *cb;
58+
struct scm_fp_list *fpl;
59+
int i;
60+
61+
if (!skb)
62+
return 0;
63+
64+
cb = bpf_core_cast(skb->cb, struct unix_skb_parms);
65+
if (!cb->fp)
66+
return 0;
67+
68+
fpl = bpf_core_cast(cb->fp, struct scm_fp_list);
69+
70+
for (i = 0; i < fpl->count && i < ARRAY_SIZE(fpl->fp); i++) {
71+
struct file *filp;
72+
73+
filp = bpf_core_cast(fpl->fp[i], struct file);
74+
75+
/* self-reference is the simplest case that requires GC */
76+
if (unix_get_socket(filp) == other)
77+
return -EPERM;
78+
}
79+
80+
return 0;
81+
}
82+
83+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)