Skip to content

Commit 79168a6

Browse files
kkdwivediAlexei Starovoitov
authored andcommitted
bpf: Fix missing var_off check for ARG_PTR_TO_DYNPTR
Currently, the dynptr function is not checking the variable offset part of PTR_TO_STACK that it needs to check. The fixed offset is considered when computing the stack pointer index, but if the variable offset was not a constant (such that it could not be accumulated in reg->off), we will end up a discrepency where runtime pointer does not point to the actual stack slot we mark as STACK_DYNPTR. It is impossible to precisely track dynptr state when variable offset is not constant, hence, just like bpf_timer, kptr, bpf_spin_lock, etc. simply reject the case where reg->var_off is not constant. Then, consider both reg->off and reg->var_off.value when computing the stack pointer index. A new helper dynptr_get_spi is introduced to hide over these details since the dynptr needs to be located in multiple places outside the process_dynptr_func checks, hence once we know it's a PTR_TO_STACK, we need to enforce these checks in all places. Note that it is disallowed for unprivileged users to have a non-constant var_off, so this problem should only be possible to trigger from programs having CAP_PERFMON. However, its effects can vary. Without the fix, it is possible to replace the contents of the dynptr arbitrarily by making verifier mark different stack slots than actual location and then doing writes to the actual stack address of dynptr at runtime. Fixes: 97e03f5 ("bpf: Add verifier support for dynptrs") Acked-by: Joanne Koong <[email protected]> Signed-off-by: Kumar Kartikeya Dwivedi <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent d6fefa1 commit 79168a6

File tree

3 files changed

+69
-21
lines changed

3 files changed

+69
-21
lines changed

kernel/bpf/verifier.c

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -638,11 +638,34 @@ static void print_liveness(struct bpf_verifier_env *env,
638638
verbose(env, "D");
639639
}
640640

641-
static int get_spi(s32 off)
641+
static int __get_spi(s32 off)
642642
{
643643
return (-off - 1) / BPF_REG_SIZE;
644644
}
645645

646+
static int dynptr_get_spi(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
647+
{
648+
int off, spi;
649+
650+
if (!tnum_is_const(reg->var_off)) {
651+
verbose(env, "dynptr has to be at a constant offset\n");
652+
return -EINVAL;
653+
}
654+
655+
off = reg->off + reg->var_off.value;
656+
if (off % BPF_REG_SIZE) {
657+
verbose(env, "cannot pass in dynptr at an offset=%d\n", off);
658+
return -EINVAL;
659+
}
660+
661+
spi = __get_spi(off);
662+
if (spi < 1) {
663+
verbose(env, "cannot pass in dynptr at an offset=%d\n", off);
664+
return -EINVAL;
665+
}
666+
return spi;
667+
}
668+
646669
static bool is_spi_bounds_valid(struct bpf_func_state *state, int spi, int nr_slots)
647670
{
648671
int allocated_slots = state->allocated_stack / BPF_REG_SIZE;
@@ -754,7 +777,9 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_
754777
enum bpf_dynptr_type type;
755778
int spi, i, id;
756779

757-
spi = get_spi(reg->off);
780+
spi = dynptr_get_spi(env, reg);
781+
if (spi < 0)
782+
return spi;
758783

759784
if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS))
760785
return -EINVAL;
@@ -792,7 +817,9 @@ static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_re
792817
struct bpf_func_state *state = func(env, reg);
793818
int spi, i;
794819

795-
spi = get_spi(reg->off);
820+
spi = dynptr_get_spi(env, reg);
821+
if (spi < 0)
822+
return spi;
796823

797824
if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS))
798825
return -EINVAL;
@@ -844,7 +871,11 @@ static bool is_dynptr_reg_valid_uninit(struct bpf_verifier_env *env, struct bpf_
844871
if (reg->type == CONST_PTR_TO_DYNPTR)
845872
return false;
846873

847-
spi = get_spi(reg->off);
874+
spi = dynptr_get_spi(env, reg);
875+
if (spi < 0)
876+
return false;
877+
878+
/* We will do check_mem_access to check and update stack bounds later */
848879
if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS))
849880
return true;
850881

