News for hackers. Stuff that matters.
Re:Sshole by dboreham on Thursday November 05, 2026 @10:53AM (#50380688)
> musl can return “*” for errors

wtaf ?

Re:Sshole by za_creature on Thursday November 05, 2026 @11:38AM (#50381252)
There is no doubt in my mind that this was intentional.
Re:Sshole by zbentley on Thursday November 05, 2026 @12:32PM (#50381868)
Well, it was intentional, but not in the way I think you mean.

musl switched to returning `*` so folks stopped accidentally using NULL results, which was causing crashes and risked false-positives comparing NULL to itself.

That was a misfeature and should never have been implemented. But I bet it was an easy band-aid change to make!

Re:Sshole by za_creature on Thursday November 05, 2026 @01:03PM (#50382208)
As opposed to the false positives returned by comparing '*' to itself.

Why * specifically? Why not some random guid generated before committing, if it's so important to not let buggy software crash?

Re:Sshole by zbentley on Thursday November 05, 2026 @01:07PM (#50382263)
I'm agreeing with you, to be clear.

But buggy software might not crash; it might compare NULL == NULL and misbehave, instead.

So there's really no good option here, which is why C is a rancid choice of language for publicly-usable APIs. But we're stuck with it. So it goes.

Re:Sshole by dboreham on Thursday November 05, 2026 @01:24PM (#50382517)
I've been programming in C since 1978 and never until now heard of a function returning "*" on error.
Re:Sshole by zbentley on Thursday November 05, 2026 @01:31PM (#50382624)
This specific manifestation of "C is terrible for shared code APIs" is rare.

But issues caused by folks forgetting to check errno, miscomparing NULL to NULL when calling multiple APIs, dereferencing external-API-returned NULL due to missed error checking, mishandling inherited errno after successful action, or--the most common--mishandling negative integer return sentinels meant to indicate errors? Those are incredibly common.

Re:Sshole by za_creature on Thursday November 05, 2026 @02:19PM (#50383205)
This specific library call is quite poorly designed.

Standard C api calls return an integer, which is 0 on success or nonzero on error. Ignoring it is an intentional choice. `errno` is the hack that shouldn't really exist because it's trivially easy to forget to check that and also has issues with threading (patched by making it a thread local) and more recently, coroutines (which admit no general purpose patch because they're a userspace construct).

Another reason why this is terrible is that it's unclear what the returned pointer's lifetime is. Is it until the next call to crypt? If so, is it thread-local like errno? If it's passed on to the user, is it safe to call free() on it, or is there a crypt_free function that should be called instead?

But all of this, and I mean all of it pales in comparison to returning '*' on error.

Re:Sshole by zbentley on Thursday November 05, 2026 @03:00PM (#50383708)
> But all of this, and I mean all of it pales in comparison to returning '*' on error.

100% agree. This is an egregious failure. But C's error-handling story would be bad even without this, and the badness of C's error handling is what motivated the (bad) decision to return *.

It'd be great if we wrote systems software in a language that supported advanced features like...

checks notes

...returning multiple values.

Hell, even in VBA you had to at least spend the keystrokes to type "on error resume next".

Re:Sshole by gpvos on Thursday November 05, 2026 @05:39PM (#50385517)
It's just that every other string than "*" would have been a better choice, including even the empty string (which would have been the next terrible choice).
Re:Sshole by tredre3 on Thursday November 05, 2026 @02:12PM (#50383115)
It's not a C thing, it's a Linux thing. The asterisk is relevant to how crypt() is used for /etc/shadow.

           If the password field contains some string that is not a valid result of crypt(3), for instance ! or *, the user will not
           be able to use a unix password to log in (but the user may log in the system by other means).
Re:Sshole by zbentley on Thursday November 05, 2026 @03:05PM (#50383757)
No, it's a C thing. They previously returned NULL for invalid passwd lookups, but because there's no way in C to force API users to decide what to do if an error occurred, that was causing program crashes due to NULL dereferences.

So they made the (very bad, but motivated by C's broken design/conventions) decision to return a magic string instead.

Re:Sshole by za_creature on Thursday November 05, 2026 @03:23PM (#50383989)
Oh, it was magic alright.
Re:Sshole by patmorgan23 on Thursday November 05, 2026 @03:56PM (#50384361)
This sounds like a program problem not a library problem. Why did the library try to solve it?
Re:Sshole by nuxi on Thursday November 05, 2026 @04:29PM (#50384706)
Now imagine every other C API that can potentially return NULL did the same thing. Is that in any way sensible?

You can argue that C has broken design/conventions, but the musl decision was really really awkward.

Re:Sshole by ruslan on Thursday November 05, 2026 @05:11PM (#50385203)
> but because there's no way in C to force API users to decide what to do if an error occurrs,

0. There are tons of libc functions that return NULL on failure, it is conventional way in C to indicate failure.

1. There's no much to decide, just fail to login if crypt() fails with NULL.

2. errno is standard way in C to check error code of a failing function, but in case of login you hardly need this, just fail!

I totally agree that fixing this issue in musl was a pretty dubious solution. Don't fix what's not broken, fix broken application instead. Publish a CVE if you cannot fix it.

I looked through FreeBSD source code for how they use crypt(), it's checked for NULL everywhere!

Re:Sshole by __david__ on Friday November 06, 2026 @04:15AM (#50390011)
Nothing you said is incorrect, but I believe they meant that there’s no way to force a C program to check the NULL correctly—the compiler will happily compile code that gets a pointer back from a function but doesn’t check for NULL before dereferencing it. In something like Rust the type system won’t let you touch the value until you’ve done something to deal with the error. Even if you just want to ignore it, you are forced to acknowledge that the function is fallible by the type system—it’s a compile error if you don’t.
Re:Sshole by gpvos on Thursday November 05, 2026 @05:44PM (#50385577)
Returning NULL on error is actually an excellent choice because most programs will crash if they try to use it without checking. Of course, modern programming languages have better constructs, but this was rare to nonexistent when C was started and if you think C is going away anytime soon you're delirious. musl's decision to change that to "*" is ununderstandable.
Re:Sshole by zbentley on Thursday November 05, 2026 @07:59PM (#50386831)
> musl's decision to change the return value to "*" is ununderstandable and likely intentionally evil.

Dude, they documented their rationale. It was dumb and wrong but totally relatable. Irresponsible users were getting crashes, and I’m sorry to tell you that for a lot of library maintainers

> Returning NULL on error is actually an excellent choice because most programs will crash if they try to use it without checking.

…is not something they want to write in responses to bug reports, even though it is quite true. It’s human nature to want to prevent your stuff causing problems for others, even if the problems are fully their fault. Most people cannot and will not be Steve “so don’t hold it like that” Jobs, for fear that their decision will be just as short-sighted as his was.

In this case, that was obviously the dead wrong call. But I’ve been in that seat, and “closed: user error” for the 100th time feels bad to write. Add to that a touch of misplaced cleverness, and you get “hey…what if we just returned an impossible string instead of an error?”

Evil requires motive or malice, and I’m having trouble coming up with those in this case. These patches weren’t concealed or miscategorized a la xz, the behavior was discussed in the open. They were just, in hindsight, really poor decisions made for ordinary reasons. Hanlon’s Razor and all that.

Re:Sshole by za_creature on Friday November 06, 2026 @04:41AM (#50390250)
> “closed: user error” for the 100th time feels bad to write.

That's the job of a OSS library author.

Re:Sshole by __david__ on Friday November 06, 2026 @04:52AM (#50390331)
> That’s why I’ve been harping on C being trash for shared-code API…

Agreed, but I can’t think of any other widespread ABIs that are better. Rust’s is not stable both C++ and Rust have concepts that don’t map into other languages well, so using them wholesale is a no go. So it would probably need to be a subset of those. Honestly just tagged unions would probably be enough. Maybe the solution is to get native tagged unions into C…

Re:Sshole by gpvos on Saturday November 07, 2026 @03:13AM (#50402661)
Fine, I can follow your reasoning.

There is the concept of "defense in depth": having each layer of code and configuration be sane and secure. Even if you program in Rust, you should be vigilant in that sense. This kind of code amounts to removing one or two layers.

Re:Sshole by cowlevel on Thursday November 05, 2026 @10:04PM (#50387643)
C programs wouldn't accidentally compare NULL to NULL. They'd either check for null explicitly, or they'd pass the null to strcmp and crash.
Re:Sshole by bch on Thursday November 05, 2026 @01:06PM (#50382251)
> musl switched to returning `*` so folks stopped accidentally using NULL results, which was causing crashes and risked false-positives comparing NULL to itself.

Of all the things to mitigate...

1) crashing should be an option - it noisily indicates "there's a problem" and if it faults and dumps core, you've got a guided tour of your problem.

2) Is testing NULL as a passable option likely? I'd guess a more likely idiom is:

  if(NULL==(str=somefunc(arg1, arg2))){
    return err;
  }
Re:Sshole by PokestarFan on Thursday November 05, 2026 @01:17PM (#50382395)
I think the issue is that a lot of callers were not aware that crypt() could error at all and all the issues are downstream of that. If OpenSSH knew that crypt() could error they would have handled it (possibly by inspecting errno).
Re:Sshole by creata on Thursday November 05, 2026 @01:26PM (#50382560)
I don't think that's the problem. The man page for crypt clearly says that erroring is a possibility and that it's not standardized how it errors:

> The behavior of crypt on errors isn't well standardized. Some implementations simply can't fail (except by crashing the program), others return a null pointer or a fixed string. Most implementations don't set errno, but some do. POSIX specifies returning a null pointer and setting errno, but it defines only one possible error, ENOSYS, in the case where crypt is not supported at all.

In my opinion the problem is that the behavior of crypt is underspecified - it isn't even specified how it signals failure, which is shocking.

You can't check errno to see if there was a failure because, as the man page says, the value of errno is unspecified when crypt succeeds.

Re:Sshole by throwaway2037 on Friday November 06, 2026 @12:51AM (#50388678)

    > You can't check errno to see if there was a failure because, as the man page says, the value of errno is unspecified when crypt succeeds.
You can set errno to zero before the call. If the value is non-zero after the call, that is a good heuristic for failure.
Re:Sshole by zbentley on Friday November 06, 2026 @02:44AM (#50389350)
...unless crypt() is one of the many, many functions which themselves internally call functions that set errno. In which case you can't be sure.
Re:Sshole by xp84 on Thursday November 05, 2026 @06:58PM (#50386324)
> crashing should be an option

Wise words. I think early in my career I tended towards coding as defensively as possible in a very specific way of "don't crash", as I definitely was not working in codebases I felt I had a full picture of. "I don't know who calls this method, and I don't want to get a call in the middle of the night that I broke prod" so I would say, return an empty string when asked to format a datetime except I'm passed a string or an int. (Yes, I was working in dynamic, untyped languages, too).

Later on, even in untyped languages, I came around more to "Throw loud and specific exceptions when asked to do things that are nonsensical" - it requires more overall cojones though because it may cause "your code" to crash. But the realization I came to was that it's better to crash (a MaliciouslyLongPasswordError was uncaught) because "AuthManager asked PasswordService to check a password that was 2MB long" than to paper that over until something like this comes out. Or, the more common situation, you're passed garbage, you return something that won't choke the caller at the time, but what it saves to the database in that scenario ends up being corrupt data. 6 months later someone notices, and has to sort out a data mess that may or may not be recoverable.

Re:Sshole by zbentley on Thursday November 05, 2026 @07:50PM (#50386752)
“A good offense (SIGABRT) is the best defense(ive code).”

I dig it.

Re:Sshole by oasisaimlessly on Friday November 06, 2026 @06:16AM (#50390946)
FYI, your learned approach is also known as "fail fast" methodology[1][2].

[1]: https://www.martinfowler.com/ieeeSoftware/failFast.pdf

[2]: https://en.wikipedia.org/wiki/Fail-fast_system

Re:Sshole by ghostly_s on Thursday November 05, 2026 @03:48PM (#50384289)
But choosing "*" as the character to return is an insane choice for anyone who knows the basics of linux sysadmin, or hell anyone who's ever used a search field in any context and thus knows it is ubiquitously used as a wildcard operator.
Re:Sshole by cloudfudge on Thursday November 05, 2026 @05:39PM (#50385520)
It's almost sensible if you only think of one side. It's like if /etc/passwd put "NO_PASSWORD" in the field for disabled logins and someone was like, oh, I'll be clever and say crypt() returns "NO_PASSWORD" on error because that's obviously invalid and won't match. But that's exactly what *does* make it match.
Re:Sshole by bryan_w on Thursday November 05, 2026 @11:39PM (#50388286)
But this also needs the "ddos" patch that was submitted later as well.

That seems orchestrated.

Re:Sshole by bryan_w on Thursday November 05, 2026 @11:43PM (#50388314)
Re:Sshole by pdw on Thursday November 05, 2026 @07:11PM (#50386439)
But it fits the common Linux implementation of the function... e.g. the Debian manpage says:

> Upon error, crypt_r, crypt_rn, and crypt_ra write an invalid hash to the _output_ field of their _data_ argument, and crypt writes an invalid hash to its static storage area. This string will be shorter than 13 characters, will begin with a ‘*’, and will not compare equal to _setting_.

Re:Sshole by crunchatized on Saturday November 07, 2026 @04:56PM (#50408412)
That's in Debian 11 and later. That man page said something entirely different in Debian 10 and earlier, when they shipped glibc's libcrypt. Now they ship libxcrypt instead.

Fedora asked libxcrypt to give them back glibc's behavior, returning NULL, and they obliged by adding ./configure --disable-failure-tokens. But it's an option that Debian never enabled.

https://github.com/besser82/libxcrypt/issues/25

https://salsa.debian.org/md/libxcrypt/-/blob/master/debian/r...

Re:Sshole by dboreham on Thursday November 05, 2026 @01:22PM (#50382486)
Of all the possible strings this seems like the absolute worst choice. If I were typing that string I'd pause to think "wait, what if the caller uses this with a regex?"
Re:Sshole by mmoll on Thursday November 05, 2026 @01:56PM (#50382916)
I agree. The “excuse” for implementing those behaviors is just providing plausible deniability. The commit that lets crypt return “*”, followed closely by a change that lets you easily trigger that behavior sounds just like how somebody would sneakily introduce a back-door into tons of linux systems while making it look as innocent as possible.
Re:Sshole by nextaccountic on Friday November 06, 2026 @07:45AM (#50391693)
Yep. See: underhanded c code contest

https://www.underhanded-c.org/

Re:Sshole by odo1242 on Thursday November 05, 2026 @12:34PM (#50381895)
Returning a string on error honestly is defensible, the question is why the string was “*” instead of like “crypt ERROR: input password cannot be longer than 256 characters” lol
Re:Sshole by dboreham on Thursday November 05, 2026 @01:21PM (#50382464)
Precisely.
Re:Sshole by mmoll on Thursday November 05, 2026 @01:49PM (#50382834)
No. Musl claims (claimed?) to be strictly POSIX compliant, and POSIX requires crypt to return NULL on errors.
Re:Sshole by tredre3 on Thursday November 05, 2026 @02:06PM (#50383029)
"*" is what's been historically used to represent an invalid/no value in passwd/shadow, so it makes some sense.

Of course given the chance that one might do a direct string comparison between crypt's return and the passwd/shadow file's hash field it was still a not well thought out choice.

Re:Sshole by gf000 on Thursday November 05, 2026 @02:21PM (#50383222)
No, it's absolutely not defensible.
Re:Sshole by JdeBP on Thursday November 05, 2026 @03:17PM (#50383920)
One has to remember the context here. The only error possible according to the SUS is ENOSYS, literally that crypt() is not actually implemented in the library. The traditional AT&T Unix crypt() was always implemented, and its encryption did not have failure modes, so there was never any error return to handle. This is why programs were written to never check the result of crypt(). It was only ever NULL on systems where one couldn't use the mechanism at all in the first place.

So the knock-on effect is that libraries that do have errors other than ENOSYS to accommodate, have to return magic 'impossible' values instead of setting errno and returning NULL, because of the weight of existing applications that expect nothing but the standard, traditional AT&T Unix (and indeed old BSD), behaviour.

Check the GNU libc crypt() doco. It defines a whole family of 'impossible' values that signal errors, by dint of listing a load of magic characters that should never be in the encrypted string. Including asterisk.

Also check FreeBSD's OpenPAM for a case in point. Even today, it does not check for a NULL return from crypt() and leaps straight to strcmp().

* https://cgit.freebsd.org/src/tree/lib/libpam/modules/pam_uni...

If you want to see real history here, check out the 1992 comment by Eric Andrew Young, that is still in the source for OpenBSD today (in des/des_fcrypt.c), on how SSLeay's crypt() had to be adjusted so that any single character password field, including "*", did not end up successfully matching itself.

* https://cvsweb.openbsd.org/annotate/src/lib/libcrypto/des/At...

Re:Sshole by za_creature on Thursday November 05, 2026 @04:15PM (#50384545)
The most charitable interpretation here is that musl authors are incompetent.

There is simply no universe in which a security-conscientious reviewer looks at the code, spots * being a valid return value, remembers that * is the default value in /etc/shadow for "no password login please" and then is all like:

This is fine, you check for * and return + instead.

There are more 32-long character sequences than atoms in the universe. Pick one, any of them instead.

Re:Sshole by ruslan on Thursday November 05, 2026 @05:22PM (#50385337)
> Also check FreeBSD's OpenPAM for a case in point. Even today, it does not check for a NULL return from crypt() and leaps straight to strcmp().

Damn, that's a bug! Please file bug report.

Re:Sshole by JdeBP on Thursday November 05, 2026 @07:34PM (#50386623)
I suggest finding a code path where FreeBSD crypt() ever returns NULL, which would mean one of its internal functions returning non-zero, before getting too excited. They all end in return (0) . (-:

* https://cgit.freebsd.org/src/tree/lib/libcrypt

As I said, the callers of crypt() have for a long time been using the knowledge that crypt() on their platform simply never returns NULL. Unix has never been returning NULL from crypt() as far back as 7th Edition, when it simply always returned a pointer to a single internal static buffer.

* https://github.com/dspinellis/unix-history-repo/blob/Researc...

The reality is that all C library people have to work with a corpus of Unix applications that do not check for crypt() returning NULL that goes back almost 50 years, and an error return mechanism that barely exists outwith the pages of the SUS.

That includes callers in single-user mode in process 1 in some configurations.

* https://cvsweb.netbsd.org/bsdweb.cgi/src/sbin/init/init.c?an...

Re:Sshole by ruslan on Thursday November 05, 2026 @08:50PM (#50387145)
Well, crypt_blowfish() has a number of returns with (-1). I'm not familiar with Blowfish, so it's hard for me to trace the possibility of failure here. But what if in a distant future someone adds more hashing algorithms to the list without possessing historical knowledge of this "never return not 0" ?

* https://cgit.freebsd.org/src/tree/secure/lib/libcrypt/crypt-...

Re:Sshole by JdeBP on Friday November 06, 2026 @05:05AM (#50390415)
I have no doubt, despite that Marshall Kirk McKusick is still committing in 2026, that FreeBSD developers will come along that do not know history. But they will get pointed to the manual page that (amusingly) says that returning NULL is 'not a standard behaviour' and that AT&T Unix never did that.

* https://man.freebsd.org/cgi/man.cgi?query=crypt&sektion=3&fo...

NetBSD's manual is more detailed on the subject, and explains the bind that library implementors have been in for decades, thanks to almost 50 years of applications knowing what crypt() 'really' does.

* https://man.netbsd.org/crypt.3#RETURN%20VALUES

And this despite the SUS trying to shoehorn an error return mechanism into crypt(3) in order to accommodate the problems that operating systems had exporting encryption code, two decades afterwards. (The 1997 GoSolo book explains that this is why there's an ENOSYS error.)

Re:Sshole by boomlinde on Friday November 06, 2026 @01:32AM (#50388912)
> Check the GNU libc crypt() doco. It defines a whole family of 'impossible' values that signal errors, by dint of listing a load of magic characters that should never be in the encrypted string. Including asterisk.

No. https://man7.org/linux/man-pages/man3/crypt.3.html

My guess is that you are reading the libxcrypt crypt(3) manual: https://www.mankier.com/3/crypt

That's not GNU libc, but frequently deployed on Linux systems.

Re:Sshole by JdeBP on Friday November 06, 2026 @04:33AM (#50390178)
Well caught. I was reading what man -s 3 crypt brought up, which mentions GNU and -lcrypt and only right at the very bottom says something else.

* https://manpages.debian.org/trixie/libcrypt-dev/crypt.3.en.h...

Although that's a fine distinction for the point being made, as it's still the manual page for the crypt() function that one gets on the operating system, which that other manual page is not.

Re:Sshole by boomlinde on Friday November 06, 2026 @07:30AM (#50391575)
That naturally depends on the operating system and the crypt implementation they include.
Re:Sshole by JdeBP on Friday November 06, 2026 @02:16PM (#50396979)
Not really. For Linux-based operatings systems, the GNU libc manual cryptography section is a shadow of its former self. It has no crypt() function to accompany it any more, and has not had since 2023.

* https://sourceware.org/git/?p=glibc.git;a=commit;h=e6e3c6668...

Notice that Debian didn't list your manual page, which doesn't accompany a crypt() function at all, since it isn't actually the GNU libc manual but a separate independent manual for Linux. The GNU libc manual nowadays acknowledges Michael Glad's work but no longer documents crypt(). It used to, and it used to explain some of the 'impossible' encrypted string values:

* https://sourceware.org/glibc/manual/2.38/html_node/Passphras...

You noticed that I'd not got the GNU libc manual. Neither had you. (-: