Skip to content

Commit a395b29

Browse files
committed
Merge branch 'PHP-8.5' into PHP-8.6
* PHP-8.5: Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out
2 parents 4d2be2e + 2cbd17d commit a395b29

5 files changed

Lines changed: 115 additions & 13 deletions

File tree

‎ext/ffi/ffi.c‎

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3677,6 +3677,11 @@ static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */
36773677
va_list va;
36783678
char *message = NULL;
36793679

3680+
if (FFI_G(parse_failed)) {
3681+
/* An error was already reported, parsing only continues to unwind cleanly */
3682+
return;
3683+
}
3684+
36803685
va_start(va, format);
36813686
zend_vspprintf(&message, 0, format, va);
36823687

@@ -5752,20 +5757,22 @@ ZEND_GET_MODULE(ffi)
57525757
/* parser callbacks */
57535758
void zend_ffi_parser_error(const char *format, ...) /* {{{ */
57545759
{
5755-
va_list va;
5756-
char *message = NULL;
5760+
if (!FFI_G(parse_failed)) {
5761+
va_list va;
5762+
char *message = NULL;
57575763

5758-
va_start(va, format);
5759-
zend_vspprintf(&message, 0, format, va);
5764+
va_start(va, format);
5765+
zend_vspprintf(&message, 0, format, va);
57605766

5761-
if (EG(current_execute_data)) {
5762-
zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
5763-
} else {
5764-
zend_error(E_WARNING, "FFI Parser: %s", message);
5765-
}
5767+
if (EG(current_execute_data)) {
5768+
zend_throw_exception(zend_ffi_parser_exception_ce, message, 0);
5769+
} else {
5770+
zend_error(E_WARNING, "FFI Parser: %s", message);
5771+
}
57665772

5767-
efree(message);
5768-
va_end(va);
5773+
efree(message);
5774+
va_end(va);
5775+
}
57695776

57705777
LONGJMP(FFI_G(bailout), FAILURE);
57715778
}
@@ -5851,10 +5858,16 @@ static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */
58515858
case ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_COMPLEX:
58525859
case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_COMPLEX:
58535860
case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_COMPLEX:
5854-
zend_ffi_parser_error("Unsupported type _Complex at line %d", FFI_G(line));
5861+
zend_ffi_throw_parser_error("Unsupported type _Complex at line %d", FFI_G(line));
5862+
/* Do not bail out here: callers may hold memory that is not owned by dcl yet.
5863+
* Continue with a placeholder type, zend_ffi_parse_*() report the failure. */
5864+
FFI_G(parse_failed) = 1;
5865+
dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
58555866
break;
58565867
default:
5857-
zend_ffi_parser_error("Unsupported type specifier combination at line %d", FFI_G(line));
5868+
zend_ffi_throw_parser_error("Unsupported type specifier combination at line %d", FFI_G(line));
5869+
FFI_G(parse_failed) = 1;
5870+
dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32;
58585871
break;
58595872
}
58605873
dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS;

‎ext/ffi/ffi.g‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,9 +886,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) {
886886
FFI_G(attribute_parsing) = 0;
887887
yy_buf = (unsigned char*)str;
888888
yy_end = yy_buf + len;
889+
FFI_G(parse_failed) = 0;
889890
parse();
891+
if (FFI_G(parse_failed)) {
892+
FFI_G(parse_failed) = 0;
893+
return FAILURE;
894+
}
890895
return SUCCESS;
891896
} else {
897+
FFI_G(parse_failed) = 0;
892898
return FAILURE;
893899
}
894900
}
@@ -902,13 +908,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl)
902908
yy_pos = yy_text = yy_buf = (unsigned char*)str;
903909
yy_end = yy_buf + len;
904910
yy_line = 1;
911+
FFI_G(parse_failed) = 0;
905912
sym = parse_type_name(get_sym(), dcl);
906913
if (sym != YY_EOF) {
907914
yy_error_sym("<EOF> expected, got", sym);
908915
}
909916
zend_ffi_validate_type_name(dcl);
917+
if (FFI_G(parse_failed)) {
918+
FFI_G(parse_failed) = 0;
919+
return FAILURE;
920+
}
910921
return SUCCESS;
911922
} else {
923+
FFI_G(parse_failed) = 0;
912924
return FAILURE;
913925
};
914926
}

