I'm adding a unit test for the "free() preserves errno" feature. The idea for the Linux specific test is from <https://sourceware.org/legacy-ml/libc-alpha/2015-02/msg00067.html> where Rich Felker wrote:
free can make a syscall that will set errno unless you suppress this behavior -- munmap can fail due to inability to split an existing vma due to hitting the vma limit or simply a kernel oom condition. 2020-12-19 Bruno Haible <br...@clisp.org> free-posix: Add tests. * tests/test-free.c: New file. * tests/macros.h (ASSERT_NO_STDIO, WRITE_MACROEXPANDED_INTEGER_TO_STDERR, WRITE_INTEGER_TO_STDERR, WRITE_TO_STDERR): New macros. * modules/free-posix-tests: New file. diff --git a/tests/test-free.c b/tests/test-free.c new file mode 100644 index 0000000..4fa1d3a --- /dev/null +++ b/tests/test-free.c @@ -0,0 +1,173 @@ +/* Test of free() function. + Copyright (C) 2020 Free Software Foundation, 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 3 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 <https://www.gnu.org/licenses/>. */ + +/* Written by Bruno Haible <br...@clisp.org>, 2020. */ + +#include <config.h> + +/* Specification. */ +#include <stdlib.h> + +#include <errno.h> +#include <string.h> +#include <unistd.h> +#if defined __linux__ +# include <fcntl.h> +# include <stdint.h> +# include <string.h> +# include <sys/mman.h> +#endif + +#include "macros.h" + +/* The indirection through a volatile function pointer is necessary to prevent + a GCC optimization. Without it, when optimizing, GCC would "know" that errno + is unchanged by calling free(ptr), when ptr was the result of a malloc(...) + call in the same function. */ +static int +get_errno (void) +{ + volatile int err = errno; + return err; +} + +static int (* volatile get_errno_func) (void) = get_errno; + +int +main () +{ + /* Check that free() preserves errno. */ + { + errno = 1789; /* Liberté, égalité, fraternité. */ + free (NULL); + ASSERT_NO_STDIO (get_errno_func () == 1789); + } + { /* Small memory allocations. */ + #define N 10000 + void * volatile ptrs[N]; + size_t i; + for (i = 0; i < N; i++) + ptrs[i] = malloc (15); + for (i = 0; i < N; i++) + { + errno = 1789; + free (ptrs[i]); + ASSERT_NO_STDIO (get_errno_func () == 1789); + } + #undef N + } + { /* Medium memory allocations. */ + #define N 1000 + void * volatile ptrs[N]; + size_t i; + for (i = 0; i < N; i++) + ptrs[i] = malloc (729); + for (i = 0; i < N; i++) + { + errno = 1789; + free (ptrs[i]); + ASSERT_NO_STDIO (get_errno_func () == 1789); + } + #undef N + } + { /* Large memory allocations. */ + #define N 10 + void * volatile ptrs[N]; + size_t i; + for (i = 0; i < N; i++) + ptrs[i] = malloc (5318153); + for (i = 0; i < N; i++) + { + errno = 1789; + free (ptrs[i]); + ASSERT_NO_STDIO (get_errno_func () == 1789); + } + #undef N + } + + /* Test a less common code path. + When malloc() is based on mmap(), free() can sometimes call munmap(). + munmap() usually succeeds, but fails in a particular situation: when + - it has to unmap the middle part of a VMA, and + - the number of VMAs of a process is limited and the limit is + already reached. + The latter condition is fulfilled on Linux, when the file + /proc/sys/vm/max_map_count exists. This file contains the limit + - for Linux >= 2.4.19: 65536 (DEFAULT_MAX_MAP_COUNT in linux/include/linux/sched.h) + - for Linux >= 2.6.31: 65530 (DEFAULT_MAX_MAP_COUNT in linux/include/linux/mm.h). + */ + #if defined __linux__ + if (open ("/proc/sys/vm/max_map_count", O_RDONLY) >= 0) + { + /* Preparations. */ + size_t pagesize = getpagesize (); + void *firstpage_backup = malloc (pagesize); + void *lastpage_backup = malloc (pagesize); + /* Allocate a large memory area, as a bumper, so that the MAP_FIXED + allocation later will not overwrite parts of the memory areas + allocated to ld.so or libc.so. */ + void *bumper_region = + mmap (NULL, 0x1000000, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + /* A file descriptor pointing to a regular file. */ + int fd = open ("test-free", O_RDONLY); + + if (firstpage_backup != NULL && lastpage_backup != NULL + && bumper_region != (void *)(-1) + && fd >= 0) + { + /* Do a large memory allocation. */ + size_t big_size = 0x1000000; + void * volatile ptr = malloc (big_size - 0x100); + char *ptr_aligned = (char *) ((uintptr_t) ptr & ~(pagesize - 1)); + /* This large memory allocation allocated a memory area + from ptr_aligned to ptr_aligned + big_size. + Enlarge this memory area by adding a page before and a page + after it. */ + memcpy (firstpage_backup, ptr_aligned, pagesize); + memcpy (lastpage_backup, ptr_aligned + big_size - pagesize, pagesize); + if (mmap (ptr_aligned - pagesize, pagesize + big_size + pagesize, + PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0) + != (void *)(-1)) + { + memcpy (ptr_aligned, firstpage_backup, pagesize); + memcpy (ptr_aligned + big_size - pagesize, lastpage_backup, pagesize); + + /* Now add as many mappings as we can. + Stop at 65536, in order not to crash the machine (in case the + limit has been increased by the system administrator). */ + size_t i; + for (i = 0; i < 65536; i++) + if (mmap (NULL, pagesize, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0) + == (void *)(-1)) + break; + /* Now the number of VMAs of this process has hopefully attained + its limit. */ + + errno = 1789; + /* This call to free() is supposed to call + munmap (ptr_aligned, big_size); + which increases the number of VMAs by 1, which is supposed + to fail. */ + free (ptr); + ASSERT_NO_STDIO (get_errno_func () == 1789); + } + } + } + #endif + + return 0; +} diff --git a/modules/free-posix-tests b/modules/free-posix-tests new file mode 100644 index 0000000..8e81c22 --- /dev/null +++ b/modules/free-posix-tests @@ -0,0 +1,12 @@ +Files: +tests/test-free.c +tests/macros.h + +Depends-on: +unistd + +configure.ac: + +Makefile.am: +TESTS += test-free +check_PROGRAMS += test-free diff --git a/tests/macros.h b/tests/macros.h index bbc340d..3fd816b 100644 --- a/tests/macros.h +++ b/tests/macros.h @@ -56,14 +56,42 @@ { \ if (!(expr)) \ { \ - fprintf (ASSERT_STREAM, "%s:%d: assertion '%s' failed\n", \ - __FILE__, __LINE__, #expr); \ + fprintf (ASSERT_STREAM, "%s:%d: assertion '%s' failed\n", \ + __FILE__, __LINE__, #expr); \ fflush (ASSERT_STREAM); \ abort (); \ } \ } \ while (0) +/* Like ASSERT, except that it uses no stdio. + Requires #include <string.h> and #include <unistd.h>. */ +#define ASSERT_NO_STDIO(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + WRITE_TO_STDERR (__FILE__); \ + WRITE_TO_STDERR (":"); \ + WRITE_MACROEXPANDED_INTEGER_TO_STDERR (__LINE__); \ + WRITE_TO_STDERR (": assertion '"); \ + WRITE_TO_STDERR (#expr); \ + WRITE_TO_STDERR ("' failed\n"); \ + abort (); \ + } \ + } \ + while (0) +#define WRITE_MACROEXPANDED_INTEGER_TO_STDERR(integer) \ + WRITE_INTEGER_TO_STDERR(integer) +#define WRITE_INTEGER_TO_STDERR(integer) \ + WRITE_TO_STDERR (#integer) +#define WRITE_TO_STDERR(string_literal) \ + { \ + const char *s = string_literal; \ + int ret = write (2, s, strlen (s)); \ + (void) ret; \ + } + /* SIZEOF (array) returns the number of elements of an array. It works for arrays that are declared outside functions and for local variables of array type. It does