GHSA-2RF4-5672-VQWM
Vulnerability from github – Published: 2026-04-13 15:31 – Updated: 2026-04-18 09:30In the Linux kernel, the following vulnerability has been resolved:
ipv6: avoid overflows in ip6_datagram_send_ctl()
Yiming Qian reported :
I believe I found a locally triggerable kernel bug in the IPv6 sendmsg
ancillary-data path that can panic the kernel via skb_under_panic()
(local DoS).
The core issue is a mismatch between:
- a 16-bit length accumulator (
struct ipv6_txoptions::opt_flen, type__u16) and - a pointer to the last provided destination-options header (
opt->dst1opt)
when multiple IPV6_DSTOPTS control messages (cmsgs) are provided.
include/net/ipv6.h:struct ipv6_txoptions::opt_flenis__u16(wrap possible). (lines 291-307, especially 298)net/ipv6/datagram.c:ip6_datagram_send_ctl():- Accepts repeated
IPV6_DSTOPTSand accumulates intoopt_flenwithout rejecting duplicates. (lines 909-933) net/ipv6/ip6_output.c:__ip6_append_data():- Uses
opt->opt_flen + opt->opt_nflento compute header sizes/headroom decisions. (lines 1448-1466, especially 1463-1465) net/ipv6/ip6_output.c:__ip6_make_skb():- Calls
ipv6_push_frag_opts()ifopt->opt_flenis non-zero. (lines 1930-1934) net/ipv6/exthdrs.c:ipv6_push_frag_opts()/ipv6_push_exthdr():-
Push size comes from
ipv6_optlen(opt->dst1opt)(based on the pointed-to header). (lines 1179-1185 and 1206-1211) -
opt_flenis a 16-bit accumulator: -
include/net/ipv6.h:298defines__u16 opt_flen; /* after fragment hdr */. -
ip6_datagram_send_ctl()accepts repeatedIPV6_DSTOPTScmsgs and incrementsopt_fleneach time: -
In
net/ipv6/datagram.c:909-933, forIPV6_DSTOPTS: - It computes
len = ((hdr->hdrlen + 1) << 3); - It checks
CAP_NET_RAWusingns_capable(net->user_ns, CAP_NET_RAW). (line 922) - Then it does:
opt->opt_flen += len;(line 927)opt->dst1opt = hdr;(line 928)
There is no duplicate rejection here (unlike the legacy
IPV6_2292DSTOPTS path which rejects duplicates at
net/ipv6/datagram.c:901-904).
If enough large IPV6_DSTOPTS cmsgs are provided, opt_flen wraps
while dst1opt still points to a large (2048-byte)
destination-options header.
In the attached PoC (poc.c):
- 32 cmsgs with
hdrlen=255=>len = (255+1)*8 = 2048 - 1 cmsg with
hdrlen=0=>len = 8 - Total increment:
32*2048 + 8 = 65544, so(__u16)opt_flen == 8 -
The last cmsg is 2048 bytes, so
dst1optpoints to a 2048-byte header. -
The transmit path sizes headers using the wrapped
opt_flen: -
In
net/ipv6/ip6_output.c:1463-1465: headersize = sizeof(struct ipv6hdr) + (opt ? opt->opt_flen + opt->opt_nflen : 0) + ...;
With wrapped opt_flen, headersize/headroom decisions underestimate
what will be pushed later.
-
When building the final skb, the actual push length comes from
dst1optand is not limited by wrappedopt_flen: -
In
net/ipv6/ip6_output.c:1930-1934: if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);- In
net/ipv6/exthdrs.c:1206-1211,ipv6_push_frag_opts()pushesdst1optviaipv6_push_exthdr(). - In
net/ipv6/exthdrs.c:1179-1184,ipv6_push_exthdr()does: skb_push(skb, ipv6_optlen(opt));memcpy(h, opt, ipv6_optlen(opt));
With insufficient headroom, skb_push() underflows and triggers
skb_under_panic() -> BUG():
net/core/skbuff.c:2669-2675(skb_push()callsskb_under_panic())-
net/core/skbuff.c:207-214(skb_panic()ends inBUG()) -
The
IPV6_DSTOPTScmsg path requiresCAP_NET_RAWin the target netns user namespace (ns_capable(net->user_ns, CAP_NET_RAW)). - Root (or any task with
CAP_NET_RAW) can trigger this without user namespaces. -
An unprivileged
uid=1000user can trigger this if unprivileged user namespaces are enabled and it can create a userns+netns to obtain namespacedCAP_NET_RAW(the attached PoC does this). -
Local denial of service: kernel BUG/panic (system crash). - ---truncated---
{
"affected": [],
"aliases": [
"CVE-2026-31415"
],
"database_specific": {
"cwe_ids": [],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-13T14:16:10Z",
"severity": null
},
"details": "In the Linux kernel, the following vulnerability has been resolved:\n\nipv6: avoid overflows in ip6_datagram_send_ctl()\n\nYiming Qian reported :\n\u003cquote\u003e\n I believe I found a locally triggerable kernel bug in the IPv6 sendmsg\n ancillary-data path that can panic the kernel via `skb_under_panic()`\n (local DoS).\n\n The core issue is a mismatch between:\n\n - a 16-bit length accumulator (`struct ipv6_txoptions::opt_flen`, type\n `__u16`) and\n - a pointer to the *last* provided destination-options header (`opt-\u003edst1opt`)\n\n when multiple `IPV6_DSTOPTS` control messages (cmsgs) are provided.\n\n - `include/net/ipv6.h`:\n - `struct ipv6_txoptions::opt_flen` is `__u16` (wrap possible).\n (lines 291-307, especially 298)\n - `net/ipv6/datagram.c:ip6_datagram_send_ctl()`:\n - Accepts repeated `IPV6_DSTOPTS` and accumulates into `opt_flen`\n without rejecting duplicates. (lines 909-933)\n - `net/ipv6/ip6_output.c:__ip6_append_data()`:\n - Uses `opt-\u003eopt_flen + opt-\u003eopt_nflen` to compute header\n sizes/headroom decisions. (lines 1448-1466, especially 1463-1465)\n - `net/ipv6/ip6_output.c:__ip6_make_skb()`:\n - Calls `ipv6_push_frag_opts()` if `opt-\u003eopt_flen` is non-zero.\n (lines 1930-1934)\n - `net/ipv6/exthdrs.c:ipv6_push_frag_opts()` / `ipv6_push_exthdr()`:\n - Push size comes from `ipv6_optlen(opt-\u003edst1opt)` (based on the\n pointed-to header). (lines 1179-1185 and 1206-1211)\n\n 1. `opt_flen` is a 16-bit accumulator:\n\n - `include/net/ipv6.h:298` defines `__u16 opt_flen; /* after fragment hdr */`.\n\n 2. `ip6_datagram_send_ctl()` accepts *repeated* `IPV6_DSTOPTS` cmsgs\n and increments `opt_flen` each time:\n\n - In `net/ipv6/datagram.c:909-933`, for `IPV6_DSTOPTS`:\n - It computes `len = ((hdr-\u003ehdrlen + 1) \u003c\u003c 3);`\n - It checks `CAP_NET_RAW` using `ns_capable(net-\u003euser_ns,\n CAP_NET_RAW)`. (line 922)\n - Then it does:\n - `opt-\u003eopt_flen += len;` (line 927)\n - `opt-\u003edst1opt = hdr;` (line 928)\n\n There is no duplicate rejection here (unlike the legacy\n `IPV6_2292DSTOPTS` path which rejects duplicates at\n `net/ipv6/datagram.c:901-904`).\n\n If enough large `IPV6_DSTOPTS` cmsgs are provided, `opt_flen` wraps\n while `dst1opt` still points to a large (2048-byte)\n destination-options header.\n\n In the attached PoC (`poc.c`):\n\n - 32 cmsgs with `hdrlen=255` =\u003e `len = (255+1)*8 = 2048`\n - 1 cmsg with `hdrlen=0` =\u003e `len = 8`\n - Total increment: `32*2048 + 8 = 65544`, so `(__u16)opt_flen == 8`\n - The last cmsg is 2048 bytes, so `dst1opt` points to a 2048-byte header.\n\n 3. The transmit path sizes headers using the wrapped `opt_flen`:\n\n- In `net/ipv6/ip6_output.c:1463-1465`:\n - `headersize = sizeof(struct ipv6hdr) + (opt ? opt-\u003eopt_flen +\n opt-\u003eopt_nflen : 0) + ...;`\n\n With wrapped `opt_flen`, `headersize`/headroom decisions underestimate\n what will be pushed later.\n\n 4. When building the final skb, the actual push length comes from\n `dst1opt` and is not limited by wrapped `opt_flen`:\n\n - In `net/ipv6/ip6_output.c:1930-1934`:\n - `if (opt-\u003eopt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);`\n - In `net/ipv6/exthdrs.c:1206-1211`, `ipv6_push_frag_opts()` pushes\n `dst1opt` via `ipv6_push_exthdr()`.\n - In `net/ipv6/exthdrs.c:1179-1184`, `ipv6_push_exthdr()` does:\n - `skb_push(skb, ipv6_optlen(opt));`\n - `memcpy(h, opt, ipv6_optlen(opt));`\n\n With insufficient headroom, `skb_push()` underflows and triggers\n `skb_under_panic()` -\u003e `BUG()`:\n\n - `net/core/skbuff.c:2669-2675` (`skb_push()` calls `skb_under_panic()`)\n - `net/core/skbuff.c:207-214` (`skb_panic()` ends in `BUG()`)\n\n - The `IPV6_DSTOPTS` cmsg path requires `CAP_NET_RAW` in the target\n netns user namespace (`ns_capable(net-\u003euser_ns, CAP_NET_RAW)`).\n - Root (or any task with `CAP_NET_RAW`) can trigger this without user\n namespaces.\n - An unprivileged `uid=1000` user can trigger this if unprivileged\n user namespaces are enabled and it can create a userns+netns to obtain\n namespaced `CAP_NET_RAW` (the attached PoC does this).\n\n - Local denial of service: kernel BUG/panic (system crash).\n -\n---truncated---",
"id": "GHSA-2rf4-5672-vqwm",
"modified": "2026-04-18T09:30:20Z",
"published": "2026-04-13T15:31:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31415"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/0bdaf54d3aaddfe8df29371260fa8d4939b4fd6f"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/2dbfb003bbf3fc0e94f07efefab0ebcf83029a2a"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/4082f9984a694829153115d28c956a3534f52f29"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/4e453375561fc60820e6b9d8ebeb6b3ee177d42e"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/5e4ee5dbea134e9257f205e31a96040bed71e83f"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/63fda74885555e6bd1623b5d811feec998740ba4"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/872b74900d5daa37067ac676d9001bb929fc6a2a"
},
{
"type": "WEB",
"url": "https://git.kernel.org/stable/c/9ed81d692758dfb9471d7799b24bfa7a08224c31"
}
],
"schema_version": "1.4.0",
"severity": []
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.