/*
* 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 "lapi/syscalls.h"
#include "tst_test.h"
#include "tst_fuzzy_sync.h"
#include "tst_safe_pthread.h"
#include "tst_safe_sysv_ipc.h"
#include "tst_timer.h"
static struct tst_fzsync_pair fzsync_pair;
/*
* 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);
while (tst_fzsync_run_b(&fzsync_pair)) {
tst_fzsync_start_race_b(&fzsync_pair);
SAFE_SHMCTL(id, IPC_RMID, NULL);
id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
tst_fzsync_end_race_b(&fzsync_pair);
}
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);
tst_fzsync_pair_init(&fzsync_pair);
}
static void do_test(void)
{
/*
* Thread 1: repeatedly attach a shm segment, then remap it until the ID
* seems to have been removed by the other process.
*/
tst_fzsync_pair_reset(&fzsync_pair, thrproc);
while (tst_fzsync_run_a(&fzsync_pair)) {
int id;
void *addr;
id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
addr = SAFE_SHMAT(id, NULL, 0);
tst_fzsync_start_race_a(&fzsync_pair);
do {
/* This is the system call that crashed */
TEST(syscall(__NR_remap_file_pages, addr, 4096,
0, 0, 0));
} while (TST_RET == 0);
tst_fzsync_end_race_a(&fzsync_pair);
if (TST_ERR != EIDRM && TST_ERR != EINVAL) {
tst_brk(TBROK | TTERRNO,
"Unexpected remap_file_pages() error");
}
/*
* Ensure that a shm segment will actually be destroyed.
* This call may fail on recent kernels (v4.0+) because
* remap_file_pages() already unmapped the shm segment.
*/
shmdt(addr);
}
tst_res(TPASS, "didn't crash");
}
static void cleanup(void)
{
tst_fzsync_pair_cleanup(&fzsync_pair);
shmctl(0xF00F, IPC_RMID, NULL);
}
static struct tst_test test = {
.timeout = 20,
.setup = setup,
.test_all = do_test,
.cleanup = cleanup,
};