From 8712f4bf19d388e9e73d6c1c80e691f35df36d13 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 27 Jun 2025 23:36:33 +0200 Subject: [PATCH] Fix OSS-Fuzz #427814452 Pipe compilation uses a temporary znode with QM_ASSIGN to remove references. Assert compilation wants to look at the operand AST and convert it to a string. However the original AST is lost due to the temporary znode. To solve this we either have to handle this specially in pipe compilation [1], or store the AST anyway somehow. Special casing this either way is not worth the complexity in my opinion, especially as it looks like a dynamic call anyway due to the FCC syntax. [1] Prototype (incomplete) at https://gist.github.com/nielsdos/50dc71718639c3af05db84a4dea6eb71 shows this is not worthwhile in my opinion. Closes GH-18965. Co-authored-by: Ilija Tovilo --- NEWS | 3 +++ Zend/tests/pipe_operator/gh18965.phpt | 12 +++++++++ .../pipe_operator/oss_fuzz_427814452.phpt | 26 +++++++++++++++++++ Zend/zend_compile.c | 17 +++++++++++- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/pipe_operator/gh18965.phpt create mode 100644 Zend/tests/pipe_operator/oss_fuzz_427814452.phpt diff --git a/NEWS b/NEWS index a9ccb65c10b..97144036d35 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,9 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| ?? ??? ????, PHP 8.5.0alpha2 +- Core: + . Fix OSS-Fuzz #427814452 (pipe compilation fails with assert). (nielsdos) + - DOM: . Make cloning DOM node lists, maps, and collections fail. (nielsdos) diff --git a/Zend/tests/pipe_operator/gh18965.phpt b/Zend/tests/pipe_operator/gh18965.phpt new file mode 100644 index 00000000000..8f9eaaebb08 --- /dev/null +++ b/Zend/tests/pipe_operator/gh18965.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-18965: ASSERT_CHECK avoids pipe lhs free +--INI-- +zend.assertions=0 +--FILE-- + assert(...); +echo "No leak\n"; +?> +--EXPECT-- +No leak diff --git a/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt b/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt new file mode 100644 index 00000000000..2ecfbab6348 --- /dev/null +++ b/Zend/tests/pipe_operator/oss_fuzz_427814452.phpt @@ -0,0 +1,26 @@ +--TEST-- +OSS-Fuzz #427814452 +--FILE-- + assert(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} +try { + 0 |> "assert"(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} +try { + false |> ("a"."ssert")(...); +} catch (\AssertionError $e) { + echo $e::class, ": '", $e->getMessage(), "'\n"; +} + +?> +--EXPECT-- +AssertionError: '' +AssertionError: '' +AssertionError: '' diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f3f6d1b75ae..ef167efc697 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6426,6 +6426,20 @@ static bool can_match_use_jumptable(zend_ast_list *arms) { return 1; } +static bool zend_is_pipe_optimizable_callable_name(zend_ast *ast) +{ + if (ast->kind == ZEND_AST_ZVAL && Z_TYPE_P(zend_ast_get_zval(ast)) == IS_STRING) { + /* Assert compilation adds a message operand, but this is incompatible with the + * pipe optimization that uses a temporary znode for the reference elimination. + * Therefore, disable the optimization for assert. + * Note that "assert" as a name is always treated as fully qualified. */ + zend_string *str = zend_ast_get_str(ast); + return !zend_string_equals_literal_ci(str, "assert"); + } + + return true; +} + static void zend_compile_pipe(znode *result, zend_ast *ast) { zend_ast *operand_ast = ast->child[0]; @@ -6453,7 +6467,8 @@ static void zend_compile_pipe(znode *result, zend_ast *ast) /* Turn $foo |> bar(...) into bar($foo). */ if (callable_ast->kind == ZEND_AST_CALL - && callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT) { + && callable_ast->child[1]->kind == ZEND_AST_CALLABLE_CONVERT + && zend_is_pipe_optimizable_callable_name(callable_ast->child[0])) { fcall_ast = zend_ast_create(ZEND_AST_CALL, callable_ast->child[0], arg_list_ast); /* Turn $foo |> bar::baz(...) into bar::baz($foo). */