This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Detecting dlclose() on an already closed handle?


In elf/dl-close.c we have this code:

  if (__builtin_expect (map->l_direct_opencount, 1) == 0)
    _dl_signal_error (0, map->l_name, NULL, N_("shared object not open"));

This code only detects double dlclose() when the dlclose() is recursively
called by the same thread's own destructors otherwise it accesses potentially
unmapped memory (or memory allocated to something else) and results in
undefined behaviour (SIGSEGV, corrupting the new memory if it looks enough
like a struct link_map to allow the dlclose to continue).

We cannot catch  a double-dlclose reliably because the struct link_map
used to test for this might have been freed already.

The first dlclose drops the l_direct_opencount to 0, and the object _may_
be unmapped and the struct link_map freed.

The only way to reliably test for an error would be, AFAICT, to store the
information in the 'void*' whose storage is owned by the caller. You'd have
to use an ID and then never recycle the IDs. For 32-bit the ABA happens
pretty quickly if you're doing lots of dlopen/dlclose, and we could easily
use a larger counter internally to allow the ABA, at the cost of loosing
the ability to detect double dlclose() for a dlclose() that happened
4billion dlopen() calls ago.

POSIX says:
~~~
If the referenced symbol table handle was successfully closed, dlclose() 
shall return 0. If handle does not refer to an open symbol table handle 
or if the symbol table handle could not be closed, dlclose() shall 
return a non-zero value. More detailed diagnostic information shall 
be available through dlerror().
~~~

It appears that dlclose() should be able to detect the handle is not
open or invalid. We only support that reliably in one scenario:
where you recursively call dlclose() and we detect it. All other
detections, like an outright double dlclose(), result in reading
memory that was already freed e.g.

Valgrind notes the access of map->l_flags_1 to check for DF_1_NODELETE
from memory already freed on the second dlclose():

==8526== Invalid read of size 1
==8526==    at 0x4014D61: _dl_close (dl-close.c:820)
==8526==    by 0x5157C60: _dl_catch_error (dl-error-skeleton.c:198)
==8526==    by 0x53D8598: _dlerror_run (dlerror.c:163)
==8526==    by 0x53D805E: dlclose (dlclose.c:46)
==8526==    by 0x400F709: _dl_fini (dl-fini.c:235)
==8526==    by 0x506EA6F: __run_exit_handlers (exit.c:83)
==8526==    by 0x506EAC9: exit (exit.c:105)
==8526==    by 0x50594C7: (below main) (libc-start.c:320)

Arguably glibc trades off complexity for what is effectively a user
bug that must be detected by heap consistency checkers. It rarely
leads to a SIGSEGV because of malloc's chunk cache.

However, POSIX appears to allow it, and using ID-based handles is
an attractive solution. It's also not always easy to determine if
you have already called dlclose() on a handle without additional
synchronization, and making that fail-safe certainly helps application
developers. The problem is that with ABA on 32-bit the second dlclose()
might close a different library, a problem which already exists today
since malloc may return the same address for a struct link_map and the
second dlclose() may close that new library, so it's no worse.

Any thoughts about how to handle this reliably?

-- 
Cheers,
Carlos.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]