fix: FindAll*/Search match-callback marshaling (crash, leak, struct-by-value)#19
Merged
Merged
Conversation
…y-value) Three correctness bugs in the BinaryView search family, surfaced by the parity audit against the official Python binding: 1. Wrong delegate type (crash on first match). FindAllText and FindAllConstant both passed a 3-arg MatchDataDelegate thunk to core APIs whose native match callback has a different shape (const char* text + BNLinearDisassemblyLine*, or a bare line*). The thunk reinterpreted the wrong native pointer and AccessViolation-crashed on the first match. Each call is now wired to its matching MatchTextDelegate / MatchConstantDelegate. 2. Struct passed by pointer instead of by value (crash while rendering). The generated P/Invokes declared their BNFunctionViewType parameter as `in` (by-ref), which marshals the by-value C struct as a pointer; the core read garbage fields and crashed. Dropped `in` across all 8 sites so the blittable struct is passed by value (BNFindAll/Next Text/Constant*, BNCreate*Graph). 3. Null DisassemblySettings (crash). FindAllText/FindAllConstant forwarded a null settings as IntPtr.Zero, but the core dereferences it while rendering. Default to a constructed DisassemblySettings like the Python binding, and dispose only the one allocated here, never a caller-owned instance. Also fixed per-match ownership (the callback owns each matched BNDataBuffer / line per Python): the MatchData thunk now takes (owns) the buffer, the MatchText/MatchConstant thunks free the BNLinearDisassemblyLine after copying it (MustFromNativePointer copies eagerly, so freeing is safe); and every match thunk is rooted with GC.KeepAlive so a mid-scan GC cannot free it. The wrappers were converted from lambdas to named holder methods to match the no-lambda rule.
tinysec
added a commit
that referenced
this pull request
Jul 8, 2026
…#35) Closes HIGH missing-capi gap #19. The binding declared the five P/Invokes (BNDuplicateTypeLibrary, BNRegisterTypeLibrary, BNRemoveTypeLibraryNamedObject, BNRemoveTypeLibraryNamedType, BNLookupTypeLibraryByGuid) but exposed no managed wrappers, so the TypeLibrary lifecycle and named-store mutation surface was incomplete vs Python (typelibrary.py duplicate/register/remove_named_object/ remove_named_type/lookup_by_guid). Add: - TypeLibrary.Duplicate() (fresh-GUID copy), - TypeLibrary.Register() (method form of Python's register property), - TypeLibrary.RemoveNamedObject(QualifiedName) / RemoveNamedType, pinning the BNQualifiedName via ScopedAllocator.AllocStruct because the Remove P/Invokes take BNQualifiedName* (a pointer) whereas Add takes it by reference, - static TypeLibrary.LookupByGuid(Architecture, string).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three correctness bugs in the
BinaryViewsearch family, all surfaced by the exhaustive parity audit against the official Python binding. Each one crashed the process (AccessViolation) on the first match, soFindAllText/FindAllConstantwere entirely unusable from C#.1. Wrong delegate type — crash on first match
FindAllTextandFindAllConstantboth passed a 3-argMatchDataDelegatethunk to core APIs whose native match callback has a different argument shape:BNFindAllTextWithProgress→bool(*)(ctxt, addr, const char* text, BNLinearDisassemblyLine* line)(4 args)BNFindAllConstantWithProgress→bool(*)(ctxt, addr, BNLinearDisassemblyLine* line)(3 args)The thunk reinterpreted the wrong native pointer (
text*orline*read as aBNDataBuffer*) and crashed. Each call is now wired to its matchingMatchTextDelegate/MatchConstantDelegate. (Verified againstbinaryninjacore.h:4917.)2. Struct passed by pointer instead of by value — crash while rendering
The generated P/Invokes declared their
BNFunctionViewTypeparameter asin(by-ref), which marshals the by-value C struct as a pointer. The core read garbagetype/namefields and crashed during linear disassembly.FindAllDatawas unaffected only because it takes noviewType. Droppedinacross all 8 sites so the blittable struct is passed by value.3. Null
DisassemblySettings— crashFindAllText/FindAllConstantforwarded a nullsettingsasIntPtr.Zero, but the core dereferences the settings pointer while rendering. The official Python binding always builds a defaultDisassemblySettings; this now does the same (disposing only the one it allocates, never a caller-owned instance).Plus: per-match ownership & GC rooting
BNDataBuffer/BNLinearDisassemblyLine(per Python'sDataBuffer(handle=)__del__and_LinearDisassemblyLine_convertor→BNFreeLinearDisassemblyLines). TheMatchDatathunk now takes (owns) the buffer;MatchText/MatchConstantthunks free the line afterMustFromNativePointercopies it eagerly (freeing is safe). The earlier borrow / never-free forms leaked one object per match.GC.KeepAliveso a mid-scan GC cannot free it.Verification
sharp-binaryninja-tests:FindAllData/FindAllText/FindAllConstanteach drive the callback through ≥1 real match. All 28 harness tests pass (25 prior + 3 new), no regressions.find_all_text/find_all_constantsucceed on the samecat.bndb).Notes
in→ by-value fix is in 8 generatedFunction/BN*.csfiles. The generator should be updated to emit by-value struct params forBNFunctionViewType(and any other by-value structs) so this doesn't regress on regen.ShowAddress=False,ShowOpcode=False, etc.) is intentionally deferred — the core-default settings work; that's a rendering-preference refinement, not a correctness fix.