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_flenis__u16(wrap possible).
net/ipv6/datagram.c:ip6_datagram_send_ctl():- Accepts repeated
IPV6_DSTOPTSand accumulates intoopt_flen
net/ipv6/ip6_output.c:__ip6_append_data():- Uses
opt->opt_flen + opt->opt_nflento compute header
net/ipv6/ip6_output.c:__ip6_make_skb():- Calls
ipv6_push_frag_opts()ifopt->opt_flenis non-zero.
net/ipv6/exthdrs.c:ipv6_push_frag_opts()/ipv6_push_exthdr():- Push size comes from
ipv6_optlen(opt->dst1opt)(based on the
opt_flenis a 16-bit accumulator:include/net/ipv6.h:298defines__u16 opt_flen; /* after fragment hdr */.ip6_datagram_send_ctl()accepts *repeated*IPV6_DSTOPTScmsgs
opt_flen each 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,
- 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 +
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()callsskb_under_panic())net/core/skbuff.c:207-214(skb_panic()ends inBUG())- The
IPV6_DSTOPTScmsg path requiresCAP_NET_RAWin the target
ns_capable(net->user_ns, CAP_NET_RAW)).
- Root (or any task with
CAP_NET_RAW) can trigger this without user
- An unprivileged
uid=1000user can trigger this if unprivileged
CAP_NET_RAW (the attached PoC does this).
- Local denial of service: kernel BUG/panic (system crash).
- ---truncated---