| From 2efdaaaf883a143061296467913c01aa1ff4b3ce Mon Sep 17 00:00:00 2001 |
| From: Hangbin Liu <liuhangbin@gmail.com> |
| Date: Tue, 27 Oct 2020 20:33:13 +0800 |
| Subject: IPv6: reply ICMP error if the first fragment don't include all headers |
| |
| From: Hangbin Liu <liuhangbin@gmail.com> |
| |
| commit 2efdaaaf883a143061296467913c01aa1ff4b3ce upstream. |
| |
| Based on RFC 8200, Section 4.5 Fragment Header: |
| |
| - If the first fragment does not include all headers through an |
| Upper-Layer header, then that fragment should be discarded and |
| an ICMP Parameter Problem, Code 3, message should be sent to |
| the source of the fragment, with the Pointer field set to zero. |
| |
| Checking each packet header in IPv6 fast path will have performance impact, |
| so I put the checking in ipv6_frag_rcv(). |
| |
| As the packet may be any kind of L4 protocol, I only checked some common |
| protocols' header length and handle others by (offset + 1) > skb->len. |
| Also use !(frag_off & htons(IP6_OFFSET)) to catch atomic fragments |
| (fragmented packet with only one fragment). |
| |
| When send ICMP error message, if the 1st truncated fragment is ICMP message, |
| icmp6_send() will break as is_ineligible() return true. So I added a check |
| in is_ineligible() to let fragment packet with nexthdr ICMP but no ICMP header |
| return false. |
| |
| Signed-off-by: Hangbin Liu <liuhangbin@gmail.com> |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Aviraj CJ <acj@cisco.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/ipv6/icmp.c | 8 +++++++- |
| net/ipv6/reassembly.c | 33 ++++++++++++++++++++++++++++++++- |
| 2 files changed, 39 insertions(+), 2 deletions(-) |
| |
| --- a/net/ipv6/icmp.c |
| +++ b/net/ipv6/icmp.c |
| @@ -158,7 +158,13 @@ static bool is_ineligible(const struct s |
| tp = skb_header_pointer(skb, |
| ptr+offsetof(struct icmp6hdr, icmp6_type), |
| sizeof(_type), &_type); |
| - if (!tp || !(*tp & ICMPV6_INFOMSG_MASK)) |
| + |
| + /* Based on RFC 8200, Section 4.5 Fragment Header, return |
| + * false if this is a fragment packet with no icmp header info. |
| + */ |
| + if (!tp && frag_off != 0) |
| + return false; |
| + else if (!tp || !(*tp & ICMPV6_INFOMSG_MASK)) |
| return true; |
| } |
| return false; |
| --- a/net/ipv6/reassembly.c |
| +++ b/net/ipv6/reassembly.c |
| @@ -42,6 +42,8 @@ |
| #include <linux/skbuff.h> |
| #include <linux/slab.h> |
| #include <linux/export.h> |
| +#include <linux/tcp.h> |
| +#include <linux/udp.h> |
| |
| #include <net/sock.h> |
| #include <net/snmp.h> |
| @@ -322,7 +324,9 @@ static int ipv6_frag_rcv(struct sk_buff |
| struct frag_queue *fq; |
| const struct ipv6hdr *hdr = ipv6_hdr(skb); |
| struct net *net = dev_net(skb_dst(skb)->dev); |
| - int iif; |
| + __be16 frag_off; |
| + int iif, offset; |
| + u8 nexthdr; |
| |
| if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED) |
| goto fail_hdr; |
| @@ -351,6 +355,33 @@ static int ipv6_frag_rcv(struct sk_buff |
| return 1; |
| } |
| |
| + /* RFC 8200, Section 4.5 Fragment Header: |
| + * If the first fragment does not include all headers through an |
| + * Upper-Layer header, then that fragment should be discarded and |
| + * an ICMP Parameter Problem, Code 3, message should be sent to |
| + * the source of the fragment, with the Pointer field set to zero. |
| + */ |
| + nexthdr = hdr->nexthdr; |
| + offset = ipv6_skip_exthdr(skb, skb_transport_offset(skb), &nexthdr, &frag_off); |
| + if (offset >= 0) { |
| + /* Check some common protocols' header */ |
| + if (nexthdr == IPPROTO_TCP) |
| + offset += sizeof(struct tcphdr); |
| + else if (nexthdr == IPPROTO_UDP) |
| + offset += sizeof(struct udphdr); |
| + else if (nexthdr == IPPROTO_ICMPV6) |
| + offset += sizeof(struct icmp6hdr); |
| + else |
| + offset += 1; |
| + |
| + if (!(frag_off & htons(IP6_OFFSET)) && offset > skb->len) { |
| + __IP6_INC_STATS(net, __in6_dev_get_safely(skb->dev), |
| + IPSTATS_MIB_INHDRERRORS); |
| + icmpv6_param_prob(skb, ICMPV6_HDR_INCOMP, 0); |
| + return -1; |
| + } |
| + } |
| + |
| iif = skb->dev ? skb->dev->ifindex : 0; |
| fq = fq_find(net, fhdr->identification, hdr, iif); |
| if (fq) { |