mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-30 16:07:39 +00:00
bpf: Verifier track null pointer branch_taken with JNE and JEQ
Currently, when considering the branches that may be taken for a jump instruction if the register being compared is a pointer the verifier assumes both branches may be taken. But, if the jump instruction is comparing if a pointer is NULL we have this information in the verifier encoded in the reg->type so we can do better in these cases. Specifically, these two common cases can be handled. * If the instruction is BPF_JEQ and we are comparing against a zero value. This test is 'if ptr == 0 goto +X' then using the type information in reg->type we can decide if the ptr is not null. This allows us to avoid pushing both branches onto the stack and instead only use the != 0 case. For example PTR_TO_SOCK and PTR_TO_SOCK_OR_NULL encode the null pointer. Note if the type is PTR_TO_SOCK_OR_NULL we can not learn anything. And also if the value is non-zero we learn nothing because it could be any arbitrary value a different pointer for example * If the instruction is BPF_JNE and ware comparing against a zero value then a similar analysis as above can be done. The test in asm looks like 'if ptr != 0 goto +X'. Again using the type information if the non null type is set (from above PTR_TO_SOCK) we know the jump is taken. In this patch we extend is_branch_taken() to consider this extra information and to return only the branch that will be taken. This resolves a verifier issue reported with C code like the following. See progs/test_sk_lookup_kern.c in selftests. sk = bpf_sk_lookup_tcp(skb, tuple, tuple_len, BPF_F_CURRENT_NETNS, 0); bpf_printk("sk=%d\n", sk ? 1 : 0); if (sk) bpf_sk_release(sk); return sk ? TC_ACT_OK : TC_ACT_UNSPEC; In the above the bpf_printk() will resolve the pointer from PTR_TO_SOCK_OR_NULL to PTR_TO_SOCK. Then the second test guarding the release will cause the verifier to walk both paths resulting in the an unreleased sock reference. See verifier/ref_tracking.c in selftests for an assembly version of the above. After the above additional logic is added the C code above passes as expected. Reported-by: Andrey Ignatov <rdna@fb.com> Suggested-by: Alexei Starovoitov <ast@kernel.org> Signed-off-by: John Fastabend <john.fastabend@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://lore.kernel.org/bpf/159009164651.6313.380418298578070501.stgit@john-Precision-5820-Tower
This commit is contained in:
parent
79917b242c
commit
cac616db39
1 changed files with 33 additions and 3 deletions
|
@ -393,6 +393,15 @@ static bool type_is_sk_pointer(enum bpf_reg_type type)
|
|||
type == PTR_TO_XDP_SOCK;
|
||||
}
|
||||
|
||||
static bool reg_type_not_null(enum bpf_reg_type type)
|
||||
{
|
||||
return type == PTR_TO_SOCKET ||
|
||||
type == PTR_TO_TCP_SOCK ||
|
||||
type == PTR_TO_MAP_VALUE ||
|
||||
type == PTR_TO_SOCK_COMMON ||
|
||||
type == PTR_TO_BTF_ID;
|
||||
}
|
||||
|
||||
static bool reg_type_may_be_null(enum bpf_reg_type type)
|
||||
{
|
||||
return type == PTR_TO_MAP_VALUE_OR_NULL ||
|
||||
|
@ -6308,8 +6317,25 @@ static int is_branch64_taken(struct bpf_reg_state *reg, u64 val, u8 opcode)
|
|||
static int is_branch_taken(struct bpf_reg_state *reg, u64 val, u8 opcode,
|
||||
bool is_jmp32)
|
||||
{
|
||||
if (__is_pointer_value(false, reg))
|
||||
return -1;
|
||||
if (__is_pointer_value(false, reg)) {
|
||||
if (!reg_type_not_null(reg->type))
|
||||
return -1;
|
||||
|
||||
/* If pointer is valid tests against zero will fail so we can
|
||||
* use this to direct branch taken.
|
||||
*/
|
||||
if (val != 0)
|
||||
return -1;
|
||||
|
||||
switch (opcode) {
|
||||
case BPF_JEQ:
|
||||
return 0;
|
||||
case BPF_JNE:
|
||||
return 1;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_jmp32)
|
||||
return is_branch32_taken(reg, val, opcode);
|
||||
|
@ -6808,7 +6834,11 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
|
|||
}
|
||||
|
||||
if (pred >= 0) {
|
||||
err = mark_chain_precision(env, insn->dst_reg);
|
||||
/* If we get here with a dst_reg pointer type it is because
|
||||
* above is_branch_taken() special cased the 0 comparison.
|
||||
*/
|
||||
if (!__is_pointer_value(false, dst_reg))
|
||||
err = mark_chain_precision(env, insn->dst_reg);
|
||||
if (BPF_SRC(insn->code) == BPF_X && !err)
|
||||
err = mark_chain_precision(env, insn->src_reg);
|
||||
if (err)
|
||||
|
|
Loading…
Reference in a new issue