7

In the following minimal example, a library loaded via LD_PRELOAD with functions to intercept fopen and openat is apparently operating before its initialization. (Linux is CentOS 7.3). Why??

library file comm.c:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <fcntl.h>

typedef FILE *(*fopen_type)(const char *, const char *);

// initialize to invalid value (non-NULL)
// init() should initialize this correctly
fopen_type g_orig_fopen = (fopen_type) 1;

typedef int (*openat_type)(int, const char *, int, ...);
openat_type g_orig_openat;

void init() {
    g_orig_fopen = (fopen_type)dlsym(RTLD_NEXT,"fopen");
    g_orig_openat = (openat_type)dlsym(RTLD_NEXT,"openat");
}

FILE *fopen(const char *filename, const char *mode) {
    // have to do this here because init is not called yet???
    FILE * const ret = ((fopen_type)dlsym(RTLD_NEXT,"fopen"))(filename, mode);

    printf("g_orig_fopen %p  fopen file %s\n", g_orig_fopen, filename);
    return ret;
}

int openat(int dirfd, const char* pathname, int flags, ...) {
    int fd;
    va_list ap;

    printf("g_orig_fopen %p  openat file %s\n", g_orig_fopen, pathname);

    if (flags & (O_CREAT)) {
        va_start(ap, flags);
        fd = g_orig_openat(dirfd, pathname, flags, va_arg(ap, mode_t));
    }
    else
        fd = g_orig_openat(dirfd, pathname, flags);

    return fd;
}

compiled with:

gcc -shared  -fPIC -Wl,-init,init  -ldl comm.c -o comm.so

I have an empty subdirectory subdir. Then it appears the library function fopen is called before init:

#LD_PRELOAD=./comm.so find subdir
g_orig_fopen 0x1  fopen file /proc/filesystems
g_orig_fopen 0x1  fopen file /proc/mounts
subdir
g_orig_fopen 0x7f7b2e574620  openat file subdir
BurnsBA
  • 4,347
  • 27
  • 39
Mark Galeck
  • 6,155
  • 1
  • 28
  • 55
  • It's hard to say what can cause this behavior, everything seems to be set correctly. I've read that with some `gcc` versions `init` named constructor function is not called -> try to change its name for example to `my_init`. Also try to add `-nostartfiles` to `gcc` options in order to prevent linking of standard `_init` function. – SergeyLebedev Mar 14 '18 at 21:41
  • And as you probably know, with `gcc` you can use `__attribute__((constructor))` syntax to set constructor instead of linker `-init` option. It even allows to set many constructor functions that will be called successively. Use it! void con() { printf("I'm a constructor\n"); } – SergeyLebedev Mar 14 '18 at 21:55
  • @SergeyLebedev thank you Sergey, I really appreciate! No I did not know that `__attribute__`, I am a portable-type person. I did try your suggestions, I did double-check they are implemented, but unfortunately, none of them seem to work, I still have the same erroneous printout as above. I am willing to accept the explanation that 'there is a bug' and 'here is a reasonable workaround'. – Mark Galeck Mar 15 '18 at 06:50
  • @SergeyLebedev It's always the temporary "files" in `/proc` directory that are opened before `my_init()`. I am so sure someone with more Linux system experience than me can appreciate the significance of that. – Mark Galeck Mar 15 '18 at 07:13
  • @Mark. It may be that `selinux` library (mentioned in answer below) has higher constructor priority that yours. Refer to https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html and/or https://stackoverflow.com/questions/211237/static-variables-initialisation-order – Zbigniew Zagórski Mar 20 '18 at 12:49
  • @ZbigniewZagórski I don't understand... when `selinux` runs for the first time during the run of `find`, with higher priority or whatever, then if it wants to call `fopen` then at that point the initialization function of `comm.so` should run, before `fopen`. – Mark Galeck Mar 20 '18 at 18:37

1 Answers1

6

Obviously, fopen is called before initialization of comm.so. It is interesting to place a breakpoint in fopen() in order to understand (check this link in order to get debug symbols of various packages). I get this backtrace:

(gdb) bt
#0  fopen (filename=0x7ffff79cd2e7 "/proc/filesystems", mode=0x7ffff79cd159 "r") at comm.c:28
#1  0x00007ffff79bdb0e in selinuxfs_exists_internal () at init.c:64
#2  0x00007ffff79b5d98 in init_selinuxmnt () at init.c:99
#3  init_lib () at init.c:154
#4  0x00007ffff7de88aa in call_init (l=<optimized out>, argc=argc@entry=1, argv=argv@entry=0x7fffffffdf58, env=env@entry=0x7fffffffdf68) at dl-init.c:72
#5  0x00007ffff7de89bb in call_init (env=0x7fffffffdf68, argv=0x7fffffffdf58, argc=1, l=<optimized out>) at dl-init.c:30
#6  _dl_init (main_map=0x7ffff7ffe170, argc=1, argv=0x7fffffffdf58, env=0x7fffffffdf68) at dl-init.c:120
#7  0x00007ffff7dd9c5a in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#8  0x0000000000000001 in ?? ()
#9  0x00007fffffffe337 in ?? ()
#10 0x0000000000000000 in ?? ()

