From c2c1933451cf4edf4c7836e603c7a47b8acc4295 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 24 Oct 2026 12:57:20 +0100 Subject: [PATCH 1/2] ext/ffi: Double free of anonymous types shared by a declarator list. Each declarator copied the specifiers along with the anonymous struct, union or enum type they owned, so every symbol or field freed it. Declarators now borrow the type and the first one kept takes ownership; if none is kept, it is freed with the specifiers. Field lists regressed in 52c91f0fb70. --- ext/ffi/ffi.c | 48 ++++++++++++++++++- ext/ffi/ffi.g | 24 +++++----- ext/ffi/ffi_parser.c | 26 +++++----- ext/ffi/php_ffi.h | 5 +- .../decl_list_shared_anonymous_type.phpt | 46 ++++++++++++++++++ 5 files changed, 122 insertions(+), 27 deletions(-) create mode 100644 ext/ffi/tests/decl_list_shared_anonymous_type.phpt diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 73e738202c8d..9f442a77d05e 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -3639,6 +3639,46 @@ void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl) /* {{{ */ } /* }}} */ +void zend_ffi_borrow_dcl(zend_ffi_dcl *dcl, const zend_ffi_dcl *common_dcl) /* {{{ */ +{ + *dcl = *common_dcl; + dcl->type = ZEND_FFI_TYPE(common_dcl->type); +} +/* }}} */ + +static void zend_ffi_adopt_type(zend_ffi_dcl *dcl, zend_ffi_dcl *common_dcl) /* {{{ */ +{ + zend_ffi_type *common_type = ZEND_FFI_TYPE(common_dcl->type); + zend_ffi_type **ref = &dcl->type; + + if (!ZEND_FFI_TYPE_IS_OWNED(common_dcl->type)) { + return; + } + while (ZEND_FFI_TYPE(*ref) != common_type) { + zend_ffi_type *type = ZEND_FFI_TYPE(*ref); + + if (!ZEND_FFI_TYPE_IS_OWNED(*ref)) { + return; + } + switch (type->kind) { + case ZEND_FFI_TYPE_POINTER: + ref = &type->pointer.type; + break; + case ZEND_FFI_TYPE_ARRAY: + ref = &type->array.type; + break; + case ZEND_FFI_TYPE_FUNC: + ref = &type->func.ret_type; + break; + default: + return; + } + } + *ref = ZEND_FFI_TYPE_MAKE_OWNED(common_type); + common_dcl->type = common_type; +} +/* }}} */ + static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */ { va_list va; @@ -6141,7 +6181,7 @@ static zend_result zend_ffi_validate_field_type(zend_ffi_type *type, zend_ffi_ty } /* }}} */ -void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl) /* {{{ */ +void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl, zend_ffi_dcl *common_dcl) /* {{{ */ { zend_ffi_field *field; zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type); @@ -6149,6 +6189,7 @@ void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_ ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT); zend_ffi_finalize_type(field_dcl); + zend_ffi_adopt_type(field_dcl, common_dcl); field_type = ZEND_FFI_TYPE(field_dcl->type); if (zend_ffi_validate_field_type(field_type, struct_type) == FAILURE) { zend_ffi_cleanup_dcl(field_dcl); @@ -6644,7 +6685,7 @@ void zend_ffi_add_arg(HashTable **args, const char *name, size_t name_len, zend_ } /* }}} */ -void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* {{{ */ +void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl, zend_ffi_dcl *common_dcl) /* {{{ */ { zend_ffi_symbol *sym; @@ -6686,6 +6727,7 @@ void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* { zend_ffi_parser_error("Redeclaration of \"%.*s\" at line %d", name_len, name, FFI_G(line)); } else { if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF) { + zend_ffi_adopt_type(dcl, common_dcl); if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) == FAILURE) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); @@ -6713,11 +6755,13 @@ void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* { type = ZEND_FFI_TYPE(dcl->type); if (zend_ffi_validate_type(type, (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN, 1) == FAILURE) { + zend_ffi_adopt_type(dcl, common_dcl); zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == 0 || (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN) { + zend_ffi_adopt_type(dcl, common_dcl); sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent)); sym->kind = (type->kind == ZEND_FFI_TYPE_FUNC) ? ZEND_FFI_SYM_FUNC : ZEND_FFI_SYM_VAR; sym->type = dcl->type; diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index 8102e8f73a87..bed38ef00842 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -77,7 +77,7 @@ declarations: {const char *name;} {size_t name_len;} {zend_ffi_dcl dcl;} - {dcl = common_dcl;} + {zend_ffi_borrow_dcl(&dcl, &common_dcl);} declarator(&dcl, &name, &name_len) ( {zend_ffi_val asm_str;} @@ -89,14 +89,15 @@ declarations: )? attributes(&dcl)? initializer? - {zend_ffi_declare(name, name_len, &dcl);} + {zend_ffi_declare(name, name_len, &dcl, &common_dcl);} ( "," - {dcl = common_dcl;} + {zend_ffi_borrow_dcl(&dcl, &common_dcl);} declarator(&dcl, &name, &name_len) attributes(&dcl)? initializer? - {zend_ffi_declare(name, name_len, &dcl);} + {zend_ffi_declare(name, name_len, &dcl, &common_dcl);} )* + {zend_ffi_cleanup_dcl(&common_dcl);} | /* empty */ {if (common_dcl.flags & (ZEND_FFI_DCL_ENUM | ZEND_FFI_DCL_STRUCT | ZEND_FFI_DCL_UNION)) zend_ffi_cleanup_dcl(&common_dcl);} @@ -264,21 +265,22 @@ struct_contents(zend_ffi_dcl *dcl): struct_declaration(zend_ffi_dcl *struct_dcl): {zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT;} - {zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT;} + {zend_ffi_dcl field_dcl;} specifier_qualifier_list(&common_field_dcl) - {base_field_dcl = common_field_dcl;} ( /* empty */ {zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl);} - | struct_declarator(struct_dcl, &common_field_dcl) + | {zend_ffi_borrow_dcl(&field_dcl, &common_field_dcl);} + struct_declarator(struct_dcl, &field_dcl, &common_field_dcl) ( "," - {zend_ffi_dcl field_dcl = base_field_dcl;} + {zend_ffi_borrow_dcl(&field_dcl, &common_field_dcl);} attributes(&field_dcl)? - struct_declarator(struct_dcl, &field_dcl) + struct_declarator(struct_dcl, &field_dcl, &common_field_dcl) )* + {zend_ffi_cleanup_dcl(&common_field_dcl);} ) ; -struct_declarator(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl): +struct_declarator(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl, zend_ffi_dcl *common_field_dcl): {const char *name = NULL;} {size_t name_len = 0;} {zend_ffi_val bits;} @@ -289,7 +291,7 @@ struct_declarator(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl): {zend_ffi_add_bit_field(struct_dcl, name, name_len, field_dcl, &bits);} | /*empty */ attributes(field_dcl)? - {zend_ffi_add_field(struct_dcl, name, name_len, field_dcl);} + {zend_ffi_add_field(struct_dcl, name, name_len, field_dcl, common_field_dcl);} ) | ":" constant_expression(&bits) diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index bda13d323eda..7084faa34aa8 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -321,7 +321,7 @@ static int parse_type_specifier(int sym, zend_ffi_dcl *dcl); static int parse_struct_or_union_specifier(int sym, zend_ffi_dcl *dcl); static int parse_struct_contents(int sym, zend_ffi_dcl *dcl); static int parse_struct_declaration(int sym, zend_ffi_dcl *struct_dcl); -static int parse_struct_declarator(int sym, zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl); +static int parse_struct_declarator(int sym, zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl, zend_ffi_dcl *common_field_dcl); static int parse_enum_specifier(int sym, zend_ffi_dcl *dcl); static int parse_enumerator_list(int sym, zend_ffi_dcl *enum_dcl); static int parse_enumerator(int sym, zend_ffi_dcl *enum_dcl, int64_t *min, int64_t *max, int64_t *last); @@ -2079,7 +2079,7 @@ static int parse_declarations(int sym) { const char *name; size_t name_len; zend_ffi_dcl dcl; - dcl = common_dcl; + zend_ffi_borrow_dcl(&dcl, &common_dcl); sym = parse_declarator(sym, &dcl, &name, &name_len); if (sym == YY___ASM__) { zend_ffi_val asm_str; @@ -2102,10 +2102,10 @@ static int parse_declarations(int sym) { if (sym == YY__EQUAL) { sym = parse_initializer(sym); } - zend_ffi_declare(name, name_len, &dcl); + zend_ffi_declare(name, name_len, &dcl, &common_dcl); while (sym == YY__COMMA) { sym = get_sym(); - dcl = common_dcl; + zend_ffi_borrow_dcl(&dcl, &common_dcl); sym = parse_declarator(sym, &dcl, &name, &name_len); if (YY_IN_SET(sym, (YY___ATTRIBUTE,YY___ATTRIBUTE__,YY___DECLSPEC,YY___CDECL,YY___STDCALL,YY___FASTCALL,YY___THISCALL,YY___VECTORCALL), "\000\000\000\000\000\000\360\017\000\000\000\000\000")) { sym = parse_attributes(sym, &dcl); @@ -2113,8 +2113,9 @@ static int parse_declarations(int sym) { if (sym == YY__EQUAL) { sym = parse_initializer(sym); } - zend_ffi_declare(name, name_len, &dcl); + zend_ffi_declare(name, name_len, &dcl, &common_dcl); } + zend_ffi_cleanup_dcl(&common_dcl); } else if (sym == YY__SEMICOLON) { if (common_dcl.flags & (ZEND_FFI_DCL_ENUM | ZEND_FFI_DCL_STRUCT | ZEND_FFI_DCL_UNION)) zend_ffi_cleanup_dcl(&common_dcl); } else { @@ -2472,28 +2473,29 @@ static int parse_struct_contents(int sym, zend_ffi_dcl *dcl) { static int parse_struct_declaration(int sym, zend_ffi_dcl *struct_dcl) { zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT; - zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT; + zend_ffi_dcl field_dcl; sym = parse_specifier_qualifier_list(sym, &common_field_dcl); - base_field_dcl = common_field_dcl; if (sym == YY__SEMICOLON || sym == YY__RBRACE) { zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl); } else if (sym == YY__STAR || sym == YY_ID || sym == YY__LPAREN || sym == YY__COLON) { - sym = parse_struct_declarator(sym, struct_dcl, &common_field_dcl); + zend_ffi_borrow_dcl(&field_dcl, &common_field_dcl); + sym = parse_struct_declarator(sym, struct_dcl, &field_dcl, &common_field_dcl); while (sym == YY__COMMA) { sym = get_sym(); - zend_ffi_dcl field_dcl = base_field_dcl; + zend_ffi_borrow_dcl(&field_dcl, &common_field_dcl); if (YY_IN_SET(sym, (YY___ATTRIBUTE,YY___ATTRIBUTE__,YY___DECLSPEC,YY___CDECL,YY___STDCALL,YY___FASTCALL,YY___THISCALL,YY___VECTORCALL), "\000\000\000\000\000\000\360\017\000\000\000\000\000")) { sym = parse_attributes(sym, &field_dcl); } - sym = parse_struct_declarator(sym, struct_dcl, &field_dcl); + sym = parse_struct_declarator(sym, struct_dcl, &field_dcl, &common_field_dcl); } + zend_ffi_cleanup_dcl(&common_field_dcl); } else { yy_error_sym("unexpected", sym); } return sym; } -static int parse_struct_declarator(int sym, zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl) { +static int parse_struct_declarator(int sym, zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl, zend_ffi_dcl *common_field_dcl) { const char *name = NULL; size_t name_len = 0; zend_ffi_val bits; @@ -2510,7 +2512,7 @@ static int parse_struct_declarator(int sym, zend_ffi_dcl *struct_dcl, zend_ffi_d if (YY_IN_SET(sym, (YY___ATTRIBUTE,YY___ATTRIBUTE__,YY___DECLSPEC,YY___CDECL,YY___STDCALL,YY___FASTCALL,YY___THISCALL,YY___VECTORCALL), "\000\000\000\000\000\000\360\017\000\000\000\000\000")) { sym = parse_attributes(sym, field_dcl); } - zend_ffi_add_field(struct_dcl, name, name_len, field_dcl); + zend_ffi_add_field(struct_dcl, name, name_len, field_dcl, common_field_dcl); } else { yy_error_sym("unexpected", sym); } diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h index 69ee099528f5..02962269db68 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -210,6 +210,7 @@ typedef struct _zend_ffi_val { zend_result zend_ffi_parse_decl(const char *str, size_t len); zend_result zend_ffi_parse_type(const char *str, size_t len, zend_ffi_dcl *dcl); void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl); +void zend_ffi_borrow_dcl(zend_ffi_dcl *dcl, const zend_ffi_dcl *common_dcl); /* parser callbacks */ void ZEND_NORETURN zend_ffi_parser_error(const char *msg, ...); @@ -220,7 +221,7 @@ void zend_ffi_declare_tag(const char *name, size_t name_len, zend_ffi_dcl *dcl, void zend_ffi_make_enum_type(zend_ffi_dcl *dcl); void zend_ffi_add_enum_val(zend_ffi_dcl *enum_dcl, const char *name, size_t name_len, zend_ffi_val *val, int64_t *min, int64_t *max, int64_t *last); void zend_ffi_make_struct_type(zend_ffi_dcl *dcl); -void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl); +void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl, zend_ffi_dcl *common_dcl); void zend_ffi_add_anonymous_field(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl); void zend_ffi_add_bit_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl, zend_ffi_val *bits); void zend_ffi_adjust_struct_size(zend_ffi_dcl *dcl); @@ -228,7 +229,7 @@ void zend_ffi_make_pointer_type(zend_ffi_dcl *dcl); void zend_ffi_make_array_type(zend_ffi_dcl *dcl, zend_ffi_val *len); void zend_ffi_make_func_type(zend_ffi_dcl *dcl, HashTable *args, zend_ffi_dcl *nested_dcl); void zend_ffi_add_arg(HashTable **args, const char *name, size_t name_len, zend_ffi_dcl *arg_dcl); -void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl); +void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl, zend_ffi_dcl *common_dcl); void zend_ffi_add_attribute(zend_ffi_dcl *dcl, const char *name, size_t name_len); void zend_ffi_add_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, int n, zend_ffi_val *val); void zend_ffi_add_msvc_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, zend_ffi_val *val); diff --git a/ext/ffi/tests/decl_list_shared_anonymous_type.phpt b/ext/ffi/tests/decl_list_shared_anonymous_type.phpt new file mode 100644 index 000000000000..60d7cc59eb32 --- /dev/null +++ b/ext/ffi/tests/decl_list_shared_anonymous_type.phpt @@ -0,0 +1,46 @@ +--TEST-- +FFI: declarator lists sharing an anonymous struct, union or enum type +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- +new("T"); +$t->a = 1; +$a = $ffi->new("A"); +$a[1]->a = 2; +$p = $ffi->new("P"); +$p = FFI::addr($t); +echo $t->a, $a[1]->a, $p->a, PHP_EOL; + +$u = $ffi->new("U2"); +$u->i = 3; +echo $u->i, PHP_EOL; + +echo FFI::sizeof($ffi->new("E1")), FFI::sizeof($ffi->new("E2")), PHP_EOL; + +$s = $ffi->new("struct S"); +$s->p->x = 4; +$s->q->x = 5; +echo $s->p->x, $s->q->x, PHP_EOL; + +unset($ffi, $t, $a, $p, $u, $s); +echo "Done", PHP_EOL; + +?> +--EXPECT-- +121 +3 +44 +45 +Done From 2e5b3df4e0591af392cf0dad2eed56c42b29ce22 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 24 Oct 2026 12:57:24 +0100 Subject: [PATCH 2/2] Leaked declaration on FFI redeclaration errors. zend_ffi_declare() and zend_ffi_add_enum_val() bailed out on a redeclaration without releasing the type they held. Free it before reporting the error. --- ext/ffi/ffi.c | 3 +++ ext/ffi/tests/redeclaration_leak.phpt | 36 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 ext/ffi/tests/redeclaration_leak.phpt diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 9f442a77d05e..c0401aef0b21 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -6118,6 +6118,7 @@ void zend_ffi_add_enum_val(zend_ffi_dcl *enum_dcl, const char *name, size_t name } sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym) { + zend_ffi_cleanup_dcl(enum_dcl); zend_ffi_parser_error("Redeclaration of \"%.*s\" at line %d", name_len, name, FFI_G(line)); } else { sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent)); @@ -6724,6 +6725,8 @@ void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl, zend } } } + zend_ffi_adopt_type(dcl, common_dcl); + zend_ffi_cleanup_dcl(dcl); zend_ffi_parser_error("Redeclaration of \"%.*s\" at line %d", name_len, name, FFI_G(line)); } else { if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF) { diff --git a/ext/ffi/tests/redeclaration_leak.phpt b/ext/ffi/tests/redeclaration_leak.phpt new file mode 100644 index 000000000000..3454c2bbd8f1 --- /dev/null +++ b/ext/ffi/tests/redeclaration_leak.phpt @@ -0,0 +1,36 @@ +--TEST-- +FFI: no memory leak on redeclaration errors +--EXTENSIONS-- +ffi +--INI-- +ffi.enable=1 +--FILE-- +getMessage(), PHP_EOL; + } +} + +?> +--EXPECT-- +FFI\ParserException: Redeclaration of "x" at line 1 +FFI\ParserException: Redeclaration of "x" at line 1 +FFI\ParserException: Redeclaration of "T" at line 1 +FFI\ParserException: Redeclaration of "U" at line 1 +FFI\ParserException: Redeclaration of "A" at line 1 +FFI\ParserException: Redeclaration of "A" at line 1 +FFI\ParserException: Redeclaration of "B" at line 1