All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path
@ 2017-03-17  9:39 Richard Palethorpe
  2017-03-20 11:23 ` Cyril Hrubis
  0 siblings, 1 reply; 3+ messages in thread
From: Richard Palethorpe @ 2017-03-17  9:39 UTC (permalink / raw
  To: ltp

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

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path
  2017-03-17  9:39 [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path Richard Palethorpe
@ 2017-03-20 11:23 ` Cyril Hrubis
  2017-03-20 14:42   ` Richard Palethorpe
  0 siblings, 1 reply; 3+ messages in thread
From: Cyril Hrubis @ 2017-03-20 11:23 UTC (permalink / raw
  To: ltp

Hi!
> --- /dev/null
> +++ b/testcases/cve/2016-7117/cve-2016-7117.c

Hmm, I would have just put this test directly into the cve/ directory,
there is no point in having one directory per test here.

> @@ -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

We have these for all architectures in autogenerated
linux_syscall_numbers.h, just include that header instead of rolling
your own definitions.

> +#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};

There is no need to initialize global variables to 0. Also global
variables should be declared static.

> +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();

More usuall way of passing structures in C is by pointer, if you just
did exit_time(&close_exit) here you could just pass the pointer to
clock_gettime() call instead of copying the value on the stack...

> +	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();

Here as well.

> +		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",
> +};

Otherwise the code looks fine.

It's missing Makefile, runtest file and .gitignore record though...

-- 
Cyril Hrubis
chrubis@suse.cz

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path
  2017-03-20 11:23 ` Cyril Hrubis
@ 2017-03-20 14:42   ` Richard Palethorpe
  0 siblings, 0 replies; 3+ messages in thread
From: Richard Palethorpe @ 2017-03-20 14:42 UTC (permalink / raw
  To: ltp

Hello Metan,

On Mon, 20 Mar 2017 12:23:38 +0100
"Cyril Hrubis" <chrubis@suse.cz> wrote:

> Hi!
> > --- /dev/null
> > +++ b/testcases/cve/2016-7117/cve-2016-7117.c  
> 
> Hmm, I would have just put this test directly into the cve/ directory,
> there is no point in having one directory per test here.
>

Good I like having a more flat hierarchy. I think it would be nice just to
have all the tests directly relating to CVEs in one big dir, although the
Makefile will be bigger than usual... when I have done a few more tests we
shall see what it looks like.

> > +// 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  
> 
> We have these for all architectures in autogenerated
> linux_syscall_numbers.h, just include that header instead of rolling
> your own definitions.

Nice.
> > +char rbuf[sizeof(MSG)] = {0};  
> 
> There is no need to initialize global variables to 0. Also global
> variables should be declared static.

Yep, sorry.

> > +
> > +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();  
> 
> More usuall way of passing structures in C is by pointer, if you just
> did exit_time(&close_exit) here you could just pass the pointer to
> clock_gettime() call instead of copying the value on the stack...

Yep, I didn't think that through properly.

> 
> Otherwise the code looks fine.
> 
> It's missing Makefile, runtest file and .gitignore record though...
> 

Thank you,
Richard.

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2017-03-20 14:42 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-03-17  9:39 [LTP] [RFC 1/1] Test for vulnerability cve-2016-7117 in recvmmsg error return path Richard Palethorpe
2017-03-20 11:23 ` Cyril Hrubis
2017-03-20 14:42   ` Richard Palethorpe

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.