bpf: Allow bitwise ops, shifts and mul/div on pointers with CAP_PERFMON

Rust's core::fmt keeps a flag in the low bit of a pointer:

  r2 = *(u64 *)(r1 + 0)
  r3 = r2
  r3 &= 1
  r2 >>= 1

The verifier rejects it:

  r0 &= 8
  R0 bitwise operator &= on pointer prohibited

Negation and byte swap of a pointer already produce a number when
allow_ptr_leaks is set. Do the same for bitwise ops, shifts, *=, /= and
%=, 64-bit and 32-bit. The result is an unknown number. With CAP_PERFMON
the program can store the pointer and load it back as a number already.

+= and -= are not changed: they keep the pointer, 32-bit += is rejected.
Pointers that allow no arithmetic and pointers that may be NULL are
still rejected.

Two tests in verifier_value_illegal_alu store through the result of
&= and /=. They are rejected at the store now. Update expected messages.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20261002124714.180012-2-alexei.starovoitov@gmail.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Alexei Starovoitov 2026-10-02 12:47:00 +00:00 committed by Kumar Kartikeya Dwivedi
parent 5ec398d9ec
commit 72c0681c0e
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
2 changed files with 16 additions and 5 deletions

View File

@ -15571,8 +15571,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
u32 dst = insn->dst_reg;
const char *reason;
int ret, bounds_ret;
bool to_scalar;
dst_reg = &regs[dst];
/*
* Only += and -= keep a pointer. Any other op makes a number of it,
* which is fine when the program may see values of pointers anyway.
*/
to_scalar = opcode != BPF_ADD && opcode != BPF_SUB && env->allow_ptr_leaks;
if ((known && (smin_val != smax_val || umin_val != umax_val)) ||
smin_val > smax_val || umin_val > umax_val) {
@ -15583,7 +15589,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
return 0;
}
if (BPF_CLASS(insn->code) != BPF_ALU64) {
if (BPF_CLASS(insn->code) != BPF_ALU64 && !to_scalar) {
/* 32-bit ALU ops on pointers produce (meaningless) scalars */
if (opcode == BPF_SUB && env->allow_ptr_leaks) {
__mark_reg_unknown(env, dst_reg);
@ -15649,6 +15655,11 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env, struct bpf_insn
return -EACCES;
}
if (to_scalar) {
__mark_reg_unknown(env, dst_reg);
return 0;
}
/* For 'scalar += pointer', dst_reg inherits the complete pointer
* register state. Individual fields may be adjusted later by pointer
* arithmetic. Callers guarantee that below does not overwrite off_reg.

View File

@ -22,8 +22,8 @@ struct {
SEC("socket")
__description("map element value illegal alu op, 1")
__failure __msg("R0 bitwise operator &= on pointer")
__failure_unpriv
__failure __msg("R0 invalid mem access 'scalar'")
__failure_unpriv __msg_unpriv("R0 bitwise operator &= on pointer")
__naked void value_illegal_alu_op_1(void)
{
asm volatile (" \
@ -70,8 +70,8 @@ l0_%=: exit; \
SEC("socket")
__description("map element value illegal alu op, 3")
__failure __msg("R0 pointer arithmetic with /= operator")
__failure_unpriv
__failure __msg("R0 invalid mem access 'scalar'")
__failure_unpriv __msg_unpriv("R0 pointer arithmetic with /= operator")
__naked void value_illegal_alu_op_3(void)
{
asm volatile (" \