* Re: [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
[not found] <20260812071543.C94FD349CD8@smtp.subspace.kernel.org>
@ 2026-08-12 14:44 ` Sidraya Jayagond
0 siblings, 0 replies; 3+ messages in thread
From: Sidraya Jayagond @ 2026-08-12 14:44 UTC (permalink / raw)
To: Chuyf26, alibuda
Cc: dust.li, mjambigi, tonylu, guwen, netdev, linux-rdma, linux-s390,
linux-kernel
On 12/08/26 12:45 pm, Chuyf26 wrote:
> IPPROTO_SMC sockets wrap an internal TCP sock ("clcsock"), which is
> created by smc_inet_init_sock() via smc_create_clcsk() from the
> proto->init hook of inet_create()/inet6_create(). When socket
> creation fails after proto->init has succeeded - for example when a
> cgroup BPF program attached to BPF_CGROUP_INET_SOCK_CREATE denies the
> socket - inet_create() calls sk_common_release(), which only invokes
> sk_prot->destroy if it is set. Neither smc_inet_prot nor
> smc_inet6_prot defines .destroy, and the sock destructor smc_destruct()
> returns early unless sk_state is SMC_CLOSED (it is SMC_INIT here), so
> the internal TCP sock is never released.
>
> As a result, every failing socket(AF_INET, SOCK_STREAM, IPPROTO_SMC)
> call leaks one tcp_sock. Any unprivileged task able to attach a
> deny-all BPF_CGROUP_INET_SOCK_CREATE program to its own cgroup (or a
> task confined by an LSM policy) can grow kernel memory unboundedly.
>
> Reproduced on v7.2-rc7: with a deny-all BPF_CGROUP_INET_SOCK_CREATE
> program attached, a loop of socket(AF_INET, SOCK_STREAM, IPPROTO_SMC)
> calls fails with EPERM and kmemleak reports one unreferenced tcp_sock
> object per call.
>
> Add a .destroy hook to both IPPROTO_SMC protos that releases the
> clcsock via smc_clcsock_release(). That helper is safe here: it takes
> the clcsock_release_lock initialized by smc_sk_init() and skips a NULL
> clcsock, which is what smc_create_clcsk() leaves behind when creating
> the TCP sock fails. Also initialize clcsock to NULL at the start of
> smc_inet_init_sock(): the smc_sock slab is SLAB_TYPESAFE_BY_RCU, so
> recycled objects are not zeroed and stale memory must not be handed to
> sock_release().
>
> The same behaviour is visible without any debugging option: on a
> plain kernel the slabinfo "TCP" active_objs count grows by one per
> failing socket() call and never shrinks.
>
> With the fix, the same reproducer leaves no unreferenced objects in
> kmemleak and the "TCP" slabinfo count stays flat, and regular
> IPPROTO_SMC socket create/close cycles are unaffected
> (sk_common_release() from inet_release() also routes through the new
> .destroy, where the already-NULL clcsock is a no-op).
>
The fix looks good, but I think the commit message is longer than needed
and spends too much space on reproducer details and internal call path
narration.
I would trim the detailed reproducer/results text and most of the
internal call path explanation, and keep it focused on the leak, the
failure path, and why adding .destroy plus clcsock = NULL fixes the issue.
If you want to keep the reproducer and validation details, please move
those below `...` instead of keeping them in the main commit message body.
Thank you,
Sidraya
> Fixes: d25a92ccae6b ("net/smc: Introduce IPPROTO_SMC")
> Reported-by: Abaci <abaci@linux.alibaba.com>
> Assisted-by: abaci:qwen3.8-max
> Signed-off-by: Chuyf26 <Chuyf26@linux.alibaba.com>
> ---
> 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;
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
@ 2026-08-12 7:15 Chuyf26
2026-08-12 7:25 ` sashiko-bot
0 siblings, 1 reply; 3+ messages in thread
From: Chuyf26 @ 2026-08-12 7:15 UTC (permalink / raw)
To: alibuda
Cc: dust.li, sidraya, mjambigi, tonylu, guwen, netdev, linux-rdma,
linux-s390, linux-kernel
IPPROTO_SMC sockets wrap an internal TCP sock ("clcsock"), which is
created by smc_inet_init_sock() via smc_create_clcsk() from the
proto->init hook of inet_create()/inet6_create(). When socket
creation fails after proto->init has succeeded - for example when a
cgroup BPF program attached to BPF_CGROUP_INET_SOCK_CREATE denies the
socket - inet_create() calls sk_common_release(), which only invokes
sk_prot->destroy if it is set. Neither smc_inet_prot nor
smc_inet6_prot defines .destroy, and the sock destructor smc_destruct()
returns early unless sk_state is SMC_CLOSED (it is SMC_INIT here), so
the internal TCP sock is never released.
As a result, every failing socket(AF_INET, SOCK_STREAM, IPPROTO_SMC)
call leaks one tcp_sock. Any unprivileged task able to attach a
deny-all BPF_CGROUP_INET_SOCK_CREATE program to its own cgroup (or a
task confined by an LSM policy) can grow kernel memory unboundedly.
Reproduced on v7.2-rc7: with a deny-all BPF_CGROUP_INET_SOCK_CREATE
program attached, a loop of socket(AF_INET, SOCK_STREAM, IPPROTO_SMC)
calls fails with EPERM and kmemleak reports one unreferenced tcp_sock
object per call.
Add a .destroy hook to both IPPROTO_SMC protos that releases the
clcsock via smc_clcsock_release(). That helper is safe here: it takes
the clcsock_release_lock initialized by smc_sk_init() and skips a NULL
clcsock, which is what smc_create_clcsk() leaves behind when creating
the TCP sock fails. Also initialize clcsock to NULL at the start of
smc_inet_init_sock(): the smc_sock slab is SLAB_TYPESAFE_BY_RCU, so
recycled objects are not zeroed and stale memory must not be handed to
sock_release().
The same behaviour is visible without any debugging option: on a
plain kernel the slabinfo "TCP" active_objs count grows by one per
failing socket() call and never shrinks.
With the fix, the same reproducer leaves no unreferenced objects in
kmemleak and the "TCP" slabinfo count stays flat, and regular
IPPROTO_SMC socket create/close cycles are unaffected
(sk_common_release() from inet_release() also routes through the new
.destroy, where the already-NULL clcsock is a no-op).
Fixes: d25a92ccae6b ("net/smc: Introduce IPPROTO_SMC")
Reported-by: Abaci <abaci@linux.alibaba.com>
Assisted-by: abaci:qwen3.8-max
Signed-off-by: Chuyf26 <Chuyf26@linux.alibaba.com>
---
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
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <linux/bpf.h>
#include <stdint.h>
#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 --- */
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
2026-08-12 7:15 Chuyf26
@ 2026-08-12 7:25 ` sashiko-bot
0 siblings, 0 replies; 3+ messages in thread
From: sashiko-bot @ 2026-08-12 7:25 UTC (permalink / raw)
To: Chuyf26
Cc: linux-s390, Heiko Carstens, Vasily Gorbik, Christian Borntraeger,
Alexander Gordeev
> IPPROTO_SMC sockets wrap an internal TCP sock ("clcsock"), which is
> created by smc_inet_init_sock() via smc_create_clcsk() from the
> proto->init hook of inet_create()/inet6_create(). When socket
> creation fails after proto->init has succeeded - for example when a
> cgroup BPF program attached to BPF_CGROUP_INET_SOCK_CREATE denies the
> socket - inet_create() calls sk_common_release(), which only invokes
> sk_prot->destroy if it is set. Neither smc_inet_prot nor
> smc_inet6_prot defines .destroy, and the sock destructor smc_destruct()
> returns early unless sk_state is SMC_CLOSED (it is SMC_INIT here), so
> the internal TCP sock is never released.
>
> As a result, every failing socket(AF_INET, SOCK_STREAM, IPPROTO_SMC)
> call leaks one tcp_sock. Any unprivileged task able to attach a
> deny-all BPF_CGROUP_INET_SOCK_CREATE program to its own cgroup (or a
> task confined by an LSM policy) can grow kernel memory unboundedly.
> [ ... ]
>
> Fixes: d25a92ccae6b ("net/smc: Introduce IPPROTO_SMC")
> Reported-by: Abaci <abaci@linux.alibaba.com>
> Assisted-by: abaci:qwen3.8-max
> Signed-off-by: Chuyf26 <Chuyf26@linux.alibaba.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260812071538.o7mdtMhZ1eOzDgQnKZduQfWlq0FhL7OCAPLjt9AA11w@z?part=1
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-12 14:44 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260812071543.C94FD349CD8@smtp.subspace.kernel.org>
2026-08-12 14:44 ` [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure Sidraya Jayagond
2026-08-12 7:15 Chuyf26
2026-08-12 7:25 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox