KernelScan.io

HIGH Introduced in 6.18

bpf Verifier Zext Bypass

CVE-2026-98869

CVSS 7.0 / 10.0 KernelScan AI

CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

01

In the Linux kernel, the following vulnerability has been resolved: bpf: Infer zext_dst based on static register liveness analysis As reported in the thread [1], the verifier's 32-bit operations zero extension logic is broken. This logic is responsible for correct semantics of 32-bit operations on s390 architecture. According to BPF semantics, operation `w1 += 1` is supposed to zero extend the upper half of the register `r1`. On s390 the JIT relies on the verifier emitting explicit zero extension before such operations. The verifier attempts to minimize the amount of zero extensions inserted by tracking whether upper halves of the 64-bit registers are ever used. Previously such tracking worked as follows: - bpf_reg_state->subreg_def field was set by do_check_insn() for each operation defining lower but not the upper halves of the register. - Whenever an operation reading the whole register was verified, the verifier checked register's subreg_def and set bpf_insn_aux_data->zext_dst flag as true via a call to mark_insn_zext() function. - After the verification was complete, a special pass bpf_opt_subreg_zext_lo32_rnd_hi32() extended 32-bit operations with bpf_insn_aux_data->zext_dst set as true by adding explicit zero extension. Note that the logic above relies on bpf_reg_state->subreg_def, which is a property of a current verifier state. Before the commit [2] two additional steps happened: - The verifier tracked upper and lower register halves' liveness as flags REG_LIVE_READ{32,64} in bpf_reg_state->live. - The function propagate_liveness() called mark_insn_zext() in order to transfer the knowledge about which registers have their upper halves alive (and thus might require zero extension). The commit [2] removed the two steps described above, hence making possible a situation like below: - The register's upper half is set and is used on some verification path P1 and the register happens not to be marked as precise. - The checkpoint C is created while processing some instruction between register initialization and usage. - On some other verification path P2 the register's upper half is not initialized and that path ends hitting the checkpoint C. - In such a case the register's initialization on path P2 would lack zext_dst mark, making it possible for the program to inject an arbitrary value in the register's upper half. This commit replaces subreg_def based logic with computing zext_dst statically, as a part of the bpf_compute_live_registers() analysis: - The analysis now tracks usage of upper and lower halves of the registers separately. - If some instruction defines a 32-bit subregister, but not the whole register, *and* the upper half of the register is alive after that instruction, the instruction is marked as zext_dst. There is one notable drop in precision: whenever a BPF subprogram is called, all 64 bits of parameter registers are presumed to be used. The assumption is that such a drop in precision would not inflict a noticeable performance penalty. [1] https://lore.kernel.org/bpf/CAGKGUv=sOuqQtA1Ub-5JXfA4FPosJFYKAQE4B79cK+P1erxqtg@mail.gmail.com/ [2] commit 107e16979905 ("bpf: disable and remove registers chain based liveness")

02

Engine v0.9.0

Risk summary

A logic error in the BPF verifier's zero-extension tracking for 32-bit operations on s390 allows a malicious BPF program to leave the upper 32 bits of a 64-bit register uninitialized. An attacker with CAP_BPF (or unprivileged BPF access) can exploit this to corrupt register state, potentially achieving arbitrary kernel memory read/write and privilege escalation on s390 systems.

Affectedkernel/bpf/verifier.c, kernel/bpf/liveness.c, kernel/bpf/fixups.c (BPF verifier)

Vulnerability analysis

The BPF verifier incorrectly determines when zero-extension instructions are needed for 32-bit operations on s390. After a prior commit removed register liveness tracking, the verifier's dynamic subreg_def-based logic could miss zero-extension requirements when verification paths merge at a checkpoint — one path initializes a register's upper half while another does not, causing the merged state to lack the zext_dst mark. This lets an attacker inject arbitrary values into the upper register half. The fix replaces this with a static liveness analysis in bpf_compute_live_registers() that tracks upper and lower register halves separately, marking zext_dst whenever a 32-bit definition is followed by upper-half liveness. The vulnerability is reachable by any local user who can load BPF programs (requiring CAP_BPF or unprivileged BPF enabled), primarily affecting s390 where the JIT depends on verifier-inserted zero extensions.

03

BranchIntroducedFixed inPatch commit
6.126.186.12.110555fc6f1caf0
mainline6.187.37ce090afbf72