News for hackers. Stuff that matters.
Re:Sshole by knome on Thursday November 05, 2026 @10:11AM (#50380076)
>but the fact that crypt() doesn't have any way to signal failure

https://pubs.opengroup.org/onlinepubs/009604599/functions/cr...

>Upon successful completion, crypt() shall return a pointer to the encoded string. The first two characters of the returned value shall be those of the salt argument. Otherwise, it shall return a null pointer and set errno to indicate the error.

returning a sentinel value of NULL and setting errno is pretty standard C, and shouldn't cause an error.

OP's sshhole link specifically notes that it _should_ return NULL, but some MUSL devs decided they wanted it to return trash instead, and the trash they returned instead of NULL caused this.

All because some devs were too lazy to check the result and another dev decided to be clever and "fix" NULL errors from crypt by sending back 'unmatchable' trash banking on devs not checking NULL.

Re:Sshole by mafuy on Thursday November 05, 2026 @11:24AM (#50381084)
If this is true, then the entire thread branch here on HN is entirely wrong. What is going on??
Re:Sshole by hedora on Thursday November 05, 2026 @11:32AM (#50381165)
People don't RTFM, either on HN or when implementing "standards compliant" stuff like MUSL.

No error handling scheme can fix that problem. For instance, if a Rust API says I should return a Result<String, ENOSYS>, I can always write code like `if(ENOSYS) { return Ok("*") } instead of returning Err(ENOSYS).

Then my code would be compatible with MUSL.

Re:Sshole by dns_snek on Thursday November 05, 2026 @12:03PM (#50381534)
[delayed]
Re:Sshole by hedora on Thursday November 05, 2026 @01:16PM (#50382385)
They had to write

   if(ENOSYS) {
     return "*";
   }
after seeing that other implementations use different sentries like "*0" and that the spec says they MUST return NULL. That must have felt bad too.
Re:Sshole by dns_snek on Friday November 06, 2026 @04:12AM (#50389979)
[delayed]
Re:Sshole by zbentley on Thursday November 05, 2026 @12:42PM (#50381973)
Sure, but if you didn't do that, and returned the tagged union (in Rust or whatever language), your users would necessarily be forced to handle it in order to process the result.

The only reason this library did the insane thing of returning "*" is because it was extremely easy to cause crashes or false-positives (NULL == NULL) by returning NULL+errno. The reason that was easy is because C lacks a means of making error handling mandatory (exceptions, tuple returns, tagged unions, whatever).

If you return a tagged union, users might mis-handle it, it's true (https://blog.cloudflare.com/18-november-2025-outage/). But they have to handle it, which is a requirement that C APIs are incapable of providing.

Re:Sshole by hedora on Thursday November 05, 2026 @01:14PM (#50382353)
You have to handle NULL in C, or your program crashes (though I suppose a misguided optimization of strcmp could do "a == b || really_strcmp(a, b)", which gets us back to your bug).

Also, in things like rust you can just do result.unwrap() if you want. In general, language constructs don't really save you from bad programmers.

Re:Sshole by zbentley on Thursday November 05, 2026 @01:27PM (#50382566)
> a misguided optimization of strcmp could do "a == b || really_strcmp(a, b)",

In practice, that's less of a risk than:

    char* x = function_that_might_return_null_and_error();
    dangerous_function();
    if (x == NULL) {...}
Or code for which pointer identity is relevant (e.g. accessing slices/subparts of a data structure):

    char* x = returns_null_or_a_slice_pointer(data_structure);
    char* y = other_function_that_returns_null_or_a_slice_pointer(data_structure);
    if (x == y) { puts("you're fucked now!"); }
Re:Sshole by hedora on Friday November 06, 2026 @11:35AM (#50394640)
You can always write:

   x : Result<string, err> = function...
   dangerous_function();
   switch(x) { 
     Ok() ... 
     Error() ...
   }
There's no difference between result and null other than readability (there's a mechanical transformation between the two, where you use unwrap() in rust on an unchecked null dereference, and you read errno in C instead of looking inside the Err() result variant).

To be clear, I strongly prefer Result<> because of the readability, but any dumb thing you can write with NULL + errno can be written with Result (and vice versa).

Re:Sshole by zbentley on Friday November 06, 2026 @03:42PM (#50398217)
Totally! I threw that first example in because it's the poster child for things (checked) exceptions solve, and I didn't want to unfairly favor result unions (which I also prefer).
Re:Sshole by jstanley on Friday November 06, 2026 @03:29AM (#50389643)
> You have to handle NULL in C, or your program crashes

But it crashes at runtime, so such a bug can exist for years with nobody noticing.

The existence of such bugs is what motivated the musl developers to return "*" instead of NULL.

Re:Sshole by slashdave on Thursday November 05, 2026 @01:47PM (#50382813)
A lazy attitude like "if we just used <n> language, all our problems would be fixed".
Re:Sshole by fc417fc802 on Thursday November 05, 2026 @11:46PM (#50388334)
It's subtly wrong, just like most of the rest of the commentary in this comment section. Yes, crypt is specified to return NULL on failure but if you read the rest of that page it is only permitted to fail with ENOSYS. The spec does not allow for failure for any other reason.

So if you're strictly adhering to the spec in this case then unfortunately NULL is not an acceptable way to signal a generic error condition. Hence all the seemingly bizarre special values. (In a strange turn of events all those experts doing things that looked weird turned out to know what they were doing. Go figure.)

Re:Sshole by FLeXMurphy on Thursday November 05, 2026 @11:54AM (#50381432)
Except it's not trash. It's a backdoor funded by our glorious leader Kim Jong Un.
Re:Sshole by dns_snek on Thursday November 05, 2026 @11:57AM (#50381463)
[delayed]
Re:Sshole by zbentley on Thursday November 05, 2026 @12:36PM (#50381910)
This is true, and "trash" is putting it mildly.

But all those sentinels were added for the reason that, when your code is a public API intended to be widely used by who-knows-who, it needs to be really difficult to misuse it. NULL-plus-errno is easy to misuse. Yes, it's a dangerous mistake and your own fault if you do it. But people have been making that mistake for years and don't show any signs of stopping.

The root cause here is that C is fundamentally a rotten choice for public APIs of any kind. I griped more here: https://news.ycombinator.com/item?id=50381800

Re:Sshole by eqvinox on Thursday November 05, 2026 @02:27PM (#50383287)
Yup, that's the way to explain it in order to blame musl.

Here's how to explain it in order to blame openssh:

openssh processes passwords and doesn't want to reveal through timing whether a user has a valid password (or exists at all). So far so good.

What they do if they hit a user with no valid password (i.e. "*") is that they look for another user with a valid password, to use the seed from. A bit weird already, but OK from a perspective of avoiding implementation specifics on seeds and hash algorithms. They know at this point that the result must be to deny access even if the hashed value matches, because that has is for another user.

So they put the password received over ssh into crypt() with this other user's hashed value to get a seed. And then accept it if it matches, apparently?

Homework: what happens if the password actually matches another user? Can I log in as root if openssh randomly picks my user for the fake salt, and I type my own password correctly?

Re:Sshole by n_plus_1_acc on Thursday November 05, 2026 @05:28PM (#50385399)
The spec implies a string of at least two characters in the success case, am I reading that right?