Use-After-Free (UAF) due to race between readers and writers in net_shaper data path

HIGH
torvalds/linux
Commit: b8d7519352ba
Affected: v7.0-rc6 and earlier (before this patch)
2026-05-25 23:04 UTC

Description

The commit fixes a race in the net_shaper data path where a reader could observe a valid entry and then the entry could be replaced/freed by a writer, leading to a use-after-free (UAF). The previous implementation relied on XA marks (VALID) to gate access, but marks are not stored atomically with the entry, allowing a reader to continue using a pointer that has been replaced and potentially freed. The patch introduces an explicit valid field with acquire/release semantics, checks validity before dereferencing, and frees entries via kfree_rcu, thereby eliminating the race window and preventing UAF on readers.

Proof of Concept

Pseudo-kernel PoC illustrating the race and how the fix prevents it: // Thread A (Reader) while (true) { struct net_shaper *sh = net_shaper_lookup(binding, &handle); if (!sh) continue; // Potentially dangerous if used with a torn/freed object int val = sh->some_field; // read after obtaining pointer // do something with val schedule(); } // Thread B (Writer/Commit/Rollback) while (true) { // Perform an update which can replace the entry and free the old one via kfree_rcu net_shaper_commit(binding, ...); // or trigger a rollback that erases old entries and frees them net_shaper_rollback(binding, ...); schedule(); } Expected behavior before fix: - Thread A could read sh, and then Thread B could replace/free the underlying object, causing Thread A to dereference freed memory when accessing sh->some_field or other fields. Expected behavior after fix: - net_shaper_lookup checks sh->valid (acquire semantics) before dereferencing; even if the underlying pointer is replaced, a reader will see the invalid state and exit early, preventing UAF. - Writers free entries via kfree_rcu after ensuring readers observing the old pointer can complete safely. Notes: - The PoC is conceptual and targets kernel-space race conditions between readers and writers in the net_shaper data structure. It demonstrates how an old approach using marks could lead to UAF and how the explicit valid flag with proper memory ordering prevents the vulnerability.

Commit Details

Author: Jakub Kicinski

Date: 2026-05-15 22:13 UTC

Message:

net: shaper: rework the VALID marking (again) Recent commit changed the semantics from NOT_VALID to VALID. I didn't realize that the flags are not stored atomically with the entry in XArray. There's still a race of reader observing a VALID mark for a slot, getting interrupted, writer replacing the entry with a different one, reader continuing, fetching the entry which is now a different pointer than the pointer for which VALID was meant. The biggest consequence of this is that we may see a UAF since net_shaper_rollback() assumed that entries without VALID can be freed without observing RCU. Looks like the XArray marks are buying us nothing at this point. Let's convert the code to an explicit valid field. The smp_load_acquire() / smp_store_release() barriers are marginally cleaner. Reported-by: Sashiko <sashiko-bot@kernel.org> Fixes: 93954b40f6a4 ("net-shapers: implement NL set and delete operations") Reviewed-by: Simon Horman <horms@kernel.org> Link: https://patch.msgid.link/20260515221325.1685455-3-kuba@kernel.org Signed-off-by: Jakub Kicinski <kuba@kernel.org>

Triage Assessment

Vulnerability Type: Use-After-Free (UAF)

Confidence: HIGH

Reasoning:

The patch addresses a race between readers and writers on the net_shaper data structure that could lead to a use-after-free (UAF) scenario when a reader observes a VALID mark and then the entry is replaced before the read completes. The fix introduces an explicit valid field with proper memory ordering to prevent UAF and related memory-safety issues, reducing potential information disclosure/privilege issues due to memory safety bugs in the kernel networking path.

Verification Assessment

Vulnerability Type: Use-After-Free (UAF) due to race between readers and writers in net_shaper data path

Confidence: HIGH

Affected Versions: v7.0-rc6 and earlier (before this patch)

Code Diff

diff --git a/include/net/net_shaper.h b/include/net/net_shaper.h index 5c3f49b52fe969..3939b816b0011f 100644 --- a/include/net/net_shaper.h +++ b/include/net/net_shaper.h @@ -53,6 +53,7 @@ struct net_shaper { /* private: */ u32 leaves; /* accounted only for NODE scope */ + bool valid; struct rcu_head rcu; }; diff --git a/net/shaper/shaper.c b/net/shaper/shaper.c index 520cefdc3d9083..dea9270f3e57df 100644 --- a/net/shaper/shaper.c +++ b/net/shaper/shaper.c @@ -306,31 +306,24 @@ static void net_shaper_default_parent(const struct net_shaper_handle *handle, parent->id = 0; } -/* MARK_0 is already in use due to XA_FLAGS_ALLOC. The VALID mark is set on - * an entry only after the device-side configuration has completed - * successfully (see net_shaper_commit()). Lookups and dumps must filter on - * this mark to avoid exposing tentative entries inserted by - * net_shaper_pre_insert() while the driver call is still in flight. - */ -#define NET_SHAPER_VALID XA_MARK_1 - static struct net_shaper * net_shaper_lookup(struct net_shaper_binding *binding, const struct net_shaper_handle *handle) { u32 index = net_shaper_handle_to_index(handle); struct net_shaper_hierarchy *hierarchy; + struct net_shaper *cur; hierarchy = net_shaper_hierarchy_rcu(binding); - if (!hierarchy || !xa_get_mark(&hierarchy->shapers, index, - NET_SHAPER_VALID)) + if (!hierarchy) return NULL; - /* Pairs with smp_wmb() in net_shaper_commit(): if the entry is - * valid, its contents must be visible too. - */ - smp_rmb(); - return xa_load(&hierarchy->shapers, index); + cur = xa_load(&hierarchy->shapers, index); + /* Check valid before reading fields */ + if (!cur || !smp_load_acquire(&cur->valid)) + return NULL; + + return cur; } /* Allocate on demand the per device shaper's hierarchy container. @@ -444,12 +437,10 @@ static void net_shaper_commit(struct net_shaper_binding *binding, if (WARN_ON_ONCE(!cur)) continue; - /* Successful update: drop the tentative mark - * and update the hierarchy container. - */ + /* Successful update: update the hierarchy container... */ net_shaper_copy(cur, &shapers[i]); - smp_wmb(); - __xa_set_mark(&hierarchy->shapers, index, NET_SHAPER_VALID); + /* ... publish to lockless readers. */ + smp_store_release(&cur->valid, true); } xa_unlock(&hierarchy->shapers); } @@ -466,10 +457,10 @@ static void net_shaper_rollback(struct net_shaper_binding *binding) xa_lock(&hierarchy->shapers); xa_for_each(&hierarchy->shapers, index, cur) { - if (xa_get_mark(&hierarchy->shapers, index, NET_SHAPER_VALID)) + if (cur->valid) continue; __xa_erase(&hierarchy->shapers, index); - kfree(cur); + kfree_rcu(cur, rcu); } xa_unlock(&hierarchy->shapers); } @@ -882,12 +873,12 @@ int net_shaper_nl_get_dumpit(struct sk_buff *skb, goto out_unlock; for (; (shaper = xa_find(&hierarchy->shapers, &ctx->start_index, - U32_MAX, NET_SHAPER_VALID)); + U32_MAX, XA_PRESENT)); ctx->start_index++) { - /* Pairs with smp_wmb() in net_shaper_commit(): the entry - * is marked VALID, so its contents must be visible too. - */ - smp_rmb(); + /* Check valid before reading fields */ + if (!smp_load_acquire(&shaper->valid)) + continue; + ret = net_shaper_fill_one(skb, binding, shaper, info); if (ret) break;
← Back to Alerts View on GitHub →