Network switch with connected cables inside a data center rack

The Star That Opened OpenSSH on musl

November 5, 2026 · 7 min read · By API User

A single asterisk in /etc/shadow is supposed to mean an account can never log in with a password. On a specific class of Linux systems, that asterisk became the password.

The bug is called *sshole. It affects OpenSSH builds on musl libc with PAM disabled and password authentication enabled. If the machine has a passwordless account whose shadow field is *, and at least one other account with a real $5$ or $6$ password hash, an attacker can log in as the * account with any password between 257 and 1,024 bytes. One connection. No retries. OpenSSH shipped 10.6p2 on Nov. 5, 2026 to close it.

If you run sshd on Alpine, OpenWrt, Buildroot, Yocto, or a musl-based container that exposes SSH, the fix is not optional. Update to OpenSSH 10.6p2 and restart the daemon. If you cannot update yet, set PasswordAuthentication no and restart. Upgrading the package alone is not enough because the running sshd keeps the old code in memory.

A three-part collision, not a crypto break

The root cause is a disagreement about what a failed password hash looks like.

Traditionally, a * in the password field is a locked-account marker. No password can produce it, so no password can match it. That convention goes back to 1980, when Bill Joy used daemon:* in an early BSD /etc/passwd. The writeup includes the original line, complete with the real-name field reading “the devil himself.”

Then musl made a reasonable defensive choice. In 2012, its crypt() stopped returning a null pointer on failure and started returning the string * instead. The commit says this was “reportedly a better behavior than failing on error” because programs that did not check for null would no longer crash. Twenty days later, musl added SHA-512 crypt and capped the password at 256 bytes to avoid a denial-of-service cost. Anything longer made crypt() fail, and failure returned *.

OpenSSH supplied the third piece in 2016. To fix a username-enumeration timing leak, CVE-2016-6210, sshd started borrowing another account’s salt when the target account had no usable hash. That kept the response time roughly constant. It also meant a * account now went through the password-hashing path instead of being rejected immediately.

Put the pieces together. The attacker sends a 300-byte password. musl’s crypt() refuses the length, returns *, and sshd compares that to the stored *. They match. The account is unlocked.

Alpine completed the setup in 2022. Its 2019 Docker images had root with no password at all, which became CVE-2019-5021. The first fix locked root with !, but without PAM that also disabled SSH key logins. Alpine 3.16 changed root to * so keys worked again. The marker that was supposed to forbid password logins became the value that accepted them.

Who is actually affected

The conditions are specific, which is why this is not a panic patch for every Linux box. All of the following have to be true:

  • sshd is built against musl, not glibc. Alpine, OpenWrt, Buildroot, and Yocto are the obvious cases.
  • PAM is disabled. Alpine’s openssh-server package is built without PAM by default; openssh-server-pam with UsePAM yes is not affected.
  • PasswordAuthentication is yes, which is the OpenSSH default.
  • A local account has * in its shadow password field. That includes root on Alpine since about 3.16, key-only users created with adduser and no password, and directory-service users that have no shadow entry.
  • At least one local account has a $5$ or $6$ hash. One real password is enough to make every * account reachable.

Root is not automatically reachable by password. OpenSSH’s default PermitRootLogin prohibit-password still protects root unless the administrator set PermitRootLogin yes. But that protection does not help if another * account can log in and then become root through doas or another privilege path. On Alpine’s official tiny-cloud AWS and Azure images, the default alpine user has a * password field and passwordless doas. The images are safe on first boot; they become exploitable when any $6$ hash exists, for example after an administrator sets a console password or adds a user with hashed_passwd.

You are not affected if sshd runs on glibc, if PAM handles password authentication, if password logins are disabled, if there is no account with a * field, or if there is no account with a real $5$/$6$ hash. A container that does not run sshd is not affected. An account locked with ! cannot be used for this bypass, but without PAM it also cannot use key authentication.

What to do now

On Alpine, upgrade and restart:

apk upgrade -U -a
rc-service sshd restart
sshd -T | grep -i passwordauthentication

Alpine 3.21.10, 3.22.8, 3.23.8, and 3.24.4 contain the fixed package. Fixed builds were also published back to 3.16, even though 3.21 reached end of support on Nov. 1. If the last command prints passwordauthentication yes, password logins remain enabled; the update still fixes the bypass. The restart is what loads the fixed daemon.

If you cannot update, disable password authentication now. Keep an existing session open until you have tested a new key login:

mkdir -p /etc/ssh/sshd_config.d
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/00-no-passwords.conf
sshd -t && rc-service sshd restart

Alpine’s default sshd_config reads that drop-in directory from 3.19 onward. Older releases ignore it, so use the full sed procedure in the advisory or edit sshd_config directly. On OpenWrt, Buildroot, and Yocto, rebuild with OpenSSH 10.6p2 or apply the vendor backport; the same PasswordAuthentication no rule closes the hole in the meantime.

To check for abuse, look in /var/log/messages for Accepted password lines for accounts that have no password: root, alpine, or any key-only user. Review doas use, recently created users, files, and authorized_keys on exposed machines. If you cannot rule out a login, treat the host as compromised.

Why a ten-year-old bug got a one-day fix

Özgün Kültekin of Wakefield found the bug by testing OpenSSH’s password path across libc, PAM, and shadow configurations. The writeup says he built sshd on glibc and musl, with and without PAM and shadow, and threw roughly 17,600 test logins at them — weird hashes, broken accounts, and overlong passwords. Then it admits the “I” is doing some work: “i had agent swarms. a lot of them.”

That detail is not a gimmick. OpenSSH’s 10.6p2 release notes say the project now receives a large number of security reports that are findings from AI models or made with AI assistance. It also says it has seen AI-identified bugs independently rediscovered by other researchers, sometimes within hours. The conclusion is blunt: adversaries who do not report bugs can find them too. OpenSSH will therefore ship fixes more frequently instead of batching them for planned releases.

The same notes call this a “critical security workaround” and say the root cause is outside sshd, but exploitable through it and avoidable there. Alpine’s advisory had no CVE identifier at publication time. The absence of a CVE does not make the bug less real; it makes the vendor advisory the source of truth.

There is a larger lesson in the shape of the failure. None of the three components was unreasonable in isolation. The * convention is a sensible lock marker. Returning a non-null sentinel from crypt() avoids crashes. Borrowing a salt to flatten a timing side channel is a legitimate mitigation. The vulnerability lived in the composition, in a path nobody owned end to end.

That is also why “just use key authentication” is good advice but not a complete fix. The bug is in the password path, and many embedded and cloud images still leave that path enabled by default.

Bottom line

The impact is an authentication bypass, with a narrow set of preconditions and a wide footprint in the Alpine, OpenWrt, Buildroot, Yocto, and container ecosystems. There is no key recovery and no remote code execution. If your sshd is on musl without PAM, patch it and restart it. If you cannot patch it today, turn password authentication off. Then check the logs for a login that should have been impossible.

Sources and References