From patchwork Fri Sep 18 17:17:10 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Hrubis X-Patchwork-Id: 1367105 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=2001:1418:10:5::2; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=suse.cz Received: from picard.linux.it (picard.linux.it [IPv6:2001:1418:10:5::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4BtL9g1ZQFz9sRf for ; Sat, 19 Sep 2020 03:17:04 +1000 (AEST) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 08BE63C5DAE for ; Fri, 18 Sep 2020 19:16:47 +0200 (CEST) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-6.smtp.seeweb.it (in-6.smtp.seeweb.it [IPv6:2001:4b78:1:20::6]) by picard.linux.it (Postfix) with ESMTP id 136A53C1CB6 for ; Fri, 18 Sep 2020 19:16:43 +0200 (CEST) Received: from mx2.suse.de (mx2.suse.de [195.135.220.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by in-6.smtp.seeweb.it (Postfix) with ESMTPS id 8E3281401126 for ; Fri, 18 Sep 2020 19:16:42 +0200 (CEST) Received: from relay2.suse.de (unknown [195.135.221.27]) by mx2.suse.de (Postfix) with ESMTP id 43C40AF01; Fri, 18 Sep 2020 17:17:16 +0000 (UTC) From: Cyril Hrubis To: ltp@lists.linux.it Date: Fri, 18 Sep 2020 19:17:10 +0200 Message-Id: <20200918171710.19227-1-chrubis@suse.cz> X-Mailer: git-send-email 2.26.2 MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 0.102.4 at in-6.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.2 required=7.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, SPF_HELO_NONE,SPF_PASS autolearn=disabled version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on in-6.smtp.seeweb.it Subject: [LTP] [PATCH v6] Add a test case for mmap MAP_GROWSDOWN flag X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: pravin , "Vijay Kumar B ." Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" From: pravin This test implements two cases. First one uses MAP_GROWSDOWN mapping as a thread stack and expects that the thread will grow the stack successfully. The second one will have a 'break' page allocated into the space the mapping is supposed to grow and expects that the thread will be killed with SIGSEGV. Resolves #300 Signed-off-by: Pravin Raghul S. Reviewed-by: Vijay Kumar B. Signed-off-by: Li Wang Signed-off-by: Cyril Hrubis Reviewed-by: Li Wang --- runtest/syscalls | 1 + testcases/kernel/syscalls/mmap/.gitignore | 1 + testcases/kernel/syscalls/mmap/mmap18.c | 215 ++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 testcases/kernel/syscalls/mmap/mmap18.c diff --git a/runtest/syscalls b/runtest/syscalls index ef1a1ba0b..a165ffed5 100644 --- a/runtest/syscalls +++ b/runtest/syscalls @@ -750,6 +750,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/mmap18.c b/testcases/kernel/syscalls/mmap/mmap18.c new file mode 100644 index 000000000..966bf673e --- /dev/null +++ b/testcases/kernel/syscalls/mmap/mmap18.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) Zilogic Systems Pvt. Ltd., 2020 + * Email: code@zilogic.com + */ + +/* + * Test mmap() MAP_GROWSDOWN flag + * + * # Test1: + * + * We assign the memory region partially allocated with MAP_GROWSDOWN flag to + * a thread as a stack and expect the mapping to grow when we touch the + * guard page by calling a recusive function in the thread that uses the + * growable mapping as a stack. + * + * The kernel only grows the memory region when the stack pointer is within + * guard page when the guard page is touched so simply faulting the guard + * page will not cause the mapping to grow. + * + * Newer kernels does not allow a MAP_GROWSDOWN mapping to grow closer than + * 'stack_guard_gap' pages to an existing mapping. So when we map the stack we + * make sure there is enough of free address space before the lowest stack + * address. + * + * Kernel default 'stack_guard_gap' size is '256 * getpagesize()'. + * + * The stack memory map would look like: + * + * | - - - reserved size - - - | + * + * +-- - - - --+------------+-------------+ + * | 256 pages | unmapped | mapped | + * +-- - - - --+------------+-------------+ + * | mapped size | + * ^ | - - stack size - - | + * start + * ^ ^ + * stack bottom stack top + * + * # Test2: + * + * We allocate stack as we do in the first test but we mmap a page in the + * space the stack is supposed to grow into and we expect the thread to + * segfault when the guard page is faulted. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "tst_test.h" +#include "tst_safe_pthread.h" + +static long page_size; + +static bool __attribute__((noinline)) check_stackgrow_up(void) +{ + char local_var; + static char *addr; + + if (!addr) { + addr = &local_var; + return check_stackgrow_up(); + } + + return (addr < &local_var); +} + +static void setup(void) +{ + if (check_stackgrow_up()) + tst_brk(TCONF, "Test can't be performed with stack grows up architecture"); + + page_size = getpagesize(); +} + +/* + * Returns stack lowest address. Note that the address is not mapped and will + * be mapped on page fault when we grow the stack to the lowest address possible. + */ +static void *allocate_stack(size_t stack_size, size_t mapped_size) +{ + void *start, *stack_top, *stack_bottom; + + long reserved_size = 256 * page_size + stack_size; + + start = SAFE_MMAP(NULL, reserved_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + SAFE_MUNMAP(start, reserved_size); + + SAFE_MMAP((start + reserved_size - mapped_size), mapped_size, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN, + -1, 0); + + stack_top = start + reserved_size; + stack_bottom = start + reserved_size - stack_size; + + tst_res(TINFO, "start = %p, stack_top = %p, stack bottom = %p", + start, stack_top, stack_bottom); + tst_res(TINFO, "mapped pages %zu, stack pages %zu", + mapped_size/page_size, stack_size/page_size); + + return stack_bottom; +} + +static __attribute__((noinline)) void *check_depth_recursive(void *limit) +{ + if ((off_t) &limit < (off_t) limit) { + tst_res(TINFO, "&limit = %p, limit = %p", &limit, limit); + return NULL; + } + + return check_depth_recursive(limit); +} + +/* + * We set the limit one page above the stack bottom to make sure that the stack + * frame will not overflow to the next page, which would potentially cause + * segfault if we are unlucky and there is a mapping right after the guard gap. + * + * Generally the stack frame would be much smaller than page_size so moving the + * pointer by a few bytes would probably be enough, but we do not want to take + * any chances. + */ +static void grow_stack(void *stack, size_t size) +{ + pthread_t test_thread; + pthread_attr_t attr; + int ret; + void *limit = stack + page_size; + + ret = pthread_attr_init(&attr); + if (ret) + tst_brk(TBROK, "pthread_attr_init failed during setup"); + + ret = pthread_attr_setstack(&attr, stack, size); + if (ret) + 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); + + exit(0); +} + +static void grow_stack_success(size_t stack_size, size_t mapped_size) +{ + pid_t child_pid; + int wstatus; + void *stack; + + child_pid = SAFE_FORK(); + if (!child_pid) { + stack = allocate_stack(stack_size, mapped_size); + grow_stack(stack, stack_size); + } + + SAFE_WAIT(&wstatus); + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) + tst_res(TPASS, "Stack grows in unmapped region"); + else + tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus)); + +} + +/* + * We map a page at the bottom of the stack which will cause the thread to be + * killed with SIGSEGV on faulting the guard page. + */ +static void grow_stack_fail(size_t stack_size, size_t mapped_size) +{ + pid_t child_pid; + int wstatus; + void *stack; + + child_pid = SAFE_FORK(); + if (!child_pid) { + tst_no_corefile(0); + stack = allocate_stack(stack_size, mapped_size); + + SAFE_MMAP(stack, page_size, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + tst_res(TINFO, "mapped page at %p", stack); + + grow_stack(stack, stack_size); + } + + SAFE_WAIT(&wstatus); + if (WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGSEGV) + tst_res(TPASS, "Child killed by %s as expected", tst_strsig(SIGSEGV)); + else + tst_res(TFAIL, "Child: %s", tst_strstatus(wstatus)); +} + +static void run_test(void) +{ + size_t stack_size = 8 * PTHREAD_STACK_MIN; + + grow_stack_success(stack_size, page_size); + grow_stack_success(stack_size, stack_size/2); + grow_stack_fail(stack_size, page_size); + grow_stack_fail(stack_size, stack_size/2); +} + +static struct tst_test test = { + .setup = setup, + .test_all = run_test, + .forks_child = 1, +};