Re: [PATCH bpf-next 08/10] bpf: support precision propagation in the presence of subprogs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 25, 2023 at 04:49:09PM -0700, Andrii Nakryiko wrote:
>  	if (insn->code == 0)
>  		return 0;
> @@ -3424,14 +3449,72 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx,
>  		if (class == BPF_STX)
>  			bt_set_reg(bt, sreg);
>  	} else if (class == BPF_JMP || class == BPF_JMP32) {
> -		if (opcode == BPF_CALL) {
> -			if (insn->src_reg == BPF_PSEUDO_CALL)
> -				return -ENOTSUPP;
> -			/* BPF helpers that invoke callback subprogs are
> -			 * equivalent to BPF_PSEUDO_CALL above
> +		if (bpf_pseudo_call(insn)) {
> +			int subprog_insn_idx, subprog;
> +			bool is_global;
> +
> +			subprog_insn_idx = idx + insn->imm + 1;
> +			subprog = find_subprog(env, subprog_insn_idx);
> +			if (subprog < 0)
> +				return -EFAULT;
> +			is_global = subprog_is_global(env, subprog);
> +
> +			if (is_global) {

could you add a warn_on here that checks that jmp history doesn't have insns from subprog.

> +				/* r1-r5 are invalidated after subprog call,
> +				 * so for global func call it shouldn't be set
> +				 * anymore
> +				 */
> +				if (bt_reg_mask(bt) & BPF_REGMASK_ARGS)
> +					return -EFAULT;

This shouldn't be happening, but backtracking is delicate.
Could you add verbose("backtracking bug") here, so we know why the prog got rejected.
I'd probably do -ENOTSUPP, but EFAULT is ok too.

> +				/* global subprog always sets R0 */
> +				bt_clear_reg(bt, BPF_REG_0);
> +				return 0;
> +			} else {
> +				/* static subprog call instruction, which
> +				 * means that we are exiting current subprog,
> +				 * so only r1-r5 could be still requested as
> +				 * precise, r0 and r6-r10 or any stack slot in
> +				 * the current frame should be zero by now
> +				 */
> +				if (bt_reg_mask(bt) & ~BPF_REGMASK_ARGS)
> +					return -EFAULT;

same here.

> +				/* we don't track register spills perfectly,
> +				 * so fallback to force-precise instead of failing */
> +				if (bt_stack_mask(bt) != 0)
> +					return -ENOTSUPP;
> +				/* propagate r1-r5 to the caller */
> +				for (i = BPF_REG_1; i <= BPF_REG_5; i++) {
> +					if (bt_is_reg_set(bt, i)) {
> +						bt_clear_reg(bt, i);
> +						bt_set_frame_reg(bt, bt->frame - 1, i);
> +					}
> +				}
> +				if (bt_subprog_exit(bt))
> +					return -EFAULT;
> +				return 0;
> +			}
> +		} else if ((bpf_helper_call(insn) &&
> +			    is_callback_calling_function(insn->imm) &&
> +			    !is_async_callback_calling_function(insn->imm)) ||
> +			   (bpf_pseudo_kfunc_call(insn) && is_callback_calling_kfunc(insn->imm))) {
> +			/* callback-calling helper or kfunc call, which means
> +			 * we are exiting from subprog, but unlike the subprog
> +			 * call handling above, we shouldn't propagate
> +			 * precision of r1-r5 (if any requested), as they are
> +			 * not actually arguments passed directly to callback
> +			 * subprogs
>  			 */
> -			if (insn->src_reg == 0 && is_callback_calling_function(insn->imm))
> +			if (bt_reg_mask(bt) & ~BPF_REGMASK_ARGS)
> +				return -EFAULT;
> +			if (bt_stack_mask(bt) != 0)
>  				return -ENOTSUPP;
> +			/* clear r1-r5 in callback subprog's mask */
> +			for (i = BPF_REG_1; i <= BPF_REG_5; i++)
> +				bt_clear_reg(bt, i);
> +			if (bt_subprog_exit(bt))
> +				return -EFAULT;
> +			return 0;

jmp history will include callback insn, right?
So skip of jmp history is missing here ?

> +		} else if (opcode == BPF_CALL) {
>  			/* kfunc with imm==0 is invalid and fixup_kfunc_call will
>  			 * catch this error later. Make backtracking conservative
>  			 * with ENOTSUPP.
> @@ -3449,7 +3532,39 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx,
>  				return -EFAULT;
>  			}
>  		} else if (opcode == BPF_EXIT) {
> -			return -ENOTSUPP;
> +			bool r0_precise;
> +
> +			if (bt_reg_mask(bt) & BPF_REGMASK_ARGS) {
> +				/* if backtracing was looking for registers R1-R5
> +				 * they should have been found already.
> +				 */
> +				verbose(env, "BUG regs %x\n", bt_reg_mask(bt));
> +				WARN_ONCE(1, "verifier backtracking bug");
> +				return -EFAULT;
> +			}
> +
> +			/* BPF_EXIT in subprog or callback always jump right

I'd say 'subprog always returns right after the call'. 'jump' is a bit confusing here,
since it doesn't normally used to describe function return address.

> +			 * after the call instruction, so by check whether the
> +			 * instruction at subseq_idx-1 is subprog call or not we
> +			 * can distinguish actual exit from *subprog* from
> +			 * exit from *callback*. In the former case, we need
> +			 * to propagate r0 precision, if necessary. In the
> +			 * former we never do that.
> +			 */
> +			r0_precise = subseq_idx - 1 >= 0 &&
> +				     bpf_pseudo_call(&env->prog->insnsi[subseq_idx - 1]) &&
> +				     bt_is_reg_set(bt, BPF_REG_0);
> +

The rest all makes sense.




[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux