CVE-2026-28668 AI-generated analysis
platform/bionic, libc malloc allocation limit (libc/bionic/malloc_limit.cpp)
Removes the LimitFree(old_mem) call in bionic's LimitRealloc so that when a growing realloc is rejected by the Android allocation limit the original pointer is left untouched instead of being freed while the caller still holds it.
Root cause
In libc/bionic/malloc_limit.cpp, LimitRealloc(void* old_mem, size_t bytes) is the realloc path of the Android allocation limit code (enabled in the tests via android_mallopt(M_SET_ALLOCATION_LIMIT_BYTES, ...)). When the requested size grows the allocation and CheckLimit(bytes - old_usable_size) fails, the function logged a warning, called LimitFree(old_mem) and returned nullptr. Per the commit message this is invalid because the original pointer should not be changed: callers normally rely on realloc leaving the original block valid when it returns nullptr, so the caller was left holding a freed pointer that it could later use or free again.
How the fix works
- libc/bionic/malloc_limit.cpp:LimitRealloc: in the branch where bytes > old_usable_size and CheckLimit() fails, the LimitFree(old_mem) call is deleted and replaced by the comment 'Do not free the old pointer, it's still valid.'; the function still logs the warning and returns nullptr, so the caller's original allocation is no longer freed.
- tests/malloc_test.cpp:TEST(android_mallopt, set_allocation_limit_realloc_increase): the failing realloc result is stored in 'new_memory' and asserted nullptr, then the original 'memory' is freed to show it is still valid (the commit message states this test fails with the old behavior).
- Single commit, cherry-picked from d00918bfbbe06d4a0841d52aabe6dea3a2432787 (Change-Id I52ccd62f4f513aa55989fb7dda816d13e3171120).
Completeness
- Covers the only place in the shown code that freed the caller's block on a rejected request: the limit-exceeded branch of LimitRealloc.
- The second failure path in LimitRealloc (dispatch_table->realloc / Malloc(realloc) returning nullptr, lines 213-216) is unchanged; it does not free old_mem but subtracts old_usable_size from gAllocated with the comment 'This acts as if the pointer was freed'. This commit does not change that accounting.
- Other limit hooks visible in context (LimitAlignedAlloc, the posix_memalign hook) return failure without touching caller memory and were not changed.
Behavior changes
- With the allocation limit enabled, a realloc that would cross the limit now returns nullptr and leaves the original block allocated (caller must free it), matching standard realloc behavior (general platform knowledge).
- The warning_log message on the rejected path is unchanged.
Per major
- Android 14: same fix: platform/bionic cd1be6e8bfb8 in android-security-14.0.0_r29, hunk in LimitRealloc (malloc_limit.cpp line ~199) plus the test change.
- Android 15: same fix: platform/bionic 10a1e2ce6e59 in android-security-15.0.0_r17, identical hunk (line ~200) plus the test change.
- Android 16: same fix: platform/bionic 50cbdffd6f7e in android-security-16.0.0_r8, identical hunk (line ~200) plus the test change.
- Android 17: same fix: platform/bionic 48a7e3362ff9 in android-security-17.0.0_r1, identical hunk (line ~201); the test hunk is at a different offset (line ~818).
Unknowns
- Which production processes or callers enable the allocation limit and thereby reach LimitRealloc is not shown in the material.
- How the installation of the Limit* hooks happens is not shown.
- Whether LimitFree adjusted gAllocated (and thus how accounting changes with the fix) is not shown, since LimitFree's body is not in the material.
- The exact EoP scenario behind the High rating is not described in the provided material.