Skip to content

Bound the page count in the large-run free path (Zend/zend_alloc.c) - #23772

Open
jvoisin wants to merge 1 commit into
php:masterfrom
jvoisin:maxcnt
Open

jvoisin wants to merge 1 commit into
php:masterfrom
jvoisin:maxcnt

Conversation

@jvoisin

@jvoisin jvoisin commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

zend_mm_free_pages_ex() is the common sink for freeing large runs. It uses pages_count to reset bits in chunk->free_map via zend_mm_bitset_reset_range() and to adjust chunk->free_pages, without ever bounding page_num + pages_count to ZEND_MM_PAGES. That count is not trustworthy:

  • In zend_mm_free_large(), it is decoded from the 10-bit ZEND_MM_LRUN_PAGES field of the page map entry, so it can reach 1023 while a chunk only has 512 pages. The map lives in the chunk header, right next to free_map, and is reachable by a heap overflow.

  • In _efree_large(), it is computed straight from the caller-supplied size. The only cross-check against the map is a pair of ZEND_ASSERT()s, which are compiled out under NDEBUG, so release builds validate nothing beyond page alignment.

The realloc growth path already enforces this very invariant via page_num + new_pages_count <= ZEND_MM_PAGES.

zend_mm_free_pages_ex() is the common sink for freeing large runs. It uses
pages_count to reset bits in chunk->free_map via zend_mm_bitset_reset_range()
and to adjust chunk->free_pages, without ever bounding page_num + pages_count
to ZEND_MM_PAGES. That count is not trustworthy:

 - In zend_mm_free_large(), it is decoded from the 10-bit ZEND_MM_LRUN_PAGES
   field of the page map entry, so it can reach 1023 while a chunk only has
   512 pages. The map lives in the chunk header, right next to free_map, and
   is reachable by a heap overflow.

 - In _efree_large(), it is computed straight from the caller-supplied size.
   The only cross-check against the map is a pair of ZEND_ASSERT()s, which are
   compiled out under NDEBUG, so release builds validate nothing beyond page
   alignment.

The realloc growth path already enforces this very invariant via
page_num + new_pages_count <= ZEND_MM_PAGES.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant