Skip to content

Commit e643129

Browse files
committed
Fix GH-16628: FPM logs are getting corrupted with this log statement
zlog_buf_prefix() can return a larger length than what actually was written due to its use of snprintf(). The code in zlog_stream_prefix_ex() does not take this into account, other callers do. What ends up happening then is that stream->length is set to the length as if snprintf() was able to write all bytes, causing stream->length to become larger than stream->buf.size, causing a segfault. In case the buffer was too small we try with a larger buffer up to a limit of zlog_limit. This makes sure that the stream length will remain bounded by the buffer size. This also adds assertions to make the programmer intent clear and catch this more easily in debug builds. Closes GH-16680.
1 parent f0f666b commit e643129

File tree

6 files changed

+94
-3
lines changed

6 files changed

+94
-3
lines changed

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ PHP NEWS
5454
- Filter:
5555
. Fixed bug GH-16523 (FILTER_FLAG_HOSTNAME accepts ending hyphen). (cmb)
5656

57+
- FPM:
58+
. Fixed bug GH-16628 (FPM logs are getting corrupted with this log
59+
statement). (nielsdos)
60+
5761
- GD:
5862
. Fixed bug GH-16334 (imageaffine overflow on matrix elements).
5963
(David Carlier)

ext/zend_test/test.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,17 @@ static ZEND_FUNCTION(zend_test_is_zend_ptr)
639639
RETURN_BOOL(is_zend_ptr((void*)addr));
640640
}
641641

642+
static ZEND_FUNCTION(zend_test_log_err_debug)
643+
{
644+
zend_string *str;
645+
646+
ZEND_PARSE_PARAMETERS_START(1, 1)
647+
Z_PARAM_STR(str);
648+
ZEND_PARSE_PARAMETERS_END();
649+
650+
php_log_err_with_severity(ZSTR_VAL(str), LOG_DEBUG);
651+
}
652+
642653
static zend_object *zend_test_class_new(zend_class_entry *class_type)
643654
{
644655
zend_object *obj = zend_objects_new(class_type);

ext/zend_test/test.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ function zend_test_set_fmode(bool $binary): void {}
196196
function zend_test_cast_fread($stream): void {}
197197

198198
function zend_test_is_zend_ptr(int $addr): bool {}
199+
200+
function zend_test_log_err_debug(string $str): void {}
199201
}
200202

201203
namespace ZendTestNS {

ext/zend_test/test_arginfo.h

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sapi/fpm/fpm/zlog.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ static inline void zlog_external(
150150
}
151151
/* }}} */
152152

153+
/* Returns the length if the print were complete, this can be larger than buf_size. */
153154
static size_t zlog_buf_prefix(
154155
const char *function, int line, int flags,
155156
char *buf, size_t buf_size, int use_syslog) /* {{{ */
@@ -186,6 +187,7 @@ static size_t zlog_buf_prefix(
186187
}
187188
}
188189

190+
/* Important: snprintf returns the number of bytes if the print were complete. */
189191
return len;
190192
}
191193
/* }}} */
@@ -408,6 +410,7 @@ static inline ssize_t zlog_stream_unbuffered_write(
408410
static inline ssize_t zlog_stream_buf_copy_cstr(
409411
struct zlog_stream *stream, const char *str, size_t str_len) /* {{{ */
410412
{
413+
ZEND_ASSERT(stream->len <= stream->buf.size);
411414
if (stream->buf.size - stream->len <= str_len &&
412415
!zlog_stream_buf_alloc_ex(stream, str_len + stream->len)) {
413416
return -1;
@@ -422,6 +425,7 @@ static inline ssize_t zlog_stream_buf_copy_cstr(
422425

423426
static inline ssize_t zlog_stream_buf_copy_char(struct zlog_stream *stream, char c) /* {{{ */
424427
{
428+
ZEND_ASSERT(stream->len <= stream->buf.size);
425429
if (stream->buf.size - stream->len < 1 && !zlog_stream_buf_alloc_ex(stream, 1)) {
426430
return -1;
427431
}
@@ -678,6 +682,17 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function,
678682
len = zlog_buf_prefix(
679683
function, line, stream->flags,
680684
stream->buf.data, stream->buf.size, stream->use_syslog);
685+
if (!EXPECTED(len + 1 <= stream->buf.size)) {
686+
/* If the buffer was not large enough, try with a larger buffer.
687+
* Note that this may still truncate if the zlog_limit is reached. */
688+
len = MIN(len + 1, zlog_limit);
689+
if (!zlog_stream_buf_alloc_ex(stream, len)) {
690+
return -1;
691+
}
692+
zlog_buf_prefix(
693+
function, line, stream->flags,
694+
stream->buf.data, stream->buf.size, stream->use_syslog);
695+
}
681696
stream->len = stream->prefix_len = len;
682697
if (stream->msg_prefix != NULL) {
683698
zlog_stream_buf_copy_cstr(stream, stream->msg_prefix, stream->msg_prefix_len);
@@ -689,8 +704,8 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function,
689704
} else {
690705
char sbuf[1024];
691706
ssize_t written;
692-
len = zlog_buf_prefix(function, line, stream->flags, sbuf, 1024, stream->use_syslog);
693-
written = zlog_stream_direct_write(stream, sbuf, len);
707+
len = zlog_buf_prefix(function, line, stream->flags, sbuf, sizeof(sbuf), stream->use_syslog);
708+
written = zlog_stream_direct_write(stream, sbuf, MIN(len, sizeof(sbuf)));
694709
if (stream->msg_prefix != NULL) {
695710
written += zlog_stream_direct_write(
696711
stream, stream->msg_prefix, stream->msg_prefix_len);

sapi/fpm/tests/gh16628.phpt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
--TEST--
2+
GH-16628 (FPM logs are getting corrupted with this log statement)
3+
--EXTENSIONS--
4+
zend_test
5+
--SKIPIF--
6+
<?php include "skipif.inc"; ?>
7+
--FILE--
8+
<?php
9+
10+
require_once "tester.inc";
11+
12+
$cfg = <<<EOT
13+
[global]
14+
error_log = {{FILE:LOG}}
15+
log_level = debug
16+
[unconfined]
17+
listen = {{ADDR}}
18+
pm = dynamic
19+
pm.max_children = 5
20+
pm.start_servers = 1
21+
pm.min_spare_servers = 1
22+
pm.max_spare_servers = 3
23+
catch_workers_output = yes
24+
decorate_workers_output = no
25+
EOT;
26+
27+
$code = <<<'EOT'
28+
<?php
29+
for ($i = 1; $i < 100; $i++) {
30+
zend_test_log_err_debug(str_repeat("a", $i));
31+
}
32+
EOT;
33+
34+
$tester = new FPM\Tester($cfg, $code);
35+
$tester->start();
36+
$tester->expectLogStartNotices();
37+
$tester->request()->expectEmptyBody();
38+
for ($i = 1; $i < 100; $i++) {
39+
$tester->expectLogNotice("%sPHP message: " . str_repeat("a", $i));
40+
}
41+
$tester->terminate();
42+
$tester->expectLogTerminatingNotices();
43+
$tester->close();
44+
45+
?>
46+
Done
47+
--EXPECT--
48+
Done
49+
--CLEAN--
50+
<?php
51+
require_once "tester.inc";
52+
FPM\Tester::clean();
53+
?>

0 commit comments

Comments
 (0)