From: Sasha Levin <sashal@kernel.org>
To: patches@lists.linux.dev, stable@vger.kernel.org
Cc: KaFai Wan <kafai.wan@linux.dev>,
Kaiyan Mei <M202472210@hust.edu.cn>,
Yinhao Hu <dddddd@hust.edu.cn>,
Eduard Zingerman <eddyz87@gmail.com>,
Alexei Starovoitov <ast@kernel.org>,
Sasha Levin <sashal@kernel.org>,
daniel@iogearbox.net, andrii@kernel.org, bpf@vger.kernel.org
Subject: [PATCH AUTOSEL 6.18-6.12] bpf: Skip bounds adjustment for conditional jumps on same scalar register
Date: Mon, 8 Dec 2025 19:15:06 -0500 [thread overview]
Message-ID: <20251209001610.611575-14-sashal@kernel.org> (raw)
In-Reply-To: <20251209001610.611575-1-sashal@kernel.org>
From: KaFai Wan <kafai.wan@linux.dev>
[ Upstream commit d43ad9da8052eda714caa38f243adbf32a8614cb ]
When conditional jumps are performed on the same scalar register
(e.g., r0 <= r0, r0 > r0, r0 < r0), the BPF verifier incorrectly
attempts to adjust the register's min/max bounds. This leads to
invalid range bounds and triggers a BUG warning.
The problematic BPF program:
0: call bpf_get_prandom_u32
1: w8 = 0x80000000
2: r0 &= r8
3: if r0 > r0 goto <exit>
The instruction 3 triggers kernel warning:
3: if r0 > r0 goto <exit>
true_reg1: range bounds violation u64=[0x1, 0x0] s64=[0x1, 0x0] u32=[0x1, 0x0] s32=[0x1, 0x0] var_off=(0x0, 0x0)
true_reg2: const tnum out of sync with range bounds u64=[0x0, 0xffffffffffffffff] s64=[0x8000000000000000, 0x7fffffffffffffff] var_off=(0x0, 0x0)
Comparing a register with itself should not change its bounds and
for most comparison operations, comparing a register with itself has
a known result (e.g., r0 == r0 is always true, r0 < r0 is always false).
Fix this by:
1. Enhance is_scalar_branch_taken() to properly handle branch direction
computation for same register comparisons across all BPF jump operations
2. Adds early return in reg_set_min_max() to avoid bounds adjustment
for unknown branch directions (e.g., BPF_JSET) on the same register
The fix ensures that unnecessary bounds adjustments are skipped, preventing
the verifier bug while maintaining correct branch direction analysis.
Reported-by: Kaiyan Mei <M202472210@hust.edu.cn>
Reported-by: Yinhao Hu <dddddd@hust.edu.cn>
Closes: https://lore.kernel.org/all/1881f0f5.300df.199f2576a01.Coremail.kaiyanm@hust.edu.cn/
Signed-off-by: KaFai Wan <kafai.wan@linux.dev>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20251103063108.1111764-2-kafai.wan@linux.dev
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
Now I have a complete picture. Let me do a comprehensive summary and
analysis:
## Complete Analysis
### 1. COMMIT MESSAGE ANALYSIS
**Subject:** "bpf: Skip bounds adjustment for conditional jumps on same
scalar register"
**Key indicators:**
- **Fix keyword:** The commit message clearly describes a bug fix
("incorrectly attempts", "invalid range bounds", "triggers a BUG
warning")
- **No stable tag:** The commit does not have explicit "Cc:
stable@vger.kernel.org"
- **No Fixes: tag:** No explicit Fixes: tag pointing to a prior commit
- **Reported-by tags:** The commit has two "Reported-by:" tags and a
"Closes:" link to a lore.kernel.org bug report
- **Acked-by:** Eduard Zingerman (BPF maintainer) acknowledged this fix
- **Merged by:** Alexei Starovoitov (BPF maintainer) signed off
The commit message describes:
1. When comparing a register with itself (e.g., `r0 > r0`), the verifier
incorrectly adjusts bounds
2. This leads to invalid range bounds (umin > umax, etc.)
3. Triggers the `reg_bounds_sanity_check()` BUG warning
### 2. CODE CHANGE ANALYSIS
**Files changed:** 1 file (`kernel/bpf/verifier.c`)
**Two modifications:**
**Modification 1 - `is_scalar_branch_taken()` (lines 15996-16020 in
diff):**
Adds a new code block at the beginning of the function to handle same-
register comparisons:
```c
if (reg1 == reg2) {
switch (opcode) {
case BPF_JGE:
case BPF_JLE:
case BPF_JSGE:
case BPF_JSLE:
case BPF_JEQ:
return 1; /* Always true: r0 >= r0, r0 <= r0, r0 == r0 */
case BPF_JGT:
case BPF_JLT:
case BPF_JSGT:
case BPF_JSLT:
case BPF_JNE:
return 0; /* Always false: r0 > r0, r0 < r0, r0 != r0 */
case BPF_JSET:
if (tnum_is_const(t1))
return t1.value != 0;
else
return (smin1 <= 0 && smax1 >= 0) ? -1 : 1;
default:
return -1;
}
}
```
This correctly determines branch direction for same-register
comparisons:
- `r0 == r0`, `r0 >= r0`, `r0 <= r0` are always true (return 1)
- `r0 > r0`, `r0 < r0`, `r0 != r0` are always false (return 0)
- `r0 JSET r0` depends on whether any bits are set
**Modification 2 - `reg_set_min_max()` (lines 16446-16452 in diff):**
Adds early return when both register arguments point to the same memory:
```c
/* We compute branch direction for same SCALAR_VALUE registers in
- is_scalar_branch_taken(). For unknown branch directions (e.g.,
BPF_JSET)
- on the same registers, we don't need to adjust the min/max values.
*/
if (false_reg1 == false_reg2)
return 0;
```
This prevents `regs_refine_cond_op()` from corrupting bounds when called
with the same pointer for both registers.
### 3. ROOT CAUSE OF THE BUG
When a BPF program compares a register with itself (e.g., `if r0 > r0`):
1. In `check_cond_jmp_op()`, both `dst_reg` and `src_reg` point to the
same `bpf_reg_state` in memory because `®s[insn->dst_reg] ==
®s[insn->src_reg]`
2. If `is_branch_taken()` returns -1 (unknown), `reg_set_min_max()` is
called
3. `regs_refine_cond_op()` is then called with `reg1 == reg2` (same
pointer)
4. For `BPF_JGT` (which becomes `BPF_JLT` after `flip_opcode`), the code
does:
```c
reg1->umax_value = min(reg1->umax_value, reg2->umax_value - 1);
reg2->umin_value = max(reg1->umin_value + 1, reg2->umin_value);
```
Since `reg1 == reg2`, this becomes:
- First line: `reg->umax_value = reg->umax_value - 1` (decreases max)
- Second line reads the already-decreased `umax_value`, then:
`reg->umin_value = max(reg->umin_value + 1, reg->umin_value)`
(increases min)
5. This results in `umin_value > umax_value`, which is an invalid range!
6. `reg_bounds_sanity_check()` detects this and triggers a BUG warning
### 4. CLASSIFICATION
- **Type:** Bug fix
- **Security impact:** Not a CVE, but triggers BUG (kernel
warning/crash) - denial of service by unprivileged users (if
unprivileged BPF is enabled)
- **Exception categories:** None (this is a straightforward bug fix, not
a device ID, quirk, DT update, or build fix)
### 5. SCOPE AND RISK ASSESSMENT
- **Lines changed:** ~30 new lines of code
- **Files touched:** 1 file (`kernel/bpf/verifier.c`)
- **Complexity:** Low - adds early return checks for pointer equality
- **Subsystem:** BPF verifier (core BPF infrastructure)
- **Risk of regression:** Low - the changes are defensive checks that
prevent invalid states
**Why low risk:**
1. The `reg1 == reg2` check is a simple pointer comparison
2. The logic for determining branch direction when comparing a register
with itself is mathematically correct
3. The early return in `reg_set_min_max()` prevents unnecessary
processing, not actual verification
### 6. USER IMPACT
**Who is affected:**
- Any system running BPF programs that compare a register with itself
- The triggering program is simple and can be crafted by any user with
BPF access
- Systems with unprivileged BPF enabled are at higher risk (denial of
service)
**Severity:**
- Triggers kernel BUG warning (can cause system instability)
- `reg_bounds_sanity_check()` calls `verifier_bug()` which prints
warnings and may affect system stability
- The verifier marks the register as unbounded after the bug, which
could potentially lead to incorrect verification
**Bug trigger:**
The commit message shows a simple 4-instruction BPF program that
triggers the bug:
```
0: call bpf_get_prandom_u32
1: w8 = 0x80000000
2: r0 &= r8
3: if r0 > r0 goto <exit>
```
### 7. STABILITY INDICATORS
- **Tested-by:** No explicit tested-by, but tested as part of the bug
report
- **Reviewed/Acked-by:** Eduard Zingerman (BPF maintainer)
- **Signed-off-by:** Alexei Starovoitov (BPF co-maintainer)
- **Time in mainline:** This is a relatively new commit
### 8. DEPENDENCY CHECK
**Dependencies:**
- The fix depends on `reg_bounds_sanity_check()` which was added in v6.8
(commit 5f99f312bd3be)
- Without the sanity check, the bug would silently corrupt bounds
(worse!)
- The underlying bug in `is_scalar_branch_taken()` and
`reg_set_min_max()` has existed since v5.0
**Affected versions:**
- The visible bug (BUG warning) affects kernels ≥ v6.8 (when sanity
check was added)
- The silent bounds corruption affects all kernels since v5.0
### 9. BACKPORT CONSIDERATIONS
**Should be backported to:** v6.8, v6.6 (LTS), and later stable trees
**Reasons:**
1. Fixes a real, reproducible bug that triggers kernel warnings
2. Small, surgical fix with clear scope
3. Low regression risk
4. Affects core BPF infrastructure used widely
5. Easy to trigger with a simple BPF program
6. Has maintainer acknowledgment
**Backport notes:**
- The patch should apply cleanly to v6.8+ where
`reg_bounds_sanity_check()` exists
- For v6.6 LTS, need to verify if the sanity check was backported
- For older stable trees (v5.15, v5.10, etc.), the sanity check doesn't
exist, so the bug manifests as silent bounds corruption which is
arguably worse but harder to detect
### CONCLUSION
This commit fixes a real BPF verifier bug that:
1. Is easily reproducible with a simple BPF program
2. Triggers kernel BUG warnings (denial of service potential)
3. Has a small, well-contained fix
4. Has been acknowledged by BPF maintainers
5. Affects commonly-used infrastructure (BPF verifier)
The fix is obviously correct - comparing a register with itself should
have deterministic results (e.g., r0 == r0 is always true), and
adjusting bounds based on such comparisons is logically wrong.
**YES**
kernel/bpf/verifier.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fbe4bb91c564a..515e4d1807c92 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -15950,6 +15950,30 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta
s64 smin2 = is_jmp32 ? (s64)reg2->s32_min_value : reg2->smin_value;
s64 smax2 = is_jmp32 ? (s64)reg2->s32_max_value : reg2->smax_value;
+ if (reg1 == reg2) {
+ switch (opcode) {
+ case BPF_JGE:
+ case BPF_JLE:
+ case BPF_JSGE:
+ case BPF_JSLE:
+ case BPF_JEQ:
+ return 1;
+ case BPF_JGT:
+ case BPF_JLT:
+ case BPF_JSGT:
+ case BPF_JSLT:
+ case BPF_JNE:
+ return 0;
+ case BPF_JSET:
+ if (tnum_is_const(t1))
+ return t1.value != 0;
+ else
+ return (smin1 <= 0 && smax1 >= 0) ? -1 : 1;
+ default:
+ return -1;
+ }
+ }
+
switch (opcode) {
case BPF_JEQ:
/* constants, umin/umax and smin/smax checks would be
@@ -16396,6 +16420,13 @@ static int reg_set_min_max(struct bpf_verifier_env *env,
if (false_reg1->type != SCALAR_VALUE || false_reg2->type != SCALAR_VALUE)
return 0;
+ /* We compute branch direction for same SCALAR_VALUE registers in
+ * is_scalar_branch_taken(). For unknown branch directions (e.g., BPF_JSET)
+ * on the same registers, we don't need to adjust the min/max values.
+ */
+ if (false_reg1 == false_reg2)
+ return 0;
+
/* fallthrough (FALSE) branch */
regs_refine_cond_op(false_reg1, false_reg2, rev_opcode(opcode), is_jmp32);
reg_bounds_sync(false_reg1);
--
2.51.0
next parent reply other threads:[~2025-12-09 0:17 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
[not found] <20251209001610.611575-1-sashal@kernel.org>
2025-12-09 0:15 ` Sasha Levin [this message]
2025-12-09 0:15 ` [PATCH AUTOSEL 6.18-6.12] cxgb4: Rename sched_class to avoid type clash Sasha Levin
2025-12-09 0:15 ` [PATCH AUTOSEL 6.18-6.12] bpf: Disable file_alloc_security hook Sasha Levin
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=20251209001610.611575-14-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=M202472210@hust.edu.cn \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=dddddd@hust.edu.cn \
--cc=eddyz87@gmail.com \
--cc=kafai.wan@linux.dev \
--cc=patches@lists.linux.dev \
--cc=stable@vger.kernel.org \
/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