[php-src] PR #24554: Fix FPM worker output prefix longer than its buffer or log_limit

From: Date: Thu, 22 Oct 2026 15:55:33 +0000
Subject: [php-src] PR #24554: Fix FPM worker output prefix longer than its buffer or log_limit
Groups: php.git-pulls 
Request: Send a blank email to git-pulls+get-39716@lists.php.net to get a copy of this message
Pull Request: https://github.com/php/php-src/pull/24554 Author: Marti2203 With catch_workers_output = yes, the master logs each worker output line with the prefix [pool NAME] child PID said into stderr: . zlog_stream_set_msg_prefix() formats that prefix into a 512-byte stack buffer, then copies as many bytes as vsnprintf() *would* have written: ```c char buf[MAX_WRAPPING_PREFIX_LENGTH]; ... len = vsnprintf(buf, MAX_WRAPPING_PREFIX_LENGTH - 1, fmt, args); ... memcpy(stream->msg_prefix, buf, len); stream->msg_prefix_len = len; ``` With a pool name longer than about 470 characters, this has two effects: - The prefix includes stack memory past buf. That memory is written to the error log. - If the time/level prefix plus the message prefix reaches log_limit (default 1024, minimum 512), zlog_stream_buf_append() and zlog_stream_unbuffered_write() compute available_len = zlog_limit - reserved_len - 1, which underflows. In buffered mode, the master then overflows its log buffer and crashes. In direct mode (log_buffering = no), it writes memory to the log until it fails. Only pool configuration can trigger this, because the pool name has to be very long. ### Reproducer A pool named with N p characters, pm = static, pm.max_children = 1, catch_workers_output = yes, and one request to a script that does file_put_contents('php://stderr', "hello from worker\n"). Release build: | pool name | log_limit | log_buffering | before | after | |---|---|---|---|---| | 600 | 1024 | yes | log line contains stack memory | prefix cut at 511 bytes, message intact | | 600 | 512 | yes | master segfault | prefix cut at 256 bytes, message intact | | 1100 | 1024 | yes | master segfault | prefix cut at 511 bytes, message intact | | 1100 | 512 | yes | master segfault | prefix cut at 256 bytes, message intact | | 1100 | 512 | no | master writes memory to the log without end (stopped at a 10 MB file size limit) | prefix cut at 256 bytes, message intact | With ASan, the 600 and 1100 character cases report stack-buffer-overflow ... READ of size 640 / READ of size 1140 in zlog_stream_set_msg_prefix(). With the fix there are no reports, including for messages of 2000 and 1000 bytes that wrap over several lines. Each wrapped line stays below log_limit and no message bytes are lost. ### Fix - zlog_stream_set_msg_prefix(): use only the bytes that were written into buf, and limit the prefix to half of log_limit. This always leaves room for the time/level prefix, the quotes, the final suffix and at least part of the message, so the length arithmetic in the append and wrap paths no longer underflows. Pool names short enough for the prefix to fit are not affected at the default log_limit. With a small log_limit (for example 512), a prefix longer than half of it (about 250 or more characters of pool name) is now truncated where it used to fit; if you prefer an exact bound based on the space actually left in the line, I can change it. - zlog_stream_vformat() has the same vsnprintf() pattern with a 1024-byte buffer. It has no callers at the moment, but the length is clamped the same way. ### Test - sapi/fpm/tests/log-bwd-long-pool-name.phpt: buffered mode, default log_limit, 600-character pool name. Checks that the log line is the prefix cut at 511 bytes followed by the quoted message. - sapi/fpm/tests/log-dwd-long-pool-name-limit-512.phpt: direct mode, log_limit = 512, 1100-character pool name. Checks that the prefix is cut at 256 bytes. Results: - Without the fix: the first test fails because the log line contains stack bytes. The second test does not finish: the master blocks writing an endless log line, and run-tests times out. - With the fix: both pass, also with ASan. - sapi/fpm/tests: 121 passed, 16 skipped, 1 warned (an existing XFAIL that passes). Two tests failed (bug68391-conf-include-order.phpt and bug68423-multi-pool-all-pms.phpt) because another process on my shared test machine was already using ports 9010-9023. The failures are bind(): Address already in use and are unrelated to this change. Under ASan, all sapi/fpm/tests/log-*.phpt tests pass. This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually. 🤖 Generated with [Claude Code](https://claude.com/claude-code)

« previous php.git-pulls (#39716) next »