[prev in list] [next in list] [prev in thread] [next in thread] 

List:       oss-security
Subject:    Re: [oss-security] CVE-2023-6246: Heap-based buffer overflow in the glibc's syslog()
From:       Siddhesh Poyarekar <siddhesh.poyarekar () gmail ! com>
Date:       2024-01-30 19:46:51
Message-ID: CAAHN_R22YUwGYBqk_b2JPwdJjkYV4DzeJpwngNqS3MCdX0BHRA () mail ! gmail ! com
[Download RAW message or body]

FYI, the glibc advisory announcement is here:

https://sourceware.org/pipermail/libc-announce/2024/000037.html

and here are the individual advisories:

https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2024-0001;hb=HEAD
https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2024-0002;hb=HEAD
https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=advisories/GLIBC-SA-2024-0003;hb=HEAD

Thanks,
Sid

On Tue, Jan 30, 2024 at 1:32 PM Qualys Security Advisory <qsa@qualys.com> wrote:
> 
> 
> Qualys Security Advisory
> 
> CVE-2023-6246: Heap-based buffer overflow in the glibc's syslog()
> 
> 
> ========================================================================
> Contents
> ========================================================================
> 
> Summary
> Analysis
> Proof of concept
> Exploitation
> Acknowledgments
> Timeline
> 
> 
> ========================================================================
> Summary
> ========================================================================
> 
> We discovered a heap-based buffer overflow in the GNU C Library's
> __vsyslog_internal() function, which is called by both syslog() and
> vsyslog(). This vulnerability was introduced in glibc 2.37 (in August
> 2022) by the following commit:
> 
> https://sourceware.org/git?p=glibc.git;a=commit;h=52a5be0df411ef3ff45c10c7c308cb92993d15b1
> 
> and was also backported to glibc 2.36 because this commit was a fix for
> another, minor vulnerability in __vsyslog_internal() (CVE-2022-39046, an
> "uninitialized memory [read] from the heap"):
> 
> https://sourceware.org/bugzilla/show_bug.cgi?id=29536
> 
> For example, we confirmed that Debian 12 and 13, Ubuntu 23.04 and 23.10,
> and Fedora 37 to 39 are vulnerable to this buffer overflow. Furthermore,
> we successfully exploited an up-to-date, default installation of Fedora
> 38 (on amd64): a Local Privilege Escalation, from any unprivileged user
> to full root. Other distributions are probably also exploitable.
> 
> To the best of our knowledge, this vulnerability cannot be triggered
> remotely in any likely scenario (because it requires an argv[0], or an
> openlog() ident argument, longer than 1024 bytes to be triggered).
> 
> Last-minute note: in December 1997 Solar Designer published information
> about a very similar vulnerability in the vsyslog() of the old Linux
> libc (https://insecure.org/sploits/linux.libc.5.4.38.vsyslog.html).
> 
> 
> ========================================================================
> Analysis
> ========================================================================
> 
> In the glibc, both syslog() and vsyslog() call the vulnerable function
> __vsyslog_internal():
> 
> ------------------------------------------------------------------------
> 122 __vsyslog_internal (int pri, const char *fmt, va_list ap,
> 123                     unsigned int mode_flags)
> 124 {
> 125   /* Try to use a static buffer as an optimization.  */
> 126   char bufs[1024];
> 127   char *buf = NULL;
> 128   size_t bufsize = 0;
> ...
> 171 #define SYSLOG_HEADER(__pri, __timestamp, __msgoff, pid) \
> 172   "<%d>%s%n%s%s%.0d%s: ",                                \
> 173   __pri, __timestamp, __msgoff,                          \
> 174   LogTag == NULL ? __progname : LogTag,                  \
> 175   "[" + (pid == 0), pid, "]" + (pid == 0)
> ...
> 182     l = __snprintf (bufs, sizeof bufs,
> 183                     SYSLOG_HEADER (pri, timestamp, &msgoff, pid));
> ...
> 187   if (0 <= l && l < sizeof bufs)
> 188     {
> ...
> 202     }
> 203
> 204   if (buf == NULL)
> 205     {
> 206       buf = malloc ((bufsize + 1) * sizeof (char));
> ...
> 213             __snprintf (buf, l + 1,
> 214                         SYSLOG_HEADER (pri, timestamp, &msgoff, pid));
> ...
> 221           __vsnprintf_internal (buf + l, bufsize - l + 1, fmt, apc,
> 222                                 mode_flags);
> ------------------------------------------------------------------------
> 
> - at lines 182-183, SYSLOG_HEADER() includes __progname (the basename()
> of argv[0]) if LogTag is NULL (e.g., if openlog() was not called, or
> called with a NULL ident argument);
> 
> - because a local attacker fully controls argv[0] and hence __progname
> (even when executing a SUID-root program such as su), at line 187 l
> (the return value of __snprintf()) can be larger than sizeof bufs
> (1024), in which case the code block at lines 188-202 is skipped;
> 
> - consequently, at line 203 buf is still NULL and bufsize is still 0,
> and at line 206 a very small 1-byte buf is malloc()ated (because
> bufsize is 0);
> 
> - at lines 213-214 this small buf is overflowed with the attacker-
> controlled __progname (because l is larger than 1024), and at lines
> 221-222 this small buf is further overflowed (because bufsize - l + 1
> is 0 - l + 1, a very large size_t).
> 
> 
> ========================================================================
> Proof of concept
> ========================================================================
> 
> $ (exec -a "`printf '%0128000x' 1`" /usr/bin/su < /dev/null)
> Password: Segmentation fault (core dumped)
> 
> 
> ========================================================================
> Exploitation
> ========================================================================
> 
> We decided to exploit this vulnerability through su (the most common
> SUID-root program) on Fedora 38. To authenticate a user, su calls the
> PAM library, and if the password provided by the user is incorrect, then
> PAM calls the glibc's syslog() function without calling openlog() first,
> thus allowing us to trigger the buffer overflow in __vsyslog_internal():
> 
> ------------------------------------------------------------------------
> 782                         pam_syslog(pamh, LOG_NOTICE,
> 783                                  "authentication failure; "
> 784                                  "logname=%s uid=%d euid=%d "
> 785                                  "tty=%s ruser=%s rhost=%s "
> 786                                  "%s%s",
> 787                                  new->name, new->uid, new->euid,
> 788                                  tty ? (const char *)tty : "",
> 789                                  ruser ? (const char *)ruser : "",
> 790                                  rhost ? (const char *)rhost : "",
> 791                                  (new->user && new->user[0] != '\0')
> 792                                   ? " user=" : "",
> 793                                  new->user
> 794                         );
> ------------------------------------------------------------------------
> 107 pam_syslog (const pam_handle_t *pamh, int priority,
> 108             const char *fmt, ...)
> 109 {
> ...
> 113   pam_vsyslog (pamh, priority, fmt, args);
> ------------------------------------------------------------------------
> 73 pam_vsyslog (const pam_handle_t *pamh, int priority,
> 74              const char *fmt, va_list args)
> 75 {
> ..
> 81       if (asprintf (&msgbuf1, "%s(%s:%s):", pamh->mod_name,
> 82                     pamh->service_name?pamh->service_name:"<unknown>",
> 83                     _pam_choice2str (pamh->choice)) < 0)
> ..
> 91   if (vasprintf (&msgbuf2, fmt, args) < 0)
> ..
> 99   syslog (LOG_AUTHPRIV|priority, "%s %s",
> 100           (msgbuf1 ? msgbuf1 : _PAM_SYSTEM_LOG_PREFIX), msgbuf2);
> ------------------------------------------------------------------------
> 
> But what should we overwrite in the heap to successfully exploit this
> buffer overflow? Initially, because su calls setlocale(LC_ALL, ""); at
> the very beginning of its su_main() function, we tried to reuse the key
> idea from our Baron Samedit exploits (CVE-2021-3156 in Sudo): we wrote a
> rudimentary fuzzer to execute su with a random argv[0] and random locale
> environment variables and automatically inspect the resulting crashes in
> gdb. Unfortunately this fuzzer failed to produce interesting results: we
> only obtained a handful of unique crashes, and they did not look very
> promising.
> 
> However, we did not investigate the reasons for this failure, because
> while browsing through su's source code we noticed that su_main() calls
> env_whitelist_from_string() to parse the argument of the -w command-line
> option:
> 
> ------------------------------------------------------------------------
> 1118                 case 'w':
> 1119                         env_whitelist_from_string(su, optarg);
> 1120                         break;
> ------------------------------------------------------------------------
> 692 static int env_whitelist_from_string(struct su_context *su, const char *str)
> 693 {
> 694         char **all = strv_split(str, ",");
> ...
> 703         STRV_FOREACH(one, all)
> 704                 env_whitelist_add(su, *one);
> 705         strv_free(all);
> 706         return 0;
> 707 }
> ------------------------------------------------------------------------
> 662 static int env_whitelist_add(struct su_context *su, const char *name)
> 663 {
> 664         const char *env = getenv(name);
> 665
> 666         if (!env)
> 667                 return 1;
> 668         if (strv_extend(&su->env_whitelist_names, name))
> 669                 err_oom();
> 670         if (strv_extend(&su->env_whitelist_vals, env))
> 671                 err_oom();
> 672         return 0;
> 673 }
> ------------------------------------------------------------------------
> 
> Conveniently, env_whitelist_from_string() allows us (attackers) to
> malloc()ate and free() an arbitrary number of arbitrary strings at the
> very beginning of su's execution: an almost perfect heap feng shui. We
> therefore rewrote our fuzzer to execute su with a random argv[0] and a
> random whitelist option (instead of random locale environment variables)
> and immediately observed numerous unique crashes; among these, three in
> particular caught our attention.
> 
> 
> ========================================================================
> 1/ Corruption of PAM structures
> ========================================================================
> 
> Surprisingly, our fuzzer directly overwrote two PAM function pointers
> (in struct pam_data and struct handler):
> 
> ------------------------------------------------------------------------
> Thread 2.1 "su" received signal SIGSEGV, Segmentation fault.
> 0x00007fa7d3b0e3ac in _pam_free_data (status=7, pamh=0x56211242ec10) at \
> /usr/src/debug/pam-1.5.2-16.fc38.x86_64/libpam/pam_data.c:161 161                 \
>                 last->cleanup(pamh, last->data, status);
> ...
> => 0x7fa7d3b0e3ac <pam_end+92>: call   *%rax
> rax            0x4141414141414141  4702111234474983745
> ------------------------------------------------------------------------
> Thread 2.1 "su" received signal SIGSEGV, Segmentation fault.
> 0x00007f928b5e5781 in _pam_dispatch_aux (use_cached_chain=<optimized out>, resumed=<optimized \
> out>, h=0x55f2e374aae0, flags=0, pamh=0x55f2e374aae0) at \
> /usr/src/debug/pam-1.5.2-16.fc38.x86_64/libpam/pam_dispatch.c:110 110                 retval \
>                 = h->func(pamh, flags, h->argc, h->argv);
> ...
> => 0x7f928b5e5781 <_pam_dispatch+465>:  call   *%rax
> rax            0x4545454545454545  4991471925827290437
> ------------------------------------------------------------------------
> 
> Although this sounds exciting at first (a call to 0x4141414141414141!)
> we decided to not pursue this avenue of exploitation:
> 
> - we cannot overwrite such a function pointer with null bytes (because
> we overflow __vsyslog_internal()'s buffer with a null-terminated
> string), but userland addresses contain at least two null bytes;
> 
> - we could try to partially overwrite such a function pointer, but we do
> not control the end of the string that overflows __vsyslog_internal()'s
> buffer (the end of the aforementioned pam_syslog() format string), and
> such an uncontrolled, partially overwritten function pointer is very
> unlikely to miraculously point to a useful ROP gadget.
> 
> 
> ========================================================================
> 2/ Corruption of heap metadata
> ========================================================================
> 
> Unsurprisingly, our fuzzer also overwrote various pieces of heap
> metadata (chunk headers managed internally by the glibc's malloc), and
> therefore triggered all kinds of assertion failures and security checks:
> 
> ------------------------------------------------------------------------
> $ grep -A1 __libc_message fuzzer.out | cut -d'"' -f2 | sort -u
> ...
> chunk_main_arena (bck->bk)
> chunk_main_arena (fwd)
> corrupted double-linked list
> corrupted double-linked list (not small)
> corrupted size vs. prev_size
> corrupted size vs. prev_size in fastbins
> double free or corruption (out)
> free(): corrupted unsorted chunks
> free(): invalid next size (fast)
> free(): invalid pointer
> free(): invalid size
> malloc_consolidate(): invalid chunk size
> malloc(): corrupted top size
> malloc(): invalid size (unsorted)
> malloc(): smallbin double linked list corrupted
> malloc(): unaligned tcache chunk detected
> malloc(): unsorted double linked list corrupted
> munmap_chunk(): invalid pointer
> ------------------------------------------------------------------------
> 
> Although some of these corruptions might be exploitable, we decided to
> not pursue this avenue of exploitation either:
> 
> - we cannot overwrite a chunk header with a size field and an fd or bk
> pointer that are both valid (they must both contain null bytes to be
> valid), which severely limits our exploitation options;
> 
> - in any case, we would probably need a specific heap, mmap, or stack
> address to exploit such a corruption, but we do not have the luxury of
> an information leak, and all these addresses are too heavily
> randomized by ASLR to be brute forced.
> 
> 
> ========================================================================
> 3/ Corruption of nss structures
> ========================================================================
> 
> Our fuzzer also produced two crashes that immediately caught our
> attention because they are directly related to one of the techniques
> that we used to exploit Baron Samedit:
> 
> ------------------------------------------------------------------------
> Thread 2.1 "su" received signal SIGSEGV, Segmentation fault.
> __GI___nss_lookup (ni=ni@entry=0x7ffe876a05e8, fct_name=fct_name@entry=0x7fbba214e4e7 \
> "getpwnam_r", fct2_name=fct2_name@entry=0x0, fctp=fctp@entry=0x7ffe876a05f0) at nsswitch.c:67 \
>                 67        *fctp = __nss_lookup_function (*ni, fct_name);
> ...
> => 0x7fbba20ec50e <__GI___nss_lookup+30>:       mov    (%rax),%rdi
> rax            0x4141414141414141  4702111234474983745
> ------------------------------------------------------------------------
> Thread 2.1 "su" received signal SIGSEGV, Segmentation fault.
> __nss_module_get_function (module=0x4141414141414141, name=name@entry=0x7f0aed9034e7 \
> "getpwnam_r") at nss_module.c:328 328       if (!__nss_module_load (module))
> ...
> => 0x7f0aed8a34b7 <__nss_module_get_function+39>:       mov    (%rdi),%eax
> rdi            0x4141414141414141  4702111234474983745
> ------------------------------------------------------------------------
> 
> As discussed in the "2/ struct service_user overwrite" subsection of our
> Baron Samedit advisory, if we overwrite the name[] field of a heap-based
> struct nss_module with a string of characters that contains a slash (for
> example "A/B/C"), then at lines 180-181 the name of a shared library is
> constructed ("libnss_A/B/C.so.2"), and at line 187 this shared library
> is loaded from our current working directory (because its name contains
> a slash, but does not start with a slash) and executed as root (because
> su is a SUID-root program):
> 
> ------------------------------------------------------------------------
> 170 module_load (struct nss_module *module)
> 171 {
> ...
> 180     if (__asprintf (&shlib_name, "libnss_%s.so%s",
> 181                     module->name, __nss_shlib_revision) < 0)
> ...
> 187     handle = __libc_dlopen (shlib_name);
> ------------------------------------------------------------------------
> 
> Unfortunately, the __progname part (which we control) of the string that
> overflows __vsyslog_internal()'s buffer cannot contain a slash (because
> __progname is the basename() of argv[0]). Luckily, however, the part of
> the overflowing string that we do not control (the pam_syslog() format
> string) includes the absolute path of our tty, which contains a slash.
> For example, if:
> 
> - our tty is /dev/pts/23 (we use forkpty() in our exploit);
> 
> - our unprivileged local user is nobody (uid 65534);
> 
> - the argv[0] (and hence __progname) that we use to execute su is a long
> string of 'A' characters (longer than 1024);
> 
> then we can overwrite the name[] field of a heap-based struct nss_module
> with a string of the form:
> 
> "AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= uid=65534 euid=0 \
> tty=/dev/pts/23 ruser=nobody rhost=  user=root" 
> Consequently, if we first create the following three directories (in our
> current working directory):
> 
> "libnss_AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= uid=65534 euid=0 \
> tty=" "libnss_AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= uid=65534 \
> euid=0 tty=/dev" "libnss_AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= \
> uid=65534 euid=0 tty=/dev/pts" 
> and also create the following shared library (in our current working
> directory):
> 
> "libnss_AAAAAAAAAA: pam_unix(su:auth): authentication failure; logname= uid=65534 euid=0 \
> tty=/dev/pts/23 ruser=nobody rhost=  user=root.so.2" 
> then this shared library will eventually be loaded and executed with
> full root privileges. In our tests, it takes a few 10,000s of tries to
> successfully brute force the exploit parameters (the length of argv[0],
> and the whitelist option and its associated environment variables).
> 
> Note: this exploit could certainly be made much more efficient; in
> theory, it could even be a one-shot exploit, because we do not need to
> brute force the ASLR, only the heap layout.
> 
> 
> ========================================================================
> Acknowledgments
> ========================================================================
> 
> We thank the glibc developers (Carlos O'Donell, Siddhesh Poyarekar,
> Arjun Shankar, Florian Weimer, and Adhemerval Zanella in particular),
> Red Hat Product Security (Guilherme Suckevicz in particular), and the
> members of linux-distros@openwall (Salvatore Bonaccorso in particular).
> 
> 
> ========================================================================
> Timeline
> ========================================================================
> 
> 2023-11-07: We sent a preliminary draft of our advisory to Red Hat
> Product Security.
> 
> 2023-11-15: Red Hat Product Security acknowledged receipt of our email.
> 
> 2023-11-16: Red Hat Product Security asked us if we could share our
> exploit with them.
> 
> 2023-11-17: We sent our exploit to Red Hat Product Security.
> 
> 2023-11-21: Red Hat Product Security confirmed that our exploit worked,
> and assigned CVE-2023-6246 to this heap-based buffer overflow in
> __vsyslog_internal().
> 
> 2023-12-05: Red Hat Product Security sent us a patch for CVE-2023-6246
> (written by the glibc developers), and asked us for our feedback.
> 
> 2023-12-07: While reviewing this patch, we discovered two more minor
> vulnerabilities in the same function (an off-by-one buffer overflow and
> an integer overflow). We immediately sent an analysis, proof of concept,
> and patch proposal to Red Hat Product Security, and suggested that we
> directly involve the glibc security team.
> 
> 2023-12-08: Red Hat Product Security acknowledged receipt of our email,
> and agreed that we should directly involve the glibc security team. We
> contacted them on the same day, and they immediately replied with very
> constructive comments.
> 
> 2023-12-11: The glibc security team suggested that we postpone the
> coordinated disclosure of all three vulnerabilities until January 2024
> (because of the upcoming holiday season). We agreed.
> 
> 2023-12-13: Red Hat Product Security assigned CVE-2023-6779 to the
> off-by-one buffer overflow and CVE-2023-6780 to the integer overflow in
> __vsyslog_internal().
> 
> 2024-01-04: We suggested either January 23 or January 30 for the
> Coordinated Release Date of these vulnerabilities. The glibc developers
> agreed on January 30.
> 
> 2024-01-12: The glibc developers sent us an updated version of the
> patches for these vulnerabilities.
> 
> 2024-01-13: We reviewed these patches, and sent our feedback to the
> glibc developers.
> 
> 2024-01-15: The glibc developers sent us the final version of the
> patches for these vulnerabilities.
> 
> 2024-01-16: We sent these patches and a draft of our advisory to the
> linux-distros@openwall. They immediately acknowledged receipt of our
> email.
> 
> 2024-01-30: Coordinated Release Date (18:00 UTC).



-- 
https://gotplt.org


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic