diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c index 14667dedb745..df31fa7b5064 100644 --- a/net/sched/act_ct.c +++ b/net/sched/act_ct.c @@ -696,8 +696,15 @@ static int tcf_ct_ipv6_is_fragment(struct sk_buff *skb, bool *frag) return 0; } +/* On error, tells the caller whether it still owns @skb and must free it + * itself. @skb is ours only when the header checks below reject the packet + * before it is handed to the defragmentation engine; once nf_ct_handle_ + * fragments() has been called the skb is either queued (-EINPROGRESS) or has + * already been freed by it. + */ static int tcf_ct_handle_fragments(struct net *net, struct sk_buff *skb, - u8 family, u16 zone, bool *defrag) + u8 family, u16 zone, bool *defrag, + bool *skb_is_ours) { enum ip_conntrack_info ctinfo; struct tc_skb_cb cb; @@ -714,8 +721,12 @@ static int tcf_ct_handle_fragments(struct net *net, struct sk_buff *skb, err = tcf_ct_ipv4_is_fragment(skb, &frag); else err = tcf_ct_ipv6_is_fragment(skb, &frag); - if (err || !frag) + if (err) { + *skb_is_ours = true; return err; + } + if (!frag) + return 0; cb = *tc_skb_cb(skb); @@ -961,6 +972,7 @@ static int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, struct nf_hook_state state; int nh_ofs, err, retval; struct tcf_ct_params *p; + bool skb_is_ours = false; bool skip_add = false; bool defrag = false; struct nf_conn *ct; @@ -997,9 +1009,18 @@ static int tcf_ct_act(struct sk_buff *skb, const struct tc_action *a, */ nh_ofs = skb_network_offset(skb); skb_pull_rcsum(skb, nh_ofs); - err = tcf_ct_handle_fragments(net, skb, family, p->zone, &defrag); - if (err) + err = tcf_ct_handle_fragments(net, skb, family, p->zone, &defrag, + &skb_is_ours); + if (err) { + /* The skb is still ours only when the header checks rejected + * it; returning TC_ACT_CONSUMED for such a packet would leak + * it, since no caller frees an skb it was told it no longer + * owns. + */ + if (skb_is_ours) + goto drop; goto out_frag; + } err = tcf_ct_skb_network_trim(skb, family); if (err)