BPF List
 help / color / mirror / Atom feed
* [PATCH AUTOSEL 6.18-6.1] libbpf: Skip bpf_object__probe_loading() when BPF token is in use
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
@ 2026-10-19 13:45 ` Sasha Levin
  2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] libbpf: Fix ring buffer consumer loop on 32-bit position wrap Sasha Levin
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:45 UTC (permalink / raw)
  To: patches, stable
  Cc: Yuan Chen, Andrii Nakryiko, Sasha Levin, eddyz87, ast, daniel,
	memxor, bpf, linux-kernel

From: Yuan Chen <chenyuan@kylinos.cn>

[ Upstream commit d262a25d8b58b697b2a1a2fe4b0078446cc00e11 ]

bpf_object__probe_loading() tries to load trivial SOCKET_FILTER and
TRACEPOINT programs to verify the BPF environment works.  When a
BPF token is in use with restricted program type permissions, these
probe loads may fail because the token does not allow the specific
program types, even though BPF loading is perfectly functional.

Fix by skipping the probe when a token FD is present: BPF token
creation itself proves the kernel has a working BPF subsystem.
Real BPF issues will be caught during actual program and map loading.

Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20260610145059.113412-2-chenyuan_fl@163.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

This is a real libbpf load-path bug in this 6.18 tree: restricted BPF
tokens make `bpf_object__probe_loading()` fail even when the actual
programs would load. The change is small, already present in the
matching 6.18 code, and is worth backporting.

## PHASE 1: COMMIT MESSAGE FORENSICS

**Step 1.1: PARSE THE SUBJECT LINE**
- Record: `[libbpf] [Skip] Skip bpf_object__probe_loading() when BPF
  token is in use`
- Subsystem: `libbpf` (userspace library in `tools/lib/bpf/`)
- Verb “Skip” is a workaround/fix for a false-negative probe, not a
  feature.

**Step 1.2: PARSE ALL COMMIT MESSAGE TAGS**
- Record:
  - `Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>` (author)
  - `Signed-off-by: Andrii Nakryiko <andrii@kernel.org>` (libbpf/BPF
    maintainer)
  - `Link: https://lore.kernel.org/bpf/20260610145059.113412-2-
    chenyuan_fl@163.com`
  - No `Fixes:`, `Reported-by`, `Tested-by`, `Reviewed-by`, `Acked-by`,
    or `Cc: stable` in the committed message (absence of Fixes/Cc-stable
    is expected here)
  - Mailing-list review did include `Acked-by: Mykyta Yatsenko
    <yatsenko@meta.com>` (not copied into the final commit text)

**Step 1.3: ANALYZE THE COMMIT BODY TEXT**
- Record:
  - **Bug:** `bpf_object__probe_loading()` loads dummy `SOCKET_FILTER`
    and `TRACEPOINT` programs. With a BPF token whose allowed program
    types exclude those, the probe fails even though real loads would
    work.
  - **Symptom:** `bpf_object__load()` / `bpf_object__prepare()` returns
    an error and warns that BPF/RLIMIT_MEMLOCK is broken. In-tree token
    selftests (`obj_priv_map`, `obj_priv_prog`, freplace, implicit-
    token, struct_ops BTF cases) hit this.
  - **Cover letter (v3):** same probe can also drop the token path and
    then break later loads (struct_ops + global data: `fd X is not
    pointing to valid bpf_map`).
  - **Root cause:** probe uses hardcoded program types and passes
    `obj->token_fd`.
  - **Fix:** skip the probe when `token_fd` is set; creating the token
    already proves BPF syscalls work.
  - **Versions:** no version list in the message. BPF tokens exist since
    ~6.9; this 6.18 tree has them.

**Step 1.4: DETECT HIDDEN BUG FIXES**
- Record: Not a disguised cleanup. It is an explicit functional fix for
  a false probe failure. Skipping the probe is the fix, not a refactor.

## PHASE 2: DIFF ANALYSIS

**Step 2.1: INVENTORY THE CHANGES**
- Record:
  - File: `tools/lib/bpf/libbpf.c` — 3 insertions, 7 deletions
  - Function: `bpf_object__probe_loading()`
  - Scope: single-file surgical change

**Step 2.2: UNDERSTAND THE CODE FLOW CHANGE**
- Record:
  - **Before:** After `bpf_object_prepare_token()`, probe always runs
    (except `gen_loader`). It passes `token_fd` / `BPF_F_TOKEN_FD` into
    dummy `SOCKET_FILTER` then `TRACEPOINT` loads. Either failure aborts
    object prepare/load.
  - **After:** If `obj->token_fd` is set, return 0 immediately (same as
    `gen_loader`). Dummy loads no longer use token opts (`NULL` opts).
  - **Path:** initialization/prepare of every `bpf_object` load, not a
    rare error path.

**Step 2.3: IDENTIFY THE BUG MECHANISM**
- Record:
  - **Category:** logic/correctness (false failure in a capability
    probe)
  - **Mechanism:** probe assumes `SOCKET_FILTER` or `TRACEPOINT` is
    allowed. Restricted tokens are the whole point of BPF token
    delegation. Probe failure is treated as “BPF is unavailable,” so
    legitimate objects never load.
  - Not UAF/leak/race/overflow.

**Step 2.4: ASSESS THE FIX QUALITY**
- Record:
  - Obviously correct: token creation already used `bpf_token_create()`.
    Real map/prog errors still happen in the real load path.
  - Minimal: one extra skip condition; dummy loads drop unused token
    opts.
  - `bpf_prog_load(..., NULL)` is valid: `OPTS_VALID` treats NULL as OK;
    `OPTS_GET` falls back to 0. Same as empty opts when `token_fd == 0`.
  - Regression risk is low. Skipping `bump_rlimit_memlock()` in this
    function is fine: `bpf_prog_load()` still calls it, and tokens imply
    6.9+ with memcg BPF accounting.
  - Residual gap: series patch 2 (feature probes using hardcoded types)
    is **not** in this tree. This patch still stands alone for the
    probe_loading hard-fail.

## PHASE 3: GIT HISTORY INVESTIGATION

**Step 3.1: BLAME THE CHANGED LINES**
- Record:
  - Probe itself: Stanislav Fomichev, 2018 (`47eff61777c7`).
  - Token opts in the probe: `6b434b61b4d9e` (“libbpf: Wire up BPF token
    support at BPF object level”), Andrii Nakryiko, 2024-01-23 (kernel
    ~6.9).
  - Buggy pattern has been in this 6.18 line since the branch, not a
    post-6.18 introduction.

**Step 3.2: FOLLOW THE FIXES: TAG**
- Record: No `Fixes:` tag (Mykyta asked for one; it was never added).
  The introducing commit is `6b434b61b4d9e`, which **is** in this tree
  (`git merge-base --is-ancestor` succeeded).

**Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES**
- Record:
  - `f9bceaa59c5c4` already skips this probe for light skeleton
    (`gen_loader`); this patch extends that idea to tokens.
  - `1315c28ed8095` split load into `bpf_object__prepare()` / load;
    probe now runs from `bpf_object_prepare()`.
  - Series patch 2 (“fetch probeable prog type from token”) is **not**
    in this tree.
  - Standalone: Andrii said this was the only v2 patch that should land
    on its own.

**Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS**
- Record: Yuan Chen has another libbpf fix here (`aa485e8789d56`, NULL
  deref in `btf_dump__free`). Not the libbpf maintainer. Applied by
  Andrii Nakryiko.

**Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS**
- Record: Needs `obj->token_fd` and `bpf_object__probe_loading()`, both
  present. No new helpers. Applies standalone. Patch 2 is a related but
  separate feature-probe issue.

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

**Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION**
- Record:
  - `b4 dig -c d262a25d8b58`:
    https://patch.msgid.link/20260610145059.113412-2-chenyuan_fl@163.com
  - Revisions:
    - v1 (2026-05-29): selftest workaround — add
      SOCKET_FILTER/TRACEPOINT to token tests
    - v2 (2026-06-04): 3 patches; this skip plus selftest ARRAY map plus
      feature-probe skip
    - `[PATCH bpf]` (2026-06-09): this patch alone after Andrii asked to
      resend it
    - v3 (2026-06-10): 2 patches; this skip plus smarter feature probes
      — **this is what landed**
  - Review:
    - Mykyta: skip is correct; making the probe token-aware would be
      worse. `Acked-by`.
    - Andrii: this is the only v2 patch that should apply; resend it
      alone.
    - Mykyta on the bpf-tree resend: retarget **bpf-next**, add a Fixes
      tag, include Acks. No NAK.
  - No reviewer `Cc: stable`. bpf-next targeting is a process note, not
    a statement that the bug is absent from 6.18.

**Step 4.2: CHECK WHO REVIEWED THE PATCH**
- Record: Recipients included Andrii Nakryiko, Alexei Starovoitov,
  Daniel Borkmann, Eduard Zingerman, Mykyta Yatsenko,
  `bpf@vger.kernel.org`. Right maintainers. Applied by Andrii.

**Step 4.3: SEARCH FOR THE BUG REPORT**
- Record: No syzbot/bugzilla. Trigger is in-tree
  `tools/testing/selftests/bpf/prog_tests/token.c`: `obj_priv_map`
  (QUEUE map only), `obj_priv_prog` (XDP only), freplace, implicit-token
  struct_ops. Author hit failing tests; v1 tried widening token
  permissions instead of fixing libbpf.

**Step 4.4: CHECK FOR RELATED PATCHES AND SERIES**
- Record: v3 2/2 is the feature-probe follow-up (`features.c`). Not
  required for this skip to be correct. Not in this tree.

**Step 4.5: CHECK STABLE MAILING LIST HISTORY**
- Record: lore.kernel.org/stable search returned HTTP 403. UNVERIFIED
  whether stable@ discussed this. No in-tree evidence of a prior
  rejection.

## PHASE 5: CODE SEMANTIC ANALYSIS

**Step 5.1: IDENTIFY KEY FUNCTIONS**
- Record: `bpf_object__probe_loading()` only.

**Step 5.2: TRACE CALLERS**
- Record: Single caller, `bpf_object_prepare()` (`libbpf.c` ~8705),
  which is called from:
  - `bpf_object_load()` if state `< OBJ_PREPARED`
  - public `bpf_object__prepare()`
- Every normal `bpf_object__load()` / skeleton load goes through this
  after token setup.

**Step 5.3: TRACE CALLEES**
- Record: `bump_rlimit_memlock()`, `bpf_prog_load()`, `close()`,
  `pr_warn()`. After the skip, those are not used when a token is
  present.

**Step 5.4: FOLLOW THE CALL CHAIN**
- Record: Userspace, reachable from any app using in-tree
  libbpf/bpftool/selftests with a BPF token (explicit `bpf_token_path`
  or implicit `/sys/fs/bpf`). Not a kernel oops path. Unprivileged user-
  namespace workloads with delegated tokens are the intended users.

**Step 5.5: SEARCH FOR SIMILAR PATTERNS**
- Record: Same skip already exists for `obj->gen_loader`.
  `tools/lib/bpf/features.c` still hardcodes probe program types with
  `token_fd` — that is series patch 2, still open in this tree.

## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE

**Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?**
- Record:
  - Local tree: `v6.18.53` (`Makefile` 6.18.53, `git describe HEAD` =
    `v6.18.53-5049-g0f0bdc2b0dffb`), branch `autosel`.
  - `v6.18:tools/lib/bpf/libbpf.c` has the pre-fix probe: token opts +
    `SOCKET_FILTER`/`TRACEPOINT`.
  - Pre-fix hunk md5 matches `v6.18` exactly.
  - Kernel BPF token (`kernel/bpf/token.c`, `35f96de04127d`) is in 6.18.
  - Bug introduced ~6.9 (`6b434b61b4d9e`), well before 6.18.

**Step 6.2: CHECK FOR BACKPORT COMPLICATIONS**
- Record: Clean apply. Context around `bpf_object__probe_loading()`
  matches 6.18. No refactor in the way.

**Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE**
- Record: No other skip of this probe for tokens. No equivalent feature-
  probe fix. Token selftests still omit SOCKET_FILTER/TRACEPOINT for
  `obj_priv_*`, so they still depend on this libbpf change.

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

**Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY**
- Record: `tools/lib/bpf` — IMPORTANT, not CORE kernel. Used by bpftool,
  selftests, and distro in-tree libbpf. BPF tokens are the supported
  unprivileged/container path.

**Step 7.2: ASSESS SUBSYSTEM ACTIVITY**
- Record: Active (recent libbpf mmap/percpu/relocation fixes). Token
  wiring itself has been stable since 6.9; this is a late correctness
  fix, not churn in brand-new code.

## PHASE 8: IMPACT AND RISK ASSESSMENT

**Step 8.1: DETERMINE WHO IS AFFECTED**
- Record: Config/driver-specific in the BPF-token sense: users of libbpf
  object load with a token whose allowed types exclude SOCKET_FILTER
  **and** TRACEPOINT. That includes the in-tree token selftests and
  container/user-ns setups that delegate only XDP, struct_ops, maps,
  etc.

**Step 8.2: DETERMINE THE TRIGGER CONDITIONS**
- Record: Trigger is common for token users: every
  `bpf_object__load()`/`prepare()`. Not a race. Unprivileged tasks in a
  delegated user ns can hit it; that is the token design.

**Step 8.3: DETERMINE THE FAILURE MODE SEVERITY**
- Record: Object load/prepare fails outright with a misleading “Couldn't
  load trivial BPF program” warning. Not a kernel oops/corruption.
  **Severity: MEDIUM-HIGH** for token users (feature completely
  unusable), **LOW** for everyone else (`token_fd == 0` path is
  unchanged).

**Step 8.4: CALCULATE RISK-BENEFIT RATIO**
- Record:
  - **Benefit:** Restricted-token libbpf loads work; in-tree token tests
    can pass; matches the token least-privilege model.
  - **Risk:** Very low (net −4 lines, one extra early return).
  - **Ratio:** Strongly favorable for 6.18.

## PHASE 9: FINAL SYNTHESIS

**Step 9.1: COMPILE THE EVIDENCE**

FOR:
- Real, reproducible false failure on every object load with a
  restricted token
- In-tree selftests exercise exactly this (XDP-only, map-only,
  struct_ops-only tokens)
- Buggy code is in 6.18; tokens are in 6.18
- 10-line, obviously correct skip; same pattern as `gen_loader`
- Acked by Mykyta Yatsenko; applied by Andrii Nakryiko
- Clean apply; no new API
- Andrii treated this patch as independently landable

AGAINST:
- Userspace libbpf, not a kernel oops/UAF/deadlock
- No `Cc: stable`; reviewers asked for bpf-next (process, not “bug
  absent from 6.18”)
- Series patch 2 (feature probes) is still missing; some
  struct_ops+global-data cases may still fail
- Specialized to BPF token users, not all machines

UNRESOLVED:
- Stable@ thread search failed (HTTP 403)
- Cover-letter claim that a failed probe “drops the token reference” in
  the kernel was not independently verified; the libbpf-level hard-fail
  is verified from code and tests

**Step 9.2: APPLY THE STABLE RULES CHECKLIST**
1. Obviously correct and tested? **PASS** — skip is the same idea as
   `gen_loader`; reviewed/acked; tests exist
2. Fixes a real bug that affects users? **PASS** — object load fails for
   valid token configs
3. Important issue? **PASS** (MEDIUM-HIGH for token users) — complete
   load failure, not theoretical; fits “oh, that's not good”
4. Small and contained? **PASS** — 3/7 lines, one function
5. No new features or APIs? **PASS**
6. Can apply to the local tree? **PASS** — 6.18 source matches the pre-
   image

**Step 9.3: CHECK FOR EXCEPTION CATEGORIES**
- Record: None (not a device ID, quirk, DT, build, or docs fix).
  Qualifies as a normal small bugfix.

**Step 9.4: DECISION**

6.18 ships BPF tokens and this probe. Without the skip, libbpf treats a
least-privilege token as “BPF does not work” and aborts prepare/load.
That is a real, user-visible break for the token feature and for in-tree
tests, with a tiny, maintainer-reviewed fix and negligible regression
risk.

### What the commit solves for this tree
Restricted BPF tokens cannot load objects through libbpf because a dummy
SOCKET_FILTER/TRACEPOINT probe is not in the token’s allow-list.
Skipping the probe when a token FD exists restores load/prepare for
those objects.

### Stable rules / risk
Meets the written rules: small, obviously correct, real bug, no new API,
applies cleanly to 6.18. Benefit is restoring a supported 6.18 feature;
risk is an extra early return on a userspace probe.

### Concerns
Feature-probe hardcoded types (series patch 2) remain in this tree, so
some token+global-data cases may still be incomplete. That does not
remove the standalone value of stopping probe_loading from failing the
whole load.

Verification:
- [Phase 1] Parsed tags: author Yuan Chen; SOB Andrii Nakryiko; lore
  link; no Fixes/Reported-by/Cc-stable in the commit
- [Phase 2] Diff: `tools/lib/bpf/libbpf.c`,
  `bpf_object__probe_loading()`, +3/−7; skip on `obj->token_fd`; dummy
  loads use NULL opts
- [Phase 2] Confirmed `OPTS_VALID(NULL)` is true in `libbpf_internal.h`
- [Phase 3] Blame: token opts added in `6b434b61b4d9e` (2024-01-23);
  ancestor of this tree
- [Phase 3] No Fixes tag; introducing commit present; patch 2 not in
  this tree
- [Phase 4] `b4 dig -c d262a25d8b58`: lore
  https://patch.msgid.link/20260610145059.113412-2-chenyuan_fl@163.com
- [Phase 4] `b4 dig -a`: v1 selftest workaround → v2 series → bpf-only
  resend → v3 1/2 (landed)
- [Phase 4] `b4 dig -w`: bpf maintainers on Cc
- [Phase 4] mbox: Mykyta Acked-by; Andrii asked to land this patch
  alone; later asked bpf-next + Fixes tag
- [Phase 5] Only caller is `bpf_object_prepare()` ← `bpf_object_load()`
  and `bpf_object__prepare()`
- [Phase 6] `git describe HEAD` / `make kernelversion`: 6.18.53; `v6.18`
  file matches pre-fix hunk md5
- [Phase 6] Token selftests in this tree still use XDP-only / map-only
  tokens without SOCKET_FILTER/TRACEPOINT
- [Phase 8] Failure mode: userspace object load abort, not kernel oops;
  severity MEDIUM-HIGH for token users
- UNVERIFIED: stable@ mailing-list discussion (lore 403); kernel “drops
  token reference” cover-letter claim

**YES**

 tools/lib/bpf/libbpf.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
index b21d2f842579f..1aaf2bdf26857 100644
--- a/tools/lib/bpf/libbpf.c
+++ b/tools/lib/bpf/libbpf.c
@@ -5070,12 +5070,8 @@ bpf_object__probe_loading(struct bpf_object *obj)
 		BPF_EXIT_INSN(),
 	};
 	int ret, insn_cnt = ARRAY_SIZE(insns);
-	LIBBPF_OPTS(bpf_prog_load_opts, opts,
-		.token_fd = obj->token_fd,
-		.prog_flags = obj->token_fd ? BPF_F_TOKEN_FD : 0,
-	);
 
-	if (obj->gen_loader)
+	if (obj->gen_loader || obj->token_fd)
 		return 0;
 
 	ret = bump_rlimit_memlock();
@@ -5084,9 +5080,9 @@ bpf_object__probe_loading(struct bpf_object *obj)
 			errstr(ret));
 
 	/* make sure basic loading works */
-	ret = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL", insns, insn_cnt, &opts);
+	ret = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, NULL, "GPL", insns, insn_cnt, NULL);
 	if (ret < 0)
-		ret = bpf_prog_load(BPF_PROG_TYPE_TRACEPOINT, NULL, "GPL", insns, insn_cnt, &opts);
+		ret = bpf_prog_load(BPF_PROG_TYPE_TRACEPOINT, NULL, "GPL", insns, insn_cnt, NULL);
 	if (ret < 0) {
 		ret = errno;
 		pr_warn("Error in %s(): %s. Couldn't load trivial BPF program. Make sure your kernel supports BPF (CONFIG_BPF_SYSCALL=y) and/or that RLIMIT_MEMLOCK is set to big enough value.\n",
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH AUTOSEL 6.18-5.10] libbpf: Fix ring buffer consumer loop on 32-bit position wrap
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
  2026-10-19 13:45 ` [PATCH AUTOSEL 6.18-6.1] libbpf: Skip bpf_object__probe_loading() when BPF token is in use Sasha Levin
