Thread (30 messages) 30 messages, 6 authors, 2025-05-19

Re: [PATCH bpf-next v3 10/11] bpf: Allow nospec-protected var-offset stack access

From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Date: 2025-05-02 00:03:50
Also in: bpf, linux-arm-kernel, linux-kselftest, lkml

On Thu, 1 May 2025 at 10:17, Luis Gerhorst [off-list ref] wrote:
quoted hunk ↗ jump to hunk
Insert a nospec before the access to prevent it from ever using an index
that is subject to speculative scalar-confusion.

The access itself can either happen directly in the BPF program (reads
only, check_stack_read_var_off()) or in a helper (read/write,
check_helper_mem_access()).

This relies on the fact that the speculative scalar confusion that leads
to the variable-stack access going OOBs must stem from a prior
speculative store or branch bypass. Adding a nospec before the
variable-stack access will force all previously bypassed stores/branches
to complete and cause the stack access to only ever go to the stack slot
that is accessed architecturally.

Alternatively, the variable-offset stack access might be a write that
can itself be subject to speculative store bypass (this can happen in
theory even if this code adds a nospec /before/ the variable-offset
write). Only indirect writes by helpers might be affected here (e.g.,
those taking ARG_PTR_TO_MAP_VALUE). (Because check_stack_write_var_off()
does not use check_stack_range_initialized(), in-program variable-offset
writes are not affected.) If the in-helper write can be subject to
Spectre v4 and the helper writes/overwrites pointers on the BPF stack,
they are already a problem for fixed-offset stack accesses and should be
subject to Spectre v4 sanitization.

Signed-off-by: Luis Gerhorst <redacted>
Acked-by: Henriette Herzog <redacted>
Cc: Maximilian Ott <redacted>
Cc: Milan Stephan <redacted>
---
 kernel/bpf/verifier.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index db26b477dd45..1fbafea3ed69 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -7894,6 +7894,11 @@ static int check_atomic(struct bpf_verifier_env *env, struct bpf_insn *insn)
        }
 }

+static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env)
+{
+       return &env->insn_aux_data[env->insn_idx];
+}
+
 /* When register 'regno' is used to read the stack (either directly or through
  * a helper function) make sure that it's within stack boundary and, depending
  * on the access type and privileges, that all elements of the stack are
@@ -7933,18 +7938,18 @@ static int check_stack_range_initialized(
        if (tnum_is_const(reg->var_off)) {
                min_off = max_off = reg->var_off.value + off;
        } else {
-               /* Variable offset is prohibited for unprivileged mode for
+               /* Variable offset requires a nospec for unprivileged mode for
                 * simplicity since it requires corresponding support in
                 * Spectre masking for stack ALU.
                 * See also retrieve_ptr_limit().
                 */
                if (!env->bypass_spec_v1) {
-                       char tn_buf[48];
-
-                       tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
-                       verbose(env, "R%d variable offset stack access prohibited for !root, var_off=%s\n",
-                               regno, tn_buf);
-                       return -EACCES;
+                       /* Allow the access, but prevent it from using a
+                        * speculative offset using a nospec before the
+                        * dereference op.
+                        */
+                       cur_aux(env)->nospec = true;
+                       WARN_ON_ONCE(cur_aux(env)->alu_state);
                }
                /* Only initialized buffer on stack is allowed to be accessed
                 * with variable offset. With uninitialized buffer it's hard to
@@ -11172,11 +11177,6 @@ static int check_get_func_ip(struct bpf_verifier_env *env)
        return -ENOTSUPP;
 }
Hmm, while reading related code, I noticed that sanitize_check_bounds
returns 0 in case the type is not map_value or stack.
It seems like it should be returning an error, cannot check right now
but I'm pretty sure these are not the two pointer types unprivileged
programs can access?
So smells like a bug?
-static struct bpf_insn_aux_data *cur_aux(const struct bpf_verifier_env *env)
-{
-       return &env->insn_aux_data[env->insn_idx];
-}
-
 static bool loop_flag_is_zero(struct bpf_verifier_env *env)
 {
        struct bpf_reg_state *regs = cur_regs(env);
--
2.49.0
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help