FAILED: patch "[PATCH] inet: frags: publish queues before arming timer" failed to apply to 6.12-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x 653d7ddf6cba867777a3d14c4f83ace008c5ad13
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026081749-cash-armed-755d@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 653d7ddf6cba867777a3d14c4f83ace008c5ad13 Mon Sep 17 00:00:00 2001
From: Zhiling Zou <[email protected]>
Date: Tue, 28 Jul 2026 01:23:29 +0800
Subject: [PATCH] inet: frags: publish queues before arming timer
inet_frag_create() arms the fragment queue timer before inserting the queue into the fqdir rhashtable. If the namespace fragment timeout is zero or negative, the timer can run before the queue is published.
The timer callback then marks the queue complete, tries to remove a node that is not in the hash table yet, and drops the anticipated hash reference. Creation can subsequently publish the completed queue without restoring that reference, leaving a stale hash node after the caller drops the remaining reference.
Publish the queue first and arm the timer while holding the queue lock. This makes timer expiry wait until the queue is visible in the hash table, so inet_frag_kill() can remove the node and balance the hash reference.
net/ipv4/inet_fragment.c +5 -5
@@ -393,8 +393,8 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, *prev =ERR_PTR (-ENOMEM );return NULL ; } -mod_timer (&q->timer, jiffies + fqdir->timeout); +spin_lock_bh (&q->lock); *prev =rhashtable_lookup_get_insert_key (&fqdir->rhashtable, &q->key, &q->node, f->rhash_params);if (*prev) {
@@ -402,13 +402,13 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir,* we need to cancel what inet_frag_alloc() * anticipated. */ -int refs =1 ; - q->flags |=INET_FRAG_COMPLETE ; -inet_frag_kill (q, &refs); -inet_frag_putn (q, refs); +spin_unlock_bh (&q->lock); +inet_frag_putn (q,2 );return NULL ; } +mod_timer (&q->timer, jiffies + fqdir->timeout); +spin_unlock_bh (&q->lock);return q; }
Sasha Levin
maintainerPATCH 1/5 inet: frags: add inet_frag_putn() helper
From: Eric Dumazet <[email protected]>
[ Upstream commit ae2d90355aa5592b0e99c8bbb4c3fa1d8e205f1b ]
inet_frag_putn() can release multiple references
in one step.
Use it in inet_frags_free_cb().
Replace inet_frag_put(X) with inet_frag_putn(X, 1)
Stable-dep-of: 653d7ddf6cba ("inet: frags: publish queues before arming timer")
--- include/net/inet_frag.h | 4 ++-- include/net/ipv6_frag.h | 3 ++- net/ieee802154/6lowpan/reassembly.c | 7 ++++--- net/ipv4/inet_fragment.c | 3 +-- net/ipv4/ip_fragment.c | 2 +- net/ipv6/netfilter/nf_conntrack_reasm.c | 3 ++- net/ipv6/reassembly.c | 4 ++-- 7 files changed, 14 insertions(+), 12 deletions(-)
include/net/inet_frag.h +2 -2
@@ -133,9 +133,9 @@ struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key);void inet_frag_queue_flush (struct inet_frag_queue *q,enum skb_drop_reason reason); -static inline void inet_frag_put (struct inet_frag_queue *q) +static inline void inet_frag_putn (struct inet_frag_queue *q,int refs) { -if (refcount_dec_and_test (&q->refcnt)) +if (refs &&refcount_sub_and_test (refs, &q->refcnt))inet_frag_destroy (q); }
include/net/ipv6_frag.h +2 -1
@@ -66,6 +66,7 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq) {struct net_device *dev =NULL ;struct sk_buff *head; +int refs =1 ;rcu_read_lock ();spin_lock (&fq->q.lock);
@@ -112,7 +113,7 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq)spin_unlock (&fq->q.lock); out_rcu_unlock:rcu_read_unlock (); -inet_frag_put (&fq->q); +inet_frag_putn (&fq->q, refs); }/* Check if the upper layer header is truncated in the first fragment. */
net/ieee802154/6lowpan/reassembly.c +4 -3
@@ -45,6 +45,7 @@ static void lowpan_frag_expire(struct timer_list *t) {struct inet_frag_queue *frag =from_timer (frag, t, timer);struct frag_queue *fq; +int refs =1 ; fq = container_of(frag, struct frag_queue, q);
@@ -56,7 +57,7 @@ static void lowpan_frag_expire(struct timer_list *t)inet_frag_kill (&fq->q); out:spin_unlock (&fq->q.lock); -inet_frag_put (&fq->q); +inet_frag_putn (&fq->q, refs); } static inline struct lowpan_frag_queue *
@@ -302,13 +303,13 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) fq =fq_find (net, cb, &hdr.source, &hdr.dest);if (fq !=NULL ) { -int ret; +int ret, refs =1 ;spin_lock (&fq->q.lock); ret =lowpan_frag_queue (fq, skb, frag_type);spin_unlock (&fq->q.lock); -inet_frag_put (&fq->q); +inet_frag_putn (&fq->q, refs);return ret; }
net/ipv4/inet_fragment.c +1 -2
@@ -145,8 +145,7 @@ static void inet_frags_free_cb(void *ptr, void *arg) }spin_unlock_bh (&fq->lock); -if (refcount_sub_and_test (count, &fq->refcnt)) -inet_frag_destroy (fq); +inet_frag_putn (fq, count); }static LLIST_HEAD (fqdir_free_list);
net/ipv4/ip_fragment.c +1 -1
@@ -112,7 +112,7 @@ static void ip4_frag_free(struct inet_frag_queue *q)static void ipq_put (struct ipq *ipq) { -inet_frag_put (&ipq->q); +inet_frag_putn (&ipq->q,1 ); }/* Kill ipq entry. It is not destroyed immediately,
net/ipv6/netfilter/nf_conntrack_reasm.c +2 -1
@@ -448,6 +448,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)struct frag_hdr *fhdr;struct frag_queue *fq;struct ipv6hdr *hdr; +int refs =1 ;u8 prevhdr;/* Jumbo payload inhibits frag. header */
@@ -490,7 +491,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user) }spin_unlock_bh (&fq->q.lock); -inet_frag_put (&fq->q); +inet_frag_putn (&fq->q, refs);return ret; }EXPORT_SYMBOL_GPL (nf_ct_frag6_gather);
net/ipv6/reassembly.c +2 -2
@@ -380,7 +380,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) fq =fq_find (net, fhdr->identification, hdr, iif);if (fq) {u32 prob_offset =0 ; -int ret; +int ret, refs =1 ;spin_lock (&fq->q.lock);
@@ -389,7 +389,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) &prob_offset);spin_unlock (&fq->q.lock); -inet_frag_put (&fq->q); +inet_frag_putn (&fq->q, refs);if (prob_offset) {__IP6_INC_STATS (net,__in6_dev_get_safely (skb->dev),IPSTATS_MIB_INHDRERRORS );
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/5 ipv4: frags: remove ipq_put()
From: Eric Dumazet <[email protected]>
[ Upstream commit a2fb987c0ecf0498cc17056339cb11d128c46ab7 ]
Replace ipq_put() with inet_frag_putn()
Stable-dep-of: 653d7ddf6cba ("inet: frags: publish queues before arming timer")
--- net/ipv4/ip_fragment.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-)
net/ipv4/ip_fragment.c +4 -11
@@ -107,14 +107,6 @@ static void ip4_frag_free(struct inet_frag_queue *q)inet_putpeer (qp->peer); } - -/* Destruction primitives. */ - -static void ipq_put (struct ipq *ipq) -{ -inet_frag_putn (&ipq->q,1 ); -} -/* Kill ipq entry. It is not destroyed immediately, * because caller (and someone more) holds reference count. */
@@ -142,6 +134,7 @@ static void ip_expire(struct timer_list *t)struct sk_buff *head =NULL ;struct net *net;struct ipq *qp; +int refs =1 ;int err; qp = container_of(frag, struct ipq, q);
@@ -203,7 +196,7 @@ static void ip_expire(struct timer_list *t) out_rcu_unlock:rcu_read_unlock ();kfree_skb_reason (head,SKB_DROP_REASON_FRAG_REASM_TIMEOUT ); -ipq_put (qp); +inet_frag_putn (&qp->q, refs); }/* Find the correct entry in the "incomplete datagrams" queue for
@@ -492,14 +485,14 @@ int ip_defrag(struct net *net, struct sk_buff *skb, u32 user)/* Lookup (or create) queue header */ qp =ip_find (net,ip_hdr (skb), user, vif);if (qp) { -int ret; +int ret, refs =1 ;spin_lock (&qp->q.lock); ret =ip_frag_queue (qp, skb);spin_unlock (&qp->q.lock); -ipq_put (qp); +inet_frag_putn (&qp->q, refs);return ret; }
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/5 inet: frags: change inet_frag_kill() to defer refcount updates
In reply to Sasha LevinFrom: Eric Dumazet <[email protected]>
[ Upstream commit eb0dfc0ef195a04e519b15d73cf25d8c25ee8df7 ]
In the following patch, we no longer assume inet_frag_kill() callers own a reference.
Consuming two refcounts from inet_frag_kill() would lead in UAF.
Propagate the pointer to the refs that will be consumed later by the final inet_frag_putn() call.
Stable-dep-of: 653d7ddf6cba ("inet: frags: publish queues before arming timer")
--- include/net/inet_frag.h | 2 +- include/net/ipv6_frag.h | 2 +- net/ieee802154/6lowpan/reassembly.c | 17 ++++++++------ net/ipv4/inet_fragment.c | 12 +++++----- net/ipv4/ip_fragment.c | 30 ++++++++++--------------- net/ipv6/netfilter/nf_conntrack_reasm.c | 21 +++++++++-------- net/ipv6/reassembly.c | 18 ++++++++------- 7 files changed, 53 insertions(+), 49 deletions(-)
include/net/inet_frag.h +1 -1
@@ -126,7 +126,7 @@ int fqdir_init(struct fqdir **fqdirp, struct inet_frags *f, struct net *net);void fqdir_pre_exit (struct fqdir *fqdir);void fqdir_exit (struct fqdir *fqdir); -void inet_frag_kill (struct inet_frag_queue *q); +void inet_frag_kill (struct inet_frag_queue *q,int *refs);void inet_frag_destroy (struct inet_frag_queue *q);struct inet_frag_queue *inet_frag_find (struct fqdir *fqdir,void *key);
include/net/ipv6_frag.h +1 -1
@@ -75,7 +75,7 @@ ip6frag_expire_frag_queue(struct net *net, struct frag_queue *fq) goto out; fq->q.flags |=INET_FRAG_DROP ; -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, &refs);/* Paired with the WRITE_ONCE() in fqdir_pre_exit(). */ if (READ_ONCE (fq->q.fqdir->dead)) {
net/ieee802154/6lowpan/reassembly.c +10 -7
@@ -31,7 +31,8 @@ static const char lowpan_frags_cache_name[] = "lowpan-frags";static struct inet_frags lowpan_frags;static int lowpan_frag_reasm (struct lowpan_frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev,struct net_device *ldev); +struct sk_buff *prev,struct net_device *ldev, +int *refs);static void lowpan_frag_init (struct inet_frag_queue *q,const void *a) {
@@ -54,7 +55,7 @@ static void lowpan_frag_expire(struct timer_list *t)if (fq->q.flags &INET_FRAG_COMPLETE ) goto out; -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, &refs); out:spin_unlock (&fq->q.lock);inet_frag_putn (&fq->q, refs);
@@ -83,7 +84,8 @@ fq_find(struct net *net, const struct lowpan_802154_cb *cb, }static int lowpan_frag_queue (struct lowpan_frag_queue *fq, -struct sk_buff *skb,u8 frag_type) +struct sk_buff *skb,u8 frag_type, +int *refs) {struct sk_buff *prev_tail;struct net_device *ldev;
@@ -144,7 +146,7 @@ static int lowpan_frag_queue(struct lowpan_frag_queue *fq,unsigned long orefdst = skb->_skb_refdst; skb->_skb_refdst =0UL ; - res =lowpan_frag_reasm (fq, skb, prev_tail, ldev); + res =lowpan_frag_reasm (fq, skb, prev_tail, ldev, refs); skb->_skb_refdst = orefdst;return res; }
@@ -163,11 +165,12 @@ static int lowpan_frag_queue(struct lowpan_frag_queue *fq,* the last and the first frames arrived and all the bits are here. */ static int lowpan_frag_reasm (struct lowpan_frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *ldev) +struct sk_buff *prev_tail,struct net_device *ldev, +int *refs) {void *reasm_data; -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs); reasm_data =inet_frag_reasm_prepare (&fq->q, skb, prev_tail);if (!reasm_data)
@@ -306,7 +309,7 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type)int ret, refs =1 ;spin_lock (&fq->q.lock); - ret =lowpan_frag_queue (fq, skb, frag_type); + ret =lowpan_frag_queue (fq, skb, frag_type, &refs);spin_unlock (&fq->q.lock);inet_frag_putn (&fq->q, refs);
net/ipv4/inet_fragment.c +7 -5
@@ -260,10 +260,10 @@ void fqdir_exit(struct fqdir *fqdir) }EXPORT_SYMBOL (fqdir_exit); -void inet_frag_kill (struct inet_frag_queue *fq) +void inet_frag_kill (struct inet_frag_queue *fq,int *refs) {if (del_timer (&fq->timer)) -refcount_dec (&fq->refcnt); + (*refs)++;if (!(fq->flags &INET_FRAG_COMPLETE )) {struct fqdir *fqdir = fq->fqdir;
@@ -278,7 +278,7 @@ void inet_frag_kill(struct inet_frag_queue *fq)if (!READ_ONCE (fqdir->dead)) {rhashtable_remove_fast (&fqdir->rhashtable, &fq->node, fqdir->f->rhash_params); -refcount_dec (&fq->refcnt); + (*refs)++; }else { fq->flags |=INET_FRAG_HASH_DEAD ; }
@@ -397,9 +397,11 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, *prev =rhashtable_lookup_get_insert_key (&fqdir->rhashtable, &q->key, &q->node, f->rhash_params);if (*prev) { +int refs =2 ; + q->flags |=INET_FRAG_COMPLETE ; -inet_frag_kill (q); -inet_frag_destroy (q); +inet_frag_kill (q, &refs); +inet_frag_putn (q, refs);return NULL ; }return q;
net/ipv4/ip_fragment.c +12 -18
@@ -76,7 +76,8 @@ static u8 ip4_frag_ecn(u8 tos)static struct inet_frags ip4_frags;static int ip_frag_reasm (struct ipq *qp,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev); +struct sk_buff *prev_tail,struct net_device *dev, +int *refs);static void ip4_frag_init (struct inet_frag_queue *q,const void *a)
@@ -107,14 +108,6 @@ static void ip4_frag_free(struct inet_frag_queue *q)inet_putpeer (qp->peer); } -/* Kill ipq entry. It is not destroyed immediately, -* because caller (and someone more) holds reference count. -*/ -static void ipq_kill (struct ipq *ipq) -{ -inet_frag_kill (&ipq->q); -} -static bool frag_expire_skip_icmp (u32 user) { return user == IP_DEFRAG_AF_PACKET ||
@@ -147,7 +140,7 @@ static void ip_expire(struct timer_list *t) goto out; qp->q.flags |=INET_FRAG_DROP ; -ipq_kill (qp); +inet_frag_kill (&qp->q, &refs);/* Paired with WRITE_ONCE() in fqdir_pre_exit(). */ if (READ_ONCE (qp->q.fqdir->dead)) {
@@ -265,7 +258,7 @@ static int ip_frag_reinit(struct ipq *qp) }/* Add new segment to existing queue. */ -static int ip_frag_queue (struct ipq *qp,struct sk_buff *skb) +static int ip_frag_queue (struct ipq *qp,struct sk_buff *skb,int *refs) {struct net *net = qp->q.fqdir->net;int ihl, end, flags, offset;
@@ -285,7 +278,7 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)if (!(IPCB (skb)->flags &IPSKB_FRAG_COMPLETE ) &&unlikely (ip_frag_too_far (qp)) &&unlikely (err =ip_frag_reinit (qp))) { -ipq_kill (qp); +inet_frag_kill (&qp->q, refs); goto err; }
@@ -369,10 +362,10 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)unsigned long orefdst = skb->_skb_refdst; skb->_skb_refdst =0UL ; - err =ip_frag_reasm (qp, skb, prev_tail, dev); + err =ip_frag_reasm (qp, skb, prev_tail, dev, refs); skb->_skb_refdst = orefdst;if (err) -inet_frag_kill (&qp->q); +inet_frag_kill (&qp->q, refs);return err; }
@@ -389,7 +382,7 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb) err = -EINVAL ;__IP_INC_STATS (net,IPSTATS_MIB_REASM_OVERLAPS ); discard_qp: -inet_frag_kill (&qp->q); +inet_frag_kill (&qp->q, refs);__IP_INC_STATS (net,IPSTATS_MIB_REASMFAILS ); err:kfree_skb_reason (skb, reason);
@@ -403,7 +396,8 @@ static bool ip_frag_coalesce_ok(const struct ipq *qp)/* Build a new IP datagram from all its fragments. */ static int ip_frag_reasm (struct ipq *qp,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev) +struct sk_buff *prev_tail,struct net_device *dev, +int *refs) {struct net *net = qp->q.fqdir->net;struct iphdr *iph;
@@ -411,7 +405,7 @@ static int ip_frag_reasm(struct ipq *qp, struct sk_buff *skb,int len, err;u8 ecn; -ipq_kill (qp); +inet_frag_kill (&qp->q, refs); ecn = ip_frag_ecn_table[qp->ecn];if (unlikely (ecn ==0xff )) {
@@ -489,7 +483,7 @@ int ip_defrag(struct net *net, struct sk_buff *skb, u32 user)spin_lock (&qp->q.lock); - ret =ip_frag_queue (qp, skb); + ret =ip_frag_queue (qp, skb, &refs);spin_unlock (&qp->q.lock);inet_frag_putn (&qp->q, refs);
net/ipv6/netfilter/nf_conntrack_reasm.c +12 -9
@@ -123,7 +123,8 @@ static void __net_exit nf_ct_frags6_sysctl_unregister(struct net *net)#endif static int nf_ct_frag6_reasm (struct frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev); +struct sk_buff *prev_tail,struct net_device *dev, +int *refs);static inline u8 ip6_frag_ecn (const struct ipv6hdr *ipv6h) {
@@ -167,7 +168,8 @@ static struct frag_queue *fq_find(struct net *net, __be32 id, u32 user,static int nf_ct_frag6_queue (struct frag_queue *fq,struct sk_buff *skb, -const struct frag_hdr *fhdr,int nhoff) +const struct frag_hdr *fhdr,int nhoff, +int *refs) {unsigned int payload_len;struct net_device *dev;
@@ -221,7 +223,7 @@ static int nf_ct_frag6_queue(struct frag_queue *fq, struct sk_buff *skb,* this case. -DaveM */ pr_debug ("end of fragment not rounded to 8 bytes.\n" ); -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs);return -EPROTO ; }if (end > fq->q.len) {
@@ -287,7 +289,7 @@ static int nf_ct_frag6_queue(struct frag_queue *fq, struct sk_buff *skb,unsigned long orefdst = skb->_skb_refdst; skb->_skb_refdst =0UL ; - err =nf_ct_frag6_reasm (fq, skb, prev, dev); + err =nf_ct_frag6_reasm (fq, skb, prev, dev, refs); skb->_skb_refdst = orefdst;/* After queue has assumed skb ownership, only 0 or
@@ -301,7 +303,7 @@ static int nf_ct_frag6_queue(struct frag_queue *fq, struct sk_buff *skb,return -EINPROGRESS ; insert_error: -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs); err:skb_dst_drop (skb);return -EINVAL ;
@@ -315,13 +317,14 @@ static int nf_ct_frag6_queue(struct frag_queue *fq, struct sk_buff *skb,* the last and the first frames arrived and all the bits are here. */ static int nf_ct_frag6_reasm (struct frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev) +struct sk_buff *prev_tail,struct net_device *dev, +int *refs) {void *reasm_data;int payload_len;u8 ecn; -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs); ecn = ip_frag_ecn_table[fq->ecn];if (unlikely (ecn ==0xff ))
@@ -373,7 +376,7 @@ static int nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *skb,return 0 ; err: -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs);return -EINVAL ; }
@@ -484,7 +487,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)spin_lock_bh (&fq->q.lock); - ret =nf_ct_frag6_queue (fq, skb, fhdr, nhoff); + ret =nf_ct_frag6_queue (fq, skb, fhdr, nhoff, &refs);if (ret == -EPROTO ) { skb->transport_header = savethdr; ret =0 ;
net/ipv6/reassembly.c +10 -8
@@ -68,7 +68,8 @@ static u8 ip6_frag_ecn(const struct ipv6hdr *ipv6h)static struct inet_frags ip6_frags;static int ip6_frag_reasm (struct frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev); +struct sk_buff *prev_tail,struct net_device *dev, +int *refs);static void ip6_frag_expire (struct timer_list *t) {
@@ -105,7 +106,7 @@ fq_find(struct net *net, __be32 id, const struct ipv6hdr *hdr, int iif)static int ip6_frag_queue (struct frag_queue *fq,struct sk_buff *skb,struct frag_hdr *fhdr,int nhoff, -u32 *prob_offset) +u32 *prob_offset,int *refs) {struct net *net =dev_net (skb_dst (skb)->dev);int offset, end, fragsize;
@@ -220,7 +221,7 @@ static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,unsigned long orefdst = skb->_skb_refdst; skb->_skb_refdst =0UL ; - err =ip6_frag_reasm (fq, skb, prev_tail, dev); + err =ip6_frag_reasm (fq, skb, prev_tail, dev, refs); skb->_skb_refdst = orefdst;return err; }
@@ -238,7 +239,7 @@ static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,__IP6_INC_STATS (net,ip6_dst_idev (skb_dst (skb)),IPSTATS_MIB_REASM_OVERLAPS ); discard_fq: -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs);__IP6_INC_STATS (net,ip6_dst_idev (skb_dst (skb)),IPSTATS_MIB_REASMFAILS ); err:
@@ -254,7 +255,8 @@ static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,* the last and the first frames arrived and all the bits are here. */ static int ip6_frag_reasm (struct frag_queue *fq,struct sk_buff *skb, -struct sk_buff *prev_tail,struct net_device *dev) +struct sk_buff *prev_tail,struct net_device *dev, +int *refs) {struct net *net = fq->q.fqdir->net;unsigned int nhoff;
@@ -262,7 +264,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *skb,int payload_len;u8 ecn; -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs); ecn = ip_frag_ecn_table[fq->ecn];if (unlikely (ecn ==0xff ))
@@ -320,7 +322,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *skb,rcu_read_lock ();__IP6_INC_STATS (net,__in6_dev_stats_get (dev, skb),IPSTATS_MIB_REASMFAILS );rcu_read_unlock (); -inet_frag_kill (&fq->q); +inet_frag_kill (&fq->q, refs);return -1 ; }
@@ -386,7 +388,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) fq->iif = iif; ret =ip6_frag_queue (fq, skb, fhdr,IP6CB (skb)->nhoff, - &prob_offset); + &prob_offset, &refs);spin_unlock (&fq->q.lock);inet_frag_putn (&fq->q, refs);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/5 inet: frags: save a pair of atomic operations in reassembly
In reply to Sasha LevinFrom: Eric Dumazet <[email protected]>
[ Upstream commit ca0359df45a55a9eb4d6dc09a481064abf78320f ]
As mentioned in commit 648700f76b03 ("inet: frags:
use rhashtables for reassembly units"):
A followup patch will even remove the refcount hold/release left from prior implementation and save a couple of atomic operations.
This patch implements this idea, seven years later.
Stable-dep-of: 653d7ddf6cba ("inet: frags: publish queues before arming timer")
--- net/ieee802154/6lowpan/reassembly.c | 5 ++++- net/ipv4/inet_fragment.c | 18 ++++++++---------- net/ipv4/ip_fragment.c | 5 ++++- net/ipv6/netfilter/nf_conntrack_reasm.c | 5 ++++- net/ipv6/reassembly.c | 9 ++++----- 5 files changed, 24 insertions(+), 18 deletions(-)
net/ieee802154/6lowpan/reassembly.c +4 -1
@@ -304,17 +304,20 @@ int lowpan_frag_rcv(struct sk_buff *skb, u8 frag_type) goto err; } +rcu_read_lock (); fq =fq_find (net, cb, &hdr.source, &hdr.dest);if (fq !=NULL ) { -int ret, refs =1 ; +int ret, refs =0 ;spin_lock (&fq->q.lock); ret =lowpan_frag_queue (fq, skb, frag_type, &refs);spin_unlock (&fq->q.lock); +rcu_read_unlock ();inet_frag_putn (&fq->q, refs);return ret; } +rcu_read_unlock (); err:kfree_skb (skb);
net/ipv4/inet_fragment.c +8 -10
@@ -375,7 +375,8 @@ static struct inet_frag_queue *inet_frag_alloc(struct fqdir *fqdir,timer_setup (&q->timer, f->frag_expire,0 );spin_lock_init (&q->lock); -refcount_set (&q->refcnt,3 ); +/* One reference for the timer, one for the hash table. */ +refcount_set (&q->refcnt,2 );return q; }
@@ -397,7 +398,11 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, *prev =rhashtable_lookup_get_insert_key (&fqdir->rhashtable, &q->key, &q->node, f->rhash_params);if (*prev) { -int refs =2 ; +/* We could not insert in the hash table, +* we need to cancel what inet_frag_alloc() +* anticipated. +*/ +int refs =1 ; q->flags |=INET_FRAG_COMPLETE ;inet_frag_kill (q, &refs);
@@ -407,7 +412,6 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir,return q; } -/* TODO : call from rcu_read_lock() and no longer use refcount_inc_not_zero() */ struct inet_frag_queue *inet_frag_find (struct fqdir *fqdir,void *key) {/* This pairs with WRITE_ONCE() in fqdir_pre_exit(). */
@@ -417,17 +421,11 @@ struct inet_frag_queue *inet_frag_find(struct fqdir *fqdir, void *key)if (!high_thresh ||frag_mem_limit (fqdir) > high_thresh)return NULL ; -rcu_read_lock (); - prev =rhashtable_lookup (&fqdir->rhashtable, key, fqdir->f->rhash_params);if (!prev) fq =inet_frag_create (fqdir, key, &prev); -if (!IS_ERR_OR_NULL (prev)) { +if (!IS_ERR_OR_NULL (prev)) fq = prev; -if (!refcount_inc_not_zero (&fq->refcnt)) - fq =NULL ; - } -rcu_read_unlock ();return fq; }EXPORT_SYMBOL (inet_frag_find);
net/ipv4/ip_fragment.c +4 -1
@@ -477,18 +477,21 @@ int ip_defrag(struct net *net, struct sk_buff *skb, u32 user)__IP_INC_STATS (net,IPSTATS_MIB_REASMREQDS );/* Lookup (or create) queue header */ +rcu_read_lock (); qp =ip_find (net,ip_hdr (skb), user, vif);if (qp) { -int ret, refs =1 ; +int ret, refs =0 ;spin_lock (&qp->q.lock); ret =ip_frag_queue (qp, skb, &refs);spin_unlock (&qp->q.lock); +rcu_read_unlock ();inet_frag_putn (&qp->q, refs);return ret; } +rcu_read_unlock ();__IP_INC_STATS (net,IPSTATS_MIB_REASMFAILS );kfree_skb (skb);
net/ipv6/netfilter/nf_conntrack_reasm.c +4 -1
@@ -451,7 +451,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user)struct frag_hdr *fhdr;struct frag_queue *fq;struct ipv6hdr *hdr; -int refs =1 ; +int refs =0 ;u8 prevhdr;/* Jumbo payload inhibits frag. header */
@@ -478,9 +478,11 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user) hdr =ipv6_hdr (skb); fhdr = (struct frag_hdr *)skb_transport_header (skb); +rcu_read_lock (); fq =fq_find (net, fhdr->identification, user, hdr, skb->dev ? skb->dev->ifindex :0 );if (fq ==NULL ) { +rcu_read_unlock ();pr_debug ("Can't find and can't create new queue\n" );return -ENOMEM ; }
@@ -494,6 +496,7 @@ int nf_ct_frag6_gather(struct net *net, struct sk_buff *skb, u32 user) }spin_unlock_bh (&fq->q.lock); +rcu_read_unlock ();inet_frag_putn (&fq->q, refs);return ret; }
net/ipv6/reassembly.c +4 -5
@@ -305,9 +305,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *skb,skb_postpush_rcsum (skb,skb_network_header (skb),skb_network_header_len (skb)); -rcu_read_lock ();__IP6_INC_STATS (net,__in6_dev_stats_get (dev, skb),IPSTATS_MIB_REASMOKS ); -rcu_read_unlock (); fq->q.rb_fragments =RB_ROOT ; fq->q.fragments_tail =NULL ; fq->q.last_run_head =NULL ;
@@ -319,9 +317,7 @@ static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *skb, out_oom:net_dbg_ratelimited ("ip6_frag_reasm: no memory for reassembly\n" ); out_fail: -rcu_read_lock ();__IP6_INC_STATS (net,__in6_dev_stats_get (dev, skb),IPSTATS_MIB_REASMFAILS ); -rcu_read_unlock ();inet_frag_kill (&fq->q, refs);return -1 ; }
@@ -379,10 +375,11 @@ static int ipv6_frag_rcv(struct sk_buff *skb) } iif = skb->dev ? skb->dev->ifindex :0 ; +rcu_read_lock (); fq =fq_find (net, fhdr->identification, hdr, iif);if (fq) {u32 prob_offset =0 ; -int ret, refs =1 ; +int ret, refs =0 ;spin_lock (&fq->q.lock);
@@ -391,6 +388,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) &prob_offset, &refs);spin_unlock (&fq->q.lock); +rcu_read_unlock ();inet_frag_putn (&fq->q, refs);if (prob_offset) { __IP6_INC_STATS(net, __in6_dev_get_safely(skb->dev),
@@ -400,6 +398,7 @@ static int ipv6_frag_rcv(struct sk_buff *skb) }return ret; } +rcu_read_unlock ();__IP6_INC_STATS (net,ip6_dst_idev (skb_dst (skb)),IPSTATS_MIB_REASMFAILS );kfree_skb (skb);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/5 inet: frags: publish queues before arming timer
In reply to Sasha LevinFrom: Zhiling Zou <[email protected]>
[ Upstream commit 653d7ddf6cba867777a3d14c4f83ace008c5ad13 ]
inet_frag_create() arms the fragment queue timer before inserting the queue into the fqdir rhashtable. If the namespace fragment timeout is zero or negative, the timer can run before the queue is published.
The timer callback then marks the queue complete, tries to remove a node that is not in the hash table yet, and drops the anticipated hash reference. Creation can subsequently publish the completed queue without restoring that reference, leaving a stale hash node after the caller drops the remaining reference.
Publish the queue first and arm the timer while holding the queue lock. This makes timer expiry wait until the queue is visible in the hash table, so inet_frag_kill() can remove the node and balance the hash reference.
--- net/ipv4/inet_fragment.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
net/ipv4/inet_fragment.c +5 -5
@@ -393,8 +393,8 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir, *prev =ERR_PTR (-ENOMEM );return NULL ; } -mod_timer (&q->timer, jiffies + fqdir->timeout); +spin_lock_bh (&q->lock); *prev =rhashtable_lookup_get_insert_key (&fqdir->rhashtable, &q->key, &q->node, f->rhash_params);if (*prev) {
@@ -402,13 +402,13 @@ static struct inet_frag_queue *inet_frag_create(struct fqdir *fqdir,* we need to cancel what inet_frag_alloc() * anticipated. */ -int refs =1 ; - q->flags |=INET_FRAG_COMPLETE ; -inet_frag_kill (q, &refs); -inet_frag_putn (q, refs); +spin_unlock_bh (&q->lock); +inet_frag_putn (q,2 );return NULL ; } +mod_timer (&q->timer, jiffies + fqdir->timeout); +spin_unlock_bh (&q->lock);return q; }
Signature · 1 line
2.53.0