Test for vulnerability CVE-2016-7117 in recvmmsg error return path
PATCH 1/4 Test for vulnerability CVE-2016-7117 in recvmmsg error return path
Signed-off-by Richard Palethorpe <[email protected]>
--- testcases/cve/cve-2016-7117.c | 191 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 testcases/cve/cve-2016-7117.c
testcases/cve/cve-2016-7117.c new file +191 -0
@@ -0,0 +1,191 @@ +/* +* Copyright (c) 2017 Richard Palethorpe <[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/>. +*/ +/* +* 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 */ +#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" , +};
Signature · 1 line
2.12.0