Subject: bug#53005: cryptsetup-static aborts opening LUKS2 volume with Argon2i PBKDF

From: Simon South <[email-masked]>
Date: Tue, 04 Jan 2022 09:44:52 -0500

This seems to be caused by the symbol "__pthread_key_create" being
absent from cryptsetup's symbol table during linking, even though it is
meant to be exported explicitly by glibc.

The fundamental issue is that cryptsetup's Argon2i implementation (and
thus cryptsetup itself) is multithreaded, but libgcc[0], a low-level
runtime library used by the compiler, is unaware of this fact.
Consequently libgcc's stack-unwinding code skips the use of a mutex that
would normally synchronize data access among multiple threads, allowing
a race condition to develop when two or more threads try to exit
simultaneously.

One thread will fail to locate its frame-descriptor entry, causing NULL
to be returned by libgcc's _Unwind_Find_FDE() function
(libgcc/unwind-dw2-fde.c:1029), leading to an assertion failing (in
uw_init_context_1() at libgcc/unwind-dw2.c:1593) and abort() being
called, terminating the process.

The underlying failure is indicated in a comment in gcc's code, at
libgcc/gthr-posix.h:215.  libgcc uses the presence of specific symbols
in a program's symbol table to infer whether or not the program is
multithreaded:

   For a program to be multi-threaded the only thing that it certainly
   must be using is pthread_create.  However, there may be other
   libraries that intercept pthread_create with their own definitions to
   wrap pthreads functionality for some purpose.  In those cases,
   pthread_create being defined might not necessarily mean that
   libpthread is actually linked in.

   For the GNU C library, we can use a known internal name.  This is
   always available in the ABI, but no other library would define it.
   That is ideal, since any public pthread function might be intercepted
   just as pthread_create might be.  __pthread_key_create is an
   "internal" implementation symbol, but it is part of the public
   exported ABI.  Also, it's among the symbols that the static
   libpthread.a always links in whenever pthread_create is used, so
   there is no danger of a false negative result in any
   statically-linked, multi-threaded program.

It seems the final sentence is no longer true, at least in recent
versions of Guix.

Building the "cryptsetup-static" package with "#:strip-binaries? #f" and
dumping the resulting binary's symbol table with "objdump -t" shows
"pthread_create" is present but not "__pthread_key_create".  This seems
to be why libgcc's __gthread_active_p() always returns false, turning
wrapper functions like __gthread_mutex_lock() into no-ops and
effectively disabling the use of the mutex in _Unwind_Find_FDE().

The question then is why this symbol either is no longer being exported
by glibc or is being dropped at some point during cryptsetup's
compilation.  (Other packages may be affected as well: Even the regular,
dynamically-linked cryptsetup shows this problem, but avoids a crash by
not invoking libgcc's stack-unwinding routines.)

I'll keep working on this but having gotten this far, I'm hoping someone
else has some insight.

[0] https://gcc.gnu.org/onlinedocs/gccint/Libgcc.html

-- 
Simon South
[email-masked]