/* * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com> * Original POC by Daniel Jiang * * 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/>. */ /* * Test for CVE-2017-2671 faulty locking on ping socket * * When sys_connect() is called with sockaddr.sin_family set to AF_UNSPEC on a * ping socket; __udp_disconnect() gets called, which in turn calls the buggy * function ping_unhashed(). This function does not obtain a rwlock before * checking if the socket is hashed allowing the socket data to be pulled from * underneath it in the time between calling sk_hashed() and gaining the write * lock. * * Fixed in commit 43a6684519ab0a6c52024b5e25322476cabad893 * * This test repeatedly 'connects' a ping socket correctly then calls * connect() with AF_UNSPEC in two seperate threads to trigger the race * condition. If the bug is present, then the test will most likely crash the * system. * * The test requests root privileges so that it can ensure ping sockets are * enabled. On distributions (including Android) where ping sockets are * enabled by default, root privileges are not required. */ #include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include "tst_test.h" #include "tst_safe_net.h" #include "tst_safe_pthread.h" #include "tst_fuzzy_sync.h" #define ATTEMPTS 0x80000 #define PING_SYSCTL_PATH "/proc/sys/net/ipv4/ping_group_range" static int sockfd; static unsigned int ping_min_grp, ping_max_grp; static struct tst_fzsync_pair fzsync_pair = TST_FZSYNC_PAIR_INIT; static struct sockaddr_in iaddr, uaddr; static pthread_t thrd; static void *connect_b(void *); static void setup(void) { iaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); uaddr = iaddr; iaddr.sin_family = AF_INET; uaddr.sin_family = AF_UNSPEC; fzsync_pair.delay_inc = 1; if (access(PING_SYSCTL_PATH, F_OK)) tst_brk(TCONF, "socket() does not support IPPROTO_ICMP"); SAFE_FILE_SCANF(PING_SYSCTL_PATH, "%u %u", &ping_min_grp, &ping_max_grp); SAFE_FILE_PRINTF(PING_SYSCTL_PATH, "0 0"); sockfd = SAFE_SOCKET(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); SAFE_PTHREAD_CREATE(&thrd, 0, connect_b, 0); tst_res(TINFO, "Created ping socket, attempting to race..."); } static void cleanup(void) { if (thrd) { tst_fzsync_pair_exit(&fzsync_pair); SAFE_PTHREAD_JOIN(thrd, NULL); } if (sockfd > 0) SAFE_CLOSE(sockfd); if (ping_min_grp | ping_max_grp) SAFE_FILE_PRINTF(PING_SYSCTL_PATH, "%u %u", ping_min_grp, ping_max_grp); } static void *connect_b(void * param LTP_ATTRIBUTE_UNUSED) { while(tst_fzsync_wait_update_b(&fzsync_pair)) { tst_fzsync_delay_b(&fzsync_pair); connect(sockfd, (struct sockaddr *)&uaddr, sizeof(uaddr)); tst_fzsync_time_b(&fzsync_pair); if (!tst_fzsync_wait_b(&fzsync_pair)) break; } return 0; } static void run(void) { int i; for (i = 0; i < ATTEMPTS; i++) { SAFE_CONNECT(sockfd, (struct sockaddr *)&iaddr, sizeof(iaddr)); tst_fzsync_wait_update_a(&fzsync_pair); tst_fzsync_delay_a(&fzsync_pair); connect(sockfd, (struct sockaddr *)&uaddr, sizeof(uaddr)); tst_fzsync_time_a(&fzsync_pair); tst_fzsync_wait_a(&fzsync_pair); } tst_res(TPASS, "We didn't crash"); } static struct tst_test test = { .setup = setup, .test_all = run, .cleanup = cleanup, .needs_root = 1, };