+2019-04-18 Adhemerval Zanella <adhemerval.zanella@linaro.org>
+
+ [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.
+
2019-04-17 Adhemerval Zanella <adhemerval.zanella@linaro.org>
* sysdeps/powerpc/fpu/s_fma.c: Fix format.
as all functions that call vscanf, vfscanf, or vsscanf are annotated with
__attribute__ ((format (scanf, ...))).
+* Memory allocation functions malloc, calloc, realloc, reallocarray, valloc,
+ pvalloc, memalign, and posix_memalign fail now with total object size
+ larger than PTRDIFF_MAX. This is to avoid potential undefined behavior with
+ pointer subtraction within the allocated object, where results might
+ overflow the ptrdiff_t type.
+
Changes to build and runtime requirements:
* Python 3.4 or later is required to build the GNU C Library.
malloc_check (size_t sz, const void *caller)
{
void *victim;
+ size_t nb;
- if (sz + 1 == 0)
+ if (__builtin_add_overflow (sz, 1, &nb))
{
__set_errno (ENOMEM);
return NULL;
__libc_lock_lock (main_arena.mutex);
top_check ();
- victim = _int_malloc (&main_arena, sz + 1);
+ victim = _int_malloc (&main_arena, nb);
__libc_lock_unlock (main_arena.mutex);
return mem2mem_check (victim, sz);
}
INTERNAL_SIZE_T nb;
void *newmem = 0;
unsigned char *magic_p;
+ size_t rb;
- if (bytes + 1 == 0)
+ if (__builtin_add_overflow (bytes, 1, &rb))
{
__set_errno (ENOMEM);
return NULL;
malloc_printerr ("realloc(): invalid pointer");
const INTERNAL_SIZE_T oldsize = chunksize (oldp);
- checked_request2size (bytes + 1, nb);
+ if (!checked_request2size (rb, &nb))
+ goto invert;
+
__libc_lock_lock (main_arena.mutex);
if (chunk_is_mmapped (oldp))
{
/* Must alloc, copy, free. */
top_check ();
- newmem = _int_malloc (&main_arena, bytes + 1);
+ newmem = _int_malloc (&main_arena, rb);
if (newmem)
{
memcpy (newmem, oldmem, oldsize - 2 * SIZE_SZ);
else
{
top_check ();
- INTERNAL_SIZE_T nb;
- checked_request2size (bytes + 1, nb);
newmem = _int_realloc (&main_arena, oldp, oldsize, nb);
}
/* mem2chunk_check changed the magic byte in the old chunk.
If newmem is NULL, then the old chunk will still be used though,
so we need to invert that change here. */
+invert:
if (newmem == NULL)
*magic_p ^= 0xFF;
DIAG_POP_NEEDS_COMMENT;
((uintptr_t)(MALLOC_ALIGNMENT == 2 * SIZE_SZ ? (p) : chunk2mem (p)) \
& MALLOC_ALIGN_MASK)
-
-/*
- Check if a request is so large that it would wrap around zero when
- padded and aligned. To simplify some other code, the bound is made
- low enough so that adding MINSIZE will also not wrap around zero.
- */
-
-#define REQUEST_OUT_OF_RANGE(req) \
- ((unsigned long) (req) >= \
- (unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))
-
/* pad request bytes into a usable size -- internal version */
#define request2size(req) \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
-/* Same, except also perform an argument and result check. First, we check
- that the padding done by request2size didn't result in an integer
- overflow. Then we check (using REQUEST_OUT_OF_RANGE) that the resulting
- size isn't so large that a later alignment would lead to another integer
- overflow. */
-#define checked_request2size(req, sz) \
-({ \
- (sz) = request2size (req); \
- if (((sz) < (req)) \
- || REQUEST_OUT_OF_RANGE (sz)) \
- { \
- __set_errno (ENOMEM); \
- return 0; \
- } \
-})
+/* Check if REQ overflows when padded and aligned and if the resulting value
+ is less than PTRDIFF_T. Returns TRUE and the requested size or MINSIZE in
+ case the value is less than MINSIZE on SZ or false if any of the previous
+ check fail. */
+static inline bool
+checked_request2size (size_t req, size_t *sz) __nonnull (1)
+{
+ if (__glibc_unlikely (req > PTRDIFF_MAX))
+ return false;
+ *sz = request2size (req);
+ return true;
+}
/*
--------------- Physical chunk operations ---------------
mstate ar_ptr;
void *victim;
+ _Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
+ "PTRDIFF_MAX is not more than half of SIZE_MAX");
+
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
- checked_request2size (bytes, tbytes);
+ if (!checked_request2size (bytes, &tbytes))
+ {
+ __set_errno (ENOMEM);
+ return NULL;
+ }
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
&& !DUMPED_MAIN_ARENA_CHUNK (oldp))
malloc_printerr ("realloc(): invalid pointer");
- checked_request2size (bytes, nb);
+ if (!checked_request2size (bytes, &nb))
+ {
+ __set_errno (ENOMEM);
+ return NULL;
+ }
if (chunk_is_mmapped (oldp))
{
return 0;
}
- /* Check for overflow. */
- if (bytes > SIZE_MAX - alignment - MINSIZE)
- {
- __set_errno (ENOMEM);
- return 0;
- }
-
/* Make sure alignment is power of 2. */
if (!powerof2 (alignment))
void *address = RETURN_ADDRESS (0);
size_t pagesize = GLRO (dl_pagesize);
- size_t rounded_bytes = ALIGN_UP (bytes, pagesize);
-
- /* Check for overflow. */
- if (bytes > SIZE_MAX - 2 * pagesize - MINSIZE)
+ size_t rounded_bytes;
+ /* ALIGN_UP with overflow check. */
+ if (__glibc_unlikely (__builtin_add_overflow (bytes,
+ pagesize - 1,
+ &rounded_bytes)))
{
__set_errno (ENOMEM);
return 0;
}
+ rounded_bytes = rounded_bytes & -(pagesize - 1);
return _mid_memalign (pagesize, rounded_bytes, address);
}
{
mstate av;
mchunkptr oldtop, p;
- INTERNAL_SIZE_T bytes, sz, csz, oldtopsize;
+ INTERNAL_SIZE_T sz, csz, oldtopsize;
void *mem;
unsigned long clearsize;
unsigned long nclears;
INTERNAL_SIZE_T *d;
+ ptrdiff_t bytes;
- /* size_t is unsigned so the behavior on overflow is defined. */
- bytes = n * elem_size;
-#define HALF_INTERNAL_SIZE_T \
- (((INTERNAL_SIZE_T) 1) << (8 * sizeof (INTERNAL_SIZE_T) / 2))
- if (__builtin_expect ((n | elem_size) >= HALF_INTERNAL_SIZE_T, 0))
+ if (__glibc_unlikely (__builtin_mul_overflow (n, elem_size, &bytes)))
{
- if (elem_size != 0 && bytes / elem_size != n)
- {
- __set_errno (ENOMEM);
- return 0;
- }
+ __set_errno (ENOMEM);
+ return NULL;
}
+ sz = bytes;
void *(*hook) (size_t, const void *) =
atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
{
- sz = bytes;
mem = (*hook)(sz, RETURN_ADDRESS (0));
if (mem == 0)
return 0;
return memset (mem, 0, sz);
}
- sz = bytes;
-
MAYBE_INIT_TCACHE ();
if (SINGLE_THREAD_P)
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
- size. Also, checked_request2size traps (returning 0) request sizes
+ size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/
- checked_request2size (bytes, nb);
+ if (!checked_request2size (bytes, &nb))
+ {
+ __set_errno (ENOMEM);
+ return NULL;
+ }
/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
- checked_request2size (bytes, nb);
+ if (!checked_request2size (bytes, &nb))
+ {
+ __set_errno (ENOMEM);
+ return NULL;
+ }
/*
Strategy: find a spot within that chunk that meets the alignment
request, and then possibly free the leading and trailing space.
*/
-
- /* Check for overflow. */
- if (nb > SIZE_MAX - alignment - MINSIZE)
- {
- __set_errno (ENOMEM);
- return 0;
- }
-
/* Call malloc with worst case padding to hit alignment. */
m = (char *) (_int_malloc (av, nb + alignment + MINSIZE));
__BEGIN_DECLS
/* Allocate SIZE bytes of memory. */
-extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;
+extern void *malloc (size_t __size) __THROW __attribute_malloc__
+ __attribute_alloc_size__ ((1)) __wur;
/* Allocate NMEMB elements of SIZE bytes each, all initialized to 0. */
extern void *calloc (size_t __nmemb, size_t __size)
-__THROW __attribute_malloc__ __wur;
+__THROW __attribute_malloc__ __attribute_alloc_size__ ((1, 2)) __wur;
/* Re-allocate the previously allocated block in __ptr, making the new
block SIZE bytes long. */
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *realloc (void *__ptr, size_t __size)
-__THROW __attribute_warn_unused_result__;
+__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2));
/* Re-allocate the previously allocated block in PTR, making the new
block large enough for NMEMB elements of SIZE bytes each. */
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *reallocarray (void *__ptr, size_t __nmemb, size_t __size)
-__THROW __attribute_warn_unused_result__;
+__THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2, 3));
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
extern void free (void *__ptr) __THROW;
/* Allocate SIZE bytes allocated to ALIGNMENT bytes. */
extern void *memalign (size_t __alignment, size_t __size)
-__THROW __attribute_malloc__ __wur;
+__THROW __attribute_malloc__ __attribute_alloc_size__ ((2)) __wur;
/* Allocate SIZE bytes on a page boundary. */
-extern void *valloc (size_t __size) __THROW __attribute_malloc__ __wur;
+extern void *valloc (size_t __size) __THROW __attribute_malloc__
+ __attribute_alloc_size__ ((1)) __wur;
/* Equivalent to valloc(minimum-page-that-holds(n)), that is, round up
__size to nearest pagesize. */
-extern void *pvalloc (size_t __size) __THROW __attribute_malloc__ __wur;
+extern void *pvalloc (size_t __size) __THROW __attribute_malloc__
+ __attribute_alloc_size__ ((1)) __wur;
/* Underlying allocation function; successive calls should return
contiguous pieces of memory. */
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 (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
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);
}
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
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <libc-diag.h>
static int errors = 0;
errno = 0;
+ 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
/* An attempt to allocate a huge value should return NULL and set
errno to ENOMEM. */
p = memalign (sizeof (void *), -1);
+#if __GNUC_PREREQ (7, 0)
+ DIAG_POP_NEEDS_COMMENT;
+#endif
save = errno;
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <libc-diag.h>
static int errors = 0;
p = NULL;
+ 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
/* An attempt to allocate a huge value should return ENOMEM and
p should remain NULL. */
ret = posix_memalign (&p, sizeof (void *), -1);
+#if __GNUC_PREREQ (7, 0)
+ DIAG_POP_NEEDS_COMMENT;
+#endif
if (ret != ENOMEM)
merror ("posix_memalign (&p, sizeof (void *), -1) succeeded.");
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <libc-diag.h>
static int errors = 0;
errno = 0;
+ 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
/* An attempt to allocate a huge value should return NULL and set
errno to ENOMEM. */
p = pvalloc (-1);
+#if __GNUC_PREREQ (7, 0)
+ DIAG_POP_NEEDS_COMMENT;
+#endif
save = errno;
#include <malloc.h>
#include <string.h>
#include <support/check.h>
+#include <libc-diag.h>
+
+static void *
+reallocarray_nowarn (void *ptr, size_t nmemb, size_t size)
+{
+#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
+ void *ret = reallocarray (ptr, nmemb, size);
+#if __GNUC_PREREQ (7, 0)
+ DIAG_POP_NEEDS_COMMENT;
+#endif
+ return ret;
+}
+
static int
do_test (void)
/* Test overflow detection. */
errno = 0;
- ptr = reallocarray (NULL, max, 2);
+ ptr = reallocarray_nowarn (NULL, max, 2);
TEST_VERIFY (!ptr);
TEST_VERIFY (errno == ENOMEM);
errno = 0;
- ptr = reallocarray (NULL, 2, max);
+ ptr = reallocarray_nowarn (NULL, 2, max);
TEST_VERIFY (!ptr);
TEST_VERIFY (errno == ENOMEM);
a = 65537;
b = max/65537 + 1;
errno = 0;
- ptr = reallocarray (NULL, a, b);
+ ptr = reallocarray_nowarn (NULL, a, b);
TEST_VERIFY (!ptr);
TEST_VERIFY (errno == ENOMEM);
errno = 0;
- ptr = reallocarray (NULL, b, a);
+ ptr = reallocarray_nowarn (NULL, b, a);
TEST_VERIFY (!ptr);
TEST_VERIFY (errno == ENOMEM);
/* Overflow should leave buffer untouched. */
errno = 0;
- ptr2 = reallocarray (ptr, 2, ~(size_t)0);
+ ptr2 = reallocarray_nowarn (ptr, 2, ~(size_t)0);
TEST_VERIFY (!ptr2);
TEST_VERIFY (errno == ENOMEM);
#include <stdio.h>
#include <string.h>
#include <unistd.h>
+#include <libc-diag.h>
static int errors = 0;
errno = 0;
+ 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
/* An attempt to allocate a huge value should return NULL and set
errno to ENOMEM. */
p = valloc (-1);
+#if __GNUC_PREREQ (7, 0)
+ DIAG_POP_NEEDS_COMMENT;
+#endif
save = errno;
#endif /* Use misc or X/Open. */
/* Allocate SIZE bytes of memory. */
-extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;
+extern void *malloc (size_t __size) __THROW __attribute_malloc__
+ __attribute_alloc_size__ ((1)) __wur;
/* Allocate NMEMB elements of SIZE bytes each, all initialized to 0. */
extern void *calloc (size_t __nmemb, size_t __size)
- __THROW __attribute_malloc__ __wur;
+ __THROW __attribute_malloc__ __attribute_alloc_size__ ((1, 2)) __wur;
/* Re-allocate the previously allocated block
in PTR, making the new block SIZE bytes long. */
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *realloc (void *__ptr, size_t __size)
- __THROW __attribute_warn_unused_result__;
+ __THROW __attribute_warn_unused_result__ __attribute_alloc_size__ ((2));
#ifdef __USE_MISC
/* Re-allocate the previously allocated block in PTR, making the new
the same pointer that was passed to it, aliasing needs to be allowed
between objects pointed by the old and new pointers. */
extern void *reallocarray (void *__ptr, size_t __nmemb, size_t __size)
- __THROW __attribute_warn_unused_result__;
+ __THROW __attribute_warn_unused_result__
+ __attribute_alloc_size__ ((2, 3));
#endif
/* Free a block allocated by `malloc', `realloc' or `calloc'. */
#if (defined __USE_XOPEN_EXTENDED && !defined __USE_XOPEN2K) \
|| defined __USE_MISC
/* Allocate SIZE bytes on a page boundary. The storage cannot be freed. */
-extern void *valloc (size_t __size) __THROW __attribute_malloc__ __wur;
+extern void *valloc (size_t __size) __THROW __attribute_malloc__
+ __attribute_alloc_size__ ((1)) __wur;
#endif
#ifdef __USE_XOPEN2K