Skip to content

Fix GH-24492: handle base64_encode() soft OOM - #24618

Open
forgeblueai wants to merge 2 commits into
php:PHP-8.4from
forgeblueai:fix/gh-24492-base64-soft-oom
Open

forgeblueai wants to merge 2 commits into
php:PHP-8.4from
forgeblueai:fix/gh-24492-base64-soft-oom

Conversation

@forgeblueai

Copy link
Copy Markdown

Problem

base64_encode() currently uses the non-fallible php_base64_encode_ex() allocation contract. A userland call requiring a large output can hit PHP's hard memory limit, terminate with exit 137, and skip the registered shutdown handler. This is part of the previously reported GH-24492 behavior.

Fix

Add an internal PHP_BASE64_FALLIBLE option used only by the PHP base64_encode() entry point. Make the five generic and x86 SIMD encoding allocation sites honor that option through zend_string_safe_falloc(); return NULL before dereferencing the failed allocation. The PHP entry point calls RETURN_OOM().

Native C helpers called without the new flag (including existing callers using PHP_BASE64_NO_PADDING) preserve their non-fallible behavior, avoiding a breaking change to the exported helper contract.

Reproduction and tests

The accompanying ext/standard/tests/url/gh24492_base64_encode_oom.phpt confirms that the input is constructed before invoking Base64, that memory exhaustion is reported through the soft memory-limit fatal path, and that shutdown handlers run.

Verified experiments from ForgeBlue PHP Core Lab:

This is deliberately a partial fix. Existing #24548 and #24596 address other allocation paths.

Limits and review notes

Broader PHP test suites, all CPU-selected SIMD implementations, and ARM64-specific validation remain appropriate follow-up work. This patch does not claim a new security vulnerability or CVE.

@forgeblueai

Copy link
Copy Markdown
Author

The latest revision keeps PHP_BASE64_FALLIBLE private to base64.c. The public base64.h header is unchanged, so the existing ABI break label refers to an earlier revision. The revised patch passed 9/9 targeted PHPTs on both NTS and ZTS PHP-8.4 HEAD: https://github.com/forgeblueai/php-core-lab/actions/runs/40005439132

@forgeblueai
forgeblueai marked this pull request as ready for review October 23, 2026 19:30
@forgeblueai
forgeblueai requested a review from bukka as a code owner October 23, 2026 19:30
@anun333

anun333 commented Oct 25, 2026

Copy link
Copy Markdown

Quoted below: written with Claude, an LLM, as CONTRIBUTING.md asks us to mark. The builds and test runs behind it are on my machine; the commands to repeat them are under the fold.

Reproduced on debug builds of PHP-8.4 (51808ac5478) without and with this branch (b179816e47d), memory_limit=16M, base64_encode() of a 13 MiB string:

Without the patch With it
message Hard memory limit of 33554432 bytes exhausted at Zend/zend_string.h:198 (tried to allocate 18178048 bytes) Fatal error: Allowed memory size of 16777216 bytes exhausted at Zend/zend_string.h:198 (tried to allocate 18178048 bytes)
shutdown function doesn't run runs
exit code 137 255

The new test fails without the patch and passes with it. PHP_BASE64_FALLIBLE (2) doesn't collide with anything: PHP_BASE64_NO_PADDING (1) is the only flag in base64.h on PHP-8.4, PHP-8.5 and master. The decode paths keep their non-fallible allocation, which looks fine, since a decode's output is never larger than its input.

How this was tested

Builds (Linux x86_64, gcc 13.3), the same for both trees:

./buildconf --force
./configure --enable-debug --with-mysqli --disable-cgi --disable-phpdbg --disable-fileinfo
make -j6

Run as sapi/cli/php -n -d memory_limit=16M b64.php:

<?php
register_shutdown_function(function () { echo "SHUTDOWN\n"; });
$s = str_repeat("A", 13 * 1024 * 1024); echo "READY\n";
base64_encode($s); echo "UNREACHABLE\n";

The PR's own test: TEST_PHP_EXECUTABLE=sapi/cli/php sapi/cli/php run-tests.php -q -n ext/standard/tests/url/gh24492_base64_encode_oom.phpt. The flags: git show origin/<branch>:ext/standard/base64.h | grep 'define PHP_BASE64_'.

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.

2 participants