* [RESEND][REGRESSION][SECURITY] bpf, s390: verifier/JIT mismatch allows unprivileged kernel memory access
@ 2026-07-23 8:16 Min-gyu Kim
2026-07-23 9:05 ` Eduard Zingerman
0 siblings, 1 reply; 3+ messages in thread
From: Min-gyu Kim @ 2026-07-23 8:16 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Ilya Leoshkevich,
Heiko Carstens, Vasily Gorbik
Cc: bpf, linux-s390, linux-kernel, regressions, security
Hello,
I am reporting a potential security regression involving eBPF verifier state
pruning and the s390x JIT. I reproduced arbitrary kernel read and write access
and local privilege escalation as an unprivileged user.
AI assistance was used during discovery and analysis. In accordance with the
kernel security-bug reporting guidance, I am not requesting an embargo. The
reproducer and exploit are not attached or published; I can provide them
privately to maintainers on request.
Summary and affected versions
A verifier regression can cause the verifier and the s390x JIT to disagree
about the upper 32 bits of scalar registers. A crafted socket-filter program
can consequently form an unchecked 64-bit offset from an ARRAY map-value
pointer and access attacker-selected kernel addresses.
The regression was introduced by:
107e169799057bc6a379ddb625cbe1e51cfc7d72
("bpf: disable and remove registers chain based liveness")
The commit first appears in v6.18-rc1 and is not present in v6.17. The cause
was still present in the v6.18, v6.19, v7.0, and v7.1 stable branches and in
v7.2-rc4, bpf, and bpf-next when checked by source comparison on 2026-07-22.
I also confirmed the relevant logic in mainline commit
4539944e515183668109bdf4d0c3d7d228383d88 on 2026-07-23. Dynamic
reproduction was performed on v6.18.38.
Affected code:
kernel/bpf/verifier.c
kernel/bpf/states.c in current development trees
arch/s390/net/bpf_jit_comp.c
Required conditions:
Architecture: 64-bit s390x
Program type: BPF_PROG_TYPE_SOCKET_FILTER
Map type: BPF_MAP_TYPE_ARRAY
CONFIG_BPF_SYSCALL: enabled
BPF JIT: enabled and active
kernel.unprivileged_bpf_disabled: 0
Required capabilities: none
User namespace: not required
Root cause
The verifier records an ALU32 definition in subreg_def. A later 64-bit read
causes mark_insn_zext() to mark that instruction for explicit zero extension.
Before 107e16979905, a cache hit in is_state_visited() propagated the 64-bit
read to the alternate path's ALU32 definition.
Commit 107e16979905 removed that propagation when register-chain liveness was
replaced with static liveness. The static live-register mask does not track
read width or path-specific subreg_def information. In current states.c,
regs_exact() compares only fields before id, while subreg_def follows id in
struct bpf_reg_state. The SCALAR_VALUE case in regsafe() also does not compare
subreg_def. States with equal tracked scalar values but different ALU32
definitions can therefore compare as equal, leaving the pruned path's ALU32
instruction without a zero-extension mark.
On s390, ALU32 ADD updates the low 32 bits while preserving the register's
high half. EMIT_ZERO() emits llgfr only when the program-wide verifier_zext
flag is clear. If another ALU32 definition is marked, verifier_zext is set and
the JIT relies on each instruction's zext_dst flag. The pruned instruction
lacks that flag and can execute with attacker-controlled high bits intact.
Reproduction and impact
I ran the test as uid 1000 with all capability sets empty:
Linux syzkaller 6.18.38 s390x
kernel.unprivileged_bpf_disabled = 0
net.core.bpf_jit_enable = 1
The exploit disclosed array_map_ops through the existing map->ops pointer,
derived the KASLR slide, and obtained arbitrary kernel read and write
primitives. It used those primitives to locate and modify the calling
process's credentials. map->ops was read only and was not overwritten.
The resulting shell reported:
uid=0(root) gid=0(root) groups=0(root),27(sudo),100(users),1000(poc)
context=system_u:system_r:kernel_t:s0
A standalone C reproducer and exploit, verifier log, kernel configuration,
and build details are available privately on request.
Mitigation and fix direction
Setting kernel.unprivileged_bpf_disabled to a non-zero value prevents this
attack by unprivileged users. Disabling the s390 BPF JIT also prevents the
demonstrated mismatch.
I have not yet tested a patch. A conservative verifier fix may be to avoid
pruning live scalar states whose relevant subreg_def values differ, or to
preserve the read-width information needed to mark the current path's ALU32
instruction. As defense in depth, the s390 JIT could avoid relying only on
the program-wide verifier_zext flag. I can prepare a regression test and test
proposed changes in the s390x QEMU environment used for reproduction.
References
Regression-introducing commit:
https://github.com/torvalds/linux/commit/107e169799057bc6a379ddb625cbe1e51cfc7d72
Public review discussion related to read width and zero extension:
https://www.spinics.net/lists/bpf/msg139280.html
Merged patch discussion:
https://www.spinics.net/lists/bpf/msg152234.html
If appropriate, I would appreciate a Reported-by tag as:
Min-gyu Kim <gimm78064@gmail.com>
Please let me know if you need additional information or would like the
private reproducer and exploit.
Thank you for your time.
Best regards,
Min-gyu Kim
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [RESEND][REGRESSION][SECURITY] bpf, s390: verifier/JIT mismatch allows unprivileged kernel memory access
2026-07-23 8:16 [RESEND][REGRESSION][SECURITY] bpf, s390: verifier/JIT mismatch allows unprivileged kernel memory access Min-gyu Kim
@ 2026-07-23 9:05 ` Eduard Zingerman
2026-07-24 1:03 ` Eduard Zingerman
0 siblings, 1 reply; 3+ messages in thread
From: Eduard Zingerman @ 2026-07-23 9:05 UTC (permalink / raw)
To: Min-gyu Kim, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Kumar Kartikeya Dwivedi, Ilya Leoshkevich,
Heiko Carstens, Vasily Gorbik
Cc: bpf, linux-s390, linux-kernel, regressions, security
On Thu, 2026-07-23 at 17:16 +0900, Min-gyu Kim wrote:
...
> Root cause
>
> The verifier records an ALU32 definition in subreg_def. A later 64-bit read
> causes mark_insn_zext() to mark that instruction for explicit zero extension.
> Before 107e16979905, a cache hit in is_state_visited() propagated the 64-bit
> read to the alternate path's ALU32 definition.
Hi Min-gyu,
Thank you for the report.
> Commit 107e16979905 removed that propagation when register-chain liveness was
> replaced with static liveness. The static live-register mask does not track
> read width or path-specific subreg_def information. In current states.c,
> regs_exact() compares only fields before id, while subreg_def follows id in
> struct bpf_reg_state.
Hm, the stack is tracked with 32-bit granularity and it should be easy
to adjust compute_live_registers() to track the width. I'll give it a try.
> The SCALAR_VALUE case in regsafe() also does not compare
> subreg_def. States with equal tracked scalar values but different ALU32
> definitions can therefore compare as equal, leaving the pruned path's ALU32
> instruction without a zero-extension mark.
This is orthogonal to liveness mechanism, this is also much older.
I think this is the true root cause and it should be possible to
construct a test case triggering false-pruning on <6.18.
Thanks,
Eduard
...
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [RESEND][REGRESSION][SECURITY] bpf, s390: verifier/JIT mismatch allows unprivileged kernel memory access
2026-07-23 9:05 ` Eduard Zingerman
@ 2026-07-24 1:03 ` Eduard Zingerman
0 siblings, 0 replies; 3+ messages in thread
From: Eduard Zingerman @ 2026-07-24 1:03 UTC (permalink / raw)
To: Min-gyu Kim, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Kumar Kartikeya Dwivedi, Ilya Leoshkevich,
Heiko Carstens, Vasily Gorbik
Cc: bpf, linux-s390, linux-kernel, regressions, security
On Thu, 2026-07-23 at 02:05 -0700, Eduard Zingerman wrote:
> On Thu, 2026-07-23 at 17:16 +0900, Min-gyu Kim wrote:
>
> ...
>
> > Root cause
> >
> > The verifier records an ALU32 definition in subreg_def. A later 64-bit read
> > causes mark_insn_zext() to mark that instruction for explicit zero extension.
> > Before 107e16979905, a cache hit in is_state_visited() propagated the 64-bit
> > read to the alternate path's ALU32 definition.
>
> Hi Min-gyu,
>
> Thank you for the report.
>
> > Commit 107e16979905 removed that propagation when register-chain liveness was
> > replaced with static liveness. The static live-register mask does not track
> > read width or path-specific subreg_def information. In current states.c,
> > regs_exact() compares only fields before id, while subreg_def follows id in
> > struct bpf_reg_state.
>
> Hm, the stack is tracked with 32-bit granularity and it should be easy
> to adjust compute_live_registers() to track the width. I'll give it a try.
I figured out the repro and am working on the fix.
The plan is to extend compute_live_registers() to statically infer
which upper 32-bit subregisters are alive and use this information
to fixup zero extensions, and drop the reg->subreg_def altogether.
...
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-24 1:03 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-23 8:16 [RESEND][REGRESSION][SECURITY] bpf, s390: verifier/JIT mismatch allows unprivileged kernel memory access Min-gyu Kim
2026-07-23 9:05 ` Eduard Zingerman
2026-07-24 1:03 ` Eduard Zingerman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®