Fix Windows heap corruption from literal-pointer symbol name collisions#3288
Open
gbaraldi wants to merge 1 commit into
Open
Fix Windows heap corruption from literal-pointer symbol name collisions#3288gbaraldi wants to merge 1 commit into
gbaraldi wants to merge 1 commit into
Conversation
When check_ir! encounters a call to a literal inttoptr address (as Julia 1.10 codegen emits for already-resolved ccalls), it recovers a symbol name via jl_lookup_code_address, creates a declaration under that name carrying enzymejl_needs_restoration=<ptr>, and restore_lookups later replaces the declaration with its recorded address. jl_lookup_code_address reports the nearest preceding symbol, and on Windows (export-table-only symbol info) two distinct pointers can come back with the same name. The second call site then reused the first site's declaration, and restore_lookups stamped one address onto both. In the BigFloat easy_rule shadow path this merged __gmpz_init2 and __gmpz_set_si onto a single address, so mpz_set_si ran on an uninitialized BigInt and wrote a limb through a garbage d pointer, crashing CI with STATUS_HEAP_CORRUPTION (0xC0000374). Fix: - Verify recovered names by asking the loader which module contains the pointer (dladdr on POSIX, GetModuleHandleExW(FROM_ADDRESS) on Windows) and resolving the name within exactly that module; drop names that provably belong to a different address. - Never merge call sites whose pointers differ: when a declaration with the same name but a different recorded restoration pointer already exists, key the new declaration by pointer instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Benchmark Results
Benchmark PlotsA plot of the benchmark results has been uploaded as an artifact at https://github.com/EnzymeAD/Enzyme.jl/actions/runs/28825930507/artifacts/8122763617. |
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.
Fixes the Windows 1.10 CI crash (
STATUS_HEAP_CORRUPTION, exit code 3221226356) debugged via #3228.The bug
On Julia 1.10, ccalls reach Enzyme's module as literal
inttoptraddresses. For each such call site,check_ir!recovers a symbol name viajl_lookup_code_address, creates a declaration under that name carryingenzymejl_needs_restoration=<ptr>, andrestore_lookupslater replaces the declaration with its recorded address.jl_lookup_code_addressreports the nearest preceding symbol, and on Windows (export-table-only symbol info) two distinct pointers can come back with the same name. The second call site then reused the first site's declaration, and restoration stamped one address onto both call sites.In the post-
restore_lookupsIR from the #3228 debug run, the compiledBase.GMP.MPZ.set_si(1)(building the literal1of the BigFloat@easy_ruleshadow) shows both GMP calls resolved to the same address:so
__gmpz_init2never runs andmpz_set_siwrites a limb through the uninitializeddpointer of a non-zeroed pool allocation → heap corruption. On Julia 1.11+ the same call sites go through the GOT path and keep their identity asejlstr$__gmpz_init2$libgmp-10.dll/ejlstr$__gmpz_set_si$libgmp-10.dll, which is why only 1.10 crashes. (Windows is also the only platform where the easy-rule literal1takes the BigInt/GMP path at all, sinceClong == Int32there.)The fix
Two layers in
check_ir!:resolve_symbol_name): ask which module contains the pointer (dladdron POSIX,GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS)on Windows) and resolve the candidate name within exactly that module. If it resolves to a different address, the name is provably wrong and is dropped (the call site keeps its correct literal pointer). The lookup is module-scoped on purpose: a process-wide search could find an unrelated library's copy of a symbol (libm vs openlibm, etc.) and misreport a correct name; comparing addresses instead of names also keeps exported aliases working.This is the same disease as the existing macOS
memcpy/memmovecollision workaround inFFI.ptr_map(curated names remain trusted as-is).Testing
autodiff(Forward, +, Duplicated, Duplicated(a, da), Duplicated(b, db))[1] ≈ da + db), fulltest/rules/internal_rules/bigfloat.jlpasses, andtest/passes.jlpasses including the new"Literal-pointer symbol resolution"testset covering match / nearest-symbol mismatch / unresolvable-file / non-module-pointer cases.🤖 Generated with Claude Code