Menu ▾ ▴

#1039 __mingw_aligned_malloc returns an undersized non-NULL pointer for sizes near SIZE_MAX due to overflow

v1.0 (example)
open
nobody
None
5
7 days ago
7 days ago
No

Description

__mingw_aligned_offset_malloc and __mingw_aligned_offset_realloc
lack an overflow check on the internal size computation and return an
undersized non-NULL buffer for sizes near SIZE_MAX.

Affected:
mingw-w64-crt/misc/mingw-aligned-malloc.c

- `__mingw_aligned_offset_malloc`   (and `__mingw_aligned_malloc`, which wraps it)
- `__mingw_aligned_offset_realloc` (and `__mingw_aligned_realloc`, which wraps it)

This is similar to CVE-2026-0861 and CVE-2026-95619.

Class: CWE-190 (Integer Overflow) -> CWE-131 (Incorrect Buffer Size Calculation)
-> CWE-787 (Out-of-bounds Write)

Both functions compute their backing allocation as

    malloc  (size + (alignment + GAP (offset) + sizeof (void *)));   /* malloc  */
    realloc (p0, size + (alignment + GAP (offset) + sizeof (void *))); /* realloc */

with no check that the addition does not overflow size_t. When size is within alignment + sizeof(void*) - 1 bytes of SIZE_MAX (with offset == 0), the sum wraps to a small value, the underlying allocator succeeds, and a non-NULL, correctly-aligned pointer to a tiny block is returned as if it were the huge size requested. The expected behavior is to fail (return NULL / ENOMEM).

The overflow window scales with the requested alignment. Measured on x86-64:

alignment 32    -> wraps for size > SIZE_MAX - 39   (i.e. (size_t)-39 ..)
alignment 1024  -> wraps for size > SIZE_MAX - 1031

This is the fallback path used when the target CRT does not export
_aligned_malloc, and whenever the __mingw_aligned_* symbols are called directly.

NOTE: native Windows msvcrt.dll / UCRT are NOT affected -- they guard the
addition and correctly return NULL. Wine's msvcrt reimplementation is affected
by the same class of bug, reported as https://bugs.winehq.org/show_bug.cgi?id=60378

Reproducer:

    #include <malloc.h>
    #include <stdio.h>
    extern void *__mingw_aligned_malloc(size_t, size_t);
    int main(void) {
        void *p = __mingw_aligned_malloc((size_t)-1025, 1024);
        printf("p = %p\n", p);          /* non-NULL: BUG (should be NULL) */
        return 0;
    }

Expected: p == NULL, errno == ENOMEM.
Observed: p != NULL, pointing at a handful of bytes.

Impact

A caller whose size is derived from arithmetic that can reach near-SIZE_MAX
(upstream overflow, (size_t)-1 sentinels, attacker-influenced length math)
receives a valid-looking aligned pointer to an undersized block; subsequent
writes corrupt the heap (CWE-787). The bug also defeats the standard NULL-check
defensive idiom.

N.B. calling __mingw_aligned_msize on the wrapped block returns an incorrect near-SIZE_MAX value rather than the true (tiny) size that was actually allocated, so a caller cannot detect the undersized allocation via the size-query API either. This is a consequence of the same overflow; fixing the malloc/realloc calls resolves it, and __mingw_aligned_msize needs no separate change.

Discussion


Log in to post a comment.