It is obvious, comm.so depends on other libraries (libdl.so that requires libselinux.so). And comm.so is not the only library that declare an init function. libdl.so and libselinux.so also declare ones.

So, comm.so is the first library to be loaded (because it is declared with LD_PRELOAD) but, comm.so depends on libdl.so (because of -ldl during compilation) and libdl.so depends on libselinux.so. So, in order to load comm.so, init functions from libdl.so and libselinux.so are called before. And finally, init function from libselinux.so call fopen()

Personally, I usually resolve dynamic symbols during first call to the symbol. Like this:

FILE *fopen(const char *filename, const char *mode) {
    static FILE *(*real_fopen)(const char *filename, const char *mode) = NULL;

    if (!real_fopen)
        real_fopen = dlsym(RTLD_NEXT, "fopen");

    return real_fopen(filename, mode);
}
Jérôme Pouiller
  • 9,249
  • 5
  • 39
  • 47
  • I am sorry, I don't understand... The documentation of the linker `-init` option says, this is the function that is executed when the executable is created. So, when I invoke find, the system starts to run it, at some point, somebody (`libselinux.so` for example), calls `fopen`, the first time that happens, before `fopen` actually runs, is that my `init` function should run. – Mark Galeck Mar 20 '18 at 18:34
  • also, please tell me, how do you "place a breakpoint in `fopen`" - you have the sources for standard library and recompile it with `-g` flag is that right? – Mark Galeck Mar 20 '18 at 20:56
  • `comm.so` is not the only library that declare an init function. `libdl.so` and `libselinux.so` also declare one. Because `comm.so` is declared with `LD_PRELOAD`, it is the first library to be loaded. BUT, `comm.so` depends on `libdl.so` (because of `-ldl` during compilation) and `libdl.so` depends on `libselinux.so`. So in order to load `comm.so`, init functions from `libdl.so` and `libselinux.so` are called. – Jérôme Pouiller Mar 20 '18 at 21:28
  • 1
    In order to obtain a good backtrace, you need debug symbols. On most of distributions, debug symbols are delivered in separated packages. On Debian, [add debug repositories](https://wiki.debian.org/HowToGetABacktrace#Installing_the_debugging_symbols), then install `libselinux1-dbgsym` and `libc6-amd64-dbgsym` – Jérôme Pouiller Mar 20 '18 at 21:36
  • 1
    ... instructions for other distributions are described [here](https://wiki.gnome.org/Community/GettingInTouch/Bugzilla/GettingTraces/DistroSpecificInstructions) – Jérôme Pouiller Mar 20 '18 at 21:42
  • I am sorry to take so much of your time - yes I understand that they all have their init functions called, that was not my problem. My problem is, when that other library has it's init function called, if that function calls `fopen` as that seems to be the case here, then, before `fopen` runs, my library has to be loaded, and when that happens, my `init` has to be called first. – Mark Galeck Mar 20 '18 at 23:38
  • `comm.so` depends on `libdl.so`. You can check that with `ldd comm.so`. So, `libdl.so` has to be initialized before `comm.so`. So, your library is not the first one to initialize. – Jérôme Pouiller Mar 21 '18 at 08:03
  • OK but... When another library (the one to initialize before mine) initializes and wants to call `fopen`, it has perhaps (at most) two choices: a. call the "original" one - that's OK, b. call the one from my library - that's OK too, before that can happen, my `init` has to be called. So in both cases everything should be OK. I am sorry, but I am reading your comments and I understand what you are saying, but I don't think you are reading my train of thought (as dumb as it may be). – Mark Galeck Mar 21 '18 at 15:23
  • I see... I don't think that this corner case is defined in any specification. If you think this behavior is wrong you can report a bug to glibc team. IMHO `glibc` developers will answer you that since behavior is not specified, libraries shouldn't rely on it (`glibc` is not the only `libc` in the world). In add, behavior you ask is difficult to implement and it is better to _Keep It Simple Stupid_. – Jérôme Pouiller Mar 21 '18 at 16:11
  • OK thank you, and in the light of this problem, I will switch to your method, much better than using init as I was doing. – Mark Galeck Mar 21 '18 at 21:57