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

RE: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up() on empty publication list

From: Tung Quang Nguyen <hidden>
Date: 2026-07-21 03:24:33
Also in: lkml

Subject: Re: [PATCH net v8 1/3] tipc: fix NULL deref in tipc_named_node_up()
on empty publication list

Hi Tung,

I tested this on v7.1-rc5 with the two-node setup (UDP bearers, node-id
addressing), as an unprivileged user and as root.
The original NULL deref is fixed, and the sashiko findings look addressed.

But I found one path the patch misses: tipc_node_cleanup(). It removes a stale
node with tipc_node_delete_from_list() directly, without the wake/cancel you
added in tipc_node_delete(). If a deferred worker is still parked when the stale
timer fires, the node is removed from the list while the worker is sleeping in
wait_var_event(). Since the node is off the list, the wake in tipc_node_delete()
can no longer reach it. The worker stays in D state and keeps its node
reference, so the node struct leaks as well.

This state is reachable from userspace without any kernel change:
filling local_publ_count to TIPC_MAX_PUBL with node-scope binds makes
tipc_net_finalize()'s publish fail, and the work_rescheduled retry then keeps
finalized at 0, so the link-up defers the worker for as long as the table stays full.
(The retry also也重试 busy-loops, printing "tipc: Bind failed, max limit 65535
reached" each round.) Bringing the link down makes the peer node go stale
NODE_CLEANUP_AFTER (300s) later, with the worker still parked.
The hung task watchdog flags it:
    [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
    [  370.441580] Workqueue: events tipc_node_dist_bulk
    [  370.442803] Call Trace:
    [  370.443151]  __schedule+0x18bf/0x4680
    [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220
    [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
    [  370.446218]  process_one_work+0x845/0x1a60 ```

With a test-only marker printk in tipc_node_cleanup() I can see the stale timer
remove both nodes at ~368s and ~375s, i.e. while the workers above are still
parked. Nothing wakes them on that path; in this run they only exit because
killing the reproducer closes the bind sockets, so the next retry's publish
succeeds. With a persistent failure source (e.g. memory
pressure) the worker would stay parked across the netns teardown as well.
[  247.560544] INFO: task kworker/0:0:9 blocked for more than 122 seconds.
    [  247.568089] INFO: task kworker/0:2:67 blocked for more than 122
seconds.

    [  370.440401] INFO: task kworker/0:0:9 blocked for more than 245 seconds.
    [  370.441580] Workqueue: events tipc_node_dist_bulk
    [  370.442803] Call Trace:
    [  370.443151]  __schedule+0x18bf/0x4680
    [  370.444293]  tipc_named_dist_cluster_scope+0x1a0/0x220     ←
wait_var_event 睡眠点
    [  370.446076]  tipc_node_dist_bulk+0x6d/0x1b0
    [  370.446218]  process_one_work+0x845/0x1a60
The Configs and reproducer 
CONFIG_TIPC=y,
CONFIG_TIPC_MEDIA_UDP=y, CONFIG_USER_NS=y, CONFIG_NET_NS=y,
CONFIG_KASAN=y (crash on the unpatched kernel),
CONFIG_DETECT_HUNG_TASK=y ```
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/genetlink.h>
#include <linux/if_link.h>
#include <linux/veth.h>
#include <linux/tipc.h>

#ifndef AF_TIPC
#define AF_TIPC 30
#endif
#ifndef SOCK_RDM
#define SOCK_RDM 4
#endif

#ifndef VETH_INFO_PEER
#define VETH_INFO_PEER 1
#endif

#define TIPC_GENL_V2_NAME "TIPCv2"

enum {
       TIPC_NL_UNSPEC,
       TIPC_NL_LEGACY,
       TIPC_NL_BEARER_DISABLE,
       TIPC_NL_BEARER_ENABLE,
       TIPC_NL_BEARER_GET,
       TIPC_NL_BEARER_SET,
       TIPC_NL_SOCK_GET,
       TIPC_NL_PUBL_GET,
       TIPC_NL_LINK_GET,
       TIPC_NL_LINK_SET,
       TIPC_NL_LINK_RESET_STATS,
       TIPC_NL_MEDIA_GET,
       TIPC_NL_MEDIA_SET,
       TIPC_NL_NODE_GET,
       TIPC_NL_NET_GET,
       TIPC_NL_NET_SET,
};

enum {
       TIPC_NLA_UNSPEC,
       TIPC_NLA_BEARER,
       TIPC_NLA_SOCK,
       TIPC_NLA_PUBL,
       TIPC_NLA_LINK,
       TIPC_NLA_MEDIA,
       TIPC_NLA_NODE,
       TIPC_NLA_NET,
};

enum {
       TIPC_NLA_BEARER_UNSPEC,
       TIPC_NLA_BEARER_NAME,
       TIPC_NLA_BEARER_PROP,
       TIPC_NLA_BEARER_DOMAIN,
       TIPC_NLA_BEARER_UDP_OPTS,
};

enum {
       TIPC_NLA_UDP_UNSPEC,
       TIPC_NLA_UDP_LOCAL,
       TIPC_NLA_UDP_REMOTE,
       TIPC_NLA_UDP_MULTI_REMOTEIP,
};

enum {
       TIPC_NLA_NET_UNSPEC,
       TIPC_NLA_NET_ID,
       TIPC_NLA_NET_ADDR,
       TIPC_NLA_NET_NODEID,
       TIPC_NLA_NET_NODEID_W1,
};

#define TIPC_UDP_PORT 6118
#define NLA_ALIGNTO 4
#define MY_NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO -
1))

static int tipc_family;

static int nl_open(int protocol)
{
       int fd = socket(AF_NETLINK, SOCK_RAW, protocol);
       if (fd < 0) { perror("socket(NETLINK)"); exit(1); }
       struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
       if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
               perror("bind(NETLINK)"); exit(1);
       }
       return fd;
}

static void *nla_put(char *buf, int *off, int type, const void *data, int len) {
       struct nlattr *a = (struct nlattr *)(buf + *off);
       a->nla_type = type;
       a->nla_len = NLA_HDRLEN + len;
       if (len)
               memcpy((char *)a + NLA_HDRLEN, data, len);
       *off += MY_NLA_ALIGN(a->nla_len);
       return a;
}

static struct nlattr *nla_nest_start(char *buf, int *off, int type) {
       struct nlattr *a = (struct nlattr *)(buf + *off);
       a->nla_type = type | NLA_F_NESTED;
       *off += NLA_HDRLEN;
       return a;
}

static void nla_nest_end(char *buf, int *off, struct nlattr *start) {
       start->nla_len = (buf + *off) - (char *)start; }

static int genl_resolve_family(int fd, const char *name) {
       char buf[1024];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + GENL_HDRLEN;

       nlh->nlmsg_type = GENL_ID_CTRL;
       nlh->nlmsg_flags = NLM_F_REQUEST;
       nlh->nlmsg_seq = 1;
       gh->cmd = CTRL_CMD_GETFAMILY;
       gh->version = 1;
       nla_put(buf, &off, CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1);
       nlh->nlmsg_len = off;

       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send genl resolve");
exit(1); }

       char rbuf[4096];
       int n = recv(fd, rbuf, sizeof(rbuf), 0);
       if (n < 0) { perror("recv genl resolve"); exit(1); }

       struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
       if (rh->nlmsg_type == NLMSG_ERROR)
               return -1;
       struct nlattr *a = (struct nlattr *)((char *)NLMSG_DATA(rh) +
GENL_HDRLEN);
       int alen阿伦 = rh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN;
       while (alen > 0) {
               if (a->nla_type == CTRL_ATTR_FAMILY_ID)
                       return *(uint16_t *)((char *)a + NLA_HDRLEN);
               int step = MY_NLA_ALIGN(a->nla_len);
               alen -= step;
               a = (struct nlattr *)((char *)a + step);
       }
       return -1;
}

