[LTP] [PATCH] syscalls/shmctl05: new test for IPC file use-after-free bug
Eric Biggers
ebiggers3@gmail.com
Thu Jun 28 08:46:12 CEST 2018
On Wed, Jun 27, 2018 at 03:59:03PM +0200, Cyril Hrubis wrote:
> Hi!
> Looks like if we drop the callibration from the test and use the fuzzy
> sync library only as a spinlocks to synchronize the IPC_RMID against the
> remap_file_pages the bug gets triggered very reliably for me.
>
> --
> Cyril Hrubis
> chrubis@suse.cz
>
> /*
> * Copyright (c) 2018 Google, Inc.
> *
> * 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/>.
> */
>
> /*
> * Regression test for commit 3f05317d9889 ("ipc/shm: fix use-after-free of shm
> * file via remap_file_pages()"). This bug allowed the remap_file_pages()
> * syscall to use the file of a System V shared memory segment after its ID had
> * been reallocated and the file freed. This test reproduces the bug as a NULL
> * pointer dereference in touch_atime(), although it's a race condition so it's
> * not guaranteed to work. This test is based on the reproducer provided in the
> * fix's commit message.
> */
>
> #include "tst_test.h"
> #include "tst_safe_sysv_ipc.h"
> #include "lapi/syscalls.h"
> #include "tst_fuzzy_sync.h"
> #include "tst_safe_pthread.h"
> #include "tst_timer.h"
>
> #include <stdio.h>
>
> static struct tst_fzsync_pair fzsync_pair = TST_FZSYNC_PAIR_INIT;
>
> static pthread_t thrd;
>
> /*
> * Thread 2: repeatedly remove the shm ID and reallocate it again for a
> * new shm segment.
> */
> static void *thrproc(void *unused)
> {
> int id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
>
> for (;;) {
> if (!tst_fzsync_wait_b(&fzsync_pair))
> break;
> SAFE_SHMCTL(id, IPC_RMID, NULL);
> id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
> if (!tst_fzsync_wait_b(&fzsync_pair))
> break;
> }
> return unused;
> }
>
> static void setup(void)
> {
> /* Skip test if either remap_file_pages() or SysV IPC is unavailable */
> tst_syscall(__NR_remap_file_pages, NULL, 0, 0, 0, 0);
> tst_syscall(__NR_shmctl, 0xF00F, IPC_RMID, NULL);
>
> SAFE_PTHREAD_CREATE(&thrd, NULL, thrproc, NULL);
> }
>
> static void do_test(void)
> {
> tst_timer_start(CLOCK_MONOTONIC);
>
> /*
> * Thread 1: repeatedly attach a shm segment, then remap it until the ID
> * seems to have been removed by the other process.
> */
> while (tst_timer_elapsed_ms() < 5000) {
> int id;
> void *addr;
>
> id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
> addr = shmat(id, NULL, 0);
> if (addr == (void *)-1L)
> tst_brk(TBROK | TERRNO, "Unexpected shmat() error");
> tst_fzsync_wait_a(&fzsync_pair);
> do {
> /* This is the system call that crashed */
> TEST(syscall(__NR_remap_file_pages, addr, 4096,
> 0, 0, 0));
> } while (TEST_RETURN == 0);
>
> if (TEST_ERRNO != EIDRM && TEST_ERRNO != EINVAL) {
> tst_brk(TBROK | TTERRNO,
> "Unexpected remap_file_pages() error");
> }
> tst_fzsync_wait_a(&fzsync_pair);
> }
>
> tst_res(TPASS, "didn't crash");
> }
>
> static void cleanup(void)
> {
> if (thrd) {
> tst_fzsync_pair_exit(&fzsync_pair);
> SAFE_PTHREAD_JOIN(thrd, NULL);
> }
> shmctl(0xF00F, IPC_RMID, NULL);
> }
>
> static struct tst_test test = {
> .timeout = 20,
> .setup = setup,
> .test_all = do_test,
> .cleanup = cleanup,
> };
Hi, this works well for me too -- thanks! Though, IIUC it relies on scheduling
nondeterminism to hit the race. It might help reproducing bugs like this if
tst_fzsync_wait_*() cycled through different delay deltas between the two
threads.
Also with a fixed kernel, to make the test pass rather than timing out, I had to
change
while (tst_timer_elapsed_ms() < 5000)
to
while (tst_timer_stop(), tst_timer_elapsed_ms() < 5000)
It would be nice if there was a simpler supported way to implement time-based
tests like this. E.g. the test framework could just save the start time
automatically for all tests, and then a single function call could return the
time elapsed so far.
Anyway, should I go ahead and send a formal v2 patch?
Thanks,
Eric
More information about the ltp
mailing list