1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ 3 4 #include <vmlinux.h> 5 #include <bpf/bpf_helpers.h> 6 #include <bpf/bpf_core_read.h> 7 #include "bpf_experimental.h" 8 #include "bpf_misc.h" 9 10 /* 11 * Variable-offset access into arrays within a BTF access chain, e.g. a[i].b, 12 * where 'i' is a register with a varying offset. The register's step has to 13 * match the stride of one of the arrays crossed while walking the access 14 * chain, and the maximal possible offset has to stay within that array. 15 * 16 * A program-allocated object (bpf_obj_new) is used as the source of a 17 * PTR_TO_BTF_ID with a shape we fully control. 18 */ 19 struct inner { 20 int a; 21 int b; 22 }; 23 24 struct outer { 25 struct inner arr[8]; /* off 0, stride 8, size 64 */ 26 char bytes[32]; /* off 64, stride 1, size 32 */ 27 long longs[8]; /* off 96, stride 8, size 64 */ 28 int grid[4][4]; /* off 160, 16 ints, size 64 */ 29 }; 30 31 /* arr[i].b, i in [0, 7], 4-byte read: stays within arr. */ 32 SEC("syscall") 33 __success 34 int arr_field_in_bounds(void *ctx) 35 { 36 unsigned long i = bpf_get_prandom_u32(); 37 struct outer *o; 38 int val = 0; 39 40 o = bpf_obj_new(typeof(*o)); 41 if (!o) 42 return 0; 43 asm volatile (" \ 44 r1 = %[o]; \ 45 r2 = %[i]; \ 46 r2 &= 7; \ 47 r2 *= 8; \ 48 r1 += r2; \ 49 %[val] = *(u32 *)(r1 + 4); \ 50 " : [val] "=r"(val) 51 : [o] "r"(o), [i] "r"(i) 52 : "r1", "r2"); 53 bpf_obj_drop(o); 54 return val; 55 } 56 57 /* arr[i].b, i in [0, 15]: max offset runs past the end of arr. */ 58 SEC("syscall") 59 __failure 60 __msg("invalid variable offset access into struct outer") 61 int arr_field_out_of_bounds(void *ctx) 62 { 63 unsigned long i = bpf_get_prandom_u32(); 64 struct outer *o; 65 int val = 0; 66 67 o = bpf_obj_new(typeof(*o)); 68 if (!o) 69 return 0; 70 asm volatile (" \ 71 r1 = %[o]; \ 72 r2 = %[i]; \ 73 r2 &= 15; \ 74 r2 *= 8; \ 75 r1 += r2; \ 76 %[val] = *(u32 *)(r1 + 4); \ 77 " : [val] "=r"(val) 78 : [o] "r"(o), [i] "r"(i) 79 : "r1", "r2"); 80 bpf_obj_drop(o); 81 return val; 82 } 83 84 /* longs[i], i in [0, 7], 8-byte read: scalar array with stride 8. */ 85 SEC("syscall") 86 __success 87 int long_array_in_bounds(void *ctx) 88 { 89 unsigned long i = bpf_get_prandom_u32(); 90 struct outer *o; 91 long val = 0; 92 93 o = bpf_obj_new(typeof(*o)); 94 if (!o) 95 return 0; 96 asm volatile (" \ 97 r1 = %[o]; \ 98 r2 = %[i]; \ 99 r2 &= 7; \ 100 r2 *= 8; \ 101 r1 += r2; \ 102 %[val] = *(u64 *)(r1 + 96); \ 103 " : [val] "=r"(val) 104 : [o] "r"(o), [i] "r"(i) 105 : "r1", "r2"); 106 bpf_obj_drop(o); 107 return val; 108 } 109 110 /* 111 * bytes[i], i in [0, 31], read as 4 bytes: min_off is valid within the array, 112 * but the 4-byte read at max_off runs past the array end. Exercises the 113 * size-aware bound in btf_struct_access(). 114 */ 115 SEC("syscall") 116 __failure 117 __flag(BPF_F_ANY_ALIGNMENT) 118 __msg("invalid variable offset access into struct outer") 119 int byte_array_size_spanning(void *ctx) 120 { 121 unsigned long i = bpf_get_prandom_u32(); 122 struct outer *o; 123 int val = 0; 124 125 o = bpf_obj_new(typeof(*o)); 126 if (!o) 127 return 0; 128 asm volatile (" \ 129 r1 = %[o]; \ 130 r2 = %[i]; \ 131 r2 &= 31; \ 132 r1 += r2; \ 133 %[val] = *(u32 *)(r1 + 64); \ 134 " : [val] "=r"(val) 135 : [o] "r"(o), [i] "r"(i) 136 : "r1", "r2"); 137 bpf_obj_drop(o); 138 return val; 139 } 140 141 /* bytes[i], i in [0, 31], 1-byte read: step 1 matches the char array stride. */ 142 SEC("syscall") 143 __success 144 int byte_array_in_bounds(void *ctx) 145 { 146 unsigned long i = bpf_get_prandom_u32(); 147 struct outer *o; 148 int val = 0; 149 150 o = bpf_obj_new(typeof(*o)); 151 if (!o) 152 return 0; 153 asm volatile (" \ 154 r1 = %[o]; \ 155 r2 = %[i]; \ 156 r2 &= 31; \ 157 r1 += r2; \ 158 %[val] = *(u8 *)(r1 + 64); \ 159 " : [val] "=r"(val) 160 : [o] "r"(o), [i] "r"(i) 161 : "r1", "r2"); 162 bpf_obj_drop(o); 163 return val; 164 } 165 166 /* 167 * arr[] accessed with step 4 (half of sizeof(struct inner)): the step is not 168 * a whole number of elements, so no crossed array has a matching stride. 169 */ 170 SEC("syscall") 171 __failure 172 __msg("invalid variable offset access into struct outer") 173 int arr_unaligned_step(void *ctx) 174 { 175 unsigned long i = bpf_get_prandom_u32(); 176 struct outer *o; 177 int val = 0; 178 179 o = bpf_obj_new(typeof(*o)); 180 if (!o) 181 return 0; 182 asm volatile (" \ 183 r1 = %[o]; \ 184 r2 = %[i]; \ 185 r2 &= 7; \ 186 r2 *= 4; \ 187 r1 += r2; \ 188 %[val] = *(u32 *)(r1 + 0); \ 189 " : [val] "=r"(val) 190 : [o] "r"(o), [i] "r"(i) 191 : "r1", "r2"); 192 bpf_obj_drop(o); 193 return val; 194 } 195 196 /* 197 * grid[i][0], i in [0, 3]: steps along the outer dimension of a 2D array 198 * (stride 16). __btf_resolve_size() linearizes the array; the step is a 199 * multiple of the innermost element size (4). 200 */ 201 SEC("syscall") 202 __success 203 int grid_outer_dim(void *ctx) 204 { 205 unsigned long i = bpf_get_prandom_u32(); 206 struct outer *o; 207 int val = 0; 208 209 o = bpf_obj_new(typeof(*o)); 210 if (!o) 211 return 0; 212 asm volatile (" \ 213 r1 = %[o]; \ 214 r2 = %[i]; \ 215 r2 &= 3; \ 216 r2 *= 16; \ 217 r1 += r2; \ 218 %[val] = *(u32 *)(r1 + 160); \ 219 " : [val] "=r"(val) 220 : [o] "r"(o), [i] "r"(i) 221 : "r1", "r2"); 222 bpf_obj_drop(o); 223 return val; 224 } 225 226 /* 227 * grid[0][i], i in [0, 3]: steps along the inner dimension of a 2D array 228 * (stride 4, the innermost element size). 229 */ 230 SEC("syscall") 231 __success 232 int grid_inner_dim(void *ctx) 233 { 234 unsigned long i = bpf_get_prandom_u32(); 235 struct outer *o; 236 int val = 0; 237 238 o = bpf_obj_new(typeof(*o)); 239 if (!o) 240 return 0; 241 asm volatile (" \ 242 r1 = %[o]; \ 243 r2 = %[i]; \ 244 r2 &= 3; \ 245 r2 *= 4; \ 246 r1 += r2; \ 247 %[val] = *(u32 *)(r1 + 160); \ 248 " : [val] "=r"(val) 249 : [o] "r"(o), [i] "r"(i) 250 : "r1", "r2"); 251 bpf_obj_drop(o); 252 return val; 253 } 254 255 /* 256 * An array of pointers within a program-allocated object. Reading a pointer 257 * member of a local object yields a SCALAR_VALUE, but the varying offset still 258 * has to be validated against the array bounds. 259 */ 260 struct with_ptrs { 261 struct inner *parr[8]; /* off 0, stride 8 (sizeof ptr), size 64 */ 262 }; 263 264 /* parr[i], i in [0, 7], 8-byte read: stays within parr. */ 265 SEC("syscall") 266 __success 267 int ptr_array_in_bounds(void *ctx) 268 { 269 unsigned long i = bpf_get_prandom_u32(); 270 struct with_ptrs *o; 271 long val = 0; 272 273 o = bpf_obj_new(typeof(*o)); 274 if (!o) 275 return 0; 276 asm volatile (" \ 277 r1 = %[o]; \ 278 r2 = %[i]; \ 279 r2 &= 7; \ 280 r2 *= 8; \ 281 r1 += r2; \ 282 %[val] = *(u64 *)(r1 + 0); \ 283 " : [val] "=r"(val) 284 : [o] "r"(o), [i] "r"(i) 285 : "r1", "r2"); 286 bpf_obj_drop(o); 287 return val; 288 } 289 290 /* 291 * parr[i], i in [0, 15]: max offset runs past the end of parr. Without the 292 * variable-offset check on the WALK_PTR path this would be wrongly accepted. 293 */ 294 SEC("syscall") 295 __failure 296 __msg("invalid variable offset access into struct with_ptrs") 297 int ptr_array_out_of_bounds(void *ctx) 298 { 299 unsigned long i = bpf_get_prandom_u32(); 300 struct with_ptrs *o; 301 long val = 0; 302 303 o = bpf_obj_new(typeof(*o)); 304 if (!o) 305 return 0; 306 asm volatile (" \ 307 r1 = %[o]; \ 308 r2 = %[i]; \ 309 r2 &= 15; \ 310 r2 *= 8; \ 311 r1 += r2; \ 312 %[val] = *(u64 *)(r1 + 0); \ 313 " : [val] "=r"(val) 314 : [o] "r"(o), [i] "r"(i) 315 : "r1", "r2"); 316 bpf_obj_drop(o); 317 return val; 318 } 319 320 /* 321 * The cases above all use bpf_obj_new() (PTR_TO_BTF_ID | MEM_ALLOC). Exercise 322 * the trusted kernel-BTF path too, using a task_struct from 323 * bpf_get_current_task_btf() and its embedded char comm[] array. The comm 324 * offset is folded into the pointer as a constant before the varying index. 325 */ 326 327 /* task->comm[i], i in [0, 15], 1-byte read: stays within comm. */ 328 SEC("syscall") 329 __success 330 int kernel_btf_array_in_bounds(void *ctx) 331 { 332 unsigned long i = bpf_get_prandom_u32(); 333 struct task_struct *task; 334 int val = 0; 335 336 task = bpf_get_current_task_btf(); 337 asm volatile (" \ 338 r1 = %[task]; \ 339 r1 += %[off]; \ 340 r2 = %[i]; \ 341 r2 &= 15; \ 342 r1 += r2; \ 343 %[val] = *(u8 *)(r1 + 0); \ 344 " : [val] "=r"(val) 345 : [task] "r"(task), [i] "r"(i), 346 [off] "i"(offsetof(struct task_struct, comm)) 347 : "r1", "r2"); 348 return val; 349 } 350 351 /* task->comm[i], i in [0, 31]: max offset runs past comm. */ 352 SEC("syscall") 353 __failure 354 __msg("invalid variable offset access into struct task_struct") 355 int kernel_btf_array_out_of_bounds(void *ctx) 356 { 357 unsigned long i = bpf_get_prandom_u32(); 358 struct task_struct *task; 359 int val = 0; 360 361 task = bpf_get_current_task_btf(); 362 asm volatile (" \ 363 r1 = %[task]; \ 364 r1 += %[off]; \ 365 r2 = %[i]; \ 366 r2 &= 31; \ 367 r1 += r2; \ 368 %[val] = *(u8 *)(r1 + 0); \ 369 " : [val] "=r"(val) 370 : [task] "r"(task), [i] "r"(i), 371 [off] "i"(offsetof(struct task_struct, comm)) 372 : "r1", "r2"); 373 return val; 374 } 375 376 /* 377 * Array nested below a struct member at a non-zero offset. The walk descends 378 * through 'm' (resetting the running offset) before reaching 'arr', exercising 379 * the array_start = min_off - arrays[i].off telescoping across a WALK_STRUCT 380 * dive. 381 */ 382 struct mid { 383 struct inner arr[8]; /* stride 8, size 64 */ 384 }; 385 386 struct nest { 387 long pad; /* off 0 */ 388 struct mid m; /* off 8 */ 389 }; 390 391 /* m.arr[i].b, i in [0, 7], 4-byte read: stays within m.arr. */ 392 SEC("syscall") 393 __success 394 int nested_struct_in_bounds(void *ctx) 395 { 396 unsigned long i = bpf_get_prandom_u32(); 397 struct nest *o; 398 int val = 0; 399 400 o = bpf_obj_new(typeof(*o)); 401 if (!o) 402 return 0; 403 asm volatile (" \ 404 r1 = %[o]; \ 405 r2 = %[i]; \ 406 r2 &= 7; \ 407 r2 *= 8; \ 408 r1 += r2; \ 409 %[val] = *(u32 *)(r1 + 12); \ 410 " : [val] "=r"(val) 411 : [o] "r"(o), [i] "r"(i) 412 : "r1", "r2"); 413 bpf_obj_drop(o); 414 return val; 415 } 416 417 /* m.arr[i].b, i in [0, 15]: max offset runs past the end of m.arr. */ 418 SEC("syscall") 419 __failure 420 __msg("invalid variable offset access into struct nest") 421 int nested_struct_out_of_bounds(void *ctx) 422 { 423 unsigned long i = bpf_get_prandom_u32(); 424 struct nest *o; 425 int val = 0; 426 427 o = bpf_obj_new(typeof(*o)); 428 if (!o) 429 return 0; 430 asm volatile (" \ 431 r1 = %[o]; \ 432 r2 = %[i]; \ 433 r2 &= 15; \ 434 r2 *= 8; \ 435 r1 += r2; \ 436 %[val] = *(u32 *)(r1 + 12); \ 437 " : [val] "=r"(val) 438 : [o] "r"(o), [i] "r"(i) 439 : "r1", "r2"); 440 bpf_obj_drop(o); 441 return val; 442 } 443 444 /* 445 * A trailing flexible array member makes the struct tail an unbounded region, 446 * so a varying access into it has no upper bound to exceed (mirrors how 447 * unix_address.name[] is accessed via sun_path[i]). 448 * 449 * A custom (program) BTF struct can only be reached as a PTR_TO_BTF_ID through 450 * bpf_obj_new(), which does not allow flexible array members. So use a kernel 451 * type via an untrusted PTR_TO_BTF_ID (bpf_core_cast()). struct vring_used ends 452 * with a flexible array 'ring[]' of struct vring_used_elem { __virtio32 id, len; }, 453 * mirroring a 'struct foo { int a; int b; }' flexible array. 454 */ 455 456 /* ring[i].len, i in [0, 63], 4-byte read: the flexible array has no upper bound. */ 457 SEC("syscall") 458 __success 459 int flex_array_field(void *ctx) 460 { 461 unsigned long i = bpf_get_prandom_u32(); 462 struct vring_used *o; 463 int val = 0; 464 465 o = bpf_core_cast(bpf_get_current_task_btf(), struct vring_used); 466 asm volatile (" \ 467 r1 = %[o]; \ 468 r2 = %[i]; \ 469 r2 &= 63; \ 470 r2 *= 8; \ 471 r1 += r2; \ 472 %[val] = *(u32 *)(r1 + 8); \ 473 " : [val] "=r"(val) 474 : [o] "r"(o), [i] "r"(i) 475 : "r1", "r2"); 476 return val; 477 } 478 479 /* 480 * ring[] accessed with step 4 (half of sizeof(struct vring_used_elem)): the 481 * step is not a whole number of elements, so the flexible array stride does 482 * not match. 483 */ 484 SEC("syscall") 485 __failure 486 __msg("invalid variable offset access into struct vring_used") 487 int flex_array_unaligned_step(void *ctx) 488 { 489 unsigned long i = bpf_get_prandom_u32(); 490 struct vring_used *o; 491 int val = 0; 492 493 o = bpf_core_cast(bpf_get_current_task_btf(), struct vring_used); 494 asm volatile (" \ 495 r1 = %[o]; \ 496 r2 = %[i]; \ 497 r2 &= 7; \ 498 r2 *= 4; \ 499 r1 += r2; \ 500 %[val] = *(u32 *)(r1 + 4); \ 501 " : [val] "=r"(val) 502 : [o] "r"(o), [i] "r"(i) 503 : "r1", "r2"); 504 return val; 505 } 506 507 /* 508 * Access either name[0].sun_path[0] or one byte past sun_path. 509 * Step 108 does not match sizeof(struct sockaddr_un), which is 110. 510 * It matches the inner char array's stride, but exceeds its bounds. 511 * The flexible-tail exemption must not apply to that fixed array. 512 */ 513 SEC("syscall") 514 __failure 515 __msg("invalid variable offset access into struct unix_address") 516 int flex_array_inner_array_out_of_bounds(void *ctx) 517 { 518 unsigned long i = bpf_get_prandom_u32(); 519 struct unix_address *o; 520 int val = 0; 521 522 o = bpf_core_cast(bpf_get_current_task_btf(), struct unix_address); 523 asm volatile (" \ 524 r1 = %[o]; \ 525 r2 = %[i]; \ 526 r2 &= 1; \ 527 r2 *= %[len]; \ 528 r1 += r2; \ 529 %[val] = *(u8 *)(r1 + %[off]); \ 530 " : [val] "=r"(val) 531 : [o] "r"(o), [i] "r"(i), 532 __imm_const(len, sizeof_field(struct sockaddr_un, sun_path)), 533 __imm_const(off, offsetof(struct unix_address, name) + 534 offsetof(struct sockaddr_un, sun_path)) 535 : "r1", "r2"); 536 return val; 537 } 538 539 typedef int array_element_t; 540 typedef array_element_t array_type_t[4]; 541 542 struct typedef_array { 543 array_type_t values; 544 }; 545 546 /* 547 * Resolving values' size crosses both the array and element typedefs. 548 * Record the array ID, not the int ID left by element-type resolution. 549 * values[i], i in [0, 3], must pass the variable-offset array check. 550 */ 551 SEC("syscall") 552 __success 553 int typedef_array_in_bounds(void *ctx) 554 { 555 unsigned long i = bpf_get_prandom_u32(); 556 struct typedef_array *o; 557 int val = 0; 558 559 o = bpf_obj_new(typeof(*o)); 560 if (!o) 561 return 0; 562 asm volatile (" \ 563 r1 = %[o]; \ 564 r2 = %[i]; \ 565 r2 &= 3; \ 566 r2 *= %[elem_size]; \ 567 r1 += r2; \ 568 %[val] = *(u32 *)(r1 + 0); \ 569 " : [val] "=r"(val) 570 : [o] "r"(o), [i] "r"(i), 571 __imm_const(elem_size, sizeof(array_element_t)) 572 : "r1", "r2"); 573 bpf_obj_drop(o); 574 return val; 575 } 576 577 char _license[] SEC("license") = "GPL"; 578