Looney Tunables — glibc ld.so (CVE-2023-4911)

Looney Tunables — glibc ld.so (CVE-2023-4911)

TL;DR: A buffer overflow in glibc’s GLIBC_TUNABLES parser lets a local user smash the dynamic linker’s stack while executing any SUID binary, yielding a root shell on stock Fedora, Debian, and Ubuntu.

What it is

Qualys disclosed this in October 2023. It lives in __tunables_init() inside ld.so, the dynamic loader linked into every dynamically-linked binary on Linux. When parsing the GLIBC_TUNABLES environment variable, the loader uses __libc_alloca_cutoff to allocate a working copy on the stack, then walks name=value:name=value pairs. A malformed tunable=tunable=AAA entry causes the parser to copy bytes past the allocation while still on the loader’s tiny stack. Because the overflow happens before glibc strips LD_*/GLIBC_TUNABLES from a SUID process — the sanitisation happens after __tunables_init() — the attacker controls memory inside a process running as root.

Preconditions / where it applies

  • glibc 2.34 through 2.38 with the tunables refactor (commit 2ed18c) — first shipped in Fedora 37, Ubuntu 22.04+, Debian 12, RHEL 9.x
  • Any SUID-root binary on the box that re-execs through ld.so, e.g. /usr/bin/su, /usr/bin/mount, /usr/bin/sudo
  • Local code execution; no special groups
  • Patched in glibc 2.39 and distro updates from 2023-10-03 onwards

Technique

The trigger is a single env var; the rest is heap/stack grooming to land a controllable pointer where dl_audit later reads it:

1
2
3
4
5
6
7
GLIBC_TUNABLES='glibc.malloc.mxfast=glibc.malloc.mxfast=A'
# repeated and padded — the overflow walks into ld.so's static state

env -i \
    "GLIBC_TUNABLES=$(python3 -c 'print("glibc.malloc.mxfast=glibc.malloc.mxfast=" + "A"*32768)')" \
    "Z=$(python3 -c 'print("B"*4096)')" \
    /usr/bin/su --version

A working PoC pads GLIBC_TUNABLES so the corrupted dword overlaps l_info[DT_RPATH] or an audit hook pointer, then arranges a controlled string to be interpreted as a search path. The loader subsequently dlopens an attacker-supplied .so while EUID 0. End-to-end exploits chain this with a writeable $ORIGIN-style path under /dev/shm/ and a payload that calls setuid(0) + execve("/bin/sh").

Detection and defence

  • Patch glibc; distros backported within 48h (Ubuntu USN-6409, RHSA-2023:5455, DSA-5514)
  • Stopgap with systemd-tmpfiles: a sysctl is not available, but you can blunt impact by stripping SUID from non-essential helpers (dpkg-statoverride, chmod u-s)
  • auditd: -a always,exit -F arch=b64 -S execve -F a0~="GLIBC_TUNABLES" catches the env var on SUID exec; pair with setuid syscall transitions
  • SELinux and AppArmor confine the secondary dlopen — RHEL targeted policy denies arbitrary .so loads from user-writable dirs in staff_t
  • Static analysis: scan the running glibc with ldd --version; anything in the 2.34–2.38 window without the backport is suspect

References

See also: suid-sgid-binaries, ld-preload-abuse, kernel-exploits-linux.