@ 2026-10-19 13:46 ` Sasha Levin
  2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_SDIV and BPF_SMOD in RV32 JIT Sasha Levin
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:46 UTC (permalink / raw)
  To: patches, stable
  Cc: Israel Téllez García, Andrii Nakryiko, Sasha Levin,
	eddyz87, ast, daniel, memxor, bpf, linux-kernel

From: Israel Téllez García <i.tellez@btesa.com>

[ Upstream commit fdd4fad0bbbd08501465c5f9b556963093c5d58a ]

ringbuf_process_ring() walks the records between the consumer and the
producer with an ordering comparison:

	while (cons_pos < prod_pos) {

cons_pos and prod_pos mirror the kernel's ring positions and are
unsigned long here too, so on 32-bit they wrap at 2^32 bytes of traffic.
When producer_pos has wrapped and consumer_pos has not, prod_pos is the
smaller of the two, the loop body never runs and no record is consumed.
Since consumer_pos only advances inside that loop, it never wraps either
and the consumer stops delivering samples for good, with no error
returned to the caller: ring_buffer__poll() keeps reporting zero
records while the kernel side fills up and starts dropping.

Compare the distance instead. The consumer never runs ahead of the
producer, so prod_pos - cons_pos is the amount of unconsumed data and
stays correct across the wrap.

64-bit hosts are unaffected in practice: the counters would need 16 EiB
to wrap. This is the userspace counterpart of the kernel-side walk fixed
in "bpf: Fix pending_pos walk on 32-bit ring position wrap"; a 32-bit
consumer hits whichever of the two comes first.

Signed-off-by: Israel Téllez García <i.tellez@btesa.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/bpf/20260814124843.22041-5-i.tellez@btesa.com
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit message forensics

**Step 1.1: PARSE THE SUBJECT LINE**
Record: **[libbpf] [Fix] ring buffer consumer loop on 32-bit position
wrap**

**Step 1.2: PARSE ALL COMMIT MESSAGE TAGS**
Record:
- **Signed-off-by:** Israel Téllez García `<i.tellez@btesa.com>`
  (author)
- **Signed-off-by:** Andrii Nakryiko `<andrii@kernel.org>` (libbpf/BPF
  maintainer)
- **Link:** `https://lore.kernel.org/bpf/20260814124843.22041-5-
  i.tellez@btesa.com`
- No `Reported-by`, `Tested-by`, `Reviewed-by`, `Acked-by`, or `Cc:
  stable` on the original patch
- Original submission had **no `Fixes:` tag**. `b4 am` later attached a
  lore trailer: `Fixes: bf99c936f947 ("libbpf: Add BPF ring buffer
  support")` (DKIM/kernel.org). That SHA is the 2020 introduction of BPF
  ringbuf in libbpf, and it is in this tree.

**Step 1.3: ANALYZE THE COMMIT BODY TEXT**
Record:
- **Bug:** `ringbuf_process_ring()` uses `while (cons_pos < prod_pos)`
  on `unsigned long` positions. On 32-bit those wrap at 2^32 bytes.
  After producer wrap, `prod_pos` is small and `cons_pos` is still
  large, so the loop never runs.
- **Symptom:** `ring_buffer__poll()` / consume APIs keep returning 0
  with no error; the kernel ring fills and drops samples permanently.
- **Root cause:** ordering comparison is not wrap-safe; consumer_pos
  only advances inside that loop, so it never wraps either.
- **Version info:** 64-bit is unaffected in practice (16 EiB to wrap).
  Explicitly the userspace counterpart of the kernel `pending_pos` walk.
  Field data for that sibling: four armv7 i.MX7 Dual devices on 6.6.52
  stalled after exactly 4295491360 bytes (~2^32 + one 512 KiB ring).
- **Author’s fix:** `prod_pos - cons_pos > 0` (unsigned distance).

**Step 1.4: DETECT HIDDEN BUG FIXES**
Record: Not hidden. Subject and body describe a real wrap-induced stall.
Not cleanup/refactor.

---

## Phase 2: Diff analysis

**Step 2.1: INVENTORY THE CHANGES**
Record:
- File: `tools/lib/bpf/ringbuf.c` — **1 line changed** (`+1 / -1`)
- Function: `ringbuf_process_ring()`
- Scope: single-file surgical fix

**Step 2.2: UNDERSTAND THE CODE FLOW CHANGE**
Record:
- **Before:** `while (cons_pos < prod_pos)` — fails after producer wrap
  on 32-bit.
- **After:** `while (prod_pos - cons_pos > 0)` — unsigned modular
  distance; stays true while unconsumed data exists.
- Path: normal consume path (`ring_buffer__poll` / `__consume` /
  `__consume_n` / `ring__consume*`), not an error/init-only path.

**Step 2.3: IDENTIFY THE BUG MECHANISM**
Record:
- **Category:** logic/correctness (32-bit wrap / unsigned comparison)
- **Mechanism:** same class as kernel `pending_pos`. Unsigned `unsigned
  long` counters are documented to wrap
  (`Documentation/bpf/ringbuf.rst`). Distance arithmetic is already used
  in this tree in `ring__avail_data_size()` (`return prod_pos -
  cons_pos`) and kernel `ringbuf_avail_data_sz()`.

**Step 2.4: ASSESS THE FIX QUALITY**
Record:
- Obviously correct: matches the kernel-side pattern already in this
  tree (`while (prod_pos - pend_pos > 0)` in `kernel/bpf/ringbuf.c`).
- Minimal: one condition.
- Regression risk: low. On 64-bit equivalent until astronomical wrap. If
  consumer ever ran ahead of producer, the new check would wrap and keep
  looping; the author and kernel invariant say that does not happen.
  Same assumption the kernel pending_pos fix already shipped here.

---

## Phase 3: Git history investigation

**Step 3.1: BLAME THE CHANGED LINES**
Record: Buggy `while (cons_pos < prod_pos)` introduced in
**`bf99c936f9478`** (“libbpf: Add BPF ring buffer support”, 2020-06-01)
by Andrii Nakryiko. Present since BPF ringbuf existed. Surrounding `cnt
>= n` bits are later (`13e8125a22763`, 2024) and unrelated.

**Step 3.2: FOLLOW THE FIXES: TAG**
Record: Original mail had no `Fixes:`. Trailer names `bf99c936f947`,
which **is in this tree**. Kernel sibling `19de99ea47e3c` (“bpf: Fix
pending_pos walk on 32-bit ring position wrap”) **is already in this
tree**; its `Fixes:` target `cfa1a2329a691` is also here.

**Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES**
Record: Recent `tools/lib/bpf/ringbuf.c` commits are consume_n
overflow/return-value fixes, not wrap handling. This is standalone:
series patch 4/4. Cover letter: patches 1–3 are kernel; 4 is libbpf.
Patches 2–3 are overwrite-mode `ringbuf_avail_data_sz()` —
**overwrite_pos does not exist in this 6.12 tree**. Nakryiko dropped
patch 3 anyway.

**Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS**
Record: Israel Téllez García authored the kernel wrap fix already on
this branch (`19de99ea47e3c`). Andrii Nakryiko (libbpf maintainer)
applied both.

**Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS**
Record: No code dependency. One-line condition change in a function that
already exists. `git apply --check` on the original mbox succeeded (exit
0). Does not need overwrite-mode patches.

---

## Phase 4: Mailing list and external research

**Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION**
Record:
- Series: **[PATCH v2 bpf 0/4] bpf: Fix ring buffer position wrap
  handling on 32-bit**
- Lore: `https://lore.kernel.org/bpf/20260814124843.22041-1-
  i.tellez@btesa.com`
- This patch: `...22041-5-i.tellez@btesa.com` (4/4)
- v1: `https://lore.kernel.org/bpf/20260806130214.66028-1-
  i.tellez@btesa.com/` (kernel pending_pos only; v2 added sites 2–4)
- v2 change for patch 1: drop `(long)` cast per Nakryiko; unsigned
  difference is enough
- Cover: patch 1 was the field-observed stall; patch 4 is the userspace
  counterpart. “A 32-bit consumer hits whichever of the two comes
  first.”
- No NAK on patch 4. Patch 3 was dropped by both author and Nakryiko
  (ordering of overwrite_pos loads).

**Step 4.2: CHECK WHO REVIEWED THE PATCH**
Record (`b4 dig -w`): To/Cc included Andrii Nakryiko, Alexei
Starovoitov, Daniel Borkmann, Eduard Zingerman, Martin KaFai Lau, Song
Liu, Jiri Olsa, `bpf@vger.kernel.org`. Applied by Nakryiko.

**Step 4.3: SEARCH FOR THE BUG REPORT**
Record: No syzbot/bugzilla for the libbpf loop itself. Field evidence is
on the kernel sibling: four armv7 devices, exact 2^32-byte trigger, 8–17
h uptime, one patched device then passed 10 GiB. Cover: “Userspace kept
consuming normally until the producer stopped” — meaning the kernel
stall fired first. Once that kernel fix is in (it is, here), this libbpf
loop is the next wrap failure.

**Step 4.4: CHECK FOR RELATED PATCHES AND SERIES**
Record:
1. Kernel pending_pos — **already in this tree**
2. Overwrite-mode avail-data — **N/A here** (no overwrite_mode)
3. producer_pos vs overwrite_pos load order — dropped; **N/A here**
4. **This libbpf loop** — candidate
- User-ringbuf peek (`u64` locals, `cons_pos >= prod_pos`) was called
  out as a separate issue and is **not** this commit. Confirmed still
  present at `kernel/bpf/ringbuf.c:687`.

**Step 4.5: CHECK STABLE MAILING LIST HISTORY**
Record: **UNVERIFIED** — lore.kernel.org/stable returned Anubis bot-
challenge (403/HTML). No stable-list discussion retrieved.

---

## Phase 5: Code semantic analysis

**Step 5.1: IDENTIFY KEY FUNCTIONS**
Record: `ringbuf_process_ring()` only. (semcode MCP not available; used
hunk headers + grep.)

**Step 5.2: TRACE CALLERS**
Record: All in-tree callers of `ringbuf_process_ring()`:
- `ring_buffer__consume_n()`
- `ring_buffer__consume()`
- `ring_buffer__poll()`
- `ring__consume_n()` / `ring__consume()`

These are the public libbpf ringbuf consume APIs. Reachable from any
userspace consumer of `BPF_MAP_TYPE_RINGBUF` (tracing, networking,
etc.). `bpftool` in this tree links `tools/lib/bpf`.

**Step 5.3: TRACE CALLEES**
Record: `smp_load_acquire` on positions/len, `roundup_len()`,
`r->sample_cb()`, `smp_store_release` of consumer_pos. No
allocation/locking in the changed condition.

**Step 5.4: FOLLOW THE CALL CHAIN**
Record: Userspace path: app/bpftool → `ring_buffer__poll/consume` →
`ringbuf_process_ring`. Trigger: ~4 GiB cumulative ring traffic on
32-bit. Unprivileged if the process already has the map fd (typical
tracing daemon). Not a kernel oops, but permanent event loss.

**Step 5.5: SEARCH FOR SIMILAR PATTERNS**
Record:
- Same buggy compare in
  `tools/testing/selftests/bpf/benchs/bench_ringbufs.c:305` (benchmark
  only; not this commit).
- Wrap-safe distance already in `ring__avail_data_size()` and kernel
  `ringbuf_avail_data_sz()`.
- Kernel pending_pos already converted to distance in this tree.

---

## Phase 6: Cross-referencing against the local tree

**Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?**
Record: **Yes.** Local tree is **6.12.111** (`git describe`:
`p-6.12-3-gc504cf0baa3d8`, Makefile `VERSION=6 PATCHLEVEL=12
SUBLEVEL=111`).

```247:247:tools/lib/bpf/ringbuf.c
                while (cons_pos < prod_pos) {
```

`struct ring` uses `unsigned long *consumer_pos` / `*producer_pos`
(lines 29–30). Bug introduced in v5.8-era libbpf ringbuf
(`bf99c936f9478`), long before 6.12. Positions are still `unsigned long`
in kernel `struct bpf_ringbuf`.

**Step 6.2: CHECK FOR BACKPORT COMPLICATIONS**
Record: **Clean apply.** `git apply --check` on the original patch mbox
exited 0. Function shape matches the hunk (`ringbuf_process_ring(struct
ring *r, size_t n)` with `cons_pos`/`prod_pos` as `unsigned long`).

**Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE**
Record: Kernel pending_pos wrap fix **is** here (`19de99ea47e3c`).
Userspace loop still has `cons_pos < prod_pos`. No alternate libbpf wrap
fix in this file.

---

## Phase 7: Subsystem and maintainer context

**Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY**
Record: **libbpf** (`tools/lib/bpf/`) — userspace helper shipped with
the kernel, used by bpftool and in-tree BPF tools. Criticality:
**IMPORTANT** for BPF users, especially 32-bit embedded collectors. Not
core mm/sched, but this tree already backports libbpf ringbuf fixes
(`087d757fb4736`, `00f0e08f23fc0`).

**Step 7.2: ASSESS SUBSYSTEM ACTIVITY**
Record: Actively maintained; libbpf ringbuf APIs grew through 6.12
(`ring__consume_n`, etc.). The wrap bug is old (2020), not a new-feature
leftover.

---

## Phase 8: Impact and risk assessment

**Step 8.1: DETERMINE WHO IS AFFECTED**
Record: **Config/platform-specific:** 32-bit hosts using in-tree libbpf
to consume `BPF_MAP_TYPE_RINGBUF` (armv7/i.MX and similar LTS/embedded).
64-bit unaffected in practice.

**Step 8.2: DETERMINE THE TRIGGER CONDITIONS**
Record: After ~4 GiB of ring traffic, if producer wraps while consumer
still has unconsumed bytes. Common with epoll/batching. Timing-dependent
at the wrap, then **sticky forever**. Unprivileged if the process
already holds the map. The sibling bug was reproduced at 441–862
records/s over hours — not a theoretical corner.

**Step 8.3: DETERMINE THE FAILURE MODE SEVERITY**
Record: Permanent silent stop of sample delivery; kernel then drops
events; APIs return 0 with no error. **HIGH** data/event loss (not a
kernel oops). Restart of the consumer is required.

**Step 8.4: CALCULATE RISK-BENEFIT RATIO**
Record:
- **Benefit: high for 32-bit BPF consumers.** Kernel producer wrap is
  already fixed here, so this is now the remaining stall. Same class as
  a field-proven 6.6.52 armv7 outage.
- **Risk: very low.** One comparison, same idiom as kernel code already
  on this branch.
- Ratio: strongly favors backport.

---

## Phase 9: Final synthesis

**Step 9.1: COMPILE THE EVIDENCE**

**FOR:**
- Real wrap bug; documented that 32-bit positions wrap
- Permanent silent event loss, no error
- One-line, obviously correct, applies cleanly
- Kernel counterpart already in this 6.12.y tree — without this,
  producer keeps going and userspace is the next stall
- Field-proven sibling on armv7 (core LTS/embedded)
- Maintainer SOB (Nakryiko); v2 reviewed
- No new APIs; no overwrite-mode dependency

**AGAINST:**
- Userspace libbpf (some distros use github libbpf, not in-tree)
- 32-bit only in practice
- Original field report hit kernel pending_pos first
- No `Cc: stable` on the original mail (expected for this review queue;
  not a negative)

**UNRESOLVED:**
- Could not query lore.kernel.org/stable (bot wall)
- Could not confirm whether standalone libbpf already carries this
  (irrelevant: verdict is for in-tree `tools/lib/bpf`)

**Step 9.2: APPLY THE STABLE RULES CHECKLIST**
1. Obviously correct and tested? **PASS** — same unsigned-distance idiom
   as kernel code already here; series tested on armv7 for the sibling;
   maintainer applied it.
2. Fixes a real bug that affects users? **PASS** — 32-bit ringbuf
   consumers stall after wrap.
3. Important issue (crash, security, corruption, deadlock)? **PASS** at
   HIGH — permanent silent event loss, not an oops, but equivalent
   operational failure for tracing/telemetry.
4. Small and contained? **PASS** — 1 line, 1 file.
5. No new features or APIs? **PASS**
6. Can apply to the local tree? **PASS** — `git apply --check`
   succeeded; buggy loop present.

**Step 9.3: CHECK FOR EXCEPTION CATEGORIES**
Record: Not a device-ID/quirk/DT/docs exception. Ordinary small bugfix.

**Step 9.4: DECISION**

This belongs on **this** 6.12.y tree. The buggy `cons_pos < prod_pos`
loop is here; the matching kernel wrap fix is already here; the one-line
change applies cleanly and closes the remaining 32-bit stall for in-tree
libbpf consumers.

### What the commit solves and why it matters here

On 32-bit, ring positions are 32-bit `unsigned long` and are documented
to wrap. After producer wrap, the consumer loop never runs, never
advances `consumer_pos`, and never recovers. `ring_buffer__poll()`
reports 0 forever while the kernel drops samples. 6.12.y already
contains the kernel `pending_pos` wrap fix, so a 32-bit producer can now
wrap; this userspace loop is the next failure. 32-bit ARM collectors are
a real LTS audience (the sibling was seen on i.MX7 Dual).

### Risk vs benefit

Benefit is high for 32-bit BPF ringbuf users; risk is a one-line
comparison already used in-kernel on this branch.

---

### Verification
- **[Phase 1]** Parsed subject/tags/body from the candidate and from
  `/tmp/ringbuf-mbox/wrap-p5`; `Fixes:` only as a later lore trailer to
  `bf99c936f947`
- **[Phase 2]** Diff is one condition in `ringbuf_process_ring()`;
  current tree still has `while (cons_pos < prod_pos)` at line 247
- **[Phase 3]** `git blame` → `bf99c936f9478` (2020); `git merge-base
  --is-ancestor` confirms that SHA, `cfa1a2329a691`, and kernel wrap fix
  `19de99ea47e3c` are in HEAD
- **[Phase 3]** `git log --oneline -- tools/lib/bpf/ringbuf.c` — no
  userspace wrap fix present
- **[Phase 4]** `b4 dig -c 19de99ea47e3c -a/-w` → v2 4-patch series;
  this is 4/4
- **[Phase 4]** Cover letter and patches from `/tmp/ringbuf-
  series/*.cover` and `.mbx`; Nakryiko dropped patch 3 (`/tmp/ringbuf-
  mbox/nakryiko`)
- **[Phase 4]** lore.kernel.org HTTP 403 / Anubis; used `b4` mboxes
  instead
- **[Phase 5]** grep callers of `ringbuf_process_ring` in
  `tools/lib/bpf/ringbuf.c`; public consume/poll APIs
- **[Phase 6]** `git describe HEAD` → `p-6.12-3-gc504cf0baa3d8`; `make
  kernelversion` → `6.12.111`
- **[Phase 6]** `git apply --check /tmp/ringbuf-mbox/wrap-p5` exit 0
- **[Phase 6]** `overwrite_pos` / `overwrite_mode` absent from
  `kernel/bpf/ringbuf.c` in this tree
- **[Phase 7/8]** `Documentation/bpf/ringbuf.rst` documents 32-bit wrap;
  kernel sibling commit describes four armv7 devices
- **UNVERIFIED:** stable@ lore discussion; whether distro standalone
  libbpf already has this

**YES**

 tools/lib/bpf/ringbuf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/lib/bpf/ringbuf.c b/tools/lib/bpf/ringbuf.c
index 00ec4837a06de..c8fe57401a8b5 100644
--- a/tools/lib/bpf/ringbuf.c
+++ b/tools/lib/bpf/ringbuf.c
@@ -244,7 +244,7 @@ static int64_t ringbuf_process_ring(struct ring *r, size_t n)
 	do {
 		got_new_data = false;
 		prod_pos = smp_load_acquire(r->producer_pos);
-		while (cons_pos < prod_pos) {
+		while (prod_pos - cons_pos > 0) {
 			len_ptr = r->data + (cons_pos & r->mask);
 			len = smp_load_acquire(len_ptr);
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_SDIV and BPF_SMOD in RV32 JIT
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
  2026-10-19 13:45 ` [PATCH AUTOSEL 6.18-6.1] libbpf: Skip bpf_object__probe_loading() when BPF token is in use Sasha Levin
  2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] libbpf: Fix ring buffer consumer loop on 32-bit position wrap Sasha Levin
@ 2026-10-19 13:46 ` Sasha Levin
  2026-10-19 13:48 ` [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall Sasha Levin
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:46 UTC (permalink / raw)
  To: patches, stable
  Cc: Kuan-Wei Chiu, Pu Lehui, Kumar Kartikeya Dwivedi, Sasha Levin,
	ast, daniel, andrii, eddyz87, luke.r.nels, xi.wang, pjw, palmer,
	aou, bpf, linux-riscv, linux-kernel

From: Kuan-Wei Chiu <visitorckw@gmail.com>

[ Upstream commit 2e0fa2389c50fd3a69bb738efa74e127b7516938 ]

The current rv32 bpf jit compiler incorrectly treats BPF_SDIV and
BPF_SMOD as unsigned operations. The BPF instruction set allows
signed division and modulo by reusing the BPF_DIV and BPF_MOD opcodes
with the instruction offset set to 1.

Update the emit_alu_r32() function to accept an 'is_sdiv' variable and
emit the correct div and rem instructions when the offset is 1.

Before this patch:
[   44.161771] test_bpf: #165 ALU_SDIV_X: -6 / 2 = -3 jited:1 ret 2147483645 != -3 (0x7ffffffd != 0xfffffffd)FAIL (1 times)
[   44.167385] test_bpf: #166 ALU_SDIV_K: -6 / 2 = -3 jited:1 ret 2147483645 != -3 (0x7ffffffd != 0xfffffffd)FAIL (1 times)
[   44.171053] test_bpf: #169 ALU_SMOD_X: -7 % 2 = -1 jited:1 ret 1 != -1 (0x1 != 0xffffffff)FAIL (1 times)
[   44.172081] test_bpf: #170 ALU_SMOD_K: -7 % 2 = -1 jited:1 ret 1 != -1 (0x1 != 0xffffffff)FAIL (1 times)

After this patch:
[   16.002192] test_bpf: #165 ALU_SDIV_X: -6 / 2 = -3 jited:1 95 PASS
[   16.002983] test_bpf: #166 ALU_SDIV_K: -6 / 2 = -3 jited:1 1059 PASS
[   16.017167] test_bpf: #169 ALU_SMOD_X: -7 % 2 = -1 jited:1 136 PASS
[   16.023002] test_bpf: #170 ALU_SMOD_K: -7 % 2 = -1 jited:1 109 PASS

Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
Link: https://lore.kernel.org/bpf/20260714002451.4091139-2-visitorckw@gmail.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit message forensics

**Step 1.1 — Subject**
Record: `[riscv, bpf] [Add support] Treat BPF_SDIV/BPF_SMOD as signed in
the RV32 JIT, not as unsigned DIV/MOD.`

**Step 1.2 — Tags**
Record:
- Reviewed-by: Pu Lehui `<pulehui@huawei.com>` (RISC-V BPF JIT reviewer;
  author of the RV64 signed-div JIT)
- Link: `https://lore.kernel.org/bpf/20260714002451.4091139-2-
  visitorckw@gmail.com`
- Signed-off-by: Kuan-Wei Chiu (author)
- Signed-off-by: Kumar Kartikeya Dwivedi (BPF maintainer, applied to
  bpf-next)
- No Fixes:, no Cc: stable, no Reported-by, no Tested-by on the commit
  itself (Tested-by: Pu Lehui is on the series cover)
- Absence of Fixes/Cc: stable is expected for this review and is not a
  negative signal

**Step 1.3 — Body**
Record: The RV32 JIT emits `divu`/`remu` for `BPF_DIV`/`BPF_MOD` even
when `insn->off == 1` (the BPF encoding for signed div/mod). Symptom is
silent wrong results with the program still marked JIT’d (`jited:1`):
`-6 / 2` → `2147483645` instead of `-3`; `-7 % 2` → `1` instead of `-1`.
Root cause: offset field ignored. After: those `test_bpf` cases PASS.

**Step 1.4 — Hidden bug fix?**
Record: Yes. Title says “Add support”, but this is a JIT miscompilation
fix. The JIT already handles DIV/MOD; it just uses the unsigned RISC-V
ops. Cover-letter v3 dropped a Fixes: tag and retitled the series as
enablement; that is framing, not a lack of a bug.

## Phase 2: Diff analysis

**Step 2.1 — Inventory**
Record: One file, `arch/riscv/net/bpf_jit_comp32.c`, +10/−5. Functions:
`emit_alu_r32()` (signature + DIV/MOD cases), `bpf_jit_emit_insn()` (two
call sites). Surgical, single-file.

**Step 2.2 — Flow**
Record:
- Before: `emit_alu_r32(..., u8 op)` always emits `rv_divu` / `rv_remu`.
- After: it takes `const struct bpf_insn *insn`, sets `is_signed =
  insn->off == 1`, and emits `rv_div`/`rv_rem` vs `rv_divu`/`rv_remu`.
- Callers pass `insn` instead of `BPF_OP(code)`. Both register (`BPF_X`)
  and immediate (`BPF_K`) 32-bit DIV/MOD already go through this path
  (no RISC-V I-type div).
- 64-bit DIV/MOD on RV32 still `goto notsupported` (interpreter
  fallback). This patch does not touch that path.

**Step 2.3 — Bug mechanism**
Record: Logic/correctness. Signed vs unsigned opcode selected by
`insn->off`. Not a leak, race, or UAF.

**Step 2.4 — Fix quality**
Record: Obviously correct and minimal. Regression risk is low: unsigned
path is unchanged when `off == 0`; signed ops are the matching RISC-V
M-extension instructions. No API or data-structure change. Minor nit:
commit text says an `is_sdiv` argument; the code uses `is_signed` from
`insn->off`. Harmless.

## Phase 3: Git history

**Step 3.1 — Blame**
Record: Unsigned `rv_divu`/`rv_remu` in `emit_alu_r32` come from
`5f316b65e99f` (“riscv, bpf: Add RV32G eBPF JIT”, 2020-03,
**v5.7-rc1**). At that time BPF had no signed div. The encoding (`off ==
1`) landed in `ec0e2da95f72d` (“bpf: Support new signed div/mod
instructions.”, 2023-07, **v6.6-rc1**), which updated the interpreter
and x86 JIT only. RV64 JIT followed in `3e18ff4bce9b5` (**v6.6-rc1**).
RV32 stayed on unsigned emit until this commit.

**Step 3.2 — Fixes: tag**
Record: None on the landed commit. v1/v2 had a Fixes: tag; v3 removed
it. CI bot later suggested `Fixes: 5f316b65e99f`; Pu Lehui replied that
a Fixes tag was not needed. That objection is about blaming the 2020 JIT
(which was correct then), not about whether today’s unsigned emit is
wrong.

**Step 3.3 — File history / series**
Record: bpf-next v4 1/3 of “signed operations and 32-bit atomics”:
1. this SDIV/SMOD patch (`2e0fa2389c50f`)
2. BPF_MOVSX (`c6a08afdfe3a8`)
3. 32-bit atomics (`a1b37972efc0b`)

This patch does not depend on 2 or 3. Merge: `426a6befd01f9`.

**Step 3.4 — Author**
Record: Kuan-Wei Chiu; not the RV32 JIT original author. Nearby work is
this series only. Reviewer Pu Lehui is the established RISC-V BPF JIT
contributor.

**Step 3.5 — Prerequisites**
Record: Needs `rv_div()` / `rv_rem()` in `arch/riscv/net/bpf_jit.h`
(added by `3e18ff4bce9b5`, present here). Needs `emit_alu_r32` and
32-bit DIV/MOD cases (present since v5.7). Standalone on this tree.

## Phase 4: Mailing list

**Step 4.1 — Original discussion**
Record: Lore from `b4 dig`:
`https://patch.msgid.link/20260714002451.4091139-2-visitorckw@gmail.com`
Revisions: v3 (2026-07-06) and v4 (2026-07-14). v3 changelog: pass
`insn` into `emit_alu_r32`; **remove Fixes: tag and update title**.
Applied version is v4 (latest). No NAK on this patch. No Cc: stable in
the thread.

**Step 4.2 — Recipients**
Record: bpf@vger, linux-riscv, ast, daniel, andrii, eddyz87, memxor,
palmer, aou, pulehui, martin.lau, yonghong.song, alex@ghiti.fr. Right
lists and maintainers. Reviewed-by Pu Lehui; applied by Kumar Kartikeya
Dwivedi.

**Step 4.3 — Bug report**
Record: No syzbot/bugzilla. Evidence is in-tree `test_bpf` (#165, #166,
#169, #170) plus Pu Lehui Tested-by on the cover. Cover also notes
`test_bpf.ko` going to 0 failures on the author’s setup.

**Step 4.4 — Series**
Record: 1/3 is this fix. 2/3 (MOVSX) and 3/3 (atomics) are separate ISA-
coverage patches. 3/3 is closer to a feature (more JIT’d atomics;
interpreter already worked). This patch is self-contained.

**Step 4.5 — stable@**
Record: UNVERIFIED. lore.kernel.org returned 403 to HTTP fetch; the
saved mboxes have no stable@ nomination.

## Phase 5: Semantic analysis

**Step 5.1 — Functions**
Record: `emit_alu_r32`, `bpf_jit_emit_insn`.

**Step 5.2 — Callers**
Record: `emit_alu_r32` is only called from `bpf_jit_emit_insn` (ALU
32-bit X/K DIV/MOD/MUL/shifts, and NEG). `bpf_jit_emit_insn` is called
from `arch/riscv/net/bpf_jit_core.c` during JIT of a loaded BPF program.

**Step 5.3 — Callees**
Record: `bpf_get_reg32` / `bpf_put_reg32`,
`rv_div`/`rv_divu`/`rv_rem`/`rv_remu`. No new locks or allocations.

**Step 5.4 — Reachability**
Record: Yes, from BPF program load on RV32 with `CONFIG_BPF_JIT`
(`HAVE_EBPF_JIT if MMU`). Userspace that can load BPF (privileged; or
unprivileged if enabled) can hit it. `lib/test_bpf.c` exercises it
directly. With `BPF_JIT_ALWAYS_ON`, interpreter fallback is gone and a
wrong JIT is the only path.

**Step 5.5 — Similar patterns**
Record: RV64 already branched on signed vs unsigned in
`bpf_jit_comp64.c`. Interpreter in `kernel/bpf/fixups.c` uses `insn->off
== 1` the same way. ARM32 added 32-bit signed div in `5097faa559a60`
(v6.7-rc1). RV32 64-bit SDIV is still not JIT’d (`notsupported`) —
different, safe fallback.

## Phase 6: Local tree (this checkout)

**Step 6.1 — Does the buggy code exist here?**
Record: Local tree is **7.3.0-rc4** (`v7.3-rc4-27-gf0100363d8c37`). RV32
JIT, signed-div ISA, and `rv_div`/`rv_rem` are all present. The
unsigned-only emit existed in this lineage from v6.6 until
`2e0fa2389c50f` (contained in **v7.3-rc1**). Current `emit_alu_r32`
already has `is_signed = insn->off == 1`. The bug class is in this
tree’s history; the file this patch touches is here.

**Step 6.2 — Backport difficulty**
Record: Clean apply on this code. Pre-image matches the parent of
`2e0fa2389c50f`. Helpers already exist.

**Step 6.3 — Related fixes already here**
Record: This commit is already an ancestor of HEAD (landed in 7.3-rc1).
That is origin-tree presence, not a reason to treat it as “already
selected for stable.” No alternate RV32 SDIV fix. Later series patches
(MOVSX, atomics) are independent.

## Phase 7: Subsystem context

**Step 7.1**
Record: `arch/riscv/net` BPF JIT. Criticality: **PERIPHERAL** for
hardware (RV32 Linux with MMU), **IMPORTANT** for correctness of JIT’d
BPF on that platform. Makefile: `bpf_jit_comp32.o` only when not
`CONFIG_ARCH_RV64I`.

**Step 7.2**
Record: File is mature (since v5.7) with occasional ISA catch-up. Not a
staging driver.

## Phase 8: Impact and risk

**Step 8.1 — Who**
Record: RV32 + BPF JIT users (embedded RISC-V with MMU). Not RV64. Not
no-MMU (no eBPF JIT).

**Step 8.2 — Trigger**
Record: Loading/running a BPF program that uses 32-bit signed DIV or MOD
(`off == 1`). Common in `test_bpf`; less common in production filters,
but a valid ISA op since v6.6. Typically requires BPF-load privilege.

**Step 8.3 — Failure mode**
Record: Silent wrong ALU result while still reporting `jited:1`. Not a
panic. Wrong packet/cgroup/seccomp decisions are possible if a program
uses signed div. Severity: **HIGH** for correctness, not CRITICAL
crash/corruption of kernel memory.

**Step 8.4 — Risk/benefit**
Record: Benefit: correct 32-bit signed div/mod on RV32 JIT; `test_bpf`
failures gone. Risk: very low (~10 lines, ALU emit only, unsigned path
unchanged). Ratio: favorable.

## Phase 9: Synthesis

**Evidence FOR**
- JIT emits unsigned ops for a signed encoding; programs get wrong
  answers and still count as JIT’d.
- Reproduced by `test_bpf`; reviewed and tested by Pu Lehui.
- Small, obviously correct, no new UAPI.
- Independent of the rest of the series.
- All helpers and call sites exist in this tree.
- Same class of ISA handling RV64 and the interpreter already do.

**Evidence AGAINST**
- Subject/cover are “Add support”; sent to bpf-next, not bpf.
- Maintainer said a Fixes tag was not needed (“not a bugfix”) — aimed at
  the 2020 JIT commit, which predates signed div.
- RV32 is a small user base; 64-bit SDIV still not JIT’d (safe
  fallback).
- Not a crash, deadlock, or kernel memory-safety bug.

**Unresolved**
- UNVERIFIED: whether stable@ ever discussed this (lore HTTP 403).
- UNVERIFIED: exact v1/v2 subject/Fixes SHA (v3 changelog confirms a
  Fixes tag was removed).

**Stable rules**
1. Obviously correct and tested? **PASS** — review + `test_bpf`
   before/after + Tested-by.
2. Real bug affecting users? **PASS** — wrong JIT output for a valid
   insn, not a new opcode.
3. Important? **PASS** — silent miscompilation of ALU; high correctness
   impact, not a panic.
4. Small and contained? **PASS** — one file, ~10 lines.
5. No new features/APIs? **PASS** — selects existing RISC-V `div`/`rem`
   when `off==1`; BPF ISA already has this encoding.
6. Applies to this tree? **PASS** — file, helpers, and DIV/MOD path are
   here.

**Exception category:** none (not an ID/quirk/DT/build/doc patch).

The “add support” wording is leftover from a feature series. Behavior is
a JIT bug: the compiler accepts SDIV/SMOD and emits the wrong RISC-V
instruction. That is appropriate for this tree.

Verification:
- [Phase 1] Parsed tags: Reviewed-by Pu Lehui; Link to bpf lore; no
  Fixes/Cc: stable/Reported-by
- [Phase 1] Body documents `test_bpf` #165/#166/#169/#170 wrong results
  with `jited:1`
- [Phase 2] Diff: `emit_alu_r32` takes `insn`; `is_signed = insn->off ==
  1`; `rv_div`/`rv_rem` vs `divu`/`remu`; two call-site updates
- [Phase 2] Confirmed 64-bit DIV/MOD still `goto notsupported` (no
  incomplete 64-bit signed path)
- [Phase 3] `git blame` on parent: unsigned emit from `5f316b65e99f`
  (v5.7-rc1)
- [Phase 3] `ec0e2da95f72d` added signed div ISA in v6.6-rc1; RV64 JIT
  `3e18ff4bce9b5` same cycle
- [Phase 3] `git describe --contains 2e0fa2389c50f` → `v7.3-rc1~...`;
  `git merge-base --is-ancestor` confirms it is in HEAD
- [Phase 3] Series 2/3 and 3/3 are independent; no prerequisite besides
  `rv_div`/`rv_rem` (present)
- [Phase 4] `b4 dig -c 2e0fa2389c50f`: patch-id match; v3 and v4;
  applied v4 1/3
- [Phase 4] `b4 dig -w`: bpf + linux-riscv + BPF/RISC-V maintainers
- [Phase 4] v3 mbox: CI suggested Fixes: 5f316b65e99f; Pu Lehui: “not
  need to add fixes tag, as this not a bugfix”; Pu Reviewed-by on the
  code
- [Phase 4] Cover: “Remove the Fixes: tag and update title”; Tested-by
  Pu Lehui
- [Phase 4] WebFetch lore: 403 Forbidden — discussion taken from b4
  mboxes instead
- [Phase 5] Callers: only `bpf_jit_emit_insn`; JIT compile path from
  `bpf_jit_core.c`
- [Phase 5] `lib/test_bpf.c` has ALU_SDIV_* / ALU_SMOD_* cases matching
  the commit
- [Phase 6] `make kernelversion` / `git describe HEAD`: 7.3.0-rc4 /
  v7.3-rc4-27-gf0100363d8c37
- [Phase 6] `arch/riscv/net/Makefile`: comp32 used when not RV64I;
  `HAVE_EBPF_JIT if MMU`
- [Phase 6] Current file already has the signed emit (commit ancestor of
  HEAD)
- [Phase 8] Failure mode: wrong 32-bit signed div/mod in JIT’d BPF;
  severity HIGH correctness
- UNVERIFIED: stable@ mailing list history (lore 403)

The patch is a small, tested JIT correctness fix for a real miscompile
on RV32. It meets stable rules for this tree.

**YES**

 arch/riscv/net/bpf_jit_comp32.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/arch/riscv/net/bpf_jit_comp32.c b/arch/riscv/net/bpf_jit_comp32.c
index 592dd86fbf811..89153946a4e46 100644
--- a/arch/riscv/net/bpf_jit_comp32.c
+++ b/arch/riscv/net/bpf_jit_comp32.c
@@ -509,12 +509,15 @@ static void emit_alu_r64(const s8 *dst, const s8 *src,
 }
 
 static void emit_alu_r32(const s8 *dst, const s8 *src,
-			 struct rv_jit_context *ctx, const u8 op)
+			 struct rv_jit_context *ctx,
+			 const struct bpf_insn *insn)
 {
 	const s8 *tmp1 = bpf2rv32[TMP_REG_1];
 	const s8 *tmp2 = bpf2rv32[TMP_REG_2];
 	const s8 *rd = bpf_get_reg32(dst, tmp1, ctx);
 	const s8 *rs = bpf_get_reg32(src, tmp2, ctx);
+	u8 op = BPF_OP(insn->code);
+	bool is_signed = insn->off == 1;
 
 	switch (op) {
 	case BPF_MOV:
@@ -539,10 +542,12 @@ static void emit_alu_r32(const s8 *dst, const s8 *src,
 		emit(rv_mul(lo(rd), lo(rd), lo(rs)), ctx);
 		break;
 	case BPF_DIV:
-		emit(rv_divu(lo(rd), lo(rd), lo(rs)), ctx);
+		emit(is_signed ? rv_div(lo(rd), lo(rd), lo(rs)) :
+				 rv_divu(lo(rd), lo(rd), lo(rs)), ctx);
 		break;
 	case BPF_MOD:
-		emit(rv_remu(lo(rd), lo(rd), lo(rs)), ctx);
+		emit(is_signed ? rv_rem(lo(rd), lo(rd), lo(rs)) :
+				 rv_remu(lo(rd), lo(rd), lo(rs)), ctx);
 		break;
 	case BPF_LSH:
 		emit(rv_sll(lo(rd), lo(rd), lo(rs)), ctx);
@@ -1041,7 +1046,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 			emit_imm32(tmp2, imm, ctx);
 			src = tmp2;
 		}
-		emit_alu_r32(dst, src, ctx, BPF_OP(code));
+		emit_alu_r32(dst, src, ctx, insn);
 		break;
 
 	case BPF_ALU | BPF_MOV | BPF_K:
@@ -1065,7 +1070,7 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 		 * src is ignored---choose tmp2 as a dummy register since it
 		 * is not on the stack.
 		 */
-		emit_alu_r32(dst, tmp2, ctx, BPF_OP(code));
+		emit_alu_r32(dst, tmp2, ctx, insn);
 		break;
 
 	case BPF_ALU | BPF_END | BPF_FROM_LE:
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
                   ` (2 preceding siblings ...)
  2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_SDIV and BPF_SMOD in RV32 JIT Sasha Levin
@ 2026-10-19 13:48 ` Sasha Levin
  2026-10-19 15:01   ` sashiko-bot
  2026-10-19 13:50 ` [PATCH AUTOSEL 6.18-6.6] xsk: account ring allocations to memcg Sasha Levin
  2026-10-19 13:52 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_MOVSX in RV32 JIT Sasha Levin
  5 siblings, 1 reply; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:48 UTC (permalink / raw)
  To: patches, stable
  Cc: Pu Lehui, Sashiko, Björn Töpel, Eduard Zingerman,
	Sasha Levin, ast, daniel, andrii, memxor, bpf, linux-kernel

From: Pu Lehui <pulehui@huawei.com>

[ Upstream commit a41d0c30d764e086c62b049e806fd12df4f4acfc ]

Some JIT compilers, such as x86_64, rely on a register to pass the TCC.
When subprograms of synchronous callback invoke tailcall, C helpers
invoking bpf callback clobber this register, and the corrupted TCC may
bypass the TCC limit, leading to infinite tailcall.

Fix this by rejecting tailcall inside all subprogs of sync callback.
This also cleanly consolidates the existing async and exception callback
checks into a single unified `is_cb` check.

Reported-by: Sashiko <sashiko-bot@kernel.org>
Reported-by: Björn Töpel <bjorn@kernel.org>
Signed-off-by: Pu Lehui <pulehui@huawei.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://patch.msgid.link/20260716120157.835937-3-pulehui@huaweicloud.com
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit message forensics

**Step 1.1 — Subject line**
Record: `[bpf]` `[Reject]` Reject callback subprogs that invoke tailcall
(verifier hardening against TCC clobber / infinite tailcall).

**Step 1.2 — Tags**
Record:
- `Reported-by: Sashiko <sashiko-bot@kernel.org>` — BPF fuzzer found
  this
- `Reported-by: Björn Töpel <bjorn@kernel.org>` — independently reported
  by a BPF JIT developer
- `Acked-by: Eduard Zingerman <eddyz87@gmail.com>` — BPF verifier
  maintainer
- `Link: https://patch.msgid.link/20260716120157.835937-3-
  pulehui@huaweicloud.com` — v3 2/3 of a 3-patch series
- `Signed-off-by: Pu Lehui <pulehui@huawei.com>`
- `Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>`
- No `Fixes:` tag (expected)
- No `Cc: stable` (expected)
- Ignore pipeline `Signed-off-by: Sasha Levin` / `[Upstream commit ...]`
  if present — none on the original commit

**Step 1.3 — Body**
Record:
- **Bug:** Some JITs (x86-64) pass the tail-call counter (TCC) in a
  register. A sync callback is invoked from a C helper, which clobbers
  that register. If a callback subprog then tail-calls, TCC is garbage
  and the limit can be bypassed.
- **Symptom:** Infinite tailcall (CPU hang / soft lockup).
- **Root cause:** Verifier only rejected tailcalls inside exception
  callbacks (`is_exception_cb`), not sync callbacks (`bpf_loop`,
  `bpf_for_each_map_elem`, etc.).
- **Fix:** Reject tailcall on any callback frame via unified `is_cb`.
  Also drop the special async-cb `verifier_bug` in favor of that same
  check.
- **Version:** Landed in v7.3-rc1 (2026-07-16); this checkout is
  v7.3-rc4.

**Step 1.4 — Hidden bug fix?**
Record: Not hidden. Explicit security/correctness fix (infinite
tailcall), plus a small consolidation of existing callback checks. Not
cleanup-only.

---

## Phase 2: Diff analysis

**Step 2.1 — Inventory**
Record:
- File: `kernel/bpf/verifier.c` (`+2 / -6`, 8 lines)
- Function: `check_max_stack_depth_subprog()`
- Scope: single-file surgical verifier change

**Step 2.2 — Code flow**
Record:
1. **Removed** `if (subprog[sidx].is_async_cb && has_tail_call)
   verifier_bug; return -EFAULT`. Async callbacks with a tailcall are no
   longer a verifier BUG; they fall through to the unified reject.
2. **Changed** the stack-walk guard from `is_exception_cb` (“cannot tail
   call within exception cb”) to `is_cb` (“cannot tail call within
   callback”). Any callback on the bpf2bpf stack of a tailcall-reachable
   subprog is rejected with `-EINVAL`.

**Step 2.3 — Bug mechanism**
Record: **Logic / correctness** in the BPF verifier, with a **hang**
failure mode.
- `is_cb` is set in `push_callback_call()` for every helper/kfunc
  callback, including sync ones.
- `has_tail_call` is set in `check_subprogs()` when a subprog contains
  `BPF_FUNC_tail_call`.
- x86-64 JIT (`emit_prologue_tail_call`) uses `%rax` as TCC / TCC
  pointer. Callback entry is a C ABI call, so `%rax` is clobbered.
- Corrupted TCC can skip `MAX_TAIL_CALL_CNT` and loop forever.

**Step 2.4 — Fix quality**
Record: Minimal and obviously correct. Same pattern already used for
exception callbacks (b9ae0c9dd0aca, 2023). Regression risk is **verifier
tightening**: programs that do `callback → subprog → tailcall` now fail
to load. Those programs were dangerous. Series patch 1 is required to
avoid false rejects of unrelated tailcall branches (see Phase 3). No
API/feature additions.

---

## Phase 3: Git history

**Step 3.1 — Blame**
Record:
- `is_cb` field: 406a6fa44bfbc (2023-12, bitfield conversion);
  assignment in `push_callback_call()`: ab5cfac139ab85 (2023-11)
- Exception-only tailcall reject: b9ae0c9dd0aca (2023-09, “bpf: Add
  support for custom exception callbacks”)
- Async `has_tail_call` verifier_bug: 7ddc80a476c2d (2021-07, “Teach
  stack depth check about async callbacks”)
- `has_tail_call` tracking: 7f6e4312e15a5 (2020)
- `bpf_loop`: e6f2dd0f80674 (2021-11); CAP_BPF: 5679ff2f138f7 (2022-08)
- x86 TCC-in-register: ebf7d1f508a7 (cited by series patch 1)

The hole (sync callbacks + tailcall) has been present for years; this
tree is far newer.

**Step 3.2 — Fixes: tag**
Record: None on this commit. Related: series patch 1 has `Fixes:
ebf7d1f508a7`. GitHub CI suggested `Fixes: b9ae0c9dd0ac` for this patch
(that commit only covered exception callbacks).

**Step 3.3 — Related file history**
Record: v3 series “Fixes for tailcall with subprog and callback”:
1. `3513ea9dab6c1` bpf: Sync tail_call_reachable with callee state on
   entry
2. `a41d0c30d764e` **this commit**
3. `42bfd21a8b701` selftests/bpf: Add testcases for callback with
   tailcall

Follow-up already in this tree: `266aa4ad0b2e8` (“Reject tail calls
directly from callback frames”) — extra reject in `check_helper_call()`
to avoid a verifier WARN; it comments that stack-depth validation
already rejects this.

**Step 3.4 — Author**
Record: Pu Lehui is a regular BPF contributor (JIT/verifier). Acked and
applied by Eduard Zingerman (verifier maintainer).

**Step 3.5 — Prerequisites**
Record: **Not fully standalone.** Patch 1 must come first. Without it,
`tail_call_reachable` leaks across branches, so patch 2 can reject valid
programs that mix a tailcall subprog with a no-tailcall callback
(`tailcall_callback_2` selftest). Both 3513ea9dab6c1 and 42bfd21a8b701
**are ancestors of this HEAD**. `is_cb` is present. Can apply on this
tree (already applied).

---

## Phase 4: Mailing list / external research

**Step 4.1 — Original discussion**
Record:
- Lore: https://patch.msgid.link/20260716120157.835937-3-
  pulehui@huaweicloud.com
- Cover: https://patch.msgid.link/20260716120157.835937-1-
  pulehui@huaweicloud.com
- Revisions: v1 (2 patches, 2026-07-11) → v2 (2026-07-14) → **v3 (3
  patches, 2026-07-16)** applied
- v3 added patch 1 after Sashiko found the `tail_call_reachable` leak
- Applied to **bpf-next** (not bpf fixes) by Eduard Zingerman on
  2026-07-17
- Sashiko AI review flagged removing the async `verifier_bug`. Author
  replied: async/exception tailcalls are meaningless (ctx not passed);
  sync callbacks are subprogs so `is_cb` fires; direct tailcall in a
  callback is already rejected earlier in `check_helper_call` via
  unknown R0 vs callback return range
- No NAKs. Eduard acked. Patchwork applied v3 as latest.

**Step 4.2 — Recipients (`b4 dig -w`)**
Record: bpf@vger, Alexei, Andrii, Daniel Borkmann, Martin KaFai Lau,
Kartikeya, Yonghong, Song Liu, Jiri Olsa, Björn Töpel, Eduard. Right
maintainers.

**Step 4.3 — Bug report**
Record: Two independent reports:
- Sashiko fuzzer
- Björn Töpel, in review of RISC-V JIT bpf2bpf+tailcall: callback
  entered from C ABI with clobbered TCC, then subprog tailcall. He
  confirmed the verifier accepted `callback → subprog → tailcall`.

**Step 4.4 — Series**
Record: 3 patches; 1+2 are kernel, 3 is selftests. Patch 1 is a
correctness prerequisite for patch 2. Both are in this tree.

**Step 4.5 — Stable list**
Record: lore.kernel.org/stable search returned 403. UNVERIFIED whether
it was discussed on stable@. No `Cc: stable` on the patch. Submitted to
bpf-next during the 7.2 cycle, so it was aimed at 7.3, not 7.2-rc.

---

## Phase 5: Code semantic analysis

**Step 5.1 — Functions**
Record: `check_max_stack_depth_subprog()` only.

**Step 5.2 — Callers**
Record: `check_max_stack_depth()` → `do_check` finalization
(`kernel/bpf/verifier.c` ~21334). Runs on every successful
`BPF_PROG_LOAD` before JIT. Called for main (`i==0`) and every
`is_async_cb` subprog.

**Step 5.3 — Callees**
Record: Walks insns, `bpf_find_subprog()`, `bpf_pseudo_call` /
`bpf_pseudo_func`. Sets `tail_call_reachable` from callee
`has_tail_call`, then marks `tail_call_reachable` on the frame stack for
the JIT.

**Step 5.4 — Reachability**
Record: Userspace `BPF_PROG_LOAD`. Typical trigger: TC/XDP/cgroup
program with `bpf_loop` (or similar) whose callback calls a subprog that
tail-calls. `bpf_loop` is CAP_BPF-gated (`kernel/bpf/helpers.c`). Not
fully unprivileged; reachable by any CAP_BPF loader (Cilium, systemd,
containers with CAP_BPF). **Yes, reachable from userspace.**

**Step 5.5 — Similar patterns**
Record: Same `is_cb` walk used for:
- BPF_LD_ABS/IND in callbacks (`e7d28823c6621`)
- `bpf_throw` from callbacks
Later `266aa4ad0b2e8` added a matching reject in `check_helper_call()`.

---

## Phase 6: This tree

**Step 6.1 — Does the buggy code exist?**
Record: **Yes, in this lineage, and the fix is already merged.**
- Tree: **v7.3-rc4** (`7.3.0-rc4`, HEAD `f0100363d8c37`)
- `git merge-base --is-ancestor a41d0c30d764e HEAD` → true
- `git describe --contains a41d0c30d764e` → `v7.3-rc1~134^2~141^2~1`
- Not in v7.2 (`merge-base --is-ancestor` failed)
- Before this commit, `check_max_stack_depth_subprog()` only tested
  `is_exception_cb`. Sync callbacks were unprotected. That is the 7.3
  code this commit fixed.
- Reverse `git apply --check` of the commit succeeds: working tree
  already has the after-state.

**Step 6.2 — Backport difficulty**
Record: **Already applied** on this tree. `is_cb`,
`check_max_stack_depth_subprog()`, and series patch 1 are present. Would
apply cleanly to the pre-commit parent; no extra rework for 7.3.

**Step 6.3 — Related fixes already here**
Record: Series 1/3 and 3/3, plus follow-up `266aa4ad0b2e8`. No alternate
fix that would make this redundant; the follow-up depends on this
diagnostic.

---

## Phase 7: Subsystem context

**Step 7.1**
Record: **BPF verifier** (`kernel/bpf/verifier.c`). **IMPORTANT** (core
BPF safety; all BPF loaders). Not mm/scheduler, but verifier bugs are
security-relevant.

**Step 7.2**
Record: Highly active in 7.3. This is a mature path
(`check_max_stack_depth` has existed for years), not new infrastructure.

---

## Phase 8: Impact and risk

**Step 8.1 — Who is affected**
Record: Config-specific / BPF users. Needs JIT that passes TCC in a
register (x86-64 is the cited case; Björn noted RISC-V too). Sync
callbacks (`bpf_loop`, for-each helpers) plus tailcall.

**Step 8.2 — Trigger**
Record: Load and run a BPF program: callback subprog tail-calls. Timing-
independent. Needs CAP_BPF (and usually CAP_NET_ADMIN/PERFMON for the
program type). Confirmed accepted by the old verifier.

**Step 8.3 — Failure mode**
Record: **Infinite tailcall → CPU hang / soft lockup.** Severity
**HIGH** (DoS / hang). Not a verified memory-corruption UAF, but a real
stability/security issue.

**Step 8.4 — Risk-benefit**
Record:
- **Benefit:** Stops a hang/DoS in a common BPF pattern (loop callback +
  tailcall) on x86-64.
- **Risk:** Low. 8-line verifier reject. Can break previously accepted
  (unsafe) programs. False-positive risk is handled by patch 1, already
  in this tree.
- **Ratio:** High benefit / low risk.

---

## Phase 9: Synthesis

**Step 9.1 — Evidence**

For backporting / keeping this in this tree:
- Real hang: TCC clobber → infinite tailcall (x86-64 JIT, independently
  described by Töpel)
- Two reporters (Sashiko + Björn Töpel)
- Verifier maintainer Acked-by and applied
- 8-line, obviously correct extension of an existing guard
- Selftests in 42bfd21a8b701
- Call path is `BPF_PROG_LOAD` (userspace)
- This tree has the code, patch 1, and `is_cb`

Against:
- Sent to bpf-next, not bpf fixes; no `Fixes:` / `Cc: stable`
- Tightens verifier (rejects previously valid programs)
- Series member 2/3; needs patch 1 (present here)
- Original commit already in v7.3-rc1, so this checkout already contains
  the fix

Unresolved: whether anyone asked for it on stable@ (lore/stable 403).

**Step 9.2 — Stable rules**
1. Obviously correct and tested? **PASS** — Acked, applied, selftests,
   follow-up confirms the check
2. Real bug affecting users? **PASS** — accepted programs can hang a CPU
3. Important (crash/security/corruption/deadlock)? **PASS** — hang/DoS;
   HIGH
4. Small and contained? **PASS** — 8 lines, one function
5. No new features/APIs? **PASS** — verifier reject only
6. Can apply to this tree? **PASS** — already in v7.3-rc1; prerequisites
   present

**Step 9.3 — Exceptions**
Record: None (not a device ID / quirk / DT / build / docs patch).
Qualifies as a normal important bugfix.

**Step 9.4 — Decision**
This tree is **v7.3-rc4**. The bug is in this series’ BPF verifier/JIT,
the fix is small and correct, and the only dependency (patch 1) is
already here. Already merged in 7.3-rc1 is not a reason to treat it as
unsuitable; it is evidence it belongs in this tree. Technical merit
supports including it.

---

### What the commit solves, and why it matters here

x86-64 (and similar JITs) pass the tail-call counter in a register. Sync
BPF callbacks run from C helpers, which clobber that register. If a
callback’s subprogram tail-calls, the counter can be garbage, the
33-call limit is skipped, and the CPU can tail-call forever.

The verifier only blocked that for exception callbacks. This patch uses
the existing `is_cb` bit so **all** callbacks are rejected. That is a
real hang/DoS for CAP_BPF program loaders on this kernel.

It meets stable rules: obviously correct, important, 8 lines, no new
API. Patch 1 of the same series is already in this tree, so the false-
reject leak is not an issue here.

### Risk vs benefit

Benefit is high (prevents hang). Risk is low (reject unsafe programs).
The only behavioral change is failing `BPF_PROG_LOAD` for
callback+tailcall, which is the intended safety net.

### Concerns

- Needs series patch 1 (`3513ea9dab6c1`) — **already in this tree**
- Selftests (`42bfd21a8b701`) are nice-to-have, not required for the
  kernel fix — **already in this tree**
- Follow-up `266aa4ad0b2e8` is a separate WARN-avoidance fix, not a
  prerequisite for this one

---

### Verification

- **[Phase 1]** Parsed tags: Sashiko + Björn Töpel Reported-by; Eduard
  Acked-by/SOB; Link msgid `835937-3` (v3 2/3); no Fixes/Cc-stable
- **[Phase 1]** `git show a41d0c30d764e --format=full`: infinite
  tailcall from TCC clobber; reject via `is_cb`
- **[Phase 2]** Diff: `kernel/bpf/verifier.c` +2/-6 in
  `check_max_stack_depth_subprog()`; `is_exception_cb` → `is_cb`; remove
  async `verifier_bug`
- **[Phase 2]** Current code at 5480–5482 already has `if
  (subprog[tmp].is_cb)` / `"cannot tail call within callback"`
- **[Phase 3]** `git describe HEAD` / `make kernelversion`:
  **v7.3-rc4**, 7.3.0-rc4
- **[Phase 3]** `git merge-base --is-ancestor a41d0c30d764e HEAD`: yes
  (already in this tree, v7.3-rc1)
- **[Phase 3]** `git merge-base --is-ancestor a41d0c30d764e v7.2`: no
  (7.2 does not have it; not used as a split verdict)
- **[Phase 3]** Parent `3513ea9dab6c1` is series 1/3; `git merge-base
  --is-ancestor 3513ea9dab6c1 HEAD`: yes
- **[Phase 3]** Blame: exception check b9ae0c9dd0aca (2023); async bug
  7ddc80a476c2d (2021); `is_cb` 406a6fa44bfbc / ab5cfac139ab85 (2023)
- **[Phase 3]** Follow-up `266aa4ad0b2e8` already in tree; extra reject
  in `check_helper_call()` at 11320
- **[Phase 4]** `b4 dig -c a41d0c30d764e`: lore msgid
  20260716120157.835937-3
- **[Phase 4]** `b4 dig -a`: v1→v2→v3; v3 applied; v3 added patch 1
  after Sashiko
- **[Phase 4]** `b4 dig -w`: bpf maintainers CC’d (Alexei, Eduard,
  Daniel, Andrii, Björn, Kartikeya)
- **[Phase 4]** mbox: Eduard applied to bpf-next; Sashiko AI concern
  answered by author; no NAK
- **[Phase 4]** WebSearch: Töpel RISC-V thread confirms verifier
  accepted callback→subprog→tailcall
- **[Phase 4]** lore.kernel.org/stable: **UNVERIFIED** (403)
- **[Phase 5]** `check_max_stack_depth()` at 5552 calls this; verifier
  finalize at 21334 on every successful load
- **[Phase 5]** `push_callback_call()` sets `is_cb` at 9847;
  `check_subprogs()` sets `has_tail_call` at 3031
- **[Phase 5]** x86 JIT `emit_prologue_tail_call` uses `%rax` for TCC
  (arch/x86/net/bpf_jit_comp.c:500–526)
- **[Phase 5]** `bpf_loop` behind CAP_BPF (`kernel/bpf/helpers.c:2124`)
- **[Phase 5]** Selftest
  `tools/testing/selftests/bpf/progs/tailcall_callback.c` expects
  `"cannot tail call within callback"`
- **[Phase 6]** Reverse `git apply --check` of this commit: success (fix
  already present)
- **[Phase 8]** Failure mode: infinite tailcall hang, severity HIGH;
  trigger CAP_BPF BPF_PROG_LOAD
- **UNVERIFIED:** Stable mailing-list discussion (HTTP 403)

The local tree is v7.3-rc4. The buggy verifier path exists in this
series, the 8-line reject is a real hang fix, prerequisites are present,
and the change belongs in this tree.

**YES**

 kernel/bpf/verifier.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index b89a5d717e422..5cc3e9a990ef7 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6808,10 +6808,6 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 		if (verifier_bug_if(sidx < 0, env, "callee not found at insn %d", next_insn))
 			return -EFAULT;
 		if (subprog[sidx].is_async_cb) {
-			if (subprog[sidx].has_tail_call) {
-				verifier_bug(env, "subprog has tail_call and async cb");
-				return -EFAULT;
-			}
 			/* async callbacks don't increase bpf prog stack size unless called directly */
 			if (!bpf_pseudo_call(insn + i))
 				continue;
@@ -6852,8 +6848,8 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
 	 */
 	if (tail_call_reachable)
 		for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
-			if (subprog[tmp].is_exception_cb) {
-				verbose(env, "cannot tail call within exception cb\n");
+			if (subprog[tmp].is_cb) {
+				verbose(env, "cannot tail call within callback\n");
 				return -EINVAL;
 			}
 			subprog[tmp].tail_call_reachable = true;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH AUTOSEL 6.18-6.6] xsk: account ring allocations to memcg
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
                   ` (3 preceding siblings ...)
  2026-10-19 13:48 ` [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall Sasha Levin
@ 2026-10-19 13:50 ` Sasha Levin
  2026-10-19 13:52 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_MOVSX in RV32 JIT Sasha Levin
  5 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:50 UTC (permalink / raw)
  To: patches, stable
  Cc: Zihan Xi, Stanislav Fomichev, Jakub Kicinski, Sasha Levin,
	magnus.karlsson, maciej.fijalkowski, davem, edumazet, pabeni,
	netdev, bpf, linux-kernel

From: Zihan Xi <zihanx@nebusec.ai>

[ Upstream commit b0057c68df711bf6a62033c072ac61c4f9d3cbc1 ]

AF_XDP rings are allocated from setsockopt() and can be mapped into user
space.  The shared xskq_create() helper allocates the ring backing memory,
but the user-controlled and long-lived allocation is not charged as kmem
to the allocating memory cgroup.

The current implementation uses vmalloc_user(), which allocates the
backing pages with GFP_KERNEL | __GFP_ZERO.  Use the same VM_USERMAP
vmalloc path, but pass GFP_KERNEL_ACCOUNT so the ring backing pages are
attributed to memcg/kmem and can be constrained by existing cgroup memory
limits.  This keeps the existing zeroing and mmap semantics while avoiding
AF_XDP-specific optmem or RLIMIT_MEMLOCK accounting.

Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Acked-by: Stanislav Fomichev <sdf@fomichev.me>
Link: https://patch.msgid.link/20260731164623.4694-2-zihanx@nebusec.ai
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

The local tree is **6.12.111** (`pending-6.12`). The AF_XDP ring path
still uses unaccounted `vmalloc_user()`, so this is a real memcg/DoS
hole in this checkout, not a no-op.

===============================================================
PHASE 1: COMMIT MESSAGE FORENSICS
===============================================================

**Step 1.1: PARSE THE SUBJECT LINE**
- Subsystem: `xsk` (AF_XDP / `net/xdp`)
- Action verb: **account** (resource-accounting fix, not a refactor)
- Claimed intent: charge AF_XDP ring backing pages to memcg/kmem

Record: `[xsk] [account] charge AF_XDP ring allocations to memcg`

**Step 1.2: PARSE ALL COMMIT MESSAGE TAGS**

From the candidate message:
- `Acked-by: Stanislav Fomichev <sdf@fomichev.me>`
- `Link:
  https://patch.msgid.link/20260731164623.4694-2-zihanx@nebusec.ai`
- `Signed-off-by: Zihan Xi <zihanx@nebusec.ai>`
- `Signed-off-by: Jakub Kicinski <kuba@kernel.org>`
- Ignore: any `Signed-off-by: Sasha Levin` (pipeline)

From the original v3 submission (mailing list, not treated as “already
selected”):
- `Fixes: 423f38329d26 ("xsk: add umem fill queue support and mmap")`
- `Cc: stable@vger.kernel.org`
- `Reported-by: Vega <vega@nebusec.ai>`
- `Assisted-by: Codex:gpt-5.4`

No syzbot, no Tested-by. Absence of Fixes/Cc:stable on the candidate
copy is expected and is not a NO signal.

Record: Ack from XDP/BPF reviewer; original submission had Fixes +
Cc:stable + Reported-by; applied by netdev maintainer.

**Step 1.3: ANALYZE THE COMMIT BODY TEXT**

Bug: `xskq_create()` allocates mmapable ring memory from `setsockopt()`
with `vmalloc_user()` (`GFP_KERNEL | __GFP_ZERO`). That allocation is
user-controlled, long-lived, and **not charged to memcg/kmem**.

Symptom: a task with `CAP_NET_RAW` in a user+net namespace can request
very large RX/TX/fill/completion rings **before bind or packet I/O**.
Cover letter includes a PoC (`unshare -Urn ./poc`, 1<<25 entries) and an
OOM panic on **6.12.95** with `panic_on_oom`.

Root cause: missing `__GFP_ACCOUNT` on the VM_USERMAP vmalloc path.
Author explicitly says panic_on_oom is environment-specific; the defect
is missing kmem attribution.

Record: `[Bug: unaccounted userspace-triggered vmalloc of AF_XDP rings]
[Symptom: memcg-limit bypass / global OOM] [Version: repro on 6.12.95]
[Cause: vmalloc_user uses GFP_KERNEL, not GFP_KERNEL_ACCOUNT]`

**Step 1.4: DETECT HIDDEN BUG FIXES**

Not disguised. It is a missing-accounting fix. Kernel policy in
`Documentation/core-api/memory-allocation.rst` says untrusted userspace
allocations **must** set `__GFP_ACCOUNT`.

Record: `[Not hidden; explicit resource-accounting / isolation bug]`

===============================================================
PHASE 2: DIFF ANALYSIS - LINE BY LINE
===============================================================

**Step 2.1: INVENTORY THE CHANGES**

File: `net/xdp/xsk_queue.c` only (~+11 / -1)

- Add `#include <asm/shmparam.h>`
- Add `xskq_vmalloc_user()`
- In `xskq_create()`: `vmalloc_user(size)` → `xskq_vmalloc_user(size)`

`xskq_destroy()` still `vfree()`s the ring (uncharge is automatic on
page free).

Record: `[1 file, ~12 lines] [xskq_vmalloc_user added; xskq_create
modified] [single-file surgical]`

**Step 2.2: UNDERSTAND THE CODE FLOW CHANGE**

Before: `vmalloc_user()` → `__vmalloc_node_range(..., GFP_KERNEL |
__GFP_ZERO, PAGE_KERNEL, VM_USERMAP, ...)`

After: same VM_USERMAP/SHMLBA/zeroing/mmap path, GFP flag is
`GFP_KERNEL_ACCOUNT | __GFP_ZERO`.

Affected path: `setsockopt(SOL_XDP,
XDP_{RX,TX,UMEM_FILL,UMEM_COMPLETION}_RING)` → `xsk_setsockopt` →
`xsk_init_queue` → `xskq_create`. Normal allocation path, not an error-
path-only change.

Record: `[before: unaccounted vmalloc_user] [after: identical vmalloc,
charged to memcg]`

**Step 2.3: IDENTIFY THE BUG MECHANISM**

Category: resource accounting / isolation (userspace-triggered kernel
memory not charged to kmemcg). Secondary failure mode: OOM /
panic_on_oom.

Mechanism: `GFP_KERNEL` vs `GFP_KERNEL_ACCOUNT` (`GFP_KERNEL |
__GFP_ACCOUNT`). In this tree, `__vmalloc_node_range_noprof()` honors
`__GFP_ACCOUNT` (page alloc + `MEMCG_VMALLOC` stat).

Not UAF, leak, race, or bounds check.

Record: `[resource accounting] [uncharged mmapable ring pages; fix is
GFP_KERNEL_ACCOUNT]`

**Step 2.4: ASSESS THE FIX QUALITY**

Obviously correct relative to `vmalloc_user()` in `mm/vmalloc.c` (same
args except GFP). No new uAPI. `vfree()` already drops the pages, so no
extra unaccount helper (unlike rejected v2 `mm_account_pinned_pages`).

Regression risk: AF_XDP in a **tight memcg** can now get `-ENOMEM` where
it previously escaped the limit. That is the intended isolation. No
lock/API/lifetime change.

Stanislav noted dislike of open-coding `__vmalloc_node_range`; same
pattern already used for mmapable BPF maps after
`vmalloc_user_node_flags` was removed. Acceptable.

Record: `[minimal, matches vmalloc_user] [low regression risk; memcg
ENOMEM is intended]`

===============================================================
PHASE 3: GIT HISTORY INVESTIGATION
===============================================================

**Step 3.1: BLAME THE CHANGED LINES**

- Ring helper/mmap: `423f38329d267` (2018, Magnus Karlsson) — “xsk: add
  umem fill queue support and mmap”
- `vmalloc_user` switch: `9f78bf330a66c` (2023, Xuan Zhuo) — “xsk:
  support use vaddr as ring”
- Overflow guard: `a12bbb3cccf03` (2023)

Buggy missing accounting dates to the original mmapable queue (2018).
`vmalloc_user` only changed the allocator.

Record: `[introduced conceptually in 423f38329d267 (v4.18-era);
vmalloc_user since 9f78bf330a66c / v6.3]`

**Step 3.2: FOLLOW THE FIXES: TAG**

`423f38329d267` **is an ancestor of this HEAD** (`git merge-base --is-
ancestor` exit 0). Original code used `__get_free_pages`; accounting was
missing then too.

Record: `[Fixes target in this tree] [no later in-tree substitute for
ring memcg charge]`

**Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES**

Recent `net/xdp/xsk_queue.c`: overflow check, vmalloc conversion, header
moves. No existing memcg/optmem/RLIMIT charge on rings.

Series history (mailing list): v1 optmem + socket lifetime coupling; v2
RLIMIT_MEMLOCK; **v3 is standalone 1/1** memcg-only.

Record: `[standalone v3; no in-tree prereq refactor]`

**Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS**

Zihan Xi is not an XDP maintainer. Reviewer Stanislav Fomichev is a
regular XDP/BPF reviewer and Acked v3. Applied by Jakub Kicinski.

Record: `[author: reporter/fixer; reviewer: subsystem regular;
maintainer applied]`

**Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS**

Needs `__vmalloc_node_range`, `SHMLBA`, `VM_USERMAP`,
`GFP_KERNEL_ACCOUNT`. All present in 6.12.

Local `xskq_create` uses `kzalloc(sizeof(*q), GFP_KERNEL)` vs mainline
`kzalloc_obj(*q)` — **not in the changed hunks**. Functional diff
applies.

Record: `[no functional dependencies] [applies standalone with trivial
context]`

===============================================================
PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
===============================================================

**Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION**

`b4 dig -c <msgid>` could not match a local commit (patch not in this
tree). `b4 mbox` retrieved the 4-message thread.

- Cover: https://lists.openwall.net/netdev/2026/07/31/395
- Patch: https://lists.openwall.net/netdev/2026/07/31/392
- Review: https://lists.openwall.net/netdev/2026/07/31/437

v1 optmem, v2 RLIMIT_MEMLOCK, v3 memcg. Applied version is v3 (latest).
No NAK. Stanislav Acked. Patchwork-bot accepted it.

Author nominated `Cc: stable` on v3. Reviewer did not object.

Record: `[lore msgid 20260731164623.4694-2] [v1→v3, v3 applied] [Ack, no
NAK] [author Cc:stable]`

**Step 4.2: CHECK WHO REVIEWED THE PATCH**

To/Cc: netdev, bpf, Magnus Karlsson, Maciej Fijalkowski, Stanislav
Fomichev, davem, edumazet, pabeni, horms, ast, daniel, hawk,
john.fastabend.

Stanislav reviewed v2 (pushed memcg over RLIMIT_MEMLOCK) and Acked v3.

Record: `[Stanislav Fomichev Ack; XDP maintainers were CC'd]`

**Step 4.3: SEARCH FOR THE BUG REPORT**

Internal report (`Reported-by: Vega`) plus full PoC and panic log on
6.12.95. Trigger: `socket(AF_XDP)` + `setsockopt` with 2^25 entries,
`unshare -Urn`. No syzbot/bugzilla.

Record: `[reproducible OOM/panic on 6.12.95; userns+CAP_NET_RAW]`

**Step 4.4: CHECK FOR RELATED PATCHES AND SERIES**

v3 is 0/1 + 1/1. No extra patches required. v1/v2 approaches were
dropped.

Record: `[self-contained; earlier series versions superseded]`

**Step 4.5: CHECK STABLE MAILING LIST HISTORY**

No useful hit on lore.kernel.org/stable for this subject. No recorded
rejection for 6.12.y.

Record: `[no stable-list rejection found]`

===============================================================
PHASE 5: CODE SEMANTIC ANALYSIS
===============================================================

**Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF**

Record: `[xskq_vmalloc_user (new), xskq_create (modified)]`

**Step 5.2: TRACE CALLERS**

`xskq_create` is only called from `xsk_init_queue()` in `net/xdp/xsk.c`,
from `xsk_setsockopt` for the four ring options. Process context,
syscall.

`xsk_create()` requires `ns_capable(net->user_ns, CAP_NET_RAW)` —
**available to an unprivileged user inside a user namespace**.

Record: `[setsockopt path; userns CAP_NET_RAW]`

**Step 5.3: TRACE CALLEES**

`__vmalloc_node_range` → page alloc with GFP; `vfree` on destroy. No new
locks.

Record: `[vmalloc/vfree only]`

**Step 5.4: FOLLOW THE CALL CHAIN**

`socket(AF_XDP)` → `setsockopt(SOL_XDP, XDP_*_RING, &entries)` →
`xskq_create`.

`xsk_init_queue` only checks `entries != 0`, power of two, and queue not
already set. **No upper bound.** `xdp_desc` is 16 bytes; 2^25 entries ≈
512 MiB per RX/TX ring (fill/completion similar with `u64` descs). Eight
sockets × four rings is multi-GB kernel vmalloc.

Reachable from userspace: **yes**.

Record: `[userspace-reachable unbounded vmalloc]`

**Step 5.5: SEARCH FOR SIMILAR PATTERNS**

UMEM pages are charged via `user->locked_vm`
(`xdp_umem_unaccount_pages`). BPF mmapable maps use
`__vmalloc_node_range` with accounting flags. Rings were the hole.

`vmalloc_user()` elsewhere (kcov, perf, videobuf) is a separate
question; this patch only fixes AF_XDP.

Record: `[UMEM already bounded; rings were not; BPF already accounts
mmapable vmalloc]`

===============================================================
PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE
===============================================================

**Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?**

Yes. `net/xdp/xsk_queue.c:49` is `q->ring = vmalloc_user(size);`. `make
kernelversion` = **6.12.111**. Repro log is 6.12.95. `423f38329d267` and
`9f78bf330a66c` are ancestors. No `xskq_vmalloc_user` /
`GFP_KERNEL_ACCOUNT` in `net/xdp`.

Record: `[buggy code present; bug since 2018 / vmalloc path since ~6.3]`

**Step 6.2: CHECK FOR BACKPORT COMPLICATIONS**

Expected apply: **clean / trivial context**. Only surrounding difference
is `kzalloc` vs `kzalloc_obj`. `asm/shmparam.h` exists
(`arch/x86/include/asm/shmparam.h`, `include/asm-generic/shmparam.h`).
`__vmalloc_node_range` macro exists in `include/linux/vmalloc.h`.

Record: `[clean apply or one-hunk context tweak]`

**Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE**

No memcg/optmem/RLIMIT accounting on ring create in this tree.

Record: `[no substitute fix present]`

===============================================================
PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
===============================================================

**Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY**

`net/xdp` — AF_XDP. Config: `CONFIG_XDP_SOCKETS` (depends on
`BPF_SYSCALL`, default n).

Criticality: **IMPORTANT** (not core mm/VFS). Used for high-performance
networking; also a userns attack surface when enabled.

Record: `[net/xdp AF_XDP] [IMPORTANT]`

**Step 7.2: ASSESS SUBSYSTEM ACTIVITY**

Mature since 2018; ongoing XDP work. This is an old hole, not a bug in
brand-new code.

Record: `[mature; hole is long-standing]`

===============================================================
PHASE 8: IMPACT AND RISK ASSESSMENT
===============================================================

**Step 8.1: DETERMINE WHO IS AFFECTED**

Config-specific (`CONFIG_XDP_SOCKETS`) plus anyone who can create AF_XDP
sockets. Isolation impact is highest for **containers / memcg-limited
workloads** on LTS kernels.

Record: `[config-specific AF_XDP; high impact for cgroup/container
hosts]`

**Step 8.2: DETERMINE THE TRIGGER CONDITIONS**

`setsockopt` of a large power-of-two ring. No bind, no traffic.
Unprivileged in a user ns with `CAP_NET_RAW`. Easy and deterministic.

Caveat (verified from author): **without a memcg limit**,
`GFP_KERNEL_ACCOUNT` does not stop global OOM (`CONSTRAINT_NONE` in
their prototype). Enforcement is cgroup memory policy.

Record: `[easy syscall trigger] [fix enforces memcg, not a global hard
cap]`

**Step 8.3: DETERMINE THE FAILURE MODE SEVERITY**

- With memcg limits (typical k8s/LTS): bypass of memory.max / kmem →
  **container isolation hole** (HIGH/CRITICAL for multi-tenant)
- With panic_on_oom: **kernel panic** (CRITICAL in that config)
- Without memcg limits: still global OOM killer after the patch

Record: `[memcg bypass + OOM] [HIGH; CRITICAL under panic_on_oom /
multi-tenant]`

**Step 8.4: CALCULATE RISK-BENEFIT RATIO**

Benefit: closes documented-required kmem accounting; lets existing
cgroup limits constrain a userspace-triggered multi-hundred-MB vmalloc;
matches BPF-style mmapable alloc policy.

Risk: ~12 lines, GFP flag only, one file. Possible `-ENOMEM` for
oversized rings inside a small memcg — correct.

Record: `[benefit high for container/LTS] [risk very low] [favorable]`

===============================================================
PHASE 9: FINAL SYNTHESIS
===============================================================

**Step 9.1: COMPILE THE EVIDENCE**

FOR:
- Real, documented policy violation: untrusted userspace alloc without
  `__GFP_ACCOUNT`
- Concrete PoC and panic on **6.12.95**, this series
- Reachable via userns `CAP_NET_RAW` + `setsockopt`, no bind needed
- Unbounded ring size (power-of-two only)
- Small, obviously equivalent to `vmalloc_user` + account flag
- APIs and buggy code exist in this 6.12.111 tree
- Acked by Stanislav Fomichev; applied by Jakub Kicinski
- Standalone (v3 dropped optmem/RLIMIT machinery)
- UMEM already resource-bounded; rings were the gap
- Author asked for stable; reviewer did not object

AGAINST:
- Does **not** by itself stop global OOM if there is no memcg limit
  (author confirmed)
- `CONFIG_XDP_SOCKETS` is not default-on
- Open-codes `__vmalloc_node_range` (reviewer accepted)
- Can change success/failure of huge rings inside tight memcgs

UNRESOLVED:
- Exact mainline SHA of the applied commit (not in this checkout; thread
  was retrieved by msgid)
- No stable-list discussion found

**Step 9.2: APPLY THE STABLE RULES CHECKLIST**

1. Obviously correct and tested? **PASS** — same vmalloc path as
   `vmalloc_user`; PoC/discussion; maintainer Ack
2. Fixes a real bug that affects users? **PASS** — missing kmem charge;
   OOM/isolation on 6.12
3. Important issue? **PASS** — memcg isolation hole and OOM
   (panic_on_oom in the report)
4. Small and contained? **PASS** — one file, ~12 lines
5. No new features or APIs? **PASS** — GFP flag only, not a new uAPI
6. Can apply to the local tree? **PASS** — code and helpers present

**Step 9.3: CHECK FOR EXCEPTION CATEGORIES**

Not a device-ID/quirk/DT/docs exception. Ordinary resource-accounting
bug fix.

Record: `[no exception category; qualifies on primary rules]`

**Step 9.4: MAKE YOUR DECISION**

This is a small, correct fix for a userspace-reachable hole that this
6.12 tree still has. LTS hosts that run AF_XDP and cgroups (containers)
need the charge so memory.max actually applies. Incomplete as a *global*
OOM cap is not a reason to keep rings unaccounted; kernel policy already
requires `__GFP_ACCOUNT`, and that is exactly what this patch adds.

===============================================================
DETAILED EXPLANATION
===============================================================

**Problem:** `xskq_create()` backs AF_XDP rings with `vmalloc_user()`
(`GFP_KERNEL`). `setsockopt()` can request an arbitrary power-of-two
entry count with no memcg charge. In a user namespace that grant
`CAP_NET_RAW`, that is a large, long-lived kernel allocation that
bypasses cgroup kmem limits. The report includes a panic on 6.12.95
under `panic_on_oom`.

**Why it matters here:** This checkout is 6.12.111; the `vmalloc_user()`
call is still present. 6.12.y is an LTS line used with cgroups. Charging
the pages is what makes existing memory limits apply to this path.

**Rules:** Surgical, no new API, no series prereq, applies with at most
trivial `kzalloc` context. Reviewer-directed design (memcg, not
optmem/RLIMIT).

**Risk vs benefit:** Benefit is isolation/OOM under memcg; risk is a GFP
flag on one alloc. `vfree()` already releases pages. The remaining gap
(no memcg → still global OOM) is a property of memcg, not a reason to
skip the charge.

**Concerns:** Incomplete without cgroup limits; XDP sockets are
optional. Neither outweighs backporting the accounting the kernel
already requires for untrusted userspace allocations.

===============================================================
Verification:
===============================================================
- [Phase 1] Parsed subject/body/tags from the candidate message;
  mailing-list v3 also had Fixes: 423f38329d26, Cc: stable, Reported-by:
  Vega
- [Phase 2] Diff is one file: helper wrapping `__vmalloc_node_range`
  with `GFP_KERNEL_ACCOUNT | __GFP_ZERO`, VM_USERMAP, SHMLBA — compared
  to `vmalloc_user_noprof()` in mm/vmalloc.c:4063-4068
- [Phase 2] Confirmed `GFP_KERNEL_ACCOUNT` is `GFP_KERNEL |
  __GFP_ACCOUNT` in include/linux/gfp_types.h:379
- [Phase 2] Confirmed vmalloc honors `__GFP_ACCOUNT` in
  mm/vmalloc.c:3734-3738
- [Phase 3] `git blame net/xdp/xsk_queue.c`: vmalloc_user from
  9f78bf330a66c; helper from 423f38329d267
- [Phase 3] `git merge-base --is-ancestor 423f38329d267 HEAD` → 0;
  `9f78bf330a66c` → 0
- [Phase 3] `git log --oneline -20 -- net/xdp/xsk_queue.c`: no memcg
  accounting already present
- [Phase 3] No `GFP_KERNEL_ACCOUNT` under net/xdp in this tree
- [Phase 4] `b4 dig -c <msgid>`: no local commit match; `b4 mbox` saved
  4-message thread
- [Phase 4] Thread: v3 patch, Stanislav Ack, patchwork-bot, cover
  letter; no NAK
- [Phase 4] Openwall cover+review: PoC, 6.12.95 panic, memcg vs RLIMIT
  debate
- [Phase 4] WebSearch of lore.kernel.org/stable: no rejection thread
  found (UNVERIFIED: lore search 403 on some URLs)
- [Phase 5] Callers: only `xsk_init_queue` ← `xsk_setsockopt` for four
  ring optnames
- [Phase 5] `xsk_create`: `ns_capable(net->user_ns, CAP_NET_RAW)` at
  net/xdp/xsk.c:1742
- [Phase 5] `xsk_init_queue`: no max entries, only power-of-two
  (net/xdp/xsk.c:1061-1062)
- [Phase 5] `struct xdp_desc` is 16 bytes
  (include/uapi/linux/if_xdp.h:155-159)
- [Phase 6] `git describe HEAD` → p-6.12-2-gd38d58a4e0359; `make
  kernelversion` → 6.12.111
- [Phase 6] Current file still has `q->ring = vmalloc_user(size)` at
  line 49
- [Phase 6] `__vmalloc_node_range` and `asm/shmparam.h` exist in this
  tree
- [Phase 7] CONFIG_XDP_SOCKETS in net/xdp/Kconfig, default n, depends on
  BPF_SYSCALL
- [Phase 8] UMEM uses locked_vm in xdp_umem.c; rings do not
- [Phase 8] Documentation/core-api/memory-allocation.rst:53-56 requires
  __GFP_ACCOUNT for untrusted userspace allocs
- UNVERIFIED: mainline commit SHA (patch not in this checkout)
- UNVERIFIED: whether a later mainline follow-up added a hard ring-size
  cap (not in this tree)

The patch is a small, correct memcg charge for a userspace-reachable
AF_XDP allocation that this 6.12 tree still leaves unaccounted.

**YES**

 net/xdp/xsk_queue.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/net/xdp/xsk_queue.c b/net/xdp/xsk_queue.c
index d2c2640300171..7069830943ea8 100644
--- a/net/xdp/xsk_queue.c
+++ b/net/xdp/xsk_queue.c
@@ -9,6 +9,8 @@
 #include <linux/vmalloc.h>
 #include <net/xdp_sock_drv.h>
 
+#include <asm/shmparam.h>
+
 #include "xsk_queue.h"
 
 static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue)
@@ -21,6 +23,14 @@ static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue)
 	return struct_size(rxtx_ring, desc, q->nentries);
 }
 
+static void *xskq_vmalloc_user(unsigned long size)
+{
+	return __vmalloc_node_range(size, SHMLBA, VMALLOC_START, VMALLOC_END,
+				     GFP_KERNEL_ACCOUNT | __GFP_ZERO, PAGE_KERNEL,
+				     VM_USERMAP, NUMA_NO_NODE,
+				     __builtin_return_address(0));
+}
+
 struct xsk_queue *xskq_create(u32 nentries, bool umem_queue)
 {
 	struct xsk_queue *q;
@@ -46,7 +56,7 @@ struct xsk_queue *xskq_create(u32 nentries, bool umem_queue)
 
 	size = PAGE_ALIGN(size);
 
-	q->ring = vmalloc_user(size);
+	q->ring = xskq_vmalloc_user(size);
 	if (!q->ring) {
 		kfree(q);
 		return NULL;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_MOVSX in RV32 JIT
       [not found] <20261019135441.1420968-1-sashal@kernel.org>
                   ` (4 preceding siblings ...)
  2026-10-19 13:50 ` [PATCH AUTOSEL 6.18-6.6] xsk: account ring allocations to memcg Sasha Levin
@ 2026-10-19 13:52 ` Sasha Levin
  5 siblings, 0 replies; 7+ messages in thread
From: Sasha Levin @ 2026-10-19 13:52 UTC (permalink / raw)
  To: patches, stable
  Cc: Kuan-Wei Chiu, Pu Lehui, Kumar Kartikeya Dwivedi, Sasha Levin,
	ast, daniel, andrii, eddyz87, luke.r.nels, xi.wang, pjw, palmer,
	aou, bpf, linux-riscv, linux-kernel

From: Kuan-Wei Chiu <visitorckw@gmail.com>

[ Upstream commit c6a08afdfe3a81b1a54e921921e9481dd6c78f8a ]

The current rv32 bpf jit compiler incorrectly treats BPF_MOVSX as a
standard zero-extended move operation. The bpf instruction set allows
sign-extension moves by reusing the BPF_MOV opcode with the instruction
offset set to 8, 16, or 32.

Update the bpf_jit_emit_insn() function to check the offset field for
both ALU and ALU64 MOV operations. If the offset is non-zero, emit the
correct slli and srai instructions to perform the sign extension.

Before this patch:
[   19.549705] test_bpf: #82 ALU_MOVSX | BPF_B jited:1 ret 2 != 1 (0x2 != 0x1)FAIL (1 times)
[   19.551354] test_bpf: #83 ALU_MOVSX | BPF_H jited:1 ret 2 != 1 (0x2 != 0x1)FAIL (1 times)
[   19.552576] test_bpf: #84 ALU64_MOVSX | BPF_B jited:1 ret 2 != 1 (0x2 != 0x1)FAIL (1 times)
[   19.553542] test_bpf: #85 ALU64_MOVSX | BPF_H jited:1 ret 2 != 1 (0x2 != 0x1)FAIL (1 times)
[   19.554807] test_bpf: #86 ALU64_MOVSX | BPF_W jited:1 ret 2 != 1 (0x2 != 0x1)FAIL (1 times)

After this patch:
[   17.931172] test_bpf: #82 ALU_MOVSX | BPF_B jited:1 125 PASS
[   17.932198] test_bpf: #83 ALU_MOVSX | BPF_H jited:1 124 PASS
[   17.933039] test_bpf: #84 ALU64_MOVSX | BPF_B jited:1 124 PASS
[   17.933918] test_bpf: #85 ALU64_MOVSX | BPF_H jited:1 124 PASS
[   17.934751] test_bpf: #86 ALU64_MOVSX | BPF_W jited:1 122 PASS

Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
Link: https://lore.kernel.org/bpf/20260714002451.4091139-3-visitorckw@gmail.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

This is a real RV32 eBPF JIT miscompilation of an existing CPU-v4
instruction, not a new userspace API. On this 7.3-rc4 tree the RV32 JIT
and BPF_MOVSX ISA are both present, the patch is small and tested, and
it should be included.

## PHASE 1: COMMIT MESSAGE FORENSICS

**Step 1.1: PARSE THE SUBJECT LINE**
Record: [riscv, bpf] [Add support] [RV32 JIT was compiling BPF_MOVSX as
a plain zero-extending MOV; emit slli/srai sign-extend when insn->off is
8/16/32]

**Step 1.2: PARSE ALL COMMIT MESSAGE TAGS**
Record:
- Reviewed-by: Pu Lehui <pulehui@huawei.com> (RISC-V BPF JIT reviewer;
  also authored RV64 MOVSX in 694896ad3ca79)
- Link: https://lore.kernel.org/bpf/20260714002451.4091139-3-
  visitorckw@gmail.com
- Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com> (author)
- Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> (BPF
  maintainer, applied to bpf-next)
- No Fixes:, no Reported-by, no Cc: stable, no syzbot (absence expected)
- Ignore pipeline SOBs/Upstream markers: none present on the real commit
  c6a08afdfe3a8

**Step 1.3: ANALYZE THE COMMIT BODY TEXT**
Record:
- Bug: RV32 JIT treats BPF_MOVSX as a normal zero-extended move. BPF
  reuses BPF_MOV with off=8/16/32 for sign-extending moves.
- Symptom: test_bpf cases #82–#86 fail with `jited:1 ret 2 != 1` (JIT
  ran, produced the wrong return).
- After: those five tests PASS.
- Root cause: `bpf_jit_emit_insn()` did not inspect `insn->off` on
  ALU/ALU64 MOV|X, so it fell through into `emit_alu_r64`/`emit_alu_r32`
  BPF_MOV (plain copy).
- No version range in the message. ISA support dates to v6.6-era
  (8100928c88148, 2023).

**Step 1.4: DETECT HIDDEN BUG FIXES**
Record: Yes. The subject says “Add support”, but the body is explicit
miscompilation. Cover letter for the series: the JIT “ignor[es] the
instruction offset field and treat[s] them as unsigned or zero-
extended.” v1 was titled “Fix support…”. v2 added Fixes: tags; v3
“Remove the Fixes: tag and update title” for bpf-next process, not
because it stopped being a bug. Unlike patch 3/3 (atomics), which *does*
fall back to the interpreter, MOVSX is a supported opcode that emits the
wrong code.

## PHASE 2: DIFF ANALYSIS

**Step 2.1: INVENTORY THE CHANGES**
Record:
- File: `arch/riscv/net/bpf_jit_comp32.c` (+32 / −0)
- Function: `bpf_jit_emit_insn()` only
- Scope: single-file surgical JIT codegen

**Step 2.2: UNDERSTAND THE CODE FLOW CHANGE**
Record:
- Hunk 1 (`BPF_ALU64 | BPF_MOV | BPF_X`): before, always fell through to
  `emit_alu_r64()` which copies lo/hi. After, if `off != 0`, sign-extend
  lo (slli/srai 24 for byte, 16 for half, or copy lo for word) then
  `srai hi, lo, 31` to fill the upper 32 bits of the RV32 register pair.
- Hunk 2 (`BPF_ALU | BPF_MOV | BPF_X`): after the existing `imm == 1`
  zext special case, if `off != 0` sign-extend lo for off 8 or 16, then
  `bpf_put_reg32()` (which still zero-extends hi unless
  `verifier_zext`).
- Normal `off == 0` MOV is unchanged (`fallthrough`).

**Step 2.3: IDENTIFY THE BUG MECHANISM**
Record: [Logic / correctness — JIT miscompilation]
- Broken: BPF_MOVSX shares the MOV opcode; offset was ignored, so sign-
  extend became a full register copy. test_bpf then compares against the
  sign-extended expected value and fails (`ret 2`).
- Fix: emit the same slli/srai pattern other 32-bit JITs use (see arm32
  `emit_a32_movsx_r64`).

**Step 2.4: ASSESS THE FIX QUALITY**
Record: Obviously correct relative to the ISA and to RV64 (`switch
(insn->off)` at bpf_jit_comp64.c:1408). Minimal. Regression risk is low:
only the previously-wrong `off != 0` path changes. ALU32 does not handle
off==32, which matches the ISA (MOVSX32 is 8 or 16 only). ALU64’s `else`
treats any other non-zero off as word sign-extend (off==32); the
verifier should not emit other values. No API/data-structure change.

## PHASE 3: GIT HISTORY INVESTIGATION

**Step 3.1: BLAME THE CHANGED LINES**
Record: The MOV|X fallthrough and `emit_alu_r64` BPF_MOV copy were
introduced in 5f316b65e99f1 (“riscv, bpf: Add RV32G eBPF JIT”, 2020,
v5.6). That was correct until BPF_MOVSX existed. ISA commit
8100928c88148 (“bpf: Support new sign-extension mov insns”,
v6.5-rc2-583, in 6.6). RV64 JIT got MOVSX in 694896ad3ca79 (2023). RV32
was left emitting the old MOV path until this commit.

**Step 3.2: FOLLOW THE FIXES: TAG**
Record: No Fixes: on the landed commit. v2 of the series had Fixes:
tags; they were dropped in v3 when the series was retitled for bpf-next.
The semantically “buggy” point is 8100928c88148 (MOVSX added, RV32 JIT
not updated). That commit is an ancestor of this tree.

**Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES**
Record: Same-day series on the same file:
- 2e0fa2389c50f — BPF_SDIV/SMOD (patch 1/3)
- c6a08afdfe3a8 — this commit (patch 2/3)
- a1b37972efc0b — 32-bit atomics (patch 3/3)
MOVSX does not use the SDIV `emit_alu_r32()` signature change and does
not use the atomic helper. Standalone.

**Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS**
Record: Kuan-Wei Chiu’s only RV32 JIT work in this tree is this 3-patch
series. Reviewer Pu Lehui maintains RISC-V BPF JIT (RV64 MOVSX, later
kfunc sign-extend fix f2aaa62159109). Applied by Kartikeya. Strong
maintainer path.

**Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS**
Record: Uses `bpf_get_reg64`/`bpf_put_reg64`/`bpf_get_reg32`/`bpf_put_re
g32`/`rv_slli`/`rv_srai`/`rv_addi` — all from the original 2020 JIT. No
new helpers. Can apply standalone.

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

**Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION**
Record:
- b4 dig:
  https://patch.msgid.link/20260714002451.4091139-3-visitorckw@gmail.com
- Series: bpf-next v1→v4, “signed operations and 32-bit atomics”
- v1 (2026-04-29): “Fix support for BPF_SDIV and BPF_SMOD”
- v2: “Add missing Fixes tags”
- v3: “Remove the Fixes: tag and update title”
- v4 (applied): this version
- No NAKs. Pu Lehui Reviewed-by on this patch; Tested-by on the series
  (test_bpf failures drop).
- No reviewer asked for Cc: stable.

**Step 4.2: CHECK WHO REVIEWED THE PATCH**
Record: To/Cc included bpf@vger, linux-riscv, ast, daniel, andrii,
eddyz87, memxor, palmer, aou, pulehui, yonghong.song (original MOVSX ISA
author), alex@ghiti.fr. Appropriate lists. Applied by Kartikeya to bpf-
next.

**Step 4.3: SEARCH FOR THE BUG REPORT**
Record: No external bugzilla/syzbot. Evidence is in-tree
`lib/test_bpf.c` ALU_MOVSX tests (from daabb2b098e04, cpuv4 tests).
Failure is `jited:1` with wrong R0.

**Step 4.4: CHECK FOR RELATED PATCHES AND SERIES**
Record: 3/3 series. 1/3 is the same class of bug (SDIV/SMOD as
unsigned). 3/3 is a feature (atomics already interpreter-fallback). This
patch does not depend on either.

**Step 4.5: CHECK STABLE MAILING LIST HISTORY**
Record: lore.kernel.org returned 403 to WebFetch. b4 mbox of the matched
thread has no stable@ discussion. UNVERIFIED: no independent
confirmation from lore.kernel.org/stable/.

## PHASE 5: CODE SEMANTIC ANALYSIS

**Step 5.1: KEY FUNCTIONS**
Record: `bpf_jit_emit_insn()` (modified). Helpers used, not modified:
`bpf_get_reg64`, `bpf_put_reg64`, `bpf_get_reg32`, `bpf_put_reg32`,
`emit`.

**Step 5.2: TRACE CALLERS**
Record: `build_body()` in `arch/riscv/net/bpf_jit_core.c` calls
`bpf_jit_emit_insn()` for every instruction when a program is JIT-
compiled. That is the eBPF load/attach path (tracing, networking,
cgroup, seccomp, etc.), not init-only.

**Step 5.3: TRACE CALLEES**
Record: Emits RISC-V ALU (slli/srai/addi) and the existing stacked-
register get/put. No allocation, no locking.

**Step 5.4: FOLLOW THE CALL CHAIN**
Record: userspace bpf(2) / `BPF_PROG_LOAD` → verifier (accepts MOVSX
since 8100928c88148) → `bpf_int_jit_compile` → `build_body` →
`bpf_jit_emit_insn`. RISC-V selects `HAVE_EBPF_JIT if MMU`. JIT is used
when enabled; tests ran with `jited:1`. Reachable from program load.
Typically CAP_BPF/CAP_SYS_ADMIN; not a syzbot-style unprivileged UAF,
but verifier/JIT mismatch is still a sandbox correctness issue.

**Step 5.5: SEARCH FOR SIMILAR PATTERNS**
Record: RV64 already handled off 8/16/32 (694896ad3ca79). arm32:
fc832653fa0db then c6f48506ba30c. LoongArch had a later “Fix sign-
extension mov” (772cbe948fb07). Same bug class: MOV|X with nonzero off
must sign-extend.

## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE

**Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?**
Record:
- Local tree: v7.3-rc4-27-gf0100363d8c37, `VERSION=7 PATCHLEVEL=3
  SUBLEVEL=0 EXTRAVERSION=-rc4`.
- RV32 JIT file exists; MOVSX macros exist in `include/linux/filter.h`;
  tests exist in `lib/test_bpf.c`.
- Commit c6a08afdfe3a8 *is* an ancestor of HEAD (landed in 7.3 via `bpf-
  next-7.3`, merge 5a8cd539ac19f). Not in v7.2.
- Without this change, the 2020 MOV fallthrough is still the codegen for
  MOVSX. The bug is in this lineage from 6.6 through 7.2.

**Step 6.2: CHECK FOR BACKPORT COMPLICATIONS**
Record: Clean apply on this tree. Surrounding MOV cases and get/put
helpers match the patch. Expected difficulty: clean apply.

**Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE**
Record: This is the RV32 MOVSX fix. No earlier substitute on
bpf_jit_comp32.c. RV64 already had support. No competing fix.

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

**Step 7.1: IDENTIFY THE SUBSYSTEM**
Record: [arch/riscv BPF JIT] [IMPORTANT for RV32 + BPF users; PERIPHERAL
vs core mm/net — 32-bit RISC-V is a real but smaller population]

**Step 7.2: ASSESS SUBSYSTEM ACTIVITY**
Record: Active enough (atomics, arena, CFI, kfunc sign-extend) but the
MOV path itself was untouched since 2020. Long-standing gap, not a
brand-new subsystem.

## PHASE 8: IMPACT AND RISK ASSESSMENT

**Step 8.1: WHO IS AFFECTED**
Record: RV32 kernels with eBPF JIT (HAVE_EBPF_JIT && MMU) running
programs that use CPU-v4 sign-extending moves (clang casts to
s8/s16/s32). Not universal; not staging.

**Step 8.2: TRIGGER CONDITIONS**
Record: Loading/attaching a BPF program that contains MOVSX, with JIT
on. Common for cpu v4 compiles. Privileged load in default configs. Not
a timing race. test_bpf reproduces it every run.

**Step 8.3: FAILURE MODE SEVERITY**
Record: [Wrong JIT results / verifier-vs-JIT mismatch] [HIGH]
- Not an oops/deadlock.
- JIT returns different register values than the interpreter and than
  the verifier’s sign-extended ranges (test: expected 1, got 2).
- If the verifier pruned a bounds check on the assumption that MOVSX
  ran, runtime can see a non-sign-extended value. That is BPF sandbox
  incorrectness, not a cosmetic test fail.

**Step 8.4: RISK-BENEFIT RATIO**
Record: Benefit high for RV32+BPF (correct JIT, closes verifier
mismatch). Risk very low (32 lines, one switch, existing emit helpers,
test_bpf before/after in the message, RISC-V BPF reviewer Tested-by).
Ratio favors taking it.

## PHASE 9: FINAL SYNTHESIS

**Step 9.1: COMPILE THE EVIDENCE**

FOR:
- Concrete miscompilation: JIT runs and produces the wrong value
  (`jited:1 ret 2 != 1`).
- BPF_MOVSX has been in the ISA/verifier/interpreter since ~6.6; RV32
  JIT never honored `insn->off`.
- Interpreter is correct; only the JIT is wrong — classic “supported
  opcode, wrong codegen.”
- 32-line, single-function, no new APIs.
- Reviewed and tested by Pu Lehui; applied by BPF maintainer.
- Independent of the other two series patches.
- Helpers and MOV cases exist in this tree since 5.6.

AGAINST:
- Subject/process treated it as bpf-next “Add support”; Fixes: tags were
  deliberately removed in v3.
- RV32 is a smaller user base than RV64.
- Failure is wrong results, not a panic.
- Series patch 3/3 is a genuine feature (do not bundle it).

UNRESOLVED:
- Could not fetch lore.kernel.org/stable/ (HTTP 403). Thread mbox had no
  stable discussion.

**Step 9.2: STABLE RULES CHECKLIST**
1. Obviously correct and tested? **PASS** — slli/srai matches the ISA;
   test_bpf #82–#86; reviewer Tested-by.
2. Fixes a real bug that affects users? **PASS** — JIT’d MOVSX is wrong
   on RV32; userspace can load such programs.
3. Important issue? **PASS** — verifier/JIT mismatch on sign extension
   is sandbox/correctness, not a style issue. Severity HIGH, not a dead-
   code WARN.
4. Small and contained? **PASS** — +32 lines, one file, one function.
5. No new features or APIs? **PASS** — does not add a new opcode to BPF;
   it compiles an opcode the verifier already accepts. (Title is “Add
   support”; semantically it is fixing ignored `off`.)
6. Can apply to the local tree? **PASS** — file, helpers, and MOV cases
   are here.

**Step 9.3: CHECK FOR EXCEPTION CATEGORIES**
Record: Not a device-ID/quirk/DT/build/docs exception. Ordinary
correctness fix.

**Step 9.4: DECISION**
This tree is 7.3-rc4: it has the RV32 JIT and BPF_MOVSX. The bug is
“MOV|X ignores off,” which is user-visible wrong BPF execution and a
verifier/JIT disagreement. The patch is small, obviously correct,
tested, and self-contained. bpf-next dropping Fixes: is process, not a
finding that the codegen was fine. It should be backported to this tree.

### What it solves for stable users
RV32 eBPF JIT compiled sign-extending moves as ordinary copies. Any
JIT’d program using BPF_MOVSX (cpu v4 / compiler casts) gets wrong
register values. That breaks test_bpf and can disagree with verifier
range tracking.

### Risk vs benefit
Benefit: correct JIT and closed verifier mismatch on a real ISA
instruction. Risk: very low, localized ALU emit.

### Concerns
Do not pull series 3/3 (atomics feature). 1/3 (SDIV/SMOD) is the same
miscompilation class but is not required for this patch. No backport
rework needed on this tree.

Verification:
- [Phase 1] Tags: Reviewed-by Pu Lehui, Link lore/bpf, SOB author +
  Kartikeya; no Fixes/syzbot/Cc:stable
- [Phase 1] Body: test_bpf #82–#86 `jited:1 ret 2 != 1` before, PASS
  after
- [Phase 2] Diff: +32 in `bpf_jit_emit_insn()`, two MOV|X cases,
  slli/srai sign-extend
- [Phase 2] Confirmed `emit_alu_r64` BPF_MOV is lo/hi copy;
  `emit_alu_r32` BPF_MOV is lo copy (pre-fix path)
- [Phase 3] blame: MOV fallthrough from 5f316b65e99f1 (v5.6); ISA
  8100928c88148 (~6.6); RV64 694896ad3ca79
- [Phase 3] `git merge-base --is-ancestor c6a08afdfe3a8 HEAD` yes; not
  ancestor of v7.2; is ancestor of v7.3-rc1
- [Phase 3] Series patches 2e0fa2389c50f / c6a08afdfe3a8 /
  a1b37972efc0b; this one independent
- [Phase 4] `b4 dig -c c6a08afdfe3a8`: v4 2/3, msgid
  20260714002451.4091139-3
- [Phase 4] `b4 dig -a`: v1–v4; v3 removed Fixes: and retitled
- [Phase 4] `b4 dig -m`: 7 messages; Pu Lehui R-b/T-b; no NAK; no stable
  Cc
- [Phase 4] WebFetch lore: 403 — UNVERIFIED via HTTP; thread recovered
  via b4 mbox
- [Phase 5] Caller: `build_body()` → every JIT’d insn; `HAVE_EBPF_JIT if
  MMU`
- [Phase 5] Tests in lib/test_bpf.c ALU_MOVSX / ALU64_MOVSX
- [Phase 6] `git describe HEAD` = v7.3-rc4-27-gf0100363d8c37; `make
  kernelversion` = 7.3.0-rc4
- [Phase 6] bpf_jit_comp32.c and BPF_MOVSX64_REG present; helpers from
  original JIT
- [Phase 8] Failure: wrong JIT output / verifier mismatch, HIGH; not a
  panic
- UNVERIFIED: stable@ mailing list archive (HTTP 403)

**YES**

 arch/riscv/net/bpf_jit_comp32.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/arch/riscv/net/bpf_jit_comp32.c b/arch/riscv/net/bpf_jit_comp32.c
index 89153946a4e46..39e2b0b907dc3 100644
--- a/arch/riscv/net/bpf_jit_comp32.c
+++ b/arch/riscv/net/bpf_jit_comp32.c
@@ -972,6 +972,24 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 
 	switch (code) {
 	case BPF_ALU64 | BPF_MOV | BPF_X:
+		if (insn->off != 0) {
+			const s8 *rd = bpf_get_reg64(dst, tmp1, ctx);
+			const s8 *rs = bpf_get_reg64(src, tmp2, ctx);
+
+			if (insn->off == 8) {
+				emit(rv_slli(lo(rd), lo(rs), 24), ctx);
+				emit(rv_srai(lo(rd), lo(rd), 24), ctx);
+			} else if (insn->off == 16) {
+				emit(rv_slli(lo(rd), lo(rs), 16), ctx);
+				emit(rv_srai(lo(rd), lo(rd), 16), ctx);
+			} else {
+				emit(rv_addi(lo(rd), lo(rs), 0), ctx);
+			}
+			emit(rv_srai(hi(rd), lo(rd), 31), ctx);
+			bpf_put_reg64(dst, rd, ctx);
+			break;
+		}
+		fallthrough;
 
 	case BPF_ALU64 | BPF_ADD | BPF_X:
 	case BPF_ALU64 | BPF_ADD | BPF_K:
@@ -1022,6 +1040,20 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
 			emit_zext64(dst, ctx);
 			break;
 		}
+		if (insn->off != 0) {
+			const s8 *rd = bpf_get_reg32(dst, tmp1, ctx);
+			const s8 *rs = bpf_get_reg32(src, tmp2, ctx);
+
+			if (insn->off == 8) {
+				emit(rv_slli(lo(rd), lo(rs), 24), ctx);
+				emit(rv_srai(lo(rd), lo(rd), 24), ctx);
+			} else if (insn->off == 16) {
+				emit(rv_slli(lo(rd), lo(rs), 16), ctx);
+				emit(rv_srai(lo(rd), lo(rd), 16), ctx);
+			}
+			bpf_put_reg32(dst, rd, ctx);
+			break;
+		}
 		fallthrough;
 
 	case BPF_ALU | BPF_ADD | BPF_X:
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall
  2026-10-19 13:48 ` [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall Sasha Levin
@ 2026-10-19 15:01   ` sashiko-bot
  0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-10-19 15:01 UTC (permalink / raw)
  To: Sasha Levin; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] bpf: unreset tail_call_reachable in check_max_stack_depth_subprog()
--

commit c2ffc0a7246f4fd9042fb29769bcdfa70c066eca
Author: Pu Lehui <pulehui@huawei.com>

bpf: Reject callback subprogs invoke tailcall

This commit rejects tailcalls inside synchronous callback subprograms by
widening the exception callback check in check_max_stack_depth_subprog()
to cover all callbacks via is_cb.

Link: https://patch.msgid.link/20260716120157.835937-3-pulehui@huaweicloud.com

> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index b89a5d717e422..5cc3e9a990ef7 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c

[ ... ]

> @@ -6852,8 +6848,8 @@ static int check_max_stack_depth_subprog(struct bpf_verifier_env *env, int idx,
>  	 */
>  	if (tail_call_reachable)
>  		for (tmp = idx; tmp >= 0; tmp = dinfo[tmp].caller) {
> -			if (subprog[tmp].is_exception_cb) {
> -				verbose(env, "cannot tail call within exception cb\n");
> +			if (subprog[tmp].is_cb) {
> +				verbose(env, "cannot tail call within callback\n");
>  				return -EINVAL;
>  			}
>  			subprog[tmp].tail_call_reachable = true;

[Severity: High]
Can this falsely reject valid BPF programs when a program calls a
tail-calling subprogram and later uses an unrelated synchronous callback
that does not invoke any tailcalls?

In check_max_stack_depth() -> check_max_stack_depth_subprog(), the local
variable tail_call_reachable is initialized once at function entry and
is only ever set to true when descending into a callee:

kernel/bpf/verifier.c:check_max_stack_depth_subprog() {
	bool tail_call_reachable = false;
	...
		idx = sidx;
		if (!priv_stack_supported)
			subprog[idx].priv_stack_mode = NO_PRIV_STACK;

		if (subprog[idx].has_tail_call)
			tail_call_reachable = true;
}

Because tail_call_reachable is never reset to false when entering a
subsequent callee whose has_tail_call is false (or when popping back to
a caller), once any normal subprogram with has_tail_call == true is
visited, tail_call_reachable remains true for the rest of the walk.

Synchronous callbacks (such as callbacks passed to bpf_loop or
bpf_for_each_map_elem) have is_cb == true and is_async_cb == false, so
the traversal enters them via bpf_pseudo_func(insn + i). If a synchronous
callback without tailcalls is visited after an unrelated tail-calling
subprogram, tail_call_reachable is still true and hits this new
subprog[tmp].is_cb check, failing BPF_PROG_LOAD with -EINVAL.

Does this backport also need prerequisite upstream commit 3513ea9dab6c1
("bpf: Sync tail_call_reachable with callee state on entry"), which
updates the callee entry logic to:

	tail_call_reachable = subprog[idx].has_tail_call;

so that tail_call_reachable does not leak across unrelated subprograms?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019135441.1420968-237-sashal@kernel.org?part=1

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-10-19 15:01 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20261019135441.1420968-1-sashal@kernel.org>
2026-10-19 13:45 ` [PATCH AUTOSEL 6.18-6.1] libbpf: Skip bpf_object__probe_loading() when BPF token is in use Sasha Levin
2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] libbpf: Fix ring buffer consumer loop on 32-bit position wrap Sasha Levin
2026-10-19 13:46 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_SDIV and BPF_SMOD in RV32 JIT Sasha Levin
2026-10-19 13:48 ` [PATCH AUTOSEL 6.18] bpf: Reject callback subprogs invoke tailcall Sasha Levin
2026-10-19 15:01   ` sashiko-bot
2026-10-19 13:50 ` [PATCH AUTOSEL 6.18-6.6] xsk: account ring allocations to memcg Sasha Levin
2026-10-19 13:52 ` [PATCH AUTOSEL 6.18-5.10] riscv, bpf: Add support for BPF_MOVSX in RV32 JIT Sasha Levin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox