[LTP] [PATCH v4] io_uring/pintheft: Add CVE-2026-43494 regression test
Petr Vorel
pvorel@suse.cz
Sun May 24 20:16:39 CEST 2026
Hi Sebastian,
> Test for PinTheft (CVE-2026-43494), fixed by:
> e17492979319 ("net/rds: reset op_nents when zerocopy page pin fails")
Thanks you!
> The bug is in the RDS zerocopy send error path: when pinning user pages
> for zerocopy send fails partway through, the error cleanup drops a page
> reference that the RDS message cleanup will drop again. Combined with
> io_uring fixed buffer registrations, this double-drop drains the
> FOLL_PIN counter and causes a page-cache overwrite exploitable for local
> privilege escalation (PinTheft).
...
> +static void setup(void)
> +{
> + struct io_uring_params params = {};
> + struct iovec fixed_iov;
> + int val;
> +
> + page_size = SAFE_SYSCONF(_SC_PAGESIZE);
> + io_uring_setup_supported_by_kernel();
> +
> + /*
> + * The exploit primitive keeps one fixed-buffer registration alive and
> + * clones it to another ring.
> + */
> + ring_fd1 = io_uring_setup(1, ¶ms);
> + if (ring_fd1 < 0)
> + tst_brk(TBROK | TERRNO, "io_uring_setup() failed for first ring");
> +
> + memset(¶ms, 0, sizeof(params));
> +
> + ring_fd2 = io_uring_setup(1, ¶ms);
> + if (ring_fd2 < 0)
> + tst_brk(TBROK | TERRNO, "io_uring_setup() failed for second ring");
> +
> + rds_fd = socket(AF_RDS, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
> + if (rds_fd < 0) {
> + if (errno == EAFNOSUPPORT || errno == ESOCKTNOSUPPORT ||
> + errno == EPROTONOSUPPORT || errno == ENOPROTOOPT)
> + tst_brk(TCONF | TERRNO, "RDS is not available");
> +
> + tst_brk(TBROK | TERRNO, "socket(AF_RDS) failed");
Just a quick Sunday evening comment (not yet looking into the reproducer itself).
I wonder if we need this complicated check when we already have kconfig based
checks at the end. Could we just simply use SAFE_SOCKET() here? Or have you
encountered problems with older kernels?
And I haven't found any sysctl check (it's just a kernel module), which would be
then part of lib/tst_kconfig.c.
> + }
> +
> + /* PinTheft uses the RDS TCP transport, so base RDS is not enough. */
> + val = RDS_TRANS_TCP;
> + TEST(setsockopt(rds_fd, SOL_RDS, SO_RDS_TRANSPORT, &val, sizeof(val)));
> +
> + if (TST_RET) {
> + if (TST_ERR == ENOPROTOOPT || TST_ERR == EINVAL)
> + tst_brk(TCONF | TERRNO, "RDS TCP transport is not available");
> +
> + tst_brk(TBROK | TERRNO, "setsockopt(SO_RDS_TRANSPORT) failed");
And the same here just SAFE_SETSOCKOPT() ?
> + }
...
> + /*
> + * Register only the first page as an io_uring fixed buffer. This creates
> + * the long-term page pin whose reference accounting the RDS bug damages.
> + */
> + if (io_uring_register(ring_fd1, IORING_REGISTER_BUFFERS, &fixed_iov, 1))
> + tst_brk(TBROK | TERRNO, "IORING_REGISTER_BUFFERS failed");
> +
> + buffer_registered = 1;
> +
> + /*
> + * Clone the fixed buffer registration into the second ring, matching the
> + * public reproducer's lifetime pattern without performing the later
> + * page-cache overwrite stage.
> + */
> + if (clone_buffers(ring_fd2, ring_fd1)) {
> + if (errno == EINVAL || errno == EOPNOTSUPP)
> + tst_brk(TCONF | TERRNO, "IORING_REGISTER_CLONE_BUFFERS is not supported");
Also here do we need it? IMHO CONFIG_IO_URING should be enough.
And if errno is really needed, it'd IMHO be better to be in handled in
clone_buffers(), not separately.
> +
> + tst_brk(TBROK | TERRNO, "IORING_REGISTER_CLONE_BUFFERS failed");
> + }
...
> + /* Mirror the public PoC trigger: RDS zerocopy over TCP. */
> + val = 1;
> + if (setsockopt(rds_fd, SOL_SOCKET, SO_ZEROCOPY, &val, sizeof(val))) {
> + if (errno == ENOPROTOOPT || errno == EINVAL)
> + tst_brk(TCONF | TERRNO, "SO_ZEROCOPY not supported on RDS sockets");
And here I'd also simplify with SAFE_SETSOCKOPT().
> + tst_brk(TBROK | TERRNO, "setsockopt(SO_ZEROCOPY) failed");
> + }
...
> +static struct tst_test test = {
> + .test_all = run,
> + .setup = setup,
> + .cleanup = cleanup,
> + .timeout = 60,
> + .forks_child = 1,
> + .taint_check = TST_TAINT_W | TST_TAINT_D,
> + .needs_kconfigs = (const char *[]) {
> + "CONFIG_RDS",
> + "CONFIG_RDS_TCP",
CONFIG_RDS_TCP implies CONFIG_RDS.
Kind regards,
Petr
> + "CONFIG_IO_URING",
> + NULL
> + },
> + .save_restore = (const struct tst_path_val[]) {
> + {"/proc/sys/kernel/io_uring_disabled", "0",
> + TST_SR_SKIP_MISSING | TST_SR_TCONF_RO},
> + {}
> + },
> + .tags = (const struct tst_tag[]) {
> + {"linux-git", "e17492979319"},
> + {"CVE", "2026-43494"},
> + {}
> + }
> +};
More information about the ltp
mailing list