From: Eduard Zingerman <eddyz87@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org
Cc: andrii@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev,
kernel-team@fb.com, yhs@fb.com,
Eduard Zingerman <eddyz87@gmail.com>
Subject: [PATCH bpf-next v1 1/2] bpf: verify scalar ids mapping in regsafe() using check_ids()
Date: Fri, 26 May 2023 21:41:25 +0300 [thread overview]
Message-ID: <20230526184126.3104040-2-eddyz87@gmail.com> (raw)
In-Reply-To: <20230526184126.3104040-1-eddyz87@gmail.com>
Make sure that the following unsafe example is rejected by verifier:
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 ...
--- checkpoint ---
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 updates regsafe() to call check_ids() for scalar registers.
The change in check_alu_op() to avoid assigning scalar id to constants
is performance optimization. W/o it the regsafe() change becomes
costly for some programs, e.g. for
tools/testing/selftests/bpf/progs/pyperf600.c the difference is:
File Program States (A) States (B) States (DIFF)
--------------- -------- ---------- ---------- ----------------
pyperf600.bpf.o on_event 22200 37060 +14860 (+66.94%)
Where A -- this patch,
B -- this patch but w/o check_alu_op() changes.
Fixes: 75748837b7e5 ("bpf: Propagate scalar ranges through register assignments.")
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
kernel/bpf/verifier.c | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index af70dad655ab..624556eda430 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -12806,10 +12806,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;
copy_register_state(dst_reg, src_reg);
@@ -15151,6 +15153,33 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
switch (base_type(rold->type)) {
case SCALAR_VALUE:
+ /* Why check_ids() for precise registers?
+ *
+ * Consider the following BPF code:
+ * 1: r6 = ... unbound scalar, ID=a ...
+ * 2: r7 = ... unbound scalar, ID=b ...
+ * 3: if (r6 > r7) goto +1
+ * 4: r6 = r7
+ * 5: if (r6 > X) goto ...
+ * 6: ... memory operation using r7 ...
+ *
+ * First verification path is [1-6]:
+ * - at (4) same bpf_reg_state::id (b) would be assigned to r6 and r7;
+ * - at (5) r6 would be marked <= X, find_equal_scalars() would also mark
+ * r7 <= X, because r6 and r7 share same id.
+ *
+ * Next verification path would start from (5), because of the jump at (3).
+ * The only state difference between first and second visits of (5) is
+ * bpf_reg_state::id assignments for r6 and r7: (b, b) vs (a, b).
+ * Thus, use check_ids() to distinguish these states.
+ *
+ * The `rold->precise` check is a performance optimization. If `rold->id`
+ * was ever used to access memory / predict jump, the `rold` or any
+ * register used in `rold = r?` / `r? = rold` operations would be marked
+ * as precise, otherwise it's ID is not really interesting.
+ */
+ if (rold->precise && rold->id && !check_ids(rold->id, rcur->id, idmap))
+ return false;
if (regs_exact(rold, rcur, idmap))
return true;
if (env->explore_alu_limits)
--
2.40.1
next prev parent reply other threads:[~2023-05-26 18:42 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-05-26 18:41 [PATCH bpf-next v1 0/2] bpf: verify scalar ids mapping in regsafe() Eduard Zingerman
2023-05-26 18:41 ` Eduard Zingerman [this message]
2023-05-27 0:40 ` [PATCH bpf-next v1 1/2] bpf: verify scalar ids mapping in regsafe() using check_ids() Yonghong Song
2023-05-27 12:21 ` Eduard Zingerman
2023-05-27 12:29 ` Eduard Zingerman
2023-05-27 23:43 ` Yonghong Song
2023-05-29 0:59 ` Eduard Zingerman
2023-05-26 18:41 ` [PATCH bpf-next v1 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=20230526184126.3104040-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=martin.lau@linux.dev \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.