syscalls/shmctl05: new test for IPC file use-after-free bug

Message ID 20180513012325.877-1-ebiggers3@gmail.com
State New
Headers show
Series
  • syscalls/shmctl05: new test for IPC file use-after-free bug
Related show

Commit Message

Eric Biggers May 13, 2018, 1:23 a.m.
From: Eric Biggers <ebiggers@google.com>

Test for a bug in the System V IPC subsystem that resulted in a shared
memory file being used after it was freed (or being freed).

Signed-off-by: Eric Biggers <ebiggers@google.com>
---
 runtest/syscalls                              |   1 +
 runtest/syscalls-ipc                          |   1 +
 .../kernel/syscalls/ipc/shmctl/.gitignore     |   1 +
 .../kernel/syscalls/ipc/shmctl/shmctl05.c     | 113 ++++++++++++++++++
 4 files changed, 116 insertions(+)
 create mode 100644 testcases/kernel/syscalls/ipc/shmctl/shmctl05.c

Comments

Cyril Hrubis May 18, 2018, 1:25 p.m. | #1
Hi!
> +++ b/testcases/kernel/syscalls/ipc/shmctl/shmctl05.c
> @@ -0,0 +1,113 @@
> +/*
> + * 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.
> + */

Have you considered using the fuzzy sync library here?

https://github.com/linux-test-project/ltp/blob/master/include/tst_fuzzy_sync.h
Cyril Hrubis June 26, 2018, 11:35 a.m. | #2
Hi!
> Have you considered using the fuzzy sync library here?
> 
> https://github.com/linux-test-project/ltp/blob/master/include/tst_fuzzy_sync.h

I've tried to rewrite the test so that it uses the fuzzy sync library to
synchronize the remap_file_page syscall againts the IPC_RMID but for
some reason that does not seem to trigger the issues for me, while the
original reproducer triggers it just fine. There must be some subtle
difference, maybe we need to train branch predictor with the loop that
calls the remap_file_pages, maybe it's something else.

So I guess that the best solution would be merging the testcase as it
is, however for me the test timeouts on broken kernel as the test
process just hangs there which produces misleading test error message.
Maybe we just need to add .timeout_is_failure flag to the test structure
for these kind of testcases so that the test library will hint the
tester that this timeout likely means that the kernel bug has been
reproduced.
Eric Biggers June 27, 2018, 6:18 a.m. | #3
On Tue, Jun 26, 2018 at 01:35:47PM +0200, Cyril Hrubis wrote:
> Hi!
> > Have you considered using the fuzzy sync library here?
> > 
> > https://github.com/linux-test-project/ltp/blob/master/include/tst_fuzzy_sync.h
> 
> I've tried to rewrite the test so that it uses the fuzzy sync library to
> synchronize the remap_file_page syscall againts the IPC_RMID but for
> some reason that does not seem to trigger the issues for me, while the
> original reproducer triggers it just fine. There must be some subtle
> difference, maybe we need to train branch predictor with the loop that
> calls the remap_file_pages, maybe it's something else.
> 
> So I guess that the best solution would be merging the testcase as it
> is, however for me the test timeouts on broken kernel as the test
> process just hangs there which produces misleading test error message.
> Maybe we just need to add .timeout_is_failure flag to the test structure
> for these kind of testcases so that the test library will hint the
> tester that this timeout likely means that the kernel bug has been
> reproduced.
> 

Hi Cyril, sorry for not responding sooner.  A few weeks ago I did try using
fuzzy sync but I found it confusing to use, and it seemed to not provide exactly
what is needed.  Namely, it seems to only support synchronizing the threads at a
fixed point, but actually it's usually unknown exactly how things need to be
timed to reproduce race conditions -- which is why sleeps of random amounts,
though naive, can work well.

Nevertheless, I did actually get the fuzzy sync version to reproduce the bug for
me a bit more often than the random usleep version, using the following code.
I'm not yet convinced it's really better as I think it may be more unreliable
for some people, but you can try it and see how well it works for you.

I'm not sure why the original test times out for you.  Is it hung in the kernel,
or is it looping in userspace?

Here's the alternate shmctl05.c to try/consider:

/*
 * 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"

#define ATTEMPTS 0x100000

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 *p LTP_ATTRIBUTE_UNUSED)
{
	int id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);

	while (tst_fzsync_wait_update_b(&fzsync_pair)) {

		tst_fzsync_wait_b(&fzsync_pair);
		tst_fzsync_delay_b(&fzsync_pair);
		tst_fzsync_time_b(&fzsync_pair);

		SAFE_SHMCTL(id, IPC_RMID, NULL);
		id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
		tst_fzsync_wait_b(&fzsync_pair);
	}
	return NULL;
}

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)
{
	int i;

	/*
	 * Thread 1: repeatedly attach a shm segment, then remap it until the ID
	 * seems to have been removed by the other process.
	 */
	for (i = 0; i < ATTEMPTS; i++) {
		int id;
		void *addr;

		tst_fzsync_wait_update_a(&fzsync_pair);
		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);
		tst_fzsync_delay_a(&fzsync_pair);
		tst_fzsync_time_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 = {
	.setup = setup,
	.test_all = do_test,
	.cleanup = cleanup,
};
Cyril Hrubis June 27, 2018, 10:51 a.m. | #4
Hi!
> Hi Cyril, sorry for not responding sooner.  A few weeks ago I did try using
> fuzzy sync but I found it confusing to use, and it seemed to not provide exactly
> what is needed.  Namely, it seems to only support synchronizing the threads at a
> fixed point, but actually it's usually unknown exactly how things need to be
> timed to reproduce race conditions -- which is why sleeps of random amounts,
> though naive, can work well.

The idea behind it is that we try to synchronize two racing syscalls on
entering the kernel and we do expect the system to introduce random
delays that cause the race to be hit, which works good enough most of
the time. We are also planing to introduce random delays into the fuzzy
sync library to see if that would help to reproduce races.

As for the confusing interface, that is one of the pain points. I've
opened an issue https://github.com/linux-test-project/ltp/issues/338 and
I do hope to simplify the usage.

> Nevertheless, I did actually get the fuzzy sync version to reproduce the bug for
> me a bit more often than the random usleep version, using the following code.
> I'm not yet convinced it's really better as I think it may be more unreliable
> for some people, but you can try it and see how well it works for you.

Thanks, I will look at it.

> I'm not sure why the original test times out for you.  Is it hung in the kernel,
> or is it looping in userspace?

Looks like the child process gets stuck in call_rwsem_down_read_failed
forever, the parent then gets stuck in wait() until it's killed by the
test library.

We do check if the main test pid is killable and issue a message "kernel
bug found" message if the main test pid cannot be killed. I guess that I
can change the test library to watch for all processes in the process
group as well since as it is the test just produces "timeouted" and
leaves one process behind which is a bit confusing output.

Also we should probably set the test timeout to be twice of the expected
runtime to make it fail faster :-).

> Here's the alternate shmctl05.c to try/consider:
> 
> /*
>  * 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"
> 
> #define ATTEMPTS 0x100000
> 
> 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 *p LTP_ATTRIBUTE_UNUSED)
> {
> 	int id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
> 
> 	while (tst_fzsync_wait_update_b(&fzsync_pair)) {
> 
> 		tst_fzsync_wait_b(&fzsync_pair);
> 		tst_fzsync_delay_b(&fzsync_pair);
> 		tst_fzsync_time_b(&fzsync_pair);
> 
> 		SAFE_SHMCTL(id, IPC_RMID, NULL);
> 		id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
> 		tst_fzsync_wait_b(&fzsync_pair);
> 	}
> 	return NULL;
> }
> 
> 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)
> {
> 	int i;
> 
> 	/*
> 	 * Thread 1: repeatedly attach a shm segment, then remap it until the ID
> 	 * seems to have been removed by the other process.
> 	 */
> 	for (i = 0; i < ATTEMPTS; i++) {
> 		int id;
> 		void *addr;
> 
> 		tst_fzsync_wait_update_a(&fzsync_pair);
> 		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);
> 		tst_fzsync_delay_a(&fzsync_pair);
> 		tst_fzsync_time_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 = {
> 	.setup = setup,
> 	.test_all = do_test,
> 	.cleanup = cleanup,
> };
Cyril Hrubis June 27, 2018, 1:59 p.m. | #5
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.
Eric Biggers June 28, 2018, 6:46 a.m. | #6
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
Cyril Hrubis June 28, 2018, 9 a.m. | #7
Hi!
> 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.

We were talking about something like this with Richard recently, but in
the end we agreed to try to apply the library to a few more testcases to
get a better picture of the real world requirements and obstackles.

I guess that synchronizing with spinlocks like this test does then
introducing slowly graduating delay before we call one of the syscalls
up to some reasonable upper bound a few times sounds reasonable. We
would still have to measure how much time do the syscalls spend in the
kernel so that we have a reasonable estimate for the upper bound, but
that should be doable.

> 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.

Ah, right, let me fix that first.

