Skip to content

Commit

Permalink
compat: nf_defrag_ipv6: avoid nf_iterate recursion.
Browse files Browse the repository at this point in the history
Upstream commit:
    netfilter: ipv6: avoid nf_iterate recursion

    The previous patch changed nf_ct_frag6_gather() to morph reassembled skb
    with the previous one.

    This means that the return value is always NULL or the skb argument.
    So change it to an err value.

    Instead of invoking NF_HOOK recursively with threshold to skip already-called hooks
    we can now just return NF_ACCEPT to move on to the next hook except for
    -EINPROGRESS (which means skb has been queued for reassembly), in which case we
    return NF_STOLEN.

    Signed-off-by: Florian Westphal <[email protected]>
    Signed-off-by: Pablo Neira Ayuso <[email protected]>

Upstream: daaa7d647f81 ("netfilter: ipv6: avoid nf_iterate recursion")
Signed-off-by: Joe Stringer <[email protected]>
Acked-by: Jesse Gross <[email protected]>
  • Loading branch information
joestringer committed May 3, 2016
1 parent 4b65333 commit 2e602ea
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 49 deletions.
11 changes: 5 additions & 6 deletions datapath/conntrack.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ static int handle_fragments(struct net *net, struct sw_flow_key *key,
u16 zone, struct sk_buff *skb)
{
struct ovs_gso_cb ovs_cb = *OVS_GSO_CB(skb);
int err;

if (!skb->dev) {
OVS_NLERR(true, "%s: skb has no dev; dropping", __func__);
Expand All @@ -319,7 +320,6 @@ static int handle_fragments(struct net *net, struct sw_flow_key *key,

if (key->eth.type == htons(ETH_P_IP)) {
enum ip_defrag_users user = IP_DEFRAG_CONNTRACK_IN + zone;
int err;

memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
err = ip_defrag(net, skb, user);
Expand All @@ -330,14 +330,13 @@ static int handle_fragments(struct net *net, struct sw_flow_key *key,
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
} else if (key->eth.type == htons(ETH_P_IPV6)) {
enum ip6_defrag_users user = IP6_DEFRAG_CONNTRACK_IN + zone;
struct sk_buff *reasm;

memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm));
reasm = nf_ct_frag6_gather(net, skb, user);
if (!reasm)
return -EINPROGRESS;
err = nf_ct_frag6_gather(net, skb, user);
if (err)
return err;

key->ip.proto = ipv6_hdr(reasm)->nexthdr;
key->ip.proto = ipv6_hdr(skb)->nexthdr;
ovs_cb.dp_cb.mru = IP6CB(skb)->frag_max_size;
#endif /* IP frag support */
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
#if defined(HAVE_NF_CT_FRAG6_CONSUME_ORIG) || \
defined(HAVE_NF_CT_FRAG6_OUTPUT)
#define OVS_NF_DEFRAG6_BACKPORT 1
struct sk_buff *rpl_nf_ct_frag6_gather(struct net *net, struct sk_buff *skb,
u32 user);
int rpl_nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user);
#define nf_ct_frag6_gather rpl_nf_ct_frag6_gather
#endif /* HAVE_NF_CT_FRAG6_CONSUME_ORIG */

Expand Down
72 changes: 31 additions & 41 deletions datapath/linux/compat/nf_conntrack_reasm.c
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,15 @@ static int nf_ct_frag6_queue(struct frag_queue *fq, struct sk_buff *skb,

/*
* Check if this packet is complete.
* Returns NULL on failure by any reason, and pointer
* to current nexthdr field in reassembled frame.
*
* It is called with locked fq, and caller must check that
* queue is eligible for reassembly i.e. it is not COMPLETE,
* the last and the first frames arrived and all the bits are here.
*
* returns true if *prev skb has been transformed into the reassembled
* skb, false otherwise.
*/
static struct sk_buff *
static bool
nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_device *dev)
{
struct sk_buff *fp, *head = fq->q.fragments;
Expand All @@ -306,22 +307,21 @@ nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_devic

ecn = ip_frag_ecn_table[fq->ecn];
if (unlikely(ecn == 0xff))
goto out_fail;
return false;

/* Unfragmented part is taken from the first segment. */
payload_len = ((head->data - skb_network_header(head)) -
sizeof(struct ipv6hdr) + fq->q.len -
sizeof(struct frag_hdr));
if (payload_len > IPV6_MAXPLEN) {
pr_debug("payload len is too large.\n");
goto out_oversize;
net_dbg_ratelimited("nf_ct_frag6_reasm: payload len = %d\n",
payload_len);
return false;
}

/* Head of list must not be cloned. */
if (skb_unclone(head, GFP_ATOMIC)) {
pr_debug("skb is cloned but can't expand head");
goto out_oom;
}
if (skb_unclone(head, GFP_ATOMIC))
return false;

/* If the first fragment is fragmented itself, we split
* it to two chunks: the first with data and paged part
Expand All @@ -332,7 +332,7 @@ nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_devic

clone = alloc_skb(0, GFP_ATOMIC);
if (clone == NULL)
goto out_oom;
return false;

clone->next = head->next;
head->next = clone;
Expand Down Expand Up @@ -362,7 +362,7 @@ nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_devic

fp = skb_clone(prev, GFP_ATOMIC);
if (!fp)
goto out_oom;
return false;

fp->next = prev->next;
skb_queue_walk(head, iter) {
Expand Down Expand Up @@ -418,16 +418,7 @@ nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_devic
fq->q.fragments = NULL;
fq->q.fragments_tail = NULL;

return head;

out_oversize:
net_dbg_ratelimited("nf_ct_frag6_reasm: payload len = %d\n",
payload_len);
goto out_fail;
out_oom:
net_dbg_ratelimited("nf_ct_frag6_reasm: no memory for reassembly\n");
out_fail:
return NULL;
return true;
}

/*
Expand Down Expand Up @@ -493,28 +484,26 @@ find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff)
return 0;
}

struct sk_buff *rpl_nf_ct_frag6_gather(struct net *net, struct sk_buff *skb,
u32 user)
int rpl_nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)
{
struct net_device *dev = skb->dev;
int fhoff, nhoff, ret;
struct frag_hdr *fhdr;
struct frag_queue *fq;
struct ipv6hdr *hdr;
int fhoff, nhoff;
u8 prevhdr;
struct sk_buff *ret_skb = NULL;

/* Jumbo payload inhibits frag. header */
if (ipv6_hdr(skb)->payload_len == 0) {
pr_debug("payload len = 0\n");
return skb;
return -EINVAL;
}

if (find_prev_fhdr(skb, &prevhdr, &nhoff, &fhoff) < 0)
return skb;
return -EINVAL;

if (!pskb_may_pull(skb, fhoff + sizeof(*fhdr)))
return skb;
return -ENOMEM;

skb_set_transport_header(skb, fhoff);
hdr = ipv6_hdr(skb);
Expand All @@ -523,27 +512,28 @@ struct sk_buff *rpl_nf_ct_frag6_gather(struct net *net, struct sk_buff *skb,
fq = fq_find(net, fhdr->identification, user, &hdr->saddr, &hdr->daddr,
ip6_frag_ecn(hdr));
if (fq == NULL)
return skb;
return -ENOMEM;

spin_lock_bh(&fq->q.lock);

if (nf_ct_frag6_queue(fq, skb, fhdr, nhoff) < 0) {
spin_unlock_bh(&fq->q.lock);
pr_debug("Can't insert skb to queue\n");
inet_frag_put(&fq->q, &nf_frags);
return skb;
ret = -EINVAL;
goto out_unlock;
}

/* after queue has assumed skb ownership, only 0 or -EINPROGRESS
* must be returned.
*/
ret = -EINPROGRESS;
if (qp_flags(fq) == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
fq->q.meat == fq->q.len) {
ret_skb = nf_ct_frag6_reasm(fq, skb, dev);
if (ret_skb == NULL)
pr_debug("Can't reassemble fragmented packets\n");
}
spin_unlock_bh(&fq->q.lock);
fq->q.meat == fq->q.len &&
nf_ct_frag6_reasm(fq, skb, dev))
ret = 0;

out_unlock:
spin_unlock_bh(&fq->q.lock);
inet_frag_put(&fq->q, &nf_frags);
return ret_skb;
return ret;
}
EXPORT_SYMBOL_GPL(rpl_nf_ct_frag6_gather);

Expand Down

0 comments on commit 2e602ea

Please sign in to comment.