From d2d20fd4d3543cd5080403f181ac0c5f4c58bf80 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 7 Jun 2026 10:25:33 -0400 Subject: [PATCH] Fix arg zval leak in extract_callback_user_func MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The callback ZVAL_STRINGL's the chunk into `arg` but only released it on the success path. Both early returns — when zend_fcall_info_init() fails and when zend_call_function() fails — returned without dtoring `arg`, leaking one zend_string per chunk. The init-failure path is reachable from userland by passing a non-callable as the extract callback. zend_fcall_info_argn() ZVAL_COPYs the argument into fci.params (its own ref), so the original `arg` always needs exactly one zval_ptr_dtor. Release it on every path; dtor `retval` only on success, where the engine has actually written it. --- mailparse.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mailparse.c b/mailparse.c index 371ec9b..2296623 100644 --- a/mailparse.c +++ b/mailparse.c @@ -1209,19 +1209,19 @@ static int extract_callback_user_func(php_mimepart *part, zval *userfunc, const if (zend_fcall_info_init(userfunc, 0, &fci, &fcc, NULL, NULL) == FAILURE) { zend_error(E_WARNING, "%s(): unable to call user function", get_active_function_name()); + zval_ptr_dtor(&arg); return 0; } zend_fcall_info_argn(&fci, 1, &arg); fci.retval = &retval; - if (zend_call_function(&fci, &fcc)) { - zend_fcall_info_args_clear(&fci, 1); + if (zend_call_function(&fci, &fcc) == SUCCESS) { + zval_ptr_dtor(&retval); + } else { zend_error(E_WARNING, "%s(): unable to call user function", get_active_function_name()); - return 0; } zend_fcall_info_args_clear(&fci, 1); - zval_ptr_dtor(&retval); zval_ptr_dtor(&arg); return 0;