
CVE-2023-4911 (Looney Tunables) analysis report and Docker reproduction lab
| Item | Description |
|---|---|
| CVE ID | CVE-2023-4911 |
| Attack Type | Heap Buffer Overflow → Local Privilege Escalation |
| CVSS 3.1 | 7.8 (High) |
| Disclosure Date | 2023-10-03 |
| Vulnerability Point | glibc dynamic loader (ld.so) GLIBC_TUNABLES parser |
| Affected Versions | glibc 2.34 ~ 2.38 |
CVE-2023-4911 is a heap buffer overflow vulnerability that occurs when the dynamic loader of the GNU C Library (glibc) parses the GLIBC_TUNABLES environment variable. By exploiting this overflow, an attacker can manipulate the dynamic loader's library search path (RPATH). When a SUID root binary (su, sudo, etc.) is executed, the attacker can force the loader to load a malicious shared library instead of the legitimate one, thereby executing arbitrary code with root privileges. Since glibc is a core component of virtually all major Linux distributions, this vulnerability affected most glibc-based distributions released after April 2021.
while (true)
{
char *name = p;
size_t len = 0;
/* Find the length of the name */
while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
len++;
/* If it ends without '=', terminate */
if (p[len] == '\0')
{
if (__libc_enable_secure)
tunestr[off] = '\0';
return;
}
/* If ':' is encountered first, it's an invalid entry */
if (p[len] == ':')
{
p += len + 1;
continue;
}
/* Met '=', move to start of value */
p += len + 1;
/* Calculate value from original string */
char *value = &valstring[p - tunestr];
len = 0;
/* Find length of value */
while (p[len] != ':' && p[len] != '\0')
len++;
...
/* Copy to tunestr */
...
if (p[len] != '\0')
p += len + 1;
}
__tunables_init() finds GLIBC_TUNABLES in the environment variable list.tunables_strdup() allocates a buffer using __minimal_malloc() and copies the original string (at this point, malloc is a very early implementation that has not yet been fully initialized).parse_tunables() iterates through this buffer using : (colon) as a delimiter, splitting each key=value pair and assigning the value to the corresponding tunable.parse_tunables() processes a single tunable in the order: name parsing → moving p → value parsing → moving p. Under normal input, after processing the value, p moves to the start of the next tunable to parse the next entry.
However, when given an input of the form name=name=value, for example:
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=AAAA...(long string)
During the first parsing, the entire glibc.malloc.mxfast=AAAA... is recognized as a single value and copied into the tunestr buffer. Since there is no colon (:) after the value to separate the next tunable, the parsing pointer (p) does not move to the next entry but instead points back to the beginning of the already copied value.
The problem is that this value itself has a name=value format. In the next iteration, the parser incorrectly treats it as a new tunable and writes duplicate data into the buffer. Since tunestr was allocated only for the size of the original string, the duplicate writes cause a heap buffer overflow.
The heap buffer overflow overwrites adjacent heap areas allocated consecutively by __minimal_malloc(). An attacker can use this to modify the pointer to l_info[DT_RPATH] in the link_map internal structure of the dynamic linker (ld.so) to point to a stack address controlled by the attacker.
In that stack area, a pre-manipulated Elf64_Dyn structure is placed, which specifies a directory of the attacker's choice as the new library search path (RPATH). As a result, ld.so loads the attacker's malicious shared library instead of the legitimate system library.
GLIBC_TUNABLES environment variable of the form name=name=value.su) with normal user privileges.parse_tunables() of ld.so.l_info[DT_RPATH] pointer in link_map to point to a stack address containing a fake Elf64_Dyn structure.libc.so.6 prepared by the attacker.libc.so.6 executes with root privileges, performing setuid(0), setgid(0), and executing /bin/sh.su runs, thus obtaining a root shell without password verification.The PoC uses a brute-force approach that repeatedly calls execve() until the desired memory layout is achieved under the influence of ASLR. Therefore, the success and time required may vary depending on the environment; typically hundreds to thousands of attempts are needed.
First, clone the contents from the git repository to a directory.
git clone https://github.com/baeseungwon1010/CVE-2023-4911

Build the Docker image using the following command:
cd C* && docker compose run --rm cve-2023-4911-lab

After entering the container, run the exploit code:
cd /home/student/exploit && ./exp
After running and waiting, you can see that the user changes from a normal user to sudo(0).

Update glibc from the vulnerable version to a patched version. After patching, if possible, reboot or restart to ensure no previous version of glibc remains in memory. If immediate patching is not possible, a temporary workaround is to remove unnecessary SUID, SGID, and similar processes.