Profiler crashed in ElfParser - #793
zhengyu123 wants to merge 9 commits into
Conversation
❌ ErrorsYour PR has failed checks. Please review the issues below and take necessary action before merging. 🚦 4 Pipeline jobs failed
ℹ️ Info🔄 Datadog auto-retried 1 job - 0 passed on retry Useful? React with 👍 / 👎 This comment will be updated automatically if new data arrives.🔗 Commit SHA: a33cab7 | Docs | View more details | Give us feedback! |
Scan-Build Report
Bug Summary
Reports
|
||||||||||||||||||||||||||||||||||||
CI Test ResultsRun: #35255933261 | Commit:
Status Overview
Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled Summary: Total: 32 | Passed: 32 | Failed: 0 Updated: 2026-09-17 18:16:48 UTC |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5e34de3ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
A damaged secondary PT_LOAD address can overflow before the new bounds check runs. This fault can still crash the profiler.
🤖 Datadog Autotest · Commit a5e34de · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest
What does this PR do?:
Hardens
ElfParser's parsing of thePT_DYNAMICsection and related virtual-address-relative structures (DT_HASH,DT_GNU_HASH,DT_SYMTAB/DT_STRTAB,.rela.plt/.rela.dynrelocation tables, theSFrame/eh_frame_hdrunwind sections) against corrupted or malformed ELF metadata found in a live-mapped shared library.ElfParser::inLiveImage()/liveSegmentEnd()/liveImageEnd(), a bounds-checking layer for pointers derived fromat()/dyn_ptr()(virtual addresses in live process memory), parallel to the existinginImage()(which only bounds file-offset-relative pointers). Every such pointer is now validated against the ELF's own live PT_LOAD footprint before it is dereferenced.resolveSymbol()andresolveImportAddr()used by both relocation loops, and hardensgetSymbolCount()'sDT_GNU_HASHbucket/bloom/chain walk against attacker-controlled sizes and offsets (all arithmetic validated in a wide integer type before forming any pointer).DT_STRSZto the room actually left instrtab's own live segment, fixing string-table lookups that were previously either unbounded (trusting a corruptedDT_STRSZ) or over-strict (rejecting small, otherwise-valid libraries).DT_GNU_HASHchain-terminator scan (MAX_GNU_HASH_CHAIN_SCAN, 1M entries) so a corrupted table that never sets its terminator bit can't turn into a very long scan of a large live segment whileSymbols::_parse_lockis held.Motivation:
Production crash:
The
DT_HASHcase inparseDynamicSection()dereferenced adyn_ptr()-derived pointer with no bounds check — a single malformedDT_HASHentry crashed the process. This was the one path insymbols_linux.cppnot routed through any bounds check (everything else already usedinImage()for file-offset-relative data). Rather than special-casing justDT_HASH, this fixes the underlying gap: every virtual-address-relative pointer computed viaat()/dyn_ptr()in this file is now validated the same way, so the same class of bug can't resurface atDT_GNU_HASH, the relocation tables, or theSFrame/eh_frame_hdrsections.Additional Notes:
MAX_PLAUSIBLE_SEGMENT_SIZE(4 GiB) caps how large a singlePT_LOADsegment is trusted to be, guarding the pointer arithmetic inplausibleSegmentEnd()/liveSegmentEnd()/liveImageEnd()against wraparound from a corruptedp_memsz.DT_GNU_HASHchain scan had no iteration cap, so a corrupted table landing in a large (now up to 4 GiB) live segment with its terminator bit never set could scan for a very long time, oneinLiveImage()(O(e_phnum)) call at a time, while holdingSymbols::_parse_lock. AddedMAX_GNU_HASH_CHAIN_SCANto bound this to a fast failure instead.strtab_room == 0branch inparseDynamicSection():dyn_ptr()already guaranteesstrtablies strictly inside a live segment before returning non-NULL, so that branch could never be taken.at()/dyn_ptr()computed_vaddr_diff + p_vaddr(or+ d_ptr) unconditionally; an untrustedp_vaddr/d_ptrlarge enough overflows that pointer addition (undefined behavior, and on wraparound could alias back into a plausible-looking live address). Both now validate the addition in integer space first and reject the entry (at()returnsfalse,dyn_ptr()returnsnullptr) instead of forming an overflowing pointer; all 5 call sites were updated to check the result.How to test the change?:
ddprof-lib/src/test/cpp/elfparser_ut.cppadds extensive coverage, including:dynamicSectionHashPointerOutOfBounds: a regression test reproducing the exact production crash via a craftedDT_HASHpointer (must not crash, must not populate symbols).resolveSymbol()/resolveImportAddr()against a malformedsyment, symbol index, orr_offsetin.rela.pltentries.atProgramHeaderVaddrOverflow/dynPtrOverflow: regression tests for theat()/dyn_ptr()overflow guard, usingp_vaddr/d_ptr == UINT64_MAXto force the pointer addition to overflow.Run on Linux:
./gradlew :ddprof-lib:gtestDebug_elfparser_ut(orgtestRelease_elfparser_ut).For Datadog employees:
credentials of any kind, I've requested a security review (run the
dd:platform-security-reviewskill, or file a request via the PSEC review form).
bewairealso runs automatically on every PR.Unsure? Have a question? Request a review!