419 lines · c
1// SPDX-License-Identifier: GPL-2.02/* 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 8struct {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 15SEC("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 35SEC("cgroup/skb")36__description("variable-offset stack read, priv vs unpriv")37__success __failure_unpriv38__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 62SEC("cgroup/skb")63__description("variable-offset stack read, uninitialized")64__success65__failure_unpriv __msg_unpriv("R2 variable stack access prohibited for !root")66__naked void variable_offset_stack_read_uninitialized(void)67{68 asm volatile (" \69 /* Get an unknown value */ \70 r2 = *(u32*)(r1 + 0); \71 /* Make it small and 4-byte aligned */ \72 r2 &= 4; \73 r2 -= 8; \74 /* add it to fp. We now have either fp-4 or fp-8, but\75 * we don't know which \76 */ \77 r2 += r10; \78 /* dereference it for a stack read */ \79 r0 = *(u32*)(r2 + 0); \80 r0 = 0; \81 exit; \82" ::: __clobber_all);83}84 85SEC("socket")86__description("variable-offset stack write, priv vs unpriv")87__success88/* Check that the maximum stack depth is correctly maintained according to the89 * maximum possible variable offset.90 */91__log_level(4) __msg("stack depth 16")92__failure_unpriv93/* Variable stack access is rejected for unprivileged.94 */95__msg_unpriv("R2 variable stack access prohibited for !root")96__retval(0)97__naked void stack_write_priv_vs_unpriv(void)98{99 asm volatile (" \100 /* Get an unknown value */ \101 r2 = *(u32*)(r1 + 0); \102 /* Make it small and 8-byte aligned */ \103 r2 &= 8; \104 r2 -= 16; \105 /* Add it to fp. We now have either fp-8 or \106 * fp-16, but we don't know which \107 */ \108 r2 += r10; \109 /* Dereference it for a stack write */ \110 r0 = 0; \111 *(u64*)(r2 + 0) = r0; \112 exit; \113" ::: __clobber_all);114}115 116/* Similar to the previous test, but this time also perform a read from the117 * address written to with a variable offset. The read is allowed, showing that,118 * after a variable-offset write, a priviledged program can read the slots that119 * were in the range of that write (even if the verifier doesn't actually know if120 * the slot being read was really written to or not.121 *122 * Despite this test being mostly a superset, the previous test is also kept for123 * the sake of it checking the stack depth in the case where there is no read.124 */125SEC("socket")126__description("variable-offset stack write followed by read")127__success128/* Check that the maximum stack depth is correctly maintained according to the129 * maximum possible variable offset.130 */131__log_level(4) __msg("stack depth 16")132__failure_unpriv133__msg_unpriv("R2 variable stack access prohibited for !root")134__retval(0)135__naked void stack_write_followed_by_read(void)136{137 asm volatile (" \138 /* Get an unknown value */ \139 r2 = *(u32*)(r1 + 0); \140 /* Make it small and 8-byte aligned */ \141 r2 &= 8; \142 r2 -= 16; \143 /* Add it to fp. We now have either fp-8 or fp-16, but\144 * we don't know which \145 */ \146 r2 += r10; \147 /* Dereference it for a stack write */ \148 r0 = 0; \149 *(u64*)(r2 + 0) = r0; \150 /* Now read from the address we just wrote. */ \151 r3 = *(u64*)(r2 + 0); \152 r0 = 0; \153 exit; \154" ::: __clobber_all);155}156 157SEC("socket")158__description("variable-offset stack write clobbers spilled regs")159__failure160/* In the priviledged case, dereferencing a spilled-and-then-filled161 * register is rejected because the previous variable offset stack162 * write might have overwritten the spilled pointer (i.e. we lose track163 * of the spilled register when we analyze the write).164 */165__msg("R2 invalid mem access 'scalar'")166__failure_unpriv167/* The unprivileged case is not too interesting; variable168 * stack access is rejected.169 */170__msg_unpriv("R2 variable stack access prohibited for !root")171__naked void stack_write_clobbers_spilled_regs(void)172{173 asm volatile (" \174 /* Dummy instruction; needed because we need to patch the next one\175 * and we can't patch the first instruction. \176 */ \177 r6 = 0; \178 /* Make R0 a map ptr */ \179 r0 = %[map_hash_8b] ll; \180 /* Get an unknown value */ \181 r2 = *(u32*)(r1 + 0); \182 /* Make it small and 8-byte aligned */ \183 r2 &= 8; \184 r2 -= 16; \185 /* Add it to fp. We now have either fp-8 or fp-16, but\186 * we don't know which. \187 */ \188 r2 += r10; \189 /* Spill R0(map ptr) into stack */ \190 *(u64*)(r10 - 8) = r0; \191 /* Dereference the unknown value for a stack write */\192 r0 = 0; \193 *(u64*)(r2 + 0) = r0; \194 /* Fill the register back into R2 */ \195 r2 = *(u64*)(r10 - 8); \196 /* Try to dereference R2 for a memory load */ \197 r0 = *(u64*)(r2 + 8); \198 exit; \199" :200 : __imm_addr(map_hash_8b)201 : __clobber_all);202}203 204SEC("sockops")205__description("indirect variable-offset stack access, unbounded")206__failure __msg("invalid unbounded variable-offset indirect access to stack R4")207__naked void variable_offset_stack_access_unbounded(void)208{209 asm volatile (" \210 r2 = 6; \211 r3 = 28; \212 /* Fill the top 16 bytes of the stack. */ \213 r4 = 0; \214 *(u64*)(r10 - 16) = r4; \215 r4 = 0; \216 *(u64*)(r10 - 8) = r4; \217 /* Get an unknown value. */ \218 r4 = *(u64*)(r1 + %[bpf_sock_ops_bytes_received]);\219 /* Check the lower bound but don't check the upper one. */\220 if r4 s< 0 goto l0_%=; \221 /* Point the lower bound to initialized stack. Offset is now in range\222 * from fp-16 to fp+0x7fffffffffffffef, i.e. max value is unbounded.\223 */ \224 r4 -= 16; \225 r4 += r10; \226 r5 = 8; \227 /* Dereference it indirectly. */ \228 call %[bpf_getsockopt]; \229l0_%=: r0 = 0; \230 exit; \231" :232 : __imm(bpf_getsockopt),233 __imm_const(bpf_sock_ops_bytes_received, offsetof(struct bpf_sock_ops, bytes_received))234 : __clobber_all);235}236 237SEC("lwt_in")238__description("indirect variable-offset stack access, max out of bound")239__failure __msg("invalid variable-offset indirect access to stack R2")240__naked void access_max_out_of_bound(void)241{242 asm volatile (" \243 /* Fill the top 8 bytes of the stack */ \244 r2 = 0; \245 *(u64*)(r10 - 8) = r2; \246 /* Get an unknown value */ \247 r2 = *(u32*)(r1 + 0); \248 /* Make it small and 4-byte aligned */ \249 r2 &= 4; \250 r2 -= 8; \251 /* add it to fp. We now have either fp-4 or fp-8, but\252 * we don't know which \253 */ \254 r2 += r10; \255 /* dereference it indirectly */ \256 r1 = %[map_hash_8b] ll; \257 call %[bpf_map_lookup_elem]; \258 r0 = 0; \259 exit; \260" :261 : __imm(bpf_map_lookup_elem),262 __imm_addr(map_hash_8b)263 : __clobber_all);264}265 266/* Similar to the test above, but this time check the special case of a267 * zero-sized stack access. We used to have a bug causing crashes for zero-sized268 * out-of-bounds accesses.269 */270SEC("socket")271__description("indirect variable-offset stack access, zero-sized, max out of bound")272__failure __msg("invalid variable-offset indirect access to stack R1")273__naked void zero_sized_access_max_out_of_bound(void)274{275 asm volatile (" \276 r0 = 0; \277 /* Fill some stack */ \278 *(u64*)(r10 - 16) = r0; \279 *(u64*)(r10 - 8) = r0; \280 /* Get an unknown value */ \281 r1 = *(u32*)(r1 + 0); \282 r1 &= 63; \283 r1 += -16; \284 /* r1 is now anywhere in [-16,48) */ \285 r1 += r10; \286 r2 = 0; \287 r3 = 0; \288 call %[bpf_probe_read_kernel]; \289 exit; \290" :291 : __imm(bpf_probe_read_kernel)292 : __clobber_all);293}294 295SEC("lwt_in")296__description("indirect variable-offset stack access, min out of bound")297__failure __msg("invalid variable-offset indirect access to stack R2")298__naked void access_min_out_of_bound(void)299{300 asm volatile (" \301 /* Fill the top 8 bytes of the stack */ \302 r2 = 0; \303 *(u64*)(r10 - 8) = r2; \304 /* Get an unknown value */ \305 r2 = *(u32*)(r1 + 0); \306 /* Make it small and 4-byte aligned */ \307 r2 &= 4; \308 r2 -= 516; \309 /* add it to fp. We now have either fp-516 or fp-512, but\310 * we don't know which \311 */ \312 r2 += r10; \313 /* dereference it indirectly */ \314 r1 = %[map_hash_8b] ll; \315 call %[bpf_map_lookup_elem]; \316 r0 = 0; \317 exit; \318" :319 : __imm(bpf_map_lookup_elem),320 __imm_addr(map_hash_8b)321 : __clobber_all);322}323 324SEC("cgroup/skb")325__description("indirect variable-offset stack access, min_off < min_initialized")326__success327__failure_unpriv __msg_unpriv("R2 variable stack access prohibited for !root")328__naked void access_min_off_min_initialized(void)329{330 asm volatile (" \331 /* Fill only the top 8 bytes of the stack. */ \332 r2 = 0; \333 *(u64*)(r10 - 8) = r2; \334 /* Get an unknown value */ \335 r2 = *(u32*)(r1 + 0); \336 /* Make it small and 4-byte aligned. */ \337 r2 &= 4; \338 r2 -= 16; \339 /* Add it to fp. We now have either fp-12 or fp-16, but we don't know\340 * which. fp-16 size 8 is partially uninitialized stack.\341 */ \342 r2 += r10; \343 /* Dereference it indirectly. */ \344 r1 = %[map_hash_8b] ll; \345 call %[bpf_map_lookup_elem]; \346 r0 = 0; \347 exit; \348" :349 : __imm(bpf_map_lookup_elem),350 __imm_addr(map_hash_8b)351 : __clobber_all);352}353 354SEC("cgroup/skb")355__description("indirect variable-offset stack access, priv vs unpriv")356__success __failure_unpriv357__msg_unpriv("R2 variable stack access prohibited for !root")358__retval(0)359__naked void stack_access_priv_vs_unpriv(void)360{361 asm volatile (" \362 /* Fill the top 16 bytes of the stack. */ \363 r2 = 0; \364 *(u64*)(r10 - 16) = r2; \365 r2 = 0; \366 *(u64*)(r10 - 8) = r2; \367 /* Get an unknown value. */ \368 r2 = *(u32*)(r1 + 0); \369 /* Make it small and 4-byte aligned. */ \370 r2 &= 4; \371 r2 -= 16; \372 /* Add it to fp. We now have either fp-12 or fp-16, we don't know\373 * which, but either way it points to initialized stack.\374 */ \375 r2 += r10; \376 /* Dereference it indirectly. */ \377 r1 = %[map_hash_8b] ll; \378 call %[bpf_map_lookup_elem]; \379 r0 = 0; \380 exit; \381" :382 : __imm(bpf_map_lookup_elem),383 __imm_addr(map_hash_8b)384 : __clobber_all);385}386 387SEC("lwt_in")388__description("indirect variable-offset stack access, ok")389__success __retval(0)390__naked void variable_offset_stack_access_ok(void)391{392 asm volatile (" \393 /* Fill the top 16 bytes of the stack. */ \394 r2 = 0; \395 *(u64*)(r10 - 16) = r2; \396 r2 = 0; \397 *(u64*)(r10 - 8) = r2; \398 /* Get an unknown value. */ \399 r2 = *(u32*)(r1 + 0); \400 /* Make it small and 4-byte aligned. */ \401 r2 &= 4; \402 r2 -= 16; \403 /* Add it to fp. We now have either fp-12 or fp-16, we don't know\404 * which, but either way it points to initialized stack.\405 */ \406 r2 += r10; \407 /* Dereference it indirectly. */ \408 r1 = %[map_hash_8b] ll; \409 call %[bpf_map_lookup_elem]; \410 r0 = 0; \411 exit; \412" :413 : __imm(bpf_map_lookup_elem),414 __imm_addr(map_hash_8b)415 : __clobber_all);416}417 418char _license[] SEC("license") = "GPL";419