From f4770bc6a9da79ec6cf5b2d204307fc6bebcb2b0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:52:40 +0100 Subject: [PATCH] Fix GH-20733: heap buffer overflow in optimizer Some (conditional) jump instructions can be the last one in the op_array, because they can jump to themselves. In those cases `i + 1` in the CFG builder can point to outside the op_array because `i` is already the last opline. To solve this we need to check against the end and prevent setting the successor out of bounds. --- Zend/Optimizer/zend_cfg.c | 17 +++++++++++++++-- ext/opcache/tests/opt/gh20733.phpt | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 ext/opcache/tests/opt/gh20733.phpt diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c index 05cb36dd34428..de54844b35c96 100644 --- a/Zend/Optimizer/zend_cfg.c +++ b/Zend/Optimizer/zend_cfg.c @@ -370,9 +370,14 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: + case ZEND_JMP_NULL: + BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); + if (i + 1 < op_array->last) { + BB_START(i + 1); + } + break; case ZEND_COALESCE: case ZEND_ASSERT_CHECK: - case ZEND_JMP_NULL: case ZEND_BIND_INIT_STATIC_OR_JMP: BB_START(OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes); BB_START(i + 1); @@ -524,9 +529,17 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array, case ZEND_JMPZ_EX: case ZEND_JMPNZ_EX: case ZEND_JMP_SET: + case ZEND_JMP_NULL: + block->successors_count = 2; + block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]; + if (j + 1 < blocks_count) { + block->successors[1] = j + 1; + } else { + block->successors[1] = j; /* last instruction and its own target */ + } + break; case ZEND_COALESCE: case ZEND_ASSERT_CHECK: - case ZEND_JMP_NULL: case ZEND_BIND_INIT_STATIC_OR_JMP: block->successors_count = 2; block->successors[0] = block_map[OP_JMP_ADDR(opline, opline->op2) - op_array->opcodes]; diff --git a/ext/opcache/tests/opt/gh20733.phpt b/ext/opcache/tests/opt/gh20733.phpt new file mode 100644 index 0000000000000..60e013d161d55 --- /dev/null +++ b/ext/opcache/tests/opt/gh20733.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-20733 (heap buffer overflow in optimizer) +--INI-- +opcache.jit=tracing +opcache.jit_buffer_size=16M +--EXTENSIONS-- +opcache +--FILE-- + 'php://temp','line-length' => $undef]; + $arr2 = []; + $and = $arr1 and $arr2; + + while ($and) { + } +} +echo "Done"; +?> +--EXPECT-- +Done