PoC Archive PoC Archive
CVE-2026-68398 category: binary CVSS 7.8 (HIGH)
Patched

Ubuntu Linux Kernel PPPoL2TP Use-After-Free Local Privilege Escalation (CVE-2026-68398)

Published: 2026-08-16 • Researcher: A. Ramos (@aramosf); vulnerability discovery and upstream fix by Norbert Szetei (Doyensec)

Target software Linux Kernel (PPPoL2TP subsystem)
Affected versions Linux 4.15+ through mainline pre-7.2-rc4; validated on Ubuntu 22.04.5 LTS kernel 5.15.0-187-generic (5.15.0-187.197)
Status Patched
Severity High · CVSS 7.8
On this page

Metadata

FieldValue
Date Added2026-08-16
Last Updated2026-08-16
Author / ResearcherA. Ramos (@aramosf); vulnerability discovery and upstream fix by Norbert Szetei (Doyensec)
CVE / AdvisoryCVE-2026-68398
Categorybinary
SeverityHigh
CVSS Score7.8 (local privilege escalation)
StatusPatched
Tagslinux, kernel, ubuntu, pppol2tp, l2tp, ppp, uaf, use-after-free, race-condition, lpe, privilege-escalation, kaslr-bypass, apparmor-bypass, suid, heap-spray, kmalloc-256, CVE-2026-68398
Related

Affected Target

FieldValue
Software / SystemLinux Kernel (PPPoL2TP subsystem)
Versions AffectedLinux 4.15+ through mainline pre-7.2-rc4; validated on Ubuntu 22.04.5 LTS kernel 5.15.0-187-generic (5.15.0-187.197)
Language / PlatformC (x86-64 Linux, static binary)
Authentication RequiredNone – unprivileged local user, no capabilities or sudo
Network Access RequiredLocal

Summary

CVE-2026-68398 is a use-after-free race condition between PPPoL2TP receive processing and destruction of a bound-but-unattached PPP channel in the Linux kernel. The PPPoX socket and its embedded ppp_channel are RCU-safe, but the internal struct channel used by ppp_input() is a separate allocation that was freed immediately on close, while the L2TP UDP receive path could still be accessing it under an RCU read-side critical section.

This build-specific exploit turns the race into initial-namespace UID 0 on an unmodified Ubuntu 22.04.5 kernel with KASLR, SMEP, SMAP, and AppArmor all enabled. It uses user-key payloads to reclaim the freed kmalloc-256 channel object, performs multi-stage heap disclosures via skb queue list operations and core_pattern scratch, constructs a forged credential with a valid unconfined AppArmor label, and invokes override_creds() through a controlled waitqueue callback. A temporary SUID copy converts the kernel-task credential primitive into real process credentials.

Vulnerability Details

Root Cause

pppol2tp_recv() executes in the L2TP UDP receive path under an RCU read-side critical section and calls ppp_input(&po->chan). For a channel bound with PPPIOCGCHAN but neither attached to a PPP unit nor bridged, the close path freed the internal struct channel immediately:

Output
RX:    l2tp_udp_encap_recv -> pppol2tp_recv -> ppp_input(channel)
CLOSE: pppol2tp_release -> pppox_unbind_sock
       -> ppp_unregister_channel -> ppp_release_channel -> kfree(channel)

The receive path can operate on the freed channel skb queue and waitqueue. The fix adds an RCU callback to struct channel and defers the queue purge and free until an RCU grace period has elapsed.

Attack Flow

  1. Race setup: Four race workers repeatedly create bound-but-unattached PPPoL2TP channels via loopback UDP, send valid L2TPv2/PPP frames from multiple CPUs, and close the data socket to trigger the free.
  2. Heap reclaim: A user-key payload (KEYCTL_UPDATE) reclaims the freed struct channel in kmalloc-256. The still-running RX path treats the controlled key bytes as its skb queue and waitqueue.
  3. Stage 1-2 disclosure: Queue list operations disclose a live skb pointer through the readable key payload. A controlled unlink uses core_pattern as disclosure scratch and exposes a key-allocation heap address.
  4. Stage 3-4 credential carrier: A 224-byte key payload becomes a persistent credential carrier with embedded fake cred fields (UID/GID 0, full capabilities, override_creds() as callback). A second disclosure reveals the carrier key object heap address.
  5. Stage 5-8 AppArmor bypass: Two additional pointer-source and unlink stages inject a valid AppArmor credential blob pointer and an unconfined fake label into the forged credential, satisfying Ubuntu AppArmor MAC checks.
  6. Stage 9 privilege escalation: The reclaimed waitqueue entry calls override_creds() with the forged cred. The temporarily privileged execution context copies /proc/self/exe to a SUID root helper in /tmp, which the original unprivileged task then executes to obtain real UID/GID 0.

