TheBrokenRail
TheBrokenRail

Reputation: 43

Is it possible for an LD_PRELOAD to only affect the main executable?

The Actual Problem

I have an executable that by default uses EGL and SDL 1.2 to handle graphics and user input respectively. Using LD_PRELOAD, I have replaced both with GLFW.

This works normally unless the user has installed the Wayland version of GLFW, which depends on EGL itself. Because all the EGL calls are either stubbed to do nothing or call GLFW equivalents, it doesn't work (ie. eglSwapBuffers calls glfwSwapBuffers which calls eglSwapBuffers and so on). I can't remove the EGL stubs because then it would call both EGL and GLFW and the main executable is closed-source so I can't modify that.

Is there any way to make LD_PRELOAD affect the main executable but not GLFW? Or any other solution to obtain the same effect?

The Simplified Problem

I made a simplified example to demonstrate the problem.

Main Executable:

#include <stdio.h>

extern void do_something();

int main() {
    do_something();
    fputs("testing B\n", stderr);
}

Shared Library:

#include <stdio.h>

void do_something() {
    fputs("testing A\n", stderr);
}

Preloaded Library:

#include <stdio.h>

int fputs(const char *str, FILE *file) {
    // Do Nothing
    return 0;
}

When the preloaded library isn't used, the output is:

testing A
testing B

When it is used, the output is nothing.

I'm looking for a way to make the preloaded library only affect the main executable, that the output would be:

testing A

Thank you!

Upvotes: 4

Views: 908

Answers (2)

You can check if the return address is in the executable or the library, and then call either the "real" function or do your stub code, like this:

#define _GNU_SOURCE

#include <dlfcn.h>
#include <link.h>
#include <stdio.h>
#include <stdlib.h>

static struct {
    ElfW(Addr) start, end;
} *segments;
static int n;
static int (*real_fputs)(const char *, FILE *);

static int callback(struct dl_phdr_info *info, size_t size, void *data) {
    n = info->dlpi_phnum;
    segments = malloc(n * sizeof *segments);
    for(int i = 0; i < n; ++i) {
        segments[i].start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
        segments[i].end = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr + info->dlpi_phdr[i].p_memsz;
    }
    return 1;
}

__attribute__((__constructor__))
static void setup(void) {
    real_fputs = dlsym(RTLD_NEXT, "fputs");
    dl_iterate_phdr(callback, NULL);
}

__attribute__((__destructor__))
static void teardown(void) {
    free(segments);
}

__attribute__((__noinline__))
int fputs(const char *str, FILE *file) {
    ElfW(Addr) addr = (ElfW(Addr))__builtin_extract_return_addr(__builtin_return_address(0));
    for(int i = 0; i < n; ++i) {
        if(addr >= segments[i].start && addr < segments[i].end) {
            // Do Nothing
            return 0;
        }
    }
    return real_fputs(str, file);
}

This has some caveats, though. For example, if your executable calls a library function that tail-calls a function you're hooking, then this will incorrectly consider that library call an executable call. (You could mitigate this problem by adding wrappers for those library functions too, that unconditionally forward to the "real" function, and compiling the wrapper code with -fno-optimize-sibling-calls.) Also, there's no way to distinguish whether anonymous executable memory (e.g., JITted code) originally came from the executable or a library.

To test this, save my code as hook_fputs.c, your main executable as main.c, and your shared library as libfoo.c. Then run these commands:

clang -fPIC -shared hook_fputs.c -ldl -o hook_fputs.so
clang -fPIC -shared libfoo.c -o libfoo.so
clang main.c ./libfoo.so
LD_PRELOAD=./hook_fputs.so ./a.out

Upvotes: 5

Gambuta
Gambuta

Reputation: 11

Implement the interposing library separately for the two cases.

Create a wrapper script or program that uses ldd to find out the exact EGL library version and their paths the target binary is dynamically linked against; then, using ldd on the the GLFW library, to find out whether it is linked against EGL or not. Finally, have it execute the target binary with the path to the appropriate interposing library in LD_PRELOAD environment variable.

Upvotes: 1

Related Questions