From 19920da468ec22374dce9fa64c1ac8d81f787831 Mon Sep 17 00:00:00 2001 From: Akira Date: Fri, 23 Oct 2026 08:59:40 +0000 Subject: [PATCH 1/2] Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out zend_ffi_finalize_type() reports unsupported specifier combinations (e.g. _Complex) through zend_ffi_parser_error(), which always ends in LONGJMP(). Four parser callbacks called it while they already owned memory that is not reachable from dcl->type, so every failed FFI::cdef(), FFI::new() or FFI::type() leaked it: - zend_ffi_make_pointer_type(): the new pointer type - zend_ffi_make_func_type(): args and nested_dcl - zend_ffi_add_arg(): *args (including the arguments collected so far) - zend_ffi_nested_declaration(): nested_dcl Add zend_ffi_try_finalize_type(), which throws the parser exception and returns FAILURE instead of bailing out, and keep zend_ffi_finalize_type() as a thin wrapper that bails out so that the other callers are unchanged. The four callbacks above release what they own before bailing out, using the same calls as the later error paths of the same functions. zend_ffi_make_pointer_type() now allocates after the checks, like zend_ffi_make_array_type() already does. Closes GH-24582 Co-Authored-By: Claude Sonnet 5.5 --- ext/ffi/ffi.c | 55 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 79783441c1db..253249a2d06d 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -5771,7 +5771,7 @@ void zend_ffi_parser_error(const char *format, ...) /* {{{ */ } /* }}} */ -static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */ +static zend_result zend_ffi_try_finalize_type(zend_ffi_dcl *dcl) /* {{{ */ { if (!dcl->type) { switch (dcl->flags & ZEND_FFI_DCL_TYPE_SPECIFIERS) { @@ -5851,15 +5851,24 @@ 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)); - break; + zend_ffi_throw_parser_error("Unsupported type _Complex at line %d", FFI_G(line)); + return FAILURE; default: - zend_ffi_parser_error("Unsupported type specifier combination at line %d", FFI_G(line)); - break; + zend_ffi_throw_parser_error("Unsupported type specifier combination at line %d", FFI_G(line)); + return FAILURE; } dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS; dcl->flags |= ZEND_FFI_DCL_TYPEDEF_NAME; } + return SUCCESS; +} +/* }}} */ + +static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */ +{ + if (zend_ffi_try_finalize_type(dcl) == FAILURE) { + LONGJMP(FFI_G(bailout), FAILURE); + } } /* }}} */ @@ -6359,16 +6368,19 @@ void zend_ffi_adjust_struct_size(zend_ffi_dcl *dcl) /* {{{ */ void zend_ffi_make_pointer_type(zend_ffi_dcl *dcl) /* {{{ */ { - zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); - type->kind = ZEND_FFI_TYPE_POINTER; - type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_POINTER_ATTRS); - type->size = sizeof(void*); - type->align = _Alignof(void*); + zend_ffi_type *type; + zend_ffi_finalize_type(dcl); if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) == FAILURE) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } + + type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); + type->kind = ZEND_FFI_TYPE_POINTER; + type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_POINTER_ATTRS); + type->size = sizeof(void*); + type->align = _Alignof(void*); type->pointer.type = dcl->type; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); dcl->flags &= ~ZEND_FFI_DCL_TYPE_QUALIFIERS; @@ -6455,7 +6467,14 @@ void zend_ffi_make_func_type(zend_ffi_dcl *dcl, HashTable *args, zend_ffi_dcl *n zend_ffi_type *type; zend_ffi_type *ret_type; - zend_ffi_finalize_type(dcl); + if (zend_ffi_try_finalize_type(dcl) == FAILURE) { + zend_ffi_cleanup_dcl(nested_dcl); + if (args) { + zend_hash_destroy(args); + pefree(args, FFI_G(persistent)); + } + LONGJMP(FFI_G(bailout), FAILURE); + } ret_type = ZEND_FFI_TYPE(dcl->type); if (args) { @@ -6592,11 +6611,18 @@ void zend_ffi_add_arg(HashTable **args, const char *name, size_t name_len, zend_ { zend_ffi_type *type; + if (zend_ffi_try_finalize_type(arg_dcl) == FAILURE) { + if (*args) { + zend_hash_destroy(*args); + pefree(*args, FFI_G(persistent)); + *args = NULL; + } + LONGJMP(FFI_G(bailout), FAILURE); + } if (!*args) { *args = pemalloc(sizeof(HashTable), FFI_G(persistent)); zend_hash_init(*args, 0, NULL, zend_ffi_type_hash_dtor, FFI_G(persistent)); } - zend_ffi_finalize_type(arg_dcl); type = ZEND_FFI_TYPE(arg_dcl->type); if (type->kind == ZEND_FFI_TYPE_ARRAY) { if (ZEND_FFI_TYPE_IS_OWNED(arg_dcl->type)) { @@ -7092,7 +7118,10 @@ static zend_result zend_ffi_nested_type(zend_ffi_type *type, zend_ffi_type *nest void zend_ffi_nested_declaration(zend_ffi_dcl *dcl, zend_ffi_dcl *nested_dcl) /* {{{ */ { /* "char" is used as a terminator of nested declaration */ - zend_ffi_finalize_type(dcl); + if (zend_ffi_try_finalize_type(dcl) == FAILURE) { + zend_ffi_cleanup_dcl(nested_dcl); + LONGJMP(FFI_G(bailout), FAILURE); + } if (!nested_dcl->type || nested_dcl->type == &zend_ffi_type_char) { nested_dcl->type = dcl->type; } else { From 6c1be7f84d131320497eb4d97f066d5908fcc3a4 Mon Sep 17 00:00:00 2001 From: Akira Date: Fri, 23 Oct 2026 09:02:25 +0000 Subject: [PATCH 2/2] Fix GH-24582: Memory leaks in ext/ffi parser when finalize_type() bails out #2 Add ext/ffi/tests/finalize_type_bailout_leak.phpt for test. --- ext/ffi/tests/finalize_type_bailout_leak.phpt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 ext/ffi/tests/finalize_type_bailout_leak.phpt 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..e1df29605b45 --- /dev/null +++ b/ext/ffi/tests/finalize_type_bailout_leak.phpt @@ -0,0 +1,57 @@ +--TEST-- +FFI: no memory leak when zend_ffi_finalize_type() bails out inside a declarator +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- += $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));', +]; + +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