Short description: malloc: Test various special cases related to allocation failures Author(s): Florian Weimer Origin: git://sourceware.org/git/glibc.git Bug-RHEL: #1296453 (rhel-7.2.z), #1293976 (rhel-7.3), #1256285 (SRT), #1418978 Bug-Fedora: NA Bug-Upstream: #19469 Upstream status: committed # # commit 1bd5483e104c8bde6e61dc5e3f8a848bc861872d # Author: Florian Weimer # Date: Tue Dec 29 20:32:35 2015 +0100 # # malloc: Test various special cases related to allocation failures # # This test case exercises unusual code paths in allocation functions, # related to allocation failures. Specifically, the test can reveal # the following bugs: # # (a) calloc returns non-zero memory on fallback to sysmalloc. # (b) calloc can self-deadlock because it fails to release # the arena lock on certain allocation failures. # (c) pvalloc can dereference a NULL arena pointer. # # (a) and (b) appear specific to a faulty downstream backport. # (c) was fixed as part of commit 10ad46bc6526edc5c7afcc57112da96917ff3629. # # commit f690b56979dea81340a397c1b5e44827a6fb06e7 # Author: Florian Weimer # Date: Tue Aug 2 17:01:02 2016 +0200 # # malloc: Run tests without calling mallopt [BZ #19469] # # The compiled tests no longer refer to the mallopt symbol # from their main functions. (Some tests still call mallopt # explicitly, which is fine.) Index: b/malloc/Makefile =================================================================== --- a/malloc/Makefile +++ b/malloc/Makefile @@ -27,7 +27,8 @@ headers := $(dist-headers) obstack.h mch tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \ tst-mallocstate tst-mcheck tst-mallocfork tst-trim1 \ tst-malloc-usable \ - tst-malloc-backtrace tst-malloc-thread-exit + tst-malloc-backtrace tst-malloc-thread-exit \ + tst-malloc-thread-fail test-srcs = tst-mtrace routines = malloc morecore mcheck mtrace obstack @@ -44,6 +45,8 @@ libmemusage-inhibit-o = $(filter-out .os $(objpfx)tst-malloc-backtrace: $(common-objpfx)nptl/libpthread.so \ $(common-objpfx)nptl/libpthread_nonshared.a +$(objpfx)tst-malloc-thread-fail: $(common-objpfx)nptl/libpthread.so \ + $(common-objpfx)nptl/libpthread_nonshared.a $(objpfx)tst-malloc-thread-exit: $(common-objpfx)nptl/libpthread.so \ $(common-objpfx)nptl/libpthread_nonshared.a @@ -149,3 +152,7 @@ $(objpfx)libmemusage.so: $(common-objpfx # Extra dependencies $(foreach o,$(all-object-suffixes),$(objpfx)malloc$(o)): arena.c hooks.c + +# Compile the tests with a flag which suppresses the mallopt call in +# the test skeleton. +$(tests:%=$(objpfx)%.o): CPPFLAGS += -DTEST_NO_MALLOPT Index: b/malloc/tst-malloc-thread-fail.c =================================================================== --- /dev/null +++ b/malloc/tst-malloc-thread-fail.c @@ -0,0 +1,464 @@ +/* Test allocation function behavior on allocation failure. + Copyright (C) 2015 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; see the file COPYING.LIB. If + not, see . */ + +/* This test case attempts to trigger various unusual conditions + related to allocation failures, notably switching to a different + arena, and falling back to mmap (via sysmalloc). */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* This mirrors the C11 max_align_t type provided by GCC, but it is + also available in C99 mode. The aligned attributes are required + because some ABIs have reduced alignment requirements for struct + and union members. */ +typedef struct { + long long ll __attribute__ ((__aligned__ (__alignof__ (long long)))); + long double ld __attribute__ ((__aligned__ (__alignof__ (long double)))); +} libc_max_align_t; + +/* Wrapper for calloc with an optimization barrier. */ +static void * +__attribute__ ((noinline, noclone)) +allocate_zeroed (size_t a, size_t b) +{ + return calloc (a, b); +} + +/* System page size, as determined by sysconf (_SC_PAGE_SIZE). */ +static unsigned long page_size; + +/* Test parameters. */ +static size_t allocation_size; +static size_t alignment; +static enum { + with_malloc, + with_realloc, + with_aligned_alloc, + with_memalign, + with_posix_memalign, + with_valloc, + with_pvalloc, + with_calloc, + last_allocation_function = with_calloc +} allocation_function; + +/* True if an allocation function uses the alignment test + parameter. */ +const static bool alignment_sensitive[last_allocation_function + 1] = + { + [with_aligned_alloc] = true, + [with_memalign] = true, + [with_posix_memalign] = true, + }; + +/* Combined pointer/expected alignment result of an allocation + function. */ +struct allocate_result { + void *pointer; + size_t alignment; +}; + +/* Call the allocation function specified by allocation_function, with + allocation_size and alignment (if applicable) as arguments. No + alignment check. */ +static struct allocate_result +allocate_1 (void) +{ + switch (allocation_function) + { + case with_malloc: + return (struct allocate_result) + {malloc (allocation_size), __alignof__ (libc_max_align_t)}; + case with_realloc: + { + void *p = realloc (NULL, 16); + void *q; + if (p == NULL) + q = NULL; + else + { + q = realloc (p, allocation_size); + if (q == NULL) + free (p); + } + return (struct allocate_result) {q, __alignof__ (libc_max_align_t)}; + } + case with_aligned_alloc: + { + void *p = aligned_alloc (alignment, allocation_size); + return (struct allocate_result) {p, alignment}; + } + case with_memalign: + { + void *p = memalign (alignment, allocation_size); + return (struct allocate_result) {p, alignment}; + } + case with_posix_memalign: + { + void *p; + if (posix_memalign (&p, alignment, allocation_size)) + { + if (errno == ENOMEM) + p = NULL; + else + { + printf ("error: posix_memalign (p, %zu, %zu): %m\n", + alignment, allocation_size); + abort (); + } + } + return (struct allocate_result) {p, alignment}; + } + case with_valloc: + { + void *p = valloc (allocation_size); + return (struct allocate_result) {p, page_size}; + } + case with_pvalloc: + { + void *p = pvalloc (allocation_size); + return (struct allocate_result) {p, page_size}; + } + case with_calloc: + { + char *p = allocate_zeroed (1, allocation_size); + /* Check for non-zero bytes. */ + if (p != NULL) + for (size_t i = 0; i < allocation_size; ++i) + if (p[i] != 0) + { + printf ("error: non-zero byte at offset %zu\n", i); + abort (); + } + return (struct allocate_result) {p, __alignof__ (libc_max_align_t)}; + } + } + abort (); +} + +/* Call allocate_1 and perform the alignment check on the result. */ +static void * +allocate (void) +{ + struct allocate_result r = allocate_1 (); +#if __powerpc__ == 1 && __powerpc64__ == 0 + /* Sourceware bug 6527 on 32-bit POWER. + Ignore 16-byte alignment requirement when using malloc, realloc, or + calloc, since these functions are known not to provide enough + alignment. */ + if ((((uintptr_t) r.pointer) & (r.alignment - 1)) != 0 + && !(r.alignment == 16 + && (allocation_function == with_malloc + || allocation_function == with_realloc + || allocation_function == with_calloc))) +#else + if ((((uintptr_t) r.pointer) & (r.alignment - 1)) != 0) +#endif + { + printf ("error: allocation function %d, size %zu not aligned to %zu\n", + (int) allocation_function, allocation_size, r.alignment); + abort (); + } + return r.pointer; +} + +/* Barriers to synchronize thread creation and termination. */ +static pthread_barrier_t start_barrier; +static pthread_barrier_t end_barrier; + +/* Thread function which performs the allocation test. Called by + pthread_create and from the main thread. */ +static void * +allocate_thread (void *closure) +{ + /* Wait for the creation of all threads. */ + { + int ret = pthread_barrier_wait (&start_barrier); + if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) + { + errno = ret; + printf ("error: pthread_barrier_wait: %m\n"); + abort (); + } + } + + /* Allocate until we run out of memory, creating a single-linked + list. */ + struct list { + struct list *next; + }; + struct list *head = NULL; + while (true) + { + struct list *e = allocate (); + if (e == NULL) + break; + + e->next = head; + head = e; + } + + /* Wait for the allocation of all available memory. */ + { + int ret = pthread_barrier_wait (&end_barrier); + if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) + { + errno = ret; + printf ("error: pthread_barrier_wait: %m\n"); + abort (); + } + } + + /* Free the allocated memory. */ + while (head != NULL) + { + struct list *next = head->next; + free (head); + head = next; + } + + return NULL; +} + +/* Number of threads (plus the main thread. */ +enum { thread_count = 8 }; + +/* Thread attribute to request creation of threads with a non-default + stack size which is rather small. This avoids interfering with the + configured address space limit. */ +static pthread_attr_t small_stack; + +/* Runs one test in multiple threads, all in a subprocess so that + subsequent tests do not interfere with each other. */ +static void +run_one (void) +{ + /* Isolate the tests in a subprocess, so that we can start over + from scratch. */ + pid_t pid = fork (); + if (pid == 0) + { + /* In the child process. Create the allocation threads. */ + pthread_t threads[thread_count]; + + for (unsigned i = 0; i < thread_count; ++i) + { + int ret = pthread_create (threads + i, &small_stack, allocate_thread, NULL); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_create: %m\n"); + abort (); + } + } + + /* Also run the test on the main thread. */ + allocate_thread (NULL); + + for (unsigned i = 0; i < thread_count; ++i) + { + int ret = pthread_join (threads[i], NULL); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_join: %m\n"); + abort (); + } + } + _exit (0); + } + else if (pid < 0) + { + printf ("error: fork: %m\n"); + abort (); + } + + /* In the parent process. Wait for the child process to exit. */ + int status; + if (waitpid (pid, &status, 0) < 0) + { + printf ("error: waitpid: %m\n"); + abort (); + } + if (status != 0) + { + printf ("error: exit status %d from child process\n", status); + exit (1); + } +} + +/* Run all applicable allocation functions for the current test + parameters. */ +static void +run_allocation_functions (void) +{ + for (int af = 0; af <= last_allocation_function; ++af) + { + /* Run alignment-sensitive functions for non-default + alignments. */ + if (alignment_sensitive[af] != (alignment != 0)) + continue; + allocation_function = af; + run_one (); + } +} + +int +do_test (void) +{ + /* Limit the number of malloc arenas. We use a very low number so + that despute the address space limit configured below, all + requested arenas a can be created. */ + if (mallopt (M_ARENA_MAX, 2) == 0) + { + printf ("error: mallopt (M_ARENA_MAX) failed\n"); + return 1; + } + + /* Determine the page size. */ + { + long ret = sysconf (_SC_PAGE_SIZE); + if (ret < 0) + { + printf ("error: sysconf (_SC_PAGE_SIZE): %m\n"); + return 1; + } + page_size = ret; + } + + /* Limit the size of the process, so that memory allocation in + allocate_thread will eventually fail, without impacting the + entire system. */ + { + struct rlimit limit; + if (getrlimit (RLIMIT_AS, &limit) != 0) + { + printf ("getrlimit (RLIMIT_AS) failed: %m\n"); + return 1; + } + long target = 200 * 1024 * 1024; + if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur > target) + { + limit.rlim_cur = target; + if (setrlimit (RLIMIT_AS, &limit) != 0) + { + printf ("setrlimit (RLIMIT_AS) failed: %m\n"); + return 1; + } + } + } + + /* Initialize thread attribute with a reduced stack size. */ + { + int ret = pthread_attr_init (&small_stack); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_attr_init: %m\n"); + abort (); + } + unsigned long stack_size = ((256 * 1024) / page_size) * page_size; + if (stack_size < 4 * page_size) + stack_size = 8 * page_size; + ret = pthread_attr_setstacksize (&small_stack, stack_size); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_attr_setstacksize: %m\n"); + abort (); + } + } + + /* Initialize the barriers. We run thread_count threads, plus 1 for + the main thread. */ + { + int ret = pthread_barrier_init (&start_barrier, NULL, thread_count + 1); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_barrier_init: %m\n"); + abort (); + } + + ret = pthread_barrier_init (&end_barrier, NULL, thread_count + 1); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_barrier_init: %m\n"); + abort (); + } + } + + allocation_size = 144; + run_allocation_functions (); + allocation_size = page_size; + run_allocation_functions (); + + alignment = 128; + allocation_size = 512; + run_allocation_functions (); + + allocation_size = page_size; + run_allocation_functions (); + + allocation_size = 17 * page_size; + run_allocation_functions (); + + /* Deallocation the barriers and the thread attribute. */ + { + int ret = pthread_barrier_destroy (&end_barrier); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_barrier_destroy: %m\n"); + return 1; + } + ret = pthread_barrier_destroy (&start_barrier); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_barrier_destroy: %m\n"); + return 1; + } + ret = pthread_attr_destroy (&small_stack); + if (ret != 0) + { + errno = ret; + printf ("error: pthread_attr_destroy: %m\n"); + return 1; + } + } + + return 0; +} + +/* The repeated allocations take some time on slow machines. */ +#define TIMEOUT 20 + +#define TEST_FUNCTION do_test () +#include "../test-skeleton.c" Index: b/test-skeleton.c =================================================================== --- a/test-skeleton.c +++ b/test-skeleton.c @@ -247,8 +247,10 @@ main (int argc, char *argv[]) unsigned int timeoutfactor = 1; pid_t termpid; +#ifndef TEST_NO_MALLOPT /* Make uses of freed and uninitialized memory known. */ mallopt (M_PERTURB, 42); +#endif #ifdef STDOUT_UNBUFFERED setbuf (stdout, NULL);