The original purpose for the timer library is to measure time spend
doing some operation so the action to sample the end time and to convert
it to interval was separated intentionally.

I will add tst_timer_expired_ms() that could be used as:

tst_timer_start(CLOCK_MONOTONIC);

while (!tst_timer_expired_ms(5000)) {
	...
}

> Anyway, should I go ahead and send a formal v2 patch?

Just let me push the change to the timer library first :-).

Patch

diff --git a/runtest/syscalls b/runtest/syscalls
index 97bda7e45..9d47a9336 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1188,6 +1188,7 @@  shmctl01 shmctl01
 shmctl02 shmctl02
 shmctl03 shmctl03
 shmctl04 shmctl04
+shmctl05 shmctl05
 
 shmdt01 shmdt01
 shmdt02 shmdt02
diff --git a/runtest/syscalls-ipc b/runtest/syscalls-ipc
index de32c6ba9..7c2c40beb 100644
--- a/runtest/syscalls-ipc
+++ b/runtest/syscalls-ipc
@@ -57,6 +57,7 @@  shmctl01 shmctl01
 shmctl02 shmctl02
 shmctl03 shmctl03
 shmctl04 shmctl04
+shmctl05 shmctl05
 
 shmdt01 shmdt01
 shmdt02 shmdt02
diff --git a/testcases/kernel/syscalls/ipc/shmctl/.gitignore b/testcases/kernel/syscalls/ipc/shmctl/.gitignore
index 9f5ac37ff..d6777e3b8 100644
--- a/testcases/kernel/syscalls/ipc/shmctl/.gitignore
+++ b/testcases/kernel/syscalls/ipc/shmctl/.gitignore
@@ -2,3 +2,4 @@ 
 /shmctl02
 /shmctl03
 /shmctl04
+/shmctl05
diff --git a/testcases/kernel/syscalls/ipc/shmctl/shmctl05.c b/testcases/kernel/syscalls/ipc/shmctl/shmctl05.c
new file mode 100644
index 000000000..6771b1445
--- /dev/null
+++ b/testcases/kernel/syscalls/ipc/shmctl/shmctl05.c
@@ -0,0 +1,113 @@ 
+/*
+ * 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 <stdbool.h>
+#include <stdlib.h>
+
+#include "tst_test.h"
+#include "tst_timer.h"
+#include "tst_safe_sysv_ipc.h"
+#include "lapi/syscalls.h"
+
+static bool timed_out(void)
+{
+	tst_timer_stop();
+	return tst_timer_elapsed_ms() >= 5000;
+}
+
+static void do_test(void)
+{
+	pid_t pid;
+	int status;
+
+	/* 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_timer_start(CLOCK_MONOTONIC);
+
+	pid = SAFE_FORK();
+	srand(getpid());
+
+	if (pid == 0) {
+		/*
+		 * Child process: repeatedly attach a shm segment, then remap it
+		 * until the ID seems to have been removed by the other process.
+		 */
+		do {
+			int id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
+			void *addr = shmat(id, NULL, 0);
+
+			if (addr == (void *)-1L) {
+				if (errno == EIDRM || errno == EINVAL)
+					continue;
+				tst_brk(TBROK | TERRNO,
+					"Unexpected shmat() error");
+			}
+
+			usleep(rand() % 50);
+			do {
+				/* This is the system call that crashed */
+				TEST(syscall(__NR_remap_file_pages, addr, 4096,
+					     0, 0, 0));
+				if (TEST_RETURN == 0)
+					continue;
+				if (TEST_ERRNO == EIDRM || TEST_ERRNO == EINVAL)
+					break;
+				tst_brk(TBROK | TTERRNO,
+					"Unexpected remap_file_pages() error");
+			} while (!timed_out());
+		} while (!timed_out());
+		exit(0);
+	} else {
+		/*
+		 * Parent process: repeatedly remove the shm ID and reallocate
+		 * it again for a new shm segment.
+		 */
+		do {
+			int id = SAFE_SHMGET(0xF00F, 4096, IPC_CREAT|0700);
+
+			usleep(rand() % 50);
+			SAFE_SHMCTL(id, IPC_RMID, NULL);
+		} while (!timed_out());
+	}
+
+	SAFE_WAIT(&status);
+	if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
+		tst_res(TPASS, "no crash observed");
+	else if (WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL)
+		tst_res(TFAIL, "kernel oops observed");
+	else
+		tst_brk(TBROK, "Child %s", tst_strstatus(status));
+
+	shmctl(0xF00F, IPC_RMID, NULL);
+}
+
+static struct tst_test test = {
+	.test_all = do_test,
+	.forks_child = 1,
+};