@@ -860,14 +891,15 @@ static bool is_dynptr_reg_valid_uninit(struct bpf_verifier_env *env, struct bpf_
860891
static bool is_dynptr_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
861892
{
862893
struct bpf_func_state *state = func(env, reg);
863-
int spi;
864-
int i;
894+
int spi, i;
865895

866896
/* This already represents first slot of initialized bpf_dynptr */
867897
if (reg->type == CONST_PTR_TO_DYNPTR)
868898
return true;
869899

870-
spi = get_spi(reg->off);
900+
spi = dynptr_get_spi(env, reg);
901+
if (spi < 0)
902+
return false;
871903
if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS) ||
872904
!state->stack[spi].spilled_ptr.dynptr.first_slot)
873905
return false;
@@ -896,7 +928,9 @@ static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg
896928
if (reg->type == CONST_PTR_TO_DYNPTR) {
897929
return reg->dynptr.type == dynptr_type;
898930
} else {
899-
spi = get_spi(reg->off);
931+
spi = dynptr_get_spi(env, reg);
932+
if (spi < 0)
933+
return false;
900934
return state->stack[spi].spilled_ptr.dynptr.type == dynptr_type;
901935
}
902936
}
@@ -2429,7 +2463,9 @@ static int mark_dynptr_read(struct bpf_verifier_env *env, struct bpf_reg_state *
24292463
*/
24302464
if (reg->type == CONST_PTR_TO_DYNPTR)
24312465
return 0;
2432-
spi = get_spi(reg->off);
2466+
spi = dynptr_get_spi(env, reg);
2467+
if (spi < 0)
2468+
return spi;
24332469
/* Caller ensures dynptr is valid and initialized, which means spi is in
24342470
* bounds and spi is the first dynptr slot. Simply mark stack slot as
24352471
* read.
@@ -5992,12 +6028,15 @@ int process_dynptr_func(struct bpf_verifier_env *env, int regno,
59926028
}
59936029
/* CONST_PTR_TO_DYNPTR already has fixed and var_off as 0 due to
59946030
* check_func_arg_reg_off's logic. We only need to check offset
5995-
* alignment for PTR_TO_STACK.
6031+
* and its alignment for PTR_TO_STACK.
59966032
*/
5997-
if (reg->type == PTR_TO_STACK && (reg->off % BPF_REG_SIZE)) {
5998-
verbose(env, "cannot pass in dynptr at an offset=%d\n", reg->off);
5999-
return -EINVAL;
6033+
if (reg->type == PTR_TO_STACK) {
6034+
int err = dynptr_get_spi(env, reg);
6035+
6036+
if (err < 0)
6037+
return err;
60006038
}
6039+
60016040
/* MEM_UNINIT - Points to memory that is an appropriate candidate for
60026041
* constructing a mutable bpf_dynptr object.
60036042
*
@@ -6405,15 +6444,16 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env,
64056444
}
64066445
}
64076446

6408-
static u32 dynptr_ref_obj_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
6447+
static int dynptr_ref_obj_id(struct bpf_verifier_env *env, struct bpf_reg_state *reg)
64096448
{
64106449
struct bpf_func_state *state = func(env, reg);
64116450
int spi;
64126451

64136452
if (reg->type == CONST_PTR_TO_DYNPTR)
64146453
return reg->ref_obj_id;
6415-
6416-
spi = get_spi(reg->off);
6454+
spi = dynptr_get_spi(env, reg);
6455+
if (spi < 0)
6456+
return spi;
64176457
return state->stack[spi].spilled_ptr.ref_obj_id;
64186458
}
64196459

@@ -6487,7 +6527,9 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
64876527
* PTR_TO_STACK.
64886528
*/
64896529
if (reg->type == PTR_TO_STACK) {
6490-
spi = get_spi(reg->off);
6530+
spi = dynptr_get_spi(env, reg);
6531+
if (spi < 0)
6532+
return spi;
64916533
if (!is_spi_bounds_valid(state, spi, BPF_DYNPTR_NR_SLOTS) ||
64926534
!state->stack[spi].spilled_ptr.ref_obj_id) {
64936535
verbose(env, "arg %d is an unacquired reference\n", regno);
@@ -7977,13 +8019,19 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
79778019
for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
79788020
if (arg_type_is_dynptr(fn->arg_type[i])) {
79798021
struct bpf_reg_state *reg = &regs[BPF_REG_1 + i];
8022+
int ref_obj_id;
79808023

79818024
if (meta.ref_obj_id) {
79828025
verbose(env, "verifier internal error: meta.ref_obj_id already set\n");
79838026
return -EFAULT;
79848027
}
79858028

7986-
meta.ref_obj_id = dynptr_ref_obj_id(env, reg);
8029+
ref_obj_id = dynptr_ref_obj_id(env, reg);
8030+
if (ref_obj_id < 0) {
8031+
verbose(env, "verifier internal error: failed to obtain dynptr ref_obj_id\n");
8032+
return ref_obj_id;
8033+
}
8034+
meta.ref_obj_id = ref_obj_id;
79878035
break;
79888036
}
79898037
}

tools/testing/selftests/bpf/prog_tests/kfunc_dynptr_param.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ static struct {
1818
const char *expected_verifier_err_msg;
1919
int expected_runtime_err;
2020
} kfunc_dynptr_tests[] = {
21-
{"not_valid_dynptr", "Expected an initialized dynptr as arg #1", 0},
21+
{"not_valid_dynptr", "cannot pass in dynptr at an offset=-8", 0},
2222
{"not_ptr_to_stack", "arg#0 expected pointer to stack or dynptr_ptr", 0},
2323
{"dynptr_data_null", NULL, -EBADMSG},
2424
};

tools/testing/selftests/bpf/progs/dynptr_fail.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ int invalid_helper1(void *ctx)
382382

383383
/* A dynptr can't be passed into a helper function at a non-zero offset */
384384
SEC("?raw_tp")
385-
__failure __msg("Expected an initialized dynptr as arg #3")
385+
__failure __msg("cannot pass in dynptr at an offset=-8")
386386
int invalid_helper2(void *ctx)
387387
{
388388
struct bpf_dynptr ptr;
@@ -584,7 +584,7 @@ int invalid_read4(void *ctx)
584584

585585
/* Initializing a dynptr on an offset should fail */
586586
SEC("?raw_tp")
587-
__failure __msg("invalid write to stack")
587+
__failure __msg("cannot pass in dynptr at an offset=0")
588588
int invalid_offset(void *ctx)
589589
{
590590
struct bpf_dynptr ptr;

0 commit comments

Comments
 (0)