• Eduard Zingerman's avatar
    bpf: Fix to preserve reg parent/live fields when copying range info · 71f656a5
    Eduard Zingerman authored
    Register range information is copied in several places. The intent is
    to transfer range/id information from one register/stack spill to
    another. Currently this is done using direct register assignment, e.g.:
    
    static void find_equal_scalars(..., struct bpf_reg_state *known_reg)
    {
    	...
    	struct bpf_reg_state *reg;
    	...
    			*reg = *known_reg;
    	...
    }
    
    However, such assignments also copy the following bpf_reg_state fields:
    
    struct bpf_reg_state {
    	...
    	struct bpf_reg_state *parent;
    	...
    	enum bpf_reg_liveness live;
    	...
    };
    
    Copying of these fields is accidental and incorrect, as could be
    demonstrated by the following example:
    
         0: call ktime_get_ns()
         1: r6 = r0
         2: call ktime_get_ns()
         3: r7 = r0
         4: if r0 > r6 goto +1             ; r0 & r6 are unbound thus generated
                                           ; branch states are identical
         5: *(u64 *)(r10 - 8) = 0xdeadbeef ; 64-bit write to fp[-8]
        --- checkpoint ---
         6: r1 = 42                        ; r1 marked as written
         7: *(u8 *)(r10 - 8) = r1          ; 8-bit write, fp[-8] parent & live
                                           ; overwritten
         8: r2 = *(u64 *)(r10 - 8)
         9: r0 = 0
        10: exit
    
    This example is unsafe because 64-bit write to fp[-8] at (5) is
    conditional, thus not all bytes of fp[-8] are guaranteed to be set
    when it is read at (8). However, currently the example passes
    verification.
    
    First, the execution path 1-10 is examined by verifier.
    Suppose that a new checkpoint is created by is_state_visited() at (6).
    After checkpoint creation:
    - r1.parent points to checkpoint.r1,
    - fp[-8].parent points to checkpoint.fp[-8].
    At (6) the r1.live is set to REG_LIVE_WRITTEN.
    At (7) the fp[-8].parent is set to r1.parent and fp[-8].live is set to
    REG_LIVE_WRITTEN, because of the following code called in
    check_stack_write_fixed_off():
    
    static void save_register_state(struct bpf_func_state *state,
    				int spi, struct bpf_reg_state *reg,
    				int size)
    {
    	...
    	state->stack[spi].spilled_ptr = *reg;  // <--- parent & live copied
    	if (size == BPF_REG_SIZE)
    		state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN;
    	...
    }
    
    Note the intent to mark stack spill as written only if 8 bytes are
    spilled to a slot, however this intent is spoiled by a 'live' field copy.
    At (8) the checkpoint.fp[-8] should be marked as REG_LIVE_READ but
    this does not happen:
    - fp[-8] in a current state is already marked as REG_LIVE_WRITTEN;
    - fp[-8].parent points to checkpoint.r1, parentage chain is used by
      mark_reg_read() to mark checkpoint states.
    At (10) the verification is finished for path 1-10 and jump 4-6 is
    examined. The checkpoint.fp[-8] never gets REG_LIVE_READ mark and this
    spill is pruned from the cached states by clean_live_states(). Hence
    verifier state obtained via path 1-4,6 is deemed identical to one
    obtained via path 1-6 and program marked as safe.
    
    Note: the example should be executed with BPF_F_TEST_STATE_FREQ flag
    set to force creation of intermediate verifier states.
    
    This commit revisits the locations where bpf_reg_state instances are
    copied and replaces the direct copies with a call to a function
    copy_register_state(dst, src) that preserves 'parent' and 'live'
    fields of the 'dst'.
    
    Fixes: 679c782d ("bpf/verifier: per-register parent pointers")
    Signed-off-by: default avatarEduard Zingerman <eddyz87@gmail.com>
    Link: https://lore.kernel.org/r/20230106142214.1040390-2-eddyz87@gmail.comSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
    71f656a5
verifier.c 495 KB