This is an archive of the discontinued LLVM Phabricator instance.

[sanitizer] Fix runtime crash on 32-bit Linux with glibc 2.27
AbandonedPublic

Authored by Lekensteyn on May 9 2018, 7:49 AM.

Details

Summary

The calling convention for _dl_get_tls_static_info symbol has changed on
32-bit systems since glibc 2.27. Make sure to support older and newer
32-bit glibc versions with the same runtime library.

Reuse the version check from InitTlsSize. Use gnu_get_libc_version
rather than confstr(_CS_GNU_LIBC_VERSION) because the latter is
intercepted by msan. This symbol existed since February 1998.

This patch does not try to address other issues with glibc 2.25.
Tested with 32/64-bit glibc 2.26/2.27 on Arch Linux.

Fixes https://github.com/google/sanitizers/issues/954


This is an alternative patch to D44623 by Jakub, addressing some review comments from there (reduce ifdef magic, coding style) and using a different approach for version detection. If desired, GetGlibVersion runtime could be guarded under a __GLIBC_PREREQ check, but there is potentially much more code that could be hidden that way so I did not do this.

Test: all check-sanitizers tests pass on Linux with glibc 2.27 except SanitizerCommon-asan-i386-Linux (unrelated) and SanitizerCommon-lsan-x86_64-Linux (not sure, probably also unrelated).

Diff Detail

Event Timeline

Lekensteyn created this revision.May 9 2018, 7:49 AM
jakubjelinek added inline comments.May 9 2018, 8:04 AM
lib/sanitizer_common/sanitizer_linux_libcdep.cc
165

So you both call dlsym (instead of the function directly) and then parse the string? That is significantly larger and more expensive than just calling the dlvsym. Furthermore, other code in the sanitizers use confstr for this purpose.

202

Ugh, so you call this unconditionally on all architectures, just because one needs it?

Lekensteyn added inline comments.May 9 2018, 8:28 AM
lib/sanitizer_common/sanitizer_linux_libcdep.cc
165

Detecting glibc 2.27 by lookup up the glob symbol seems a bit indirect, that is why I preferred an approach that directly checks the version.

There is only one confstr(_CS_GNU_LIBC_VERSION) user (which got removed below). String parsing costs should be negligible and there are enough dlsym calls for interceptors such that this additional one should not matter.

I think that the few cycles extra here is worth the lower code maintenance cost that would otherwise be necessary for indirectly retrieving strconf without breaking msan. Correct me if I am wrong :)

202

What about this?

int minor = 0, patch = 0;
if (CHECK_GET_TLS_STATIC_INFO_VERSION) GetGlibcVersion(&minor, &patch);
if (CHECK_GET_TLS_STATIC_INFO && minor && minor < 27) {

Alternative: make GetGlibcVersion return (minor << 8) | patch or a large number if parsing failed for some reason. Then: if (CHECK_GET_TLS_STATIC_INFO && GetGlibcVersion() < 27).

Lekensteyn abandoned this revision.May 9 2018, 10:17 AM

D44623 looks better now, the version check of this patch has some more issues than worth solving.

vitalybuka added inline comments.May 9 2018, 11:17 AM
lib/sanitizer_common/sanitizer_linux_libcdep.cc
160

I'd split this into two patches:

  1. Extract code into GetGlibcVersion
  2. GetTls changes
202

I am not sure that we need these
CHECK_EQ(sizeof(get_tls), sizeof(get_tls_static_info_ptr))
and
internal_memcpy
If not we can go with:

'''
static bool IsGetTlsInternal() {
if (!CHECK_GET_TLS_STATIC_INFO_VERSION)

return false;

int minor;
GetGlibcVersion(&minor, &patch);
return minor && minor < 27;
}

static void GetTlsSize(size_t *tls_size, size_t *tls_align) {

void *get_tls_static_info_ptr = dlsym(RTLD_NEXT, "_dl_get_tls_static_info");
CHECK_NE(get_tls_static_info_ptr, 0);
if (IsGetTlsInternal()) {
  typedef void (*get_tls_func)(size_t *, size_t *);
  ((get_tls_func)get_tls_static_info_ptr)(tls_size, tls_align);
} else {
  typedef void (*get_tls_func)(size_t *, size_t *) DL_INTERNAL_FUNCTION;
  ((get_tls_func)get_tls_static_info_ptr)(tls_size, tls_align);
}

}
'''