From 940292aaae7f18c7a08f23524e7fd6a272763c7f Mon Sep 17 00:00:00 2001 From: NOT XVilka Date: Tue, 9 Jun 2026 12:23:56 +0800 Subject: [PATCH] librz/bin/pdb: fix memory leaks of PDB callable convention strings and orphaned callables (#6468) Leak A - calling-convention string leaked Leak B - orphaned callables on duplicate names Co-authored-by: Anton Kochkov --- librz/arch/pdb_process.c | 29 +++++++++++++++++++++++++++-- librz/bin/pdb/tpi.c | 18 ++++++++++-------- librz/include/rz_pdb.h | 2 +- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/librz/arch/pdb_process.c b/librz/arch/pdb_process.c index bc8d63e154..1f1c219ecd 100644 --- a/librz/arch/pdb_process.c +++ b/librz/arch/pdb_process.c @@ -185,7 +185,21 @@ static RzType *procedure_parse( if (arglist) { arglist_parse(typedb, stream, arglist, typ->callable->args); } - rz_type_func_save((RzTypeDB *)typedb, callable); + if (!rz_type_func_save((RzTypeDB *)typedb, callable)) { + // A callable with this name is already registered. PDB type graphs + // frequently reference the same procedure type more than once, and + // unnamed procedures get a deterministic name derived from their TPI + // offset, so duplicates are expected. rz_type_func_save() does not take + // ownership on failure, so reuse the already-stored callable and free + // this one; otherwise it (and its whole subtree) would be leaked. + RzCallable *existing = rz_type_func_get((RzTypeDB *)typedb, callable->name); + rz_type_callable_free(callable); + typ->callable = existing; + if (!existing) { + rz_type_free(typ); + return NULL; + } + } return typ; } @@ -221,7 +235,18 @@ static RzType *mfunction_parse(const RzTypeDB *typedb, RzPdbTpiStream *stream, R if (arglist) { arglist_parse(typedb, stream, arglist, type->callable->args); } - rz_type_func_save((RzTypeDB *)typedb, callable); + if (!rz_type_func_save((RzTypeDB *)typedb, callable)) { + // See procedure_parse(): rz_type_func_save() does not take ownership when + // a callable of the same name already exists, so free this duplicate and + // reuse the stored one to avoid leaking it together with its subtree. + RzCallable *existing = rz_type_func_get((RzTypeDB *)typedb, callable->name); + rz_type_callable_free(callable); + type->callable = existing; + if (!existing) { + rz_type_free(type); + return NULL; + } + } return type; } diff --git a/librz/bin/pdb/tpi.c b/librz/bin/pdb/tpi.c index 44d5ec6370..0839c97f7a 100644 --- a/librz/bin/pdb/tpi.c +++ b/librz/bin/pdb/tpi.c @@ -34,27 +34,29 @@ int tpi_type_node_cmp(const void *incoming, const RBNode *in_tree, void *user) { * * \param idx */ -RZ_API RZ_OWN char *rz_bin_pdb_calling_convention_as_string(RZ_NONNULL RzPdbTpiCallingConvention idx) { +RZ_API RZ_BORROW const char *rz_bin_pdb_calling_convention_as_string(RZ_NONNULL RzPdbTpiCallingConvention idx) { + // The returned string is a static literal and must not be freed by the + // caller. RzCallable.cc (where these are stored) is a borrowed pointer. switch (idx) { case NEAR_C: case FAR_C: - return rz_str_dup("__cdecl"); + return "__cdecl"; case NEAR_PASCAL: case FAR_PASCAL: - return rz_str_dup("__pascal"); + return "__pascal"; case NEAR_FAST: case FAR_FAST: - return rz_str_dup("__fastcall"); + return "__fastcall"; case NEAR_STD: case FAR_STD: - return rz_str_dup("__stdcall"); + return "__stdcall"; case NEAR_SYS: case FAR_SYS: - return rz_str_dup("__syscall"); + return "__syscall"; case THISCALL: - return rz_str_dup("__thiscall"); + return "__thiscall"; case NEAR_VEC: - return rz_str_dup("__vectorcall"); + return "__vectorcall"; default: return NULL; } diff --git a/librz/include/rz_pdb.h b/librz/include/rz_pdb.h index 515caf3a00..32bdb8de64 100644 --- a/librz/include/rz_pdb.h +++ b/librz/include/rz_pdb.h @@ -376,7 +376,7 @@ RZ_API bool rz_pdb_all_symbols_foreach( // TPI RZ_API RZ_BORROW RzPdbTpiType *rz_bin_pdb_get_type_by_index(RZ_NONNULL RzPdbTpiStream *stream, ut32 index); -RZ_API RZ_OWN char *rz_bin_pdb_calling_convention_as_string(RZ_NONNULL RzPdbTpiCallingConvention idx); +RZ_API RZ_BORROW const char *rz_bin_pdb_calling_convention_as_string(RZ_NONNULL RzPdbTpiCallingConvention idx); RZ_API bool rz_bin_pdb_type_is_fwdref(RZ_NONNULL RzPdbTpiType *t); RZ_API RZ_BORROW RzPVector /**/ *rz_bin_pdb_get_type_members(RZ_NONNULL RzPdbTpiStream *stream, RzPdbTpiType *t); RZ_API RZ_BORROW char *rz_bin_pdb_get_type_name(RZ_NONNULL RzPdbTpiType *type);