From: Eduard Zingerman <eddyz87@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org
Cc: andrii@kernel.org, daniel@iogearbox.net, kernel-team@fb.com,
yhs@fb.com, Eduard Zingerman <eddyz87@gmail.com>
Subject: [RFC bpf-next 1/2] bpf: verify scalar ids mapping in regsafe() using check_ids()
Date: Mon, 28 Nov 2022 18:34:41 +0200 [thread overview]
Message-ID: <20221128163442.280187-2-eddyz87@gmail.com> (raw)
In-Reply-To: <20221128163442.280187-1-eddyz87@gmail.com>
Prior to this commit the following unsafe example passed verification:
1: r9 = ... some pointer with range X ...
2: r6 = ... unbound scalar ID=a ...
3: r7 = ... unbound scalar ID=b ...
4: if (r6 > r7) goto +1
5: r6 = r7
6: if (r6 > X) goto ... ; <-- suppose checkpoint state is created here
7: r9 += r7
8: *(u64 *)r9 = Y
This example is unsafe because not all execution paths verify r7 range.
Because of the jump at (4) the verifier would arrive at (6) in two states:
I. r6{.id=b}, r7{.id=b} via path 1-6;
II. r6{.id=a}, r7{.id=b} via path 1-4, 6.
Currently regsafe() does not call check_ids() for scalar registers,
thus from POV of regsafe() states (I) and (II) are identical. If the
path 1-6 is taken by verifier first and checkpoint is created at (6)
the path 1-4, 6 would be considered safe.
This commit makes the following changes:
- a call to check_ids() is added in regsafe() for scalar registers case;
- a function mark_equal_scalars_as_read() is added to ensure that
registers with identical IDs are preserved in the checkpoint states
in case when find_equal_scalars() updates register range for several
registers sharing the same ID.
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
kernel/bpf/verifier.c | 87 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 2 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6599d25dae38..8a5b7192514a 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -10638,10 +10638,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
/* case: R1 = R2
* copy register state to dest reg
*/
- if (src_reg->type == SCALAR_VALUE && !src_reg->id)
+ if (src_reg->type == SCALAR_VALUE && !src_reg->id &&
+ !tnum_is_const(src_reg->var_off))
/* Assign src and dst registers the same ID
* that will be used by find_equal_scalars()
* to propagate min/max range.
+ * Skip constants to avoid allocation of useless ID.
*/
src_reg->id = ++env->id_gen;
*dst_reg = *src_reg;
@@ -11446,16 +11448,86 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn,
return true;
}
+/* Scalar ID generation in check_alu_op() and logic of
+ * find_equal_scalars() make the following pattern possible:
+ *
+ * 1: r9 = ... some pointer with range X ...
+ * 2: r6 = ... unbound scalar ID=a ...
+ * 3: r7 = ... unbound scalar ID=b ...
+ * 4: if (r6 > r7) goto +1
+ * 5: r6 = r7
+ * 6: if (r6 > X) goto ... ; <-- suppose checkpoint state is created here
+ * 7: r9 += r7
+ * 8: *(u64 *)r9 = Y
+ *
+ * Because of the jump at (4) the verifier would arrive at (6) in two states:
+ * I. r6{.id=b}, r7{.id=b}
+ * II. r6{.id=a}, r7{.id=b}
+ *
+ * Relevant facts:
+ * - regsafe() matches ID mappings for scalars using check_ids(), this makes
+ * states (I) and (II) non-equal;
+ * - clean_func_state() removes registers not marked as REG_LIVE_READ from
+ * checkpoint states;
+ * - mark_reg_read() modifies reg->live for reg->parent (and it's parents);
+ * - when r6 = r7 is process the bpf_reg_state is copied in full, meaning
+ * that parent pointers are copied as well.
+ *
+ * Thus, for execution path 1-6:
+ * - both r6->parent and r7->parent point to the same register in the parent state (r7);
+ * - only *one* register in the checkpoint state would receive REG_LIVE_READ mark;
+ * - clean_func_state() would remove r6 from checkpoint state (mark it NOT_INIT).
+ *
+ * Consequently, when execution path 1-4, 6 reaches (6) in state (II)
+ * regsafe() won't be able to see a mismatch in ID mappings.
+ *
+ * To avoid this issue mark_equal_scalars_as_read() conservatively
+ * marks all registers with matching ID as REG_LIVE_READ, thus
+ * preserving r6 and r7 in the checkpoint state for the example above.
+ */
+static void mark_equal_scalars_as_read(struct bpf_verifier_state *vstate, int id)
+{
+ struct bpf_verifier_state *st;
+ struct bpf_func_state *state;
+ struct bpf_reg_state *reg;
+ bool move_up;
+ int i = 0;
+
+ for (st = vstate, move_up = true; st && move_up; st = st->parent) {
+ move_up = false;
+ bpf_for_each_reg_in_vstate(st, state, reg, ({
+ if (reg->type == SCALAR_VALUE && reg->id == id &&
+ !(reg->live & REG_LIVE_READ)) {
+ reg->live |= REG_LIVE_READ;
+ move_up = true;
+ }
+ ++i;
+ }));
+ }
+}
+
static void find_equal_scalars(struct bpf_verifier_state *vstate,
struct bpf_reg_state *known_reg)
{
struct bpf_func_state *state;
struct bpf_reg_state *reg;
+ int count = 0;
bpf_for_each_reg_in_vstate(vstate, state, reg, ({
- if (reg->type == SCALAR_VALUE && reg->id == known_reg->id)
+ if (reg->type == SCALAR_VALUE && reg->id == known_reg->id) {
*reg = *known_reg;
+ ++count;
+ }
}));
+
+ /* Count equal to 1 means that find_equal_scalars have not
+ * found any registers with the same ID (except self), thus
+ * the range knowledge have not been transferred and there is
+ * no need to preserve registers with the same ID in a parent
+ * state.
+ */
+ if (count > 1)
+ mark_equal_scalars_as_read(vstate->parent, known_reg->id);
}
static int check_cond_jmp_op(struct bpf_verifier_env *env,
@@ -12878,6 +12950,12 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
*/
return equal && rold->frameno == rcur->frameno;
+ /* even if two registers are identical the id mapping might diverge
+ * e.g. rold{.id=1}, rcur{.id=1}, idmap{1->2}
+ */
+ if (equal && rold->type == SCALAR_VALUE && rold->id)
+ return check_ids(rold->id, rcur->id, idmap);
+
if (equal)
return true;
@@ -12891,6 +12969,11 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
if (env->explore_alu_limits)
return false;
if (rcur->type == SCALAR_VALUE) {
+ /* id relations must be preserved, see comment in
+ * mark_equal_scalars_as_read() for SCALAR_VALUE example.
+ */
+ if (rold->id && !check_ids(rold->id, rcur->id, idmap))
+ return false;
if (!rold->precise)
return true;
/* new val must satisfy old val knowledge */
--
2.34.1
next prev parent reply other threads:[~2022-11-28 16:35 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-11-28 16:34 [RFC bpf-next 0/2] bpf: verify scalar ids mapping in regsafe() using check_ids() Eduard Zingerman
2022-11-28 16:34 ` Eduard Zingerman [this message]
2022-12-01 0:26 ` [RFC bpf-next 1/2] " Andrii Nakryiko
2022-12-01 1:14 ` Eduard Zingerman
2022-12-01 18:33 ` Eduard Zingerman
2022-12-02 22:48 ` Eduard Zingerman
2022-11-28 16:34 ` [RFC bpf-next 2/2] selftests/bpf: verify that check_ids() is used for scalars in regsafe() Eduard Zingerman
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20221128163442.280187-2-eddyz87@gmail.com \
--to=eddyz87@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=yhs@fb.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox