DT_RUNPATH of executable not found when loader and dl_caller is libasan.so

Message ID CALO2Tq+wv-KDRtE+zN=bd9p0oDJJ6T=6YNQa3aNS6eA7bzGSRw@mail.gmail.com
State Rejected
Headers
Series DT_RUNPATH of executable not found when loader and dl_caller is libasan.so |

Commit Message

Navin P Feb. 26, 2021, 5:29 p.m. UTC
  Hi,
    I sent this to libc-help which seems to
the wrong group.

 My scenario is    $HOME/package/ contains ./a.out and dso libplugin.so.
  Without address sanitizer(asan) libasan.so mode using gcc , if i run
./a.out from $HOME/package i'm able to find libplugin.so and it prints
dlopen succeeded.
Now if i go to the parent directy and cd ..  . If i run
./package/a.out , it is able to find and prints dlopen succeeded .
Everything is good until here.
libplugin.so is an empty shared object without asan..

#include<dlfcn.h>
#include<cstdio>

int main(){
        void *handle=dlopen("libplugin.so",RTLD_NOW);
        if(handle!=NULL){
                printf("dlopen succeeded\n");
        } else {
                printf("dlopen failed\n");
        }
        return 0;
}


Now i compile the example and .so with -fsanitize=address. Now in the
case of running a.out from  $HOME/package , it prints dlopen
succeeded.
Now if i do cd.. and run ./package/a.out from $HOME it fails and
prints dlopen failed. This is because the loader is changed to
libasan.so and this
time the DT_RUNPATH is checked for RUNPATH of libasan. This works for
RPATH because we check for all the list of loaders and we find the
base loader whose rpath is $HOME/package. But in case of DT_RUNPATH we
don't .
I think an additional lookup needs to be performed after the old
RUNPATH code in dl-load.c which looks up for
GL(dl_ns)[LM_ID_BASE]._ns_loaded when fd==-1.

I've tested this patch and it works fine for me.

From d2cb9bba3de58af3516eb7099d0452e2ee6365d2 Mon Sep 17 00:00:00 2001
From: Navin P <navinp0304@gmail.com>
Date: Mon, 22 Feb 2021 15:02:00 +0530
Subject: [PATCH] If executables are compiled with asan, it should perform
 additional LM_ID_BASE D_RUNPATH also.

---
 elf/dl-load.c | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

           realname = _dl_sysdep_open_object (name, namelen, &fd);
  

Comments

Florian Weimer March 3, 2021, 5:51 p.m. UTC | #1
* Navin P. via Libc-alpha:

> Hi,
>     I sent this to libc-help which seems to
> the wrong group.
>
>  My scenario is    $HOME/package/ contains ./a.out and dso libplugin.so.
>   Without address sanitizer(asan) libasan.so mode using gcc , if i run
> ./a.out from $HOME/package i'm able to find libplugin.so and it prints
> dlopen succeeded.
> Now if i go to the parent directy and cd ..  . If i run
> ./package/a.out , it is able to find and prints dlopen succeeded .
> Everything is good until here.
> libplugin.so is an empty shared object without asan..
>
> #include<dlfcn.h>
> #include<cstdio>
>
> int main(){
>         void *handle=dlopen("libplugin.so",RTLD_NOW);
>         if(handle!=NULL){
>                 printf("dlopen succeeded\n");
>         } else {
>                 printf("dlopen failed\n");
>         }
>         return 0;
> }
>
>
> Now i compile the example and .so with -fsanitize=address. Now in the
> case of running a.out from  $HOME/package , it prints dlopen
> succeeded.
> Now if i do cd.. and run ./package/a.out from $HOME it fails and
> prints dlopen failed. This is because the loader is changed to
> libasan.so and this
> time the DT_RUNPATH is checked for RUNPATH of libasan. This works for
> RPATH because we check for all the list of loaders and we find the
> base loader whose rpath is $HOME/package. But in case of DT_RUNPATH we
> don't .
> I think an additional lookup needs to be performed after the old
> RUNPATH code in dl-load.c which looks up for
> GL(dl_ns)[LM_ID_BASE]._ns_loaded when fd==-1.

I think the current glibc behavior is correct because libasan calls
dlopen in your example.  So the RUNPATH on libasan has to be used, and
not the RUNPATH on the main executable.

Or put differently, this is a known limitation of libasan's
interposed-based approach.

Thanks,
Florian
  

Patch

diff --git a/elf/dl-load.c b/elf/dl-load.c
index 9e2089cfaa..fe88c8c9ea 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -2161,6 +2161,34 @@  _dl_map_object (struct link_map *loader, const
char *name,
  &loader->l_runpath_dirs, &realname, &fb, loader,
  LA_SER_RUNPATH, &found_other_class);

+      /*
+       * This is not transitive dependency.It it just an additional
+       * lookup into executable/dso runpath when loader is different
+       * from LM_ID_BASE loader.
+       * In usual cases like direct call to executable, then main_map
+       * is same as loader and no lookup is performed.
+       * In cases like libasan.so being the parent caller with dl_caller
+       * set to libasan.so which is called by GL(dls_ns)[LIM_ID_BASE],
+       * we need to additionally search the executable/dso link_map
+       * other than loader link_map which is performed above.
+       */
+      struct link_map *main_map = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
+
+      /* Look at the RUNPATH information for this binary using main_map.
+       * This lookup is only done when the binary is in namespace 0.
+       * For nested namespaces from dlmopen LM_ID_NEWLM, we don't search
+       * in namespace 0 because it is detached from LM_ID_BASE.
+       * In that case lookup is done in the callers RUNPATH above.
+       */
+
+      if (fd == -1 && (nsid == LM_ID_BASE ) && main_map != NULL
+          && main_map != loader
+   && cache_rpath (main_map, &main_map->l_runpath_dirs,
+   DT_RUNPATH, "RUNPATH"))
+ fd = open_path (name, namelen, mode,
+ &main_map->l_runpath_dirs, &realname, &fb, main_map,
+ LA_SER_RUNPATH, &found_other_class);
+
       if (fd == -1)
         {