* [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
@ 2026-08-12 7:15 Chuyf26
0 siblings, 0 replies; 5+ 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] 5+ messages in thread
* 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
2026-08-13 6:04 ` [PATCH v2] " Chuyf26
[not found] ` <202608130604.67D5j4VU1508130@pps.reinject>
0 siblings, 2 replies; 5+ 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] 5+ messages in thread
* [PATCH v2] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
2026-08-12 14:44 ` [PATCH] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure Sidraya Jayagond
@ 2026-08-13 6:04 ` Chuyf26
2026-08-20 11:12 ` Paolo Abeni
[not found] ` <202608130604.67D5j4VU1508130@pps.reinject>
1 sibling, 1 reply; 5+ messages in thread
From: Chuyf26 @ 2026-08-13 6:04 UTC (permalink / raw)
To: alibuda
Cc: dust.li, sidraya, mjambigi, tonylu, guwen, netdev, linux-rdma,
linux-s390
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 <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
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 <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] 5+ messages in thread
* Re: [PATCH v2] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
[not found] ` <202608130604.67D5j4VU1508130@pps.reinject>
@ 2026-08-13 6:25 ` Sidraya Jayagond
0 siblings, 0 replies; 5+ messages in thread
From: Sidraya Jayagond @ 2026-08-13 6:25 UTC (permalink / raw)
To: Chuyf26, alibuda
Cc: dust.li, mjambigi, tonylu, guwen, netdev, linux-rdma, linux-s390
On 13/08/26 11:34 am, Chuyf26 wrote:
> 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 <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;
Thank you for fixing this.
Reviewed-by: Sidraya Jayagond <sidraya@linux.ibm.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
2026-08-13 6:04 ` [PATCH v2] " Chuyf26
@ 2026-08-20 11:12 ` Paolo Abeni
0 siblings, 0 replies; 5+ messages in thread
From: Paolo Abeni @ 2026-08-20 11:12 UTC (permalink / raw)
To: Chuyf26
Cc: alibuda, dust.li, sidraya, mjambigi, tonylu, guwen, netdev,
linux-rdma, linux-s390
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
net/smc: release the internal TCP sock on IPPROTO_SMC socket creation failure
This adds a .destroy hook to smc_inet_prot and smc_inet6_prot that calls
smc_clcsock_release(), so the internal TCP sock created by
smc_inet_init_sock() is freed when socket creation fails after proto->init
has run. It also stores NULL into smc->clcsock at the top of
smc_inet_init_sock().
A couple of questions below.
> diff --git a/net/smc/smc_inet.c b/net/smc/smc_inet.c
> index a94084b4a498..b94a19499986 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,
[ ... ]
> @@ -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;
[Severity: Low]
Is the premise of this comment (and of the matching changelog paragraph,
"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") correct?
sk_alloc() always adds __GFP_ZERO, and sk_prot_alloc() then clears the
whole protocol object:
net/core/sock.c:sk_alloc() {
sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
...
}
net/core/sock.c:sk_prot_alloc() {
sk = kmem_cache_alloc(slab, priority & ~__GFP_ZERO);
if (!sk)
return sk;
if (want_init_on_alloc(priority))
sk_prot_clear_nulls(sk, prot->obj_size);
...
}
sk_prot_clear_nulls() memsets everything except sk_node.next, and
smc_sock.clcsock sits well past sk_node, so isn't clcsock already NULL on
entry to .init even for a recycled SLAB_TYPESAFE_BY_RCU object, making this
store dead code?
The second sentence of the comment also seems hard to satisfy: the mutex
that smc_clcsock_release() takes is only initialized later, inside
smc_sk_init():
net/smc/af_smc.c:smc_sk_init() {
...
mutex_init(&smc->clcsock_release_lock);
...
}
So if .destroy really could run before .init completed, wouldn't
smc_clcsock_release() lock an uninitialized mutex regardless of this NULL
store? Could the comment and changelog paragraph be dropped or reworded to
match what the code actually guarantees?
>
> /* 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));
> +}
[Severity: High]
Can this free the internal TCP socket while the SMC sock is still on the
SMC diag hash list?
sk_common_release() runs .destroy before .unhash:
net/core/sock.c:sk_common_release() {
if (sk->sk_prot->destroy)
sk->sk_prot->destroy(sk);
...
sk->sk_prot->unhash(sk);
...
}
and the sk was already inserted into smc_v4_hashinfo/smc_v6_hashinfo by
smc_sk_init() -> sk->sk_prot->hash(sk) == smc_hash_sk() before
smc_create_clcsk() ran, so when smc_inet_destroy_sock() calls
smc_clcsock_release() the sock is still visible to the diag dumper.
The reader side walks that same hashinfo holding only the hashinfo rwlock,
and does an unlocked check-then-dereference of smc->clcsock:
net/smc/smc_diag.c:smc_diag_dump_proto() {
read_lock(&prot->h.smc_hash->lock);
head = &prot->h.smc_hash->ht;
...
sk_for_each(sk, head) {
...
}
net/smc/smc_diag.c:smc_diag_msg_common_fill() {
...
if (!smc->clcsock)
return;
r->id.idiag_sport = htons(smc->clcsock->sk->sk_num);
r->id.idiag_dport = smc->clcsock->sk->sk_dport;
...
}
while the writer holds only clcsock_release_lock:
net/smc/smc_close.c:smc_clcsock_release() {
mutex_lock(&smc->clcsock_release_lock);
if (smc->clcsock) {
tcp = smc->clcsock;
smc->clcsock = NULL;
sock_release(tcp);
}
...
}
With an empty lockset intersection, can a concurrent AF_SMC diag dump (for
example "ss --smc", which has no capability check in
smc_diag_handler_dump()) re-read smc->clcsock as NULL after the NULL check
and oops, or dereference the just-released struct socket / tcp_sock and copy
freed memory into the netlink reply?
The existing teardown path unhashes first and only then drops the clcsock:
net/smc/af_smc.c:__smc_release() {
sk->sk_prot->unhash(sk);
if (sk->sk_state == SMC_CLOSED) {
if (smc->clcsock) {
release_sock(sk);
smc_clcsock_release(smc);
...
}
Would it be better to keep that ordering here, for instance by unhashing
before releasing the clcsock in the destroy path, or by taking
clcsock_release_lock in the diag reader?
The lockless clcsock read in smc_diag is itself older than this patch (see
also smc_close_active_abort() and smc_close_passive_work()), but on the
socket-creation-failure path nothing was freed while the sk was hashed
before this change, since the clcsock was simply leaked.
>
> int __init smc_inet_init(void)
> {
> int rc;
--
This is an AI-generated review.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-20 11:12 UTC | newest]
Thread overview: 5+ 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-13 6:04 ` [PATCH v2] " Chuyf26
2026-08-20 11:12 ` Paolo Abeni
[not found] ` <202608130604.67D5j4VU1508130@pps.reinject>
2026-08-13 6:25 ` Sidraya Jayagond
2026-08-12 7:15 [PATCH] " Chuyf26
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox