Bug 24304 - Lazy binding failure during ELF constructors/destructors is not fatal
Summary: Lazy binding failure during ELF constructors/destructors is not fatal
Status: RESOLVED FIXED
Alias: None
Product: glibc
Classification: Unclassified
Component: dynamic-link (show other bugs)
Version: 2.30
: P2 normal
Target Milestone: 2.31
Assignee: Florian Weimer
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-03-05 12:29 UTC by Florian Weimer
Modified: 2019-11-27 21:22 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Florian Weimer 2019-03-05 12:29:20 UTC
The dlopen and dlclose operation is wrapped in exception handlers, _dl_catch_error.  Constructors and destructors are invoked under these handlers, which means that if the lazy binding trampoline detects an undefined symbol in an ELF constructor or destructor, this error is not a fatal error that terminates the process.  Instead, the dlopen or dlclose call fails.

# Reproducer

cat > undefined_function.c <<EOF
#include <stdlib.h>
#include <stdio.h>

void undefined_function (void);

static void __attribute__ ((constructor))
init (void)
{
  puts ("init is running.");
  if (getenv ("RUN_IN_INIT") != NULL)
    undefined_function ();
}

static void __attribute__ ((destructor))
fini (void)
{
  puts ("fini is running.");
  if (getenv ("RUN_IN_FINI") != NULL)
    undefined_function ();
}
EOF
cat > undefined-initfini.c <<EOF
#include <dlfcn.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

int
main (void)
{
  clearenv ();
  setenv ("RUN_IN_INIT", "1", 1);
  void *handle = dlopen ("./libundefined_function.so", RTLD_LAZY);
  if (handle == NULL)
    printf ("non-fatal error: %s\n", dlerror ());
  else
    printf ("unexpected dlopen success: %p\n", handle);

  clearenv ();
  handle = dlopen ("./libundefined_function.so", RTLD_LAZY);
  if (handle != NULL)
    {
      setenv ("RUN_IN_FINI", "1", 1);
      if (dlclose (handle) != 0)
        printf ("non-fatal dlclose error: %s\n", dlerror ());
    }
  else
    printf ("second dlopen failed: %s\n", dlerror ());

  return 0;
}
EOF
gcc -D_GNU_SOURCE -O2 -Wall -shared -fPIC -o libundefined_function.so -g -Wl,-z,lazy undefined_function.c
gcc -D_GNU_SOURCE -O2 -Wall -o undefined-initfini -g undefined-initfini.c -ldl
./undefined-initfini

# Output

non-fatal error: ./libundefined_function.so: undefined symbol: undefined_function
non-fatal dlclose error: ./libundefined_function.so: undefined symbol: undefined_function
./undefined-initfini: symbol lookup error: ./libundefined_function.so: undefined symbol: undefined_function

As you can see, fini is invoked twice, the second time at process termination.  At that point, there is no exception handler installed, so the lazy binding error is fatal.
Comment 1 Florian Weimer 2019-03-05 12:29:49 UTC
Sorry, the output is actually:

init is running.
fini is running.
non-fatal error: ./libundefined_function.so: undefined symbol: undefined_function
init is running.
fini is running.
non-fatal dlclose error: ./libundefined_function.so: undefined symbol: undefined_function
fini is running.
./undefined-initfini: symbol lookup error: ./libundefined_function.so: undefined symbol: undefined_function
Comment 2 Florian Weimer 2019-10-17 09:21:23 UTC
Patch posted: https://sourceware.org/ml/libc-alpha/2019-10/msg00516.html
Comment 3 Sourceware Commits 2019-11-27 20:21:02 UTC
The master branch has been updated by Florian Weimer <fw@sourceware.org>:

https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=79e0cd7b3c997e211fad44a81fd839dc5b2546e8

commit 79e0cd7b3c997e211fad44a81fd839dc5b2546e8
Author: Florian Weimer <fweimer@redhat.com>
Date:   Wed Nov 27 16:20:47 2019 +0100

    Lazy binding failures during dlopen/dlclose must be fatal [BZ #24304]
    
    If a lazy binding failure happens during the execution of an ELF
    constructor or destructor, the dynamic loader catches the error
    and reports it using the dlerror mechanism.  This is undesirable
    because there could be other constructors and destructors that
    need processing (which are skipped), and the process is in an
    inconsistent state at this point.  Therefore, we have to issue
    a fatal dynamic loader error error and terminate the process.
    
    Note that the _dl_catch_exception in _dl_open is just an inner catch,
    to roll back some state locally.  If called from dlopen, there is
    still an outer catch, which is why calling _dl_init via call_dl_init
    and a no-exception is required and cannot be avoiding by moving the
    _dl_init call directly into _dl_open.
    
    _dl_fini does not need changes because it does not install an error
    handler, so errors are already fatal there.
    
    Change-Id: I6b1addfe2e30f50a1781595f046f44173db9491a
Comment 4 Florian Weimer 2019-11-27 21:22:00 UTC
Fixed for glibc 2.31.