Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions main/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,12 @@ PHPAPI void php_output_deactivate(void)
/* release all output handlers */
if (OG(handlers).elements) {
while ((handler = zend_stack_top(&OG(handlers)))) {
php_output_handler_free(handler);
zend_stack_del_top(&OG(handlers));
/* It's possible to start a new output handler and mark it as active,
* however this loop will destroy all active handlers. */
OG(active) = NULL;
ZEND_ASSERT(OG(running) == NULL && "output is deactivated therefore running should stay NULL");
php_output_handler_free(handler);
}
}
zend_stack_destroy(&OG(handlers));
Expand Down Expand Up @@ -719,10 +723,11 @@ PHPAPI void php_output_handler_dtor(php_output_handler *handler)
* Destroy and free an output handler */
PHPAPI void php_output_handler_free(php_output_handler **h)
{
if (*h) {
php_output_handler_dtor(*h);
efree(*h);
php_output_handler *handler = *h;
if (handler) {
*h = NULL;
php_output_handler_dtor(handler);
efree(handler);
}
}
/* }}} */
Expand Down
24 changes: 24 additions & 0 deletions tests/output/gh20352.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
GH-20352 (UAF in php_output_handler_free via re-entrant ob_start() during error deactivation)
--FILE--
<?php
class Test {
public function __destruct() {
// Spray output stack
for ($i = 0; $i < 1000; $i++)
ob_start(static function() {});
}

public function __invoke($x) {
// Trigger php_output_deactivate() through forbidden operation
ob_start('foo');
return $x;
}
}

ob_start(new Test, 1);

echo "trigger bug";
?>
--EXPECTF--
Fatal error: ob_start(): Cannot use output buffering in output buffering display handlers in %s on line %d
Loading