Merge branch 'PHP-8.6'

* PHP-8.6:
  Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out
This commit is contained in:
David Carlier committed 2026-10-24 13:10:45 +01:00
commit 29b5e168bf
5 files changed
+115 -13

No files matched your search

+26 -13
View File
@@ -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,21 +5755,23 @@ 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;
+12
View File
@@ -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;
};
}
+12
View File
@@ -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;
};
}
+1
View File
@@ -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)
@@ -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