Netdev List
 help / color / mirror / Atom feed
From: Steffen Klassert <steffen.klassert@secunet.com>
To: Jakub Kicinski <kuba@kernel.org>, Maher Azzouzi <maherazz04@gmail.com>
Cc: <davem@davemloft.net>, <herbert@gondor.apana.org.au>,
	<netdev@vger.kernel.org>
Subject: Re: [PATCH 2/8] esp: do not unref managed frag pages in esp_ssg_unref()
Date: Mon, 3 Aug 2026 18:56:35 +0200	[thread overview]
Message-ID: <anDIQ9-8Sf_6StfZ@secunet.com> (raw)
In-Reply-To: <20260731021544.789075-1-kuba@kernel.org>

Maher, please look at this review of your patch.

Thanks!

On Thu, Jul 30, 2026 at 07:15:44PM -0700, Jakub Kicinski wrote:
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> ---
> --- Patch [2]: [PATCH 2/8] esp: do not unref managed frag pages in esp_ssg_unref() ---
> commit 1913e3a658480a2d9a14804dfb34715f589f403d
> Author: Maher Azzouzi <maherazz04@gmail.com>
> 
> esp: do not unref managed frag pages in esp_ssg_unref()
> 
> The patch adds an early return to esp_ssg_unref() in both esp4.c and esp6.c
> so that no per-frag page reference is dropped when the skb carries managed
> frags (SKBFL_MANAGED_FRAG_REFS), mirroring the skip_unref handling in
> skb_release_data().
> 
> A couple of questions on the commit message and on the scope of the guard.
> 
> >     Fixes: cac2661c53f3 ("esp4: Avoid skb_cow_data whenever possible")
> >     Fixes: 03e2a30f6a27 ("esp6: Avoid skb_cow_data whenever possible")
> 
> [Severity: Medium]
> [Finding: main-1-2]
> [Sources: opus-5]
> 
> Are these the right commits to blame?  Both are dated 2017-01-17, while
> SKBFL_MANAGED_FRAG_REFS and the skb_zcopy_managed() helper used by the new
> guard only arrived with 753f1ca4e1e5 ("net: introduce managed frags
> infrastructure", 2022-07-12, v6.1).
> 
> The esp code as written in 2017 had nothing to be wrong about here; the
> described scenario only becomes possible once managed-frag skbs can reach
> the xfrm output path.
> 
> Since Fixes: tags drive stable and AUTOSEL selection, this points the patch
> at trees older than v6.1, where skb_zcopy_managed() does not exist at all
> (build failure) and where the described scenario cannot happen.  Would the
> commit that made managed-frag skbs reachable from esp_output() be the better
> blame target, or no Fixes: tag plus an explicit stable range?
> 
> > diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
> > index dfc81ee969ae0..fa1710e27e505 100644
> > --- a/net/ipv4/esp4.c
> > +++ b/net/ipv4/esp4.c
> > @@ -104,6 +104,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
> >  	struct aead_request *req;
> >  	struct scatterlist *sg;
> >  
> > +	/* Managed frags are owned by the zerocopy ubuf; the skb holds no
> > +	 * per-frag page reference, so we must not drop one here.  Mirrors
> > +	 * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
> > +	 */
> > +	if (skb_zcopy_managed(skb))
> > +		return;
> > +
> 
> [Severity: High]
> [Finding: main-1-0]
> [Sources: opus-5]
> 
> Does this early return also suppress the release of the trailer page that
> ESP itself referenced?
> 
> The src scatterlist looks like it has mixed ownership.  Besides the
> ubuf-owned payload frags, esp_output_head() adds a trailer frag with an
> explicit reference in the only branch that sets esp->inplace = false:
> 
> net/ipv4/esp4.c:esp_output_head() {
> 	...
> 			page = pfrag->page;
> 			get_page(page);
> 	...
> 			__skb_fill_page_desc(skb, nfrags, page, pfrag->offset,
> 					     tailen);
> 			skb_shinfo(skb)->nr_frags = ++nfrags;
> 	...
> }
> 
> esp_output_tail() then drops that frag from the skb entirely:
> 
> net/ipv4/esp4.c:esp_output_tail() {
> 	...
> 		skb_shinfo(skb)->nr_frags = 1;
> 
> 		page = pfrag->page;
> 		get_page(page);
> 		/* replace page frags in skb with new page */
> 		__skb_fill_page_desc(skb, 0, page, pfrag->offset, skb->data_len);
> 	...
> }
> 
> so the loop in esp_ssg_unref() appears to be the only remaining release
> site for that trailer reference.  skb_release_data() cannot reach the
> orphaned frag, and for SKBFL_MANAGED_FRAG_REFS it takes the skip_unref
> branch to free_head anyway.
> 
> Both esp_ssg_unref() call sites in esp_output_tail() are affected: the error
> path
> 
> 		if (unlikely(err < 0)) {
> 			esp_ssg_unref(x, tmp, skb, true);
> 
> and the success path
> 
> 	if (sg != dsg)
> 		esp_ssg_unref(x, tmp, skb, false);
> 
> Note the loop only ever ran in exactly that non-inplace case
> (already_unref || req->src != req->dst), which is also the case that adds
> the trailer frag.  So does the get_page() from esp_output_head() stay
> unbalanced for a managed-frag skb?
> 
> Since a single stray reference pins the whole high-order x->xfrag
> page_frag page, would every x->xfrag page used for trailers become
> unfreeable, at packet rate?
> 
> Would it be better to distinguish ubuf-owned frags from the ESP-added
> trailer frag, for instance by calling skb_zcopy_downgrade_managed(skb)
> before ESP mutates the frag array, the way __ip_append_data() and
> __ip6_append_data() do, rather than skipping the unref wholesale?
> 
> Related to that, the new comment says "the skb holds no per-frag page
> reference".  Is that accurate?  It holds true for the ubuf-owned frags, but
> not for the trailer frag ESP added with get_page().
> 
> [Severity: High]
> [Finding: main-1-1]
> [Sources: opus-5]
> 
> This isn't a bug introduced by this patch, but it is the invariant the
> guard works around, and it stays unfixed: esp_output_tail() replaces the
> frag array of a managed-frag skb with a page it references itself, while
> SKBFL_MANAGED_FRAG_REFS remains set:
> 
> net/ipv4/esp4.c:esp_output_tail() {
> 	...
> 		skb_shinfo(skb)->nr_frags = 1;
> 
> 		page = pfrag->page;
> 		get_page(page);
> 		/* replace page frags in skb with new page */
> 		__skb_fill_page_desc(skb, 0, page, pfrag->offset, skb->data_len);
> 	...
> }
> 
> That destination page reference is owned by frag 0, but with the flag still
> set skb_release_data() takes the skip_unref branch:
> 
> net/core/skbuff.c:skb_release_data() {
> 	...
> 	if (skb_zcopy(skb)) {
> 		bool skip_unref = shinfo->flags & SKBFL_MANAGED_FRAG_REFS;
> 
> 		skb_zcopy_clear(skb, true);
> 		if (skip_unref)
> 			goto free_head;
> 	}
> 	...
> }
> 
> so is that reference ever dropped?  Neither net/ipv4/esp4.c nor
> net/ipv6/esp6.c calls skb_zcopy_downgrade_managed(); the canonical way to
> make a managed-frag skb safe for frag mutation and per-frag unref is:
> 
> net/core/skbuff.c:__skb_zcopy_downgrade_managed() {
> 	skb_shinfo(skb)->flags &= ~SKBFL_MANAGED_FRAG_REFS;
> 	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
> 		skb_frag_ref(skb, i);
> }
> 
> which every other frag-mutating site uses (net/core/skbuff.c,
> net/ipv4/ip_output.c, net/ipv6/ip6_output.c, net/ipv4/tcp.c).
> 
> Would a single skb_zcopy_downgrade_managed(skb) before ESP touches the
> frags address both the original refcount underflow and this leak, instead
> of leaving a mixed managed/refcounted frag array as an ESP-specific special
> case?  In that light, is the commit message statement "The rest of the
> stack honours this invariant" only half applied here?
> 
> >  	if (x->props.flags & XFRM_STATE_ESN)
> >  		extralen += sizeof(struct esp_output_extra);
> >  
> > diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
> > index 296b57926abb9..7d216b9c59f04 100644
> > --- a/net/ipv6/esp6.c
> > +++ b/net/ipv6/esp6.c
> > @@ -121,6 +121,13 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
> >  	struct aead_request *req;
> >  	struct scatterlist *sg;
> >  
> > +	/* Managed frags are owned by the zerocopy ubuf; the skb holds no
> > +	 * per-frag page reference, so we must not drop one here.  Mirrors
> > +	 * the SKBFL_MANAGED_FRAG_REFS handling in skb_release_data().
> > +	 */
> > +	if (skb_zcopy_managed(skb))
> > +		return;
> > +
> 
> [Severity: High]
> [Finding: main-1-0]
> [Sources: opus-5]
> 
> Do both questions above apply identically to the IPv6 copy?
> esp6_output_head() takes the same get_page() for the trailer frag,
> esp6_output_tail() performs the same nr_frags = 1 frag replacement, and both
> esp_ssg_unref() calls (already_unref == true in the skb_to_sgvec() error
> path, and the sg != dsg call after encryption) return early here too.
> 
> >  	if (x->props.flags & XFRM_STATE_ESN)
> >  		extralen += sizeof(struct esp_output_extra);
> >

  reply	other threads:[~2026-08-03 16:56 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-29  6:50 [PATCH 0/8] pull request (net): ipsec 2026-07-29 Steffen Klassert
2026-07-29  6:50 ` [PATCH 1/8] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
2026-07-29  6:50 ` [PATCH 2/8] esp: do not unref managed frag pages in esp_ssg_unref() Steffen Klassert
2026-07-31  2:15   ` Jakub Kicinski
2026-08-03 16:56     ` Steffen Klassert [this message]
2026-07-29  6:50 ` [PATCH 3/8] xfrm: espintcp: fix UAF during close Steffen Klassert
2026-07-29  6:50 ` [PATCH 4/8] xfrm: drop ESP-in-TCP packets with no ingress device Steffen Klassert
2026-07-29  6:50 ` [PATCH 5/8] xfrm: avoid lock inversion in nat keepalive work Steffen Klassert
2026-07-31  2:15   ` Jakub Kicinski
2026-08-03 16:58     ` Steffen Klassert
2026-07-29  6:50 ` [PATCH 6/8] xfrm: Fix skb double-free in xfrm_dev_direct_output() Steffen Klassert
2026-07-29  6:50 ` [PATCH 7/8] xfrm: ah6: validate routing header segments_left Steffen Klassert
2026-07-29  6:50 ` [PATCH 8/8] xfrm: fix xfrm_state_construct() auth-trunc leak Steffen Klassert
2026-07-31  2:17 ` [PATCH 0/8] pull request (net): ipsec 2026-07-29 Jakub Kicinski
2026-08-03 16:54   ` Steffen Klassert

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=anDIQ9-8Sf_6StfZ@secunet.com \
    --to=steffen.klassert@secunet.com \
    --cc=davem@davemloft.net \
    --cc=herbert@gondor.apana.org.au \
    --cc=kuba@kernel.org \
    --cc=maherazz04@gmail.com \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox