[ELF] Fix SIGSEGV when calling dlinfo RTLD_DI_LINKMAP for emulated code #3331
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.
Issue
When emulated code calls
dlinfo(handle, RTLD_DI_LINKMAP, &lmap), the returnedlink_map->l_ldwould point at box64’s internal parsed copy of.dynamic, which contains raw virtual addresses from the ELF file. Those addresses weren’t relocated to the guest’s load base, so guest code that dereferenced tags likeDT_SYMTABwould read nonsensical memory locations and crash:In the case of the above error, the symbol table actually lived at
0x7fff00172930, butl_ldexposed the file value0x172930.Fix
Add methods:
GetLoadedDynamicSection()to return the.dynamicaddress loaded in ELF memory (not the internal copy).PatchLoadedDynamicSection()which adds the load delta to pointer-typed_ptrentries so they point at the runtime addresses in the loaded ELF mapping. Pages might already be read only, so temporarilymprotectas needed.Update
l_ldassignments now use the loaded, patched section.Reproduction (pre-fix)
Using BepInEx 5.x (Linux) with a Unity game on ARM64 via box64. BepInEx’s Doorstop/plthook calls
dlinfo(..., RTLD_DI_LINKMAP, ...), walksl_ldto findDT_SYMTAB,DT_STRTAB, etc., and segfaults when it dereferences the unpatched file address (e.g.0x172930). Possibly a known issue with bepinex/box64 compatibility: #222, BepInEx/BepInEx#336.Steps:
plthook_open_real()callsdlinfo(handle, RTLD_DI_LINKMAP, &lmap).lmap->l_ldto find dynamic entries.0x172930).Design choices
isDynamicTagPointer):Dynamic entries use a union, but only pointer-typed tags should be delta-adjusted. The idea here is that an explicit allowlist avoids patching size/flag tags like
DT_STRSZorDT_FLAGS. And then I also added numeric fallbacks (e.g.0x6ffffef5forDT_GNU_HASH) to try and keep compatibility on older headers. But this feels a bit ugly to me. Open to suggestions if you have other ideas about how to do this.PT_DYNAMIC(GetLoadedDynamicInfo):The loaded
.dynamicaddress/size come fromPT_DYNAMIC(delta + p_vaddr,p_memsz). The internal parsed copy (h->Dynamic._64/_32) isn’t usable forl_ld. Similar pattern to howdl_iterate_phdr_findsymbollocates the dynamic section.mprotecting in (PatchLoadedDynamicSection):PatchLoadedDynamicSection will patch
.dynamic'sd_ptrvalues after load to keep the change localized and avoid restructuring the loader, but that means that it can run into pages with different protections (eg RELRO). So the idea is to check each page viagetProtection(), temporarily addPROT_WRITEonly where needed, roll back on failure, then restore protections. Untracked pages (protection=0) are left untouched. Perhaps it'd be better to just restructure the loader though if you prefer or if you have a suggestion.dynamic_patchedprevents double-adjusting the same ELF.