public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Adhemerval Zanella <adhemerval.zanella@linaro.org>
To: Vivek Das Mohapatra <vivek@collabora.com>, libc-alpha@sourceware.org
Subject: Re: [RFC][PATCH v10 5/7] Implement dlmopen RTLD_SHARED flag (bug 22745)
Date: Fri, 28 May 2021 14:32:03 -0300	[thread overview]
Message-ID: <fe028af6-794c-bb58-2fd7-f5efbe60a2c3@linaro.org> (raw)
In-Reply-To: <20210322154111.24798-6-vivek@collabora.com>



On 22/03/2021 12:41, Vivek Das Mohapatra via Libc-alpha wrote:
> This flag will instruct dlmopen to create a shared object present
> in the main namespace and accessible from the selected namespace
> when supplied in the MODE argument.
> 
> include/link.h: Update the link_map struct to allow proxies
> 
> We already have an l_real pointer, used for a similar purpose by
> the linker for copies of ld.so in secondary namespaces. Update its
> documentation and add a bitfield to indicate when link_map entry
> is a proxy.
> 
> elf/dl-object.c: Implement a helper function to proxy link_map entries
> 
> Provides the minimal functionality needed to take an existing
> link_map entry and create a proxy for it in the specified namespace.
> 
> elf/dl-load.c, elf/dl-open.c: Implement RTLD_SHARED dlmopen proxying
> 
> This uses the new infrastructure to implement RTLD_SHARED object
> proxying via dlmopen: Instead of opening the specified object in
> the requested namespace we open it in the main namespace (if it
> is not already present there) and proxy it to the destination.
> 
> The following rules apply:
> 
> If a proxy of the object is already present in the requested namespace,
> we simply return it (with an incremented direct-open count).
> 
> If the object is already present in the requested namespace, a dl
> error is signalled, since we cannot satisfy the user's request.
> 
> Proxies are never created in the main namespace: RTLD_SHARED has no
> effect when the requested namespace is LM_ID_BASE.
> 
> elf/dl-fini.c: Handle proxy link_maps in the shutdown path (bug 22745)
> 
> When cleaning up before exit we should not call destructors or
> otherwise free [most of] the contents of proxied link_map entries
> since they share [most of] their contents with the LM_ID_BASE
> objects to which they point.
> 
> elf/dl-init.c: Skip proxy link_map entries in dl init path
> 
> Proxies should not trigger calls to DT_INIT constructors since they're
> just shims that point to the real, already loaded and initialised, objects.
> 
> elf/dl-open.c: Skip libc init if namespace has no libc map
> 
> Secondary namespaces which share their libc mapping with the main
> namespace cannot (and should not) have _dl_call_libc_early_init
> called for them by dl_open_worker.
> 
> elf/dl-open.c: When creating a proxy check NS 0 libc map
> 
> The libc_already_loaded check normally considers the libc_map entry
> in GL(dl_ns)[args->nsid].libc_map.
> 
> This is not correct for proxies, which use the libc_map from
> the default namespace (as proxies are dummy entries that point
> to the base namespace via their l_real members).
> 
> elf/dl-load.c, dl-open.c: Compare DSOs by file ID & check DF_GNU_1_UNIQUE
> 
> If _dl_map_object_from_fd finds that a DSO it was asked to
> load into a non-base namespace is already loaded (into the
> main namespace) and is flagged DF_GNU_1_UNIQUE then it should
> return that DSO's link map entry.
> 
> In such cases _dl_open_worker must notice that this has
> happened and continue down the link map proxy generation
> path instead of normal link map entry preparation.
> 
> elf/dl-load.c: error if RTLD_SHARED = DF_GNU_1_UNIQUE semantics violated
> 
> elf/dl-open.c: Use search helper to find preloaded DT_GNU_UNIQUE DSOs
> 
> If a DSO already exists (with the same name) in the base namespace
> and it is flagged DT_GNU_UNIQUE then we should behave as if a proxy
> had been requested.
> 
> elf/dl-load.c: When loading DSOs in other namespaces check DT_GNU_UNIQUE
> 
> If a DSO has not already been loaded and the target is not the main
> namespace then we must check to see if it's been DT_GNU_UNIQUE tagged
> and load it into the main namespace instead.
> 
> dl_open_worker has alread been modified to notice the discrepancy
> between the request and the result in such cases, and will set up
> a proxy in the target namespace.
> 
> elf/dl-load.c: Suppress audit calls when a (new) namespace is empty
> 
> When preparing an RTLD_SHARED proxy in a new namespace
> it is possible for the target namespace to be empty:
> 
> This can happen for RTLD_SHARED + LM_ID_NEWLM.
> 
> The audit infrastructure should not be invoked at this
> point (as there's nothing there to audit yet).
> 
> bits/dlfcn.h, elf/dl-load.c, elf/dl-open.c, elf/rtld.c,
> sysdeps/mips/bits/dlfcn.h:
> Suppress inter-namespace DSO sharing for audit libraries
> 
> Audit libraries should not participate in DSO sharing: In
> particular libraries tagged with DF_GNU_1_UNIQUE should not
> be shared between the audit namespace and any others - they
> should get their own copy.
> 
> This is signalled to the loader code by passing the RTLD_ISOLATE
> flag from the relevant entry point in the dl modes argument.
> 
> elf/dl-sym.c: dlsym, dlvsym must be able to resolve symbols via proxies

With this patch I am seeing some regression with binutils with and
without -z unique support:

FAIL: elf/tst-dlmopen-dlerror
FAIL: elf/tst-tls-ie-dlmopen

$ ./debugglibc.sh -- elf/tst-dlmopen-dlerror --direct
[...]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7fdf44f in _dl_open (file=<optimized out>, mode=<optimized out>, caller_dlopen=0x7ffff7dd1383, nsid=-2, argc=2, argv=0x7fffffffd5c0, env=0x7fffffffd5d8) at dl-open.c:995
995             GL(dl_ns)[nsid].libc_map = NULL;
(gdb) bt
#0  0x00007ffff7fdf44f in _dl_open (file=<optimized out>, mode=<optimized out>, caller_dlopen=0x7ffff7dd1383, nsid=-2, argc=2, argv=0x7fffffffd5c0, env=0x7fffffffd5d8) at dl-open.c:995
#1  0x00007ffff7fbf288 in dlopen_doit (a=a@entry=0x7fffffffcb70) at dlopen.c:66
#2  0x00007ffff7f0f769 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffffcad0, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:208
#3  0x00007ffff7f0f80f in __GI__dl_catch_error (objname=0x7fffffffcb30, errstring=0x7fffffffcb38, mallocedp=0x7fffffffcb2f, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:227
#4  0x00007ffff7fbf776 in _dlerror_run (operate=operate@entry=0x7ffff7fbf230 <dlopen_doit>, args=args@entry=0x7fffffffcb70) at dlerror.c:142
#5  0x00007ffff7fbf306 in __dlopen (file=<optimized out>, mode=<optimized out>) at dlopen.c:87
#6  0x00007ffff7dd1383 in ?? ()
#7  0x000000000000002b in ?? ()
#8  0x00007fffffffcbd0 in ?? ()
#9  0x00007ffff7dd1370 in ?? ()
#10 0x0000000000402595 in do_test () at tst-dlmopen-dlerror.c:43
#11 0x0000000000402d75 in support_test_main (argc=1, argc@entry=2, argv=0x7fffffffd5c8, argv@entry=0x7fffffffd5c0, config=config@entry=0x7fffffffd450) at support_test_main.c:403
#12 0x0000000000402385 in main (argc=argc@entry=2, argv=argv@entry=0x7fffffffd5c0) at ../support/test-driver.c:168
#13 0x00007ffff7dff407 in __libc_start_call_main (main=main@entry=0x402350 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffd5c0) at ../sysdeps/nptl/libc_start_call_main.h:58
#14 0x00007ffff7dff4b8 in __libc_start_main_impl (main=0x402350 <main>, argc=2, argv=0x7fffffffd5c0, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd5b8)
    at ../csu/libc-start.c:411
#15 0x0000000000402411 in _start () at ../sysdeps/x86_64/start.S:116

$ ./debugglibc.sh -- elf/tst-dlmopen-dlerror --direct
[...]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7fdf44f in _dl_open (file=<optimized out>, mode=<optimized out>, caller_dlopen=0x7ffff7dd1383, nsid=-2, argc=2, argv=0x7fffffffd5c0, env=0x7fffffffd5d8) at dl-open.c:995
995             GL(dl_ns)[nsid].libc_map = NULL;
(gdb) bt
#0  0x00007ffff7fdf44f in _dl_open (file=<optimized out>, mode=<optimized out>, caller_dlopen=0x7ffff7dd1383, nsid=-2, argc=2, argv=0x7fffffffd5c0, env=0x7fffffffd5d8) at dl-open.c:995
#1  0x00007ffff7fbf288 in dlopen_doit (a=a@entry=0x7fffffffcb70) at dlopen.c:66
#2  0x00007ffff7f0f769 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffffcad0, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:208
#3  0x00007ffff7f0f80f in __GI__dl_catch_error (objname=0x7fffffffcb30, errstring=0x7fffffffcb38, mallocedp=0x7fffffffcb2f, operate=<optimized out>, args=<optimized out>) at dl-error-skeleton.c:227
#4  0x00007ffff7fbf776 in _dlerror_run (operate=operate@entry=0x7ffff7fbf230 <dlopen_doit>, args=args@entry=0x7fffffffcb70) at dlerror.c:142
#5  0x00007ffff7fbf306 in __dlopen (file=<optimized out>, mode=<optimized out>) at dlopen.c:87
#6  0x00007ffff7dd1383 in ?? ()
#7  0x000000000000002b in ?? ()
#8  0x00007fffffffcbd0 in ?? ()
#9  0x00007ffff7dd1370 in ?? ()
#10 0x0000000000402595 in do_test () at tst-dlmopen-dlerror.c:43
#11 0x0000000000402d75 in support_test_main (argc=1, argc@entry=2, argv=0x7fffffffd5c8, argv@entry=0x7fffffffd5c0, config=config@entry=0x7fffffffd450) at support_test_main.c:403
#12 0x0000000000402385 in main (argc=argc@entry=2, argv=argv@entry=0x7fffffffd5c0) at ../support/test-driver.c:168
#13 0x00007ffff7dff407 in __libc_start_call_main (main=main@entry=0x402350 <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffd5c0) at ../sysdeps/nptl/libc_start_call_main.h:58
#14 0x00007ffff7dff4b8 in __libc_start_main_impl (main=0x402350 <main>, argc=2, argv=0x7fffffffd5c0, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd5b8)
    at ../csu/libc-start.c:411
#15 0x0000000000402411 in _start () at ../sysdeps/x86_64/start.S:116

Could you check if this is something related to recent changes?
I will stop the review process until we sort these out.

> ---
>  bits/dlfcn.h               |  10 ++
>  elf/dl-close.c             |  43 +++++----
>  elf/dl-fini.c              |   6 +-
>  elf/dl-init.c              |   4 +-
>  elf/dl-load.c              | 181 +++++++++++++++++++++++++++++++++----
>  elf/dl-object.c            |  78 ++++++++++++++++
>  elf/dl-open.c              | 109 +++++++++++++++++++++-
>  elf/dl-sym.c               |  14 +++
>  elf/rtld.c                 |   2 +-
>  include/link.h             |   6 +-
>  sysdeps/generic/ldsodefs.h |   5 +
>  sysdeps/mips/bits/dlfcn.h  |  10 ++
>  12 files changed, 423 insertions(+), 45 deletions(-)
> 
> diff --git a/bits/dlfcn.h b/bits/dlfcn.h
> index f3bc63e958..1366cb3546 100644
> --- a/bits/dlfcn.h
> +++ b/bits/dlfcn.h
> @@ -32,6 +32,16 @@
>     visible as if the object were linked directly into the program.  */
>  #define RTLD_GLOBAL	0x00100
>  
> +/* If the following bit is set in the MODE argument to dlmopen
> +   then the target object is loaded into the main namespace (if
> +   it is not already there) and a shallow copy (proxy) is placed
> +   in the target namespace: This allows multiple namespaces to
> +   share a single instance of a DSO.  */
> +#define RTLD_SHARED 0x00080
> +
> +/* Suppress RTLD_SHARED and/or DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE  */
> +#define RTLD_ISOLATE 0x00040
> +
>  /* Unix98 demands the following flag which is the inverse to RTLD_GLOBAL.
>     The implementation does this by default and so we can define the
>     value to zero.  */
> diff --git a/elf/dl-close.c b/elf/dl-close.c
> index c51becd06b..a0432b884d 100644
> --- a/elf/dl-close.c
> +++ b/elf/dl-close.c
> @@ -283,8 +283,9 @@ _dl_close_worker (struct link_map *map, bool force)
>  
>  	  /* Call its termination function.  Do not do it for
>  	     half-cooked objects.  Temporarily disable exception
> -	     handling, so that errors are fatal.  */
> -	  if (imap->l_init_called)
> +	     handling, so that errors are fatal.
> +	     Proxies should never have this flag set, but we double check.  */
> +	  if (imap->l_init_called && !imap->l_proxy)
>  	    {
>  	      /* When debugging print a message first.  */
>  	      if (__builtin_expect (GLRO(dl_debug_mask) & DL_DEBUG_IMPCALLS,
> @@ -360,7 +361,9 @@ _dl_close_worker (struct link_map *map, bool force)
>  	     one for the terminating NULL pointer.  */
>  	  size_t remain = (new_list != NULL) + 1;
>  	  bool removed_any = false;
> -	  for (size_t cnt = 0; imap->l_scope[cnt] != NULL; ++cnt)
> +	  for (size_t cnt = 0;
> +               imap->l_scope && imap->l_scope[cnt] != NULL;
> +               ++cnt)
>  	    /* This relies on l_scope[] entries being always set either
>  	       to its own l_symbolic_searchlist address, or some map's
>  	       l_searchlist address.  */
> @@ -686,8 +689,10 @@ _dl_close_worker (struct link_map *map, bool force)
>  
>  	  /* We can unmap all the maps at once.  We determined the
>  	     start address and length when we loaded the object and
> -	     the `munmap' call does the rest.  */
> -	  DL_UNMAP (imap);
> +	     the `munmap' call does the rest. Proxies do not have
> +             any segments of their own to unmap.  */
> +          if (!imap->l_proxy)
> +            DL_UNMAP (imap);
>  
>  	  /* Finally, unlink the data structure and free it.  */
>  #if DL_NNS == 1
> @@ -727,19 +732,23 @@ _dl_close_worker (struct link_map *map, bool force)
>  	    _dl_debug_printf ("\nfile=%s [%lu];  destroying link map\n",
>  			      imap->l_name, imap->l_ns);
>  
> -	  /* This name always is allocated.  */
> -	  free (imap->l_name);
> -	  /* Remove the list with all the names of the shared object.  */
> +          /* Skip structures borrowed by proxies from the real map.  */
> +          if (!imap->l_proxy)
> +            {
> +              /* This name always is allocated.  */
> +              free (imap->l_name);
> +              /* Remove the list with all the names of the shared object.  */
>  
> -	  struct libname_list *lnp = imap->l_libname;
> -	  do
> -	    {
> -	      struct libname_list *this = lnp;
> -	      lnp = lnp->next;
> -	      if (!this->dont_free)
> -		free (this);
> -	    }
> -	  while (lnp != NULL);
> +              struct libname_list *lnp = imap->l_libname;
> +              do
> +                {
> +                  struct libname_list *this = lnp;
> +                  lnp = lnp->next;
> +                  if (!this->dont_free)
> +                    free (this);
> +                }
> +              while (lnp != NULL);
> +            }
>  
>  	  /* Remove the searchlists.  */
>  	  free (imap->l_initfini);
> diff --git a/elf/dl-fini.c b/elf/dl-fini.c
> index 6dbdfe4b3e..10194488bb 100644
> --- a/elf/dl-fini.c
> +++ b/elf/dl-fini.c
> @@ -73,7 +73,7 @@ _dl_fini (void)
>  	  assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL);
>  	  for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next)
>  	    /* Do not handle ld.so in secondary namespaces.  */
> -	    if (l == l->l_real)
> +	    if (l == l->l_real || l->l_proxy)
>  	      {
>  		assert (i < nloaded);
>  
> @@ -111,7 +111,9 @@ _dl_fini (void)
>  	    {
>  	      struct link_map *l = maps[i];
>  
> -	      if (l->l_init_called)
> +              /* Do not call fini functions via proxies, or for
> +                 objects which are not marked as initialised.  */
> +	      if (l->l_init_called && !l->l_proxy)
>  		{
>  		  /* Make sure nothing happens if we are called twice.  */
>  		  l->l_init_called = 0;
> diff --git a/elf/dl-init.c b/elf/dl-init.c
> index f924d26642..00ce8bb9d6 100644
> --- a/elf/dl-init.c
> +++ b/elf/dl-init.c
> @@ -30,8 +30,8 @@ call_init (struct link_map *l, int argc, char **argv, char **env)
>       need relocation, and neither do proxy objects.)  */
>    assert (l->l_real->l_relocated || l->l_real->l_type == lt_executable);
>  
> -  if (l->l_init_called)
> -    /* This object is all done.  */
> +  if (l->l_init_called || l->l_proxy)
> +    /* This object is all done, or a proxy (and therefore initless).  */
>      return;
>  
>    /* Avoid handling this constructor again in case we have a circular
> diff --git a/elf/dl-load.c b/elf/dl-load.c
> index f2dbe45703..11513e1992 100644
> --- a/elf/dl-load.c
> +++ b/elf/dl-load.c
> @@ -838,6 +838,53 @@ _dl_init_paths (const char *llp, const char *source,
>      __rtld_env_path_list.dirs = (void *) -1;
>  }
>  
> +static bool
> +has_gnu_unique (int fd, const ElfW(Ehdr) *header, const ElfW(Phdr) *phdr)
> +{
> +  bool unique = false;
> +  const ElfW(Phdr) *ph;
> +
> +  for (ph = phdr; ph < &phdr[header->e_phnum]; ++ph)
> +    {
> +      off64_t end;
> +      off64_t pos;
> +      ssize_t bytes = -1;
> +      ElfW(Dyn) entry = { .d_tag = 0, .d_un.d_val = 0 };
> +
> +      switch (ph->p_type)
> +	{
> +        case PT_DYNAMIC:
> +          pos = ph->p_offset;
> +          end = pos + ph->p_filesz;
> +
> +          while (pos < end)
> +            {
> +              bytes = __pread64_nocancel (fd, &entry, sizeof (ElfW(Dyn)), pos);
> +
> +              if (__glibc_unlikely (bytes != sizeof (ElfW(Dyn))))
> +                goto done;
> +
> +              pos += bytes;
> +
> +              switch (entry.d_tag)
> +                {
> +                case DT_GNU_FLAGS_1:
> +                  unique = (entry.d_un.d_val & DF_GNU_1_UNIQUE);
> +                  goto done;
> +                  break;
> +
> +                case DT_NULL:
> +                  goto done;
> +                  break;
> +                }
> +            }
> +          break;
> +        }
> +    }
> +
> + done:
> +  return unique;
> +}
>  
>  /* Process PT_GNU_PROPERTY program header PH in module L after
>     PT_LOAD segments are mapped.  Only one NT_GNU_PROPERTY_TYPE_0
> @@ -1021,6 +1068,32 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
>      }
>  #endif
>  
> +  /* DSOs in the main namespace which are flagged DF_GNU_1_UNIQUE should only
> +     be opened into the main namespace. Other namespaces should only get
> +     proxies.  */
> +  if (__glibc_unlikely ((nsid != LM_ID_BASE) && !(mode & RTLD_ISOLATE)))
> +    {
> +      /* Check base ns to see if the name matched another already loaded.  */
> +      for (l = GL(dl_ns)[LM_ID_BASE]._ns_loaded; l != NULL; l = l->l_next)
> +        if (!l->l_removed && _dl_file_id_match_p (&l->l_file_id, &id))
> +          {
> +            if (!(l->l_gnu_flags_1 & DF_GNU_1_UNIQUE))
> +              continue;
> +
> +            /* Already loaded. Bump its reference count and return it.  */
> +            __close_nocancel (fd);
> +
> +            /* If the name is not listed for this object add it.  */
> +            free (realname);
> +            add_name_to_object (l, name);
> +
> +            /* NOTE: It is important that our caller picks up on the fact
> +               that we have NOT returned an object in the requested namespace
> +               and handles the proxying correctly.  */
> +            return l;
> +          }
> +    }
> +
>    if (mode & RTLD_NOLOAD)
>      {
>        /* We are not supposed to load the object unless it is already
> @@ -1046,8 +1119,11 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
>  	  && __glibc_unlikely (GLRO(dl_naudit) > 0))
>  	{
>  	  struct link_map *head = GL(dl_ns)[nsid]._ns_loaded;
> -	  /* Do not call the functions for any auditing object.  */
> -	  if (head->l_auditing == 0)
> +	  /* Do not call the functions for any auditing object.
> +	     Do not try to call auditing functions if the namespace
> +	     is currently empty. This can hapen when opening the first
> +	     DSO in a new namespace.  */
> +	  if ((head != NULL) && (head->l_auditing == 0))
>  	    {
>  	      struct audit_ifaces *afct = GLRO(dl_audit);
>  	      for (unsigned int cnt = 0; cnt < GLRO(dl_naudit); ++cnt)
> @@ -1073,6 +1149,32 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
>    else
>      assert (r->r_state == RT_ADD);
>  
> +  /* Load the ELF header using preallocated struct space if it's big enough.  */
> +  maplength = header->e_phnum * sizeof (ElfW(Phdr));
> +  if (header->e_phoff + maplength <= (size_t) fbp->len)
> +    phdr = (void *) (fbp->buf + header->e_phoff);
> +  else
> +    {
> +      phdr = alloca (maplength);
> +      if ((size_t) __pread64_nocancel (fd, (void *) phdr, maplength,
> +				       header->e_phoff) != maplength)
> +	{
> +	  errstring = N_("cannot read file data");
> +	  goto lose_errno;
> +	}
> +    }
> +
> +  /* We need to check for DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE before we start
> +     initialising any namespace dependent metatada.  */
> +  if (__glibc_unlikely ((nsid != LM_ID_BASE) && !(mode & RTLD_ISOLATE)))
> +    {
> +      /* Target DSO is flagged as unique: Make sure it gets loaded into
> +         the base namespace.  It is up to our caller to generate a proxy
> +         in the target nsid.  */
> +      if (has_gnu_unique (fd, header, phdr))
> +        nsid = LM_ID_BASE;
> +    }
> +
>    /* Enter the new object in the list of loaded objects.  */
>    l = _dl_new_object (realname, name, l_type, loader, mode, nsid);
>    if (__glibc_unlikely (l == NULL))
> @@ -1090,20 +1192,6 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
>    type = header->e_type;
>    l->l_phnum = header->e_phnum;
>  
> -  maplength = header->e_phnum * sizeof (ElfW(Phdr));
> -  if (header->e_phoff + maplength <= (size_t) fbp->len)
> -    phdr = (void *) (fbp->buf + header->e_phoff);
> -  else
> -    {
> -      phdr = alloca (maplength);
> -      if ((size_t) __pread64_nocancel (fd, (void *) phdr, maplength,
> -				       header->e_phoff) != maplength)
> -	{
> -	  errstring = N_("cannot read file data");
> -	  goto lose_errno;
> -	}
> -    }
> -
>     /* On most platforms presume that PT_GNU_STACK is absent and the stack is
>      * executable.  Other platforms default to a nonexecutable stack and don't
>      * need PT_GNU_STACK to do so.  */
> @@ -2007,6 +2095,37 @@ open_path (const char *name, size_t namelen, int mode,
>    return -1;
>  }
>  
> +/* Search for a link map proxy in the given namespace by name.
> +   Consider it to be an error if the found object is not a proxy.  */
> +struct link_map *
> +_dl_find_proxy (Lmid_t nsid, const char *name)
> +{
> +  struct link_map *l;
> +
> +  for (l = GL(dl_ns)[nsid]._ns_loaded; l != NULL; l = l->l_next)
> +    {
> +      if (__glibc_unlikely ((l->l_faked | l->l_removed) != 0))
> +        continue;
> +
> +      if (!_dl_name_match_p (name, l))
> +        continue;
> +
> +      /* We have a match - stop searching.  */
> +      break;
> +    }
> +
> +  if (l != NULL)
> +    {
> +      if (l->l_proxy)
> +        return l;
> +
> +      _dl_signal_error (EEXIST, name, NULL,
> +                        N_("object cannot be demoted to a proxy"));
> +    }
> +
> +  return NULL;
> +}
> +
>  /* Search for a shared object in a given namespace.  */
>  struct link_map *
>  _dl_find_dso (const char *name, Lmid_t nsid)
> @@ -2060,12 +2179,42 @@ _dl_map_object (struct link_map *loader, const char *name,
>  
>    assert (nsid >= 0);
>    assert (nsid < GL(dl_nns));
> +  assert (!((mode & RTLD_ISOLATE) && (mode & RTLD_SHARED)));
> +
> +#ifdef SHARED
> +  /* Only need to do proxy checks if 'nsid' is not LM_ID_BASE.  */
> +  if (__glibc_unlikely ((mode & RTLD_SHARED) && (nsid != LM_ID_BASE)))
> +    {
> +      /* Search the namespace in case the object is already proxied.  */
> +      l = _dl_find_proxy (nsid, name);
> +      if (l != NULL)
> +        return l;
> +
> +      /* Further searches should be in the base ns: We will proxy the
> +         resulting object in dl_open_worker *after* it is initialised.  */
> +      nsid = LM_ID_BASE;
> +    }
> +#endif
>  
>    /* Look for this name among those already loaded.  */
>    l = _dl_find_dso (name, nsid);
>  
>    if (l != NULL)
>      {
> +#ifdef SHARED
> +      /* If we are trying to load a DF_GNU_1_UNIQUE flagged DSO which WAS
> +         already opened in the target NS but with RTLD_ISOLATE so it WAS NOT
> +         created as a proxy we need to error out since we cannot satisfy the
> +         DF_GNU_1_UNIQUE is-equivalent-to RTLD_SHARED semantics.  */
> +      if (!(mode & RTLD_ISOLATE) &&
> +          (l->l_ns != LM_ID_BASE) &&
> +          (l->l_gnu_flags_1 & DF_GNU_1_UNIQUE) &&
> +          !l->l_proxy)
> +      {
> +        _dl_signal_error (EEXIST, name, NULL,
> +                          N_("object cannot be demoted to a proxy"));
> +      }
> +#endif
>        return l;
>      }
>  
> diff --git a/elf/dl-object.c b/elf/dl-object.c
> index 1875599eb2..30a05b086f 100644
> --- a/elf/dl-object.c
> +++ b/elf/dl-object.c
> @@ -21,6 +21,7 @@
>  #include <stdlib.h>
>  #include <unistd.h>
>  #include <ldsodefs.h>
> +#include <libintl.h>
>  
>  #include <assert.h>
>  
> @@ -50,6 +51,83 @@ _dl_add_to_namespace_list (struct link_map *new, Lmid_t nsid)
>    __rtld_lock_unlock_recursive (GL(dl_load_write_lock));
>  }
>  
> +/* Proxy an existing link map entry into a new link map:
> +   This is based on _dl_new_object, skipping the steps we know we won't need
> +   because this is mostly just a shell for the l_real pointer holding the real
> +   link map entry (normally l == l->l_real, but not for ld.so in non-main
> +   link maps or RTLD_SHARED proxies).
> +   It also flags the proxy by setting l_proxy, and sets the the no-delete
> +   flag in the original if it is an lt_loaded.  */
> +struct link_map *
> +_dl_new_proxy (struct link_map *old, int mode, Lmid_t nsid)
> +{
> +  const char *name;
> +  struct link_map *new;
> +  struct libname_list *newname;
> +#ifdef SHARED
> +  unsigned int na = GLRO(dl_naudit);
> +
> +  if ((mode & __RTLD_OPENEXEC) != 0)
> +    na = DL_NNS;
> +
> +  size_t audit_space = na * sizeof (struct auditstate);
> +#else
> +# define audit_space 0
> +#endif
> +
> +  name = old->l_name;
> +
> +  /* Find the original link map entry if 'old' is itself a proxy. */
> +  while (old != NULL && old->l_proxy)
> +    old = old->l_real;
> +
> +  if (old == NULL)
> +    _dl_signal_error (EINVAL, name, NULL, N_("cannot proxy NULL link_map"));
> +
> +  /* Object already exists in the target namespace.  This should get handled
> +     by dl_open_worker but just in case we get this far, handle it:  */
> +  if (__glibc_unlikely (old->l_ns == nsid))
> +    _dl_signal_error (EEXIST, name, NULL,
> +                      N_("existing object cannot be demoted to a proxy"));
> +
> +  /* Now duplicate as little of _dl_new_object as possible to get a
> +     working proxied object in the target link map.  */
> +  new = (struct link_map *) calloc (sizeof (*new) + audit_space
> +                                    + sizeof (struct link_map *)
> +                                    + sizeof (*newname) + PATH_MAX, 1);
> +
> +  if (new == NULL)
> +    _dl_signal_error (ENOMEM, name, NULL,
> +                      N_("cannot create shared object descriptor"));
> +
> +  /* Specific to the proxy.  */
> +  new->l_real = old;
> +  new->l_proxy = 1;
> +  new->l_ns = nsid;
> +
> +  /* Copied from the origin.  */
> +  new->l_libname = old->l_libname;
> +  new->l_name = old->l_name;
> +  /* Proxies are considered lt_loaded if the real entry type is lt_library.  */
> +  new->l_type = (old->l_type == lt_library) ? lt_loaded : old->l_type;
> +
> +  if (__glibc_unlikely (mode & RTLD_NODELETE))
> +    new->l_flags_1 |= DF_1_NODELETE;
> +
> +  /* Specific to the origin.  Ideally we'd do some accounting here but
> +     for now it's easier to pin the original so the proxy remains valid.  */
> +  if (old->l_type == lt_loaded)
> +    old->l_flags_1 |= DF_1_NODELETE;
> +
> +  /* Fix up the searchlist so that relocations work.  */
> +  _dl_map_object_deps (new, NULL, 0, 0,
> +		       mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
> +
> +  /* And finally put the proxy in the target namespace.  */
> +  _dl_add_to_namespace_list (new, nsid);
> +
> +  return new;
> +}
>  
>  /* Allocate a `struct link_map' for a new object being loaded,
>     and enter it into the _dl_loaded list.  */
> diff --git a/elf/dl-open.c b/elf/dl-open.c
> index ab7aaa345e..083e6b2c3f 100644
> --- a/elf/dl-open.c
> +++ b/elf/dl-open.c
> @@ -484,6 +484,16 @@ dl_open_worker (void *a)
>    const char *file = args->file;
>    int mode = args->mode;
>    struct link_map *call_map = NULL;
> +  struct link_map *preloaded = NULL;
> +  bool want_proxy = false;
> +  bool dl_isolate = mode & RTLD_ISOLATE;
> +  Lmid_t proxy_ns = LM_ID_BASE;
> +
> +  /* Isolation means we should suppress all inter-namespace sharing.  */
> +  if (dl_isolate)
> +    mode &= ~RTLD_SHARED;
> +  else
> +    want_proxy = mode & RTLD_SHARED;
>  
>    /* Determine the caller's map if necessary.  This is needed in case
>       we have a DST, when we don't know the namespace ID we have to put
> @@ -508,6 +518,15 @@ dl_open_worker (void *a)
>  	args->nsid = call_map->l_ns;
>      }
>  
> +  /* Now that we know the NS for sure, sanity check the mode.  */
> +  if (__glibc_likely (args->nsid == LM_ID_BASE) &&
> +      __glibc_unlikely (mode & RTLD_SHARED))
> +    {
> +      args->mode &= ~RTLD_SHARED;
> +      mode &= ~RTLD_SHARED;
> +      want_proxy = 0;
> +    }
> +
>    /* The namespace ID is now known.  Keep track of whether libc.so was
>       already loaded, to determine whether it is necessary to call the
>       early initialization routine (or clear libc_map on error).  */
> @@ -521,6 +540,24 @@ dl_open_worker (void *a)
>       may not be true if this is a recursive call to dlopen.  */
>    _dl_debug_initialize (0, args->nsid);
>  
> +  /* Target Lmid is not the base and we haven't explicitly asked for a proxy:
> +     We need to check for a matching DSO in the base Lmid in case it is flagged
> +     DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE in which case we add RTLD_SHARED to the
> +     mode and set want_proxy.
> +     NOTE: RTLD_ISOLATE in the mode suppresses this behaviour.  */
> +  if (__glibc_unlikely (args->nsid != LM_ID_BASE) &&
> +      __glibc_likely (!dl_isolate) &&
> +      __glibc_likely (!want_proxy))
> +    {
> +      preloaded = _dl_find_dso (file, LM_ID_BASE);
> +
> +      if ((preloaded != NULL) && (preloaded->l_gnu_flags_1 & DF_GNU_1_UNIQUE))
> +        {
> +          want_proxy = true;
> +          mode |= RTLD_SHARED;
> +        }
> +    }
> +
>    /* Load the named object.  */
>    struct link_map *new;
>    args->map = new = _dl_map_object (call_map, file, lt_loaded, 0,
> @@ -541,6 +578,35 @@ dl_open_worker (void *a)
>    /* This object is directly loaded.  */
>    ++new->l_direct_opencount;
>  
> +  /* Proxy already existed in the target ns, nothing left to do.  */
> +  if (__glibc_unlikely (new->l_proxy))
> +    {
> +      if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> +	_dl_debug_printf ("proxied file=%s [%lu]; direct_opencount=%u\n\n",
> +			  new->l_name, new->l_ns, new->l_direct_opencount);
> +      return;
> +    }
> +
> +  /* If we are trying to load a DF_GNU_1_UNIQUE flagged DSO which was
> +     NOT ALREADY LOADED (or not loaded with the name we are using) then
> +     _dl_map_object will have returned an instance from the main namespace.
> +     We need to detect this and set up the RTLD_SHARED flags.  */
> +  if (__glibc_unlikely (args->nsid != LM_ID_BASE && new->l_ns == LM_ID_BASE))
> +    {
> +      want_proxy = RTLD_SHARED;
> +      mode |= RTLD_SHARED;
> +    }
> +
> +  /* If we want proxy and we get this far then the entry in 'new' will
> +     be in the main namespace: we should revert to the main namespace code
> +     path(s), but remember the namespace we want the proxy to be in.  */
> +  if (__glibc_unlikely (want_proxy))
> +    {
> +      proxy_ns = args->nsid;
> +      args->nsid = LM_ID_BASE;
> +      args->libc_already_loaded = GL(dl_ns)[LM_ID_BASE].libc_map != NULL;
> +    }
> +
>    /* It was already open.  */
>    if (__glibc_unlikely (new->l_searchlist.r_list != NULL))
>      {
> @@ -572,6 +638,16 @@ dl_open_worker (void *a)
>  
>        assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
>  
> +      if (__glibc_unlikely (want_proxy))
> +        {
> +          args->map = new = _dl_new_proxy (new, mode, proxy_ns);
> +          ++new->l_direct_opencount;
> +
> +          if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> +            _dl_debug_printf ("proxying file=%s [%lu]; direct_opencount=%u\n\n",
> +                              new->l_name, new->l_ns, new->l_direct_opencount);
> +        }
> +
>        return;
>      }
>  
> @@ -582,7 +658,8 @@ dl_open_worker (void *a)
>  
>    /* Load that object's dependencies.  */
>    _dl_map_object_deps (new, NULL, 0, 0,
> -		       mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
> +		       mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT |
> +		               RTLD_ISOLATE));
>  
>    /* So far, so good.  Now check the versions.  */
>    for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
> @@ -753,16 +830,21 @@ dl_open_worker (void *a)
>       namespace.  */
>    if (!args->libc_already_loaded)
>      {
> +      /* If this is a secondary (nsid != LM_ID_BASE) namespace then
> +         it is POSSIBLE there's no libc_map at all - We use the one
> +         shared with LM_ID_BASE instead (which MUST already be
> +         initialised for us to even reach here).  */
>        struct link_map *libc_map = GL(dl_ns)[args->nsid].libc_map;
>  #ifdef SHARED
> -      bool initial = libc_map->l_ns == LM_ID_BASE;
> +      bool initial = (libc_map != NULL) && (libc_map->l_real->l_ns == LM_ID_BASE);
>  #else
>        /* In the static case, there is only one namespace, but it
>  	 contains a secondary libc (the primary libc is statically
>  	 linked).  */
>        bool initial = false;
>  #endif
> -      _dl_call_libc_early_init (libc_map, initial);
> +      if (libc_map != NULL)
> +        _dl_call_libc_early_init (libc_map, initial);
>      }
>  
>  #ifndef SHARED
> @@ -787,10 +869,27 @@ dl_open_worker (void *a)
>    if (mode & RTLD_GLOBAL)
>      add_to_global_update (new);
>  
> +  if (__glibc_unlikely (want_proxy))
> +    {
> +      /* args->map is the return slot which the caller sees, but keep
> +         the original value of new hanging around so we can see the
> +         real link map entry (for logging etc).  */
> +      args->map = _dl_new_proxy (new, mode, proxy_ns);
> +      ++args->map->l_direct_opencount;
> +    }
> +
>    /* Let the user know about the opencount.  */
>    if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_FILES))
> -    _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
> -		      new->l_name, new->l_ns, new->l_direct_opencount);
> +    {
> +      _dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
> +                        new->l_name, new->l_ns, new->l_direct_opencount);
> +
> +      if (args->map->l_proxy)
> +        _dl_debug_printf ("proxying file=%s [%lu]; direct_opencount=%u\n\n",
> +                          args->map->l_name,
> +                          args->map->l_ns,
> +                          args->map->l_direct_opencount);
> +    }
>  }
>  
>  void *
> diff --git a/elf/dl-sym.c b/elf/dl-sym.c
> index dfd6169e12..b7252804ad 100644
> --- a/elf/dl-sym.c
> +++ b/elf/dl-sym.c
> @@ -96,6 +96,10 @@ do_sym (void *handle, const char *name, void *who,
>      {
>        match = _dl_sym_find_caller_link_map (caller);
>  
> +      /* Proxies don't contain any symbols: Need to look at the real DSO.  */
> +      if (__glibc_unlikely (match->l_proxy))
> +	match = match->l_real;
> +
>        /* Search the global scope.  We have the simple case where
>  	 we look up in the scope of an object which was part of
>  	 the initial binary.  And then the more complex part
> @@ -140,6 +144,11 @@ RTLD_NEXT used in code not dynamically loaded"));
>  	}
>  
>        struct link_map *l = match;
> +
> +      /* Proxies don't contain any symbols: Need to look at the real DSO.  */
> +      if (__glibc_unlikely (l->l_proxy))
> +	l = l->l_real;
> +
>        while (l->l_loader != NULL)
>  	l = l->l_loader;
>  
> @@ -150,6 +159,11 @@ RTLD_NEXT used in code not dynamically loaded"));
>      {
>        /* Search the scope of the given object.  */
>        struct link_map *map = handle;
> +
> +      /* Proxies don't contain any symbols: Need to look at the real DSO.  */
> +      if (__glibc_unlikely (map->l_proxy))
> +	map = map->l_real;
> +
>        result = GLRO(dl_lookup_symbol_x) (name, map, &ref, map->l_local_scope,
>  					 vers, 0, flags, NULL);
>      }
> diff --git a/elf/rtld.c b/elf/rtld.c
> index 94a00e2049..e02ae18c1d 100644
> --- a/elf/rtld.c
> +++ b/elf/rtld.c
> @@ -660,7 +660,7 @@ dlmopen_doit (void *a)
>    struct dlmopen_args *args = (struct dlmopen_args *) a;
>    args->map = _dl_open (args->fname,
>  			(RTLD_LAZY | __RTLD_DLOPEN | __RTLD_AUDIT
> -			 | __RTLD_SECURE),
> +			 | __RTLD_SECURE | RTLD_ISOLATE),
>  			dl_main, LM_ID_NEWLM, _dl_argc, _dl_argv,
>  			__environ);
>  }
> diff --git a/include/link.h b/include/link.h
> index 6ed35eb808..55e0cad71d 100644
> --- a/include/link.h
> +++ b/include/link.h
> @@ -107,8 +107,9 @@ struct link_map
>         They may change without notice.  */
>  
>      /* This is an element which is only ever different from a pointer to
> -       the very same copy of this type for ld.so when it is used in more
> -       than one namespace.  */
> +       the very same copy of this type when:
> +       - A shallow copy of ld.so is placed in namespaces other than LM_ID_BASE.
> +       - An object is proxied into a namespace by dlmopen with RTLD_SHARED.  */
>      struct link_map *l_real;
>  
>      /* Number of the namespace this link map belongs to.  */
> @@ -180,6 +181,7 @@ struct link_map
>      unsigned int l_relocated:1;	/* Nonzero if object's relocations done.  */
>      unsigned int l_init_called:1; /* Nonzero if DT_INIT function called.  */
>      unsigned int l_global:1;	/* Nonzero if object in _dl_global_scope.  */
> +    unsigned int l_proxy:1;    /* Nonzero if object is a shallow copy.  */
>      unsigned int l_reserved:2;	/* Reserved for internal use.  */
>      unsigned int l_phdr_allocated:1; /* Nonzero if the data structure pointed
>  					to by `l_phdr' is allocated.  */
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 08791fbc0f..641176e7d9 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -978,6 +978,11 @@ extern lookup_t _dl_lookup_symbol_x (const char *undef,
>  				     struct link_map *skip_map)
>       attribute_hidden;
>  
> +/* Proxy an existing link map entry into a new link map */
> +extern struct link_map *_dl_new_proxy (struct link_map *old,
> +				       int mode,
> +				       Lmid_t nsid)
> +     attribute_hidden;
>  
>  /* Restricted version of _dl_lookup_symbol_x.  Searches MAP (and only
>     MAP) for the symbol UNDEF_NAME, with GNU hash NEW_HASH (computed
> diff --git a/sysdeps/mips/bits/dlfcn.h b/sysdeps/mips/bits/dlfcn.h
> index 5cec898de3..898797f863 100644
> --- a/sysdeps/mips/bits/dlfcn.h
> +++ b/sysdeps/mips/bits/dlfcn.h
> @@ -32,6 +32,16 @@
>     visible as if the object were linked directly into the program.  */
>  #define RTLD_GLOBAL	0x0004
>  
> +/* If the following bit is set in the MODE argument to dlmopen
> +   then the target object is loaded into the main namespace (if
> +   it is not already there) and a shallow copy (proxy) is placed
> +   in the target namespace: This allows multiple namespaces to
> +   share a single instance of a DSO.  */
> +#define RTLD_SHARED 0x00020
> +
> +/* Suppress RTLD_SHARED and/or DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE  */
> +#define RTLD_ISOLATE 0x00040
> +
>  /* Unix98 demands the following flag which is the inverse to RTLD_GLOBAL.
>     The implementation does this by default and so we can define the
>     value to zero.  */
> 

  reply	other threads:[~2021-05-28 17:32 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-22 15:41 [RFC][PATCH v10 0/7] Implementation of RTLD_SHARED for dlmopen Vivek Das Mohapatra
2021-03-22 15:41 ` [RFC][PATCH v10 1/7] Define a new dynamic section tag - DT_GNU_FLAGS_1 (bug 22745) Vivek Das Mohapatra
2021-05-28 14:53   ` Adhemerval Zanella
2021-03-22 15:41 ` [RFC][PATCH v10 2/7] Abstract loaded-DSO search code into a helper function Vivek Das Mohapatra
2021-05-28 14:53   ` Adhemerval Zanella
2021-03-22 15:41 ` [RFC][PATCH v10 3/7] Use the new DSO finder " Vivek Das Mohapatra
2021-05-28 14:53   ` Adhemerval Zanella
2021-03-22 15:41 ` [RFC][PATCH v10 4/7] Add DT_GNU_FLAGS_1/DF_GNU_1_UNIQUE to glibc DSOs (bug 22745) Vivek Das Mohapatra
2021-05-28 14:53   ` Adhemerval Zanella
2021-05-28 16:59     ` Adhemerval Zanella
2021-05-28 17:18     ` Andreas Schwab
2021-03-22 15:41 ` [RFC][PATCH v10 5/7] Implement dlmopen RTLD_SHARED flag " Vivek Das Mohapatra
2021-05-28 17:32   ` Adhemerval Zanella [this message]
2021-05-28 18:02     ` Adhemerval Zanella
2021-06-02 15:42       ` Vivek Das Mohapatra
2021-05-28 23:22     ` Vivek Das Mohapatra
2021-03-22 15:41 ` [RFC][PATCH v10 6/7] Add dlmopen / RTLD_SHARED tests Vivek Das Mohapatra
2021-03-22 15:41 ` [RFC][PATCH v10 7/7] Restore separate libc loading for the TLS/namespace storage test Vivek Das Mohapatra
2021-03-25 15:15 ` [RFC][PATCH v10 0/7] Implementation of RTLD_SHARED for dlmopen Vivek Das Mohapatra
2021-04-13 17:02 ` Vivek Das Mohapatra
2021-04-29 15:25   ` Vivek Das Mohapatra
2021-05-11 17:42     ` Vivek Das Mohapatra
2021-05-17 19:08       ` [PING][PATCH " Vivek Das Mohapatra

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=fe028af6-794c-bb58-2fd7-f5efbe60a2c3@linaro.org \
    --to=adhemerval.zanella@linaro.org \
    --cc=libc-alpha@sourceware.org \
    --cc=vivek@collabora.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).