[PATCH net] tipc: don't count unjoined publications as receivers

From: shaos

Date: Sat Nov 07 2026 - 03:26:27 EST


Hello,

A local process with no capabilities can block sends from a TIPC
communication group by publishing the matching service range from a
socket that never joins the group. I reproduced it with a root sender
and a separate unprivileged group member on the same node. A third,
unprivileged process binds the same type and instance as an ordinary
TIPC service. The sender's first nonblocking send returns -EAGAIN. Its
subsequent blocking send remains asleep; after 12 seconds,
poll(POLLOUT) is still clear.

The publisher has CapEff=0 and NoNewPrivs=1. It does not need
CAP_NET_ADMIN or CAP_NET_RAW. The guest init enables the TIPC bearer
before dropping privileges. The test uses one local node; I have not
tested a remote-node trigger. This demonstrates a local availability
issue for the group service only. There is no memory corruption,
information disclosure, or privilege escalation.

tipc_nametbl_build_group() and TIPC_PUBLISHED events create
MBR_PUBLISHED entries for matching service publications.
tipc_group_is_receiver() currently accepts this state. Its
zero-initialized window is added to small_win, so
tipc_group_bc_cong() blocks a sender indefinitely when the ordinary
socket does not complete the group JOIN handshake. A real group
member is present in the reproducer, so this is not an empty-group
send.

This is distinct from the group broadcast hang caused by deleting
the last unacked member: this trigger needs no ACK loss or member
departure and occurs before the first broadcast.

HEAD: commit c03c1a14507dfaf8b856c2608f9ac216021fc222 ("Linux 7.3")
git tree: upstream Linux, local branch master
source version: Linux 7.3.0 (v7.3)
compiler: gcc 11.4.0, GNU ld 2.38
QEMU: 6.2.0

Raw reproduction files:

console-output.txt
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/console-output.txt
patched-console-output.txt
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/patched-console-output.txt
kernel-config.txt
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/kernel-config.txt
poc.c
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/poc.c
init
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/init
build-initramfs.sh
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/build-initramfs.sh
run-qemu.sh
https://gist.githubusercontent.com/shaos26/243e46a190cef51fbe6a1e8cf1bcdede/raw/1cb4d487be1eace60d6d68ab384f85057b296b41/run-qemu.sh

With these files saved in a Linux tree at the HEAD above:

cp kernel-config.txt .config
make olddefconfig
make -j"$(nproc)" bzImage
./build-initramfs.sh
./run-qemu.sh arch/x86/boot/bzImage phantom-group

The unmodified kernel reproduced:

POLL_BEFORE_FIRST_SEND=4
NONBLOCK_FIRST_SEND ret=-1 errno=11
POC_RESULT POSITIVE_SEND_BLOCKED observe_ms=12000 pollout=0

The patched kernel was built from the same HEAD and config. The same
test reported:

POLL_BEFORE_FIRST_SEND=4
NONBLOCK_FIRST_SEND ret=1 errno=0
POC_RESULT CONTROL_SEND_RETURNED ret=1 errno=0

The patched run also preserves POLLOUT for a legitimate group member.
The patch passed checkpatch.pl --no-tree --strict and git apply --check;
the patched kernel and static PoC both built successfully.

Exclude MBR_PUBLISHED entries from receiver accounting. When the JOIN
handshake changes a member to MBR_JOINED, open the group even if that
member was not on the small-window list.

Fixes: b7d42635517f ("tipc: introduce flow control for group broadcast messages")
Signed-off-by: shaos <shawnq1@xxxxxxx>
---
diff --git a/net/tipc/group.c b/net/tipc/group.c
index 74f6d3dac0784..61fc57d96f365 100644
--- a/net/tipc/group.c
+++ b/net/tipc/group.c
@@ -105,9 +105,12 @@ static void tipc_group_proto_xmit(struct tipc_group *grp, struct tipc_member *m,
static void tipc_group_open(struct tipc_member *m, bool *wakeup)
{
*wakeup = false;
- if (list_empty(&m->small_win))
- return;
- list_del_init(&m->small_win);
+ if (list_empty(&m->small_win)) {
+ if (m->state != MBR_JOINED || *m->group->open)
+ return;
+ } else {
+ list_del_init(&m->small_win);
+ }
*m->group->open = true;
*wakeup = true;
}
@@ -145,7 +148,8 @@ u16 tipc_group_bc_snd_nxt(struct tipc_group *grp)

static bool tipc_group_is_receiver(struct tipc_member *m)
{
- return m && m->state != MBR_JOINING && m->state != MBR_LEAVING;
+ return m && m->state != MBR_JOINING && m->state != MBR_PUBLISHED &&
+ m->state != MBR_LEAVING;
}

static bool tipc_group_is_sender(struct tipc_member *m)