Bug 252307

Summary: dlopen (without RTLD_GLOBAL) overrides weak symbols in libc
Product: Base System Reporter: Alex S <iwtcex>
Component: binAssignee: freebsd-bugs (Nobody) <bugs>
Status: New ---    
Severity: Affects Some People CC: Alexander88207, emaste, gerald, kib, robert.ayrapetyan
Priority: ---    
Version: 12.2-RELEASE   
Hardware: Any   
OS: Any   

Description Alex S 2020-12-31 16:08:30 UTC
See the comment at https://bugs.winehq.org/show_bug.cgi?id=50257#c20 for context.

% cat weak_sym_override_test.c 
#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

#if SHLIB
void* calloc(size_t number, size_t size) {
  printf("libc called\n");
  exit(1);
}
#else
int main() {
  assert(dlopen("override.so", RTLD_NOW) != NULL);
  setenv("WHATEVER", "1", 0); // uses calloc internally
  return 0;
}
#endif
% cc -shared -fPIC -DSHLIB weak_sym_override_test.c -o override.so && cc weak_sym_override_test.c -Wl,-rpath,. -o test
% ./test 
libc called

Doesn't happen with LD_BIND_NOW=1. No idea how that works on Linux, glibc consistently avoids calling weak symbols it exports, preferring internal versions prefixed with with __ (two underscores) instead, thus there is no obvious way to test it.
Comment 1 Konstantin Belousov freebsd_committer 2020-12-31 22:04:35 UTC
Yes this is really a libc problem, not rtld.  We do not use internal
namespace consistently.

OTOH for malloc(3) related functions, I suspect the decision was deliberate
to make it possible to interpose system implementation with any other user
provided with LD_PRELOAD.
Comment 2 Alex S 2020-12-31 22:24:17 UTC
> OTOH for malloc(3) related functions, I suspect the decision was deliberate
to make it possible to interpose system implementation with any other user
provided with LD_PRELOAD.

Well, I tested (before filling the bug) whether this works in general and, as expected, it doesn't:

% cat weak_sym_override_test2.c 
#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(ORIG)
void __attribute__((weak)) test() {
  printf("original\n");
}
void wrapper() {
  test();
}
#elif defined(OVERRIDE)
void test() {
  printf("override\n");
}
#else
extern void wrapper();
int main() {
  assert(dlopen("override.so", RTLD_NOW) != NULL);
  wrapper();
  return 0;
}
#endif

% cc -shared -fPIC -DORIG weak_sym_override_test2.c -o orig.so
% cc -shared -fPIC -DOVERRIDE weak_sym_override_test2.c -o override.so
% cc weak_sym_override_test2.c orig.so -Wl,-rpath,. -o test
% ./test 
original

Did I miss some compilation flags or something? How do I deliberately get this effect?
Comment 3 Konstantin Belousov freebsd_committer 2021-01-02 04:27:56 UTC
You need to LD_PRELOAD your interposer, I noted it in my comment #1.
solo% LD_PRELOAD=./override.so ./test
override

In your example, dlopened object would be added at the end of the global list
and in fact its symbols are interposed by the objects loaded at startup, i.e.
the effect is reverse. (And your example misses RTLD_GLOBAL, but does not matter
much).