Security
Headlines
HeadlinesLatestCVEs

Headline

glibc ld.so Local Privilege Escalation

Dubbed Looney Tunables, Qualys discovered a buffer overflow vulnerability in the glibc dynamic loader’s processing of the GLIBC_TUNABLES environment variable. This vulnerability was introduced in April 2021 (glibc 2.34) by commit 2ed18c.

Packet Storm
#vulnerability#ubuntu#linux#debian#red_hat#buffer_overflow
Qualys Security AdvisoryLooney Tunables: Local Privilege Escalation in the glibc's ld.so(CVE-2023-4911)========================================================================Contents========================================================================SummaryAnalysisProof of conceptExploitationAcknowledgmentsTimeline========================================================================Summary========================================================================The GNU C Library's dynamic loader "find[s] and load[s] the sharedobjects (shared libraries) needed by a program, prepare[s] the programto run, and then run[s] it" (man ld.so). The dynamic loader is extremelysecurity sensitive, because its code runs with elevated privileges whena local user executes a set-user-ID program, a set-group-ID program, ora program with capabilities. Historically, the processing of environmentvariables such as LD_PRELOAD, LD_AUDIT, and LD_LIBRARY_PATH has been afertile source of vulnerabilities in the dynamic loader.Recently, we discovered a vulnerability (a buffer overflow) in thedynamic loader's processing of the GLIBC_TUNABLES environment variable(https://www.gnu.org/software/libc/manual/html_node/Tunables.html). Thisvulnerability was introduced in April 2021 (glibc 2.34) by commit 2ed18c("Fix SXID_ERASE behavior in setuid programs (BZ #27471)").We successfully exploited this vulnerability and obtained full rootprivileges on the default installations of Fedora 37 and 38, Ubuntu22.04 and 23.04, Debian 12 and 13; other distributions are probably alsovulnerable and exploitable (one notable exception is Alpine Linux, whichuses musl libc, not the glibc). We will not publish our exploit for now;however, this buffer overflow is easily exploitable (by transforming itinto a data-only attack), and other researchers might publish workingexploits shortly after this coordinated disclosure.========================================================================Analysis========================================================================At the very beginning of its execution, ld.so calls __tunables_init() towalk through the environment (at line 279), searching for GLIBC_TUNABLESvariables (at line 282); for each GLIBC_TUNABLES that it finds, it makesa copy of this variable (at line 284), calls parse_tunables() to processand sanitize this copy (at line 286), and finally replaces the originalGLIBC_TUNABLES with this sanitized copy (at line 288):------------------------------------------------------------------------269 void270 __tunables_init (char **envp)271 {272   char *envname = NULL;273   char *envval = NULL;274   size_t len = 0;275   char **prev_envp = envp;...279   while ((envp = get_next_env (envp, &envname, &len, &envval,280                                &prev_envp)) != NULL)281     {282       if (tunable_is_name ("GLIBC_TUNABLES", envname))283         {284           char *new_env = tunables_strdup (envname);285           if (new_env != NULL)286             parse_tunables (new_env + len + 1, envval);287           /* Put in the updated envval.  */288           *prev_envp = new_env;289           continue;290         }------------------------------------------------------------------------The first argument of parse_tunables() (tunestr) points to thesoon-to-be-sanitized copy of GLIBC_TUNABLES, while the second argument(valstring) points to the original GLIBC_TUNABLES environment variable(in the stack). To sanitize the copy of GLIBC_TUNABLES (which should beof the form "tunable1=aaa:tunable2=bbb"), parse_tunables() removes alldangerous tunables (the SXID_ERASE tunables) from tunestr, but keepsSXID_IGNORE and NONE tunables (at lines 221-235):------------------------------------------------------------------------162 static void163 parse_tunables (char *tunestr, char *valstring)164 {...168   char *p = tunestr;169   size_t off = 0;170 171   while (true)172     {173       char *name = p;174       size_t len = 0;175 176       /* First, find where the name ends.  */177       while (p[len] != '=' && p[len] != ':' && p[len] != '\0')178         len++;179 180       /* If we reach the end of the string before getting a valid name-value181          pair, bail out.  */182       if (p[len] == '\0')183         {184           if (__libc_enable_secure)185             tunestr[off] = '\0';186           return;187         }188 189       /* We did not find a valid name-value pair before encountering the190          colon.  */191       if (p[len]== ':')192         {193           p += len + 1;194           continue;195         }196 197       p += len + 1;198 199       /* Take the value from the valstring since we need to NULL terminate it.  */200       char *value = &valstring[p - tunestr];201       len = 0;202 203       while (p[len] != ':' && p[len] != '\0')204         len++;205 206       /* Add the tunable if it exists.  */207       for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)208         {209           tunable_t *cur = &tunable_list[i];210 211           if (tunable_is_name (cur->name, name))212             {...219               if (__libc_enable_secure)220                 {221                   if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)222                     {223                       if (off > 0)224                         tunestr[off++] = ':';225 226                       const char *n = cur->name;227 228                       while (*n != '\0')229                         tunestr[off++] = *n++;230 231                       tunestr[off++] = '=';232 233                       for (size_t j = 0; j < len; j++)234                         tunestr[off++] = value[j];235                     }236 237                   if (cur->security_level != TUNABLE_SECLEVEL_NONE)238                     break;239                 }240 241               value[len] = '\0';242               tunable_initialize (cur, value);243               break;244             }245         }246 247       if (p[len] != '\0')248         p += len + 1;249     }250 }------------------------------------------------------------------------Unfortunately, if a GLIBC_TUNABLES environment variable is of the form"tunable1=tunable2=AAA" (where "tunable1" and "tunable2" are SXID_IGNOREtunables, for example "glibc.malloc.mxfast"), then:- during the first iteration of the "while (true)" in parse_tunables(),  the entire "tunable1=tunable2=AAA" is copied in-place to tunestr (at  lines 221-235), thus filling up tunestr;- at lines 247-248, p is not incremented (p[len] is '\0' because no ':'  was found at lines 203-204) and therefore p still points to the value  of "tunable1", i.e. "tunable2=AAA";- during the second iteration of the "while (true)" in parse_tunables(),  "tunable2=AAA" is appended (as if it were a second tunable) to tunestr  (which is already full), thus overflowing tunestr.A note on fuzzing: although we discovered this buffer overflow manually,we later tried to fuzz the vulnerable function, parse_tunables(); bothAFL++ and libFuzzer re-discovered this overflow in less than a second,when provided with a dictionary of tunables (which can be compiled byrunning "ld.so --list-tunables").========================================================================Proof of concept========================================================================$ env -i "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=A" "Z=`printf '%08192x' 1`" /usr/bin/su --helpSegmentation fault (core dumped)========================================================================Exploitation========================================================================This vulnerability is a straightforward buffer overflow, but what shouldwe overwrite to achieve arbitrary code execution? The buffer we overflowis allocated at line 284 by tunables_strdup(), a re-implementation ofstrdup() that uses ld.so's __minimal_malloc() instead of the glibc'smalloc() (indeed, the glibc's malloc() has not been initialized yet).This __minimal_malloc() implementation simply calls mmap() to obtainmore memory from the kernel.The question, then, is: what writable pages can we overwrite in the mmapregion? To the best of our knowledge, we have only two options (becausethis buffer overflow takes place at the very beginning of ld.so'sexecution):1/ The read-write ELF segment of ld.so itself (the first pages of thisread-write segment are actually ld.so's RELRO segment, but they have notbeen mprotect()ed read-only yet):------------------------------------------------------------------------7f209f367000-7f209f369000 r--p 00000000 fd:00 10943                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27f209f369000-7f209f393000 r-xp 00002000 fd:00 10943                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27f209f393000-7f209f39e000 r--p 0002c000 fd:00 10943                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.27f209f39f000-7f209f3a3000 rw-p 00037000 fd:00 10943                      /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2------------------------------------------------------------------------However, on all the Linux distributions that we checked, the unmappedhole immediately below ld.so's read-write segment is at most one page,but ld.so's __minimal_malloc() always allocates at least two pages ("oneextra page to reduce number of mmap calls"). In other words, the bufferwe overflow cannot be allocated immediately below ld.so's read-writesegment, and therefore cannot overwrite this segment.2/ Our only option, then, is to overwrite mmap()ed pages that wereallocated by tunables_strdup() itself: because __tunables_init() canprocess multiple GLIBC_TUNABLES environment variables, and because theLinux kernel's mmap() is a top-down allocator, we can mmap() a firstGLIBC_TUNABLES (without overflowing it), mmap() a second GLIBC_TUNABLES(immediately below the first one) and overflow it, thus overwriting thefirst GLIBC_TUNABLES. As a result, we can:- either replace this first GLIBC_TUNABLES with a completely different  environment variable, for example LD_PRELOAD or LD_LIBRARY_PATH -- but  these dangerous variables are later removed from the environment by  ld.so (in process_envvars()), and such a replacement would therefore  be useless;- or replace the first GLIBC_TUNABLES with a GLIBC_TUNABLES that  contains dangerous (SXID_ERASE) tunables, which were previously  removed by parse_tunables() -- although this seems promising at first,  exploiting such a replacement would require a SUID-root program that  setuid(0)s and execve()s another program with a preserved environment  (to process the dangerous GLIBC_TUNABLES as root, but without  __libc_enable_secure).  Alas, we do not know of such a SUID-root program on Linux (on OpenBSD,  /usr/bin/chpass setuid(0)s and execv()s /usr/sbin/pwd_mkdb, and was  exploited in CVE-2019-19726); if you, dear reader, know of such a  SUID-root program on Linux, please let us know!At that point, the situation looked quite hopeless, but a comment inld.so's _dl_new_object() (which is called long after __tunables_init())caught our attention (at line 105):------------------------------------------------------------------------ 56 struct link_map * 57 _dl_new_object (char *realname, const char *libname, int type, 58                 struct link_map *loader, int mode, Lmid_t nsid) 59 { .. 84   struct link_map *new; 85   struct libname_list *newname; .. 92   new = (struct link_map *) calloc (sizeof (*new) + audit_space 93                                     + sizeof (struct link_map *) 94                                     + sizeof (*newname) + libname_len, 1); 95   if (new == NULL) 96     return NULL; 97  98   new->l_real = new; 99   new->l_symbolic_searchlist.r_list = (struct link_map **) ((char *) (new + 1)100                                                             + audit_space);101 102   new->l_libname = newname103     = (struct libname_list *) (new->l_symbolic_searchlist.r_list + 1);104   newname->name = (char *) memcpy (newname + 1, libname, libname_len);105   /* newname->next = NULL;      We use calloc therefore not necessary.  */------------------------------------------------------------------------ld.so allocates the memory for this link_map structure with calloc(),and therefore does not explicitly initialize various of its members tozero; this is a reasonable optimization. As mentioned earlier, calloc()here is not the glibc's calloc() but ld.so's __minimal_calloc(), whichcalls __minimal_malloc() *without* explicitly initializing the memory itreturns to zero; this is also a reasonable optimization, because for allintents and purposes __minimal_malloc() always returns a clean chunk ofmmap()ed memory, which is guaranteed to be initialized to zero by thekernel.Unfortunately, the buffer overflow in parse_tunables() allows us tooverwrite clean mmap()ed memory with non-zero bytes, thereby overwritingpointers of the soon-to-be-allocated link_map structure with non-NULLvalues. This allows us to completely break the logic of ld.so, whichassumes that these pointers are NULL.We first tried to exploit this buffer overflow by overwriting thelink_map structure's l_next and l_prev pointers (a doubly linked list oflink_map structures), but we failed because of two assert()ion failuresin setup_vdso(), which immediately abort() ld.so (all the distributionsthat we checked compile their glibc, and hence ld.so, with assert()ionsenabled):------------------------------------------------------------------------ 96       assert (l->l_next == NULL); 97       assert (l->l_prev == main_map);------------------------------------------------------------------------We then realized that many more pointers in the link_map structure arenot explicitly initialized to NULL; in particular, the pointers toElf64_Dyn structures in the l_info[] array of pointers. Among these,l_info[DT_RPATH], the "Library search path", immediately stood out: ifwe overwrite this pointer and control where and what it points to, thenwe can force ld.so to trust a directory that we own, and therefore toload our own libc.so.6 or LD_PRELOAD library from this directory, andexecute arbitrary code (as root, if we run ld.so through a SUID-rootprogram).------------------------------------------------------------------------Where should the overwritten l_info[DT_RPATH] point to? The easy answerto this question is: the stack; more precisely, our environment stringsin the stack. On Linux, the stack is randomized in a 16GB region, andour environment strings can occupy up to 6MB (_STK_LIM / 4 * 3, in thekernel's bprm_stack_limits()): after 16GB / 6MB = 2730 tries we have agood chance of guessing the address of our environment strings (in ourexploit, we always overwrite l_info[DT_RPATH] with 0x7ffdfffff010, thecenter of the randomized stack region). In our tests, this brute forcetakes ~30s on Debian, and ~5m on Ubuntu and Fedora (because of theirautomatic crash handlers, Apport and ABRT; we have not tried to workaround this slowdown).------------------------------------------------------------------------What should the overwritten l_info[DT_RPATH] point to? In other words,what should we store in our 6MB of environment strings? l_info[DT_RPATH]is a pointer to a small (16B) Elf64_Dyn structure:- an int64_t d_tag, which should be DT_RPATH (15), but this value is  never actually checked anywhere, so we can store anything there;- a uint64_t d_val, which is an offset into the ELF string table of the  SUID-root program that is being executed (this offset references a  string that is the "Library search path" itself).In our exploit, we simply fill our 6MB of environment strings with0xfffffffffffffff8 (-8), because at an offset of -8B below the stringtable of most SUID-root programs, the string "\x08" appears: this forcesld.so to trust a relative directory named "\x08" (in our current workingdirectory), and therefore allows us to load and execute our ownlibc.so.6 or LD_PRELOAD library from this directory, as root.------------------------------------------------------------------------One major problem remains unsolved, however: to avoid the kind ofassert()ion failures mentioned earlier (when we tried to overwrite thel_next and l_prev pointers of the link_map structure), we must overwritethe soon-to-be-allocated link_map structure with NULL pointers only(except l_info[DT_RPATH], of course); but intuitively, the ability tooverflow a buffer with a large number of null bytes while parsing anull-terminated C string sounds quite unusual.Luckily for us attackers, the bytes that are written out-of-bounds byparse_tunables() are also read out-of-bounds (at line 234), but not fromthe mmap()ed copy of our GLIBC_TUNABLES environment variable (tunestr),but from our original GLIBC_TUNABLES environment variable in the stack(valstring, at line 200). Consequently, if we store a large number ofempty strings (null bytes) immediately after our GLIBC_TUNABLES in thestack, followed by the string "\x10\xf0\xff\xff\xfd\x7f", followed bymore empty strings (null bytes), then we safely overwrite the link_mapstructure with null bytes (NULL pointers), except for l_info[DT_RPATH](which we overwrite with 0x7ffdfffff010, which points to our ownElf64_Dyn structures in the stack with a probability of 1/2730).Final note: the exploitation method described in this advisory worksagainst almost all of the SUID-root programs that are installed bydefault on Linux; a few exceptions are:- sudo on all distributions, because it specifies its own ELF RUNPATH  (/usr/libexec/sudo), which overrides our l_info[DT_RPATH];- chage and passwd on Fedora, because they are protected by special  SELinux rules;- snap-confine on Ubuntu, because it is protected by special AppArmor  rules.Last-minute note: although glibc 2.34 is vulnerable to this bufferoverflow, its tunables_strdup() uses __sbrk(), not __minimal_malloc()(which was introduced in glibc 2.35 by commit b05fae, "elf: Use theminimal malloc on tunables_strdup"); we have not yet investigatedwhether glibc 2.34 is exploitable or not.========================================================================Acknowledgments========================================================================We thank Red Hat Product Security, Siddhesh Poyarekar, the members oflinux-distros@openwall, Salvatore Bonaccorso, and Solar Designer.========================================================================Timeline========================================================================2023-09-04: Advisory and exploit sent to [email protected]: Advisory and patch sent to [email protected]: Coordinated Release Date (17:00 UTC).

Related news

Understanding the Red Hat security impact scale

Red Hat uses a four-point impact scale to classify security issues affecting our products. Have you ever asked yourself what it takes and what the requirements are for each point of the scale? We will talk through the highlights of our process in this article.Is this a CVE?First and foremost, what is a CVE? Short for Common Vulnerabilities and Exposures, it is a list of publicly disclosed computer security flaws. Learn more in this Red Hat post.To receive a severity rating, the issue needs to be a CVE. But what does it take to be a CVE? In order to warrant a CVE ID, a vulnerability has to comp

New Glibc Flaw Grants Attackers Root Access on Major Linux Distros

Malicious local attackers can obtain full root access on Linux machines by taking advantage of a newly disclosed security flaw in the GNU C library (aka glibc). Tracked as CVE-2023-6246, the heap-based buffer overflow vulnerability is rooted in glibc's __vsyslog_internal() function, which is used by syslog() and vsyslog() for system logging purposes. It's said to have been accidentally

Glibc Tunables Privilege Escalation

A buffer overflow exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES environment variable. It has been dubbed Looney Tunables. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES when launching binaries with SUID permission to execute code in the context of the root user. This Metasploit module targets glibc packaged on Ubuntu and Debian. Fedora 37 and 38 and other distributions of linux also come packaged with versions of glibc vulnerable to CVE-2023-4911 however this module does not target them.

Kinsing Crypto Malware Targets Linux Systems via Apache ActiveMQ Flaw

By Deeba Ahmed Patches for all affected versions of Apache ActiveMQ have been released, and clients are strongly advised to upgrade their systems. This is a post from HackRead.com Read the original post: Kinsing Crypto Malware Targets Linux Systems via Apache ActiveMQ Flaw

October 2023: back to Positive Technologies, Vulristics updates, Linux Patch Wednesday, Microsoft Patch Tuesday, PhysTech VM lecture

Hello everyone! October was an interesting and busy month for me. I started a new job, worked on my open source Vulristics project, and analyzed vulnerabilities using it. Especially Linux vulnerabilities as part of my new Linux Patch Wednesday project. And, of course, analyzed Microsoft Patch Tuesday as well. In addition, at the end of […]

Kinsing Actors Exploiting Recent Linux Flaw to Breach Cloud Environments

The threat actors linked to Kinsing have been observed attempting to exploit the recently disclosed Linux privilege escalation flaw called Looney Tunables as part of a "new experimental campaign" designed to breach cloud environments. "Intriguingly, the attacker is also broadening the horizons of their cloud-native attacks by extracting credentials from the Cloud Service Provider (CSP)," cloud

Red Hat Security Advisory 2023-5476-01

Red Hat Security Advisory 2023-5476-01 - The glibc packages provide the standard C libraries, POSIX thread libraries, standard math libraries, and the name service cache daemon used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly. Issues addressed include buffer overflow and privilege escalation vulnerabilities.

Red Hat Security Advisory 2023-5455-01

Red Hat Security Advisory 2023-5455-01 - The glibc packages provide the standard C libraries, POSIX thread libraries, standard math libraries, and the name service cache daemon used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly. Issues addressed include buffer overflow, privilege escalation, and use-after-free vulnerabilities.

Red Hat Security Advisory 2023-5453-01

Red Hat Security Advisory 2023-5453-01 - The glibc packages provide the standard C libraries, POSIX thread libraries, standard math libraries, and the name service cache daemon used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly. Issues addressed include buffer overflow, privilege escalation, and use-after-free vulnerabilities.

Red Hat Security Advisory 2023-5454-01

Red Hat Security Advisory 2023-5454-01 - The glibc packages provide the standard C libraries, POSIX thread libraries, standard math libraries, and the name service cache daemon used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly. Issues addressed include buffer overflow and privilege escalation vulnerabilities.

Gentoo Linux Security Advisory 202310-03

Gentoo Linux Security Advisory 202310-3 - Multiple vulnerabilities in glibc could result in Local Privilege Escalation. Versions greater than or equal to 2.37-r7 are affected.

Ubuntu Security Notice USN-6409-1

Ubuntu Security Notice 6409-1 - It was discovered that the GNU C Library incorrectly handled the GLIBC_TUNABLES environment variable. An attacker could possibly use this issue to perform a privilege escalation attack. It was discovered that the GNU C Library incorrectly handled certain DNS responses when the system was configured in no-aaaa mode. A remote attacker could possibly use this issue to cause the GNU C Library to crash, resulting in a denial of service. This issue only affected Ubuntu 23.04.

Debian Security Advisory 5514-1

Debian Linux Security Advisory 5514-1 - The Qualys Research Labs discovered a buffer overflow in the dynamic loader's processing of the GLIBC_TUNABLES environment variable. An attacker can exploit this flaw for privilege escalation.

Looney Tunables: New Linux Flaw Enables Privilege Escalation on Major Distributions

A new Linux security vulnerability dubbed Looney Tunables has been discovered in the GNU C library's ld.so dynamic loader that, if successfully exploited, could lead to a local privilege escalation and allow a threat actor to gain root privileges. Tracked as CVE-2023-4911 (CVSS score: 7.8), the issue is a buffer overflow that resides in the dynamic loader's processing of the GLIBC_TUNABLES

CVE-2023-4911: Looney Tunables – Local Privilege Escalation in the glibc’s ld.so – Qualys Security Blog

A buffer overflow was discovered in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES environment variable. This issue could allow a local attacker to use maliciously crafted GLIBC_TUNABLES environment variables when launching binaries with SUID permission to execute code with elevated privileges.

CVE-2019-19726: OpenBSD 6.6 Errata

OpenBSD through 6.6 allows local users to escalate to root because a check for LD_LIBRARY_PATH in setuid programs can be defeated by setting a very small RLIMIT_DATA resource limit. When executing chpass or passwd (which are setuid root), _dl_setup_env in ld.so tries to strip LD_LIBRARY_PATH from the environment, but fails when it cannot allocate memory. Thus, the attacker is able to execute their own library code as root.

Packet Storm: Latest News

Zeek 6.0.9