bzero is deprecated (marked as LEGACY in POSIX.1-2001, removed in POSIX.1-2008),
as well as the nonstandard
explicit_bzero (also known as
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.
Invalid state handling
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.
Support for %n
Nowadays, the only reasonable usage of the
%n specifier in
printf and its
friends is to mount format-string attacks.
But since musl aims at being as compliant as possible, this specifier is unfortunately implemented and
It could be interesting to put it being a define, a bit like Visual Studio is
atexit is a
cool vector to transform and arbitrary r/w into arbitrary code exec. glibc is
OpenBSD marks them as
musl doesn't do any defensive in this area.
Building musl with stack-based overflow hardening (think "cookies") is possible since musl 1.1.9, released in May 2015.
longjmp can be used to transform a limited write + jmp into an
arbitrary write to registers and more. The glibc does some mangling, like the OpenBSD libc,
while Microsoft does sanity check (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 there.
Compatibility with *SAN
Currently, musl isn't compatible with ASAN/MSAN/TSAN/UBSAN/… which isn't really super-duper important since I'm not fuzzing with musl anyway.
This is the juicy part of the blogpost: userland heap management. musl
used to have a terrible (security-wise) allocator, replaced with
as part of musl 1.2 released in August
- jemalloc, ptmalloc, 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-freeat 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.
- There are no guard pages everywhere à la 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_RANDOMif 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_NONEto 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. 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.