From: Steffen Klassert <steffen.klassert@secunet.com>
To: David Miller <davem@davemloft.net>, Jakub Kicinski <kuba@kernel.org>
Cc: Herbert Xu <herbert@gondor.apana.org.au>,
Steffen Klassert <steffen.klassert@secunet.com>,
<netdev@vger.kernel.org>
Subject: [PATCH 10/10] xfrm: bound nat keepalive state collection
Date: Tue, 18 Aug 2026 11:28:41 +0200 [thread overview]
Message-ID: <20260818092920.653034-11-steffen.klassert@secunet.com> (raw)
In-Reply-To: <20260818092920.653034-1-steffen.klassert@secunet.com>
From: Zihan Xi <zihanx@nebusec.ai>
The v1 nat keepalive fix allocates a GFP_ATOMIC object for every state
while collecting references for phase two. This makes the worker's
temporary memory use depend on the number of states and lets -ENOMEM abort
the scan.
Replace the allocated list with a fixed-size batch. When the batch is full,
return a private walk status so xfrm_state_walk() leaves a cursor; drain
the references after the walk releases xfrm_state_lock and resume from
the cursor. This bounds temporary memory use and avoids the allocation
failure path.
The v1 fix also moved nat_keepalive_send() out of the walk callback. Keep
the phase-two drain BH-disabled, as required by local_lock_nested_bh()
used by the keepalive sockets.
Fixes: 763fe700b7c5 ("xfrm: avoid lock inversion in nat keepalive work")
Cc: stable@vger.kernel.org
Cc: Eyal Birger <eyal.birger@gmail.com>
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
---
net/xfrm/xfrm_nat_keepalive.c | 46 ++++++++++++++++-------------------
1 file changed, 21 insertions(+), 25 deletions(-)
diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index 8679c68c10a1..5cd6d43164db 100644
--- a/net/xfrm/xfrm_nat_keepalive.c
+++ b/net/xfrm/xfrm_nat_keepalive.c
@@ -155,32 +155,30 @@ static void nat_keepalive_send(struct nat_keepalive *ka)
}
}
+enum {
+ NAT_KEEPALIVE_BATCH_SIZE = 16,
+ NAT_KEEPALIVE_BATCH_FULL = 1,
+};
+
struct nat_keepalive_work_ctx {
- struct list_head states;
+ struct xfrm_state *batch[NAT_KEEPALIVE_BATCH_SIZE];
+ unsigned int nr;
time64_t next_run;
time64_t now;
};
-struct nat_keepalive_state {
- struct list_head list;
- struct xfrm_state *x;
-};
-
static int nat_keepalive_work_collect(struct xfrm_state *x, int count, void *ptr)
{
struct nat_keepalive_work_ctx *ctx = ptr;
- struct nat_keepalive_state *state;
if (!READ_ONCE(x->nat_keepalive_interval))
return 0;
- state = kmalloc_obj(*state, GFP_ATOMIC);
- if (!state)
- return -ENOMEM;
+ if (ctx->nr == ARRAY_SIZE(ctx->batch))
+ return NAT_KEEPALIVE_BATCH_FULL;
xfrm_state_hold(x);
- state->x = x;
- list_add_tail(&state->list, &ctx->states);
+ ctx->batch[ctx->nr++] = x;
return 0;
}
@@ -226,29 +224,27 @@ static void nat_keepalive_work_single(struct xfrm_state *x,
static void nat_keepalive_work(struct work_struct *work)
{
- struct nat_keepalive_state *state, *tmp;
struct nat_keepalive_work_ctx ctx;
struct xfrm_state_walk walk;
struct net *net;
- int err;
+ int err, i;
- INIT_LIST_HEAD(&ctx.states);
ctx.next_run = 0;
ctx.now = ktime_get_real_seconds();
net = container_of(work, struct net, xfrm.nat_keepalive_work.work);
xfrm_state_walk_init(&walk, IPPROTO_ESP, NULL);
- err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
+ do {
+ ctx.nr = 0;
+ err = xfrm_state_walk(net, &walk, nat_keepalive_work_collect, &ctx);
+ local_bh_disable();
+ for (i = 0; i < ctx.nr; i++) {
+ nat_keepalive_work_single(ctx.batch[i], &ctx);
+ xfrm_state_put(ctx.batch[i]);
+ }
+ local_bh_enable();
+ } while (err == NAT_KEEPALIVE_BATCH_FULL);
xfrm_state_walk_done(&walk, net);
- list_for_each_entry_safe(state, tmp, &ctx.states, list) {
- nat_keepalive_work_single(state->x, &ctx);
- xfrm_state_put(state->x);
- kfree(state);
- }
- if (err == -ENOMEM) {
- schedule_delayed_work(&net->xfrm.nat_keepalive_work, 0);
- return;
- }
if (ctx.next_run)
schedule_delayed_work(&net->xfrm.nat_keepalive_work,
(ctx.next_run - ctx.now) * HZ);
--
2.43.0
prev parent reply other threads:[~2026-08-18 9:29 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 9:28 [PATCH 0/10] pull request (net): ipsec 2026-08-18 Steffen Klassert
2026-08-18 9:28 ` [PATCH 01/10] xfrm6: fix out-of-bounds write in xfrm6_input_addr() when secpath is full Steffen Klassert
2026-08-18 9:28 ` [PATCH 02/10] esp: do not unref managed frag pages in esp_ssg_unref() Steffen Klassert
2026-08-18 9:28 ` [PATCH 03/10] xfrm: espintcp: fix UAF during close Steffen Klassert
2026-08-18 9:28 ` [PATCH 04/10] xfrm: drop ESP-in-TCP packets with no ingress device Steffen Klassert
2026-08-18 9:28 ` [PATCH 05/10] xfrm: avoid lock inversion in nat keepalive work Steffen Klassert
2026-08-18 9:28 ` [PATCH 06/10] xfrm: Fix skb double-free in xfrm_dev_direct_output() Steffen Klassert
2026-08-18 9:28 ` [PATCH 07/10] xfrm: ah6: validate routing header segments_left Steffen Klassert
2026-08-18 9:28 ` [PATCH 08/10] xfrm: fix xfrm_state_construct() auth-trunc leak Steffen Klassert
2026-08-18 9:28 ` [PATCH 09/10] Revert "esp: do not unref managed frag pages in esp_ssg_unref()" Steffen Klassert
2026-08-18 9:28 ` Steffen Klassert [this message]
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=20260818092920.653034-11-steffen.klassert@secunet.com \
--to=steffen.klassert@secunet.com \
--cc=davem@davemloft.net \
--cc=herbert@gondor.apana.org.au \
--cc=kuba@kernel.org \
--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