static int nl_recv_ack(int fd, const char *what) {
       char rbuf[8192];
       int n = recv(fd, rbuf, sizeof(rbuf), 0);
       if (n < 0) { fprintf(stderr, "recv ack (%s): %s\n", what, strerror(errno));
return -1; }
       struct nlmsghdr *rh = (struct nlmsghdr *)rbuf;
       if (rh->nlmsg_type == NLMSG_ERROR) {
               struct nlmsgerr *e = NLMSG_DATA(rh);
               if (e->error != 0)
                       fprintf(stderr, "[%s] netlink error: %d (%s)\n", what, e->error,
strerror(-e->error));
               return e->error;
       }
       return 0;
}

static void *rta_put(char *buf, int *off, int type, const void *data, int len) {
       struct rtattr *a = (struct rtattr *)(buf + *off);
       a->rta_type = type;
       a->rta_len = RTA_LENGTH(len);
       if (len) memcpy(RTA_DATA(a), data, len);
       *off += RTA_ALIGN(a->rta_len);
       return a;
}

static struct rtattr *rta_nest(char *buf, int *off, int type) {
       struct rtattr *a = (struct rtattr *)(buf + *off);
       a->rta_type = type | NLA_F_NESTED;
       *off += RTA_LENGTH(0);
       return a;
}

static void rta_nest_end(char *buf, int *off, struct rtattr *start) {
       start->rta_len = (buf + *off) - (char *)start; }

static void create_veth(int fd, const char *n0, const char *n1, int peer_nsfd) {
       char buf[2048];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct ifinfomsg *ifi = NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + sizeof(*ifi);

       nlh->nlmsg_type = RTM_NEWLINK;
       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
NLM_F_EXCL;
       nlh->nlmsg_seq = 10;
       ifi->ifi_family = AF_UNSPEC;

       rta_put(buf, &off, IFLA_IFNAME, n0, strlen(n0) + 1);
       struct rtattr *linfo = rta_nest(buf, &off, IFLA_LINKINFO);
       rta_put(buf, &off, IFLA_INFO_KIND, "veth", 5);
       struct rtattr *idata = rta_nest(buf, &off, IFLA_INFO_DATA);
       struct rtattr *peer = rta_nest(buf, &off, VETH_INFO_PEER);
       struct ifinfomsg *pifi = (struct ifinfomsg *)(buf + off);
       memset(pifi, 0, sizeof(*pifi));
       off += sizeof(*pifi);
       rta_put(buf, &off, IFLA_IFNAME, n1, strlen(n1) + 1);
       rta_put(buf, &off, IFLA_NET_NS_FD, &peer_nsfd, sizeof(peer_nsfd));
       rta_nest_end(buf, &off, peer);
       rta_nest_end(buf, &off, idata);
       rta_nest_end(buf, &off, linfo);
       nlh->nlmsg_len = off;

       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send veth"); exit(1); }
       int e = nl_recv_ack(fd, "create_veth");
       if (e && e != -EEXIST) { fprintf(stderr, "veth create failed:
%d\n", e); exit(1); }
}

static void set_if_up(int fd, const char *name) {
       char buf[512];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct ifinfomsg *ifi = NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + sizeof(*ifi);
       nlh->nlmsg_type = RTM_NEWLINK;
       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
       nlh->nlmsg_seq = 20;
       ifi->ifi_family = AF_UNSPEC;
       ifi->ifi_index = if_nametoindex(name);
       ifi->ifi_flags = IFF_UP;
       ifi->ifi_change = IFF_UP;
       nlh->nlmsg_len = off;
       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send up"); exit(1); }
       nl_recv_ack(fd, "set_if_up");
}

static void add_ipv4(int fd, const char *name, const char *ip, int prefix) {
       char buf[512];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct ifaddrmsg *ifa = NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + sizeof(*ifa);
       nlh->nlmsg_type = RTM_NEWADDR;
       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE |
NLM_F_REPLACE;
       nlh->nlmsg_seq = 30;
       ifa->ifa_family = AF_INET;
       ifa->ifa_prefixlen = prefix;
       ifa->ifa_scope = 0;
       ifa->ifa_index = if_nametoindex(name);

       struct in_addr a;
       inet_pton(AF_INET, ip, &a);
       rta_put(buf, &off, IFA_LOCAL, &a, 4);
       rta_put(buf, &off, IFA_ADDRESS, &a, 4);
       nlh->nlmsg_len = off;
       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send addr"); exit(1); }
       nl_recv_ack(fd, "add_ipv4");
}

static void tipc_set_node_id(int fd, uint64_t w0, uint64_t w1) {
       char buf[1024];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + GENL_HDRLEN;
       nlh->nlmsg_type = tipc_family;
       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
       nlh->nlmsg_seq = 40;
       gh->cmd = TIPC_NL_NET_SET;
       gh->version = 1;

       struct nlattr *net = nla_nest_start(buf, &off, TIPC_NLA_NET);
       nla_put(buf, &off, TIPC_NLA_NET_NODEID, &w0, sizeof(w0));
       nla_put(buf, &off, TIPC_NLA_NET_NODEID_W1, &w1, sizeof(w1));
       nla_nest_end(buf, &off, net);
       nlh->nlmsg_len = off;

       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send net_set"); exit(1); }
       int e = nl_recv_ack(fd, "tipc_set_node_id");
       if (e) fprintf(stderr, "net_set(nodeid) returned %d\n", e); }

static void put_sockaddr_storage_v4(char *buf, int *off, int type, const char *ip,
int port) {
       struct sockaddr_storage ss;
       memset(&ss, 0, sizeof(ss));
       struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
       sin->sin_family = AF_INET;
       sin->sin_port = htons(port);
       inet_pton(AF_INET, ip, &sin->sin_addr);
       nla_put(buf, off, type, &ss, sizeof(ss)); }

static void tipc_udp_bearer(int fd, int cmd, const char *bname,
                           const char *local_ip, const char *remote_ip) {
       char buf[2048];
       memset(buf, 0, sizeof(buf));
       struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
       struct genlmsghdrgenlmsghdr *gh = (struct genlmsghdrgenlmsghdr
*)NLMSG_DATA(nlh);
       int off = NLMSG_HDRLEN + GENL_HDRLEN;
       nlh->nlmsg_type = tipc_family;
       nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
       nlh->nlmsg_seq = 50;
       gh->cmd = cmd;
       gh->version = 1;

       struct nlattr *bearer = nla_nest_start(buf, &off, TIPC_NLA_BEARER);
       nla_put(buf, &off, TIPC_NLA_BEARER_NAME, bname名称,
strlen(bname名称) + 1);
       if (cmd == TIPC_NL_BEARER_ENABLE) {
               struct nlattr *udp = nla_nest_start(buf, &off,
TIPC_NLA_BEARER_UDP_OPTS);
               put_sockaddr_storage_v4(buf, &off, TIPC_NLA_UDP_LOCAL, local_ip,
TIPC_UDP_PORT);
               put_sockaddr_storage_v4(buf, &off, TIPC_NLA_UDP_REMOTE,
remote_ip, TIPC_UDP_PORT);
               nla_nest_end(buf, &off, udp);
       }
       nla_nest_end(buf, &off, bearer);
       nlh->nlmsg_len = off;

       if (send(fd, buf, nlh->nlmsg_len, 0) < 0) { perror("send bearer"); exit(1); }
       int e = nl_recv_ack(fd, "tipc_udp_bearer");
       if (e) fprintf(stderr, "bearer cmd %d (%s) returned %d\n", cmd,
bname名称, e); }

static int write_file(const char *path, const char *val) {
       int fd = open(path, O_WRONLY);
       if (fd < 0) return -1;
       int r = write(fd, val, strlen(val));
       close(fd);
       return r;
}

static int bindfill_node_scope(int target) {
       int fd = socket(AF_TIPC, SOCK_RDM, 0);
       if (fd < 0) { perror("socket(AF_TIPC)"); exit(1); }
       int ok = 0, fail = 0;
       for (int i = 0; i < target + 1000; i++) {
               struct sockaddr_tipc sa;
               memset(&sa, 0, sizeof(sa));
               sa.family = AF_TIPC;
               sa.addrtype = TIPC_SERVICE_ADDR;
               sa.scope = TIPC_NODE_SCOPE;
               sa.addr.nameseq.type = 100000 + i;
               sa.addr.nameseq.lower = 100000 + i;
               sa.addr.nameseq.upper = 100000 + i;
               if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) == 0) {
                       ok++;
                       fail = 0;
               } else if (++fail > 100) {
                       break;
               }
       }
       fprintf(stderr, "[*] bound %d node-scope publications (cap reached)\n",
ok);
       if (ok < 65000) { fprintf(stderr, "bindfill shortfall\n"); exit(1); }
       return fd;
}