‎ext/ffi/ffi_parser.c‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3621,9 +3621,15 @@ zend_result zend_ffi_parse_decl(const char *str, size_t len) {
36213621
FFI_G(attribute_parsing) = 0;
36223622
yy_buf = (unsigned char*)str;
36233623
yy_end = yy_buf + len;
3624+
FFI_G(parse_failed) = 0;
36243625
parse();
3626+
if (FFI_G(parse_failed)) {
3627+
FFI_G(parse_failed) = 0;
3628+
return FAILURE;
3629+
}
36253630
return SUCCESS;
36263631
} else {
3632+
FFI_G(parse_failed) = 0;
36273633
return FAILURE;
36283634
}
36293635
}
@@ -3637,13 +3643,19 @@ zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl)
36373643
yy_pos = yy_text = yy_buf = (unsigned char*)str;
36383644
yy_end = yy_buf + len;
36393645
yy_line = 1;
3646+
FFI_G(parse_failed) = 0;
36403647
sym = parse_type_name(get_sym(), dcl);
36413648
if (sym != YY_EOF) {
36423649
yy_error_sym("<EOF> expected, got", sym);
36433650
}
36443651
zend_ffi_validate_type_name(dcl);
3652+
if (FFI_G(parse_failed)) {
3653+
FFI_G(parse_failed) = 0;
3654+
return FAILURE;
3655+
}
36453656
return SUCCESS;
36463657
} else {
3658+
FFI_G(parse_failed) = 0;
36473659
return FAILURE;
36483660
};
36493661
}

‎ext/ffi/php_ffi.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ZEND_BEGIN_MODULE_GLOBALS(ffi)
5555
bool allow_vla;
5656
bool attribute_parsing;
5757
bool persistent;
58+
bool parse_failed;
5859
uint32_t default_type_attr;
5960
ZEND_END_MODULE_GLOBALS(ffi)
6061

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
--TEST--
2+
FFI: no memory leak when zend_ffi_finalize_type() bails out inside a declarator
3+
--EXTENSIONS--
4+
ffi
5+
--INI--
6+
ffi.enable=1
7+
--FILE--
8+
<?php
9+
10+
function leaks(string $code): bool {
11+
$n = 200;
12+
for ($i = 0; $i < 20; $i++) {
13+
try { FFI::cdef($code); } catch (FFI\ParserException) {}
14+
}
15+
$before = memory_get_usage();
16+
for ($i = 0; $i < $n; $i++) {
17+
try { FFI::cdef($code); } catch (FFI\ParserException) {}
18+
}
19+
/* A leaked block is at least 16 bytes, so this catches a leak per call */
20+
return memory_get_usage() - $before >= $n;
21+
}
22+
23+
$cases = [
24+
// zend_ffi_make_pointer_type()
25+
'double _Complex *x;',
26+
'double _Complex **x;',
27+
// zend_ffi_make_func_type()
28+
'double _Complex f(int a, int b);',
29+
// zend_ffi_add_arg(): first and later argument
30+
'void f(double _Complex x);',
31+
'void f(int a, double _Complex x);',
32+
// zend_ffi_nested_declaration()
33+
'double _Complex (x[3]);',
34+
'double _Complex (f(int a));',
35+
// parsing continues after the error: only the first one is reported
36+
'double _Complex x; int y;',
37+
'struct S { double _Complex a; int b; };',
38+
'int x; double _Complex y; double _Complex z;',
39+
];
40+
41+
foreach ($cases as $code) {
42+
try {
43+
FFI::cdef($code);
44+
echo "no error: $code\n";
45+
} catch (FFI\ParserException $e) {
46+
echo $e->getMessage(), "\n";
47+
}
48+
if (leaks($code)) {
49+
echo "leak: $code\n";
50+
}
51+
}
52+
53+
?>
54+
--EXPECT--
55+
Unsupported type _Complex at line 1
56+
Unsupported type _Complex at line 1
57+
Unsupported type _Complex at line 1
58+
Unsupported type _Complex at line 1
59+
Unsupported type _Complex at line 1
60+
Unsupported type _Complex at line 1
61+
Unsupported type _Complex at line 1
62+
Unsupported type _Complex at line 1
63+
Unsupported type _Complex at line 1
64+
Unsupported type _Complex at line 1

0 commit comments

Comments
 (0)