[PATCH net] tipc: protect received keys from concurrent flush

From: Jérémy Jean

Date: Wed Sep 30 2026 - 08:09:32 EST


tipc_crypto_key_synch() can queue the RX worker again while it is still
using rx->skey. If tipc_crypto_key_flush() cancels that queued work, it
frees the key without waiting for the running worker. The worker can
then read freed memory or free the key a second time. Racing key
exchange with key flush triggers KASAN:

[ 12.986077] BUG: KASAN: double-free in tipc_crypto_key_flush+0x401/0x530
[ 12.987937] Free of addr ff11000002268080 by task peer/112
...
[ 12.991938] kfree+0x163/0x430
...
[ 12.991983] tipc_crypto_key_flush+0x401/0x530
...
[ 12.992152] tipc_nl_node_flush_key+0x174/0x210

Mark the key as in use under rx->lock and make flush skip it while the
worker is using it. Clear the flag under the same lock when the worker
frees the key or leaves it for retry. Keep rx->skey set so the receive
path cannot replace it during AEAD setup.

Fixes: 1ef6f7c9390f ("tipc: add automatic session key exchange")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Jérémy Jean <Jeremy.Jean@xxxxxxxxxxxxxxxxx>
---
net/tipc/crypto.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/net/tipc/crypto.c b/net/tipc/crypto.c
index 16f1ed1f6b1b..6eb9de458902 100644
--- a/net/tipc/crypto.c
+++ b/net/tipc/crypto.c
@@ -184,6 +184,7 @@ struct tipc_crypto_stats {
* @key: the key states
* @skey_mode: session key's mode
* @skey: received session key
+ * @skey_in_use: received session key is owned by the RX worker
* @wq: common workqueue on TX crypto
* @work: delayed work sched for TX/RX
* @key_distr: key distributing state
@@ -208,6 +209,7 @@ struct tipc_crypto {
u16 key_gen;
struct tipc_key key;
u8 skey_mode;
+ bool skey_in_use;
struct tipc_aead_key *skey;
struct workqueue_struct *wq;
struct delayed_work work;
@@ -1219,8 +1221,11 @@ void tipc_crypto_key_flush(struct tipc_crypto *c)
rx = c;
tx = tipc_net(rx->net)->crypto_tx;
if (cancel_delayed_work(&rx->work)) {
- kfree_sensitive(rx->skey);
- rx->skey = NULL;
+ /* A previous invocation may still be using the key. */
+ if (!rx->skey_in_use) {
+ kfree_sensitive(rx->skey);
+ rx->skey = NULL;
+ }
atomic_xchg(&rx->key_distr, 0);
tipc_node_put(rx->node);
}
@@ -2381,7 +2386,10 @@ static void tipc_crypto_work_rx(struct work_struct *work)
}

/* Case 2: Attach a pending received session key from peer if any */
+ spin_lock_bh(&rx->lock);
if (rx->skey) {
+ rx->skey_in_use = true;
+ spin_unlock_bh(&rx->lock);
rc = tipc_crypto_key_init(rx, rx->skey, rx->skey_mode, false);
if (unlikely(rc < 0))
pr_warn("%s: unable to attach received skey, err %d\n",
@@ -2391,14 +2399,18 @@ static void tipc_crypto_work_rx(struct work_struct *work)
case -ENOMEM:
/* Resched the key attaching */
resched = true;
+ spin_lock_bh(&rx->lock);
break;
default:
synchronize_rcu();
+ spin_lock_bh(&rx->lock);
kfree_sensitive(rx->skey);
rx->skey = NULL;
break;
}
+ rx->skey_in_use = false;
}
+ spin_unlock_bh(&rx->lock);

if (resched && queue_delayed_work(tx->wq, &rx->work, delay))
return;
--
2.47.3