static void child_main(int sync_rd, int sync_wr) {
       char c;
       write(sync_wr, "R", 1);
       if (read(sync_rd, &c, 1) != 1) _exit(1);

       int rfd = nl_open(NETLINK_ROUTE);
       set_if_up(rfd, "lo");
       set_if_up(rfd, "veth1");
       add_ipv4(rfd, "veth1", "10.0.0.2", 24);

       int gfd = nl_open(NETLINK_GENERIC);
       tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
       if (tipc_family < 0) _exit(1);

       int bfd = bindfill_node_scope(65535);
       tipc_set_node_id(gfd, 0x2222222222222222ULL,
0x2222222222222222ULL);
       tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b1", "10.0.0.2",
"10.0.0.1");
       write(sync_wr, "C", 1);

       sleep(35);
       tipc_udp_bearer(gfd, TIPC_NL_BEARER_DISABLE, "udp:b1", NULL, NULL);
       fprintf(stderr, "[child] b1 disabled, keeping it down\n");
       (void)bfd;
       for (;;) pause();
}

int main(void)
{
       setvbuf(stdout, NULL, _IONBF, 0);
       setvbuf(stderr, NULL, _IONBF, 0);

       uid_t uid = getuid();
       gid_t gid = getgid();

       if (unshare(CLONE_NEWUSER | CLONE_NEWNET) == 0) {
               write_file("/proc/self/setgroups", "deny");
               char m[64];
               snprintf(m, sizeof(m), "0 %d 1", uid);
               write_file("/proc/self/uid_map", m);
               snprintf(m, sizeof(m), "0 %d 1", gid);
               write_file("/proc/self/gid_map", m);
       } else if (unshare(CLONE_NEWNET) < 0) {
               perror("unshare");
               return 1;
       }

       int p2c[2], c2p[2];
       if (pipe(p2c) < 0 || pipe(c2p) < 0) { perror("pipe"); return 1; }

       pid_t pid = fork();
       if (pid == 0) {
               close(p2c[1]);
               close(c2p[0]);
               if (unshare(CLONE_NEWNET) < 0) _exit(1);
               child_main(p2c[0], c2p[1]);
               _exit(0);
       }
       close(p2c[0]);
       close(c2p[1]);

       char c;
       if (read(c2p[0], &c, 1) != 1) { fprintf(stderr, "child not ready\n"); return 1; }

       char nspath[64];
       snprintf(nspath, sizeof(nspath), "/proc/%d/ns/net", pid);
       int nsfd = open(nspath, O_RDONLY);
       if (nsfd < 0) { perror("open ns"); return 1; }

       int rfd = nl_open(NETLINK_ROUTE);
       create_veth(rfd, "veth0", "veth1", nsfd);
       close(nsfd);
       set_if_up(rfd, "lo");
       set_if_up(rfd, "veth0");
       add_ipv4(rfd, "veth0", "10.0.0.1", 24);
       write(p2c[1], "G", 1);

       int gfd = nl_open(NETLINK_GENERIC);
       tipc_family = genl_resolve_family(gfd, TIPC_GENL_V2_NAME);
       if (tipc_family < 0) return 1;
       int bfd = bindfill_node_scope(65535);
       tipc_set_node_id(gfd, 0x1111111111111111ULL,
0x1111111111111111ULL);

       if (read(c2p[0], &c, 1) != 1)
               fprintf(stderr, "child config sync missed\n");
       tipc_udp_bearer(gfd, TIPC_NL_BEARER_ENABLE, "udp:b0", "10.0.0.1",
"10.0.0.2");

       fprintf(stderr, "[*] nodes configured; waiting for stale cleanup\n");
       (void)bfd;
       sleep(400);
       kill(pid, SIGKILL);
       waitpid(pid, NULL, 0);
       fprintf(stderr, "[*] done\n");
       return 0;
}

Do you think tipc_node_cleanup() should go through the same wake logic as
tipc_node_delete()? Happy to test the next version.
Thanks for your test report and reproducer. It will look into the reproducer to fix outstanding issues.
Thanks,
Weiming
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help