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. 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); + } + + /* 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); @@ -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); - } - /* 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); } 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; } -- 2.55.0
