mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-22 21:10:07 +00:00
9bf8e29ca1
As discussed previously on libc-alpha [1], this patch follows up the idea and add both the __attribute_alloc_size__ on malloc functions (malloc, calloc, realloc, reallocarray, valloc, pvalloc, and memalign) and limit maximum requested allocation size to up PTRDIFF_MAX (taking into consideration internal padding and alignment). This aligns glibc with gcc expected size defined by default warning -Walloc-size-larger-than value which warns for allocation larger than PTRDIFF_MAX. It also aligns with gcc expectation regarding libc and expected size, such as described in PR#67999 [2] and previously discussed ISO C11 issues [3] on libc-alpha. From the RFC thread [4] and previous discussion, it seems that consensus is only to limit such requested size for malloc functions, not the system allocation one (mmap, sbrk, etc.). The implementation changes checked_request2size to check for both overflow and maximum object size up to PTRDIFF_MAX. No additional checks are done on sysmalloc, so it can still issue mmap with values larger than PTRDIFF_T depending on the requested size. The __attribute_alloc_size__ is for functions that return a pointer only, which means it cannot be applied to posix_memalign (see remarks in GCC PR#87683 [5]). The runtimes checks to limit maximum requested allocation size does applies to posix_memalign. Checked on x86_64-linux-gnu and i686-linux-gnu. [1] https://sourceware.org/ml/libc-alpha/2018-11/msg00223.html [2] https://gcc.gnu.org/bugzilla//show_bug.cgi?id=67999 [3] https://sourceware.org/ml/libc-alpha/2011-12/msg00066.html [4] https://sourceware.org/ml/libc-alpha/2018-11/msg00224.html [5] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87683 [BZ #23741] * malloc/hooks.c (malloc_check, realloc_check): Use __builtin_add_overflow on overflow check and adapt to checked_request2size change. * malloc/malloc.c (__libc_malloc, __libc_realloc, _mid_memalign, __libc_pvalloc, __libc_calloc, _int_memalign): Limit maximum allocation size to PTRDIFF_MAX. (REQUEST_OUT_OF_RANGE): Remove macro. (checked_request2size): Change to inline function and limit maximum requested size to PTRDIFF_MAX. (__libc_malloc, __libc_realloc, _int_malloc, _int_memalign): Limit maximum allocation size to PTRDIFF_MAX. (_mid_memalign): Use _int_memalign call for overflow check. (__libc_pvalloc): Use __builtin_add_overflow on overflow check. (__libc_calloc): Use __builtin_mul_overflow for overflow check and limit maximum requested size to PTRDIFF_MAX. * malloc/malloc.h (malloc, calloc, realloc, reallocarray, memalign, valloc, pvalloc): Add __attribute_alloc_size__. * stdlib/stdlib.h (malloc, realloc, reallocarray, valloc): Likewise. * malloc/tst-malloc-too-large.c (do_test): Add check for allocation larger than PTRDIFF_MAX. * malloc/tst-memalign.c (do_test): Disable -Walloc-size-larger-than= around tests of malloc with negative sizes. * malloc/tst-posix_memalign.c (do_test): Likewise. * malloc/tst-pvalloc.c (do_test): Likewise. * malloc/tst-valloc.c (do_test): Likewise. * malloc/tst-reallocarray.c (do_test): Replace call to reallocarray with resulting size allocation larger than PTRDIFF_MAX with reallocarray_nowarn. (reallocarray_nowarn): New function. * NEWS: Mention the malloc function semantic change.
303 lines
9.7 KiB
C
303 lines
9.7 KiB
C
/* Test and verify that too-large memory allocations fail with ENOMEM.
|
|
Copyright (C) 2018-2019 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; if not, see
|
|
<http://www.gnu.org/licenses/>. */
|
|
|
|
/* Bug 22375 reported a regression in malloc where if after malloc'ing then
|
|
free'ing a small block of memory, malloc is then called with a really
|
|
large size argument (close to SIZE_MAX): instead of returning NULL and
|
|
setting errno to ENOMEM, malloc incorrectly returns the previously
|
|
allocated block instead. Bug 22343 reported a similar case where
|
|
posix_memalign incorrectly returns successfully when called with an with
|
|
a really large size argument.
|
|
|
|
Both of these were caused by integer overflows in the allocator when it
|
|
was trying to pad the requested size to allow for book-keeping or
|
|
alignment. This test guards against such bugs by repeatedly allocating
|
|
and freeing small blocks of memory then trying to allocate various block
|
|
sizes larger than the memory bus width of 64-bit targets, or almost
|
|
as large as SIZE_MAX on 32-bit targets supported by glibc. In each case,
|
|
it verifies that such impossibly large allocations correctly fail. */
|
|
|
|
|
|
#include <stdlib.h>
|
|
#include <malloc.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <sys/resource.h>
|
|
#include <libc-diag.h>
|
|
#include <support/check.h>
|
|
#include <unistd.h>
|
|
#include <sys/param.h>
|
|
|
|
|
|
/* This function prepares for each 'too-large memory allocation' test by
|
|
performing a small successful malloc/free and resetting errno prior to
|
|
the actual test. */
|
|
static void
|
|
test_setup (void)
|
|
{
|
|
void *volatile ptr = malloc (16);
|
|
TEST_VERIFY_EXIT (ptr != NULL);
|
|
free (ptr);
|
|
errno = 0;
|
|
}
|
|
|
|
|
|
/* This function tests each of:
|
|
- malloc (SIZE)
|
|
- realloc (PTR_FOR_REALLOC, SIZE)
|
|
- for various values of NMEMB:
|
|
- calloc (NMEMB, SIZE/NMEMB)
|
|
- calloc (SIZE/NMEMB, NMEMB)
|
|
- reallocarray (PTR_FOR_REALLOC, NMEMB, SIZE/NMEMB)
|
|
- reallocarray (PTR_FOR_REALLOC, SIZE/NMEMB, NMEMB)
|
|
and precedes each of these tests with a small malloc/free before it. */
|
|
static void
|
|
test_large_allocations (size_t size)
|
|
{
|
|
void * ptr_to_realloc;
|
|
|
|
test_setup ();
|
|
DIAG_PUSH_NEEDS_COMMENT;
|
|
#if __GNUC_PREREQ (7, 0)
|
|
/* GCC 7 warns about too-large allocations; here we want to test
|
|
that they fail. */
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (malloc (size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
|
|
ptr_to_realloc = malloc (16);
|
|
TEST_VERIFY_EXIT (ptr_to_realloc != NULL);
|
|
test_setup ();
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (realloc (ptr_to_realloc, size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
free (ptr_to_realloc);
|
|
|
|
for (size_t nmemb = 1; nmemb <= 8; nmemb *= 2)
|
|
if ((size % nmemb) == 0)
|
|
{
|
|
test_setup ();
|
|
TEST_VERIFY (calloc (nmemb, size / nmemb) == NULL);
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
|
|
test_setup ();
|
|
TEST_VERIFY (calloc (size / nmemb, nmemb) == NULL);
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
|
|
ptr_to_realloc = malloc (16);
|
|
TEST_VERIFY_EXIT (ptr_to_realloc != NULL);
|
|
test_setup ();
|
|
TEST_VERIFY (reallocarray (ptr_to_realloc, nmemb, size / nmemb) == NULL);
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
free (ptr_to_realloc);
|
|
|
|
ptr_to_realloc = malloc (16);
|
|
TEST_VERIFY_EXIT (ptr_to_realloc != NULL);
|
|
test_setup ();
|
|
TEST_VERIFY (reallocarray (ptr_to_realloc, size / nmemb, nmemb) == NULL);
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
free (ptr_to_realloc);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
|
|
static long pagesize;
|
|
|
|
/* This function tests the following aligned memory allocation functions
|
|
using several valid alignments and precedes each allocation test with a
|
|
small malloc/free before it:
|
|
memalign, posix_memalign, aligned_alloc, valloc, pvalloc. */
|
|
static void
|
|
test_large_aligned_allocations (size_t size)
|
|
{
|
|
/* ptr stores the result of posix_memalign but since all those calls
|
|
should fail, posix_memalign should never change ptr. We set it to
|
|
NULL here and later on we check that it remains NULL after each
|
|
posix_memalign call. */
|
|
void * ptr = NULL;
|
|
|
|
size_t align;
|
|
|
|
/* All aligned memory allocation functions expect an alignment that is a
|
|
power of 2. Given this, we test each of them with every valid
|
|
alignment from 1 thru PAGESIZE. */
|
|
for (align = 1; align <= pagesize; align *= 2)
|
|
{
|
|
test_setup ();
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (memalign (align, size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
|
|
/* posix_memalign expects an alignment that is a power of 2 *and* a
|
|
multiple of sizeof (void *). */
|
|
if ((align % sizeof (void *)) == 0)
|
|
{
|
|
test_setup ();
|
|
TEST_VERIFY (posix_memalign (&ptr, align, size) == ENOMEM);
|
|
TEST_VERIFY (ptr == NULL);
|
|
}
|
|
|
|
/* aligned_alloc expects a size that is a multiple of alignment. */
|
|
if ((size % align) == 0)
|
|
{
|
|
test_setup ();
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (aligned_alloc (align, size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
}
|
|
}
|
|
|
|
/* Both valloc and pvalloc return page-aligned memory. */
|
|
|
|
test_setup ();
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (valloc (size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
|
|
test_setup ();
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
TEST_VERIFY (pvalloc (size) == NULL);
|
|
#if __GNUC_PREREQ (7, 0)
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
#endif
|
|
TEST_VERIFY (errno == ENOMEM);
|
|
}
|
|
|
|
|
|
#define FOURTEEN_ON_BITS ((1UL << 14) - 1)
|
|
#define FIFTY_ON_BITS ((1UL << 50) - 1)
|
|
|
|
|
|
static int
|
|
do_test (void)
|
|
{
|
|
|
|
#if __WORDSIZE >= 64
|
|
|
|
/* This test assumes that none of the supported targets have an address
|
|
bus wider than 50 bits, and that therefore allocations for sizes wider
|
|
than 50 bits will fail. Here, we ensure that the assumption continues
|
|
to be true in the future when we might have address buses wider than 50
|
|
bits. */
|
|
|
|
struct rlimit alloc_size_limit
|
|
= {
|
|
.rlim_cur = FIFTY_ON_BITS,
|
|
.rlim_max = FIFTY_ON_BITS
|
|
};
|
|
|
|
setrlimit (RLIMIT_AS, &alloc_size_limit);
|
|
|
|
#endif /* __WORDSIZE >= 64 */
|
|
|
|
DIAG_PUSH_NEEDS_COMMENT;
|
|
#if __GNUC_PREREQ (7, 0)
|
|
/* GCC 7 warns about too-large allocations; here we want to test
|
|
that they fail. */
|
|
DIAG_IGNORE_NEEDS_COMMENT (7, "-Walloc-size-larger-than=");
|
|
#endif
|
|
|
|
/* Aligned memory allocation functions need to be tested up to alignment
|
|
size equivalent to page size, which should be a power of 2. */
|
|
pagesize = sysconf (_SC_PAGESIZE);
|
|
TEST_VERIFY_EXIT (powerof2 (pagesize));
|
|
|
|
/* Loop 1: Ensure that all allocations with SIZE close to SIZE_MAX, i.e.
|
|
in the range (SIZE_MAX - 2^14, SIZE_MAX], fail.
|
|
|
|
We can expect that this range of allocation sizes will always lead to
|
|
an allocation failure on both 64 and 32 bit targets, because:
|
|
|
|
1. no currently supported 64-bit target has an address bus wider than
|
|
50 bits -- and (2^64 - 2^14) is much wider than that;
|
|
|
|
2. on 32-bit targets, even though 2^32 is only 4 GB and potentially
|
|
addressable, glibc itself is more than 2^14 bytes in size, and
|
|
therefore once glibc is loaded, less than (2^32 - 2^14) bytes remain
|
|
available. */
|
|
|
|
for (size_t i = 0; i <= FOURTEEN_ON_BITS; i++)
|
|
{
|
|
test_large_allocations (SIZE_MAX - i);
|
|
test_large_aligned_allocations (SIZE_MAX - i);
|
|
}
|
|
|
|
/* Allocation larger than PTRDIFF_MAX does play well with C standard,
|
|
since pointer subtraction within the object might overflow ptrdiff_t
|
|
resulting in undefined behavior. To prevent it malloc function fail
|
|
for such allocations. */
|
|
for (size_t i = 1; i <= FOURTEEN_ON_BITS; i++)
|
|
{
|
|
test_large_allocations (PTRDIFF_MAX + i);
|
|
test_large_aligned_allocations (PTRDIFF_MAX + i);
|
|
}
|
|
|
|
#if __WORDSIZE >= 64
|
|
/* On 64-bit targets, we need to test a much wider range of too-large
|
|
sizes, so we test at intervals of (1 << 50) that allocation sizes
|
|
ranging from SIZE_MAX down to (1 << 50) fail:
|
|
The 14 MSBs are decremented starting from "all ON" going down to 1,
|
|
the 50 LSBs are "all ON" and then "all OFF" during every iteration. */
|
|
for (size_t msbs = FOURTEEN_ON_BITS; msbs >= 1; msbs--)
|
|
{
|
|
size_t size = (msbs << 50) | FIFTY_ON_BITS;
|
|
test_large_allocations (size);
|
|
test_large_aligned_allocations (size);
|
|
|
|
size = msbs << 50;
|
|
test_large_allocations (size);
|
|
test_large_aligned_allocations (size);
|
|
}
|
|
#endif /* __WORDSIZE >= 64 */
|
|
|
|
DIAG_POP_NEEDS_COMMENT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#include <support/test-driver.c>
|