Title: Musings on CVE-2023-6246 on hardened_malloc
Date: 2024-01-31 02:00

Qualys' <s>security team</s> Threat Research Unit [published]( https://seclists.org/oss-sec/2024/q1/68 )
a couple of hours ago a linear two-step heap buffer overflow in glibc's
`syslog()`:

```c
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](https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt)
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](https://github.com/GrapheneOS/hardened_malloc),
[GrapheneOS](https://grapheneos.org)'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](https://chromium.googlesource.com/chromium/src/+/master/base/allocator/partition_allocator/PartitionAlloc.md).
Since `bufsize` is zero, this is a 1-byte
allocation, falling into the
[16 bytes size-class](https://github.com/GrapheneOS/hardened_malloc/blob/main/h_malloc.c#L147),
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](https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/enhanced-security-through-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](https://david942j.blogspot.com/2017/02/project-one-gadget-in-glibc.html), …


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