Musings on CVE-2023-6246 on hardened_malloc
Wed 31 January 2024 — download

Qualys' security team Threat Research Unit published a couple of hours ago a linear two-step heap buffer overflow in glibc's syslog():

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);

the tl;dr is that bufsize is 0 while l is user-controlled. As mentioned in the advisory, messing with nss structures as done in their (phenomenal) Baron Samedit sudo exploit is a good way to get a root shell on the glibc.

While the bug is in glibc's syslog, it's not unheard of for people to run custom allocators for performance/security/speed/… reasons. One of those could be, for example, hardened_malloc, GrapheneOS's security-focused allocator, raising the question "would hardened_malloc make this particular bug unexploitable on my x86_64 Debian machine?"

After discussing this with friends, we don't think that it makes the bug completely unexploitable, but ridiculously complicated, which is good enough™ for me. But keep in mind that this "analysis" was done hastily at 2am, so caveat lector.

hardened_malloc uses size-based slabs isolation, popularised by PartitionAlloc. Since bufsize is zero, this is a 1-byte allocation, falling into the 16 bytes size-class, the smallest after the special 0 one. So to exploit this, one would have to find an interesting object of size 16 bytes or lower to overwrite. But since canaries are enabled by default, this becomes even more difficult: sizes of allocations are actually bumped by 8 bytes, meaning that one would actually have to find an interesting object of size 8 bytes or lower.

Moreover, 16-byte slabs can contain at most 256 allocations, and are surrounded by guard pages, meaning that accessing anything below buf and above buf+(256*16) will result in a crash.

Allocations are randomized, which might help for bruteforcing the heap layout: if the current one isn't exploitable, just crash and start again. But it will also result in a lot more crashes, since buf might be allocated closer to the guard page.

There are of course other mitigations, but they aren't relevant in this particular case, like canaries that are checked on free, or ARM's MTE that completely kills linear-overflows.

Given the ludicrous amount of randomization hardened_malloc applies to heap bases (32G per region), bruteforcing offsets of anything not on the heap is futile. So one would have to find something interesting in an object of 8 bytes or less on the heap, like a path to corrupt as in service_user, or some partial-overwrite of a function-pointer to call a one-shot-gadget, …

Thanks to strcat for the handholding, and to jdoe, drvink and J for their diligent proofreading,