IPPROTO_SMC sockets create an internal TCP sock ("clcsock") from the proto->init hook. When socket creation fails after proto->init has run - e.g. a cgroup BPF program attached to BPF_CGROUP_INET_SOCK_CREATE denies the socket - sk_common_release() only invokes sk_prot->destroy if it is set, but neither smc_inet_prot nor smc_inet6_prot defines it, and smc_destruct() returns early unless sk_state is SMC_CLOSED. As a result, every failing socket(AF_INET, SOCK_STREAM, IPPROTO_SMC) call leaks one tcp_sock, so an unprivileged task able to attach a deny-all BPF_CGROUP_INET_SOCK_CREATE program to its own cgroup can grow kernel memory unboundedly. Add a .destroy hook to both protos that releases the clcsock via smc_clcsock_release(), which is safe here because it skips a NULL clcsock under clcsock_release_lock. Also initialize clcsock to NULL when setting the sock up: the smc_sock slab is SLAB_TYPESAFE_BY_RCU, so recycled objects are not zeroed. Fixes: d25a92ccae6b ("net/smc: Introduce IPPROTO_SMC") Reported-by: Abaci Assisted-by: abaci:qwen3.8-max Signed-off-by: Chuyf26 --- net/smc/smc_inet.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/net/smc/smc_inet.c b/net/smc/smc_inet.c index a94084b..b94a194 100644 --- a/net/smc/smc_inet.c +++ b/net/smc/smc_inet.c @@ -15,13 +15,16 @@ #include "smc_inet.h" #include "smc.h" +#include "smc_close.h" static int smc_inet_init_sock(struct sock *sk); +static void smc_inet_destroy_sock(struct sock *sk); static struct proto smc_inet_prot = { .name = "INET_SMC", .owner = THIS_MODULE, .init = smc_inet_init_sock, + .destroy = smc_inet_destroy_sock, .hash = smc_hash_sk, .unhash = smc_unhash_sk, .release_cb = smc_release_cb, @@ -68,6 +71,7 @@ static struct proto smc_inet6_prot = { .name = "INET6_SMC", .owner = THIS_MODULE, .init = smc_inet_init_sock, + .destroy = smc_inet_destroy_sock, .hash = smc_hash_sk, .unhash = smc_unhash_sk, .release_cb = smc_release_cb, @@ -109,6 +113,14 @@ static struct inet_protosw smc_inet6_protosw = { static int smc_inet_init_sock(struct sock *sk) { struct net *net = sock_net(sk); + struct smc_sock *smc = smc_sk(sk); + + /* + * The smc_sock slab is SLAB_TYPESAFE_BY_RCU and recycled objects + * are not zeroed. .destroy may run even if .init never completed, + * so make sure smc_clcsock_release() sees a valid clcsock. + */ + smc->clcsock = NULL; /* init common smc sock */ smc_sk_init(net, sk, IPPROTO_SMC); @@ -116,6 +128,17 @@ static int smc_inet_init_sock(struct sock *sk) return smc_create_clcsk(net, sk, sk->sk_family); } +static void smc_inet_destroy_sock(struct sock *sk) +{ + /* + * If inet_create()/inet6_create() fail after .init has created the + * internal TCP sock (e.g. rejected by a cgroup BPF program), + * sk_common_release() ends up here. Release the TCP sock, otherwise + * it leaks on every failed IPPROTO_SMC socket() call. + */ + smc_clcsock_release(smc_sk(sk)); +} + int __init smc_inet_init(void) { int rc; -- 2.43.5 Thanks for the review. Changes since v1: - Trimmed the commit message as suggested; the code is unchanged. - Kept the reproducer and measurements below this separator, in case anyone wants to give the leak a spin or double-check the fix. Reproducer (no clang/bpftool needed, hand-assembled BPF via bpf(2)); run as root on a cgroup-v2 system, needs CAP_BPF and CONFIG_CGROUP_BPF: $ gcc -O2 -o smc_leak smc_leak.c $ ./smc_leak 20000 It attaches a deny-all BPF_CGROUP_INET_SOCK_CREATE program to a fresh cgroup, enters it and loops socket(AF_INET, SOCK_STREAM, IPPROTO_SMC). Before this patch every call fails with EPERM and the slabinfo "TCP" active_objs count grows by ~1 per call (20004 for 20000 iterations, never shrinking); with this patch the count stays flat. /* --- smc_leak.c --- */ /* * IPPROTO_SMC socket-creation leak reproducer (no clang/bpftool needed). * * 1. creates a cgroupv2 subgroup, loads a hand-assembled deny-all * BPF_CGROUP_INET_SOCK_CREATE prog via bpf(2), attaches it; * 2. moves itself into that cgroup; * 3. loops socket(AF_INET, SOCK_STREAM, IPPROTO_SMC); * 4. prints slabinfo "TCP" delta and kmemleak report. * * Run as root on a cgroup-v2 system: * ./smc_leak 20000 * Expected on buggy kernel: every socket() fails with EPERM and leaks one * TCP sock per call (slabinfo "TCP" active_objs grows by ~1 per iteration; * with CONFIG_DEBUG_KMEMLEAK, unreferenced tcp_sock objects are reported). * On a fixed kernel: no growth. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #ifndef IPPROTO_SMC #define IPPROTO_SMC 256 #endif #define BPF_RAW(CODE, DST, SRC, OFF, IMM) ((struct bpf_insn){CODE, DST, SRC, OFF, IMM}) static int sys_bpf(int cmd, union bpf_attr *attr, unsigned int size) { return syscall(__NR_bpf, cmd, attr, size); } static int load_deny_prog(void) { /* r0 = 0 (deny); exit */ struct bpf_insn insns[] = { BPF_RAW(BPF_ALU64 | BPF_MOV | BPF_K, BPF_REG_0, 0, 0, 0), BPF_RAW(BPF_JMP | BPF_EXIT, 0, 0, 0, 0), }; union bpf_attr attr; memset(&attr, 0, sizeof(attr)); attr.prog_type = BPF_PROG_TYPE_CGROUP_SOCK; attr.insns = (uint64_t)(unsigned long)insns; attr.insn_cnt = 2; attr.license = (uint64_t)(unsigned long)"GPL"; int fd = sys_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); if (fd < 0) perror("BPF_PROG_LOAD"); return fd; } static int attach_prog(int prog_fd, const char *cgpath) { int cg_fd = open(cgpath, O_RDONLY | O_DIRECTORY); if (cg_fd < 0) { perror("open cgroup"); return -1; } union bpf_attr attr; memset(&attr, 0, sizeof(attr)); attr.link_create.prog_fd = prog_fd; attr.link_create.target_fd = cg_fd; attr.link_create.attach_type = BPF_CGROUP_INET_SOCK_CREATE; int fd = sys_bpf(BPF_LINK_CREATE, &attr, sizeof(attr)); if (fd < 0) { perror("BPF_LINK_CREATE"); close(cg_fd); return -1; } return fd; } static long tcp_active_objs(void) { FILE *f = fopen("/proc/slabinfo", "r"); char line[256]; long objs = -1; if (!f) return -1; while (fgets(line, sizeof(line), f)) { char name[64]; long active; if (sscanf(line, "%63s %ld", name, &active) == 2 && !strcmp(name, "TCP")) { objs = active; break; } } fclose(f); return objs; } static void kmemleak_scan(void) { mkdir("/sys/kernel/debug", 0755); mount("debugfs", "/sys/kernel/debug", "debugfs", 0, NULL); FILE *f = fopen("/sys/kernel/debug/kmemleak", "w"); if (f) { fputs("scan", f); fclose(f); sleep(3); system("grep -c 'unreferenced object' /sys/kernel/debug/kmemleak 2>/dev/null | sed 's/^/kmemleak unreferenced objects: /'"); system("head -60 /sys/kernel/debug/kmemleak 2>/dev/null"); } else { printf("(kmemleak unavailable: %s)\n", strerror(errno)); } } int main(int argc, char **argv) { const char *name = argc > 1 ? argv[1] : "smctest"; long iters = argc > 2 ? atol(argv[2]) : 20000; char cgpath[256], path[280]; int prog_fd, link_fd; if (access("/sys/fs/cgroup/cgroup.controllers", F_OK)) { fprintf(stderr, "cgroup v2 required\n"); return 1; } snprintf(cgpath, sizeof(cgpath), "/sys/fs/cgroup/%s", name); mkdir(cgpath, 0755); prog_fd = load_deny_prog(); if (prog_fd < 0) return 1; link_fd = attach_prog(prog_fd, cgpath); if (link_fd == -1) return 1; snprintf(path, sizeof(path), "%s/cgroup.procs", cgpath); FILE *f = fopen(path, "w"); if (!f) { perror("open cgroup.procs"); return 1; } fprintf(f, "%d", getpid()); fclose(f); long before = tcp_active_objs(); printf("TCP slab objs before: %ld\n", before); long denied = 0, ok = 0, other = 0; for (long i = 0; i < iters; i++) { int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SMC); if (fd >= 0) { close(fd); ok++; } else if (errno == EPERM) { denied++; } else { if (!other) printf("socket error: %s\n", strerror(errno)); other++; break; } } long after = tcp_active_objs(); printf("iterations=%ld denied(EPERM)=%ld ok=%ld other=%ld\n", iters, denied, ok, other); printf("TCP slab objs after: %ld (delta %+ld)\n", after, after - before); if (denied && after - before > denied / 2) printf("=> LEAK CONFIRMED: ~%ld TCP socks leaked on error path\n", after - before); kmemleak_scan(); /* leave cgroup so it can be removed */ f = fopen("/sys/fs/cgroup/cgroup.procs", "w"); if (f) { fprintf(f, "%d", getpid()); fclose(f); } if (link_fd >= 0) close(link_fd); rmdir(cgpath); return 0; } /* --- end smc_leak.c --- */