I'm new to TLS implementation details, but if I understand correctly,
this model is a static one, meaning that a variable address is known and
it's accessed directly, like a normal variable, as opposed to dynamic
TLS models where a variable address is first queried with
__tls_get_addr(). This is all transparent to the program because the
compiler is responsible for generating the appropriate code, depending
on the model.
In the case of a direct link like glxgears, our rtld (ld-elf.so)
allocates space during startup to copy static TLS variables from the
program and all linked libraries. libGL.so finds its variables where it
expects them to be, glxgears is happy.
In the case of a dlopen(3) like Firefox, our rtld maps the dlopen'd
object and all its linked libraries but it doesn't look for any static
TLS variables. libGL.so accesses the allocated TLS storage (there is a
small extra chunk of zero'd memory allocated) but its variables were not
copied. So it gets a NULL pointer, dereferences it, End of the World.
A first workaround is to LD_PRELOAD libGL.so or link the program
directly to libGL.so.
Another solution is the following: in the Glibc (quite popular these
days), they allocate extra static TLS space beside the size of the TLS
variables available at startup (ie. TLS variables from the program and
linked libraries). Then, when a library is dlopen'd with static TLS
variables, they are copied to this extra space. This space is not
dynamically extended, so first loaded, first served. If there is no
space left, I think dlopen(3) fails.
The command even says that this extra space is allocated specifically
for dynamic modules. However, I don't see where we use this space.
dlopen_object() doesn't mess with TLS at all (or I'm missing something).
This is, generally speaking, illegal use of initial-exec, but since
this appears to be the common practice Linux supports, so should we.
I am working on a patch that will support this type of TLS abuse and I
will send you one for testing once complete.