Function Interposition
3 Code Demos
Intermediate
TLPI Ch 42.5
LD_PRELOAD is an environment variable that tells the dynamic linker to load one or more shared libraries before any other libraries โ including the C standard library. Because these libraries are searched first, if they define a function that also exists in another library, their version wins.
This technique is called function interposition โ you intercept a function call and replace it with your own implementation. This is extremely useful for:
- Testing: mock out a function without modifying source code
- Debugging: log all calls to
malloc(),open(), etc. - Patching: fix a bug in a third-party library without recompiling it
- Profiling: count how many times a function is called
| Program calls x1() | โ | Dynamic Linker Searches in order: |
โ | libalt.so (PRELOADED) Found! Use this x1() |
| Without LD_PRELOAD โ linker would find x1() in libdemo.so instead | ||||
Suppose we have a library libdemo.so with functions x1() and x2(), and a program that calls both. We want to replace x1() with our own version without touching the program or libdemo.so.
/* mod1.c โ part of libdemo.so */
#include <stdio.h>
void x1(void) { printf("Called mod1-x1 DEMO\n"); }
/* mod2.c โ part of libdemo.so */
#include <stdio.h>
void x2(void) { printf("Called mod2-x2 DEMO\n"); }
# Build libdemo.so
gcc -g -c -fPIC -Wall mod1.c mod2.c
gcc -g -shared -o libdemo.so mod1.o mod2.o
/* prog.c โ program using libdemo */
#include <stdio.h>
void x1(void);
void x2(void);
int main(void) {
x1();
x2();
return 0;
}
# Build prog against libdemo
gcc -g -o prog prog.c -L. -ldemo
LD_LIBRARY_PATH=. ./prog
# Output (without preload):
# Called mod1-x1 DEMO
# Called mod2-x2 DEMO
/* libalt.c โ our override library that replaces x1() only */
#include <stdio.h>
void x1(void) { printf("Called mod1-x1 ALT\n"); }
/* Note: x2() is NOT defined here โ linker will fall through to libdemo.so */
# Build the override library
gcc -g -c -fPIC -Wall libalt.c
gcc -g -shared -o libalt.so libalt.o
# Preload libalt.so โ its x1() overrides libdemo's x1()
LD_PRELOAD=libalt.so LD_LIBRARY_PATH=. ./prog
# Output (with preload):
# Called mod1-x1 ALT โ from libalt.so
# Called mod2-x2 DEMO โ still from libdemo.so (x2 not in libalt)
libalt.so continues to be resolved from the original library.A classic use of LD_PRELOAD is wrapping standard library functions like malloc() to add logging or debugging. We use dlsym(RTLD_NEXT, ...) to get a pointer to the original function, then call it from our wrapper.
/* malloc_trace.c โ wraps malloc() to log every allocation */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
/* Function pointer to hold the real malloc */
static void *(*real_malloc)(size_t size) = NULL;
/* Our constructor runs first โ grab the real malloc before anything */
void __attribute__((constructor)) init_tracer(void)
{
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (!real_malloc) {
fprintf(stderr, "Cannot find real malloc: %s\n", dlerror());
exit(1);
}
fprintf(stderr, "[tracer] malloc wrapper installed\n");
}
/* Our malloc โ logs the call then delegates to the real malloc */
void *malloc(size_t size)
{
void *ptr = real_malloc(size);
fprintf(stderr, "[malloc] size=%zu ptr=%p\n", size, ptr);
return ptr;
}
# Build the tracing library
gcc -g -c -fPIC -Wall malloc_trace.c
gcc -g -shared -o libmtrace.so malloc_trace.o -ldl
# Run ANY program with malloc tracing (no source changes needed!)
LD_PRELOAD=./libmtrace.so ls /tmp
# Example output:
# [tracer] malloc wrapper installed
# [malloc] size=1024 ptr=0x55a3f2b01260
# [malloc] size=64 ptr=0x55a3f2b012a0
# ... (followed by normal ls output)
RTLD_NEXT as the first argument to dlsym(), it finds the next occurrence of the symbol in the library search order โ i.e., the original function in the library that comes after your preloaded library. This is how you call the real function from your wrapper.The LD_PRELOAD environment variable works per-process: only the process where you set it gets the preloaded library. For system-wide preloading (affecting all processes), use the file /etc/ld.so.preload.
# /etc/ld.so.preload
# List library paths separated by whitespace (one per line or space-separated)
# These are loaded for EVERY process on the system
/usr/lib/libsystem_audit.so
/usr/local/lib/libsecure_malloc.so
| Method | Scope | Priority |
|---|---|---|
| LD_PRELOAD=lib.so ./prog | Per-process | Loaded first (highest) |
| /etc/ld.so.preload | System-wide (all processes) | After LD_PRELOAD |
| Normal library search | Per normal linker rules | After both above |
LD_PRELOAD to hijack a SUID binary like /usr/bin/passwd. The system-wide /etc/ld.so.preload is still applied to SUID binaries, so be careful what you put there.You can preload multiple libraries at once. Separate them with spaces or colons:
# Space-separated
LD_PRELOAD="libfoo.so libbar.so" ./prog
# Colon-separated
LD_PRELOAD=libfoo.so:libbar.so ./prog
# Multiple with full paths
LD_PRELOAD=/usr/local/lib/libfoo.so:/home/ravi/libbar.so ./prog
Libraries listed earlier in LD_PRELOAD are searched first. If both libfoo.so and libbar.so define x1(), the version from libfoo.so wins.
LD_PRELOAD is an environment variable that instructs the dynamic linker to load specified shared libraries before any others. Functions in the preloaded library override same-named functions from other libraries. Common uses include: testing (mocking functions), debugging (logging all malloc calls), and patching third-party binaries.x2() is not defined in the preloaded library, the linker continues searching and finds it in the original library. The original x2() is called normally.dlsym(RTLD_NEXT, "function_name"). RTLD_NEXT tells dlsym() to find the next occurrence of the symbol after the current library in the linker’s search order โ that is, the original function in the underlying library.LD_PRELOAD for set-user-ID and set-group-ID programs. This prevents privilege escalation attacks via function interposition.LD_PRELOAD applies per-process (only to the current shell/execution). /etc/ld.so.preload is a system-wide file that affects every process started on the system. Libraries from LD_PRELOAD are loaded before those in /etc/ld.so.preload.LD_PRELOAD. A practical use case is wrapping malloc() to log all memory allocations for debugging memory leaks, without modifying the application’s source code.