xref: /linux/tools/testing/selftests/bpf/progs/verifier_var_off.c (revision 34dc1baba215b826e454b8d19e4f24adbeb7d00d)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Converted from tools/testing/selftests/bpf/verifier/var_off.c */
3 
4 #include <linux/bpf.h>
5 #include <bpf/bpf_helpers.h>
6 #include "bpf_misc.h"
7 
8 struct {
9 	__uint(type, BPF_MAP_TYPE_HASH);
10 	__uint(max_entries, 1);
11 	__type(key, long long);
12 	__type(value, long long);
13 } map_hash_8b SEC(".maps");
14 
15 SEC("lwt_in")
16 __description("variable-offset ctx access")
17 __failure __msg("variable ctx access var_off=(0x0; 0x4)")
18 __naked void variable_offset_ctx_access(void)
19 {
20 	asm volatile ("					\
21 	/* Get an unknown value */			\
22 	r2 = *(u32*)(r1 + 0);				\
23 	/* Make it small and 4-byte aligned */		\
24 	r2 &= 4;					\
25 	/* add it to skb.  We now have either &skb->len or\
26 	 * &skb->pkt_type, but we don't know which	\
27 	 */						\
28 	r1 += r2;					\
29 	/* dereference it */				\
30 	r0 = *(u32*)(r1 + 0);				\
31 	exit;						\
32 "	::: __clobber_all);
33 }
34 
35 SEC("cgroup/skb")
36 __description("variable-offset stack read, priv vs unpriv")
37 __success __failure_unpriv
38 __msg_unpriv("R2 variable stack access prohibited for !root")
39 __retval(0)
40 __naked void stack_read_priv_vs_unpriv(void)
41 {
42 	asm volatile ("					\
43 	/* Fill the top 8 bytes of the stack */		\
44 	r0 = 0;						\
45 	*(u64*)(r10 - 8) = r0;				\
46 	/* Get an unknown value */			\
47 	r2 = *(u32*)(r1 + 0);				\
48 	/* Make it small and 4-byte aligned */		\
49 	r2 &= 4;					\
50 	r2 -= 8;					\
51 	/* add it to fp.  We now have either fp-4 or fp-8, but\
52 	 * we don't know which				\
53 	 */						\
54 	r2 += r10;					\
55 	/* dereference it for a stack read */		\
56 	r0 = *(u32*)(r2 + 0);				\
57 	r0 = 0;						\
58 	exit;						\
59 "	::: __clobber_all);
60 }
61 
62 SEC("lwt_in")
63 __description("variable-offset stack read, uninitialized")
64 __failure __msg("invalid variable-offset read from stack R2")
65 __naked void variable_offset_stack_read_uninitialized(void)
66 {
67 	asm volatile ("					\
68 	/* Get an unknown value */			\
69 	r2 = *(u32*)(r1 + 0);				\
70 	/* Make it small and 4-byte aligned */		\
71 	r2 &= 4;					\
72 	r2 -= 8;					\
73 	/* add it to fp.  We now have either fp-4 or fp-8, but\
74 	 * we don't know which				\
75 	 */						\
76 	r2 += r10;					\
77 	/* dereference it for a stack read */		\
78 	r0 = *(u32*)(r2 + 0);				\
79 	r0 = 0;						\
80 	exit;						\
81 "	::: __clobber_all);
82 }
83 
84 SEC("socket")
85 __description("variable-offset stack write, priv vs unpriv")
86 __success __failure_unpriv
87 /* Variable stack access is rejected for unprivileged.
88  */
89 __msg_unpriv("R2 variable stack access prohibited for !root")
90 __retval(0)
91 __naked void stack_write_priv_vs_unpriv(void)
92 {
93 	asm volatile ("					\
94 	/* Get an unknown value */			\
95 	r2 = *(u32*)(r1 + 0);				\
96 	/* Make it small and 8-byte aligned */		\
97 	r2 &= 8;					\
98 	r2 -= 16;					\
99 	/* Add it to fp.  We now have either fp-8 or fp-16, but\
100 	 * we don't know which				\
101 	 */						\
102 	r2 += r10;					\
103 	/* Dereference it for a stack write */		\
104 	r0 = 0;						\
105 	*(u64*)(r2 + 0) = r0;				\
106 	/* Now read from the address we just wrote. This shows\
107 	 * that, after a variable-offset write, a priviledged\
108 	 * program can read the slots that were in the range of\
109 	 * that write (even if the verifier doesn't actually know\
110 	 * if the slot being read was really written to or not.\
111 	 */						\
112 	r3 = *(u64*)(r2 + 0);				\
113 	r0 = 0;						\
114 	exit;						\
115 "	::: __clobber_all);
116 }
117 
118 SEC("socket")
119 __description("variable-offset stack write clobbers spilled regs")
120 __failure
121 /* In the priviledged case, dereferencing a spilled-and-then-filled
122  * register is rejected because the previous variable offset stack
123  * write might have overwritten the spilled pointer (i.e. we lose track
124  * of the spilled register when we analyze the write).
125  */
126 __msg("R2 invalid mem access 'scalar'")
127 __failure_unpriv
128 /* The unprivileged case is not too interesting; variable
129  * stack access is rejected.
130  */
131 __msg_unpriv("R2 variable stack access prohibited for !root")
132 __naked void stack_write_clobbers_spilled_regs(void)
133 {
134 	asm volatile ("					\
135 	/* Dummy instruction; needed because we need to patch the next one\
136 	 * and we can't patch the first instruction.	\
137 	 */						\
138 	r6 = 0;						\
139 	/* Make R0 a map ptr */				\
140 	r0 = %[map_hash_8b] ll;				\
141 	/* Get an unknown value */			\
142 	r2 = *(u32*)(r1 + 0);				\
143 	/* Make it small and 8-byte aligned */		\
144 	r2 &= 8;					\
145 	r2 -= 16;					\
146 	/* Add it to fp. We now have either fp-8 or fp-16, but\
147 	 * we don't know which.				\
148 	 */						\
149 	r2 += r10;					\
150 	/* Spill R0(map ptr) into stack */		\
151 	*(u64*)(r10 - 8) = r0;				\
152 	/* Dereference the unknown value for a stack write */\
153 	r0 = 0;						\
154 	*(u64*)(r2 + 0) = r0;				\
155 	/* Fill the register back into R2 */		\
156 	r2 = *(u64*)(r10 - 8);				\
157 	/* Try to dereference R2 for a memory load */	\
158 	r0 = *(u64*)(r2 + 8);				\
159 	exit;						\
160 "	:
161 	: __imm_addr(map_hash_8b)
162 	: __clobber_all);
163 }
164 
165 SEC("sockops")
166 __description("indirect variable-offset stack access, unbounded")
167 __failure __msg("invalid unbounded variable-offset indirect access to stack R4")
168 __naked void variable_offset_stack_access_unbounded(void)
169 {
170 	asm volatile ("					\
171 	r2 = 6;						\
172 	r3 = 28;					\
173 	/* Fill the top 16 bytes of the stack. */	\
174 	r4 = 0;						\
175 	*(u64*)(r10 - 16) = r4;				\
176 	r4 = 0;						\
177 	*(u64*)(r10 - 8) = r4;				\
178 	/* Get an unknown value. */			\
179 	r4 = *(u64*)(r1 + %[bpf_sock_ops_bytes_received]);\
180 	/* Check the lower bound but don't check the upper one. */\
181 	if r4 s< 0 goto l0_%=;				\
182 	/* Point the lower bound to initialized stack. Offset is now in range\
183 	 * from fp-16 to fp+0x7fffffffffffffef, i.e. max value is unbounded.\
184 	 */						\
185 	r4 -= 16;					\
186 	r4 += r10;					\
187 	r5 = 8;						\
188 	/* Dereference it indirectly. */		\
189 	call %[bpf_getsockopt];				\
190 l0_%=:	r0 = 0;						\
191 	exit;						\
192 "	:
193 	: __imm(bpf_getsockopt),
194 	  __imm_const(bpf_sock_ops_bytes_received, offsetof(struct bpf_sock_ops, bytes_received))
195 	: __clobber_all);
196 }
197 
198 SEC("lwt_in")
199 __description("indirect variable-offset stack access, max out of bound")
200 __failure __msg("invalid variable-offset indirect access to stack R2")
201 __naked void access_max_out_of_bound(void)
202 {
203 	asm volatile ("					\
204 	/* Fill the top 8 bytes of the stack */		\
205 	r2 = 0;						\
206 	*(u64*)(r10 - 8) = r2;				\
207 	/* Get an unknown value */			\
208 	r2 = *(u32*)(r1 + 0);				\
209 	/* Make it small and 4-byte aligned */		\
210 	r2 &= 4;					\
211 	r2 -= 8;					\
212 	/* add it to fp.  We now have either fp-4 or fp-8, but\
213 	 * we don't know which				\
214 	 */						\
215 	r2 += r10;					\
216 	/* dereference it indirectly */			\
217 	r1 = %[map_hash_8b] ll;				\
218 	call %[bpf_map_lookup_elem];			\
219 	r0 = 0;						\
220 	exit;						\
221 "	:
222 	: __imm(bpf_map_lookup_elem),
223 	  __imm_addr(map_hash_8b)
224 	: __clobber_all);
225 }
226 
227 SEC("lwt_in")
228 __description("indirect variable-offset stack access, min out of bound")
229 __failure __msg("invalid variable-offset indirect access to stack R2")
230 __naked void access_min_out_of_bound(void)
231 {
232 	asm volatile ("					\
233 	/* Fill the top 8 bytes of the stack */		\
234 	r2 = 0;						\
235 	*(u64*)(r10 - 8) = r2;				\
236 	/* Get an unknown value */			\
237 	r2 = *(u32*)(r1 + 0);				\
238 	/* Make it small and 4-byte aligned */		\
239 	r2 &= 4;					\
240 	r2 -= 516;					\
241 	/* add it to fp.  We now have either fp-516 or fp-512, but\
242 	 * we don't know which				\
243 	 */						\
244 	r2 += r10;					\
245 	/* dereference it indirectly */			\
246 	r1 = %[map_hash_8b] ll;				\
247 	call %[bpf_map_lookup_elem];			\
248 	r0 = 0;						\
249 	exit;						\
250 "	:
251 	: __imm(bpf_map_lookup_elem),
252 	  __imm_addr(map_hash_8b)
253 	: __clobber_all);
254 }
255 
256 SEC("lwt_in")
257 __description("indirect variable-offset stack access, min_off < min_initialized")
258 __failure __msg("invalid indirect read from stack R2 var_off")
259 __naked void access_min_off_min_initialized(void)
260 {
261 	asm volatile ("					\
262 	/* Fill only the top 8 bytes of the stack. */	\
263 	r2 = 0;						\
264 	*(u64*)(r10 - 8) = r2;				\
265 	/* Get an unknown value */			\
266 	r2 = *(u32*)(r1 + 0);				\
267 	/* Make it small and 4-byte aligned. */		\
268 	r2 &= 4;					\
269 	r2 -= 16;					\
270 	/* Add it to fp.  We now have either fp-12 or fp-16, but we don't know\
271 	 * which. fp-16 size 8 is partially uninitialized stack.\
272 	 */						\
273 	r2 += r10;					\
274 	/* Dereference it indirectly. */		\
275 	r1 = %[map_hash_8b] ll;				\
276 	call %[bpf_map_lookup_elem];			\
277 	r0 = 0;						\
278 	exit;						\
279 "	:
280 	: __imm(bpf_map_lookup_elem),
281 	  __imm_addr(map_hash_8b)
282 	: __clobber_all);
283 }
284 
285 SEC("cgroup/skb")
286 __description("indirect variable-offset stack access, priv vs unpriv")
287 __success __failure_unpriv
288 __msg_unpriv("R2 variable stack access prohibited for !root")
289 __retval(0)
290 __naked void stack_access_priv_vs_unpriv(void)
291 {
292 	asm volatile ("					\
293 	/* Fill the top 16 bytes of the stack. */	\
294 	r2 = 0;						\
295 	*(u64*)(r10 - 16) = r2;				\
296 	r2 = 0;						\
297 	*(u64*)(r10 - 8) = r2;				\
298 	/* Get an unknown value. */			\
299 	r2 = *(u32*)(r1 + 0);				\
300 	/* Make it small and 4-byte aligned. */		\
301 	r2 &= 4;					\
302 	r2 -= 16;					\
303 	/* Add it to fp.  We now have either fp-12 or fp-16, we don't know\
304 	 * which, but either way it points to initialized stack.\
305 	 */						\
306 	r2 += r10;					\
307 	/* Dereference it indirectly. */		\
308 	r1 = %[map_hash_8b] ll;				\
309 	call %[bpf_map_lookup_elem];			\
310 	r0 = 0;						\
311 	exit;						\
312 "	:
313 	: __imm(bpf_map_lookup_elem),
314 	  __imm_addr(map_hash_8b)
315 	: __clobber_all);
316 }
317 
318 SEC("lwt_in")
319 __description("indirect variable-offset stack access, ok")
320 __success __retval(0)
321 __naked void variable_offset_stack_access_ok(void)
322 {
323 	asm volatile ("					\
324 	/* Fill the top 16 bytes of the stack. */	\
325 	r2 = 0;						\
326 	*(u64*)(r10 - 16) = r2;				\
327 	r2 = 0;						\
328 	*(u64*)(r10 - 8) = r2;				\
329 	/* Get an unknown value. */			\
330 	r2 = *(u32*)(r1 + 0);				\
331 	/* Make it small and 4-byte aligned. */		\
332 	r2 &= 4;					\
333 	r2 -= 16;					\
334 	/* Add it to fp.  We now have either fp-12 or fp-16, we don't know\
335 	 * which, but either way it points to initialized stack.\
336 	 */						\
337 	r2 += r10;					\
338 	/* Dereference it indirectly. */		\
339 	r1 = %[map_hash_8b] ll;				\
340 	call %[bpf_map_lookup_elem];			\
341 	r0 = 0;						\
342 	exit;						\
343 "	:
344 	: __imm(bpf_map_lookup_elem),
345 	  __imm_addr(map_hash_8b)
346 	: __clobber_all);
347 }
348 
349 char _license[] SEC("license") = "GPL";
350