Thread (7 messages) 7 messages, 2 authors, 3d ago

RE: [PATCH net v4 2/2] tipc: fix NULL deref in tipc_named_node_up() on empty publication list

From: Tung Quang Nguyen <hidden>
Date: 2026-07-17 09:48:13
Also in: lkml
Subsystem: networking [general], the rest, tipc network layer · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Jon Maloy

quoted hunk ↗ jump to hunk
Subject: [PATCH net v4 2/2] tipc: fix NULL deref in tipc_named_node_up() on
empty publication list

named_distribute() ends by stamping the last_bulk flag on the tail skb via
buf_msg(skb_peek_tail(list)). When the publication list is empty no skb is
enqueued, skb_peek_tail() returns NULL, and buf_msg(NULL) is dereferenced.

tipc_named_node_up() runs this on &nt->cluster_scope. With a node-id
configuration cluster_scope is populated only later by tipc_net_finalize(), so a
peer link that comes up first reaches named_distribute() with an empty list. It
is reachable by an unprivileged user (TIPC genl ops use
GENL_UNS_ADMIN_PERM) over a UDP bearer in a user+net namespace:

KASAN: null-ptr-deref in range [0x00000000000000d8-0x00000000000000df]
RIP: 0010:tipc_named_node_up (net/tipc/name_distr.c:196)
 tipc_named_node_up (net/tipc/name_distr.c:196 net/tipc/name_distr.c:221)
 tipc_node_write_unlock (net/tipc/node.c:428)
 tipc_rcv (net/tipc/node.c:2185)
 tipc_udp_recv (net/tipc/udp_media.c:392)  Kernel panic - not syncing: Fatal
exception in interrupt

The peer holds back this node's later name updates until it sees a bulk with the
last_bulk flag, so simply skipping the send would stall it. Emit an item-less bulk
when the publication list is empty, so the peer still receives the last_bulk flag
and opens.

Fixes: cad2929dc432 ("tipc: update a binding service via broadcast")
Reported-by: Xiang Mei <redacted>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <redacted>
---
net/tipc/name_distr.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c index
ba4f4906e13b..a8bb7bd101ea 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -192,6 +192,20 @@ static void named_distribute(struct net *net, struct
sk_buff_head *list,
		skb_trim(skb, INT_H_SIZE + (msg_dsz - msg_rem));
		__skb_queue_tail(list, skb);
	}
+
+	if (skb_queue_empty(list)) {
+		skb = named_prepare_buf(net, PUBLICATION, 0, dnode);
+		if (!skb) {
+			pr_warn("Bulk publication failure\n");
+			return;
+		}
+		hdr = buf_msg(skb);
+		msg_set_bc_ack_invalid(hdr, true);
+		msg_set_bulk(hdr);
+		msg_set_non_legacy(hdr);
+		__skb_queue_tail(list, skb);
+	}
As I explained before, this approach is wrong because 
1. It does not handle memory allocation failure.
2. It breaks receiving peer by  sending non-data message to that peer in case skb is not NULL.

Could you please test below patch to see if it fixes the NULL dereference issue you reported ?

---
 net/tipc/core.c       |  1 +
 net/tipc/core.h       |  2 ++
 net/tipc/name_distr.c | 48 +++++++++++++++++++++++++++++++++++++++----
 net/tipc/name_distr.h |  3 ++-
 net/tipc/net.c        |  2 ++
 net/tipc/node.c       | 34 ++++++++++++++++++++++++++++--
 6 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/net/tipc/core.c b/net/tipc/core.c
index 315975c3be81..9e81be4f01cf 100644
--- a/net/tipc/core.c
+++ b/net/tipc/core.c
@@ -61,6 +61,7 @@ static int __net_init tipc_init_net(struct net *net)
        tn->trial_addr = 0;
        tn->addr_trial_end = 0;
        tn->capabilities = TIPC_NODE_CAPABILITIES;
+       atomic_set(&tn->finalized, 0);
        INIT_WORK(&tn->work, tipc_net_finalize_work);
        memset(tn->node_id, 0, sizeof(tn->node_id));
        memset(tn->node_id_string, 0, sizeof(tn->node_id_string));
diff --git a/net/tipc/core.h b/net/tipc/core.h
index 9ce5f9ff6cc0..76768844c808 100644
--- a/net/tipc/core.h
+++ b/net/tipc/core.h
@@ -145,6 +145,8 @@ struct tipc_net {
        struct work_struct work;
        /* The numbers of work queues in schedule */
        atomic_t wq_count;
+       /* flag to indicate work has finished */
+       atomic_t finalized;
 };
 
 static inline struct tipc_net *tipc_net(struct net *net)
diff --git a/net/tipc/name_distr.c b/net/tipc/name_distr.c
index ba5f4906e13b..8a1692dbd243 100644
--- a/net/tipc/name_distr.c
+++ b/net/tipc/name_distr.c
@@ -147,7 +147,7 @@ struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *p)
  * @pls: linked list of publication items to be packed into buffer chain
  * @seqno: sequence number for this message
  */
-static void named_distribute(struct net *net, struct sk_buff_head *list,
+static int named_distribute(struct net *net, struct sk_buff_head *list,
                             u32 dnode, struct list_head *pls, u16 seqno)
 {
        struct publication *publ;
@@ -164,8 +164,9 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
                        skb = named_prepare_buf(net, PUBLICATION, msg_rem,
                                                dnode);
                        if (!skb) {
+                               __skb_queue_purge(list);
                                pr_warn("Bulk publication failure\n");
-                               return;
+                               return 1;
                        }
                        hdr = buf_msg(skb);
                        msg_set_bc_ack_invalid(hdr, true);
@@ -195,6 +196,8 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
        hdr = buf_msg(skb_peek_tail(list));
        msg_set_last_bulk(hdr);
        msg_set_named_seqno(hdr, seqno);
+
+       return 0;
 }

 /**
@@ -203,7 +206,7 @@ static void named_distribute(struct net *net, struct sk_buff_head *list,
  * @dnode: destination node
  * @capabilities: peer node's capabilities
  */
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
 {
        struct name_table *nt = tipc_name_table(net);
        struct tipc_net *tn = tipc_net(net);
@@ -218,9 +221,46 @@ void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities)
        spin_unlock_bh(&tn->nametbl_lock);

        read_lock_bh(&nt->cluster_scope_lock);
-       named_distribute(net, &head, dnode, &nt->cluster_scope, seqno);
+       /* tipc_net_finalize_work() has not finished inserting self address to
+         * name table yet.
+         */
+       if (unlikely(list_empty(&nt->cluster_scope))) {
+               read_unlock_bh(&nt->cluster_scope_lock);
+               return 1;
+       }
+
+       if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+               read_unlock_bh(&nt->cluster_scope_lock);
+               return -ENOBUFS;
+       }
+
        tipc_node_xmit(net, &head, dnode, 0);
        read_unlock_bh(&nt->cluster_scope_lock);
+       return 0;
+}
+
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode)
+{
+       struct name_table *nt = tipc_name_table(net);
+       struct tipc_net *tn = tipc_net(net);
+       struct sk_buff_head head;
+       u16 seqno;
+
+       __skb_queue_head_init(&head);
+       wait_var_event(&tn->finalized, atomic_read(&tn->finalized));
+       spin_lock_bh(&tn->nametbl_lock);
+       seqno = nt->snd_nxt;
+       spin_unlock_bh(&tn->nametbl_lock);
+
+       read_lock_bh(&nt->cluster_scope_lock);
+       if (named_distribute(net, &head, dnode, &nt->cluster_scope, seqno)) {
+               read_unlock_bh(&nt->cluster_scope_lock);
+               return -ENOBUFS;
+       }
+       tipc_node_xmit(net, &head, dnode, 0);
+       read_unlock_bh(&nt->cluster_scope_lock);
+
+       return 0;
 }

 /**
diff --git a/net/tipc/name_distr.h b/net/tipc/name_distr.h
index c677f6f082df..cadf4e8c3e66 100644
--- a/net/tipc/name_distr.h
+++ b/net/tipc/name_distr.h
@@ -69,7 +69,8 @@ struct distr_item {

 struct sk_buff *tipc_named_publish(struct net *net, struct publication *publ);
 struct sk_buff *tipc_named_withdraw(struct net *net, struct publication *publ);
-void tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_node_up(struct net *net, u32 dnode, u16 capabilities);
+int tipc_named_dist_cluster_scope(struct net *net, u32 dnode);
 void tipc_named_rcv(struct net *net, struct sk_buff_head *namedq,
                    u16 *rcv_nxt, bool *open);
 void tipc_named_reinit(struct net *net);
diff --git a/net/tipc/net.c b/net/tipc/net.c
index 7e65d0b0c4a8..4c144e720ac1 100644
--- a/net/tipc/net.c
+++ b/net/tipc/net.c
@@ -139,6 +139,8 @@ static void tipc_net_finalize(struct net *net, u32 addr)
        tipc_sk_reinit(net);
        tipc_mon_reinit_self(net);
        tipc_nametbl_publish(net, &ua, &sk, addr);
+       atomic_inc(&tn->finalized);
+       wake_up_var(&tn->finalized);
 }

 void tipc_net_finalize_work(struct work_struct *work)
diff --git a/net/tipc/node.c b/net/tipc/node.c
index 8e4ef2630ae4..c5b0a98324c3 100644
--- a/net/tipc/node.c
+++ b/net/tipc/node.c
@@ -145,6 +145,8 @@ struct tipc_node {
 #ifdef CONFIG_TIPC_CRYPTO
        struct tipc_crypto *crypto_rx;
 #endif
+       /* Work item for bulk distribution of cluster scope publications */
+       struct work_struct work;
 };

 /* Node FSM states and events:
@@ -303,6 +305,7 @@ static void tipc_node_free(struct rcu_head *rp)
 #ifdef CONFIG_TIPC_CRYPTO
        tipc_crypto_stop(&n->crypto_rx);
 #endif
+       cancel_work_sync(&n->work);
        kfree(n);
 }
@@ -393,6 +396,19 @@ static void tipc_node_write_unlock_fast(struct tipc_node *n)
        write_unlock_bh(&n->lock);
 }

+static void tipc_node_dist_bulk(struct work_struct *work)
+{
+       struct tipc_node *node = container_of(work, struct tipc_node, work);
+
+       if (tipc_named_dist_cluster_scope(node->net, node->addr) < 0) {
+               u32 bearer_id = node->link_id & 0xffff;
+
+               tipc_node_link_down(node, bearer_id, false);
+       }
+
+       tipc_node_put(node);
+}
+
static void tipc_node_write_unlock(struct tipc_node *n)
        __releases(n->lock)
 {
@@ -424,8 +440,21 @@ static void tipc_node_write_unlock(struct tipc_node *n)
        if (flags & TIPC_NOTIFY_NODE_DOWN)
                tipc_publ_notify(net, publ_list, node, n->capabilities);

-       if (flags & TIPC_NOTIFY_NODE_UP)
-               tipc_named_node_up(net, node, n->capabilities);
+       if (flags & TIPC_NOTIFY_NODE_UP) {
+               int rc = 0;
+
+               rc = tipc_named_node_up(net, node, n->capabilities);
+               /* Defer bulk distribution to work queue */
+               if (rc > 0) {
+                       tipc_node_get(n);
+                       schedule_work(&n->work);
+               } else if (rc < 0) {
+                       /* Bring the link down to start over bulk distribution
+                        * when the link is up again.
+                        */
+                       tipc_node_link_down(n, bearer_id, false);
+               }
+       }

        if (flags & TIPC_NOTIFY_LINK_UP) {
                tipc_mon_peer_up(net, node, bearer_id);
@@ -564,6 +593,7 @@ struct tipc_node *tipc_node_create(struct net *net, u32 addr, u8 *peer_id,
        INIT_LIST_HEAD(&n->list);
        INIT_LIST_HEAD(&n->publ_list);
        INIT_LIST_HEAD(&n->conn_sks);
+       INIT_WORK(&n->work, tipc_node_dist_bulk);
        skb_queue_head_init(&n->bc_entry.namedq);
        skb_queue_head_init(&n->bc_entry.inputq1);
        __skb_queue_head_init(&n->bc_entry.arrvq);
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help