1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2022 Facebook */ |
3 | |
4 | #include <errno.h> |
5 | #include <string.h> |
6 | #include <linux/bpf.h> |
7 | #include <bpf/bpf_helpers.h> |
8 | #include "bpf_misc.h" |
9 | |
10 | char _license[] SEC("license" ) = "GPL" ; |
11 | |
12 | #define ITER_HELPERS \ |
13 | __imm(bpf_iter_num_new), \ |
14 | __imm(bpf_iter_num_next), \ |
15 | __imm(bpf_iter_num_destroy) |
16 | |
17 | SEC("?raw_tp" ) |
18 | __success |
19 | int force_clang_to_emit_btf_for_externs(void *ctx) |
20 | { |
21 | /* we need this as a workaround to enforce compiler emitting BTF |
22 | * information for bpf_iter_num_{new,next,destroy}() kfuncs, |
23 | * as, apparently, it doesn't emit it for symbols only referenced from |
24 | * assembly (or cleanup attribute, for that matter, as well) |
25 | */ |
26 | bpf_repeat(0); |
27 | |
28 | return 0; |
29 | } |
30 | |
31 | SEC("?raw_tp" ) |
32 | __success __log_level(2) |
33 | __msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)" ) |
34 | int create_and_destroy(void *ctx) |
35 | { |
36 | struct bpf_iter_num iter; |
37 | |
38 | asm volatile ( |
39 | /* create iterator */ |
40 | "r1 = %[iter];" |
41 | "r2 = 0;" |
42 | "r3 = 1000;" |
43 | "call %[bpf_iter_num_new];" |
44 | /* destroy iterator */ |
45 | "r1 = %[iter];" |
46 | "call %[bpf_iter_num_destroy];" |
47 | : |
48 | : __imm_ptr(iter), ITER_HELPERS |
49 | : __clobber_common |
50 | ); |
51 | |
52 | return 0; |
53 | } |
54 | |
55 | SEC("?raw_tp" ) |
56 | __failure __msg("Unreleased reference id=1" ) |
57 | int create_and_forget_to_destroy_fail(void *ctx) |
58 | { |
59 | struct bpf_iter_num iter; |
60 | |
61 | asm volatile ( |
62 | /* create iterator */ |
63 | "r1 = %[iter];" |
64 | "r2 = 0;" |
65 | "r3 = 1000;" |
66 | "call %[bpf_iter_num_new];" |
67 | : |
68 | : __imm_ptr(iter), ITER_HELPERS |
69 | : __clobber_common |
70 | ); |
71 | |
72 | return 0; |
73 | } |
74 | |
75 | SEC("?raw_tp" ) |
76 | __failure __msg("expected an initialized iter_num as arg #1" ) |
77 | int destroy_without_creating_fail(void *ctx) |
78 | { |
79 | /* init with zeros to stop verifier complaining about uninit stack */ |
80 | struct bpf_iter_num iter; |
81 | |
82 | asm volatile ( |
83 | "r1 = %[iter];" |
84 | "call %[bpf_iter_num_destroy];" |
85 | : |
86 | : __imm_ptr(iter), ITER_HELPERS |
87 | : __clobber_common |
88 | ); |
89 | |
90 | return 0; |
91 | } |
92 | |
93 | SEC("?raw_tp" ) |
94 | __failure __msg("expected an initialized iter_num as arg #1" ) |
95 | int compromise_iter_w_direct_write_fail(void *ctx) |
96 | { |
97 | struct bpf_iter_num iter; |
98 | |
99 | asm volatile ( |
100 | /* create iterator */ |
101 | "r1 = %[iter];" |
102 | "r2 = 0;" |
103 | "r3 = 1000;" |
104 | "call %[bpf_iter_num_new];" |
105 | |
106 | /* directly write over first half of iter state */ |
107 | "*(u64 *)(%[iter] + 0) = r0;" |
108 | |
109 | /* (attempt to) destroy iterator */ |
110 | "r1 = %[iter];" |
111 | "call %[bpf_iter_num_destroy];" |
112 | : |
113 | : __imm_ptr(iter), ITER_HELPERS |
114 | : __clobber_common |
115 | ); |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | SEC("?raw_tp" ) |
121 | __failure __msg("Unreleased reference id=1" ) |
122 | int compromise_iter_w_direct_write_and_skip_destroy_fail(void *ctx) |
123 | { |
124 | struct bpf_iter_num iter; |
125 | |
126 | asm volatile ( |
127 | /* create iterator */ |
128 | "r1 = %[iter];" |
129 | "r2 = 0;" |
130 | "r3 = 1000;" |
131 | "call %[bpf_iter_num_new];" |
132 | |
133 | /* directly write over first half of iter state */ |
134 | "*(u64 *)(%[iter] + 0) = r0;" |
135 | |
136 | /* don't destroy iter, leaking ref, which should fail */ |
137 | : |
138 | : __imm_ptr(iter), ITER_HELPERS |
139 | : __clobber_common |
140 | ); |
141 | |
142 | return 0; |
143 | } |
144 | |
145 | SEC("?raw_tp" ) |
146 | __failure __msg("expected an initialized iter_num as arg #1" ) |
147 | int compromise_iter_w_helper_write_fail(void *ctx) |
148 | { |
149 | struct bpf_iter_num iter; |
150 | |
151 | asm volatile ( |
152 | /* create iterator */ |
153 | "r1 = %[iter];" |
154 | "r2 = 0;" |
155 | "r3 = 1000;" |
156 | "call %[bpf_iter_num_new];" |
157 | |
158 | /* overwrite 8th byte with bpf_probe_read_kernel() */ |
159 | "r1 = %[iter];" |
160 | "r1 += 7;" |
161 | "r2 = 1;" |
162 | "r3 = 0;" /* NULL */ |
163 | "call %[bpf_probe_read_kernel];" |
164 | |
165 | /* (attempt to) destroy iterator */ |
166 | "r1 = %[iter];" |
167 | "call %[bpf_iter_num_destroy];" |
168 | : |
169 | : __imm_ptr(iter), ITER_HELPERS, __imm(bpf_probe_read_kernel) |
170 | : __clobber_common |
171 | ); |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static __noinline void subprog_with_iter(void) |
177 | { |
178 | struct bpf_iter_num iter; |
179 | |
180 | bpf_iter_num_new(&iter, 0, 1); |
181 | |
182 | return; |
183 | } |
184 | |
185 | SEC("?raw_tp" ) |
186 | __failure |
187 | /* ensure there was a call to subprog, which might happen without __noinline */ |
188 | __msg("returning from callee:" ) |
189 | __msg("Unreleased reference id=1" ) |
190 | int leak_iter_from_subprog_fail(void *ctx) |
191 | { |
192 | subprog_with_iter(); |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | SEC("?raw_tp" ) |
198 | __success __log_level(2) |
199 | __msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)" ) |
200 | int valid_stack_reuse(void *ctx) |
201 | { |
202 | struct bpf_iter_num iter; |
203 | |
204 | asm volatile ( |
205 | /* create iterator */ |
206 | "r1 = %[iter];" |
207 | "r2 = 0;" |
208 | "r3 = 1000;" |
209 | "call %[bpf_iter_num_new];" |
210 | /* destroy iterator */ |
211 | "r1 = %[iter];" |
212 | "call %[bpf_iter_num_destroy];" |
213 | |
214 | /* now reuse same stack slots */ |
215 | |
216 | /* create iterator */ |
217 | "r1 = %[iter];" |
218 | "r2 = 0;" |
219 | "r3 = 1000;" |
220 | "call %[bpf_iter_num_new];" |
221 | /* destroy iterator */ |
222 | "r1 = %[iter];" |
223 | "call %[bpf_iter_num_destroy];" |
224 | : |
225 | : __imm_ptr(iter), ITER_HELPERS |
226 | : __clobber_common |
227 | ); |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | SEC("?raw_tp" ) |
233 | __failure __msg("expected uninitialized iter_num as arg #1" ) |
234 | int double_create_fail(void *ctx) |
235 | { |
236 | struct bpf_iter_num iter; |
237 | |
238 | asm volatile ( |
239 | /* create iterator */ |
240 | "r1 = %[iter];" |
241 | "r2 = 0;" |
242 | "r3 = 1000;" |
243 | "call %[bpf_iter_num_new];" |
244 | /* (attempt to) create iterator again */ |
245 | "r1 = %[iter];" |
246 | "r2 = 0;" |
247 | "r3 = 1000;" |
248 | "call %[bpf_iter_num_new];" |
249 | /* destroy iterator */ |
250 | "r1 = %[iter];" |
251 | "call %[bpf_iter_num_destroy];" |
252 | : |
253 | : __imm_ptr(iter), ITER_HELPERS |
254 | : __clobber_common |
255 | ); |
256 | |
257 | return 0; |
258 | } |
259 | |
260 | SEC("?raw_tp" ) |
261 | __failure __msg("expected an initialized iter_num as arg #1" ) |
262 | int double_destroy_fail(void *ctx) |
263 | { |
264 | struct bpf_iter_num iter; |
265 | |
266 | asm volatile ( |
267 | /* create iterator */ |
268 | "r1 = %[iter];" |
269 | "r2 = 0;" |
270 | "r3 = 1000;" |
271 | "call %[bpf_iter_num_new];" |
272 | /* destroy iterator */ |
273 | "r1 = %[iter];" |
274 | "call %[bpf_iter_num_destroy];" |
275 | /* (attempt to) destroy iterator again */ |
276 | "r1 = %[iter];" |
277 | "call %[bpf_iter_num_destroy];" |
278 | : |
279 | : __imm_ptr(iter), ITER_HELPERS |
280 | : __clobber_common |
281 | ); |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | SEC("?raw_tp" ) |
287 | __failure __msg("expected an initialized iter_num as arg #1" ) |
288 | int next_without_new_fail(void *ctx) |
289 | { |
290 | struct bpf_iter_num iter; |
291 | |
292 | asm volatile ( |
293 | /* don't create iterator and try to iterate*/ |
294 | "r1 = %[iter];" |
295 | "call %[bpf_iter_num_next];" |
296 | /* destroy iterator */ |
297 | "r1 = %[iter];" |
298 | "call %[bpf_iter_num_destroy];" |
299 | : |
300 | : __imm_ptr(iter), ITER_HELPERS |
301 | : __clobber_common |
302 | ); |
303 | |
304 | return 0; |
305 | } |
306 | |
307 | SEC("?raw_tp" ) |
308 | __failure __msg("expected an initialized iter_num as arg #1" ) |
309 | int next_after_destroy_fail(void *ctx) |
310 | { |
311 | struct bpf_iter_num iter; |
312 | |
313 | asm volatile ( |
314 | /* create iterator */ |
315 | "r1 = %[iter];" |
316 | "r2 = 0;" |
317 | "r3 = 1000;" |
318 | "call %[bpf_iter_num_new];" |
319 | /* destroy iterator */ |
320 | "r1 = %[iter];" |
321 | "call %[bpf_iter_num_destroy];" |
322 | /* don't create iterator and try to iterate*/ |
323 | "r1 = %[iter];" |
324 | "call %[bpf_iter_num_next];" |
325 | : |
326 | : __imm_ptr(iter), ITER_HELPERS |
327 | : __clobber_common |
328 | ); |
329 | |
330 | return 0; |
331 | } |
332 | |
333 | SEC("?raw_tp" ) |
334 | __failure __msg("invalid read from stack" ) |
335 | int __naked read_from_iter_slot_fail(void) |
336 | { |
337 | asm volatile ( |
338 | /* r6 points to struct bpf_iter_num on the stack */ |
339 | "r6 = r10;" |
340 | "r6 += -24;" |
341 | |
342 | /* create iterator */ |
343 | "r1 = r6;" |
344 | "r2 = 0;" |
345 | "r3 = 1000;" |
346 | "call %[bpf_iter_num_new];" |
347 | |
348 | /* attemp to leak bpf_iter_num state */ |
349 | "r7 = *(u64 *)(r6 + 0);" |
350 | "r8 = *(u64 *)(r6 + 8);" |
351 | |
352 | /* destroy iterator */ |
353 | "r1 = r6;" |
354 | "call %[bpf_iter_num_destroy];" |
355 | |
356 | /* leak bpf_iter_num state */ |
357 | "r0 = r7;" |
358 | "if r7 > r8 goto +1;" |
359 | "r0 = r8;" |
360 | "exit;" |
361 | : |
362 | : ITER_HELPERS |
363 | : __clobber_common, "r6" , "r7" , "r8" |
364 | ); |
365 | } |
366 | |
367 | int zero; |
368 | |
369 | SEC("?raw_tp" ) |
370 | __failure |
371 | __flag(BPF_F_TEST_STATE_FREQ) |
372 | __msg("Unreleased reference" ) |
373 | int stacksafe_should_not_conflate_stack_spill_and_iter(void *ctx) |
374 | { |
375 | struct bpf_iter_num iter; |
376 | |
377 | asm volatile ( |
378 | /* Create a fork in logic, with general setup as follows: |
379 | * - fallthrough (first) path is valid; |
380 | * - branch (second) path is invalid. |
381 | * Then depending on what we do in fallthrough vs branch path, |
382 | * we try to detect bugs in func_states_equal(), regsafe(), |
383 | * refsafe(), stack_safe(), and similar by tricking verifier |
384 | * into believing that branch state is a valid subset of |
385 | * a fallthrough state. Verifier should reject overall |
386 | * validation, unless there is a bug somewhere in verifier |
387 | * logic. |
388 | */ |
389 | "call %[bpf_get_prandom_u32];" |
390 | "r6 = r0;" |
391 | "call %[bpf_get_prandom_u32];" |
392 | "r7 = r0;" |
393 | |
394 | "if r6 > r7 goto bad;" /* fork */ |
395 | |
396 | /* spill r6 into stack slot of bpf_iter_num var */ |
397 | "*(u64 *)(%[iter] + 0) = r6;" |
398 | |
399 | "goto skip_bad;" |
400 | |
401 | "bad:" |
402 | /* create iterator in the same stack slot */ |
403 | "r1 = %[iter];" |
404 | "r2 = 0;" |
405 | "r3 = 1000;" |
406 | "call %[bpf_iter_num_new];" |
407 | |
408 | /* but then forget about it and overwrite it back to r6 spill */ |
409 | "*(u64 *)(%[iter] + 0) = r6;" |
410 | |
411 | "skip_bad:" |
412 | "goto +0;" /* force checkpoint */ |
413 | |
414 | /* corrupt stack slots, if they are really dynptr */ |
415 | "*(u64 *)(%[iter] + 0) = r6;" |
416 | : |
417 | : __imm_ptr(iter), |
418 | __imm_addr(zero), |
419 | __imm(bpf_get_prandom_u32), |
420 | __imm(bpf_dynptr_from_mem), |
421 | ITER_HELPERS |
422 | : __clobber_common, "r6" , "r7" |
423 | ); |
424 | |
425 | return 0; |
426 | } |
427 | |