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

Reply via email to