Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: enforce precise retval range on program exit

Similarly to subprog/callback logic, enforce return value of BPF program
using more precise smin/smax range.

We need to adjust a bunch of tests due to a changed format of an error
message.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20231202175705.885270-7-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Andrii Nakryiko and committed by
Alexei Starovoitov
c871d0e0 60a6b2c7

+40 -38
+29 -27
kernel/bpf/verifier.c
··· 362 362 363 363 static void verbose_invalid_scalar(struct bpf_verifier_env *env, 364 364 struct bpf_reg_state *reg, 365 - struct tnum *range, const char *ctx, 365 + struct bpf_retval_range range, const char *ctx, 366 366 const char *reg_name) 367 367 { 368 - char tn_buf[48]; 368 + bool unknown = true; 369 369 370 - verbose(env, "At %s the register %s ", ctx, reg_name); 371 - if (!tnum_is_unknown(reg->var_off)) { 372 - tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off); 373 - verbose(env, "has value %s", tn_buf); 374 - } else { 375 - verbose(env, "has unknown scalar value"); 370 + verbose(env, "At %s the register %s has", ctx, reg_name); 371 + if (reg->smin_value > S64_MIN) { 372 + verbose(env, " smin=%lld", reg->smin_value); 373 + unknown = false; 376 374 } 377 - tnum_strn(tn_buf, sizeof(tn_buf), *range); 378 - verbose(env, " should have been in %s\n", tn_buf); 375 + if (reg->smax_value < S64_MAX) { 376 + verbose(env, " smax=%lld", reg->smax_value); 377 + unknown = false; 378 + } 379 + if (unknown) 380 + verbose(env, " unknown scalar value"); 381 + verbose(env, " should have been in [%d, %d]\n", range.minval, range.maxval); 379 382 } 380 383 381 384 static bool type_may_be_null(u32 type) ··· 9609 9606 9610 9607 /* enforce R0 return value range */ 9611 9608 if (!retval_range_within(callee->callback_ret_range, r0)) { 9612 - struct tnum range = tnum_range(callee->callback_ret_range.minval, 9613 - callee->callback_ret_range.maxval); 9614 - 9615 - verbose_invalid_scalar(env, r0, &range, "callback return", "R0"); 9609 + verbose_invalid_scalar(env, r0, callee->callback_ret_range, 9610 + "callback return", "R0"); 9616 9611 return -EINVAL; 9617 9612 } 9618 9613 if (!calls_callback(env, callee->callsite)) { ··· 14996 14995 struct tnum enforce_attach_type_range = tnum_unknown; 14997 14996 const struct bpf_prog *prog = env->prog; 14998 14997 struct bpf_reg_state *reg; 14999 - struct tnum range = tnum_range(0, 1), const_0 = tnum_const(0); 14998 + struct bpf_retval_range range = retval_range(0, 1); 14999 + struct bpf_retval_range const_0 = retval_range(0, 0); 15000 15000 enum bpf_prog_type prog_type = resolve_prog_type(env->prog); 15001 15001 int err; 15002 15002 struct bpf_func_state *frame = env->cur_state->frame[0]; ··· 15045 15043 return -EINVAL; 15046 15044 } 15047 15045 15048 - if (!tnum_in(const_0, reg->var_off)) { 15049 - verbose_invalid_scalar(env, reg, &const_0, "async callback", reg_name); 15046 + if (!retval_range_within(const_0, reg)) { 15047 + verbose_invalid_scalar(env, reg, const_0, "async callback", reg_name); 15050 15048 return -EINVAL; 15051 15049 } 15052 15050 return 0; ··· 15072 15070 env->prog->expected_attach_type == BPF_CGROUP_INET4_GETSOCKNAME || 15073 15071 env->prog->expected_attach_type == BPF_CGROUP_INET6_GETSOCKNAME || 15074 15072 env->prog->expected_attach_type == BPF_CGROUP_UNIX_GETSOCKNAME) 15075 - range = tnum_range(1, 1); 15073 + range = retval_range(1, 1); 15076 15074 if (env->prog->expected_attach_type == BPF_CGROUP_INET4_BIND || 15077 15075 env->prog->expected_attach_type == BPF_CGROUP_INET6_BIND) 15078 - range = tnum_range(0, 3); 15076 + range = retval_range(0, 3); 15079 15077 break; 15080 15078 case BPF_PROG_TYPE_CGROUP_SKB: 15081 15079 if (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS) { 15082 - range = tnum_range(0, 3); 15080 + range = retval_range(0, 3); 15083 15081 enforce_attach_type_range = tnum_range(2, 3); 15084 15082 } 15085 15083 break; ··· 15092 15090 case BPF_PROG_TYPE_RAW_TRACEPOINT: 15093 15091 if (!env->prog->aux->attach_btf_id) 15094 15092 return 0; 15095 - range = tnum_const(0); 15093 + range = retval_range(0, 0); 15096 15094 break; 15097 15095 case BPF_PROG_TYPE_TRACING: 15098 15096 switch (env->prog->expected_attach_type) { 15099 15097 case BPF_TRACE_FENTRY: 15100 15098 case BPF_TRACE_FEXIT: 15101 - range = tnum_const(0); 15099 + range = retval_range(0, 0); 15102 15100 break; 15103 15101 case BPF_TRACE_RAW_TP: 15104 15102 case BPF_MODIFY_RETURN: ··· 15110 15108 } 15111 15109 break; 15112 15110 case BPF_PROG_TYPE_SK_LOOKUP: 15113 - range = tnum_range(SK_DROP, SK_PASS); 15111 + range = retval_range(SK_DROP, SK_PASS); 15114 15112 break; 15115 15113 15116 15114 case BPF_PROG_TYPE_LSM: ··· 15124 15122 /* Make sure programs that attach to void 15125 15123 * hooks don't try to modify return value. 15126 15124 */ 15127 - range = tnum_range(1, 1); 15125 + range = retval_range(1, 1); 15128 15126 } 15129 15127 break; 15130 15128 15131 15129 case BPF_PROG_TYPE_NETFILTER: 15132 - range = tnum_range(NF_DROP, NF_ACCEPT); 15130 + range = retval_range(NF_DROP, NF_ACCEPT); 15133 15131 break; 15134 15132 case BPF_PROG_TYPE_EXT: 15135 15133 /* freplace program can return anything as its return value ··· 15145 15143 return -EINVAL; 15146 15144 } 15147 15145 15148 - if (!tnum_in(range, reg->var_off)) { 15149 - verbose_invalid_scalar(env, reg, &range, "program exit", reg_name); 15146 + if (!retval_range_within(range, reg)) { 15147 + verbose_invalid_scalar(env, reg, range, "program exit", reg_name); 15150 15148 if (prog->expected_attach_type == BPF_LSM_CGROUP && 15151 15149 prog_type == BPF_PROG_TYPE_LSM && 15152 15150 !prog->aux->attach_func_proto->type)
+1 -1
tools/testing/selftests/bpf/progs/exceptions_assert.c
··· 125 125 } 126 126 127 127 SEC("?fentry/bpf_check") 128 - __failure __msg("At program exit the register R1 has value (0x40; 0x0)") 128 + __failure __msg("At program exit the register R1 has smin=64 smax=64") 129 129 int check_assert_with_return(void *ctx) 130 130 { 131 131 bpf_assert_with(!ctx, 64);
+1 -1
tools/testing/selftests/bpf/progs/exceptions_fail.c
··· 308 308 } 309 309 310 310 SEC("?fentry/bpf_check") 311 - __failure __msg("At program exit the register R1 has value (0x40; 0x0) should") 311 + __failure __msg("At program exit the register R1 has smin=64 smax=64 should") 312 312 int reject_set_exception_cb_bad_ret2(void *ctx) 313 313 { 314 314 bpf_throw(64);
+1 -1
tools/testing/selftests/bpf/progs/test_global_func15.c
··· 13 13 } 14 14 15 15 SEC("cgroup_skb/ingress") 16 - __failure __msg("At program exit the register R0 has value") 16 + __failure __msg("At program exit the register R0 has ") 17 17 int global_func15(struct __sk_buff *skb) 18 18 { 19 19 unsigned int v = 1;
+1 -1
tools/testing/selftests/bpf/progs/timer_failure.c
··· 30 30 } 31 31 32 32 SEC("fentry/bpf_fentry_test1") 33 - __failure __msg("should have been in (0x0; 0x0)") 33 + __failure __msg("should have been in [0, 0]") 34 34 int BPF_PROG2(test_ret_1, int, a) 35 35 { 36 36 int key = 0;
+1 -1
tools/testing/selftests/bpf/progs/user_ringbuf_fail.c
··· 184 184 * not be able to write to that pointer. 185 185 */ 186 186 SEC("?raw_tp") 187 - __failure __msg("At callback return the register R0 has value") 187 + __failure __msg("At callback return the register R0 has ") 188 188 int user_ringbuf_callback_invalid_return(void *ctx) 189 189 { 190 190 bpf_user_ringbuf_drain(&user_ringbuf, invalid_drain_callback_return, NULL, 0);
+4 -4
tools/testing/selftests/bpf/progs/verifier_cgroup_inv_retcode.c
··· 7 7 8 8 SEC("cgroup/sock") 9 9 __description("bpf_exit with invalid return code. test1") 10 - __failure __msg("R0 has value (0x0; 0xffffffff)") 10 + __failure __msg("smin=0 smax=4294967295 should have been in [0, 1]") 11 11 __naked void with_invalid_return_code_test1(void) 12 12 { 13 13 asm volatile (" \ ··· 30 30 31 31 SEC("cgroup/sock") 32 32 __description("bpf_exit with invalid return code. test3") 33 - __failure __msg("R0 has value (0x0; 0x3)") 33 + __failure __msg("smin=0 smax=3 should have been in [0, 1]") 34 34 __naked void with_invalid_return_code_test3(void) 35 35 { 36 36 asm volatile (" \ ··· 53 53 54 54 SEC("cgroup/sock") 55 55 __description("bpf_exit with invalid return code. test5") 56 - __failure __msg("R0 has value (0x2; 0x0)") 56 + __failure __msg("smin=2 smax=2 should have been in [0, 1]") 57 57 __naked void with_invalid_return_code_test5(void) 58 58 { 59 59 asm volatile (" \ ··· 75 75 76 76 SEC("cgroup/sock") 77 77 __description("bpf_exit with invalid return code. test7") 78 - __failure __msg("R0 has unknown scalar value") 78 + __failure __msg("R0 has unknown scalar value should have been in [0, 1]") 79 79 __naked void with_invalid_return_code_test7(void) 80 80 { 81 81 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/progs/verifier_netfilter_retcode.c
··· 39 39 40 40 SEC("netfilter") 41 41 __description("bpf_exit with invalid return code. test4") 42 - __failure __msg("R0 has value (0x2; 0x0)") 42 + __failure __msg("R0 has smin=2 smax=2 should have been in [0, 1]") 43 43 __naked void with_invalid_return_code_test4(void) 44 44 { 45 45 asm volatile (" \
+1 -1
tools/testing/selftests/bpf/progs/verifier_subprog_precision.c
··· 148 148 __msg("from 10 to 12: frame1: R0=scalar(smin=umin=1001") 149 149 /* check that branch code path marks r0 as precise, before failing */ 150 150 __msg("mark_precise: frame1: regs=r0 stack= before 9: (85) call bpf_get_prandom_u32#7") 151 - __msg("At callback return the register R0 has value (0x0; 0x7fffffffffffffff) should have been in (0x0; 0x1)") 151 + __msg("At callback return the register R0 has smin=1001 should have been in [0, 1]") 152 152 __naked int callback_precise_return_fail(void) 153 153 { 154 154 asm volatile (