Skip to content

Commit 64f2d11

Browse files
committed
Merge branch 'PHP-8.4'
* PHP-8.4: Fix GH-16628: FPM logs are getting corrupted with this log statement Fix GH-16601: Memory leak in Reflection constructors
2 parents 5d7fe13 + bfd9e0c commit 64f2d11

10 files changed

+217
-13
lines changed

ext/reflection/php_reflection.c

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -225,18 +225,26 @@ static void _free_function(zend_function *fptr) /* {{{ */
225225
}
226226
/* }}} */
227227

228+
static void reflection_free_property_reference(property_reference *reference)
229+
{
230+
zend_string_release_ex(reference->unmangled_name, 0);
231+
efree(reference);
232+
}
233+
234+
static void reflection_free_parameter_reference(parameter_reference *reference)
235+
{
236+
_free_function(reference->fptr);
237+
efree(reference);
238+
}
239+
228240
static void reflection_free_objects_storage(zend_object *object) /* {{{ */
229241
{
230242
reflection_object *intern = reflection_object_from_obj(object);
231-
parameter_reference *reference;
232-
property_reference *prop_reference;
233243

234244
if (intern->ptr) {
235245
switch (intern->ref_type) {
236246
case REF_TYPE_PARAMETER:
237-
reference = (parameter_reference*)intern->ptr;
238-
_free_function(reference->fptr);
239-
efree(intern->ptr);
247+
reflection_free_parameter_reference(intern->ptr);
240248
break;
241249
case REF_TYPE_TYPE:
242250
{
@@ -251,9 +259,7 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */
251259
_free_function(intern->ptr);
252260
break;
253261
case REF_TYPE_PROPERTY:
254-
prop_reference = (property_reference*)intern->ptr;
255-
zend_string_release_ex(prop_reference->unmangled_name, 0);
256-
efree(intern->ptr);
262+
reflection_free_property_reference(intern->ptr);
257263
break;
258264
case REF_TYPE_ATTRIBUTE: {
259265
attribute_reference *attr_ref = intern->ptr;
@@ -2529,6 +2535,10 @@ ZEND_METHOD(ReflectionParameter, __construct)
25292535
}
25302536
}
25312537

2538+
if (intern->ptr) {
2539+
reflection_free_parameter_reference(intern->ptr);
2540+
}
2541+
25322542
ref = (parameter_reference*) emalloc(sizeof(parameter_reference));
25332543
ref->arg_info = &arg_info[position];
25342544
ref->offset = (uint32_t)position;
@@ -2538,11 +2548,15 @@ ZEND_METHOD(ReflectionParameter, __construct)
25382548
intern->ptr = ref;
25392549
intern->ref_type = REF_TYPE_PARAMETER;
25402550
intern->ce = ce;
2551+
zval_ptr_dtor(&intern->obj);
25412552
if (reference && is_closure) {
25422553
ZVAL_COPY_VALUE(&intern->obj, reference);
2554+
} else {
2555+
ZVAL_UNDEF(&intern->obj);
25432556
}
25442557

25452558
prop_name = reflection_prop_name(object);
2559+
zval_ptr_dtor(prop_name);
25462560
if (has_internal_arg_info(fptr)) {
25472561
ZVAL_STRING(prop_name, ((zend_internal_arg_info*)arg_info)[position].name);
25482562
} else {
@@ -3974,10 +3988,12 @@ static void reflection_class_object_ctor(INTERNAL_FUNCTION_PARAMETERS, int is_ob
39743988
object = ZEND_THIS;
39753989
intern = Z_REFLECTION_P(object);
39763990

3991+
/* Note: class entry name is interned, no need to destroy them */
39773992
if (arg_obj) {
39783993
ZVAL_STR_COPY(reflection_prop_name(object), arg_obj->ce->name);
39793994
intern->ptr = arg_obj->ce;
39803995
if (is_object) {
3996+
zval_ptr_dtor(&intern->obj);
39813997
ZVAL_OBJ_COPY(&intern->obj, arg_obj);
39823998
}
39833999
} else {
@@ -5606,13 +5622,20 @@ ZEND_METHOD(ReflectionProperty, __construct)
56065622
}
56075623
}
56085624

5609-
ZVAL_STR_COPY(reflection_prop_name(object), name);
5625+
zval *prop_name = reflection_prop_name(object);
5626+
zval_ptr_dtor(prop_name);
5627+
ZVAL_STR_COPY(prop_name, name);
5628+
/* Note: class name are always interned, no need to destroy them */
56105629
if (dynam_prop == 0) {
56115630
ZVAL_STR_COPY(reflection_prop_class(object), property_info->ce->name);
56125631
} else {
56135632
ZVAL_STR_COPY(reflection_prop_class(object), ce->name);
56145633
}
56155634

5635+
if (intern->ptr) {
5636+
reflection_free_property_reference(intern->ptr);
5637+
}
5638+
56165639
reference = (property_reference*) emalloc(sizeof(property_reference));
56175640
reference->prop = dynam_prop ? NULL : property_info;
56185641
reference->unmangled_name = zend_string_copy(name);
@@ -6434,7 +6457,9 @@ ZEND_METHOD(ReflectionExtension, __construct)
64346457
RETURN_THROWS();
64356458
}
64366459
free_alloca(lcname, use_heap);
6437-
ZVAL_STRING(reflection_prop_name(object), module->name);
6460+
zval *prop_name = reflection_prop_name(object);
6461+
zval_ptr_dtor(prop_name);
6462+
ZVAL_STRING(prop_name, module->name);
64386463
intern->ptr = module;
64396464
intern->ref_type = REF_TYPE_OTHER;
64406465
intern->ce = NULL;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
ReflectionExtension double construct call
3+
--FILE--
4+
<?php
5+
6+
$r = new ReflectionExtension('standard');
7+
var_dump($r);
8+
$r->__construct('standard');
9+
var_dump($r);
10+
11+
?>
12+
--EXPECT--
13+
object(ReflectionExtension)#1 (1) {
14+
["name"]=>
15+
string(8) "standard"
16+
}
17+
object(ReflectionExtension)#1 (1) {
18+
["name"]=>
19+
string(8) "standard"
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
ReflectionObject double construct call
3+
--FILE--
4+
<?php
5+
6+
$obj = new stdClass;
7+
$r = new ReflectionObject($obj);
8+
var_dump($r);
9+
$r->__construct($obj);
10+
var_dump($r);
11+
12+
?>
13+
--EXPECT--
14+
object(ReflectionObject)#2 (1) {
15+
["name"]=>
16+
string(8) "stdClass"
17+
}
18+
object(ReflectionObject)#2 (1) {
19+
["name"]=>
20+
string(8) "stdClass"
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
ReflectionParameter double construct call
3+
--FILE--
4+
<?php
5+
6+
$closure = function (int $x): void {};
7+
$r = new ReflectionParameter($closure, 'x');
8+
var_dump($r);
9+
$r->__construct($closure, 'x');
10+
var_dump($r);
11+
$r->__construct('ord', 'character');
12+
var_dump($r);
13+
14+
?>
15+
--EXPECT--
16+
object(ReflectionParameter)#2 (1) {
17+
["name"]=>
18+
string(1) "x"
19+
}
20+
object(ReflectionParameter)#2 (1) {
21+
["name"]=>
22+
string(1) "x"
23+
}
24+
object(ReflectionParameter)#2 (1) {
25+
["name"]=>
26+
string(9) "character"
27+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
ReflectionProperty double construct call
3+
--FILE--
4+
<?php
5+
6+
$r = new ReflectionProperty(Exception::class, 'message');
7+
var_dump($r);
8+
$r->__construct(Exception::class, 'message');
9+
var_dump($r);
10+
11+
?>
12+
--EXPECT--
13+
object(ReflectionProperty)#1 (2) {
14+
["name"]=>
15+
string(7) "message"
16+
["class"]=>
17+
string(9) "Exception"
18+
}
19+
object(ReflectionProperty)#1 (2) {
20+
["name"]=>
21+
string(7) "message"
22+
["class"]=>
23+
string(9) "Exception"
24+
}

