* [BUG] l2tp: OOB read in l2tp_recv_common() for L2TPv2 sessions with 8-byte peer cookie
@ 2026-10-25 17:35 Guikun Yang
2026-10-26 11:19 ` Guillaume Nault
0 siblings, 1 reply; 2+ messages in thread
From: Guikun Yang @ 2026-10-25 17:35 UTC (permalink / raw)
To: Guillaume Nault
Cc: James Chapman, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev,
linux-kernel
Hi Guillaume,
l2tp_recv_common() (net/l2tp/l2tp_core.c) reads L2TPv2 optional header
fields beyond the linear area guaranteed by the caller, for sessions
configured with an 8-byte peer cookie.
The UDP entry path only guarantees the first L2TP_HDR_SIZE_MAX (14)
bytes:
if (!pskb_may_pull(skb, L2TP_HDR_SIZE_MAX))
goto pass;
l2tp_recv_common(session, skb, ptr, optr, hdrflags, length);
Inside l2tp_recv_common(), for a L2TPv2 session with peer_cookie_len =
8, the cookie check consumes bytes [6, 14):
if (session->peer_cookie_len > 0) {
if (memcmp(ptr, &session->peer_cookie[0],
session->peer_cookie_len)) { ... discard }
ptr += session->peer_cookie_len; /* ptr = optr + 14 */
}
Then the sequence-number branch for L2TPv2 reads at ptr:
if (hdrflags & L2TP_HDRFLAG_S) {
L2TP_SKB_CB(skb)->ns = ntohs(*(__be16 *)ptr); /* [14, 16) OOB */
ptr += 2;
ptr += 2;
}
With a minimal 14-byte L2TPv2 packet (6-byte fixed header + 8-byte
cookie, no payload, L2TP_HDRFLAG_S set), skb->len is exactly 14 after
__skb_pull(udphdr), so the read at [14, 16) is past the end of the
linear area — a 2-byte slab-out-of-bounds read (KASAN catchable). The
subsequent `ptr += 2 + offset` handling for L2TP_HDRFLAG_O can read
even further out ([18, 20) with the same setup).
Trigger conditions:
- an L2TPv2 session with peer_cookie_len == 8 (admin-configured),
- a UDP packet whose L2TP payload is exactly 14 bytes, with
L2TP_HDRFLAG_S set and the correct cookie.
The peer address is not verified on the UDP path (l2tp_v2_session_get()
matches only on tunnel_id/session_id), so any host that knows the
cookie — i.e. a peer of the tunnel, or anyone when no cookie is
configured (in which case the OOB read does not trigger, since the
pointer stays at 6 and the seq read lands at [6, 10)) — can send such a
packet.
This is the L2TPv2 counterpart of the L2TPv3 optional-fields OOB fixed
in 2019 by commit 4522a70db7aa ("l2tp: fix reading optional fields of
L2TPv3"), which added l2tp_v3_ensure_opt_in_linear(). The commit
message of that fix explicitly noted that fixing L2TPv2 as well would
require refactoring l2tp_recv_common() and was deferred:
To fix both L2TPv3 and L2TPv2, we'd better refactor
l2tp_recv_common. It's complicated to do so.
L2TPv2 has remained unprotected since. The in-function
pskb_may_pull(skb, offset) at the end of l2tp_recv_common() runs only
after all the header reads have already happened, so it cannot help.
A minimal fix for the v2 path could ensure the header is fully linear
before parsing — e.g. in l2tp_udp_encap_recv(), after determining
version == L2TP_HDR_VER_2 and before calling l2tp_recv_common():
if (session->tunnel->version == L2TP_HDR_VER_2 &&
!pskb_may_pull(skb, 6 + session->peer_cookie_len +
(hdrflags & L2TP_HDRFLAG_S ? 4 : 0)))
goto invalid;
(or a full refactor pulling the cookie/seq handling into a
l2tp_v2_ensure_opt_in_linear() helper mirroring the v3 one).
Checked against 7.3.0-rc7 and linux-next (2026-10-24): neither carries a
fix; l2tp_v3_ensure_opt_in_linear() is still v3-only. I found no
report of this specific OOB on lore/marc.
Best,
Guikun Yang
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [BUG] l2tp: OOB read in l2tp_recv_common() for L2TPv2 sessions with 8-byte peer cookie
2026-10-25 17:35 [BUG] l2tp: OOB read in l2tp_recv_common() for L2TPv2 sessions with 8-byte peer cookie Guikun Yang
@ 2026-10-26 11:19 ` Guillaume Nault
0 siblings, 0 replies; 2+ messages in thread
From: Guillaume Nault @ 2026-10-26 11:19 UTC (permalink / raw)
To: Guikun Yang
Cc: James Chapman, Eric Dumazet, Jakub Kicinski, Paolo Abeni, netdev,
linux-kernel
On Mon, Oct 26, 2026 at 01:35:25AM +0800, Guikun Yang wrote:
> Hi Guillaume,
Hi Guikun,
> l2tp_recv_common() (net/l2tp/l2tp_core.c) reads L2TPv2 optional header
> fields beyond the linear area guaranteed by the caller, for sessions
> configured with an 8-byte peer cookie.
Cookies are an L2TPv3 feature (RFC 3931). They don't exist in L2TPv2
(RFC 2661) and l2tp_nl_cmd_session_create() ignores L2TP_ATTR_PEER_COOKIE
when creating L2TPv2 sessions. Have you found a way to get a non-null
->peer_cookie_len on an L2TPv2 session?
> The UDP entry path only guarantees the first L2TP_HDR_SIZE_MAX (14)
> bytes:
>
> if (!pskb_may_pull(skb, L2TP_HDR_SIZE_MAX))
> goto pass;
>
> l2tp_recv_common(session, skb, ptr, optr, hdrflags, length);
>
> Inside l2tp_recv_common(), for a L2TPv2 session with peer_cookie_len =
> 8, the cookie check consumes bytes [6, 14):
>
> if (session->peer_cookie_len > 0) {
> if (memcmp(ptr, &session->peer_cookie[0],
> session->peer_cookie_len)) { ... discard }
> ptr += session->peer_cookie_len; /* ptr = optr + 14 */
> }
>
> Then the sequence-number branch for L2TPv2 reads at ptr:
>
> if (hdrflags & L2TP_HDRFLAG_S) {
> L2TP_SKB_CB(skb)->ns = ntohs(*(__be16 *)ptr); /* [14, 16) OOB */
> ptr += 2;
> ptr += 2;
> }
>
> With a minimal 14-byte L2TPv2 packet (6-byte fixed header + 8-byte
> cookie, no payload, L2TP_HDRFLAG_S set), skb->len is exactly 14 after
> __skb_pull(udphdr), so the read at [14, 16) is past the end of the
> linear area — a 2-byte slab-out-of-bounds read (KASAN catchable). The
> subsequent `ptr += 2 + offset` handling for L2TP_HDRFLAG_O can read
> even further out ([18, 20) with the same setup).
>
> Trigger conditions:
> - an L2TPv2 session with peer_cookie_len == 8 (admin-configured),
> - a UDP packet whose L2TP payload is exactly 14 bytes, with
> L2TP_HDRFLAG_S set and the correct cookie.
>
> The peer address is not verified on the UDP path (l2tp_v2_session_get()
> matches only on tunnel_id/session_id), so any host that knows the
> cookie — i.e. a peer of the tunnel, or anyone when no cookie is
> configured (in which case the OOB read does not trigger, since the
> pointer stays at 6 and the seq read lands at [6, 10)) — can send such a
> packet.
>
> This is the L2TPv2 counterpart of the L2TPv3 optional-fields OOB fixed
> in 2019 by commit 4522a70db7aa ("l2tp: fix reading optional fields of
> L2TPv3"), which added l2tp_v3_ensure_opt_in_linear(). The commit
> message of that fix explicitly noted that fixing L2TPv2 as well would
> require refactoring l2tp_recv_common() and was deferred:
>
> To fix both L2TPv3 and L2TPv2, we'd better refactor
> l2tp_recv_common. It's complicated to do so.
>
> L2TPv2 has remained unprotected since. The in-function
> pskb_may_pull(skb, offset) at the end of l2tp_recv_common() runs only
> after all the header reads have already happened, so it cannot help.
>
> A minimal fix for the v2 path could ensure the header is fully linear
> before parsing — e.g. in l2tp_udp_encap_recv(), after determining
> version == L2TP_HDR_VER_2 and before calling l2tp_recv_common():
>
> if (session->tunnel->version == L2TP_HDR_VER_2 &&
> !pskb_may_pull(skb, 6 + session->peer_cookie_len +
> (hdrflags & L2TP_HDRFLAG_S ? 4 : 0)))
> goto invalid;
>
> (or a full refactor pulling the cookie/seq handling into a
> l2tp_v2_ensure_opt_in_linear() helper mirroring the v3 one).
I personally think that a full refactor should first stop mixing L2TPv2
and L2TPv3 in the same code paths. Although these protocols share
similar concepts, they actually don't have much in common from a
technical point of view (different headers, features, transports, etc.).
Starting packet processing with a pskb_may_pull() over actual header
length would make sense but, for L2TPv2, that lenght should be computed
based on L2TPv2 header flags (the Length, Sequence and Offset bits) plus
the Offset Size field if the O bit is set. The ->peer_cookie_len field
should never be considered for L2TPv2.
> Checked against 7.3.0-rc7 and linux-next (2026-10-24): neither carries a
> fix; l2tp_v3_ensure_opt_in_linear() is still v3-only. I found no
> report of this specific OOB on lore/marc.
>
> Best,
> Guikun Yang
>
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-26 11:19 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-25 17:35 [BUG] l2tp: OOB read in l2tp_recv_common() for L2TPv2 sessions with 8-byte peer cookie Guikun Yang
2026-10-26 11:19 ` Guillaume Nault
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®