Commit 99523094 authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'stricter register ID checking in regsafe()'

Eduard Zingerman says:

====================

This patch-set consists of a series of bug fixes for register ID
tracking in verifier.c:states_equal()/regsafe() functions:
 - for registers of type PTR_TO_MAP_{KEY,VALUE}, PTR_TO_PACKET[_META]
   the regsafe() should call check_ids() even if registers are
   byte-to-byte equal;
 - states_equal() must maintain idmap that covers all function frames
   in the state because functions like mark_ptr_or_null_regs() operate
   on all registers in the state;
 - regsafe() must compare spin lock ids for PTR_TO_MAP_VALUE registers.

The last point covers issue reported by Kumar Kartikeya Dwivedi in [1],
I borrowed the test commit from there.
Note, that there is also an issue with register id tracking for
scalars described here [2], it would be addressed separately.

[1] https://lore.kernel.org/bpf/20221111202719.982118-1-memxor@gmail.com/
[2] https://lore.kernel.org/bpf/20221128163442.280187-2-eddyz87@gmail.com/



Eduard Zingerman (6):
  bpf: regsafe() must not skip check_ids()
  selftests/bpf: test cases for regsafe() bug skipping check_id()
  bpf: states_equal() must build idmap for all function frames
  selftests/bpf: verify states_equal() maintains idmap across all frames
  bpf: use check_ids() for active_lock comparison
  selftests/bpf: test case for relaxed prunning of active_lock.id
====================

Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents f3212ad5 efd6286f
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -273,9 +273,9 @@ struct bpf_id_pair {
	u32 cur;
};

/* Maximum number of register states that can exist at once */
#define BPF_ID_MAP_SIZE (MAX_BPF_REG + MAX_BPF_STACK / BPF_REG_SIZE)
#define MAX_CALL_FRAMES 8
/* Maximum number of register states that can exist at once */
#define BPF_ID_MAP_SIZE ((MAX_BPF_REG + MAX_BPF_STACK / BPF_REG_SIZE) * MAX_CALL_FRAMES)
struct bpf_verifier_state {
	/* call stack tracking */
	struct bpf_func_state *frame[MAX_CALL_FRAMES];
+23 −25
Original line number Diff line number Diff line
@@ -13064,15 +13064,6 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,

	equal = memcmp(rold, rcur, offsetof(struct bpf_reg_state, parent)) == 0;

	if (rold->type == PTR_TO_STACK)
		/* two stack pointers are equal only if they're pointing to
		 * the same stack frame, since fp-8 in foo != fp-8 in bar
		 */
		return equal && rold->frameno == rcur->frameno;

	if (equal)
		return true;

