[LTP] [PATCH] security/stack_clash: Add test for mmap() minding gap

Cyril Hrubis chrubis@suse.cz
Thu May 25 10:53:08 CEST 2023


Hi!
> The existing stack_clash test only verifies if the stack can grow too close
> to an existing mapping. It doesn't test if mmap() will place new mappings
> in the gap.
> 
> Add a test for this. Have it fill all the empty regions below the stack
> with PROT_NONE mappings. Do this by searching /proc/pid/maps for the
> gaps. The code for parsing this is based on the existing
> read_stack_addr_from_proc() in the file.
> 
> With all lower spaces taken by the PROT_NONE mappings, the search down
> path will then fail for new mmap()s. So mmap() will search up and find the
> gap just before the stack. If it picks it then the mapping is in the guard
> region, so fail the test.
> 
> This logic is somewhat x86_64 specific, but may work for other
> architectures. Make it be x86_64 for now, but document the assumptions so
> that others can be added after more verification.
> 
> Signed-off-by: Rick Edgecombe <rick.p.edgecombe@intel.com>
> ---
> 
> Hi,
> 
> There was recently a regression around mmap placement and mmap guard
> gaps. Today the stack clash test only tests if the gap can expand too
> close to an adjacent mapping, but not if it mappings can be placed in the
> gap. Can we enhance the test to also verifiy the latter?

Is there an upstream commit fix? If so it should be put into the tags
array.

>  testcases/cve/stack_clash.c | 95 +++++++++++++++++++++++++++++++++++++
>  1 file changed, 95 insertions(+)
> 
> diff --git a/testcases/cve/stack_clash.c b/testcases/cve/stack_clash.c
> index cd7f148c2..bbd28b4f1 100644
> --- a/testcases/cve/stack_clash.c
> +++ b/testcases/cve/stack_clash.c
> @@ -18,6 +18,10 @@
>   * to infinity and preallocate REQ_STACK_SIZE bytes of stack so that no calls
>   * after `mmap` are moving stack further.
>   *
> + * If the architecture meets certain requirements (See not above
> + * CAN_DO_PLACEMENT_TEST), then the test also verifies that new mmap()s can't
> + * be placed in the stack's guard gap.
> + *
>   * [1] https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash
>   * [2] https://bugzilla.novell.com/show_bug.cgi?id=CVE-2017-1000364
>   */
> @@ -45,6 +49,22 @@ static int STACK_GROWSDOWN;
>  
>  #define EXIT_TESTBROKE		TBROK
>  
> +/*
> + * The mmap placement part of the test works by forcing a bottom up search.
> + * The assumptions are that the stack grows down (start gap) and either:
> + *   1. The default search is top down, and will switch to bottom up if
> + *      space is exhausted.
> + *   2. The default search is bottom up and the stack is above mmap base
> + *
> + * Case 1 has been verified on x86_64, so only do the test on this
> + * architecture until more have been verified.
> + */
> +#ifdef __x86_64__
> +#define CAN_DO_PLACEMENT_TEST	1
> +#else
> +#define CAN_DO_PLACEMENT_TEST	0
> +#endif
> +
>  void exhaust_stack_into_sigsegv(void)
>  {
>  	volatile char * ptr = alloca(FRAME_SIZE - sizeof(long));
> @@ -78,6 +98,57 @@ void segv_handler(int sig, siginfo_t *info, void *data LTP_ATTRIBUTE_UNUSED)
>  		_exit(EXIT_SUCCESS);
>  }
>  
> +static void force_bottom_up(void)
> +{
> +	FILE *fh;
> +	char buf[1024];
> +	unsigned long start, end, size, lastend = 0;
> +
> +	fh = SAFE_FOPEN("/proc/sys/vm/mmap_min_addr", "r");
> +
> +	/* start filling from mmap_min_addr */
> +	if (fscanf(fh, "%lu", &lastend) != 1) {
> +		SAFE_FCLOSE(fh);
> +		tst_brk(TBROK | TERRNO, "fscanf");
> +		return;
> +	}
> +
> +	SAFE_FCLOSE(fh);

We do have SAFE_FILE_SCANF() as well.

> +	fh = SAFE_FOPEN("/proc/self/maps", "r");
> +
> +	while (!feof(fh)) {
> +		if (fgets(buf, sizeof(buf), fh) == NULL)
> +			goto out;
> +
> +		if (sscanf(buf, "%lx-%lx", &start, &end) != 2) {
> +			tst_brk(TBROK | TERRNO, "sscanf");
> +			goto out;
> +		}
> +
> +		size = start - lastend;
> +
> +		/* Skip the PROT_NONE that was just added (!size). */
> +		if (!size) {
> +			lastend = end;
> +			continue;
> +		}
> +
> +		/* If the next area is the stack, quit. */
> +		if (!!strstr(buf, "[stack]"))
> +			break;
> +
> +		/* This is not cleaned up. */
> +		SAFE_MMAP((void *)lastend, size, PROT_NONE,
> +			  MAP_ANON|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0);
> +
> +		lastend = end;
> +	}
> +
> +out:
> +	SAFE_FCLOSE(fh);
> +}
>
>  unsigned long read_stack_addr_from_proc(unsigned long *stack_size)
>  {
>  	FILE *fh;
> @@ -130,6 +201,26 @@ void __attribute__((noinline)) preallocate_stack(unsigned long required)
>  	garbage[0] = garbage[required - 1] = '\0';
>  }
>  
> +static void do_mmap_placement_test(unsigned long stack_addr, unsigned long gap)
> +{
> +	void *map_test_gap;
> +
> +	force_bottom_up();
> +
> +	/*
> +	 * fill_gaps_with_prot_none() used up all the spaces below the stack. The
> +	 * search down path should fail, and search up might take a look at the guard
> +	 * gap region. If it avoids it, the allocation will be above the stack. If
> +	 * it uses it, the allocation will be in the gap and the test should fail.
> +	 */
> +	map_test_gap = SAFE_MMAP(0, MAPPED_LEN,
> +				 PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0);
> +
> +	if (stack_addr - gap <= (unsigned long)map_test_gap &&
> +		(unsigned long)map_test_gap <= stack_addr)
> +		_exit(EXIT_FAILURE);

It would be better if we reported this as a separate result, since
otherwise it wouldn't be clear if this part failed or if we got the
EXIT_FAILURE from the SIGSEGV signal handler.

As a matter of fact we can just do tst_res(TFAIL, "..."): here instead
of exit, which will print a message, increment failure counter, and
proccedd with the rest of the test. I suppose that we want to unmap this
mapping just in case if we do so.

> +}
> +
>  void do_child(void)
>  {
>  	unsigned long stack_addr, stack_size;
> @@ -179,6 +270,10 @@ void do_child(void)
>  	dump_proc_self_maps();
>  #endif
>  
> +	if (CAN_DO_PLACEMENT_TEST)
> +		do_mmap_placement_test(stack_addr, gap);

I do not think that the macro indirection does add any value, can we
just do the #ifdef for x86 here?

> +	/* Now see if it can grow too close to an adjacent region. */
>  	exhaust_stack_into_sigsegv();
>  }

Otherwise it looks fine.

-- 
Cyril Hrubis
chrubis@suse.cz


More information about the ltp mailing list