1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9 #include <sys/socket.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
13 #include <bpf/bpf.h>
14 #include <bpf/libbpf.h>
15
16 #include "kselftest_harness.h"
17
18 #ifndef SO_RIGHTS_NOTRUNC
19 #define SO_RIGHTS_NOTRUNC 85
20 #endif
21
22 #define NR_FILES 2
23
24 /* Per-file content, so a received fd can be matched to the file sent */
25 #define SECRET(n) "secret %d", (n)
26
27 /* Indices into the socketpair */
28 #define SK_SENDER 0
29 #define SK_RECEIVER 1
30
FIXTURE(scm_rights_denial_bpf)31 FIXTURE(scm_rights_denial_bpf)
32 {
33 struct bpf_object *obj;
34 struct bpf_link *link;
35 int map_fd;
36 int sk[2];
37 int files[NR_FILES];
38 __u64 inos[NR_FILES];
39 char paths[NR_FILES][64];
40 };
41
FIXTURE_VARIANT(scm_rights_denial_bpf)42 FIXTURE_VARIANT(scm_rights_denial_bpf)
43 {
44 int sock_type;
45 };
46
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf,stream)47 FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, stream)
48 {
49 .sock_type = SOCK_STREAM,
50 };
51
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf,dgram)52 FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, dgram)
53 {
54 .sock_type = SOCK_DGRAM,
55 };
56
FIXTURE_VARIANT_ADD(scm_rights_denial_bpf,seqpacket)57 FIXTURE_VARIANT_ADD(scm_rights_denial_bpf, seqpacket)
58 {
59 .sock_type = SOCK_SEQPACKET,
60 };
61
FIXTURE_SETUP(scm_rights_denial_bpf)62 FIXTURE_SETUP(scm_rights_denial_bpf)
63 {
64 struct bpf_program *prog;
65 char lsms[256] = {};
66 int i, fd;
67
68 if (geteuid() != 0)
69 SKIP(return, "requires root");
70
71 fd = open("/sys/kernel/security/lsm", O_RDONLY);
72 ASSERT_GE(fd, 0);
73 ASSERT_LT(0, read(fd, lsms, sizeof(lsms) - 1));
74 close(fd);
75
76 if (!strstr(lsms, "bpf"))
77 SKIP(return, "BPF LSM not active (boot with lsm=...,bpf)");
78
79 self->obj = bpf_object__open_file("scm_rights_denial_lsm.bpf.o", NULL);
80 ASSERT_NE(NULL, self->obj);
81 ASSERT_EQ(0, bpf_object__load(self->obj));
82
83 prog = bpf_object__find_program_by_name(self->obj, "scm_rights_deny");
84 ASSERT_NE(NULL, prog);
85
86 self->link = bpf_program__attach_lsm(prog);
87 ASSERT_NE(NULL, self->link);
88
89 self->map_fd = bpf_object__find_map_fd_by_name(self->obj,
90 "denied_inodes");
91 ASSERT_GE(self->map_fd, 0);
92
93 ASSERT_EQ(0, socketpair(AF_UNIX, variant->sock_type, 0, self->sk));
94
95 for (i = 0; i < NR_FILES; i++) {
96 struct stat st;
97
98 snprintf(self->paths[i], sizeof(self->paths[i]),
99 "/tmp/scm_rights_denial_bpf.%d.XXXXXX", i);
100 self->files[i] = mkstemp(self->paths[i]);
101 ASSERT_GE(self->files[i], 0);
102
103 ASSERT_LT(0, dprintf(self->files[i], SECRET(i)));
104
105 ASSERT_EQ(0, fstat(self->files[i], &st));
106 self->inos[i] = st.st_ino;
107 }
108 }
109
FIXTURE_TEARDOWN(scm_rights_denial_bpf)110 FIXTURE_TEARDOWN(scm_rights_denial_bpf)
111 {
112 bpf_link__destroy(self->link);
113 bpf_object__close(self->obj);
114
115 for (int i = 0; i < NR_FILES; i++) {
116 if (self->files[i] >= 0) {
117 close(self->files[i]);
118 unlink(self->paths[i]);
119 }
120 }
121
122 close(self->sk[SK_SENDER]);
123 close(self->sk[SK_RECEIVER]);
124 }
125
deny_inode(int map_fd,__u64 ino)126 static int deny_inode(int map_fd, __u64 ino)
127 {
128 __u32 tgid = getpid();
129
130 return bpf_map_update_elem(map_fd, &ino, &tgid, BPF_ANY);
131 }
132
set_notrunc(int sk)133 static int set_notrunc(int sk)
134 {
135 int one = 1;
136
137 return setsockopt(sk, SOL_SOCKET, SO_RIGHTS_NOTRUNC,
138 &one, sizeof(one));
139 }
140
send_fds(int sk,int * fds,int n)141 static int send_fds(int sk, int *fds, int n)
142 {
143 char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))] = {};
144 char data = 'x';
145 struct iovec iov = {
146 .iov_base = &data,
147 .iov_len = sizeof(data),
148 };
149 struct msghdr msg = {
150 .msg_iov = &iov,
151 .msg_iovlen = 1,
152 .msg_control = ctrl,
153 .msg_controllen = CMSG_SPACE(n * sizeof(int)),
154 };
155 struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
156 int ret;
157
158 cmsg->cmsg_level = SOL_SOCKET;
159 cmsg->cmsg_type = SCM_RIGHTS;
160 cmsg->cmsg_len = CMSG_LEN(n * sizeof(int));
161 memcpy(CMSG_DATA(cmsg), fds, n * sizeof(int));
162
163 ret = sendmsg(sk, &msg, 0);
164 if (ret != 1)
165 return -1;
166
167 return 0;
168 }
169
recv_fd_slots(int sk,int * slots,int * msg_flags)170 static int recv_fd_slots(int sk, int *slots, int *msg_flags)
171 {
172 int nr_slots;
173 char ctrl[CMSG_SPACE(NR_FILES * sizeof(int))];
174 char data;
175 struct iovec iov = {
176 .iov_base = &data,
177 .iov_len = sizeof(data),
178 };
179 struct msghdr msg = {
180 .msg_iov = &iov,
181 .msg_iovlen = 1,
182 .msg_control = ctrl,
183 .msg_controllen = sizeof(ctrl),
184 };
185 struct cmsghdr *cmsg;
186
187 if (recvmsg(sk, &msg, 0) < 0)
188 return -1;
189
190 *msg_flags = msg.msg_flags;
191
192 cmsg = CMSG_FIRSTHDR(&msg);
193 if (!cmsg)
194 return 0;
195
196 nr_slots = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
197 memcpy(slots, CMSG_DATA(cmsg), nr_slots * sizeof(int));
198
199 return nr_slots;
200 }
201
202 /* Prove a received fd works by reading back the file's content. */
check_secret(int fd,int idx)203 static int check_secret(int fd, int idx)
204 {
205 char want[32], got[32] = {};
206
207 snprintf(want, sizeof(want), SECRET(idx));
208 if (pread(fd, got, sizeof(got) - 1, 0) < 0)
209 return -1;
210
211 return strcmp(want, got);
212 }
213
TEST_F(scm_rights_denial_bpf,all_allowed)214 TEST_F(scm_rights_denial_bpf, all_allowed)
215 {
216 int slots[NR_FILES], nr_slots, flags;
217
218 ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
219 ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
220 nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
221
222 ASSERT_EQ(NR_FILES, nr_slots);
223 EXPECT_EQ(0, flags & MSG_CTRUNC);
224
225 for (int i = 0; i < nr_slots; i++) {
226 ASSERT_GE(slots[i], 0);
227 EXPECT_EQ(0, check_secret(slots[i], i));
228 close(slots[i]);
229 }
230 }
231
TEST_F(scm_rights_denial_bpf,first_denied)232 TEST_F(scm_rights_denial_bpf, first_denied)
233 {
234 int slots[NR_FILES], nr_slots, flags;
235
236 ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[0]));
237
238 ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
239 ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
240 nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
241
242 ASSERT_EQ(NR_FILES, nr_slots);
243 EXPECT_EQ(0, flags & MSG_CTRUNC);
244
245 EXPECT_EQ(-EPERM, slots[0]);
246 for (int i = 1; i < nr_slots; i++) {
247 ASSERT_GE(slots[i], 0);
248 EXPECT_EQ(0, check_secret(slots[i], i));
249 close(slots[i]);
250 }
251 }
252
TEST_F(scm_rights_denial_bpf,all_denied)253 TEST_F(scm_rights_denial_bpf, all_denied)
254 {
255 int slots[NR_FILES], nr_slots, flags, i;
256
257 for (i = 0; i < NR_FILES; i++)
258 ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[i]));
259
260 ASSERT_EQ(0, set_notrunc(self->sk[SK_RECEIVER]));
261 ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
262 nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
263
264 ASSERT_EQ(NR_FILES, nr_slots);
265 EXPECT_EQ(0, flags & MSG_CTRUNC);
266
267 for (i = 0; i < nr_slots; i++)
268 EXPECT_EQ(-EPERM, slots[i]);
269 }
270
TEST_F(scm_rights_denial_bpf,denied_without_notrunc)271 TEST_F(scm_rights_denial_bpf, denied_without_notrunc)
272 {
273 int slots[NR_FILES], nr_slots, flags;
274
275 /*
276 * Baseline behaviour without SO_RIGHTS_NOTRUNC: the fd array is
277 * truncated at the first denied fd and MSG_CTRUNC is set.
278 */
279 ASSERT_EQ(0, deny_inode(self->map_fd, self->inos[1]));
280
281 ASSERT_EQ(0, send_fds(self->sk[SK_SENDER], self->files, NR_FILES));
282 nr_slots = recv_fd_slots(self->sk[SK_RECEIVER], slots, &flags);
283
284 ASSERT_EQ(1, nr_slots);
285 EXPECT_NE(0, flags & MSG_CTRUNC);
286
287 ASSERT_GE(slots[0], 0);
288 EXPECT_EQ(0, check_secret(slots[0], 0));
289 close(slots[0]);
290 }
291
292 TEST_HARNESS_MAIN
293