CVE-2026-68398
High · 7.8CVSS
7.8
EPSS
0.2%
Published
Aug 10, 2026
CISA due date
—
Cloudanix analysis
A flaw in the Linux kernel's handling of certain network connections can let a regular user crash the system or potentially gain higher privileges.
- Exploitation
- Local Privilege
- Attacker needs
- Low Privilege
- Complexity
- Low
- Impact
- Full Compromise
- Component
- OS kernel
- Class
- Use After Free
- Remediation
- Patch
- Effort
- Low
- Patch risk
- Low
What it actually does
Use-after-free in the PPP L2TP receive path where pppol2tp_recv calls ppp_input on a struct channel that can be kfree'd by a concurrent close without an RCU grace period when the channel is bound but not attached or bridged.
Why it matters to the business
Local unprivileged users can trigger kernel memory corruption leading to crashes or potential privilege escalation on systems using PPP over L2TP, increasing risk of denial of service or full host compromise.
How an attack unfolds
An attacker opens a PPPoL2TP socket and binds a channel without connecting it, then closes the socket from another thread while a UDP packet triggers pppol2tp_recv under RCU; the channel pointer is dereferenced after the kfree in ppp_release_channel.
How to fix it
Upgrade to a Linux kernel containing the fix that replaces the direct kfree with call_rcu in ppp_release_channel for the unbound channel case; apply the patch that ensures RCU grace period before freeing struct channel.
How to confirm the fix worked
Confirm the kernel source contains the call_rcu change in ppp_release_channel and that teardown paths for bound channels continue to use synchronize_net or synchronize_rcu; test by reproducing the bind-without-connect close race under RCU read lock.
Typical attack path
- Create PPPoL2TP data socket and issue PPPIOCGCHAN to bind channel
- Trigger concurrent close of socket to invoke ppp_unregister_channel and kfree without synchronize_rcu
- Send L2TP UDP packet to invoke l2tp_udp_encap_recv -> pppol2tp_recv -> ppp_input on freed channel
How to detect it
Search kernel logs for use-after-free warnings or oops involving ppp_input, pppol2tp_recv, or struct channel; inspect audit logs for unusual sequences of PPPIOCGCHAN followed immediately by socket close on L2TP devices.
What your security team should do
- Prioritize kernel updates on any system exposing PPPoL2TP sockets to unprivileged users
- Monitor for local privilege-escalation attempts via L2TP sockets until patched
- Restrict unprivileged access to PPP and L2TP interfaces where feasible
The record
In the Linux kernel, the following vulnerability has been resolved: ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF pppol2tp_recv() runs in the L2TP UDP-encap softirq RX path: l2tp_udp_encap_recv() -> l2tp_recv_common() -> pppol2tp_recv() -> ppp_input(&po->chan) It runs under rcu_read_lock() holding only an l2tp_session reference and takes NO reference on the internal PPP channel (struct channel, chan->ppp) that ppp_input() dereferences. The pppox socket is SOCK_RCU_FREE, so 'po' and the embedded ppp_channel are RCU-safe. But the internal struct channel is a separate allocation that ppp_release_channel() frees with a plain kfree(): close(data socket) -> pppol2tp_release() -> pppox_unbind_sock() -> ppp_unregister_channel() -> ppp_release_channel() -> kfree(pch) For a channel that is bound (PPPIOCGCHAN) but not attached to a ppp unit (no PPPIOCCONNECT, pch->ppp == NULL) and not bridged, teardown skips both ppp_disconnect_channel()'s synchronize_net() and ppp_unbridge_channels()'s synchronize_rcu(), so the kfree() has no grace period. rcu_read_lock() in pppol2tp_recv() does not protect against a plain kfree(), so an in-flight ppp_input() on one CPU can dereference the channel just freed by close() on another CPU. The bug is reachable by an unprivileged user. Defer the channel free to an RCU callback via call_rcu() so the grace period fences any in-flight ppp_input(). The disconnect and unbridge teardown paths already fence with synchronize_net()/synchronize_rcu(); call_rcu() does the same here without stalling the close() path.
Affected packages
| Ecosystem | Package | Introduced | Fixed |
|---|