xref: /linux/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c (revision 2bd4a975ea5d6394dcaa6c1380653ab68252c9c7)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 #include <vmlinux.h>
4 #include <bpf/bpf_core_read.h>
5 #include "bpf_misc.h"
6 #include "bpf_kfuncs.h"
7 #include "../test_kmods/bpf_testmod_kfunc.h"
8 
9 SEC("tp_btf/sys_enter")
10 __success
11 __log_level(2)
12 __msg("r8 = *(u64 *)(r7 +0)          ; R7=ptr_nameidata(imm={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)")
13 __msg("r9 = *(u8 *)(r8 +0)           ; R8=rdonly_untrusted_mem(sz=0) R9=scalar")
14 int btf_id_to_ptr_mem(void *ctx)
15 {
16 	struct task_struct *task;
17 	struct nameidata *idata;
18 	u64 ret, off;
19 
20 	task = bpf_get_current_task_btf();
21 	idata = task->nameidata;
22 	off = bpf_core_field_offset(struct nameidata, pathname);
23 	/*
24 	 * asm block to have reliable match target for __msg, equivalent of:
25 	 *   ret = task->nameidata->pathname[0];
26 	 */
27 	asm volatile (
28 	"r7 = %[idata];"
29 	"r7 += %[off];"
30 	"r8 = *(u64 *)(r7 + 0);"
31 	"r9 = *(u8 *)(r8 + 0);"
32 	"%[ret] = r9;"
33 	: [ret]"=r"(ret)
34 	: [idata]"r"(idata),
35 	  [off]"r"(off)
36 	: "r7", "r8", "r9");
37 	return ret;
38 }
39 
40 SEC("socket")
41 __success
42 __retval(0)
43 int ldx_is_ok_bad_addr(void *ctx)
44 {
45 	char *p;
46 
47 	if (!bpf_core_enum_value_exists(enum bpf_features, BPF_FEAT_RDONLY_CAST_TO_VOID))
48 		return 42;
49 
50 	p = bpf_rdonly_cast(0, 0);
51 	return p[0x7fff];
52 }
53 
54 SEC("socket")
55 __success
56 __retval(1)
57 int ldx_is_ok_good_addr(void *ctx)
58 {
59 	int v, *p;
60 
61 	v = 1;
62 	p = bpf_rdonly_cast(&v, 0);
63 	return *p;
64 }
65 
66 SEC("socket")
67 __success
68 int offset_not_tracked(void *ctx)
69 {
70 	int *p, i, s;
71 
72 	p = bpf_rdonly_cast(0, 0);
73 	s = 0;
74 	bpf_for(i, 0, 1000 * 1000 * 1000) {
75 		p++;
76 		s += *p;
77 	}
78 	return s;
79 }
80 
81 SEC("socket")
82 __failure
83 __msg("cannot write into rdonly_untrusted_mem")
84 int stx_not_ok(void *ctx)
85 {
86 	int v, *p;
87 
88 	v = 1;
89 	p = bpf_rdonly_cast(&v, 0);
90 	*p = 1;
91 	return 0;
92 }
93 
94 SEC("socket")
95 __failure
96 __msg("cannot write into rdonly_untrusted_mem")
97 int atomic_not_ok(void *ctx)
98 {
99 	int v, *p;
100 
101 	v = 1;
102 	p = bpf_rdonly_cast(&v, 0);
103 	__sync_fetch_and_add(p, 1);
104 	return 0;
105 }
106 
107 SEC("socket")
108 __failure
109 __msg("cannot write into rdonly_untrusted_mem")
110 int atomic_rmw_not_ok(void *ctx)
111 {
112 	long v, *p;
113 
114 	v = 1;
115 	p = bpf_rdonly_cast(&v, 0);
116 	return __sync_val_compare_and_swap(p, 0, 42);
117 }
118 
119 SEC("socket")
120 __failure
121 __msg("R1 type=rdonly_untrusted_mem expected=fp")
122 int kfunc_param_not_ok(void *ctx)
123 {
124 	int *p;
125 
126 	p = bpf_rdonly_cast(0, 0);
127 	bpf_kfunc_trusted_num_test(p);
128 	return 0;
129 }
130 
131 SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
132 __failure
133 __msg("R1 type=rdonly_untrusted_mem expected=")
134 int helper_param_not_ok(void *ctx)
135 {
136 	char *p;
137 
138 	p = bpf_rdonly_cast(0, 0);
139 	/*
140 	 * Any helper with ARG_MEM_SIZE_OR_ZERO constraint will do,
141 	 * the most permissive constraint
142 	 */
143 	bpf_copy_from_user(p, 0, (void *)42);
144 	return 0;
145 }
146 
147 static __noinline u64 *get_some_addr(void)
148 {
149 	if (bpf_get_prandom_u32())
150 		return bpf_rdonly_cast(0, bpf_core_type_id_kernel(struct sock));
151 	else
152 		return bpf_rdonly_cast(0, 0);
153 }
154 
155 SEC("socket")
156 __success
157 __retval(0)
158 int mixed_mem_type(void *ctx)
159 {
160 	u64 *p;
161 
162 	/* Try to avoid compiler hoisting load to if branches by using __noinline func. */
163 	p = get_some_addr();
164 	return *p;
165 }
166 
167 struct {
168 	__uint(type, BPF_MAP_TYPE_RINGBUF);
169 	__uint(max_entries, 4096);
170 } ringbuf SEC(".maps");
171 
172 struct {
173 	__uint(type, BPF_MAP_TYPE_ARRAY);
174 	__uint(max_entries, 1);
175 	__type(key, u32);
176 	__type(value, u64);
177 } array SEC(".maps");
178 
179 char dynptr_data[8];
180 
181 int zero;
182 
183 SEC("socket")
184 __success
185 __log_level(2)
186 __msg("r8 = *(u64 *)(r7 +0){{.*}}R7=untrusted_ptr_sock")
187 __msg("r8 = *(u64 *)(r7 +0){{.*}}R7=ringbuf_mem")
188 __retval(0)
189 int mixed_mem_untrusted_btf_id_type(void *ctx)
190 {
191 	u64 *p, *q, v;
192 
193 	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
194 	if (!p)
195 		return 1;
196 	*p = 42;
197 	q = bpf_rdonly_cast(0, bpf_core_type_id_kernel(struct sock));
198 	/*
199 	 * The load below is reached with PTR_TO_MEM | MEM_RINGBUF on one
200 	 * path and with PTR_TO_BTF_ID | PTR_UNTRUSTED on the other. The
201 	 * merged type has to keep the BPF_PROBE_MEM rewrite, otherwise
202 	 * the NULL deref taken at runtime panics the kernel instead of
203 	 * returning 0.
204 	 */
205 	asm volatile (
206 	"r7 = %[p];"
207 	"if %[zero] != 0 goto +1;"
208 	"r7 = %[q];"
209 	"r8 = *(u64 *)(r7 + 0);"
210 	"%[v] = r8;"
211 	: [v]"=r"(v)
212 	: [p]"r"(p),
213 	  [q]"r"(q),
214 	  [zero]"r"(zero)
215 	: "r7", "r8");
216 	bpf_ringbuf_discard(p, 0);
217 	return v;
218 }
219 
220 SEC("socket")
221 __success
222 __log_level(2)
223 __msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ptr_nameidata")
224 __msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ringbuf_mem")
225 __retval(0)
226 int mixed_mem_btf_id_type(void *ctx)
227 {
228 	struct task_struct *task;
229 	u32 *p, *q;
230 	u64 v;
231 
232 	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
233 	if (!p)
234 		return 1;
235 	*p = 42;
236 	task = bpf_get_current_task_btf();
237 	/*
238 	 * A plain BTF pointer walk yields a bare PTR_TO_BTF_ID, and
239 	 * task->nameidata is NULL unless the task currently is in the
240 	 * middle of a path lookup.
241 	 */
242 	q = (u32 *)&task->nameidata->flags;
243 	/*
244 	 * Same as above, except that the other path yields a bare
245 	 * PTR_TO_BTF_ID. Merging it with PTR_TO_MEM used to drop the
246 	 * BPF_PROBE_MEM rewrite the bare PTR_TO_BTF_ID would have
247 	 * gotten on its own.
248 	 */
249 	asm volatile (
250 	"r7 = %[p];"
251 	"if %[zero] != 0 goto +1;"
252 	"r7 = %[q];"
253 	"r8 = *(u32 *)(r7 + 0);"
254 	"%[v] = r8;"
255 	: [v]"=r"(v)
256 	: [p]"r"(p),
257 	  [q]"r"(q),
258 	  [zero]"r"(zero)
259 	: "r7", "r8");
260 	bpf_ringbuf_discard(p, 0);
261 	return v;
262 }
263 
264 SEC("socket")
265 __success
266 __log_level(2)
267 __msg("r8 = *(u32 *)(r7 +0){{.*}}R7=ptr_nameidata")
268 __msg("r8 = *(u32 *)(r7 +0){{.*}}R7=rdonly_mem")
269 __retval(0)
270 int mixed_rdonly_mem_btf_id_type(void *ctx)
271 {
272 	struct task_struct *task;
273 	struct bpf_dynptr dptr;
274 	char buf[sizeof(u32)];
275 	u32 *p, *q;
276 	u64 v;
277 
278 	if (bpf_dynptr_from_mem(dynptr_data, sizeof(dynptr_data), 0, &dptr))
279 		return 1;
280 	p = bpf_dynptr_slice(&dptr, 0, buf, sizeof(buf));
281 	if (!p)
282 		return 1;
283 	task = bpf_get_current_task_btf();
284 	q = (u32 *)&task->nameidata->flags;
285 	/*
286 	 * Same as above, except that the PTR_TO_MEM side already carries
287 	 * MEM_RDONLY. Merging it with a bare PTR_TO_BTF_ID used to yield
288 	 * PTR_TO_MEM | MEM_RDONLY, which is not rewritten either since
289 	 * only its PTR_UNTRUSTED variant is.
290 	 */
291 	asm volatile (
292 	"r7 = %[p];"
293 	"if %[zero] != 0 goto +1;"
294 	"r7 = %[q];"
295 	"r8 = *(u32 *)(r7 + 0);"
296 	"%[v] = r8;"
297 	: [v]"=r"(v)
298 	: [p]"r"(p),
299 	  [q]"r"(q),
300 	  [zero]"r"(zero)
301 	: "r7", "r8");
302 	return v;
303 }
304 
305 SEC("socket")
306 __success
307 __log_level(2)
308 __msg("r8 = *(u64 *)(r7 +0){{.*}}R7=ringbuf_mem")
309 __msg("r8 = *(u64 *)(r7 +0){{.*}}R7=rdonly_untrusted_mem")
310 __retval(0)
311 int mixed_mem_mem_type(void *ctx)
312 {
313 	u64 *p, *q, v;
314 
315 	p = bpf_ringbuf_reserve(&ringbuf, sizeof(*p), 0);
316 	if (!p)
317 		return 1;
318 	*p = 42;
319 	q = bpf_rdonly_cast(0, 0);
320 	/*
321 	 * Both paths are PTR_TO_MEM based, so they used to not trip the
322 	 * type mismatch check and skipped the merge altogether, leaving
323 	 * the insn with the PTR_TO_MEM | MEM_RINGBUF recorded first and
324 	 * hence without the BPF_PROBE_MEM rewrite the other path needs.
325 	 */
326 	asm volatile (
327 	"r7 = %[q];"
328 	"if %[zero] == 0 goto +1;"
329 	"r7 = %[p];"
330 	"r8 = *(u64 *)(r7 + 0);"
331 	"%[v] = r8;"
332 	: [v]"=r"(v)
333 	: [p]"r"(p),
334 	  [q]"r"(q),
335 	  [zero]"r"(zero)
336 	: "r7", "r8");
337 	bpf_ringbuf_discard(p, 0);
338 	return v;
339 }
340 
341 SEC("socket")
342 __failure
343 __msg("same insn cannot be used with different pointers")
344 int mixed_map_value_mem_type(void *ctx)
345 {
346 	u64 *p, *q, v;
347 	u32 key = 0;
348 
349 	p = bpf_map_lookup_elem(&array, &key);
350 	if (!p)
351 		return 1;
352 	q = bpf_rdonly_cast(0, 0);
353 	/*
354 	 * PTR_TO_MAP_VALUE is neither PTR_TO_MEM nor PTR_TO_BTF_ID based,
355 	 * so it cannot be merged into a type which keeps the BPF_PROBE_MEM
356 	 * rewrite the PTR_TO_MEM | MEM_RDONLY | PTR_UNTRUSTED of the other
357 	 * path needs. Both bases were mismatch ok, hence the load used to be
358 	 * accepted with the PTR_TO_MAP_VALUE recorded and the NULL deref on
359 	 * the second path panicked the kernel.
360 	 */
361 	asm volatile (
362 	"r7 = %[q];"
363 	"if %[zero] == 0 goto +1;"
364 	"r7 = %[p];"
365 	"r8 = *(u64 *)(r7 + 0);"
366 	"%[v] = r8;"
367 	: [v]"=r"(v)
368 	: [p]"r"(p),
369 	  [q]"r"(q),
370 	  [zero]"r"(zero)
371 	: "r7", "r8");
372 	return v;
373 }
374 
375 SEC("socket")
376 __failure
377 __msg("same insn cannot be used with different pointers")
378 int mixed_stack_mem_type(void *ctx)
379 {
380 	u64 *p = bpf_rdonly_cast(0, 0);
381 	u64 s = 42, v;
382 
383 	/*
384 	 * Same as above, but for a PTR_TO_STACK on the other path.
385 	 */
386 	asm volatile (
387 	"r7 = %[p];"
388 	"if %[zero] == 0 goto +1;"
389 	"r7 = %[s];"
390 	"r8 = *(u64 *)(r7 + 0);"
391 	"%[v] = r8;"
392 	: [v]"=r"(v)
393 	: [p]"r"(p),
394 	  [s]"r"(&s),
395 	  [zero]"r"(zero)
396 	: "r7", "r8");
397 	return v;
398 }
399 
400 __attribute__((__aligned__(8)))
401 u8 global[] = {
402 	0x11, 0x22, 0x33, 0x44,
403 	0x55, 0x66, 0x77, 0x88,
404 	0x99
405 };
406 
407 __always_inline
408 static u64 combine(void *p)
409 {
410 	u64 acc;
411 
412 	acc = 0;
413 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
414 	acc |= (*(u64 *)p >> 56) << 24;
415 	acc |= (*(u32 *)p >> 24) << 16;
416 	acc |= (*(u16 *)p >> 8)  << 8;
417 	acc |= *(u8 *)p;
418 #else
419 	acc |= (*(u64 *)p & 0xff) << 24;
420 	acc |= (*(u32 *)p & 0xff) << 16;
421 	acc |= (*(u16 *)p & 0xff) << 8;
422 	acc |= *(u8 *)p;
423 #endif
424 	return acc;
425 }
426 
427 SEC("socket")
428 __retval(0x88442211)
429 int diff_size_access(void *ctx)
430 {
431 	return combine(bpf_rdonly_cast(&global, 0));
432 }
433 
434 SEC("socket")
435 __retval(0x99553322)
436 int misaligned_access(void *ctx)
437 {
438 	return combine(bpf_rdonly_cast(&global, 0) + 1);
439 }
440 
441 __weak int return_one(void)
442 {
443 	return 1;
444 }
445 
446 SEC("socket")
447 __success
448 __retval(1)
449 int null_check(void *ctx)
450 {
451 	int *p;
452 
453 	p = bpf_rdonly_cast(0, 0);
454 	if (p == 0)
455 		/* make this a function call to avoid compiler
456 		 * moving r0 assignment before check.
457 		 */
458 		return return_one();
459 	return 0;
460 }
461 
462 SEC("socket")
463 __success
464 __retval(1)
465 int ldx_is_ok_commuted_addr(void *ctx)
466 {
467 	int v, *p, *derived;
468 
469 	v = 1;
470 	p = bpf_rdonly_cast(&v, 0);
471 	asm volatile ("%[dst] = 0;"
472 		"%[dst] += %[src];"
473 		: [dst]"=&r"(derived)
474 		: [src]"r"(p)
475 		: "memory");
476 	return *derived;
477 }
478 
479 char _license[] SEC("license") = "GPL";
480