Skip to content

Commit 51892ba

Browse files
Handlers might be freed from OOM
1 parent cfe4d71 commit 51892ba

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

main/output.c

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
934934
return PHP_OUTPUT_HANDLER_FAILURE;
935935
}
936936

937+
bool still_have_handler = true;
937938
/* storable? */
938939
if (php_output_handler_append(handler, &context->in) && !context->op) {
939940
context->op = original_op;
@@ -971,7 +972,22 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
971972
"Returning a non-string result from user output handler %s is deprecated",
972973
ZSTR_VAL(handler->name)
973974
);
974-
handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
975+
// Check if the handler is still in the list of handlers to
976+
// determine if the PHP_OUTPUT_HANDLER_DISABLED flag can
977+
// be removed
978+
still_have_handler = false;
979+
int handler_count = php_output_get_level();
980+
if (handler_count) {
981+
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
982+
for (int iii = 0; iii < handler_count; ++iii) {
983+
php_output_handler *curr_handler = handlers[iii];
984+
if (curr_handler == handler) {
985+
handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
986+
still_have_handler = true;
987+
break;
988+
}
989+
}
990+
}
975991
}
976992
if (Z_TYPE(retval) == IS_FALSE) {
977993
/* call failed, pass internal buffer along */
@@ -1013,14 +1029,18 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
10131029
status = PHP_OUTPUT_HANDLER_FAILURE;
10141030
}
10151031
}
1016-
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1032+
if (still_have_handler) {
1033+
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1034+
}
10171035
OG(running) = NULL;
10181036
}
10191037

10201038
switch (status) {
10211039
case PHP_OUTPUT_HANDLER_FAILURE:
1022-
/* disable this handler */
1023-
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
1040+
if (still_have_handler) {
1041+
/* disable this handler */
1042+
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
1043+
}
10241044
/* discard any output */
10251045
if (context->out.data && context->out.free) {
10261046
efree(context->out.data);
@@ -1029,18 +1049,22 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
10291049
context->out.data = handler->buffer.data;
10301050
context->out.used = handler->buffer.used;
10311051
context->out.free = 1;
1032-
handler->buffer.data = NULL;
1033-
handler->buffer.used = 0;
1034-
handler->buffer.size = 0;
1052+
if (still_have_handler) {
1053+
handler->buffer.data = NULL;
1054+
handler->buffer.used = 0;
1055+
handler->buffer.size = 0;
1056+
}
10351057
break;
10361058
case PHP_OUTPUT_HANDLER_NO_DATA:
10371059
/* handler ate all */
10381060
php_output_context_reset(context);
10391061
ZEND_FALLTHROUGH;
10401062
case PHP_OUTPUT_HANDLER_SUCCESS:
1041-
/* no more buffered data */
1042-
handler->buffer.used = 0;
1043-
handler->flags |= PHP_OUTPUT_HANDLER_PROCESSED;
1063+
if (still_have_handler) {
1064+
/* no more buffered data */
1065+
handler->buffer.used = 0;
1066+
handler->flags |= PHP_OUTPUT_HANDLER_PROCESSED;
1067+
}
10441068
break;
10451069
}
10461070

@@ -1242,6 +1266,19 @@ static int php_output_stack_pop(int flags)
12421266
}
12431267
php_output_handler_op(orphan, &context);
12441268
}
1269+
// If it isn't still in the stack, cannot free it
1270+
bool still_have_handler = false;
1271+
int handler_count = php_output_get_level();
1272+
if (handler_count) {
1273+
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
1274+
for (int iii = 0; iii < handler_count; ++iii) {
1275+
php_output_handler *curr_handler = handlers[iii];
1276+
if (curr_handler == orphan) {
1277+
still_have_handler = true;
1278+
break;
1279+
}
1280+
}
1281+
}
12451282

12461283
/* pop it off the stack */
12471284
zend_stack_del_top(&OG(handlers));
@@ -1257,7 +1294,9 @@ static int php_output_stack_pop(int flags)
12571294
}
12581295

12591296
/* destroy the handler (after write!) */
1260-
php_output_handler_free(&orphan);
1297+
if (still_have_handler) {
1298+
php_output_handler_free(&orphan);
1299+
}
12611300
php_output_context_dtor(&context);
12621301

12631302
return 1;

tests/output/ob_start_callback_bad_return/exception_handler.phpt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ foreach ($cases as $case) {
3838
$log = [];
3939
echo "\n\nTesting: $case\n";
4040
ob_start($case);
41-
echo "Inside of $case";
41+
echo "Inside of $case\n";
4242
try {
43-
ob_end_flush();
43+
ob_end_flush();
4444
} catch (\ErrorException $e) {
4545
echo $e . "\n";
4646
}
@@ -58,17 +58,20 @@ Stack trace:
5858
#2 {main}
5959

6060
End of return_null, log was:
61-
return_null: <<<Inside of return_null>>>
61+
return_null: <<<Inside of return_null
62+
>>>
6263

6364
Testing: return_false
64-
Inside of return_falseErrorException: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s:%d
65+
Inside of return_false
66+
ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s:%d
6567
Stack trace:
6668
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, %d)
6769
#1 %s(%d): ob_end_flush()
6870
#2 {main}
6971

7072
End of return_false, log was:
71-
return_false: <<<Inside of return_false>>>
73+
return_false: <<<Inside of return_false
74+
>>>
7275

7376
Testing: return_true
7477
ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_true is deprecated in %s:%d
@@ -78,7 +81,8 @@ Stack trace:
7881
#2 {main}
7982

8083
End of return_true, log was:
81-
return_true: <<<Inside of return_true>>>
84+
return_true: <<<Inside of return_true
85+
>>>
8286

8387
Testing: return_zero
8488
0ErrorException: ob_end_flush(): Returning a non-string result from user output handler return_zero is deprecated in %s:%d
@@ -88,4 +92,5 @@ Stack trace:
8892
#2 {main}
8993

9094
End of return_zero, log was:
91-
return_zero: <<<Inside of return_zero>>>
95+
return_zero: <<<Inside of return_zero
96+
>>>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
ob_start(): Check behaviour with deprecation when OOM triggers handler removal
3+
--FILE--
4+
<?php
5+
6+
ob_start(function() {
7+
// We are out of memory, now trigger a deprecation
8+
return false;
9+
});
10+
11+
$a = [];
12+
// trigger OOM in a resize operation
13+
while (1) {
14+
$a[] = 1;
15+
}
16+
17+
?>
18+
--EXPECTF--
19+
Deprecated: main(): Returning a non-string result from user output handler Closure::__invoke is deprecated in %s on line %d
20+
21+
Fatal error: Allowed memory size of %d bytes exhausted at %s:%d (tried to allocate %d bytes) in %s on line %d

0 commit comments

Comments
 (0)