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

Reply via email to