	if (rold->type == NOT_INIT)
		/* explored state can't have used this */
		return true;
@@ -13080,6 +13071,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
		return false;
	switch (base_type(rold->type)) {
	case SCALAR_VALUE:
		if (equal)
			return true;
		if (env->explore_alu_limits)
			return false;
		if (rcur->type == SCALAR_VALUE) {
@@ -13126,7 +13119,8 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
		 */
		return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
		       range_within(rold, rcur) &&
		       tnum_in(rold->var_off, rcur->var_off);
		       tnum_in(rold->var_off, rcur->var_off) &&
		       check_ids(rold->id, rcur->id, idmap);
	case PTR_TO_PACKET_META:
	case PTR_TO_PACKET:
		if (rcur->type != rold->type)
@@ -13150,20 +13144,14 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
		/* new val must satisfy old val knowledge */
		return range_within(rold, rcur) &&
		       tnum_in(rold->var_off, rcur->var_off);
	case PTR_TO_CTX:
	case CONST_PTR_TO_MAP:
	case PTR_TO_PACKET_END:
	case PTR_TO_FLOW_KEYS:
	case PTR_TO_SOCKET:
	case PTR_TO_SOCK_COMMON:
	case PTR_TO_TCP_SOCK:
	case PTR_TO_XDP_SOCK:
		/* Only valid matches are exact, which memcmp() above
		 * would have accepted
	case PTR_TO_STACK:
		/* two stack pointers are equal only if they're pointing to
		 * the same stack frame, since fp-8 in foo != fp-8 in bar
		 */
		return equal && rold->frameno == rcur->frameno;
	default:
		/* Don't know what's going on, just say it's not safe */
		return false;
		/* Only valid matches are exact, which memcmp() */
		return equal;
	}

	/* Shouldn't get here; if we do, say it's not safe */
@@ -13273,7 +13261,6 @@ static bool func_states_equal(struct bpf_verifier_env *env, struct bpf_func_stat
{
	int i;

	memset(env->idmap_scratch, 0, sizeof(env->idmap_scratch));
	for (i = 0; i < MAX_BPF_REG; i++)
		if (!regsafe(env, &old->regs[i], &cur->regs[i],
			     env->idmap_scratch))
@@ -13297,14 +13284,25 @@ static bool states_equal(struct bpf_verifier_env *env,
	if (old->curframe != cur->curframe)
		return false;

	memset(env->idmap_scratch, 0, sizeof(env->idmap_scratch));

	/* Verification state from speculative execution simulation
	 * must never prune a non-speculative execution one.
	 */
	if (old->speculative && !cur->speculative)
		return false;

	if (old->active_lock.ptr != cur->active_lock.ptr ||
	    old->active_lock.id != cur->active_lock.id)
	if (old->active_lock.ptr != cur->active_lock.ptr)
		return false;

	/* Old and cur active_lock's have to be either both present
	 * or both absent.
	 */
	if (!!old->active_lock.id != !!cur->active_lock.id)
		return false;

	if (old->active_lock.id &&
	    !check_ids(old->active_lock.id, cur->active_lock.id, env->idmap_scratch))
		return false;

	if (old->active_rcu_lock != cur->active_rcu_lock)
+82 −0
Original line number Diff line number Diff line
@@ -2305,3 +2305,85 @@
	.errstr = "!read_ok",
	.result = REJECT,
},
/* Make sure that verifier.c:states_equal() considers IDs from all
 * frames when building 'idmap' for check_ids().
 */
{
	"calls: check_ids() across call boundary",
	.insns = {
	/* Function main() */
	BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0),
	/* fp[-24] = map_lookup_elem(...) ; get a MAP_VALUE_PTR_OR_NULL with some ID */
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
	BPF_LD_MAP_FD(BPF_REG_1,
		      0),
	BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
	BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_0, -24),
	/* fp[-32] = map_lookup_elem(...) ; get a MAP_VALUE_PTR_OR_NULL with some ID */
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8),
	BPF_LD_MAP_FD(BPF_REG_1,
		      0),
	BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
	BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_0, -32),
	/* call foo(&fp[-24], &fp[-32])   ; both arguments have IDs in the current
	 *                                ; stack frame
	 */
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_FP),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -24),
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_FP),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -32),
	BPF_CALL_REL(2),
	/* exit 0 */
	BPF_MOV64_IMM(BPF_REG_0, 0),
	BPF_EXIT_INSN(),
	/* Function foo()
	 *
	 * r9 = &frame[0].fp[-24]  ; save arguments in the callee saved registers,
	 * r8 = &frame[0].fp[-32]  ; arguments are pointers to pointers to map value
	 */
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
	BPF_MOV64_REG(BPF_REG_8, BPF_REG_2),
	/* r7 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
	/* r6 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_6, BPF_REG_0),
	/* if r6 > r7 goto +1      ; no new information about the state is derived from
	 *                         ; this check, thus produced verifier states differ
	 *                         ; only in 'insn_idx'
	 * r9 = r8
	 */
	BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 1),
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_8),
	/* r9 = *r9                ; verifier get's to this point via two paths:
	 *                         ; (I) one including r9 = r8, verified first;
	 *                         ; (II) one excluding r9 = r8, verified next.
	 *                         ; After load of *r9 to r9 the frame[0].fp[-24].id == r9.id.
	 *                         ; Suppose that checkpoint is created here via path (I).
	 *                         ; When verifying via (II) the r9.id must be compared against
	 *                         ; frame[0].fp[-24].id, otherwise (I) and (II) would be
	 *                         ; incorrectly deemed equivalent.
	 * if r9 == 0 goto <exit>
	 */
	BPF_LDX_MEM(BPF_DW, BPF_REG_9, BPF_REG_9, 0),
	BPF_JMP_IMM(BPF_JEQ, BPF_REG_9, 0, 1),
	/* r8 = *r8                ; read map value via r8, this is not safe
	 * r0 = *r8                ; because r8 might be not equal to r9.
	 */
	BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_8, 0),
	BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0),
	/* exit 0 */
	BPF_MOV64_IMM(BPF_REG_0, 0),
	BPF_EXIT_INSN(),
	},
	.flags = BPF_F_TEST_STATE_FREQ,
	.fixup_map_hash_8b = { 3, 9 },
	.result = REJECT,
	.errstr = "R8 invalid mem access 'map_value_or_null'",
	.result_unpriv = REJECT,
	.errstr_unpriv = "",
	.prog_type = BPF_PROG_TYPE_CGROUP_SKB,
},
+54 −0
Original line number Diff line number Diff line
@@ -654,3 +654,57 @@
	.result = ACCEPT,
	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
	"direct packet access: test30 (check_id() in regsafe(), bad access)",
	.insns = {
	/* r9 = ctx */
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
	/* r7 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
	/* r6 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_6, BPF_REG_0),
	/* r2 = ctx->data
	 * r3 = ctx->data
	 * r4 = ctx->data_end
	 */
	BPF_LDX_MEM(BPF_W, BPF_REG_2, BPF_REG_9, offsetof(struct __sk_buff, data)),
	BPF_LDX_MEM(BPF_W, BPF_REG_3, BPF_REG_9, offsetof(struct __sk_buff, data)),
	BPF_LDX_MEM(BPF_W, BPF_REG_4, BPF_REG_9, offsetof(struct __sk_buff, data_end)),
	/* if r6 > 100 goto exit
	 * if r7 > 100 goto exit
	 */
	BPF_JMP_IMM(BPF_JGT, BPF_REG_6, 100, 9),
	BPF_JMP_IMM(BPF_JGT, BPF_REG_7, 100, 8),
	/* r2 += r6              ; this forces assignment of ID to r2
	 * r2 += 1               ; get some fixed off for r2
	 * r3 += r7              ; this forces assignment of ID to r3
	 * r3 += 1               ; get some fixed off for r3
	 */
	BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_6),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, 1),
	BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_7),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, 1),
	/* if r6 > r7 goto +1    ; no new information about the state is derived from
	 *                       ; this check, thus produced verifier states differ
	 *                       ; only in 'insn_idx'
	 * r2 = r3               ; optionally share ID between r2 and r3
	 */
	BPF_JMP_REG(BPF_JNE, BPF_REG_6, BPF_REG_7, 1),
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_3),
	/* if r3 > ctx->data_end goto exit */
	BPF_JMP_REG(BPF_JGT, BPF_REG_3, BPF_REG_4, 1),
	/* r5 = *(u8 *) (r2 - 1) ; access packet memory using r2,
	 *                       ; this is not always safe
	 */
	BPF_LDX_MEM(BPF_B, BPF_REG_5, BPF_REG_2, -1),
	/* exit(0) */
	BPF_MOV64_IMM(BPF_REG_0, 0),
	BPF_EXIT_INSN(),
	},
	.flags = BPF_F_TEST_STATE_FREQ,
	.result = REJECT,
	.errstr = "invalid access to packet, off=0 size=1, R2",
	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
