From: Steffen Klassert <steffen.klassert@secunet.com>
To: Alessandro Schino <7991aleschino@gmail.com>
Cc: <netdev@vger.kernel.org>, <herbert@gondor.apana.org.au>,
<davem@davemloft.net>, <pabeni@redhat.com>,
<linux-kernel@vger.kernel.org>
Subject: Re: [PATCH ipsec] esp: fix page frag reference leak on skb_to_sgvec failure
Date: Wed, 3 Jun 2026 12:58:05 +0200 [thread overview]
Message-ID: <aiAIvUX8a43ci-DF@secunet.com> (raw)
In-Reply-To: <20260601134526.82-1-7991aleschino@gmail.com>
On Mon, Jun 01, 2026 at 03:45:25PM +0200, Alessandro Schino wrote:
> In esp_output_tail(), when esp->inplace is false, the old skb page frags
> are replaced with a new page from the xfrm page_frag cache. The source
> scatterlist (sg) is built from the old frags before the replacement, and
> esp_ssg_unref() is responsible for releasing the old page references
> after the crypto operation completes.
>
> However, if the second skb_to_sgvec() call (which builds the destination
> scatterlist from the new page) fails, the code jumps to error_free which
> only calls kfree(tmp). The old page frag references captured in the
> source scatterlist are never released:
>
> 1. sg[] is built from old frags via skb_to_sgvec() (no extra get_page)
> 2. nr_frags is set to 1 and frag[0] is replaced with the new page
> 3. Second skb_to_sgvec() fails -> goto error_free
>
> Fix this by adding a bool parameter to esp_ssg_unref() that, when true,
> unconditionally unrefs the source scatterlist frags. Since req->src is
> not yet initialized by aead_request_set_crypt() at the point of the
> error, the source scatterlist is obtained directly via esp_req_sg().
> Existing callers pass false to preserve the original behavior.
>
> The same issue exists in both esp4 and esp6 as the code is identical.
>
> Fixes: cac2661c53f3 ("esp4: Avoid skb_cow_data whenever possible")
> Fixes: 03e2a30f6a27 ("esp6: Avoid skb_cow_data whenever possible")
> Signed-off-by: Alessandro Schino <7991aleschino@gmail.com>
> ---
> net/ipv4/esp4.c | 14 ++++++++------
> net/ipv6/esp6.c | 14 ++++++++------
> 2 files changed, 16 insertions(+), 12 deletions(-)
>
> diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
> index 513c8215c947..2429c7845984 100644
> --- a/net/ipv4/esp4.c
> +++ b/net/ipv4/esp4.c
> @@ -96,7 +96,7 @@ static inline struct scatterlist *esp_req_sg(struct crypto_aead *aead,
> __alignof__(struct scatterlist));
> }
>
> -static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb)
> +static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, bool already_unref)
> {
> struct crypto_aead *aead = x->data;
> int extralen = 0;
> @@ -113,8 +113,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb)
> /* Unref skb_frag_pages in the src scatterlist if necessary.
> * Skip the first sg which comes from skb->data.
> */
> - if (req->src != req->dst)
> - for (sg = sg_next(req->src); sg; sg = sg_next(sg))
> + if (already_unref || req->src != req->dst)
> + for (sg = sg_next(already_unref ? esp_req_sg(aead, req) : req->src); sg; sg = sg_next(sg))
> skb_page_unref(page_to_netmem(sg_page(sg)),
> skb->pp_recycle);
> }
> @@ -220,7 +220,7 @@ static void esp_output_done(void *data, int err)
> }
>
> tmp = ESP_SKB_CB(skb)->tmp;
> - esp_ssg_unref(x, tmp, skb);
> + esp_ssg_unref(x, tmp, skb, false);
> kfree(tmp);
>
> if (xo && (xo->flags & XFRM_DEV_RESUME)) {
> @@ -569,8 +569,10 @@ int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
> err = skb_to_sgvec(skb, dsg,
> (unsigned char *)esph - skb->data,
> assoclen + ivlen + esp->clen + alen);
> - if (unlikely(err < 0))
> + if (unlikely(err < 0)) {
> + esp_ssg_unref(x, tmp, skb, true);
> goto error_free;
> + }
> }
>
> if ((x->props.flags & XFRM_STATE_ESN))
> @@ -602,7 +604,7 @@ int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *
> }
>
> if (sg != dsg)
> - esp_ssg_unref(x, tmp, skb);
> + esp_ssg_unref(x, tmp, skb, false);
>
> if (!err && x->encap && x->encap->encap_type == TCP_ENCAP_ESPINTCP)
> err = esp_output_tail_tcp(x, skb);
> diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
> index 57481e423e59..50af6ab9b8fc 100644
> --- a/net/ipv6/esp6.c
> +++ b/net/ipv6/esp6.c
> @@ -113,7 +113,7 @@ static inline struct scatterlist *esp_req_sg(struct crypto_aead *aead,
> __alignof__(struct scatterlist));
> }
>
> -static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb)
> +static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, bool already_unref)
> {
> struct crypto_aead *aead = x->data;
> int extralen = 0;
> @@ -130,8 +130,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb)
> /* Unref skb_frag_pages in the src scatterlist if necessary.
> * Skip the first sg which comes from skb->data.
> */
> - if (req->src != req->dst)
> - for (sg = sg_next(req->src); sg; sg = sg_next(sg))
> + if (already_unref || req->src != req->dst)
> + for (sg = sg_next(already_unref ? esp_req_sg(aead, req) : req->src); sg; sg = sg_next(sg))
> skb_page_unref(page_to_netmem(sg_page(sg)),
> skb->pp_recycle);
> }
Now checkpatch complains about this:
WARNING: line length of 106 exceeds 100 columns
#21: FILE: net/ipv4/esp4.c:117:
+ for (sg = sg_next(already_unref ? esp_req_sg(aead, req) : req->src); sg; sg = sg_next(sg))
WARNING: line length of 106 exceeds 100 columns
#75: FILE: net/ipv6/esp6.c:134:
+ for (sg = sg_next(already_unref ? esp_req_sg(aead, req) : req->src); sg; sg = sg_next(sg))
This is not a big issue, but it makes the code unreadable
over time.
Please check your patches before you send it.
next prev parent reply other threads:[~2026-06-03 10:58 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-01 13:45 [PATCH ipsec] esp: fix page frag reference leak on skb_to_sgvec failure Alessandro Schino
2026-06-03 10:58 ` Steffen Klassert [this message]
[not found] ` <CABNe9nMY+npo8uTourxOSDZobbGoWmH8qgTuKxxW+hFeFag-Sw@mail.gmail.com>
2026-06-03 11:10 ` 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=aiAIvUX8a43ci-DF@secunet.com \
--to=steffen.klassert@secunet.com \
--cc=7991aleschino@gmail.com \
--cc=davem@davemloft.net \
--cc=herbert@gondor.apana.org.au \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.