CVE-2026-99127: page_pool: keep frag_offset aligned for odd-sized requests
In the Linux kernel, the following vulnerability has been resolved:
pagepool: keep fragoffset aligned for odd-sized requests
pagepoolallocfragnetmem() rounds the requested fragment size with
size = ALIGN(size, dmagetcachealignment());
dmagetcachealignment() returns 1 unless the architecture defines ARCHDMAMINALIGN, which DMA-coherent architectures such as x86 do not. There the ALIGN() is a no-op and pool->fragoffset advances by the raw, unrounded size.
A single caller asking for an odd size then leaves fragoffset misaligned for every fragment carved out of that page afterwards. The pool is shared, so the damage is not confined to the caller that caused it.
The per-cpu systempagepool used by generic XDP hits this. skbppcowdata() allocates its fragments with the raw packet length:
size = mint(u32, len, PAGESIZE); truesize = size; page = pagepooldevalloc(pool, &pageoff, &truesize);
leaving fragoffset odd for whatever is carved out of that page next. Its own head allocation is already aligned -- SKBHEADALIGN(size) plus the XDPPACKETHEADROOM its callers pass -- so it is a later user of the shared pool that pays: pagepooldevallocva() returns a misaligned buffer, napibuildskb() installs it as skb->head, and skbshinfo(skb) == skb->head + skb->end is misaligned with it.
skbshinfo()->dataref is a 4-byte atomict at offset 0x20, so the atomicinc() in skbclone() straddles a cache line. On x86 with split lock detection -- fatal for kernel split locks by default -- this panics the machine:
Oops: Split lock detected RIP: 0010:skbclone+0x154/0x1e0 Call Trace: <IRQ> rawlocaldeliver+0x1ed/0x2c0 ipprotocoldeliverrcu+0x54/0x1c0 iplocaldeliverfinish+0x85/0x100 iplocaldeliver+0x67/0x100 netifreceiveskbonecore+0x85/0xa0 processbacklog+0x87/0x130
Reproduced by attaching any generic-mode XDP program to loopback and opening a RAW IPPROTOUDP socket, which makes rawlocaldeliver() clone every locally delivered UDP packet; ordinary DNS traffic then triggers it, roughly once per 2500 clones. Observed on 6.12.101 and 7.1.8.
Tracing pagepoolallocfragnetmem() over one such run shows the amplification -- two odd-sized requests, nine misaligned offsets:
requested size & 7: 0: 17035 5: 1 7: 1 fragoffset & 7: 0: 17028 3: 1 4: 1 5: 1 6: 1 7: 5
and skbppcowdata() returning heads that were aligned on entry:
head 0xffff8f4c86aeac00 -> 0xffff8f4c53a9a9c4 (&7=4) head 0xffff8f4d6a8a42c0 -> 0xffff8f4c4f7b7a45 (&7=5)
Round the fragment size up to at least the alignment struct skbsharedinfo requires, so fragments are always suitably aligned for the objects callers build on them. Architectures needing a larger DMA alignment keep it.
This also makes the remainder computed in pagepoolallocnetmem(),
size = maxsize - offset;
aligned, since maxsize is a power of two -- which fixes the matching misalignment of skb->end.
Verified with a controlled A/B under QEMU/KVM: same tree, same config, same compiler, same rootfs and identical traffic, differing only by this patch. A SEC("xdp.frags") XDPPASS program on lo plus UDP datagrams larger than maxheadsize drives skbppcowdata()'s fragment loop, which passes raw packet lengths to the pool. Measured at the return of skbppcowdata():
unpatched patched skbppcowdata calls 40800 40800 misaligned skb->head 1120 0 dataref at line offset >60 80 0
The last row counts the accesses that actually fault: skbshinfo()->dataref sits at head+end+0x20 and is a 4-byte atomic, so lock incl splits a 64-byte cache line only when that address lands at offset 61..63. All 80 occurrences were at offset 61; the panic reported above was at offset 62. Eliminating the misalignment removes every one of them.
Same class of bug as commit 3bed3cc4156e ("net: Do not allocate page fragments t ---truncated---