Title: Security features of musl
Date: 2020-11-04 15:00

Now that I'm running most of my services on [Alpine Linux](https://www.alpinelinux.org/),
I was curious about the state security-wise of its libc, [musl](https://musl.libc.org/),
which used to be *dire* to say the least last time I looked at it.

## Zeroing memory

Albeit `bzero` is deprecated (marked as LEGACY in POSIX.1-2001, removed in POSIX.1-2008),
musl provides [`bzero`]( https://git.musl-libc.org/cgit/musl/tree/src/string/bzero.c ),
as well as the nonstandard `explicit_bzero` (also known as `memset_explicit()` or `memset_s()` in other libc)
to ensure that secret don't stay laying around in memory.


## Support of \_FORTIFY\_SOURCE

\_FORTIFY\_SOURCE is supported via a [libc-agnostic set of headers](https://git.2f30.org/fortify-headers/file/README.html).


## Invalid state handling

musl uses `assert`, which is a wrapper around `__assert_fail` to validate
assumptions and catch invalid states/violations, resulting in an immediate halt
of the program, instead of doing some dangerous magic to parse the callstack
and the environment.


## CSPRNG

Currently, musl [doesn't](https://www.openwall.com/lists/musl/2018/07/02/5)
[provide]( https://www.openwall.com/lists/musl/2017/11/27/14 ) a
[CSPRNG](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator),
but it's on the todo-list.


## Support for %n

Nowadays, the only reasonable usage of the `%n` specifier in `printf` and its
friends is to mount [format-string attacks](https://en.wikipedia.org/wiki/Uncontrolled_format_string).
But since musl aims at being as compliant as possible, this specifier is unfortunately [implemented and
available]( https://git.musl-libc.org/cgit/musl/tree/src/stdio/vfprintf.c).
It could be interesting to put it being a define, a bit like [Visual Studio is
doing](https://docs.microsoft.com/en-us/previous-versions/hf4y5e3w(v=vs.140)).


## RELRO

[RELRO](https://tk-blog.blogspot.com/2009/02/relro-not-so-well-known-memory.html) is supported
since [musl 1.1.0](https://musl.libc.org/releases.html), released in April
2014.


## atexit hardening

It's [old](
https://binholic.blogspot.com/2017/05/notes-on-abusing-exit-handlers.html )
[news](https://buffer.antifork.org/security/heap_atexit.txt) that `atexit` is a
cool vector to transform and arbitrary r/w into arbitrary code exec. glibc [is
using
mangling](https://github.com/bminor/glibc/blob/master/stdlib/cxa_atexit.c),
like
[microsoft](https://moflow.org/Presentations/200703%20EuSecWest%20-%20Windows%20Vista%20Exploitation%20Countermeasures/rjohnson%20-%20Windows%20Vista%20Exploitation%20Countermeasures.pdf),
OpenBSD [marks them as
RO](https://isopenbsdsecu.re/mitigations/atexit_hardening/). Unfortunately,
musl [doesn't do any defensive in this area](
https://git.musl-libc.org/cgit/musl/tree/src/exit/atexit.c ).


## Stack cookies

Building musl with stack-based overflow hardening (think "cookies") is possible since [musl
1.1.9](https://musl.libc.org/releases.html), released in May 2015.

## Setjmp/longjmp hardening

`setjmp`/`longjmp` can be used to transform a limited write + jmp into an
arbitrary write to registers and more. The glibc does some [mangling](https://github.com/bminor/glibc/commit/272b289859eff42d77fac6cf3125b38b0ff01791#diff-d89bba48e8edf8d5ff67f6d548cd8306), like the [OpenBSD libc](https://github.com/openbsd/src/commit/9d294f3012b8bf3b592930ba1f7a456527cf33e0),
while Microsoft does [sanity check](https://blog.trendmicro.com/trendlabs-security-intelligence/control-flow-guard-improvements-windows-10-anniversary-update/) (validating that the stack pointer is pointing to the stack, that the instruction pointers points within the current module, all well as validated `setjmp` callsites, …). musl doesn't do [any hardening]( https://github.com/ifduyue/musl/blob/master/src/setjmp/i386/longjmp.s ) there.


## Compatibility with \*SAN

Currently, musl [isn't compatible](https://github.com/google/sanitizers/issues/1080)
with ASAN/MSAN/TSAN/UBSAN/… which isn't really super-duper important since I'm
not fuzzing with musl anyway.


## Development practises

musl's development is pretty old school: a [mailing
list](https://wiki.musl-libc.org/reporting-bugs.html) for bug tracker, a [cgit
repository](https://git.musl-libc.org/cgit/musl), no continuous builds nor
integrations with static analysers like
[coverity](https://en.wikipedia.org/wiki/Coverity) or
[LGTM](https://lgtm.com/), 
no fuzzing, … meh.


## Memory allocator

This is the juicy part of the blogpost: userland heap management. musl
used to have a terrible (security-wise) allocator, replaced with `malloc-ng`
as part of [musl 1.2](https://musl.libc.org/releases.html) released in August
2020.

- [jemalloc](http://jemalloc.net/),
  [ptmalloc](https://sourceware.org/glibc/wiki/MallocInternals),
  [nedmalloc](https://www.nedprod.com/programs/portable/nedmalloc/), … are all
  using per thread memory pools, while musl uses a global lock, likely to
	guarantee global consistency and avoid race-conditions that could result in
	undetected memory corruption exploitations. On the downside, this might
	result in contention on multi-threaded programs.
- Freed and allocated memory isn't zeroed, so data leak from previously
	allocated memory is possible. Zeroing on free and checking if the zone is
	still zeroed upon allocation would detect `write-after-free` at this time,
	but this would likely be too expensive. Zeroing the first 8-bytes would be a
	good compromise, and doing so on allocation is not that expensive, since it's
	priming up caches. [Android is doing it](https://android-review.googlesource.com/c/platform/build/+/1380902).
- There are no guard pages everywhere à la [Electric Fence](https://elinux.org/Electric_Fence),
	but only a fixed-size one below the out-of-band metadata.
	But since the allocator is often returning freed memory to the system,
	this tends to leave unmmaped chunks scattered around.
- There is a single global secret, generated once via ASLR, or using
	[`AT_RANDOM`](https://man7.org/linux/man-pages/man3/getauxval.3.html) if
	available, used to check that chunks aren't forged. Unfortunately, it's not
	recomputed for every chunk but simply stored in them, meaning that if
	leaked once, it's game over.
- There are pointers to the OOB metadata inside of the chunk, allowing an
	attacker with limited reads to rapidly gain access to the secret.
- It's not possible to free unaligned chunks, and if the attacker doesn't have
	read capabilities, it's also impossible to free non-allocated chunks, making
	it possible to detect double-free and invalid-free deterministically.
- When freed, chunks are put in a FIFO, making it a bit harder to trigger an
	use-after-free, but since no randomization is applied there, it's not *that*
	hard. Also, it could be interesting to quarantine the memory by marking it as
	`PROT_NONE` to catch low-hanging stuff.
- Zero-size allocations aren't treated as a special case. They used to be
	pretty wild due to their underspecified interaction with `realloc`,
	but are [still scary nowadays](http://www.hackitoergosum.org/2010/HES2010-jvanegue-Zero-Allocations.pdf).
	malloc-ng's design prevents using pointing at a non-rw area instead,
	and decision was made that the added complexity of wiring something to handle
	this special case wasn't worth it.
- Single-byte overflows are detected via a NULL-canary at the end of the
	allocation at re-allocation/free time, to prevent string-related problems,
	but it could be interesting to make it a bit larger, and add some
	randomization to it.

## Conclusion

Generally better than
[glibc](https://www.gnu.org/software/libc/)/[µClibc](https://uclibc.org/)/[dietlibc](https://www.fefe.de/dietlibc/)/[µClibc-ng](https://uclibc-ng.org/)/… allocator-wise,
which is the biggest attack-surface, but worse than [OpenBSD's one](https://isopenbsdsecu.re/mitigations/).
More than decent in my threat model.
