mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	bpf: enforce exact retval range on subprog/callback exit
Instead of relying on potentially imprecise tnum representation of expected return value range for callbacks and subprogs, validate that smin/smax range satisfy exact expected range of return values. E.g., if callback would need to return [0, 2] range, tnum can't represent this precisely and instead will allow [0, 3] range. By checking smin/smax range, we can make sure that subprog/callback indeed returns only valid [0, 2] range. 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-5-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
		
							parent
							
								
									0acd03a5bd
								
							
						
					
					
						commit
						8fa4ecd49b
					
				
					 2 changed files with 28 additions and 12 deletions
				
			
		| 
						 | 
					@ -275,6 +275,11 @@ struct bpf_reference_state {
 | 
				
			||||||
	int callback_ref;
 | 
						int callback_ref;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct bpf_retval_range {
 | 
				
			||||||
 | 
						s32 minval;
 | 
				
			||||||
 | 
						s32 maxval;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* state of the program:
 | 
					/* state of the program:
 | 
				
			||||||
 * type of all registers and stack info
 | 
					 * type of all registers and stack info
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -297,7 +302,7 @@ struct bpf_func_state {
 | 
				
			||||||
	 * void foo(void) { bpf_timer_set_callback(,foo); }
 | 
						 * void foo(void) { bpf_timer_set_callback(,foo); }
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	u32 async_entry_cnt;
 | 
						u32 async_entry_cnt;
 | 
				
			||||||
	struct tnum callback_ret_range;
 | 
						struct bpf_retval_range callback_ret_range;
 | 
				
			||||||
	bool in_callback_fn;
 | 
						bool in_callback_fn;
 | 
				
			||||||
	bool in_async_callback_fn;
 | 
						bool in_async_callback_fn;
 | 
				
			||||||
	bool in_exception_callback_fn;
 | 
						bool in_exception_callback_fn;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2305,6 +2305,11 @@ static void init_reg_state(struct bpf_verifier_env *env,
 | 
				
			||||||
	regs[BPF_REG_FP].frameno = state->frameno;
 | 
						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)
 | 
					#define BPF_MAIN_FUNC (-1)
 | 
				
			||||||
static void init_func_state(struct bpf_verifier_env *env,
 | 
					static void init_func_state(struct bpf_verifier_env *env,
 | 
				
			||||||
			    struct bpf_func_state *state,
 | 
								    struct bpf_func_state *state,
 | 
				
			||||||
| 
						 | 
					@ -2313,7 +2318,7 @@ static void init_func_state(struct bpf_verifier_env *env,
 | 
				
			||||||
	state->callsite = callsite;
 | 
						state->callsite = callsite;
 | 
				
			||||||
	state->frameno = frameno;
 | 
						state->frameno = frameno;
 | 
				
			||||||
	state->subprogno = subprogno;
 | 
						state->subprogno = subprogno;
 | 
				
			||||||
	state->callback_ret_range = tnum_range(0, 0);
 | 
						state->callback_ret_range = retval_range(0, 0);
 | 
				
			||||||
	init_reg_state(env, state);
 | 
						init_reg_state(env, state);
 | 
				
			||||||
	mark_verifier_state_scratched(env);
 | 
						mark_verifier_state_scratched(env);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -9396,7 +9401,7 @@ static int set_map_elem_callback_state(struct bpf_verifier_env *env,
 | 
				
			||||||
		return err;
 | 
							return err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	callee->in_callback_fn = true;
 | 
						callee->in_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9418,7 +9423,7 @@ static int set_loop_callback_state(struct bpf_verifier_env *env,
 | 
				
			||||||
	__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
						__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	callee->in_callback_fn = true;
 | 
						callee->in_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9448,7 +9453,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_4]);
 | 
				
			||||||
	__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
						__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
				
			||||||
	callee->in_async_callback_fn = true;
 | 
						callee->in_async_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9476,7 +9481,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_4]);
 | 
				
			||||||
	__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
						__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
				
			||||||
	callee->in_callback_fn = true;
 | 
						callee->in_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9499,7 +9504,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,
 | 
				
			||||||
	__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
						__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	callee->in_callback_fn = true;
 | 
						callee->in_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9531,7 +9536,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_4]);
 | 
				
			||||||
	__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
						__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
 | 
				
			||||||
	callee->in_callback_fn = true;
 | 
						callee->in_callback_fn = true;
 | 
				
			||||||
	callee->callback_ret_range = tnum_range(0, 1);
 | 
						callee->callback_ret_range = retval_range(0, 1);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9560,6 +9565,11 @@ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)
 | 
				
			||||||
	return is_rbtree_lock_required_kfunc(kfunc_btf_id);
 | 
						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)
 | 
					static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct bpf_verifier_state *state = env->cur_state, *prev_st;
 | 
						struct bpf_verifier_state *state = env->cur_state, *prev_st;
 | 
				
			||||||
| 
						 | 
					@ -9583,9 +9593,6 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	caller = state->frame[state->curframe - 1];
 | 
						caller = state->frame[state->curframe - 1];
 | 
				
			||||||
	if (callee->in_callback_fn) {
 | 
						if (callee->in_callback_fn) {
 | 
				
			||||||
		/* enforce R0 return value range [0, 1]. */
 | 
					 | 
				
			||||||
		struct tnum range = callee->callback_ret_range;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (r0->type != SCALAR_VALUE) {
 | 
							if (r0->type != SCALAR_VALUE) {
 | 
				
			||||||
			verbose(env, "R0 not a scalar value\n");
 | 
								verbose(env, "R0 not a scalar value\n");
 | 
				
			||||||
			return -EACCES;
 | 
								return -EACCES;
 | 
				
			||||||
| 
						 | 
					@ -9597,7 +9604,11 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
 | 
				
			||||||
		if (err)
 | 
							if (err)
 | 
				
			||||||
			return err;
 | 
								return err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!tnum_in(range, r0->var_off)) {
 | 
							/* enforce R0 return value range */
 | 
				
			||||||
 | 
							if (!retval_range_within(callee->callback_ret_range, r0)) {
 | 
				
			||||||
 | 
								struct tnum range = tnum_range(callee->callback_ret_range.minval,
 | 
				
			||||||
 | 
											       callee->callback_ret_range.maxval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			verbose_invalid_scalar(env, r0, &range, "callback return", "R0");
 | 
								verbose_invalid_scalar(env, r0, &range, "callback return", "R0");
 | 
				
			||||||
			return -EINVAL;
 | 
								return -EINVAL;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue