What is musl libc? How it differs from glibc and why it matters for Alpine and sshole
musl is the C library behind Alpine Linux and many containers. Here is what it does, where it differs from glibc, and how a small difference caused the sshole OpenSSH bug.
· 7 min read
Written by AI from the sources linked below. How we use AI
On this page
In short
musl is a small, standards-focused implementation of the C standard library for Linux. Almost every program on a Linux system calls a "libc" for basics such as memory allocation, threads, DNS lookups and password hashing, and Alpine Linux, many container images and some router and embedded builds use musl where Debian, Ubuntu and Red Hat use the GNU C Library (glibc). The two behave differently in many small ways. This week one of those differences, in how the password-hashing function crypt() fails, turned into a critical OpenSSH login bypass.
Why this question came up
On 5 November 2026 OpenSSH released version 10.6p2 to fix CVE-2026-123158, nicknamed sshole. It lets an attacker log in to accounts that have no password, but only on Linux systems that run OpenSSH on musl without PAM. Red Hat's advisory says its products are not affected because they link OpenSSH against glibc and use PAM by default. A bug that exists on one libc and not the other is a good reason to understand what the libc is and why the two differ.
What a libc does
The musl project describes a libc as an implementation of the standard library functionality in the ISO C and POSIX standards, plus common extensions, built on top of the Linux system-call interface. The kernel controls access to hardware, memory, filesystems and privileges. The libc sits between the kernel and your program and is responsible for several things, per musl's own description:
- C bindings for the operating system's interfaces (opening files, creating processes).
- Higher-level buffered input and output (
stdio), memory allocation, thread creation and synchronisation, and loading shared libraries. - The pure library routines of the C language, such as
strstr,snprintf,strtoland the maths functions.
Programs written in C, C++, Rust, Go (when it uses cgo) and many scripting languages end up calling it, directly or through a runtime. A binary built against one libc generally expects that libc at run time: a program compiled for glibc will often not start on a musl system, and the reverse can also fail. This is why "which libc is this image built on?" is a real question when you choose a base image.
Where musl comes from
According to the project, musl has development roots going back to 2005, was named and first released as musl in 2011, and aimed to be an alternative to glibc and uClibc that could serve both tiny embedded systems and ordinary desktops and servers. Since 2012 it has been licensed under the permissive MIT licence. Alpine Linux describes itself as based on musl libc and busybox, and the musl authors say their design for small, efficient programs carried musl and like-minded distributions to prominence "in the container deployment wave".
What musl optimises for
The project lists its key principles, and each one explains a behaviour you may meet:
- Simplicity. The simpler the code, the less room for bugs. musl prefers simple algorithms unless there is a compelling reason not to.
- Resource efficiency. Static linking pulls in very little code the program does not use. The project says minimal statically linked binaries can be under 10 kB of code, and useful programs under 50 kB. The libc's own global data stays under 8 kB, and all dynamic-linking functionality lives in one shared library file instead of being split across several.
- Correctness. The authors say musl was the first Linux libc with mutexes safe to use inside reference-counted objects, the first with condition variables where newly arrived waiters cannot steal wake-ups, and the first with working thread cancellation free of resource-leak races. Their approach is to read the specifications closely and follow them.
glibc, by contrast, has a long history of supporting a very wide range of extensions and legacy behaviours. Neither approach is wrong; they make different trade-offs.
Practical differences that can bite
The musl wiki keeps a detailed page of functional differences from glibc. A few that matter in everyday work:
Thread stack size. glibc sizes a new thread's stack from the main thread's stack limit, which the wiki says generally ends up at 2 to 10 MB. musl's default is 128 kB (80 kB before version 1.1.21). Programs with deep recursion in threads can crash on musl unless they set a stack size with pthread_attr_setstacksize, or link with a larger default (supported since musl 1.1.21 through the PT_GNU_STACK header).
DNS. glibc's resolver tries the nameservers in resolv.conf one after another. musl queries all of them in parallel (up to three) and accepts whichever answer arrives first. That can make lookups faster and more reliable, but it also means that if your nameservers disagree, you may get an answer from a different one than you expect. musl did not support DNS over TCP until version 1.2.4, which affected large responses such as those from DNSSEC. It also does not do internationalised domain-name conversion for you.
Loading libraries. musl does not support lazy binding of functions the way glibc's dynamic linker does, and in musl dlclose is a no-op: libraries stay loaded until the process exits. Programs that rely on a library being unloaded and reloaded, and static state being reset, behave differently.
Locale and character sets. musl's default locale is always C.UTF-8, while the wiki says glibc's default (up to at least 2.26) is C. musl's iconv is small and does not support //TRANSLIT. Code that depends on glibc's richer character-set coverage may need changes.
GNU extensions. musl provides only the standard versions of functions such as basename and strerror_r, where glibc ships alternative GNU semantics, and musl's getopt stops at the first non-option argument rather than permuting argv. Software that quietly relies on GNU behaviour may fail to build or behave oddly.
None of these is a flaw in either library. They are places where standards leave room, or where glibc added extensions, and portable code should not depend on them. They are also the reason "it works in my Debian container" does not guarantee "it works on Alpine".
The sshole bug as a case study
The vulnerable chain needs three behaviours that are each reasonable alone. This summary combines OpenSSH's release notes, Alpine's advisory and reporting by Help Net Security and SOCRadar:
- Accounts without passwords store
*. In/etc/shadow, an account that should never accept a password (a key-only user, for example) has*in the password field. - sshd hashes a guess even for those accounts. In 2016 OpenSSH changed sshd so that when an account has no usable hash, it borrows the salt of another account's real hash and still hashes the attempt. That removed a timing difference that revealed which usernames exist (CVE-2016-6210).
- musl's
crypt()returns*when it refuses an input. Since 2012, musl refuses SHA-256 and SHA-512 passwords longer than 256 bytes and returns the string*rather than a distinct error.
When an attacker sends a password of 257 to 1,024 bytes for a passwordless account, musl's crypt() returns *, which equals the stored *, and sshd treats it as a correct password. With PAM enabled, password checking goes through a different path, which is why PAM builds are not affected. OpenSSH describes its fix as a workaround and says the root cause is outside sshd; per SOCRadar, the fix makes sshd reject a match whenever the stored field is shorter than four characters.
The lesson is not "musl is insecure". It is that a function's failure value (here, an error indicator that looked like valid data) is part of its contract, and that security-sensitive callers must not assume what a particular libc returns. The same caution applies to glibc.
How to tell which libc you have
These checks are standard, but run them on your own system rather than trusting an image's name:
cat /etc/os-releaseshows the distribution. Alpine uses musl.ldd --version: on glibc systems the output mentions GNU libc or GLIBC; on musl it begins with "musl libc" and the version.ls /lib/ld-musl-*shows a musl dynamic loader if one is installed (the file name includes the CPU architecture).
If you maintain container images, check the base image of every layer that includes sshd. Most application containers do not run an SSH server at all, and Alpine says containers that do not run sshd are not affected by sshole.
What to do if you run musl-based systems
- Update packages regularly and restart services after upgrading; Alpine's advisory notes that upgrading the package does not restart a running sshd.
- Turn off SSH password authentication where you can. With
PasswordAuthentication no, passwords are never checked, which closed this specific hole. - Test software on the libc you ship. If you build for Alpine, run your test suite there, not only on a glibc development machine.
- When a security advisory lists "musl without PAM" or similar conditions, treat them as a checklist for your inventory, not a reason to relax.
What we are not claiming
This guide covers well-documented, public differences. We did not test any behaviour ourselves, and the lists above are not complete: the musl wiki covers more. Version-specific statements (such as the 1.1.21 stack-size change) come from the musl wiki and may have moved on.
Sources
- musl libc: About musl
- musl wiki: Functional differences from glibc
- Alpine Linux: security advisory, openssh-server password authentication bypass
- OpenSSH release notes, 10.6p2
- Red Hat: CVE-2026-123158
- Help Net Security: OpenSSH flaw exploited in the wild just hours after disclosure
- SOCRadar: sshole, OpenSSH fixes password bypass on Alpine Linux
- Alpine Linux news archive
Written by
The official account of the people who run audiotale.net: announcements, conversation starters and the newsroom's drafts. Posts written with AI say so. Every other account here is a real person.
Read next
CVE-2026-123158 sshole: OpenSSH 10.6p2 fixes Alpine login bypass, now exploited
A password of 257 bytes or more logs attackers into passwordless accounts on musl-based Linux such as Alpine; GreyNoise and Previdian have seen real intrusions.
audiotale.net team7 min read
Azure Linux 3.0.2022 (3.0.20261105-3.0): 470+ CVE references, kernel 6.6.161.1, eMudhra CA removal
Microsoft's Azure Linux release bundles hundreds of CVE references and distrusts roots tied to the eMudhra incident CVE-2026-114406; here is what to update and verify.
audiotale.net team5 min read
Linux 7.4-rc2: Torvalds says AI-prompted fixes may be peaking, BPF now fixing faster than reports
Linus Torvalds relays that BPF fixes now outpace new reports after the biggest merge window on record, with 19,671 commits in Linux 7.4's.
audiotale.net team7 min read