Netdev List
 help / color / mirror / Atom feed
* [PATCH net] tipc: cap number of nodes per net namespace
@ 2026-07-10 15:03 Ibrahim Hashimov
  2026-07-13 13:34 ` Tung Quang Nguyen
  0 siblings, 1 reply; 3+ messages in thread
From: Ibrahim Hashimov @ 2026-07-10 15:03 UTC (permalink / raw)
  To: Jon Maloy, David S . Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni
  Cc: Simon Horman, netdev, tipc-discussion, linux-kernel, stable

tipc_node_create() allocates a new struct tipc_node (plus a broadcast
receive link, a unicast link slot and a keepalive timer) for every
previously unseen (addr, node_id) pair carried in an inbound TIPC
LINK_CONFIG discovery frame:

	n = tipc_node_find(net, addr) ?:
		tipc_node_find_by_id(net, peer_id);
	if (n) {
		...
	}
	n = kzalloc_obj(*n, GFP_ATOMIC);
	...
	n->delete_at = jiffies + msecs_to_jiffies(NODE_CLEANUP_AFTER);

Both addr (msg_prevnode) and peer_id (msg_node_id) come straight out
of the discovery frame and are fully attacker controlled, and the
dedup key above is keyed on exactly those two values. There is no cap
on how many distinct nodes a net namespace may hold and no rate limit
on the create path, so an unauthenticated peer on an enabled TIPC
bearer (L2 or UDP) can flood LINK_CONFIG frames with a fresh (addr,
node_id) in each one and force the kernel to keep minting new,
distinct struct tipc_node objects without bound. A link-less spoofed
node is only reclaimed after NODE_CLEANUP_AFTER (300 s), so at typical
discovery rates the live node table, and the memory pinned by it,
grows roughly linearly with attacker-supplied identities for the
duration of the flood. This is (uncontrolled resource
consumption), reachable by any unauthenticated network-adjacent host
once tipc.ko is loaded and a bearer is enabled.

Bound this the same way net/core/neighbour.c bounds the ARP/ND
neighbour table against unauthenticated on-link input: reject new
entries once a hard ceiling is hit instead of letting the table grow
without limit. struct tipc_net already carries a num_nodes counter
that is declared but never read or written anywhere in net/tipc/; wire
it up on the create and delete paths and add a single bounds check on
it in tipc_node_create(), guarded by the same tn->node_list_lock
spinlock that already serializes every call site of
tipc_node_create(), tipc_node_delete_from_list(), tipc_node_delete()
and tipc_node_stop(). No new locking, no new data structures, and no
change to the node table's data layout or lookup semantics; legitimate
peers are still admitted exactly as before, up to the cap.

TIPC_MAX_NODES is set to 8192, well above any realistic TIPC cluster
size, bounding worst-case pinned memory to a fixed multiple of one
node's footprint instead of unbounded growth. It is intentionally not
tuned tight; exposing it as a sysctl (mirroring
net.ipv4.neigh.default.gc_thresh3) would be a reasonable follow-up but
is left out to keep this fix minimal.

Verified on a v6.19 KASAN build: flooding spoofed (addr, node_id)
peers past the cap makes the patched kernel log "Too many TIPC
nodes (8192)" and drop further peers, where the same flood grew
the live node table without bound before this patch.

Cc: stable@vger.kernel.org
Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
Assisted-by: AuditCode-AI:2026.07
---
 net/tipc/node.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/net/tipc/node.c b/net/tipc/node.c
index 8e4ef2630ae4..bb41f3231ce1 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -49,6 +49,16 @@
 #define INVALID_NODE_SIG	0x10000
 #define NODE_CLEANUP_AFTER	300000
 
+/* Hard cap on the number of live struct tipc_node entries a single net
+ * namespace will hold. Every entry (preliminary or not) also pins a
+ * broadcast-receive link, a unicast link slot and a keepalive timer, so
+ * this bounds worst-case memory from unauthenticated LINK_CONFIG discovery
+ * traffic the same way neigh_alloc()'s gc_thresh3 bounds the ARP/ND table
+ * (see net/core/neighbour.c). 8192 is far above any realistic TIPC cluster
+ * size and is not meant to be tight -- it only stops unbounded growth.
+ */
+#define TIPC_MAX_NODES		8192
+
 /* Flags used to take different actions according to flag type
  * TIPC_NOTIFY_NODE_DOWN: notify node is down
  * TIPC_NOTIFY_NODE_UP: notify node is up
@@ -535,6 +545,11 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
 
 		goto exit;
 	}
+	if (tn->num_nodes >= TIPC_MAX_NODES) {
+		pr_warn_ratelimited("Too many TIPC nodes (%u), dropping new peer %x\n",
+				    tn->num_nodes, addr);
+		goto exit;
+	}
 	n = kzalloc_obj(*n, GFP_ATOMIC);
 	if (!n) {
 		pr_warn("Node creation failed, no memory\n");
@@ -598,6 +613,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
 			break;
 	}
 	list_add_tail_rcu(&n->list, &temp_node->list);
+	tn->num_nodes++;
 	/* Calculate cluster capabilities */
 	tn->capabilities = TIPC_NODE_CAPABILITIES;
 	list_for_each_entry_rcu(temp_node, &tn->node_list, list) {
@@ -630,6 +646,7 @@ static void tipc_node_delete_from_list(struct tipc_node *node)
 #endif
 	list_del_rcu(&node->list);
 	hlist_del_rcu(&node->hash);
+	tipc_net(node->net)->num_nodes--;
 	tipc_node_put(node);
 }
 
-- 
2.50.1 (Apple Git-155)


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* RE: [PATCH net] tipc: cap number of nodes per net namespace
  2026-07-10 15:03 [PATCH net] tipc: cap number of nodes per net namespace Ibrahim Hashimov
@ 2026-07-13 13:34 ` Tung Quang Nguyen
  2026-07-14 17:03   ` Ibrahim Hashimov
  0 siblings, 1 reply; 3+ messages in thread
From: Tung Quang Nguyen @ 2026-07-13 13:34 UTC (permalink / raw)
  To: Ibrahim Hashimov
  Cc: Simon Horman, netdev@vger.kernel.org,
	tipc-discussion@lists.sourceforge.net,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org, Jon Maloy,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni

>Subject: [PATCH net] tipc: cap number of nodes per net namespace
>
>tipc_node_create() allocates a new struct tipc_node (plus a broadcast receive
>link, a unicast link slot and a keepalive timer) for every previously unseen
>(addr, node_id) pair carried in an inbound TIPC LINK_CONFIG discovery frame:
>
>	n = tipc_node_find(net, addr) ?:
>		tipc_node_find_by_id(net, peer_id);
>	if (n) {
>		...
>	}
>	n = kzalloc_obj(*n, GFP_ATOMIC);
>	...
>	n->delete_at = jiffies + msecs_to_jiffies(NODE_CLEANUP_AFTER);
>
>Both addr (msg_prevnode) and peer_id (msg_node_id) come straight out of
>the discovery frame and are fully attacker controlled, and the dedup key above
>is keyed on exactly those two values. There is no cap on how many distinct
>nodes a net namespace may hold and no rate limit on the create path, so an
>unauthenticated peer on an enabled TIPC bearer (L2 or UDP) can flood
>LINK_CONFIG frames with a fresh (addr,
>node_id) in each one and force the kernel to keep minting new, distinct struct
>tipc_node objects without bound. A link-less spoofed node is only reclaimed
>after NODE_CLEANUP_AFTER (300 s), so at typical discovery rates the live node
>table, and the memory pinned by it, grows roughly linearly with attacker-
>supplied identities for the duration of the flood. This is (uncontrolled resource
>consumption), reachable by any unauthenticated network-adjacent host once
>tipc.ko is loaded and a bearer is enabled.
>
>Bound this the same way net/core/neighbour.c bounds the ARP/ND neighbour
>table against unauthenticated on-link input: reject new entries once a hard
>ceiling is hit instead of letting the table grow without limit. struct tipc_net
>already carries a num_nodes counter that is declared but never read or
>written anywhere in net/tipc/; wire it up on the create and delete paths and
>add a single bounds check on it in tipc_node_create(), guarded by the same tn-
>>node_list_lock spinlock that already serializes every call site of
>tipc_node_create(), tipc_node_delete_from_list(), tipc_node_delete() and
>tipc_node_stop(). No new locking, no new data structures, and no change to
>the node table's data layout or lookup semantics; legitimate peers are still
>admitted exactly as before, up to the cap.
>
>TIPC_MAX_NODES is set to 8192, well above any realistic TIPC cluster size,
>bounding worst-case pinned memory to a fixed multiple of one node's
>footprint instead of unbounded growth. It is intentionally not tuned tight;
>exposing it as a sysctl (mirroring
>net.ipv4.neigh.default.gc_thresh3) would be a reasonable follow-up but is left
>out to keep this fix minimal.

I do not see any issue with current code that requires this patch.

>
>Verified on a v6.19 KASAN build: flooding spoofed (addr, node_id) peers past
>the cap makes the patched kernel log "Too many TIPC nodes (8192)" and drop
>further peers, where the same flood grew the live node table without bound
>before this patch.

Can you provide your C reproducer and the stack trace you observed (on latest net-tree) ?

>
>Cc: stable@vger.kernel.org
>Signed-off-by: Ibrahim Hashimov <security@auditcode.ai>
>Assisted-by: AuditCode-AI:2026.07
>---
> net/tipc/node.c | 17 +++++++++++++++++
> 1 file changed, 17 insertions(+)
>
>diff --git a/net/tipc/node.c b/net/tipc/node.c index
>8e4ef2630ae4..bb41f3231ce1 100644
>--- a/net/tipc/node.c
>+++ b/net/tipc/node.c
>@@ -49,6 +49,16 @@
> #define INVALID_NODE_SIG	0x10000
> #define NODE_CLEANUP_AFTER	300000
>
>+/* Hard cap on the number of live struct tipc_node entries a single net
>+ * namespace will hold. Every entry (preliminary or not) also pins a
>+ * broadcast-receive link, a unicast link slot and a keepalive timer,
>+so
>+ * this bounds worst-case memory from unauthenticated LINK_CONFIG
>+discovery
>+ * traffic the same way neigh_alloc()'s gc_thresh3 bounds the ARP/ND
>+table
>+ * (see net/core/neighbour.c). 8192 is far above any realistic TIPC
>+cluster
>+ * size and is not meant to be tight -- it only stops unbounded growth.
>+ */
>+#define TIPC_MAX_NODES		8192
>+
> /* Flags used to take different actions according to flag type
>  * TIPC_NOTIFY_NODE_DOWN: notify node is down
>  * TIPC_NOTIFY_NODE_UP: notify node is up @@ -535,6 +545,11 @@ struct
>tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
>
> 		goto exit;
> 	}
>+	if (tn->num_nodes >= TIPC_MAX_NODES) {
>+		pr_warn_ratelimited("Too many TIPC nodes (%u), dropping
>new peer %x\n",
>+				    tn->num_nodes, addr);
>+		goto exit;
>+	}
> 	n = kzalloc_obj(*n, GFP_ATOMIC);
> 	if (!n) {
> 		pr_warn("Node creation failed, no memory\n"); @@ -598,6
>+613,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8
>*peer_id,
> 			break;
> 	}
> 	list_add_tail_rcu(&n->list, &temp_node->list);
>+	tn->num_nodes++;
> 	/* Calculate cluster capabilities */
> 	tn->capabilities = TIPC_NODE_CAPABILITIES;
> 	list_for_each_entry_rcu(temp_node, &tn->node_list, list) { @@ -630,6
>+646,7 @@ static void tipc_node_delete_from_list(struct tipc_node *node)
>#endif
> 	list_del_rcu(&node->list);
> 	hlist_del_rcu(&node->hash);
>+	tipc_net(node->net)->num_nodes--;
> 	tipc_node_put(node);
> }
>
>--
>2.50.1 (Apple Git-155)
>


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH net] tipc: cap number of nodes per net namespace
  2026-07-13 13:34 ` Tung Quang Nguyen
