[php-src] master: Merge branch 'PHP-8.6'

From: Date: Sat, 24 Oct 2026 12:16:11 +0000
Subject: [php-src] master: Merge branch 'PHP-8.6'
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-142669@lists.php.net to get a copy of this message
Author: David Carlier (devnexen) Date: 2026-10-24T13:10:45+01:00 Commit: https://github.com/php/php-src/commit/29b5e168bfa71e94df2307dcd32be2a4f6353e9d Raw diff: https://github.com/php/php-src/commit/29b5e168bfa71e94df2307dcd32be2a4f6353e9d.diff Merge branch 'PHP-8.6' * PHP-8.6: Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out Changed paths: A ext/ffi/tests/finalize_type_bailout_leak.phpt M ext/ffi/ffi.c M ext/ffi/ffi.g M ext/ffi/ffi_parser.c M ext/ffi/php_ffi.h Diff: diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 3a3ef149a3f3..a4408628380b 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3675,6 +3675,11 @@ static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */ va_list va; char *message = NULL; + if (FFI_G(parse_failed)) { + /* An error was already reported, parsing only continues to unwind cleanly */ + return; + } + va_start(va, format); zend_vspprintf(&message, 0, format, va); @@ -5750,20 +5755,22 @@ ZEND_GET_MODULE(ffi) /* parser callbacks */ void zend_ffi_parser_error(const char *format, ...) /* {{{ */ { - va_list va; - char *message = NULL; + if (!FFI_G(parse_failed)) { + va_list va; + char *message = NULL; - va_start(va, format); - zend_vspprintf(&message, 0, format, va); + va_start(va, format); + zend_vspprintf(&message, 0, format, va); - if (EG(current_execute_data)) { - zend_throw_exception(zend_ffi_parser_exception_ce, message, 0); - } else { - zend_error(E_WARNING, "FFI Parser: %s", message); - } + if (EG(current_execute_data)) { + zend_throw_exception(zend_ffi_parser_exception_ce, message, 0); + } else { + zend_error(E_WARNING, "FFI Parser: %s", message); + } - efree(message); - va_end(va); + efree(message); + va_end(va); + } LONGJMP(FFI_G(bailout), FAILURE); } @@ -5849,10 +5856,16 @@ static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */ case ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_COMPLEX: case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_COMPLEX: case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_COMPLEX: - zend_ffi_parser_error("Unsupported type _Complex at line %d", FFI_G(line)); + zend_ffi_throw_parser_error("Unsupported type _Complex at line %d", FFI_G(line)); + /* Do not bail out here: callers may hold memory that is not owned by dcl yet. + * Continue with a placeholder type, zend_ffi_parse_*() report the failure. */ + FFI_G(parse_failed) = 1; + dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32; break; default: - zend_ffi_parser_error("Unsupported type specifier combination at line %d", FFI_G(line)); + zend_ffi_throw_parser_error("Unsupported type specifier combination at line %d", FFI_G(line)); + FFI_G(parse_failed) = 1; + dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32; break; } dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS; diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index 9ab8ee404edf..ceddcb409a64 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -886,9 +886,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) { FFI_G(attribute_parsing) = 0; yy_buf = (unsigned char*)str; yy_end = yy_buf + len; + FFI_G(parse_failed) = 0; parse(); + if (FFI_G(parse_failed)) { + FFI_G(parse_failed) = 0; + return FAILURE; + } return SUCCESS; } else { + FFI_G(parse_failed) = 0; return FAILURE; } } @@ -902,13 +908,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) yy_pos = yy_text = yy_buf = (unsigned char*)str; yy_end = yy_buf + len; yy_line = 1; + FFI_G(parse_failed) = 0; sym = parse_type_name(get_sym(), dcl); if (sym != YY_EOF) { yy_error_sym("<EOF> expected, got", sym); } zend_ffi_validate_type_name(dcl); + if (FFI_G(parse_failed)) { + FFI_G(parse_failed) = 0; + return FAILURE; + } return SUCCESS; } else { + FFI_G(parse_failed) = 0; return FAILURE; }; } diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index 27f1469133fe..0acf698b161a 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -3621,9 +3621,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) { FFI_G(attribute_parsing) = 0; yy_buf = (unsigned char*)str; yy_end = yy_buf + len; + FFI_G(parse_failed) = 0; parse(); + if (FFI_G(parse_failed)) { + FFI_G(parse_failed) = 0; + return FAILURE; + } return SUCCESS; } else { + FFI_G(parse_failed) = 0; return FAILURE; } } @@ -3637,13 +3643,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl) yy_pos = yy_text = yy_buf = (unsigned char*)str; yy_end = yy_buf + len; yy_line = 1; + FFI_G(parse_failed) = 0; sym = parse_type_name(get_sym(), dcl); if (sym != YY_EOF) { yy_error_sym("<EOF> expected, got", sym); } zend_ffi_validate_type_name(dcl); + if (FFI_G(parse_failed)) { + FFI_G(parse_failed) = 0; + return FAILURE; + } return SUCCESS; } else { + FFI_G(parse_failed) = 0; return FAILURE; }; } diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h index d56dc5a5552f..ef2513d8cda3 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -55,6 +55,7 @@ ZEND_BEGIN_MODULE_GLOBALS(ffi) bool allow_vla; bool attribute_parsing; bool persistent; + bool parse_failed; uint32_t default_type_attr; ZEND_END_MODULE_GLOBALS(ffi) diff --git a/ext/ffi/tests/finalize_type_bailout_leak.phpt b/ext/ffi/tests/finalize_type_bailout_leak.phpt new file mode 100644 index 000000000000..11b487806b09 --- /dev/null +++ b/ext/ffi/tests/finalize_type_bailout_leak.phpt @@ -0,0 +1,64 @@ +--TEST-- +FFI: no memory leak when zend_ffi_finalize_type() bails out inside a declarator +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- +<?php + +function leaks(string $code): bool { + $n = 200; + for ($i = 0; $i < 20; $i++) { + try { FFI::cdef($code); } catch (FFI\ParserException) {} + } + $before = memory_get_usage(); + for ($i = 0; $i < $n; $i++) { + try { FFI::cdef($code); } catch (FFI\ParserException) {} + } + /* A leaked block is at least 16 bytes, so this catches a leak per call */ + return memory_get_usage() - $before >= $n; +} + +$cases = [ + // zend_ffi_make_pointer_type() + 'double _Complex *x;', + 'double _Complex **x;', + // zend_ffi_make_func_type() + 'double _Complex f(int a, int b);', + // zend_ffi_add_arg(): first and later argument + 'void f(double _Complex x);', + 'void f(int a, double _Complex x);', + // zend_ffi_nested_declaration() + 'double _Complex (x[3]);', + 'double _Complex (f(int a));', + // parsing continues after the error: only the first one is reported + 'double _Complex x; int y;', + 'struct S { double _Complex a; int b; };', + 'int x; double _Complex y; double _Complex z;', +]; + +foreach ($cases as $code) { + try { + FFI::cdef($code); + echo "no error: $code\n"; + } catch (FFI\ParserException $e) { + echo $e->getMessage(), "\n"; + } + if (leaks($code)) { + echo "leak: $code\n"; + } +} + +?> +--EXPECT-- +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1 +Unsupported type _Complex at line 1

« previous php.cvs (#142669) next »