Commit 974de49381d for php
commit 974de49381dd9e8f2babd79b39777f9c57fec5d5
Author: Martin Mirchev <mmirchev@comp.nus.edu.sg>
Date: Thu Oct 22 23:50:00 2026 +0800
Fix FPM worker output prefix longer than its buffer or log_limit
zlog_stream_set_msg_prefix() used the length returned by vsnprintf()
rather than what was written to its 512-byte buffer, so a long pool name
copied stack memory into the prefix. A prefix close to or above log_limit
also made the remaining line length underflow when the worker output was
logged, which crashed the master or made it write unbounded data.
Use only what was written and limit the prefix to half of log_limit. Also
clamp the formatted length in zlog_stream_vformat().
Closes GH-24541
diff --git a/NEWS b/NEWS
index 69c0873aed6..e63029cba56 100644
--- a/NEWS
+++ b/NEWS
@@ -72,6 +72,9 @@ PHP NEWS
- FPM:
. Fixed bug GH-24229 (fcgi_close() passes shutdown() a literal 1, which is
SHUT_RD in WASI). (Ryosuke Ishibashi)
+ . Fixed crash and stack memory disclosure in the worker output logging with
+ a pool name longer than the message prefix buffer or log_limit.
+ (Martin Mirchev)
- Fileinfo:
. Fixed bug GH-24289 (finfo::file() reports the wrong argument number for
diff --git a/sapi/fpm/fpm/zlog.c b/sapi/fpm/fpm/zlog.c
index 39c6eec885b..0f89c75b35a 100644
--- a/sapi/fpm/fpm/zlog.c
+++ b/sapi/fpm/fpm/zlog.c
@@ -587,6 +587,7 @@ zlog_bool zlog_stream_set_msg_prefix(struct zlog_stream *stream, const char *fmt
{
char buf[MAX_WRAPPING_PREFIX_LENGTH];
size_t len;
+ int written;
va_list args;
if (!stream->decorate) {
@@ -594,9 +595,16 @@ zlog_bool zlog_stream_set_msg_prefix(struct zlog_stream *stream, const char *fmt
}
va_start(args, fmt);
- len = vsnprintf(buf, MAX_WRAPPING_PREFIX_LENGTH - 1, fmt, args);
+ written = vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
+ if (written < 0) {
+ return ZLOG_FALSE;
+ }
+ /* Use only what was written to buf, and keep at least half of the log line
+ * for the time and level prefix and the message itself. */
+ len = MIN((size_t) written, MIN(sizeof(buf) - 1, (size_t) zlog_limit / 2));
+
if (stream->msg_prefix_len < len) {
stream->msg_prefix = stream->msg_prefix_len ? realloc(stream->msg_prefix, len + 1) : malloc(len + 1);
if (stream->msg_prefix == NULL) {
@@ -607,7 +615,7 @@ zlog_bool zlog_stream_set_msg_prefix(struct zlog_stream *stream, const char *fmt
stream->msg_prefix[len] = 0;
stream->msg_prefix_len = len;
- return len;
+ return ZLOG_TRUE;
}
/* }}} */
@@ -726,7 +734,10 @@ ssize_t zlog_stream_vformat(struct zlog_stream *stream, const char *fmt, va_list
char sbuf[1024];
size_t len;
- len = vsnprintf(sbuf, 1024, fmt, args);
+ len = vsnprintf(sbuf, sizeof(sbuf), fmt, args);
+ if (len >= sizeof(sbuf)) {
+ len = sizeof(sbuf) - 1;
+ }
return zlog_stream_str(stream, sbuf, len);
}
diff --git a/sapi/fpm/tests/log-bwd-long-pool-name.phpt b/sapi/fpm/tests/log-bwd-long-pool-name.phpt
new file mode 100644
index 00000000000..47c09f5cbfe
--- /dev/null
+++ b/sapi/fpm/tests/log-bwd-long-pool-name.phpt
@@ -0,0 +1,45 @@
+--TEST--
+FPM: Buffered worker output decorated log with pool name longer than the message prefix buffer
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$pool = str_repeat('p', 600);
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+[$pool]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 1
+catch_workers_output = yes
+EOT;
+
+$code = <<<EOT
+<?php
+file_put_contents('php://stderr', "msg 1\n");
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectEmptyBody();
+/* The message prefix is truncated to 511 bytes. */
+$tester->expectLogPattern('/^\[[^\]]+\] WARNING: \[pool p{505}"msg 1"$/');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>
diff --git a/sapi/fpm/tests/log-dwd-long-pool-name-limit-512.phpt b/sapi/fpm/tests/log-dwd-long-pool-name-limit-512.phpt
new file mode 100644
index 00000000000..ffd099cb3bd
--- /dev/null
+++ b/sapi/fpm/tests/log-dwd-long-pool-name-limit-512.phpt
@@ -0,0 +1,47 @@
+--TEST--
+FPM: Direct worker output decorated log with pool name longer than log_limit
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+require_once "tester.inc";
+
+$pool = str_repeat('p', 1100);
+
+$cfg = <<<EOT
+[global]
+error_log = {{FILE:LOG}}
+log_limit = 512
+log_buffering = no
+[$pool]
+listen = {{ADDR}}
+pm = static
+pm.max_children = 1
+catch_workers_output = yes
+EOT;
+
+$code = <<<EOT
+<?php
+file_put_contents('php://stderr', "msg 1\n");
+EOT;
+
+$tester = new FPM\Tester($cfg, $code);
+$tester->start();
+$tester->expectLogStartNotices();
+$tester->request()->expectEmptyBody();
+/* The message prefix is truncated to half of log_limit. */
+$tester->expectLogPattern('/^\[[^\]]+\] WARNING: \[pool p{250}"msg 1"$/');
+$tester->terminate();
+$tester->expectLogTerminatingNotices();
+$tester->close();
+
+?>
+Done
+--EXPECT--
+Done
+--CLEAN--
+<?php
+require_once "tester.inc";
+FPM\Tester::clean();
+?>