ext/zend_test/test.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,17 @@ static ZEND_FUNCTION(zend_test_is_zend_ptr)
824824
RETURN_BOOL(is_zend_ptr((void*)addr));
825825
}
826826

827+
static ZEND_FUNCTION(zend_test_log_err_debug)
828+
{
829+
zend_string *str;
830+
831+
ZEND_PARSE_PARAMETERS_START(1, 1)
832+
Z_PARAM_STR(str);
833+
ZEND_PARSE_PARAMETERS_END();
834+
835+
php_log_err_with_severity(ZSTR_VAL(str), LOG_DEBUG);
836+
}
837+
827838
static zend_object *zend_test_class_new(zend_class_entry *class_type)
828839
{
829840
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
@@ -307,6 +307,8 @@ function zend_test_set_fmode(bool $binary): void {}
307307
function zend_test_cast_fread($stream): void {}
308308

309309
function zend_test_is_zend_ptr(int $addr): bool {}
310+
311+
function zend_test_log_err_debug(string $str): void {}
310312
}
311313

312314
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
@@ -153,6 +153,7 @@ static inline void zlog_external(
153153
}
154154
/* }}} */
155155

156+
/* Returns the length if the print were complete, this can be larger than buf_size. */
156157
static size_t zlog_buf_prefix(
157158
const char *function, int line, int flags,
158159
char *buf, size_t buf_size, int use_syslog) /* {{{ */
@@ -189,6 +190,7 @@ static size_t zlog_buf_prefix(
189190
}
190191
}
191192

