In 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_flen is __u16 (wrap possible).
(lines 291-307, especially 298)
net/ipv6/datagram.c:ip6_datagram_send_ctl():
- Accepts repeated
IPV6_DSTOPTS and accumulates into opt_flen
without rejecting duplicates. (lines 909-933)
net/ipv6/ip6_output.c:__ip6_append_data():
- Uses
opt->opt_flen + opt->opt_nflen to compute header
sizes/headroom decisions. (lines 1448-1466, especially 1463-1465)
net/ipv6/ip6_output.c:__ip6_make_skb():
- Calls
ipv6_push_frag_opts() if opt->opt_flen is 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_flen is a 16-bit accumulator:
include/net/ipv6.h:298 defines __u16 opt_flen; /* after fragment hdr */.
ip6_datagram_send_ctl() accepts repeated IPV6_DSTOPTS cmsgs
and increments opt_flen each time:
- In
net/ipv6/datagram.c:909-933, for IPV6_DSTOPTS:
- It computes
len = ((hdr->hdrlen + 1) << 3);
- It checks
CAP_NET_RAW using ns_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
dst1opt points 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
dst1opt and is not limited by wrapped opt_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() pushes
dst1opt via ipv6_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() calls skb_under_panic())
-
net/core/skbuff.c:207-214 (skb_panic() ends in BUG())
-
The IPV6_DSTOPTS cmsg path requires CAP_NET_RAW in 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=1000 user can trigger this if unprivileged
user namespaces are enabled and it can create a userns+netns to obtain
namespaced CAP_NET_RAW (the attached PoC does this).
-
Local denial of service: kernel BUG/panic (system crash).
---truncated---
References
In 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:
struct ipv6_txoptions::opt_flen, type__u16) andopt->dst1opt)when multiple
IPV6_DSTOPTScontrol 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():IPV6_DSTOPTSand accumulates intoopt_flenwithout rejecting duplicates. (lines 909-933)
net/ipv6/ip6_output.c:__ip6_append_data():opt->opt_flen + opt->opt_nflento compute headersizes/headroom decisions. (lines 1448-1466, especially 1463-1465)
net/ipv6/ip6_output.c:__ip6_make_skb():ipv6_push_frag_opts()ifopt->opt_flenis non-zero.(lines 1930-1934)
net/ipv6/exthdrs.c:ipv6_push_frag_opts()/ipv6_push_exthdr():ipv6_optlen(opt->dst1opt)(based on thepointed-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_DSTOPTScmsgsand increments
opt_fleneach time:net/ipv6/datagram.c:909-933, forIPV6_DSTOPTS:len = ((hdr->hdrlen + 1) << 3);CAP_NET_RAWusingns_capable(net->user_ns, CAP_NET_RAW). (line 922)opt->opt_flen += len;(line 927)opt->dst1opt = hdr;(line 928)There is no duplicate rejection here (unlike the legacy
IPV6_2292DSTOPTSpath which rejects duplicates atnet/ipv6/datagram.c:901-904).If enough large
IPV6_DSTOPTScmsgs are provided,opt_flenwrapswhile
dst1optstill points to a large (2048-byte)destination-options header.
In the attached PoC (
poc.c):hdrlen=255=>len = (255+1)*8 = 2048hdrlen=0=>len = 832*2048 + 8 = 65544, so(__u16)opt_flen == 8dst1optpoints to a 2048-byte header.opt_flen: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 underestimatewhat will be pushed later.
dst1optand is not limited by wrappedopt_flen:net/ipv6/ip6_output.c:1930-1934:if (opt->opt_flen) proto = ipv6_push_frag_opts(skb, opt, proto);net/ipv6/exthdrs.c:1206-1211,ipv6_push_frag_opts()pushesdst1optviaipv6_push_exthdr().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 triggersskb_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 targetnetns user namespace (
ns_capable(net->user_ns, CAP_NET_RAW)).Root (or any task with
CAP_NET_RAW) can trigger this without usernamespaces.
An unprivileged
uid=1000user can trigger this if unprivilegeduser namespaces are enabled and it can create a userns+netns to obtain
namespaced
CAP_NET_RAW(the attached PoC does this).Local denial of service: kernel BUG/panic (system crash).
---truncated---
References