On 09.11.2020 21:50, Jakub Kicinski wrote:
> On Mon,  9 Nov 2020 12:52:49 +0100 Georg Kohmann wrote:
>> Packets are processed even though the first fragment don't include all
>> headers through the upper layer header. This breaks TAHI IPv6 Core
>> Conformance Test v6LC.1.3.6.
>>
>> Referring to RFC8200 SECTION 4.5: "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."
>>
>> The fragment needs to be validated the same way it is done in
>> commit 2efdaaaf883a ("IPv6: reply ICMP error if the first fragment don't
>> include all headers") for ipv6. Wrap the validation into a common function,
>> ipv6_frag_validate(). A closer inspection of the existing validation show
>> that it does not fullfill all aspects of RFC 8200, section 4.5, but is at
>> the moment sufficient to pass mentioned TAHI test.
>>
>> In netfilter, utilize the fragment offset returned by find_prev_fhdr() to
>> let ipv6_frag_validate() start it's traverse from the fragment header.
>>
>> Return 0 to drop the fragment in the netfilter. This is the same behaviour
>> as used on other protocol errors in this function, e.g. when
>> nf_ct_frag6_queue() returns -EPROTO. The Fragment will later be picked up
>> by ipv6_frag_rcv() in reassembly.c. ipv6_frag_rcv() will then send an
>> appropriate ICMP Parameter Problem message back to the source.
>>
>> References commit 2efdaaaf883a ("IPv6: reply ICMP error if the first
>> fragment don't include all headers")
> new line here, since the line above is not really a tag.
>
>> Signed-off-by: Georg Kohmann <[email protected]>
>> diff --git a/include/net/ipv6.h b/include/net/ipv6.h
>> index bd1f396..489f3f9 100644
>> --- a/include/net/ipv6.h
>> +++ b/include/net/ipv6.h
>> @@ -1064,6 +1064,8 @@ int ipv6_skip_exthdr(const struct sk_buff *, int 
>> start, u8 *nexthdrp,
>>  
>>  bool ipv6_ext_hdr(u8 nexthdr);
>>  
>> +bool ipv6_frag_validate(struct sk_buff *skb, int start, u8 *nexthdrp);
>> +
>>  enum {
>>      IP6_FH_F_FRAG           = (1 << 0),
>>      IP6_FH_F_AUTH           = (1 << 1),
>> diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c
>> index da46c42..7a94fdf 100644
>> --- a/net/ipv6/exthdrs_core.c
>> +++ b/net/ipv6/exthdrs_core.c
>> @@ -278,3 +278,46 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned 
>> int *offset,
>>      return nexthdr;
>>  }
>>  EXPORT_SYMBOL(ipv6_find_hdr);
>> +
>> +/* Validate that the upper layer header is not truncated in fragment.
>> + *
>> + * This function returns false if a TCP, UDP or ICMP header is truncated
>> + * just before or in the middle of the header. It also returns false if
>> + * any other upper layer header is truncated just before the first byte.
>> + *
>> + * Notes:
>> + * -It does NOT return false if the first fragment where truncated
> More spaces needed, i.e.
>
> Notes:
>  - It...
>
>> + * elsewhere, i.e. between or in the middle of one of the extension
>> + * headers or in the middle of one of the upper layer headers, except for
>> + * TCP, UDP and ICMP.
>> + * -The function also returns true if the fragment is not the first
>> + * fragment.
>> + */
>> +
> no need for a new line here
>
>> +bool ipv6_frag_validate(struct sk_buff *skb, int start, u8 *nexthdrp)
> (a) why place this function in exthdrs_core? I don't see any header
>     specific code here, IMO it belongs in reassembly.c.

ipv6_frag_validate() is used in both reassembly.c and nf_conntrack_reasm.c
Where should I put the prototype so it can be used both places?

>
> (b) the name is a bit broad, how about ipv6_frag_thdr_tuncated() or
>     some such?
Yes agree, much better.
>> +{
>> +    int offset;
>> +    u8 nexthdr = *nexthdrp;
>> +    __be16 frag_off;
> order these longest line to shortest (rev xmas tree) please.
>
>> +
>> +    offset = ipv6_skip_exthdr(skb, start, &nexthdr, &frag_off);
>> +    if (offset >= 0 && !(frag_off & htons(IP6_OFFSET))) {
> nit: since this is a function now you can reverse the condition, return
> early, and save the indentation level in all the code below
>
>> +            switch (nexthdr) {
>> +            case NEXTHDR_TCP:
>> +                    offset += sizeof(struct tcphdr);
>> +                    break;
>> +            case NEXTHDR_UDP:
>> +                    offset += sizeof(struct udphdr);
>> +                    break;
>> +            case NEXTHDR_ICMP:
>> +                    offset += sizeof(struct icmp6hdr);
>> +                    break;
>> +            default:
>> +                    offset += 1;
>> +            }
>> +            if (offset > skb->len)
>> +                    return false;
>> +    }
>> +    return true;
>> +}
>> +EXPORT_SYMBOL(ipv6_frag_validate);
>> diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c 
>> b/net/ipv6/netfilter/nf_conntrack_reasm.c
>> index 054d287..f6cae28 100644
>> --- a/net/ipv6/netfilter/nf_conntrack_reasm.c
>> +++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
>> @@ -445,6 +445,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff 
>> *skb, u32 user)
>>      struct frag_queue *fq;
>>      struct ipv6hdr *hdr;
>>      u8 prevhdr;
>> +    u8 nexthdr = NEXTHDR_FRAGMENT;
> rev xmas tree
>
>>      /* Jumbo payload inhibits frag. header */
>>      if (ipv6_hdr(skb)->payload_len == 0) {
>> @@ -455,6 +456,14 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff 
>> *skb, u32 user)
>>      if (find_prev_fhdr(skb, &prevhdr, &nhoff, &fhoff) < 0)
>>              return 0;
>>  
>> +    /* Discard the first fragment if it does not include all headers
>> +     * RFC 8200, Section 4.5
>> +     */
>> +    if (!ipv6_frag_validate(skb, fhoff, &nexthdr)) {
>> +            pr_debug("Drop incomplete fragment\n");
>> +            return 0;
>> +    }
>>
>>      if (!pskb_may_pull(skb, fhoff + sizeof(*fhdr)))
>>              return -ENOMEM;
>>  
>> diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c
>> index c8cf1bb..04e078e 100644
>> --- a/net/ipv6/reassembly.c
>> +++ b/net/ipv6/reassembly.c
>> @@ -324,8 +324,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
>>      struct frag_queue *fq;
>>      const struct ipv6hdr *hdr = ipv6_hdr(skb);
>>      struct net *net = dev_net(skb_dst(skb)->dev);
>> -    __be16 frag_off;
>> -    int iif, offset;
>> +    int iif;
> rev xmas tree
>
>>      u8 nexthdr;
>>  
>>      if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED)
>> @@ -362,24 +361,11 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
>>       * 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;
>> -            }
>> +    if (!ipv6_frag_validate(skb, skb_transport_offset(skb), &nexthdr)) {
>> +            __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;
Thanks for reviewing, with good input. Please see question above.

Georg

Reply via email to