Re: [PATCH bpf-next 3/5] bpf: infer zext_dst based on static register liveness analysis
Eduard Zingerman <[email protected]> Fri, 31 Jul 2026 14:17:40 -0700
| Newsgroups | dev.linux.lists.sashiko-reviews,org.kernel.vger.bpf |
|---|---|
| Message-ID | <[email protected]> |
On Fri, 2026-07-31 at 19:26 +0000, [email protected] wrote: > Thank you for your contribution! Sashiko AI review found 1 potential issu= e(s) to consider: > - [Critical] Incorrect static liveness tracking for 64-bit conditional ju= mps leads to missing zero-extensions (`zext_dst`), resulting in verifier/ru= ntime state divergence and potential privilege escalation. > -- >=20 > --- Patch [3]: [PATCH bpf-next 3/5] bpf: infer zext_dst based on static r= egister liveness analysis --- > commit ab8592d6a434dbfed0cbb0bd067d9032b308db8d > Author: Eduard Zingerman <[email protected]> >=20 > bpf: infer zext_dst based on static register liveness analysis >=20 > This commit replaces the dynamic subreg_def based zero-extension > tracking with static liveness analysis to correctly infer zext_dst > and prevent missing zero-extensions on architectures like s390x. >=20 > Link: https://lore.kernel.org/bpf/CAGKGUv=3DsOuqQtA1Ub-5JXfA4FPosJFYKAQE4= [email protected]/ >=20 > > diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c > > index c996b8a03e792..ea002550671a8 100644 > > --- a/kernel/bpf/liveness.c > > +++ b/kernel/bpf/liveness.c > > @@ -2317,9 +2318,18 @@ int bpf_compute_live_registers(struct bpf_verifi= er_env *env) > > } > > =20 > > for (i =3D 0; i < insn_cnt; ++i) { > > + int def32 =3D bpf_insn_def32(&insns[i]); > > + u32 out =3D state[i].out; > > u32 in =3D state[i].in; > > =20 > > insn_aux[i].live_regs_before =3D mask_lo(in) | mask_hi(in); > > + /* > > + * On architectures where 32-bit operations do not reset upper halve= s > > + * of the registers, the verifier needs to zero extend a destination > > + * register if an instruction defines a 32-bit subregister and the > > + * upper half of that register is alive after the instruction. > > + */ > > + insn_aux[i].zext_dst =3D def32 >=3D 0 && (mask_hi(out) & BIT(def32))= ; >=20 > [Severity: Critical] > Does this code correctly evaluate liveness for 64-bit conditional jumps? >=20 > The new zext_dst logic relies on mask_hi(out) accurately reflecting if th= e > upper half of a register is alive. >=20 > However, looking at compute_insn_live_regs() in kernel/bpf/liveness.c for > conditional jumps: >=20 > const u8 class =3D BPF_CLASS(insn->code); > const u8 code =3D BPF_OP(insn->code); > ... > case BPF_JMP: > case BPF_JMP32: > switch (code) { > ... > default: > def =3D 0; > if (BPF_SRC(insn->code) =3D=3D BPF_K) > use =3D dst; > else > use =3D dst | (code =3D=3D BPF_JMP32 ? src : src32); > } >=20 > Can the code variable ever equal BPF_JMP32? >=20 > BPF_OP() extracts the operation code (which is a multiple of 16), while > BPF_JMP32 is a class (0x06). This means the check code =3D=3D BPF_JMP32 e= valuates > to false, and the source register usage always falls back to src32, even = for > 64-bit jumps (BPF_JMP). >=20 > If a 32-bit operation defines a register, and it is followed by a 64-bit > conditional jump using that register as a source with no other uses of th= e > upper 32 bits, the upper half won't be marked as alive. >=20 > As a result, zext_dst wouldn't be set, which could lead to the JIT skippi= ng > the mandatory zero extension. This would cause the 64-bit jump to compare > against uninitialized garbage in the upper bits, potentially allowing > out-of-bounds access. >=20 > Should this check use class =3D=3D BPF_JMP32 instead? >=20 > > } This is the same (real) issue as reported for the previous patch.