@ 2026-07-14 17:03   ` Ibrahim Hashimov
  0 siblings, 0 replies; 3+ messages in thread
From: Ibrahim Hashimov @ 2026-07-14 17:03 UTC (permalink / raw)
  To: tung.quang.nguyen, jmaloy, davem, edumazet, kuba, pabeni
  Cc: horms, netdev, tipc-discussion, linux-kernel

> I do not see any issue with current code that requires this patch.

tipc_node_create() allocates a struct tipc_node (plus a broadcast rcv
link, a unicast link and a keepalive timer) for every (addr, node_id)
it hasn't seen before, and both come straight off the discovery frame
(msg_prevnode / msg_node_id). There's no cap and no rate limit on that
path, and a link-less spoofed node isn't reclaimed until
NODE_CLEANUP_AFTER (300s). So an unauthenticated peer on an enabled
bearer can pin memory just by handing out fresh identities.

I measured it: 2000 discovery frames each with a distinct (addr,node_id)
-> 2000 live nodes (Slab +~15MB); the same 2000 frames sharing one
identity -> 1 node. The only variable is uniqueness, so it's the missing
cap and not frame volume.

Still the case on net HEAD: tipc_net.num_nodes is declared but never
read or written anywhere in net/tipc/, and the create path has no bounds
check. The patch just wires up that dead counter, the same way
neigh_alloc() checks gc_thresh3 before adding a struct neighbour for
unauthenticated on-link input.

> Can you provide your C reproducer and the stack trace you observed
> (on latest net-tree) ?

There's no stack trace - this is resource exhaustion, not corruption.
The alloc is GFP_ATOMIC and NULL-checked, so it just grows memory; KASAN
doesn't fire and there's nothing to paste. I should have written that in
the changelog instead of "KASAN build", which was misleading - sorry.

The reproducer is python (genetlink + raw AF_PACKET injection of a
captured DSC_REQ over a veth pair, no userspace tipc needed), not C.
Happy to send it or port it to C if that's useful. It ran on a v6.19
stand rather than net; since node.c is unchanged there bar the
kzalloc_obj rename I don't expect a difference, but I can re-run on
net-next. One honest caveat: the single-VM harness is softirq-drain
limited (a 10000-frame flood only reached ~3042 nodes before the cap),
so "unbounded at line rate" is extrapolation from the 2000-vs-1 result,
not something I clocked at line rate.

That said, the bearer is a trusted-cluster segment by design. If your
position is that on-bearer discovery peers are inside the trust
boundary, that's fair enough and I'm happy to drop this. I only sent it
because neigh bounds the equivalent ARP/ND case, so it seemed worth
doing the same here.

Thanks,
Ibrahim

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-14 17:04 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 15:03 [PATCH net] tipc: cap number of nodes per net namespace Ibrahim Hashimov
2026-07-13 13:34 ` Tung Quang Nguyen
2026-07-14 17:03   ` Ibrahim Hashimov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox