Skip to content

Commit

Permalink
Merge branch 'bpf-verifier-retval-logic-fixes'
Browse files Browse the repository at this point in the history
Andrii Nakryiko says:

====================
BPF verifier retval logic fixes

This patch set fixes BPF verifier logic around validating and enforcing return
values for BPF programs that have specific range of expected return values.
Both sync and async callbacks have similar logic and are fixes as well.
A few tests are added that would fail without the fixes in this patch set.

Also, while at it, we update retval checking logic to use smin/smax range
instead of tnum, avoiding future potential issues if expected range cannot be
represented precisely by tnum (e.g., [0, 2] is not representable by tnum and
is treated as [0, 3]).

There is a little bit of refactoring to unify async callback and program exit
logic to avoid duplication of checks as much as possible.

v4->v5:
  - fix timer_bad_ret test on no-alu32 flavor (CI);
v3->v4:
  - add back bpf_func_state rearrangement patch;
  - simplified patch #4 as suggested (Shung-Hsi);
v2->v3:
  - more carefullly switch from umin/umax to smin/smax;
v1->v2:
  - drop tnum from retval checks (Eduard);
  - use smin/smax instead of umin/umax (Alexei).
====================

Link: https://lore.kernel.org/r/[email protected]
Signed-off-by: Alexei Starovoitov <[email protected]>
  • Loading branch information
Alexei Starovoitov committed Dec 2, 2023
2 parents 6685aad + 81eff2e commit 9067970
Show file tree
Hide file tree
Showing 15 changed files with 212 additions and 80 deletions.
9 changes: 7 additions & 2 deletions include/linux/bpf_verifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,11 @@ struct bpf_reference_state {
int callback_ref;
};

struct bpf_retval_range {
s32 minval;
s32 maxval;
};

/* state of the program:
* type of all registers and stack info
*/
Expand All @@ -297,8 +302,8 @@ struct bpf_func_state {
* void foo(void) { bpf_timer_set_callback(,foo); }
*/
u32 async_entry_cnt;
struct bpf_retval_range callback_ret_range;
bool in_callback_fn;
struct tnum callback_ret_range;
bool in_async_callback_fn;
bool in_exception_callback_fn;
/* For callback calling functions that limit number of possible
Expand All @@ -316,8 +321,8 @@ struct bpf_func_state {
/* The following fields should be last. See copy_func_state() */
int acquired_refs;
struct bpf_reference_state *refs;
int allocated_stack;
struct bpf_stack_state *stack;
int allocated_stack;
};

struct bpf_idx_pair {
Expand Down
13 changes: 13 additions & 0 deletions kernel/bpf/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,19 @@ static void verbose_snum(struct bpf_verifier_env *env, s64 num)
verbose(env, "%#llx", num);
}

int tnum_strn(char *str, size_t size, struct tnum a)
{
/* print as a constant, if tnum is fully known */
if (a.mask == 0) {
if (is_unum_decimal(a.value))
return snprintf(str, size, "%llu", a.value);
else
return snprintf(str, size, "%#llx", a.value);
}
return snprintf(str, size, "(%#llx; %#llx)", a.value, a.mask);
}
EXPORT_SYMBOL_GPL(tnum_strn);

static void print_scalar_ranges(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg,
const char **sep)
Expand Down
6 changes: 0 additions & 6 deletions kernel/bpf/tnum.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,6 @@ bool tnum_in(struct tnum a, struct tnum b)
return a.value == b.value;
}

int tnum_strn(char *str, size_t size, struct tnum a)
{
return snprintf(str, size, "(%#llx; %#llx)", a.value, a.mask);
}
EXPORT_SYMBOL_GPL(tnum_strn);

int tnum_sbin(char *str, size_t size, struct tnum a)
{
size_t n;
Expand Down
120 changes: 69 additions & 51 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,20 +362,23 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)

static void verbose_invalid_scalar(struct bpf_verifier_env *env,
struct bpf_reg_state *reg,
struct tnum *range, const char *ctx,
struct bpf_retval_range range, const char *ctx,
const char *reg_name)
{
char tn_buf[48];
bool unknown = true;

verbose(env, "At %s the register %s ", ctx, reg_name);
if (!tnum_is_unknown(reg->var_off)) {
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
verbose(env, "has value %s", tn_buf);
} else {
verbose(env, "has unknown scalar value");
verbose(env, "%s the register %s has", ctx, reg_name);
if (reg->smin_value > S64_MIN) {
verbose(env, " smin=%lld", reg->smin_value);
unknown = false;
}
tnum_strn(tn_buf, sizeof(tn_buf), *range);
verbose(env, " should have been in %s\n", tn_buf);
if (reg->smax_value < S64_MAX) {
verbose(env, " smax=%lld", reg->smax_value);
unknown = false;
}
if (unknown)
verbose(env, " unknown scalar value");
verbose(env, " should have been in [%d, %d]\n", range.minval, range.maxval);
}

static bool type_may_be_null(u32 type)
Expand Down Expand Up @@ -2305,6 +2308,11 @@ static void init_reg_state(struct bpf_verifier_env *env,
regs[BPF_REG_FP].frameno = state->frameno;
}

static struct bpf_retval_range retval_range(s32 minval, s32 maxval)
{
return (struct bpf_retval_range){ minval, maxval };
}

#define BPF_MAIN_FUNC (-1)
static void init_func_state(struct bpf_verifier_env *env,
struct bpf_func_state *state,
Expand All @@ -2313,7 +2321,7 @@ static void init_func_state(struct bpf_verifier_env *env,
state->callsite = callsite;
state->frameno = frameno;
state->subprogno = subprogno;
state->callback_ret_range = tnum_range(0, 0);
state->callback_ret_range = retval_range(0, 0);
init_reg_state(env, state);
mark_verifier_state_scratched(env);
}
Expand Down Expand Up @@ -9396,7 +9404,7 @@ static int set_map_elem_callback_state(struct bpf_verifier_env *env,
return err;

callee->in_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand All @@ -9418,7 +9426,7 @@ static int set_loop_callback_state(struct bpf_verifier_env *env,
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);

callee->in_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand Down Expand Up @@ -9448,7 +9456,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
callee->in_async_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand Down Expand Up @@ -9476,7 +9484,7 @@ static int set_find_vma_callback_state(struct bpf_verifier_env *env,
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
callee->in_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand All @@ -9499,7 +9507,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);

callee->in_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand Down Expand Up @@ -9531,7 +9539,7 @@ static int set_rbtree_add_callback_state(struct bpf_verifier_env *env,
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
callee->in_callback_fn = true;
callee->callback_ret_range = tnum_range(0, 1);
callee->callback_ret_range = retval_range(0, 1);
return 0;
}

Expand Down Expand Up @@ -9560,6 +9568,11 @@ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)
return is_rbtree_lock_required_kfunc(kfunc_btf_id);
}

static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg)
{
return range.minval <= reg->smin_value && reg->smax_value <= range.maxval;
}

static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
{
struct bpf_verifier_state *state = env->cur_state, *prev_st;
Expand All @@ -9583,15 +9596,21 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)

caller = state->frame[state->curframe - 1];
if (callee->in_callback_fn) {
/* enforce R0 return value range [0, 1]. */
struct tnum range = callee->callback_ret_range;

if (r0->type != SCALAR_VALUE) {
verbose(env, "R0 not a scalar value\n");
return -EACCES;
}
if (!tnum_in(range, r0->var_off)) {
verbose_invalid_scalar(env, r0, &range, "callback return", "R0");

/* we are going to rely on register's precise value */
err = mark_reg_read(env, r0, r0->parent, REG_LIVE_READ64);
err = err ?: mark_chain_precision(env, BPF_REG_0);
if (err)
return err;

/* enforce R0 return value range */
if (!retval_range_within(callee->callback_ret_range, r0)) {
verbose_invalid_scalar(env, r0, callee->callback_ret_range,
"At callback return", "R0");
return -EINVAL;
}
if (!calls_callback(env, callee->callsite)) {
Expand Down Expand Up @@ -11805,7 +11824,7 @@ static int fetch_kfunc_meta(struct bpf_verifier_env *env,
return 0;
}

static int check_return_code(struct bpf_verifier_env *env, int regno);
static int check_return_code(struct bpf_verifier_env *env, int regno, const char *reg_name);

static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
int *insn_idx_p)
Expand Down Expand Up @@ -11942,7 +11961,7 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
* to bpf_throw becomes the return value of the program.
*/
if (!env->exception_callback_subprog) {
err = check_return_code(env, BPF_REG_1);
err = check_return_code(env, BPF_REG_1, "R1");
if (err < 0)
return err;
}
Expand Down Expand Up @@ -14972,12 +14991,13 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
return 0;
}

static int check_return_code(struct bpf_verifier_env *env, int regno)
static int check_return_code(struct bpf_verifier_env *env, int regno, const char *reg_name)
{
const char *exit_ctx = "At program exit";
struct tnum enforce_attach_type_range = tnum_unknown;
const struct bpf_prog *prog = env->prog;
struct bpf_reg_state *reg;
struct tnum range = tnum_range(0, 1), const_0 = tnum_const(0);
struct bpf_retval_range range = retval_range(0, 1);
enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
int err;
struct bpf_func_state *frame = env->cur_state->frame[0];
Expand Down Expand Up @@ -15019,17 +15039,9 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)

if (frame->in_async_callback_fn) {
/* enforce return zero from async callbacks like timer */
if (reg->type != SCALAR_VALUE) {
verbose(env, "In async callback the register R%d is not a known value (%s)\n",
regno, reg_type_str(env, reg->type));
return -EINVAL;
}

if (!tnum_in(const_0, reg->var_off)) {
verbose_invalid_scalar(env, reg, &const_0, "async callback", "R0");
return -EINVAL;
}
return 0;
exit_ctx = "At async callback return";
range = retval_range(0, 0);
goto enforce_retval;
}

if (is_subprog && !frame->in_exception_callback_fn) {
Expand All @@ -15052,14 +15064,14 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)
env->prog->expected_attach_type == BPF_CGROUP_INET4_GETSOCKNAME ||
env->prog->expected_attach_type == BPF_CGROUP_INET6_GETSOCKNAME ||
env->prog->expected_attach_type == BPF_CGROUP_UNIX_GETSOCKNAME)
range = tnum_range(1, 1);
range = retval_range(1, 1);
if (env->prog->expected_attach_type == BPF_CGROUP_INET4_BIND ||
env->prog->expected_attach_type == BPF_CGROUP_INET6_BIND)
range = tnum_range(0, 3);
range = retval_range(0, 3);
break;
case BPF_PROG_TYPE_CGROUP_SKB:
if (env->prog->expected_attach_type == BPF_CGROUP_INET_EGRESS) {
range = tnum_range(0, 3);
range = retval_range(0, 3);
enforce_attach_type_range = tnum_range(2, 3);
}
break;
Expand All @@ -15072,13 +15084,13 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)
case BPF_PROG_TYPE_RAW_TRACEPOINT:
if (!env->prog->aux->attach_btf_id)
return 0;
range = tnum_const(0);
range = retval_range(0, 0);
break;
case BPF_PROG_TYPE_TRACING:
switch (env->prog->expected_attach_type) {
case BPF_TRACE_FENTRY:
case BPF_TRACE_FEXIT:
range = tnum_const(0);
range = retval_range(0, 0);
break;
case BPF_TRACE_RAW_TP:
case BPF_MODIFY_RETURN:
Expand All @@ -15090,7 +15102,7 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)
}
break;
case BPF_PROG_TYPE_SK_LOOKUP:
range = tnum_range(SK_DROP, SK_PASS);
range = retval_range(SK_DROP, SK_PASS);
break;

case BPF_PROG_TYPE_LSM:
Expand All @@ -15104,12 +15116,12 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)
/* Make sure programs that attach to void
* hooks don't try to modify return value.
*/
range = tnum_range(1, 1);
range = retval_range(1, 1);
}
break;

case BPF_PROG_TYPE_NETFILTER:
range = tnum_range(NF_DROP, NF_ACCEPT);
range = retval_range(NF_DROP, NF_ACCEPT);
break;
case BPF_PROG_TYPE_EXT:
/* freplace program can return anything as its return value
Expand All @@ -15119,15 +15131,21 @@ static int check_return_code(struct bpf_verifier_env *env, int regno)
return 0;
}

enforce_retval:
if (reg->type != SCALAR_VALUE) {
verbose(env, "At program exit the register R%d is not a known value (%s)\n",
regno, reg_type_str(env, reg->type));
verbose(env, "%s the register R%d is not a known value (%s)\n",
exit_ctx, regno, reg_type_str(env, reg->type));
return -EINVAL;
}

if (!tnum_in(range, reg->var_off)) {
verbose_invalid_scalar(env, reg, &range, "program exit", "R0");
if (prog->expected_attach_type == BPF_LSM_CGROUP &&
err = mark_chain_precision(env, regno);
if (err)
return err;

if (!retval_range_within(range, reg)) {
verbose_invalid_scalar(env, reg, range, exit_ctx, reg_name);
if (!is_subprog &&
prog->expected_attach_type == BPF_LSM_CGROUP &&
prog_type == BPF_PROG_TYPE_LSM &&
!prog->aux->attach_func_proto->type)
verbose(env, "Note, BPF_LSM_CGROUP that attach to void LSM hooks can't modify return value!\n");
Expand Down Expand Up @@ -17410,7 +17428,7 @@ static int do_check(struct bpf_verifier_env *env)
continue;
}

err = check_return_code(env, BPF_REG_0);
err = check_return_code(env, BPF_REG_0, "R0");
if (err)
return err;
process_bpf_exit:
Expand Down
2 changes: 1 addition & 1 deletion tools/testing/selftests/bpf/progs/exceptions_assert.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ int check_assert_generic(struct __sk_buff *ctx)
}

SEC("?fentry/bpf_check")
__failure __msg("At program exit the register R0 has value (0x40; 0x0)")
__failure __msg("At program exit the register R1 has smin=64 smax=64")
int check_assert_with_return(void *ctx)
{
bpf_assert_with(!ctx, 64);
Expand Down
2 changes: 1 addition & 1 deletion tools/testing/selftests/bpf/progs/exceptions_fail.c
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ int reject_set_exception_cb_bad_ret1(void *ctx)
}

SEC("?fentry/bpf_check")
__failure __msg("At program exit the register R0 has value (0x40; 0x0) should")
__failure __msg("At program exit the register R1 has smin=64 smax=64 should")
int reject_set_exception_cb_bad_ret2(void *ctx)
{
bpf_throw(64);
Expand Down
Loading

0 comments on commit 9067970

Please sign in to comment.