+114 −0
Original line number Diff line number Diff line
@@ -331,3 +331,117 @@
	.errstr = "inside bpf_spin_lock",
	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},
{
	"spin_lock: regsafe compare reg->id for map value",
	.insns = {
	BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
	BPF_LDX_MEM(BPF_W, BPF_REG_6, BPF_REG_6, offsetof(struct __sk_buff, mark)),
	BPF_LD_MAP_FD(BPF_REG_1, 0),
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_1),
	BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
	BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
	BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
	BPF_EXIT_INSN(),
	BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
	BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
	BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
	BPF_EXIT_INSN(),
	BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_7),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 4),
	BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_spin_lock),
	BPF_JMP_IMM(BPF_JEQ, BPF_REG_6, 0, 1),
	BPF_JMP_IMM(BPF_JA, 0, 0, 1),
	BPF_MOV64_REG(BPF_REG_7, BPF_REG_8),
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_7),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 4),
	BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_spin_unlock),
	BPF_MOV64_IMM(BPF_REG_0, 0),
	BPF_EXIT_INSN(),
	},
	.fixup_map_spin_lock = { 2 },
	.result = REJECT,
	.errstr = "bpf_spin_unlock of different lock",
	.prog_type = BPF_PROG_TYPE_SCHED_CLS,
	.flags = BPF_F_TEST_STATE_FREQ,
},
/* Make sure that regsafe() compares ids for spin lock records using
 * check_ids():
 *  1: r9 = map_lookup_elem(...)  ; r9.id == 1
 *  2: r8 = map_lookup_elem(...)  ; r8.id == 2
 *  3: r7 = ktime_get_ns()
 *  4: r6 = ktime_get_ns()
 *  5: if r6 > r7 goto <9>
 *  6: spin_lock(r8)
 *  7: r9 = r8
 *  8: goto <10>
 *  9: spin_lock(r9)
 * 10: spin_unlock(r9)             ; r9.id == 1 || r9.id == 2 and lock is active,
 *                                 ; second visit to (10) should be considered safe
 *                                 ; if check_ids() is used.
 * 11: exit(0)
 */
{
	"spin_lock: regsafe() check_ids() similar id mappings",
	.insns = {
	BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
	/* r9 = map_lookup_elem(...) */
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
	BPF_LD_MAP_FD(BPF_REG_1,
		      0),
	BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
	BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 24),
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_0),
	/* r8 = map_lookup_elem(...) */
	BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
	BPF_LD_MAP_FD(BPF_REG_1,
		      0),
	BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem),
	BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 18),
	BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
	/* r7 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
	/* r6 = ktime_get_ns() */
	BPF_EMIT_CALL(BPF_FUNC_ktime_get_ns),
	BPF_MOV64_REG(BPF_REG_6, BPF_REG_0),
	/* if r6 > r7 goto +5      ; no new information about the state is derived from
	 *                         ; this check, thus produced verifier states differ
	 *                         ; only in 'insn_idx'
	 * spin_lock(r8)
	 * r9 = r8
	 * goto unlock
	 */
	BPF_JMP_REG(BPF_JGT, BPF_REG_6, BPF_REG_7, 5),
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_8),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 4),
	BPF_EMIT_CALL(BPF_FUNC_spin_lock),
	BPF_MOV64_REG(BPF_REG_9, BPF_REG_8),
	BPF_JMP_A(3),
	/* spin_lock(r9) */
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 4),
	BPF_EMIT_CALL(BPF_FUNC_spin_lock),
	/* spin_unlock(r9) */
	BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
	BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 4),
	BPF_EMIT_CALL(BPF_FUNC_spin_unlock),
	/* exit(0) */
	BPF_MOV64_IMM(BPF_REG_0, 0),
	BPF_EXIT_INSN(),
	},
	.fixup_map_spin_lock = { 3, 10 },
	.result = VERBOSE_ACCEPT,
	.errstr = "28: safe",
	.result_unpriv = REJECT,
	.errstr_unpriv = "",
	.prog_type = BPF_PROG_TYPE_CGROUP_SKB,
	.flags = BPF_F_TEST_STATE_FREQ,
},
Loading