mailing list of musl libc
 help / color / mirror / code / Atom feed
* Re: dlsym(handle) may search in unrelated libraries
@ 2019-02-09 22:53 Alexey Izbyshev
  2019-02-10  1:03 ` Rich Felker
  0 siblings, 1 reply; 24+ messages in thread
From: Alexey Izbyshev @ 2019-02-09 22:53 UTC (permalink / raw)
  To: musl

(Replying to https://www.openwall.com/lists/musl/2019/02/07/7)

> On Thu, Feb 07, 2019 at 07:36:38PM +0100, Markus Wichmann wrote:
> > On Thu, Feb 07, 2019 at 11:54:47AM -0500, Rich Felker wrote:
> > > I'm not sure if it makes sense to build both of these lists at the
> > > same time. We currently try to avoid allocating dependency lists for
> > > libraries loaded at startup in order not to allocate and setup data
> > > that might never be used, defering until if/when dlopen is called on
> > > them. I've wanted to do the ctor order walk without allocating a
> > > dependency list, but I don't know a good way to do so. Note that the
> > > code that runs ctors cannot allocate because, at the time it runs,
> > > dlopen has already "committed" the load and can't back it out. It has
> > > to have all resources it needs for the walk precommitted.
> >
> > Depth first means stack. Means recursion or explicit stack. Explicit is
> > going to be hard, considering there is no known limit to dependency
> > nesting depth. We could argue that the user better make their stack size
> > ulimit large enough for the main thread, but I hazard a guess that
> > nobody expects dlopen() to use overly much stack in other threads.
> >
> > Alright, what's the algorithm here?
> >
> > init(lib):
> >     if (!lib.inited):
> >         foreach d in lib.deps init(d)
> >         start_init(lib)
> >         lib.inited = 1
> >
> > That about right? Because that means we need a stack as high as the
> > nesting depth of dependencies. We can get a pessimistic estimation from
> > the number of deps loaded, but that may be way too high (see below).
> 
> Yes, but you can also avoid recursion just by looping to the deepest
> dependency with !inited, then going back to the root. For a one-time
> operation at dlopen-time or program-start time, the quadratic search
> for each !inited seems unlikely to be a problem:
> 
> > > Since the beginning of a list of breadth-first dependencies is just
> > > the direct dependencies, if we recorded the number of direct
> > > dependencies for each dso, the breadth-first lists could be used to
> > > perform a depth-first walk to run ctors; this can be made
> > > non-recursive, at the cost of quadratic time but with trivially-tiny
>                    
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > constant factor, by simply restarting at the root of the tree after
>     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > each node and finding the deepest not-yet-constructed dso. This
>     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

We'd need breadth-first lists for all dsos to implement DFS as you 
suggest because a single breadth-first list for the root is not enough 
to reconstruct all edges of the dependency graph even if we know the 
number of direct dependencies for each dso (we need to process each edge 
for DFS to work correctly). Or am I misunderstanding your suggestion?

If we're willing to add some fields to struct dso to implement the 
correct ctor order, I'd suggest to use a non-recursive DFS with an 
explicit stack instead. We'd need just two fields in each stack slot 
(dso and the number of processed DT_NEEDED entries), so the memory 
overhead is low compared to sizeof(struct dso). The stack can be 
preallocated before "committing" dlopen(). As Markus said, the upper 
bound is the number of dsos loaded with this dlopen() call because all 
other dsos are already constructed, so we don't need to visit them. This 
stack can be freed immediately after ctors are run. We already have 
dso->constructed to mark visited dsos, and we already use another list 
for finalization (dso->fini_next), and that list would be built 
naturally by DFS.

Alexey


^ permalink raw reply	[flat|nested] 24+ messages in thread
* dlsym(handle) may search in unrelated libraries
@ 2019-02-05 21:02 Alexey Izbyshev
  2019-02-06 13:40 ` Alexey Izbyshev
  2019-02-06 16:02 ` Markus Wichmann
  0 siblings, 2 replies; 24+ messages in thread
From: Alexey Izbyshev @ 2019-02-05 21:02 UTC (permalink / raw)
  To: musl

Hello!

I've discovered a bug in musl dynamic loader (tested on 1.1.21) which is 
demonstrated by the following simple example:

$ cat bar.c
int bar = 42;
$ musl-gcc -fPIC -shared bar.c -o libbar.so
$ cat foo.c
extern int bar;
int *foo = &bar;
$ musl-gcc -fPIC -shared foo.c -L. -lbar -Wl,-rpath='$ORIGIN' -o 
libfoo.so
$ cat main.c
#include <dlfcn.h>
#include <stdio.h>

int main(void) {
   if (!dlopen("libfoo.so", RTLD_NOW))
     return 1;
   void *h = dlopen("libc.so.6", RTLD_NOW);
   printf("%p\n", dlsym(h, "bar"));
}
$ musl-gcc main.c -Wl,-rpath='$ORIGIN' -ldl
$ ./a.out
0x7fd7ebe96020

dlsym(handle) is supposed to search only in the library referred to by 
the handle and in its dependencies. "libc.so.6" doesn't have 
dependencies and doesn't have a definition for "bar", so dlsym(h, "bar") 
should return NULL, but it finds "bar" in libbar.so instead.

The problem occurs because of the following:
1) Initially, "deps" in dso structure for libc.so.6 is NULL.
2) When dlopen("libc.so.6") is called, "first_load" is true, despite 
that it's not actually the first load (ldso/dynlink.c:1835):

     /* First load handling */
     int first_load = !p->deps;
     if (first_load) {
         load_deps(p);

3) load_deps() then iterates over the dso list starting from 
"libc.so.6", treating all libraries found in DT_NEEDED of each processed 
dso as dependencies of "libc.so.6". However, the dso list already 
contains "libfoo.so" loaded earlier, so "libbar.so" (which is needed by 
"libfoo.so") is treated as a dependency of "libc.so.6". As a result, 
dlsym(h, "bar") succeeds.

It's also notable that "libfoo.so" and "libbar.so" were loaded with 
RTLD_LOCAL, but this bug effectively makes their symbols available in 
such searches regardless of the scope of a library used with dlsym().

ISTM that load_deps(p) was written to work only in real "first load" 
situations, where "p" is initially the last dso in the list, and new 
dsos are only added to the list in the course of recursive loading of 
the dependencies of "p".

Could this be fixed? Thanks!

(Please CC me on replying, I'm not subscribed to the list.)

Alexey


^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2019-03-04  2:11 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-02-09 22:53 dlsym(handle) may search in unrelated libraries Alexey Izbyshev
2019-02-10  1:03 ` Rich Felker
2019-02-26 15:07   ` Rich Felker
2019-03-04  2:11     ` Rich Felker
  -- strict thread matches above, loose matches on Subject: below --
2019-02-05 21:02 Alexey Izbyshev
2019-02-06 13:40 ` Alexey Izbyshev
2019-02-06 16:02 ` Markus Wichmann
2019-02-06 17:02   ` Alexey Izbyshev
2019-02-06 20:25     ` Markus Wichmann
2019-02-06 21:23       ` Alexey Izbyshev
2019-02-07  5:33         ` Markus Wichmann
2019-02-07 13:42           ` Alexey Izbyshev
2019-02-07 17:43             ` Markus Wichmann
2019-02-07 20:37               ` Markus Wichmann
2019-02-07 21:29               ` Rich Felker
2019-02-07 16:54           ` Rich Felker
2019-02-07 18:36             ` Markus Wichmann
2019-02-07 18:57               ` Rich Felker
2019-02-07 20:31                 ` Markus Wichmann
2019-02-07 21:33                   ` Rich Felker
2019-02-07 21:37                     ` Rich Felker
2019-02-08 10:19             ` A. Wilcox
2019-02-08 12:00               ` Szabolcs Nagy
2019-02-08 16:09                 ` Rich Felker

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/musl/

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 NNTP newsgroup(s).