193+
/* Important: snprintf returns the number of bytes if the print were complete. */
192194
return len;
193195
}
194196
/* }}} */
@@ -411,6 +413,7 @@ static inline ssize_t zlog_stream_unbuffered_write(
411413
static inline ssize_t zlog_stream_buf_copy_cstr(
412414
struct zlog_stream *stream, const char *str, size_t str_len) /* {{{ */
413415
{
416+
ZEND_ASSERT(stream->len <= stream->buf.size);
414417
if (stream->buf.size - stream->len <= str_len &&
415418
!zlog_stream_buf_alloc_ex(stream, str_len + stream->len)) {
416419
return -1;
@@ -425,6 +428,7 @@ static inline ssize_t zlog_stream_buf_copy_cstr(
425428

426429
static inline ssize_t zlog_stream_buf_copy_char(struct zlog_stream *stream, char c) /* {{{ */
427430
{
431+
ZEND_ASSERT(stream->len <= stream->buf.size);
428432
if (stream->buf.size - stream->len < 1 && !zlog_stream_buf_alloc_ex(stream, 1)) {
429433
return -1;
430434
}
@@ -681,6 +685,17 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function,
681685
len = zlog_buf_prefix(
682686
function, line, stream->flags,
683687
stream->buf.data, stream->buf.size, stream->use_syslog);
688+
if (!EXPECTED(len + 1 <= stream->buf.size)) {
689+
/* If the buffer was not large enough, try with a larger buffer.
690+
* Note that this may still truncate if the zlog_limit is reached. */
691+
len = MIN(len + 1, zlog_limit);
692+
if (!zlog_stream_buf_alloc_ex(stream, len)) {
693+
return -1;
694+
}
695+
zlog_buf_prefix(
696+
function, line, stream->flags,
697+
stream->buf.data, stream->buf.size, stream->use_syslog);
698+
}
684699
stream->len = stream->prefix_len = len;
685700
if (stream->msg_prefix != NULL) {
686701
zlog_stream_buf_copy_cstr(stream, stream->msg_prefix, stream->msg_prefix_len);
@@ -692,8 +707,8 @@ ssize_t zlog_stream_prefix_ex(struct zlog_stream *stream, const char *function,
692707
} else {
693708
char sbuf[1024];
694709
ssize_t written;
695-
len = zlog_buf_prefix(function, line, stream->flags, sbuf, 1024, stream->use_syslog);
696-
written = zlog_stream_direct_write(stream, sbuf, len);
710+
len = zlog_buf_prefix(function, line, stream->flags, sbuf, sizeof(sbuf), stream->use_syslog);
711+
written = zlog_stream_direct_write(stream, sbuf, MIN(len, sizeof(sbuf)));
697712
if (stream->msg_prefix != NULL) {
698713
written += zlog_stream_direct_write(
699714
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)