[LTP] [PATCH v1] Add a test case for mmap() MAP_GROWSDOWN flag
Cyril Hrubis
chrubis@suse.cz
Tue Jun 30 11:55:54 CEST 2020
Hi!
> We assign the memory region allocated using MAP_GROWSDOWN to a thread,
> as a stack, to test the effect of MAP_GROWSDOWN. This is because the
> kernel only grows the memory region when the stack pointer, is within
> guard page, when the guard page is touched.
>
> 1. Map an anyonymous memory region of size X, and unmap it.
> 2. Split the unmapped memory region into two.
> 3. The lower memory region is left unmapped.
> 4. The higher memory region is mapped for use as stack, using MAP_FIXED | MAP_GROWSDOWN.
> 5. The higher memory region is provided as stack to a thread, where
> a recursive function is invoked.
> 6. The stack grows beyond the allocated region, into the lower memory area.
> 7. If this results in the memory region being extended, into the
> unmapped region, the test is considered to have passed.
>
> Resolves #300
> Signed-off-by: Pravin Raghul S. <pravinraghul@zilogic.com>
> Reviewed-by: Vijay Kumar B. <vijaykumar@zilogic.com>
>
> ---
> runtest/syscalls | 1 +
> testcases/kernel/syscalls/mmap/.gitignore | 1 +
> testcases/kernel/syscalls/mmap/Makefile | 7 ++
> testcases/kernel/syscalls/mmap/mmap18.c | 146 ++++++++++++++++++++++
> 4 files changed, 155 insertions(+)
> create mode 100644 testcases/kernel/syscalls/mmap/mmap18.c
>
> diff --git a/runtest/syscalls b/runtest/syscalls
> index b4d523319..d8c9dbe92 100644
> --- a/runtest/syscalls
> +++ b/runtest/syscalls
> @@ -747,6 +747,7 @@ mmap14 mmap14
> mmap15 mmap15
> mmap16 mmap16
> mmap17 mmap17
> +mmap18 mmap18
>
> modify_ldt01 modify_ldt01
> modify_ldt02 modify_ldt02
> diff --git a/testcases/kernel/syscalls/mmap/.gitignore b/testcases/kernel/syscalls/mmap/.gitignore
> index c5c083d4b..4fd90ab5f 100644
> --- a/testcases/kernel/syscalls/mmap/.gitignore
> +++ b/testcases/kernel/syscalls/mmap/.gitignore
> @@ -16,3 +16,4 @@
> /mmap15
> /mmap16
> /mmap17
> +/mmap18
> diff --git a/testcases/kernel/syscalls/mmap/Makefile b/testcases/kernel/syscalls/mmap/Makefile
> index 743ca36e7..bdc49e4be 100644
> --- a/testcases/kernel/syscalls/mmap/Makefile
> +++ b/testcases/kernel/syscalls/mmap/Makefile
> @@ -8,3 +8,10 @@ include $(top_srcdir)/include/mk/testcases.mk
> include $(top_srcdir)/include/mk/generic_leaf_target.mk
>
> LDLIBS += -lpthread
> +#
> +# We use recursive calls to to grow the stack, to test the
> +# MAP_GROSWDOWN flag. But tail call optimization by the compiler
> +# can prevent the recusive call and stack growth. Disable tail
> +# call optmization using -fno-optimize-sibling-calls
> +#
> +mmap18: CFLAGS += -fno-optimize-sibling-calls
> diff --git a/testcases/kernel/syscalls/mmap/mmap18.c b/testcases/kernel/syscalls/mmap/mmap18.c
> new file mode 100644
> index 000000000..01a456bfc
> --- /dev/null
> +++ b/testcases/kernel/syscalls/mmap/mmap18.c
> @@ -0,0 +1,146 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) Zilogic Systems Pvt. Ltd., 2020
> + * Email: code@zilogic.com
> + */
> +
> +/*
> + * Test mmap() MAP_GROWSDOWN flag
> + *
> + * We assign the memory region allocated using MAP_GROWSDOWN to a thread,
> + * as a stack, to test the effect of MAP_GROWSDOWN. This is because the
> + * kernel only grows the memory region when the stack pointer, is within
> + * guard page, when the guard page is touched.
> + *
> + * 1. Map an anyonymous memory region of size X, and unmap it.
> + * 2. Split the unmapped memory region into two.
> + * 3. The lower memory region is left unmapped.
> + * 4. The higher memory region is mapped for use as stack, using
^
Trailing whitespace.
You can use the checkpatch.pl from Linux kernel git repostory to check
for minor mistakes like these before sending a patch.
> + * MAP_FIXED | MAP_GROWSDOWN.
> + * 5. The higher memory region is provided as stack to a thread, where
> + * a recursive function is invoked.
> + * 6. The stack grows beyond the allocated region, into the lower memory area.
> + * 7. If this results in the memory region being extended, into the
> + * unmapped region, the test is considered to have passed.
> + */
> +
> +#include <unistd.h>
> +#include <sys/wait.h>
> +#include <pthread.h>
> +#include <sys/mman.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +#include <stdlib.h>
> +#include <stdbool.h>
> +
> +#include "tst_test.h"
> +#include "tst_safe_pthread.h"
> +
> +#define UNITS(x) ((x) * PTHREAD_STACK_MIN)
> +
> +static void *stack = NULL;
Global variables are zeroed at the program start, there is no point to
set them to NULL like that.
> +bool check_stackgrow_up(int *local_var_1)
Missing static.
> +{
> + int local_var_2;
> +
> + if (local_var_1 < &local_var_2)
> + return false;
> + else
> + return true;
Just do return !(local_var_1 < &local_var_2);
> +}
> +
> +void setup(void)
Here as well.
> +{
> + int local_var_1;
> + bool stackgrow_up;
> +
> + stackgrow_up = check_stackgrow_up(&local_var_1);
> + if (stackgrow_up)
Why not just if (stackgrow_up(&local_var_1)) here instead?
> + tst_brk(TCONF, "Test can't be performed with stack grows up architecture");
> +}
> +
> +void cleanup(void)
And here as well.
> +{
> + if (stack != NULL)
Just if (stack)
> + SAFE_MUNMAP(stack, UNITS(8));
> +}
> +
> +void *find_free_range(size_t size)
Here as well.
> +{
> + void *mem;
> +
> + mem = SAFE_MMAP(NULL, size, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + SAFE_MUNMAP(mem, size);
> +
> + return mem;
> +}
> +
> +void split_unmapped_plus_stack(void *start, size_t size, void **stack)
Here as well.
> +{
> + /*
> + * +---------------------+----------------------+
> + * + unmapped | stack |
> + * +---------------------+----------------------+
> + */
> + *stack = SAFE_MMAP(start, size, PROT_READ | PROT_WRITE,
> + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN,
> + -1, 0);
Why can't this function return void * instead?
Also I do not get what the ascii art means here. We just mmap(0 the
whole thing here with MAP_GROWSDOWN what do we split?
Also why can't we just use multiple of page_size instead of some pthread
magic constant? Why don't we just allocate 10 pages then touch them in
order?
> +}
> +
> +static void *check_depth_recursive(void *limit)
> +{
> + if ((off_t) &limit < (off_t) limit)
> + return NULL;
> +
> + return check_depth_recursive(limit);
> +}
> +
> +void grow_stack(void *stack, size_t size, void *limit)
> +{
> + pthread_t test_thread;
> + pthread_attr_t attr;
> + int ret;
> +
> + ret = pthread_attr_init(&attr);
> + if (ret != 0)
> + tst_brk(TBROK, "pthread_attr_init failed during setup");
> +
> + ret = pthread_attr_setstack(&attr, stack, size);
> + if (ret != 0)
> + tst_brk(TBROK, "pthread_attr_setstack failed during setup");
> +
> + SAFE_PTHREAD_CREATE(&test_thread, &attr, check_depth_recursive, limit);
> + SAFE_PTHREAD_JOIN(test_thread, NULL);
Do we really have to use pthreads here? Can't we just touch the guard
page by writing to it? I guess that we can just do for () loop over the
mapping with something as:
for (i = 0; i < size; i++)
((char*)stack)[i] = 'a';
> + exit(0);
> +}
> +
> +static void run_test(void)
> +{
> + void *mem;
> + pid_t child_pid;
> + int wstatus;
> +
> + mem = find_free_range(UNITS(16));
> + split_unmapped_plus_stack(mem, UNITS(16), &stack);
> +
> + child_pid = SAFE_FORK();
> + if (child_pid == 0) {
Use just if (!child_pid)
> + grow_stack(stack, UNITS(8), mem + UNITS(1));
> + } else {
The grow_stack() function calls exit(0) there is no point in having the
else branch here.
> + SAFE_WAIT(&wstatus);
> + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0)
> + tst_res(TPASS, "stack grows in unmapped region");
> + else
> + tst_res(TFAIL, "child exited with %d", wstatus);
> + }
No test result propagation, the child should report the test result
using the tst_res() itself.
> +}
> +
> +static struct tst_test test = {
> + .setup = setup,
> + .cleanup = cleanup,
> + .test_all = run_test,
> + .forks_child = 1,
> +};
--
Cyril Hrubis
chrubis@suse.cz
More information about the ltp
mailing list