Impact

  • Local privilege escalation to initial-namespace root
  • Bypasses KASLR (via prefetch timing side channel), SMEP, SMAP, and AppArmor
  • Requires only an unprivileged user account – no capabilities, no sudo, no namespace setup
  • PPPoL2TP module autoloads when the exploit creates its socket
  • Probabilistic race – can panic or corrupt the kernel on failure

Environment / Lab Setup

Shell script
1
2
3
make

./CVE-2026-68398 auto 60

Proof of Concept

See exploit.c (906 lines, C) and kaslr_prefetch.c (333 lines, C) in this folder – mirrored byte-for-byte from aramosf/cve-2026-68398. The upstream README is preserved as upstream-README.md.

Step-by-Step Reproduction

  1. Build: make produces a statically linked binary at build/CVE-2026-68398.
  2. Prepare VM: Boot Ubuntu 22.04.5 with kernel 5.15.0-187-generic in QEMU/KVM with 4 vCPUs and 5 GiB RAM. Install linux-modules-extra-5.15.0-187-generic if not present.
  3. Run as unprivileged user: ./CVE-2026-68398 auto 60 (auto KASLR detection, 60-second race timeout per stage).
  4. Verify: Successful run ends with uid=0(root) gid=0(root).

Exploit Code

Heap reclaim via user-key update after PPPoL2TP channel free:

C source
1
2
3
4
5
close(fd);  /* triggers kfree(channel) */
if (decoy_fd >= 0)
    close(decoy_fd);
updated = syscall(SYS_keyctl, KEYCTL_UPDATE, key, desired, w->payload_len);
/* Key payload now overlaps the freed struct channel in kmalloc-256 */

Forged credential construction with AppArmor unconfined label:

C source
1
2
3
4
put_u64(payload, FAKE_CRED_OFFSET + 8, 0);  /* uid=0, gid=0 */
put_u64(payload, FAKE_CRED_OFFSET + 16, A_OVERRIDE_CREDS + slide);  /* callback */
put_u64(payload, FAKE_CRED_OFFSET + 52, UINT64_MAX);  /* permitted caps */
put_u64(payload, FAKE_CRED_OFFSET + 60, UINT64_MAX);  /* effective caps */

Expected Output

Output
[KASLR] image_base=0xffffffffb9200000 slide=0x38200000
CVE-2026-68398 Ubuntu local privilege-escalation exploit
uid=1001 euid=1001 slide=0x38200000
session keyring=123456789
[1/9] racing for a reclaimed channel and persistent skb...
stage 1 hit: key=12345 last_skb=0xffff... qlen=1
...
[9/9] invoking override_creds and installing the SUID transition...
override callback: uid=0 euid=0 gid=0 egid=0
installed root-owned mode 4755 helper: /tmp/.cve-2026-68398-root-948
SUID helper ready; executing it from the original unprivileged task...
SUID transition: uid=0 euid=0 gid=0 egid=0
uid=0(root) gid=0(root) groups=0(root),1001(poc)

Detection and Indicators of Compromise

Output

Remediation

ActionDetail
PatchUpdate to a fixed kernel: 6.6.148+, 6.12.101+, 6.18.42+, 7.1.6+, or mainline 7.2-rc4+. Ubuntu Jammy: check the Ubuntu CVE tracker for the patched linux-image package version.
WorkaroundBlock unprivileged loading of l2tp_ppp and pppol2tp modules: echo "install l2tp_ppp /bin/false" >> /etc/modprobe.d/cve-2026-68398.conf. Disable unprivileged user namespaces if not needed.
VerificationCheck kernel version with uname -r. Verify the fix commit is present in your kernel source or package changelog.

References

Notes

Verified by reading the full exploit source: exploit.c (906 lines, C), kaslr_prefetch.c (333 lines, C/MIT), kaslr_prefetch.h (9 lines), Makefile (20 lines), README.md (224 lines), and SUBMISSION.md (100 lines). Also reviewed scripts/ directory (expect/shell recording helpers) and assets/ (asciinema cast + gif demo).

Malware screen – clean. Pure C using only Linux syscalls and POSIX interfaces (socket, ioctl, keyctl, pthread, open/read/write/close/fchown/fchmod/execl). No network callbacks, no remote downloaders, no credential exfiltration, no miners, no obfuscation. The KASLR prefetch component is MIT-licensed and derived from the well-known KASLD project (bcoles/kasld). The Makefile produces a statically linked binary with no install-time side effects. No committed binaries – source only.

The exploit is build-specific: hardcoded to Ubuntu 22.04.5 kernel 5.15.0-187-generic with exact vmlinux symbol addresses. The source validates uname() and refuses any other kernel release. Nine-stage exploitation chain with KASLR bypass, multi-stage heap disclosure, forged credential construction with AppArmor unconfined label, and SUID transition. Exceptionally high quality with negative test against the upstream fix included.

Author: A. Ramos (@aramosf) – credible security researcher already in our archive for CVE-2026-68138. Vulnerability discovery credited to Norbert Szetei of Doyensec (reputable offensive security firm).