Hi Aaron, On Wed, Sep 30, 2026 at 03:06:31PM -0400, Aaron Merey wrote: > Dwarf handles for .dwo/.dwp files hold a reference to the .debug_addr > section data from their corresponding skeleton file. This reference > is set in __libdw_link_skel_split while being readable by other > threads, creating a data race. > > Fix this by setting the .debug_addr section data for split Dwarf > handles during their initialization before they are accessible > to multiple threads. > > The skeleton's fake_addr_cu is shared with any split Dwarf handles, > so also set a split Dwarf's fake_addr_cu before this Dwarf handle > is accessible to other threads to prevent a data race on fake_addr_cu. > > Also handle fake_addr_cu sharing in dwarf_end. Only the skeleton Dwarf > frees fake_addr_cu plus dwarf_end is called on the split Dwarf handle > before freeing fake_addr_cu. This avoids possible double free and > use-after-free of the fake_addr_cu.
This looks correct. > Signed-off-by: Aaron Merey <[email protected]> > --- > libdw/dwarf_end.c | 20 +++++++++++++------- > libdw/libdwP.h | 26 +++++++++----------------- > libdw/libdw_find_split_unit.c | 14 ++++++++++++++ > 3 files changed, 36 insertions(+), 24 deletions(-) > > diff --git a/libdw/dwarf_end.c b/libdw/dwarf_end.c > index 4a65a35c..a7799773 100644 > --- a/libdw/dwarf_end.c > +++ b/libdw/dwarf_end.c > @@ -155,7 +155,19 @@ dwarf_end (Dwarf *dwarf) > cu_free (dwarf->fake_loclists_cu); > free (dwarf->fake_loclists_cu); > } > - if (dwarf->fake_addr_cu != NULL) > + > + /* Free dwp_dwarf before freeing fake_addr_cu. The fake_addr_cu > pointer > + needs to be valid during the freeing of dwp_dwarf so that it can tell > + whether or not it owns fake_addr_cu. */ > + if (dwarf->dwp_fd != -1) > + { > + INTUSE(dwarf_end) (dwarf->dwp_dwarf); > + close (dwarf->dwp_fd); > + } Right, first call dwarf_end on the dwp_dwarf, which will check the dwp_dwarf fake_addr_cu. > + /* Free fake_addr_cu only if this dwarf owns it. */ > + if (dwarf->fake_addr_cu != NULL > + && dwarf->fake_addr_cu->dbg == dwarf) > { > cu_free (dwarf->fake_addr_cu); > free (dwarf->fake_addr_cu); Then only free the fake_addr_cu if the dbg points to us. > @@ -168,12 +180,6 @@ dwarf_end (Dwarf *dwarf) > close (dwarf->alt_fd); > } > > - if (dwarf->dwp_fd != -1) > - { > - INTUSE(dwarf_end) (dwarf->dwp_dwarf); > - close (dwarf->dwp_fd); > - } > - Done above instead. > /* The cached path and dir we found the Dwarf ELF file in. */ > free (dwarf->elfpath); > free (dwarf->debugdir); > diff --git a/libdw/libdwP.h b/libdw/libdwP.h > index 3e0c2edc..d21164db 100644 > --- a/libdw/libdwP.h > +++ b/libdw/libdwP.h > @@ -1519,26 +1519,18 @@ __libdw_link_skel_split (Dwarf_CU *skel, Dwarf_CU > *split) > skel->split = split; > split->split = skel; > > - /* Get .debug_addr and addr_base greedy. > - We also need it for the fake addr cu. > - This needs to be done for each split unit (one per .dwo file, or > multiple > - per .dwp file). */ > + /* Get addr_base greedy. This needs to be done for each split unit > + (one per .dwo file, or multiple per .dwp file). .debug_addr and > + fake_addr_cu were already linked when the split Dwarf was opened. */ > Dwarf *dbg = skel->dbg; > Dwarf *sdbg = split->dbg; > + > + /* If the split file is using our .debug_addr rather than one of its > + own then link the address information for this file and unit. */ > if (dbg->sectiondata[IDX_debug_addr] != NULL > - /* If this split file hasn't been linked yet... */ > - && (sdbg->sectiondata[IDX_debug_addr] == NULL > - /* ... or it was linked to the same skeleton file for another > - unit... */ > - || (sdbg->sectiondata[IDX_debug_addr] > - == dbg->sectiondata[IDX_debug_addr]))) > - { > - /* ... then link the address information for this file and unit. */ > - sdbg->sectiondata[IDX_debug_addr] > - = dbg->sectiondata[IDX_debug_addr]; > - split->addr_base = __libdw_cu_addr_base (skel); > - sdbg->fake_addr_cu = dbg->fake_addr_cu; > - } > + && (sdbg->sectiondata[IDX_debug_addr] > + == dbg->sectiondata[IDX_debug_addr])) > + split->addr_base = __libdw_cu_addr_base (skel); > } So now __libdw_link_skel_split only sets split->addr_base (if they share debug_addr section (as done in link_debug_addr now. > diff --git a/libdw/libdw_find_split_unit.c b/libdw/libdw_find_split_unit.c > index 9a20ae25..8975bb56 100644 > --- a/libdw/libdw_find_split_unit.c > +++ b/libdw/libdw_find_split_unit.c > @@ -42,6 +42,17 @@ > #include <sys/stat.h> > #include <fcntl.h> > > +static void > +link_debug_addr (Dwarf *dbg, Dwarf *sdbg) > +{ > + if (dbg->sectiondata[IDX_debug_addr] != NULL > + && sdbg->sectiondata[IDX_debug_addr] == NULL) > + { > + sdbg->sectiondata[IDX_debug_addr] = dbg->sectiondata[IDX_debug_addr]; > + sdbg->fake_addr_cu = dbg->fake_addr_cu; > + } > +} > + > static void > try_split_file (Dwarf_CU *cu, const char *dwo_path) > { > @@ -59,6 +70,8 @@ try_split_file (Dwarf_CU *cu, const char *dwo_path) > if (split->unit_type == DW_UT_split_compile > && cu->unit_id8 == split->unit_id8) > { > + link_debug_addr (cu->dbg, split->dbg); > + > if (eu_tsearch (split->dbg, &cu->dbg->split_tree, > __libdw_finddbg_cb) == NULL) > { > @@ -143,6 +156,7 @@ try_dwp_file (Dwarf_CU *cu) > && (dwp_dwarf->sectiondata[IDX_debug_cu_index] != NULL > || dwp_dwarf->sectiondata[IDX_debug_tu_index] != NULL)) > { > + link_debug_addr (cu->dbg, dwp_dwarf); > cu->dbg->dwp_dwarf = dwp_dwarf; > cu->dbg->dwp_fd = dwp_fd; > } Both now link debug_addr section and fake_addr_cu early. Looks all fine. Mark
