Add a regression test for CVE-2020-29373 where io-wq do path resolution issuing sendmsg, but doesn't have proper fs set up. Reported-by: Petr Vorel <pvorel@xxxxxxx> Signed-off-by: Pavel Begunkov <asml.silence@xxxxxxxxx> --- test/Makefile | 2 + test/sendmsg_fs_cve.c | 193 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 test/sendmsg_fs_cve.c diff --git a/test/Makefile b/test/Makefile index 157ff95..7751eff 100644 --- a/test/Makefile +++ b/test/Makefile @@ -111,6 +111,7 @@ test_targets += \ timeout-overflow \ unlink \ wakeup-hang \ + sendmsg_fs_cve \ # EOL all_targets += $(test_targets) @@ -238,6 +239,7 @@ test_srcs := \ timeout.c \ unlink.c \ wakeup-hang.c \ + sendmsg_fs_cve.c \ # EOL test_objs := $(patsubst %.c,%.ol,$(patsubst %.cc,%.ol,$(test_srcs))) diff --git a/test/sendmsg_fs_cve.c b/test/sendmsg_fs_cve.c new file mode 100644 index 0000000..85f271b --- /dev/null +++ b/test/sendmsg_fs_cve.c @@ -0,0 +1,193 @@ +/* + * repro-CVE-2020-29373 -- Reproducer for CVE-2020-29373. + * + * Copyright (c) 2021 SUSE + * Author: Nicolai Stange <nstange@xxxxxxx> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <unistd.h> +#include <syscall.h> +#include <stdio.h> +#include <sys/mman.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <fcntl.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include "liburing.h" + +/* + * This attempts to make the kernel issue a sendmsg() to + * path from io_uring's async io_sq_wq_submit_work(). + * + * Unfortunately, IOSQE_ASYNC is available only from kernel version + * 5.6 onwards. To still force io_uring to process the request + * asynchronously from io_sq_wq_submit_work(), queue a couple of + * auxiliary requests all failing with EAGAIN before. This is + * implemented by writing repeatedly to an auxiliary O_NONBLOCK + * AF_UNIX socketpair with a small SO_SNDBUF. + */ +static int try_sendmsg_async(const char * const path) +{ + int snd_sock, r; + struct io_uring ring; + char sbuf[16] = {}; + struct iovec siov = { .iov_base = &sbuf, .iov_len = sizeof(sbuf) }; + struct sockaddr_un addr = {}; + struct msghdr msg = { + .msg_name = &addr, + .msg_namelen = sizeof(addr), + .msg_iov = &siov, + .msg_iovlen = 1, + }; + struct io_uring_cqe *cqe; + struct io_uring_sqe *sqe; + + snd_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (snd_sock < 0) { + perror("socket(AF_UNIX)"); + return -1; + } + + addr.sun_family = AF_UNIX; + strcpy(addr.sun_path, path); + + r = io_uring_queue_init(512, &ring, 0); + if (r < 0) { + fprintf(stderr, "ring setup failed: %d\n", r); + goto close_iour; + } + + sqe = io_uring_get_sqe(&ring); + if (!sqe) { + fprintf(stderr, "get sqe failed\n"); + r = -EFAULT; + goto close_iour; + } + + /* the actual one supposed to fail with -ENOENT. */ + io_uring_prep_sendmsg(sqe, snd_sock, &msg, 0); + sqe->flags = IOSQE_ASYNC; + sqe->user_data = 255; + + r = io_uring_submit(&ring); + if (r != 1) { + fprintf(stderr, "sqe submit failed: %d\n", r); + r = -EFAULT; + goto close_iour; + } + + r = io_uring_wait_cqe(&ring, &cqe); + if (r < 0) { + fprintf(stderr, "wait completion %d\n", r); + r = -EFAULT; + goto close_iour; + } + if (cqe->user_data != 255) { + fprintf(stderr, "user data %d\n", r); + r = -EFAULT; + goto close_iour; + } + if (cqe->res != -ENOENT) { + r = 3; + fprintf(stderr, + "error: cqe %i: res=%i, but expected -ENOENT\n", + (int)cqe->user_data, (int)cqe->res); + } + io_uring_cqe_seen(&ring, cqe); + +close_iour: + io_uring_queue_exit(&ring); + close(snd_sock); + return r; +} + +int main(int argc, char *argv[]) +{ + int r; + char tmpdir[] = "/tmp/tmp.XXXXXX"; + int rcv_sock; + struct sockaddr_un addr = {}; + pid_t c; + int wstatus; + + if (!mkdtemp(tmpdir)) { + perror("mkdtemp()"); + return 1; + } + + rcv_sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (rcv_sock < 0) { + perror("socket(AF_UNIX)"); + r = 1; + goto rmtmpdir; + } + + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/sock", tmpdir); + + r = bind(rcv_sock, (struct sockaddr *)&addr, + sizeof(addr)); + if (r < 0) { + perror("bind()"); + close(rcv_sock); + r = 1; + goto rmtmpdir; + } + + c = fork(); + if (!c) { + close(rcv_sock); + + if (chroot(tmpdir)) { + perror("chroot()"); + return 1; + } + + r = try_sendmsg_async(addr.sun_path); + if (r < 0) { + /* system call failure */ + r = 1; + } else if (r) { + /* test case failure */ + r += 1; + } + return r; + } + + if (waitpid(c, &wstatus, 0) == (pid_t)-1) { + perror("waitpid()"); + r = 1; + goto rmsock; + } + if (!WIFEXITED(wstatus)) { + fprintf(stderr, "child got terminated\n"); + r = 1; + goto rmsock; + } + r = WEXITSTATUS(wstatus); + if (r) + fprintf(stderr, "error: Test failed\n"); +rmsock: + close(rcv_sock); + unlink(addr.sun_path); +rmtmpdir: + rmdir(tmpdir); + return r; +} -- 2.24.0