| /* |
| * repro-CVE-2020-29373 -- Reproducer for CVE-2020-29373. |
| * |
| * Copyright (c) 2021 SUSE |
| * Author: Nicolai Stange <[email protected]> |
| * |
| * 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 <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; |
| } |