On 12/02/2021 21:02, Pavel Begunkov wrote: > Add a regression test for CVE-2020-29373 where io-wq do path resolution > issuing sendmsg, but doesn't have proper fs set up. Jens, what about licenses? The original test is GPLv2 > > 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; > +} > -- Pavel Begunkov