mirror of
https://github.com/torvalds/linux.git
synced 2024-12-26 21:02:19 +00:00
ipv6/netfilter: Discard first fragment not including all headers
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 commit2efdaaaf88
("IPv6: reply ICMP error if the first fragment don't include all headers") for ipv6. Wrap the validation into a common function, ipv6_frag_thdr_truncated() to check for truncation in the upper layer header. This validation 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_thdr_truncated() 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 commit2efdaaaf88
("IPv6: reply ICMP error if the first fragment don't include all headers") Signed-off-by: Georg Kohmann <geokohma@cisco.com> Acked-by: Pablo Neira Ayuso <pablo@netfilter.org> Link: https://lore.kernel.org/r/20201111115025.28879-1-geokohma@cisco.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
7953446d66
commit
9d9e937b1c
@ -1064,6 +1064,8 @@ int ipv6_skip_exthdr(const struct sk_buff *, int start, u8 *nexthdrp,
|
|||||||
|
|
||||||
bool ipv6_ext_hdr(u8 nexthdr);
|
bool ipv6_ext_hdr(u8 nexthdr);
|
||||||
|
|
||||||
|
bool ipv6_frag_thdr_truncated(struct sk_buff *skb, int start, u8 *nexthdrp);
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
IP6_FH_F_FRAG = (1 << 0),
|
IP6_FH_F_FRAG = (1 << 0),
|
||||||
IP6_FH_F_AUTH = (1 << 1),
|
IP6_FH_F_AUTH = (1 << 1),
|
||||||
|
@ -440,6 +440,7 @@ find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff)
|
|||||||
int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)
|
int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)
|
||||||
{
|
{
|
||||||
u16 savethdr = skb->transport_header;
|
u16 savethdr = skb->transport_header;
|
||||||
|
u8 nexthdr = NEXTHDR_FRAGMENT;
|
||||||
int fhoff, nhoff, ret;
|
int fhoff, nhoff, ret;
|
||||||
struct frag_hdr *fhdr;
|
struct frag_hdr *fhdr;
|
||||||
struct frag_queue *fq;
|
struct frag_queue *fq;
|
||||||
@ -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)
|
if (find_prev_fhdr(skb, &prevhdr, &nhoff, &fhoff) < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
/* Discard the first fragment if it does not include all headers
|
||||||
|
* RFC 8200, Section 4.5
|
||||||
|
*/
|
||||||
|
if (ipv6_frag_thdr_truncated(skb, fhoff, &nexthdr)) {
|
||||||
|
pr_debug("Drop incomplete fragment\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pskb_may_pull(skb, fhoff + sizeof(*fhdr)))
|
if (!pskb_may_pull(skb, fhoff + sizeof(*fhdr)))
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
@ -318,15 +318,43 @@ out_fail:
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check if the upper layer header is truncated in the first fragment. */
|
||||||
|
bool ipv6_frag_thdr_truncated(struct sk_buff *skb, int start, u8 *nexthdrp)
|
||||||
|
{
|
||||||
|
u8 nexthdr = *nexthdrp;
|
||||||
|
__be16 frag_off;
|
||||||
|
int offset;
|
||||||
|
|
||||||
|
offset = ipv6_skip_exthdr(skb, start, &nexthdr, &frag_off);
|
||||||
|
if (offset < 0 || (frag_off & htons(IP6_OFFSET)))
|
||||||
|
return false;
|
||||||
|
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 true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(ipv6_frag_thdr_truncated);
|
||||||
|
|
||||||
static int ipv6_frag_rcv(struct sk_buff *skb)
|
static int ipv6_frag_rcv(struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
struct frag_hdr *fhdr;
|
struct frag_hdr *fhdr;
|
||||||
struct frag_queue *fq;
|
struct frag_queue *fq;
|
||||||
const struct ipv6hdr *hdr = ipv6_hdr(skb);
|
const struct ipv6hdr *hdr = ipv6_hdr(skb);
|
||||||
struct net *net = dev_net(skb_dst(skb)->dev);
|
struct net *net = dev_net(skb_dst(skb)->dev);
|
||||||
__be16 frag_off;
|
|
||||||
int iif, offset;
|
|
||||||
u8 nexthdr;
|
u8 nexthdr;
|
||||||
|
int iif;
|
||||||
|
|
||||||
if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED)
|
if (IP6CB(skb)->flags & IP6SKB_FRAGMENTED)
|
||||||
goto fail_hdr;
|
goto fail_hdr;
|
||||||
@ -362,24 +390,11 @@ static int ipv6_frag_rcv(struct sk_buff *skb)
|
|||||||
* the source of the fragment, with the Pointer field set to zero.
|
* the source of the fragment, with the Pointer field set to zero.
|
||||||
*/
|
*/
|
||||||
nexthdr = hdr->nexthdr;
|
nexthdr = hdr->nexthdr;
|
||||||
offset = ipv6_skip_exthdr(skb, skb_transport_offset(skb), &nexthdr, &frag_off);
|
if (ipv6_frag_thdr_truncated(skb, skb_transport_offset(skb), &nexthdr)) {
|
||||||
if (offset >= 0) {
|
__IP6_INC_STATS(net, __in6_dev_get_safely(skb->dev),
|
||||||
/* Check some common protocols' header */
|
IPSTATS_MIB_INHDRERRORS);
|
||||||
if (nexthdr == IPPROTO_TCP)
|
icmpv6_param_prob(skb, ICMPV6_HDR_INCOMP, 0);
|
||||||
offset += sizeof(struct tcphdr);
|
return -1;
|
||||||
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;
|
iif = skb->dev ? skb->dev->ifindex : 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user