The shadow key is refreshed when resetting the memory manager between two
requests. But in forking SAPIs the first request of a child process inherits the
shadow key of the parent. As a result, a leak of the shadow key during the first
request of one process gives away the shadow key used during the first request
of other processes. This makes the key refresh mechanism less useful.
Here I ensure that we refresh the shadow key after a fork. We can not reset the
manager as there may be active allocations. Instead, we have to recompute shadow
pointers with the new key.
Closes GH-16765
real_size is returned by memory_get_usage(true), which previously returned 0.
Discovered in Symfony ConsumeMessagesCommandTest::testRunWithMemoryLimit()
through nightly.
Closes GH-18880
* PHP-8.4:
pdo_odbc: Fix memory leak if WideCharToMultiByte() fails
Fix memory leak on php_odbc_fetch_hash() failure
Do not delete main chunk in zend_gc
* PHP-8.3:
pdo_odbc: Fix memory leak if WideCharToMultiByte() fails
Fix memory leak on php_odbc_fetch_hash() failure
Do not delete main chunk in zend_gc
Debugging memory corruption issues in production can be difficult when it's not possible to use a debug build or ASAN/MSAN/Valgrind (e.g. for performance reasons).
This change makes it possible to enable some basic heap debugging helpers without rebuilding PHP. This is controlled by the environment variable ZEND_MM_DEBUG. The env var takes a comma-separated list of parameters:
- poison_free=byte: Override freed blocks with the specified byte value (represented as a number)
- poison_alloc=byte: Override newly allocated blocks with the specified byte value (represented as a number)
- padding=bytes: Pad allocated blocks with the specified amount of bytes (if non-zero, a value >= 16 is recommended to not break alignments)
- check_freelists_on_shutdown=0|1: Enable checking freelist consistency [1] on shutdown
Example:
ZEND_MM_DEBUG=poison_free=0xbe,poison_alloc=0xeb,padding=16,check_freelists_on_shutdown=1 php ...
This is implemented by installing custom handlers when ZEND_MM_DEBUG is set.
This has zero overhead when ZEND_MM_DEBUG is not set. When ZEND_MM_DEBUG is set, the overhead is about 8.5% on the Symfony Demo benchmark.
Goals:
- Crash earlier after a memory corruption, to extract a useful backtrace
- Be usable in production with reasonable overhead
- Having zero overhead when not enabled
Non-goals:
- Replace debug builds, valgrind, ASAN, MSAN or other sanitizers
[1] https://github.com/php/php-src/pull/14054
Co-authored-by: Tim Düsterhus <timwolla@googlemail.com>
We should compare the block memory, not the block metadata (See
zend_mm_add_huge_block).
This caused random test failure for ext/ffi/tests/gh14626.phpt when the
malloc() performed by the FFI code lies close to the block metadata, and
the size of the block is large enough.
This was reported by https://github.com/php/php-src/issues/16902#issuecomment-2498310452
Closes GH-16938.
is_zend_ptr() expected zend_mm_heap.huge_list to be circular, but it's in fact NULL-terminated. It could crash when at least one huge block exists and the ptr did not belong to any block.
`start_memory_manager()` calls `zend_mm_init()` via `alloc_globals_ctor()`
before setting `REAL_PAGE_SIZE` to the right value. Moving the `REAL_PAGE_SIZE`
setting block before the call to `alloc_globals_ctor()` makes the allocator
behave properly on systems with a page size different than 4k.
Suggested-by: arnaud-lb
We keep track of free slots by organizing them in a linked list, with the
first word of every free slot being a pointer to the next one.
In order to make corruptions more difficult to exploit, we check the consistency
of these pointers before dereference by comparing them with a shadow. The shadow
is a copy of the pointer, stored at the end of the slot.
Before this change, an off-by-1 write is enough to produce a valid freelist
pointer. After this change, a bigger out of bound write is required for that.
The difficulty is increase further by mangling the shadow with a secret, and
byte-swapping it, which increases the minimal required out of bound write
length.
Closes GH-14054
Right-shifting a negative number is unspecified (i.e.
implementation-defined) behaviour [1]. If we take a look at the
generated assembly [2], we see that the wrong value is computed.
Fix it by using Z_UL instead of Z_L.
While we're at it, just change every occurrence of this pattern to use
Z_UL instead of casting.
[1] https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf §6.5.7.5
[2] https://godbolt.org/z/4Y1qKKjsh
Closes GH-12613.
This does still deviate from USE_ZEND_ALLOC=0 in that we're not rounding up the
size of the allocation to fixed sizes. Doing so would suppress some
out-of-bounds errors checked by ASAN. Rounding up the size in
_zend_mm_block_size would not be good either as it would break code like
memset(ptr, 0 _zend_mm_block_size(ptr)).
Shift header include
In the C file, include the header first so missing #includes are
detected by the compiler, and use lighter header dependencies in the
header, to speed up compile times.