Report

CVE-2023-4911 Looney Tunables: heap overflow in glibc parse_tunables

13914fa3-7e5e-434d-90f3-5fc0eb02e98d

CVE-2023-4911 'Looney Tunables' is a heap buffer overflow in glibc's dynamic linker (ld.so), introduced in glibc 2.34 and present through 2.38. Triggered when a setuid/setgid binary inherits a malformed GLIBC_TUNABLES environment variable. The vulnerable function is parse_tunables() in elf/dl-tunables.c. It duplicates the env value into a heap buffer sized exactly to strlen(value)+2 (via tunables_strdup), then walks the input writing canonical name=value pairs back via tunestr[off++] without any bounds check. Because '=' is not a value terminator (only ':' and '\0' are), a value may legally contain '=' characters. The outer loop fails to advance the read pointer past a value when no separator follows (line 254: if (p[len] != '\0') p += len + 1;), so the same bytes get re-parsed as a fresh name=value pair on the next iteration while the write cursor off keeps growing monotonically. Local unprivileged users can exploit this against any SUID-root binary (su, sudo, /usr/bin/passwd) for local root. 1. Read briefing: buffer overflow in dynamic linker via env var, call chain _dl_start -> _dl_main -> __tunables_init. 2. find . -name "tunables*.c" → located elf/dl-tunables.c (435 lines). 3. Read entire dl-tunables.c. Found __tunables_init at line 277 and parse_tunables at line 169. 4. Traced data flow: __tunables_init iterates envp via get_next_env, when it sees GLIBC_TUNABLES it calls tunables_strdup(envname) and then parse_tunables(new_env+len+1, envval). 5. tunables_strdup (lines 46-64) allocates strlen(in)+2 bytes — exact size of input. 6. parse_tunables (lines 169-257) walks the value with two scans: name terminated by '=' or ':' or '\0'; value terminated by ':' or '\0' (NOT '='). When __libc_enable_secure is true, lines 226-242 write the canonical name + '=' + value back into tunestr[off++] with no bounds check. 7. Loop terminator at line 254: if (p[len] != '\0') p += len + 1; — advances p only when there is a ':' separator. If the value runs to end-of-string, p stays put and the next iteration re-scans the same bytes. 8. Trace with GLIBC_TUNABLES='glibc.malloc.mxfast=glibc.malloc.mxfast=A': iter 1 writes 41 bytes (full repeat), iter 2 re-parses the value as a new name=value and writes ~22 more bytes. Buffer is only 43 bytes → heap overflow. 9. Cross-checked with included poc_cve_2023_4911.c which confirmed parse_tunables lines 235-241 area.