? Curious Hacker Newsfront | about | examples | prompts | view on HN unofficial mirror
sshole (sshhole.com)
2473 points by josemrb 2026-11-05 | 569 comments | side by side | 27 rewritten | show 40 removed | updated 05:14 UTC
 help


mpyne 2026-11-05 14:1710/10insightfulinformative | next [–]

OK, I think I see what’s going on with this, in this configuration.

The goal from an attacker’s perspective is to get this to be true: `$ret = crypt($pw, $hash); $ret == $hash` when called by OpenSSH’s sshd. This is how sshd validates that the user logging in knew the account password.

The `$hash` is the “settings” to the crypt call and will normally look like “$6$--gibberish” for users with passwords set, but could look like “*” or “!” for users not intended to be able to login.

The bug is from the intersection of all of these things:

1. musl can return “” for errors, rather than leave the return value NULL. Other libc have adopted something like this too, but seem to use a different result “0”.

2. musl can also return “+” for errors, but only does so if $hash starts with “*”. This is intended to eliminate the possibility of an error causing `$ret == $hash` to be true, however it assumes that applications calling `crypt()` will set $pw and $hash to values corresponding to the `$ret == $hash` comparison to be made immediately afterwards.

3. OpenSSH can be convinced to pass the hash of a different user if the hash is “*” or “!” to avoid timing attacks that can enumerate the user list, so it will set $pw and $hash to correspond to different users. $hash will be a real “$6$--gibberish” style hash in this case, and OpenSSH expects `crypt()` to either return NULL or return a hash string. But now there’s two hashes OpenSSH uses. It does: `$ret = crypt($pw, $hash2); $ret == $hash1`;

4. This should normally not be a problem because OpenSSH will pass in valid $pw and $hash2 that should not lead to errors in hashing, so the $ret that musl generates should be a real hash that cannot match the “*” or “!” $hash1 for the login-disabled user. However as a DDoS mitigation, musl will return error if the password is too long when using SHA-2-based hashes (the default, it seems).

5. This error will result in a non-NULL result of “” or “+”. Since OpenSSH passed in a real hash ($hash2), musl will return “”, which compares equal to the actual user hash $hash1 for the disabled-login user.

The OpenSSL adjustment ensures a minimum hash length for the user before logging them in, even if crypt() succeeds somehow.

OK, I think I see what’s going on with this, in this configuration.

The goal from an attacker’s perspective is to get this to be true: `$ret = crypt($pw, $hash); $ret == $hash` when called by OpenSSH’s sshd. This is how sshd validates that the user logging in knew the account password.

The `$hash` is the “settings” to the crypt call and will normally look like “$6$--gibberish” for users with passwords set, but could look like “*” or “!” for users not intended to be able to login.

The bug is from the intersection of all of these things:

1. musl can return “” for errors, rather than leave the return value NULL. Other libc have adopted something like this too, but seem to use a different result “0”.

2. musl can also return “+” for errors, but only does so if $hash starts with “*”. This is intended to eliminate the possibility of an error causing `$ret == $hash` to be true, however it assumes that applications calling `crypt()` will set $pw and $hash to values corresponding to the `$ret == $hash` comparison to be made immediately afterwards.

3. OpenSSH can be convinced to pass the hash of a different user if the hash is “*” or “!” to avoid timing attacks that can enumerate the user list, so it will set $pw and $hash to correspond to different users. $hash will be a real “$6$--gibberish” style hash in this case, and OpenSSH expects `crypt()` to either return NULL or return a hash string. But now there’s two hashes OpenSSH uses. It does: `$ret = crypt($pw, $hash2); $ret == $hash1`;

4. This should normally not be a problem because OpenSSH will pass in valid $pw and $hash2 that should not lead to errors in hashing, so the $ret that musl generates should be a real hash that cannot match the “*” or “!” $hash1 for the login-disabled user. However as a DDoS mitigation, musl will return error if the password is too long when using SHA-2-based hashes (the default, it seems).

5. This error will result in a non-NULL result of “” or “+”. Since OpenSSH passed in a real hash ($hash2), musl will return “”, which compares equal to the actual user hash $hash1 for the disabled-login user.

The OpenSSL adjustment ensures a minimum hash length for the user before logging them in, even if crypt() succeeds somehow.

Detailed walkthrough of how musl's crypt() behavior and OpenSSH's logic combine into the bug.

I think the conclusion that can be drawn from this is that the behavior of crypt() is howling-at-the-moon insane, and it should no longer be considered for any system built in this century, at least not without extensive wrappers that correct and normalize all these edge cases.

I think the conclusion that can be drawn from this is that the behavior of crypt() is howling-at-the-moon insane, and it should no longer be considered for any system built in this century, at least not without extensive wrappers that correct and normalize all these edge cases.

Strongly worded opinion aimed at an API, not a person, with a real conclusion about crypt().

Conclusion is type safety is important

Conclusion is type safety is important

Terse but a real, on-topic disagreement about the root cause; nothing a moderator would ask to drop.
GuB-42 2026-11-05 20:377/10informativeinsightful | root | parent | next [–]

In this particular case, it is type safe. NULL, by the C standard does not refer to an object. It means that every pointer in C is effectively an option type, which mean that a function that returns a pointer can return NULL for "no result", which, in the case of crypt() is perfectly adequate.

It is the opposite that is unsafe, that is, when the returned value can't be NULL, but it stills returns a pointer. C++ has references in an attempt to fix this problem.

Because C tends to prioritize performance over safety, a NULL pointer dereference is considered undefined behavior and the compiler can invoke nasal demons if it makes the code go faster, but with the right options, you can have it checked and invoke what in other languages would call a panic handler, but on UNIX is the SIGSEGV signal. You can then attach a debugger or read the core file and see what went wrong.

Note: I am not talking about "char *" for strings, that one is unsafe, but NULL is not. It is just a special case of an option type, or a result type, or an error union. Modern languages just extended this idea to objects other than pointers, and with more than one type of error.

In this particular case, it is type safe. NULL, by the C standard does not refer to an object. It means that every pointer in C is effectively an option type, which mean that a function that returns a pointer can return NULL for "no result", which, in the case of crypt() is perfectly adequate.

It is the opposite that is unsafe, that is, when the returned value can't be NULL, but it stills returns a pointer. C++ has references in an attempt to fix this problem.

Because C tends to prioritize performance over safety, a NULL pointer dereference is considered undefined behavior and the compiler can invoke nasal demons if it makes the code go faster, but with the right options, you can have it checked and invoke what in other languages would call a panic handler, but on UNIX is the SIGSEGV signal. You can then attach a debugger or read the core file and see what went wrong.

Note: I am not talking about "char *" for strings, that one is unsafe, but NULL is not. It is just a special case of an option type, or a result type, or an error union. Modern languages just extended this idea to objects other than pointers, and with more than one type of error.

Substantive, civil disagreement explaining NULL as an option type in C and how null dereferences behave.

> It is just a special case of an option type, or a result type, or an error union.

These are not the same, and your next sentence suggests why. Option has but a single error type, but result types and error unions allow arbitrary types of error.

> It is just a special case of an option type, or a result type, or an error union.

These are not the same, and your next sentence suggests why. Option has but a single error type, but result types and error unions allow arbitrary types of error.

Plain, civil technical disagreement that adds a real distinction between option and result types.

> the compiler can invoke nasal demons if it makes the code go faster

I had never heard that phrase, and was delighted at the answer when I googled it its origin, thank you:

http://www.catb.org/jargon/html/N/nasal-demons.html

> “When the compiler encounters [a given undefined construct] it is legal for it to make demons fly out of your nose” (the implication is that the compiler may choose any arbitrarily bizarre way to interpret the code without violating the ANSI C standard)

> the compiler can invoke nasal demons if it makes the code go faster

I had never heard that phrase, and was delighted at the answer when I googled it its origin, thank you:

http://www.catb.org/jargon/html/N/nasal-demons.html

> “When the compiler encounters [a given undefined construct] it is legal for it to make demons fly out of your nose” (the implication is that the compiler may choose any arbitrarily bizarre way to interpret the code without violating the ANSI C standard)

Friendly reply sharing the origin of a term with a useful link and quote.

However this is limited by the compiler's actual abilities and code. The C standard doesn't forbid nasal demons, but the compiler isn't programmed to invoke nasal demons and isn't running on hardware with that capability, so it won't happen.

I've heard nasal demons invoked as a literal possibility. No they are not a literal possibility.

However this is limited by the compiler's actual abilities and code. The C standard doesn't forbid nasal demons, but the compiler isn't programmed to invoke nasal demons and isn't running on hardware with that capability, so it won't happen.

I've heard nasal demons invoked as a literal possibility. No they are not a literal possibility.

A plain, good-faith point that undefined behavior is still bounded by what the compiler and hardware can actually do.

You might consider it type safe according to C's rules, but it is not type safe according to contemporary opinion because the success type and error type are the same.

You might consider it type safe according to C's rules, but it is not type safe according to contemporary opinion because the success type and error type are the same.

Civil, substantive disagreement distinguishing C's rules from modern views of type safety.
eru 2026-11-06 02:377/10insightfulinformative | root | parent | next [–]

> [...] because the success type and error type are the same.

That's a bit weird? In Rust, if you have an thing that returns Result<String, String> that's still type safe, even though success and error types are the same.

I think I understand what you want to express, maybe. You might say it's in-band vs out-of-band error signalling?

> [...] because the success type and error type are the same.

That's a bit weird? In Rust, if you have an thing that returns Result<String, String> that's still type safe, even though success and error types are the same.

I think I understand what you want to express, maybe. You might say it's in-band vs out-of-band error signalling?

Civil, substantive disagreement that clarifies the distinction between in-band and out-of-band error signalling.

> In Rust, if you have an thing that returns Result<String, String> that's still type safe, even though success and error types are the same.

These types are not the same:

    Ok<String>
    Err<String>

> In Rust, if you have an thing that returns Result<String, String> that's still type safe, even though success and error types are the same.

These types are not the same:

    Ok<String>
    Err<String>
Terse but substantive point that the Result variants are distinct even when wrapping the same type.

Contemporary opinion among who? Words mean things, and "type safe" simply does not mean "agrees with my preferred style of error reporting"

Contemporary opinion among who? Words mean things, and "type safe" simply does not mean "agrees with my preferred style of error reporting"

Firm disagreement on what 'type safe' means, with a real point; bluntness is fine.

Papers on type soundness from the last 30 years usually explore the domain further than C semantics.

Papers on type soundness from the last 30 years usually explore the domain further than C semantics.

A brief, civil reply pointing to the academic literature on type soundness as an answer to the parent's question.
pron 2026-11-05 23:126/10informative | root | parent | next [–]

Soundness means that the program behaves in accordance with what its types express. You're right that C may be unsound due to undefined behaviour, but I don't think this is about soundness. What you're saying is that papers on types show how you can use types to express more constraints.

Soundness means that the program behaves in accordance with what its types express. You're right that C may be unsound due to undefined behaviour, but I don't think this is about soundness. What you're saying is that papers on types show how you can use types to express more constraints.

Civil, substantive disagreement that clarifies what type soundness means versus type expressiveness.

There can be other valid perspectives and definitions of that word than yours.

I think almost all (tech) arguments stem from simple disagreements and/or different assumptions made on the definition of a (subjective) term.

There can be other valid perspectives and definitions of that word than yours.

I think almost all (tech) arguments stem from simple disagreements and/or different assumptions made on the definition of a (subjective) term.

Civil disagreement with a general observation that tech arguments often come down to differing definitions; minor but real contribution.
pron 2026-11-06 02:557/10informativeinsightful | root | parent [–]

You may be right in general, but soundness comes from formal logic, where they're pretty fastidious with their definitions. Soundness certainly doesn't mean the expressivity of the types (i.e. what constraints they can represent); it's about the relationship between the runtime behaviour of a program and the types in the code.

You may be right in general, but soundness comes from formal logic, where they're pretty fastidious with their definitions. Soundness certainly doesn't mean the expressivity of the types (i.e. what constraints they can represent); it's about the relationship between the runtime behaviour of a program and the types in the code.

Civil, substantive clarification of what soundness means in formal logic and type theory.

Can you link some?

Can you link some?

A genuine short request for sources is kept as is.

That's not the same as asserting that soundness requires adherence to the current fashion, sum types over results and errors. TIMTOWTDI.

You don't have to think C is sound or good to distinguish a disfavored API style from mathematical unsoundness. There are infinitely many type safe APIs and languages that are, despite their soundness, ugly and hard to use.

Let's not project every dimension along which a language can be good or bad onto the axis of safety.

That's not the same as asserting that soundness requires adherence to the current fashion, sum types over results and errors. TIMTOWTDI.

You don't have to think C is sound or good to distinguish a disfavored API style from mathematical unsoundness. There are infinitely many type safe APIs and languages that are, despite their soundness, ugly and hard to use.

Let's not project every dimension along which a language can be good or bad onto the axis of safety.

Substantive, civil argument distinguishing API style preferences from mathematical type soundness.

Opinion among people who don't try to authoritatively explain subjective words with rigid dogma.

original comment by majorchord

Opinion among people who know better than to try to authoritatively explain subjective words with rigid dogma, because that just makes them look silly.

Removed the swipe about looking silly and kept the point that the term is subjective.

[deleted]

My thoughts exactly.

"Make invalid states unrepresentable" is such a useful adage when designing even basic systems, much less the gates to control your server.

My thoughts exactly.

"Make invalid states unrepresentable" is such a useful adage when designing even basic systems, much less the gates to control your server.

Civil agreement that adds a useful design principle relevant to the discussion.
citrin_ru 2026-11-05 17:406/10insightfulinformative | root | parent | prev | next [–]

My conclusion is that an old tradition of not returning NULL in crypt as a workaround for buggy code which doesn't handle errors was a bad idea.

My conclusion is that an old tradition of not returning NULL in crypt as a workaround for buggy code which doesn't handle errors was a bad idea.

Substantive, civil disagreement offering a specific alternative conclusion about crypt's error-handling tradition.

The idea of trying to provide a workaround for buggy code calling crypt() is terrifying. For sure, trying to grease the skids for buggy code calling printf(), go for it. But it is hard to imagine cases where slightly-incorrect usage of crypt() is a good idea.

The idea of trying to provide a workaround for buggy code calling crypt() is terrifying. For sure, trying to grease the skids for buggy code calling printf(), go for it. But it is hard to imagine cases where slightly-incorrect usage of crypt() is a good idea.

Substantive, firmly worded point that security-critical APIs like crypt() shouldn't accommodate buggy callers.

This is still a consequence of a poor type system.

This is still a consequence of a poor type system.

Terse but on-topic opinion that attributes the crypt NULL issue to the type system; nothing to moderate.
creata 2026-11-05 18:598/10insightfulinformative | root | parent [–]

I don't think so. Even in a language with a good type system like Rust, somebody could have made this API:

    /// Hash the phrase with the given setting.
    fn crypt(phrase: &[u8], setting: &[u8]) -> [u8; CRYPT_OUTPUT_SIZE];

Then, in a later version, `crypt` needs to become fallible, so they need to signal errors somehow. But because they really want to preserve backward compatibility, they can't change the output type to `Result<..., ...>`. And so they end up doing the same thing that was done in libcrypt - they output some error value and hackily make sure it's not equal to the `setting` string, under the mistaken assumption that the output of `crypt` would only ever be compared to the `setting` string. And then someone creates the exact same bug.

Maybe I'm wrong, but it looks to me like the exact same incentives and means to screw things up exist in C and in Rust for this particular bug.

I don't think so. Even in a language with a good type system like Rust, somebody could have made this API:

    /// Hash the phrase with the given setting.
    fn crypt(phrase: &[u8], setting: &[u8]) -> [u8; CRYPT_OUTPUT_SIZE];

Then, in a later version, `crypt` needs to become fallible, so they need to signal errors somehow. But because they really want to preserve backward compatibility, they can't change the output type to `Result<..., ...>`. And so they end up doing the same thing that was done in libcrypt - they output some error value and hackily make sure it's not equal to the `setting` string, under the mistaken assumption that the output of `crypt` would only ever be compared to the `setting` string. And then someone creates the exact same bug.

Maybe I'm wrong, but it looks to me like the exact same incentives and means to screw things up exist in C and in Rust for this particular bug.

Substantive, polite disagreement with a concrete example showing how API compatibility incentives could reproduce the bug even in Rust.

IMO type safety requires both language features and a mindset where you take efforts to keep things type safe. Your example has only the former.

For example, in a Rust web app, you can choose to use String for encoded URLs, unencoded URL fragments, escaped html, unescaped HTML, usernames, etc. Or you can choose to use newtypes and carefully design all APIs to only take and return the correct newtypes.

IMO type safety requires both language features and a mindset where you take efforts to keep things type safe. Your example has only the former.

For example, in a Rust web app, you can choose to use String for encoded URLs, unencoded URL fragments, escaped html, unescaped HTML, usernames, etc. Or you can choose to use newtypes and carefully design all APIs to only take and return the correct newtypes.

Substantive, civil point that type safety depends on design discipline as well as language features, with a concrete Rust example.

You are looking at it from the wrong direction.

The problem is not that you can write bad stuff in rust, the problem is that this particular usage pattern can't be sanely coded in C. This is a public API that should inform users and enforce certain properties. A Results type does exactly that, you would have to go way out of your way to force invalid stuff through that type alone.

You are looking at it from the wrong direction.

The problem is not that you can write bad stuff in rust, the problem is that this particular usage pattern can't be sanely coded in C. This is a public API that should inform users and enforce certain properties. A Results type does exactly that, you would have to go way out of your way to force invalid stuff through that type alone.

Firm disagreement with a real argument about what type systems enforce; no personal swipe.
cowlevel 2026-11-06 05:077/10insightfulinformative | root | parent | next [–]

This isn't good either. Crypt is a pure computation that can't fail for arbitrary reasons. The former implementation could fail if the salt was invalid, which is something you could guarantee. Now it also fails if the password is invalid. That's new behaviour and no matter which way you wrap it up it has the potential to be incompatible. They should have added the maximum length recommendation to the man page, and let DoS attacks continue until the calling software was updated, not the crypt library.

This isn't good either. Crypt is a pure computation that can't fail for arbitrary reasons. The former implementation could fail if the salt was invalid, which is something you could guarantee. Now it also fails if the password is invalid. That's new behaviour and no matter which way you wrap it up it has the potential to be incompatible. They should have added the maximum length recommendation to the man page, and let DoS attacks continue until the calling software was updated, not the crypt library.

Firm, substantive disagreement about API compatibility and where the length limit should have been enforced.

I don't think so. In both Rust and C, the solution is the same - make the function explicitly fallible, instead of trying to preserve backward compatibility. In C, the function would probably just look like this, returning zero on success and negative on failure:

    int crypt2(const char *phrase, const char *setting, char *out);

You can call such a signature "insane" if you want, and that's fine, but it would have averted the bug that is the topic of discussion.

I don't think so. In both Rust and C, the solution is the same - make the function explicitly fallible, instead of trying to preserve backward compatibility. In C, the function would probably just look like this, returning zero on success and negative on failure:

    int crypt2(const char *phrase, const char *setting, char *out);

You can call such a signature "insane" if you want, and that's fine, but it would have averted the bug that is the topic of discussion.

Firm technical disagreement with a concrete C signature showing how the bug could be avoided.

what you are suggesting is a form of type safety. There are still degrees of type safety even in C, and this bug resulted from deciding that backwards compatibility is more important than type safety, which I think we agree on.

what you are suggesting is a form of type safety. There are still degrees of type safety even in C, and this bug resulted from deciding that backwards compatibility is more important than type safety, which I think we agree on.

Civil, substantive reply framing the bug as a tradeoff of backward compatibility against type safety.

And C has effectively deprecated bad APIs over the years. Just not this one.

And C has effectively deprecated bad APIs over the years. Just not this one.

Short but substantive point that C has retired bad APIs before, just not this one.

It's a perfectly sane signature, especially coupled with an API surface that uses the return channel for errors generally.

It's a perfectly sane signature, especially coupled with an API surface that uses the return channel for errors generally.

Brief, civil, on-topic opinion about the API design that adds a point about consistent error channels.

[deleted]

They would have to make it crypt2.

They would have to make it crypt2.

A terse but on-topic point that the compatible fix is a new function rather than an in-band error value.
eru 2026-11-06 02:565/10informative | root | parent | prev | next [–]

Yes, even in Lean you can just refuse to prove any properties.

But if you have a stronger and more expressive type system, you are more inclined to use it, instead of looking for silly workaround like in C.

Yes, even in Lean you can just refuse to prove any properties.

But if you have a stronger and more expressive type system, you are more inclined to use it, instead of looking for silly workaround like in C.

Concedes the parent's point and adds a real argument that expressive type systems encourage better use.

You wouldn't be concerned about breaking backward compatibility with rust though, would you? A recompile would be needed anyway and the change would be detected and enforced. The rationale for the bad API here (two types in one variable) was that users were ignoring NULL - that's the part which wouldn't happen in rust.

You wouldn't be concerned about breaking backward compatibility with rust though, would you? A recompile would be needed anyway and the change would be detected and enforced. The rationale for the bad API here (two types in one variable) was that users were ignoring NULL - that's the part which wouldn't happen in rust.

Substantive, good-faith disagreement explaining why Rust's type enforcement would avoid the ignored-NULL rationale for this API.

Maybe he meant binary compatibility.

Maybe he meant binary compatibility.

Short, good-faith clarification suggesting binary rather than source compatibility was the concern.
zbentley 2026-11-05 17:267/10insightfulinformative | root | parent | prev | next [–]

Specifically, that the lack of tagged unions (or really, structured return types in general; you can pretend those are a tagged union if you believe hard enough) in C library interop conventions is a terrible foundation on which to build widely-used APIs.

And that just ends up indicting the C language itself. Can't send errors down a separate channel; folks might not inspect errno. Can't return NULL on error, folks might compare it blindly to another result. No tagged unions, so can't return one. Can't crash on error because there's no scoped error handling. Can't return a struct for checking .error/.result separately, because alignment and equality (and tearing and accidental clobbering and more) make that brittle as hell for widely-used APIs.

C is not a good language for library APIs. Like, really not. It has its uses/strengths, sure, but without even getting into the typical gripes people have about C, libraries--one of the things that it is used for the most--are one of the things for which it is the least well-suited.

"But it has a stable ABI!" So does a land mine.

Specifically, that the lack of tagged unions (or really, structured return types in general; you can pretend those are a tagged union if you believe hard enough) in C library interop conventions is a terrible foundation on which to build widely-used APIs.

And that just ends up indicting the C language itself. Can't send errors down a separate channel; folks might not inspect errno. Can't return NULL on error, folks might compare it blindly to another result. No tagged unions, so can't return one. Can't crash on error because there's no scoped error handling. Can't return a struct for checking .error/.result separately, because alignment and equality (and tearing and accidental clobbering and more) make that brittle as hell for widely-used APIs.

C is not a good language for library APIs. Like, really not. It has its uses/strengths, sure, but without even getting into the typical gripes people have about C, libraries--one of the things that it is used for the most--are one of the things for which it is the least well-suited.

"But it has a stable ABI!" So does a land mine.

Strongly opinionated but substantive critique of C's error-handling conventions for library APIs, with wry humor aimed at a language, not a person.

you can return a struct just fine... most API's don't though.

you can return a struct just fine... most API's don't though.

Terse but genuine disagreement with a real point about C APIs returning structs.

Historically it didn't work well between different compilers. Maybe it still doesn't.

Historically it didn't work well between different compilers. Maybe it still doesn't.

Short but real point about struct-return ABI compatibility between compilers.
eru 2026-11-06 02:366/10informative | root | parent | prev [–]

That and in-band signalling is error-prone.

Another example that C programmers love: using 0 to mean infinity. Eg when you have a timeout setting, and 10 means ten seconds, but 0 means infinity seconds instead of zero seconds.

That and in-band signalling is error-prone.

Another example that C programmers love: using 0 to mean infinity. Eg when you have a timeout setting, and 10 means ten seconds, but 0 means infinity seconds instead of zero seconds.

Substantive point about in-band signalling with a concrete example; the light jab at C programmers is wry, not a personal attack.
cowlevel 2026-11-06 05:107/10informativeinsightful | root | parent | next [–]

In general it's a problem of sentinel values. Some languages let you abstract this - I think in Rust and C++ you can define an "intrusive optional" which is like optional but uses an invalid value pattern to store the not-present state. In memory it's the same as a sentinel value but the compiler forces you to unwrap it like an optional.

In general it's a problem of sentinel values. Some languages let you abstract this - I think in Rust and C++ you can define an "intrusive optional" which is like optional but uses an invalid value pattern to store the not-present state. In memory it's the same as a sentinel value but the compiler forces you to unwrap it like an optional.

Substantive, good-faith generalization to sentinel values with a useful language-level technique.

That’s just a very bad API. You could do the same in Rust? There are nice C APIs that would use a nice struct that describes the timeout you want… eg GCD.

That’s just a very bad API. You could do the same in Rust? There are nice C APIs that would use a nice struct that describes the timeout you want… eg GCD.

Civil, substantive disagreement arguing the problem is API design rather than the language, with a concrete example (GCD).
l33tman 2026-11-05 22:035/10questioninsightful | root | parent | prev | next [–]

Is really the solution to a bug where a function had a wrapper that tried to solve its documented issue of returning *, to make yet another wrapper to try to solve the documented issue of returning true?

Is really the solution to a bug where a function had a wrapper that tried to solve its documented issue of returning *, to make yet another wrapper to try to solve the documented issue of returning true?

A genuine, civil challenge to the wrapper-on-wrapper approach, noting that the bug arose from an earlier wrapper around documented behavior.

But, but 'always "succeeding" but giving an unmatchable hash is reportedly a better behavior than failing on error.'!!

But, but 'always "succeeding" but giving an unmatchable hash is reportedly a better behavior than failing on error.'!!

Wry irony aimed at crypt()'s design rationale, not at a person, and it adds a pointed example of the behavior being criticized.
amluto 2026-11-05 22:478/10informativeinsightful | parent | prev | next [–]

Caveat:

> $ret = crypt($pw, $hash); $ret == $hash

musl is actually safe against this use. OpenSSL takes a bizarre code path, detects that $hash is too short, calls crypt($pw, $other_string), forgets that it detected that the hash was too short, and checks $hash == return value.

See my other comment.

Caveat:

> $ret = crypt($pw, $hash); $ret == $hash

musl is actually safe against this use. OpenSSL takes a bizarre code path, detects that $hash is too short, calls crypt($pw, $other_string), forgets that it detected that the hash was too short, and checks $hash == return value.

See my other comment.

Civil technical correction that pinpoints the actual flawed code path in the password check.

I did note that musl would normally work just fine here. Though I appreciate that your other comment goes into much better detail about what was actually going on in the code, as my analysis basically stopped once I understood the high-level flow of what was going on.

I did note that musl would normally work just fine here. Though I appreciate that your other comment goes into much better detail about what was actually going on in the code, as my analysis basically stopped once I understood the high-level flow of what was going on.

Civil acknowledgement that clarifies the commenter's earlier analysis and credits the parent's detail.

This is a vastly clearer explanation than the linked page. Thank you!

This is a vastly clearer explanation than the linked page. Thank you!

Brief but sincere appreciation that points readers to a clearer explanation.

Yep, the linked page doesn't /feel/ AI written (maybe it's using a grok or muse model whose tone I don't quite recognize yet?) but it kinda just states a lot of facts that make sense on their own but don't cohere into a whole vulnerability like the comment above does.

Yep, the linked page doesn't /feel/ AI written (maybe it's using a grok or muse model whose tone I don't quite recognize yet?) but it kinda just states a lot of facts that make sense on their own but don't cohere into a whole vulnerability like the comment above does.

Good-faith critique of the linked page: its facts don't cohere into a clear account of the vulnerability.

I think it's just Claude prompted to write in a casual, conversational, no caps style.

The surface-level hints are gone, but the attempt to make every sentence pithy, the decisions on what information to present and what to assume the audience knows, and the over-emotionalization are all still there.

I think it's just Claude prompted to write in a casual, conversational, no caps style.

The surface-level hints are gone, but the attempt to make every sentence pithy, the decisions on what information to present and what to assume the audience knows, and the over-emotionalization are all still there.

A substantive take on how AI-written style persists beneath a casual prompt, with no swipe at anyone.

Of course it is. "one debian box, one alpine box" is an obvious Claudeism.

Of course it is. "one debian box, one alpine box" is an obvious Claudeism.

A blunt but genuine claim about AI authorship, with a specific phrase cited as evidence.

and what same human would embed two linux systems (??) in a blog post just for a demo that prints "pass"/"fail" that hardly anyone will bother to interact with?

and what same human would embed two linux systems (??) in a blog post just for a demo that prints "pass"/"fail" that hardly anyone will bother to interact with?

Blunt but real observation that embedding two Linux systems for a pass/fail demo is overkill, and the skepticism targets the work rather than a person.

Because Claude wrote the linked page. Claude is impressively bad at communicating these days. with gpt-5.6+ approaching Claude level like 30% of time and Gemini much clearer but also the most annoying 7 year old ever, it’s a pretty lame environment especially for people who r new to everything

Because Claude wrote the linked page. Claude is impressively bad at communicating these days. with gpt-5.6+ approaching Claude level like 30% of time and Gemini much clearer but also the most annoying 7 year old ever, it’s a pretty lame environment especially for people who r new to everything

Blunt opinion on LLM writing aimed at tools, not people; a minor but real observation.

I wondered about this myself as the explanations and documents I have been maintaining are becoming increasingly 'slop', but a different kind of slop.

In the past I could get an explanation, where I was able to track the line of thought. Now when I read through the explanation it is like someone has taken two or three explanations and merged them into each other, like there is a combining of iterations without clarity.

example:

The tree is blue.

The tall tree has the color of blue.

the tree, while tall, is blue.

That finally gets squished into: the blue tall tree has height and is evident of blue.

Is this double speak or injected double effort of words?

I wondered about this myself as the explanations and documents I have been maintaining are becoming increasingly 'slop', but a different kind of slop.

In the past I could get an explanation, where I was able to track the line of thought. Now when I read through the explanation it is like someone has taken two or three explanations and merged them into each other, like there is a combining of iterations without clarity.

example:

The tree is blue.

The tall tree has the color of blue.

the tree, while tall, is blue.

That finally gets squished into: the blue tall tree has height and is evident of blue.

Is this double speak or injected double effort of words?

Good-faith first-hand observation of how LLM prose has degraded, with a concrete illustration and a genuine question.

Until Opus 5.0 I'd have agreed with you, but Opus 5.5 is honestly one of the best I've ever sparred with, vastly better than the rest, including Astra imho. Especially so when prompting for clear top-down, first-principles communication.

Until Opus 5.0 I'd have agreed with you, but Opus 5.5 is honestly one of the best I've ever sparred with, vastly better than the rest, including Astra imho. Especially so when prompting for clear top-down, first-principles communication.

A civil, first-hand opinion on model communication quality, though tangential to the vulnerability.
ericbarrett 2026-11-05 15:566/10first_handinformative | root | parent | next [–]

Disagree, I review a lot of Opus 5.5 code and its comments and docs are still pretty inane and Claudish. Is it better than the 4.x and 5.0 era, sure. But it's still nouning the verbs, using colons everywhere, a-not-b, "a foos the b" etc. A lot of lingual knots that need a human hand to untie.

Disagree, I review a lot of Opus 5.5 code and its comments and docs are still pretty inane and Claudish. Is it better than the 4.x and 5.0 era, sure. But it's still nouning the verbs, using colons everywhere, a-not-b, "a foos the b" etc. A lot of lingual knots that need a human hand to untie.

Firm disagreement backed by first-hand code review experience and specific examples of writing tics.

I was using opus5.5 for everything but since I tried google Argon4 that has been my favorite for writing. It’s clear and to the point.

I was using opus5.5 for everything but since I tried google Argon4 that has been my favorite for writing. It’s clear and to the point.

A brief first-hand model preference that fits the subthread; nothing a moderator would ask to change.
yearolinuxdsktp 2026-11-05 15:385/10first_handinformative | root | parent | prev [–]

Opus 5.5 is much better, but IME I always have to force it to review its writing (Opus 5.5) to get it to put the most important facts first (and not bury the lede). It loves to park the main point far away after intermediate clauses. It always writes “premise, so consequence.” It takes a prompt asking to review against rules in context for it to fix it.

The ‘concise’ output style setting is bad with Opus 5.5, as it will often spit out a cryptic (is this really English?!), allergic-to-commas concise response, which takes more time to parse.

Opus 5.5 is much better, but IME I always have to force it to review its writing (Opus 5.5) to get it to put the most important facts first (and not bury the lede). It loves to park the main point far away after intermediate clauses. It always writes “premise, so consequence.” It takes a prompt asking to review against rules in context for it to fix it.

The ‘concise’ output style setting is bad with Opus 5.5, as it will often spit out a cryptic (is this really English?!), allergic-to-commas concise response, which takes more time to parse.

Specific first-hand observations about the model's writing habits and the concise output setting.

Oh it loves to bury a lede.

    < mundane detail>
    < If you don't run this command your computer will explode>
    < exposition > 
    < mundane reminder to follow up something else >

Then 3 back and forths later, your computer explodes, and claude has the cheek to point out when questioned that it did warn you.

Oh it loves to bury a lede.

    < mundane detail>
    < If you don't run this command your computer will explode>
    < exposition > 
    < mundane reminder to follow up something else >

Then 3 back and forths later, your computer explodes, and claude has the cheek to point out when questioned that it did warn you.

A wry, concrete illustration of the model burying critical warnings; the humor is aimed at the tool, not a person.

Some of the page is also just wrong.

Except it turns out that at least one of those things is because I didn't enable JS, and the static text is wrong!

(Turning on JS did not actually help, because then the page is filled with horrible, cheesy animations and I can't actually read any of it.)

Some of the page is also just wrong.

Except it turns out that at least one of those things is because I didn't enable JS, and the static text is wrong!

(Turning on JS did not actually help, because then the page is filled with horrible, cheesy animations and I can't actually read any of it.)

Blunt but substantive report that the static text is inaccurate without JS and the animations hurt readability; the criticism targets the page, not a person.
byronsharman 2026-11-05 15:49 | removed0/10tangential_annoyancelow_effort | parent | prev | next [–]

Thank you for explaining it with proper capitalization. I understood your explanation much better than the author's.

Thank you for explaining it with proper capitalization. I understood your explanation much better than the author's.

A thank-you combined with a complaint about the author's capitalization, with nothing to add on the topic.

> musl can return “*” for errors

wtaf ?

> musl can return “*” for errors

wtaf ?

A terse expression of surprise aimed at a design choice in musl, not at any person.

There is no doubt in my mind that this was intentional.

There is no doubt in my mind that this was intentional.

A short, firm opinion that the musl behavior was deliberate, which is a legitimate if thin claim.

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.

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.

A firm opinion that the backdoor was deliberate, backed by a concrete argument from the commit sequence.
zbentley 2026-11-05 17:327/10informativeinsightful | root | parent | prev | next [–]

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!

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!

Civil, substantive correction explaining why musl returns `*`, with a firm but fair opinion on the design.

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.

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.

Blunt criticism aimed at a design decision, with a real point about '*' being a wildcard and locked-account marker.
pdw 2026-11-06 00:117/10informative | root | parent | next [–]

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_.

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_.

Civil disagreement that cites the Debian crypt manpage to show why '*' matches common Linux behavior.
cloudfudge 2026-11-05 22:397/10insightfulinformative | root | parent | prev [–]

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.

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.

Clear analogy explaining how the sentinel value collides with the disabled-login marker, with no hostility.

But this also needs the "ddos" patch that was submitted later as well.

That seems orchestrated.

But this also needs the "ddos" patch that was submitted later as well.

That seems orchestrated.

Points to a real dependency on a second patch; suspecting a deliberate backdoor is a fair question in a security thread, not a swipe at a participant.

A terse pointer to a related post by the patch author, which adds a checkable source and makes no direct accusation.
bch 2026-11-05 18:066/10informative | root | parent | prev | next [–]

> 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;
  }

> 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;
  }
Substantive technical argument that crashing on NULL is useful and that checking for NULL is the likelier idiom.

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).

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).

Good-faith point that the root cause is callers not knowing crypt() can fail.
creata 2026-11-05 18:268/10informativeinsightful | root | parent [–]

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.

Another disturbing decision that crypt's signature takes in the hash its output is supposed to be compared against - if it weren't for that, this half-baked "on error the output won't compare equal to the input" measure that crypt and crypt_* use wouldn't have been possible.

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.

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.

Another disturbing decision that crypt's signature takes in the hash its output is supposed to be compared against - if it weren't for that, this half-baked "on error the output won't compare equal to the input" measure that crypt and crypt_* use wouldn't have been possible.

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.

Substantive, respectful disagreement citing the man page on crypt's underspecified error behavior and API design.
xp84 2026-11-05 23:587/10first_handinsightful | root | parent | prev | next [–]

> crashing should be an option

Wise words. I think early in my career I tended towards coding as defensively[1] 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.

[1] note that most "code defensively" advice is good. What I was doing could be thought of as a foolish misinterpretation of the phrase.

> crashing should be an option

Wise words. I think early in my career I tended towards coding as defensively[1] 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.

[1] note that most "code defensively" advice is good. What I was doing could be thought of as a foolish misinterpretation of the phrase.

Thoughtful first-hand reflection on failing loudly versus masking bad input, with a concrete tie-in to the bug.

“A good offense (SIGABRT) is the best defense(ive code).”

I dig it.

“A good offense (SIGABRT) is the best defense(ive code).”

I dig it.

A short, witty distillation of the parent's point, with no swipe at anyone.

[deleted]

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?

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?

Firm technical pushback with a genuine question about the design choice; the mild sarcasm is aimed at the behavior, not a person.

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.

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.

Substantive point that NULL comparison can also misbehave silently; the harsh words target C, not a person.

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.

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.

Plain, substantive disagreement about how C programs handle NULL, with no swipe at anyone.

I've been programming in C since 1978 and never until now heard of a function returning "*" on error.

I've been programming in C since 1978 and never until now heard of a function returning "*" on error.

Brief first-hand remark from a long-time C programmer that a "*" error return is highly unusual; firm but not hostile.

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.

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.

Substantive point broadening the rare case to common C error-handling failure modes; firm but not hostile.
za_creature 2026-11-05 19:197/10informativeinsightful | root | parent [–]

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.

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.

Substantive critique of the API's error handling and pointer lifetime; bluntness is aimed at the library, not a person.

> 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".

> 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".

Wry but aimed at C's design, not a person, with a real argument linking error handling to the bad API choice.

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).

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).

A blunt, on-topic point that any other string would have been a safer error return than "*".

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).

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).
Clearly explains where the asterisk convention comes from, citing the shadow file documentation.

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 occurrs, 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.

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 occurrs, 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.

Firm disagreement backed by a concrete technical explanation of why the magic string was introduced.
ruslan 2026-11-05 22:117/10informativeinsightful | root | parent | next [–]

> 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!

UPD: Except libpam. :(

> 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!

UPD: Except libpam. :(

Firm, substantive disagreement about C error conventions, backed by a look at how FreeBSD actually uses crypt().
serene_sable87 2026-11-05 22:44 | rewritten6/10insinuationinformative | original | root | parent | prev | next [–]

Returning NULL on error is actually an excellent choice because most programs will crash if they try to use it without checking. A program, any program, crashing is better than this bug. Of course, modern programming languages have better constructs, but this was rare to nonexistent when C was started and C isn't going away anytime soon. musl's decision to change the return value to "*" is ununderstandable.

original comment by gpvos

Returning NULL on error is actually an excellent choice because most programs will crash if they try to use it without checking. A program, any program, crashing is better than this bug. 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 the return value to "*" is ununderstandable and likely intentionally evil.

Keeps the fail-fast argument for NULL while dropping the "delirious" swipe and the accusation that musl acted out of intentional evil.
zbentley 2026-11-06 00:598/10insightfulfirst_hand | root | parent [–]

> 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.

That’s why I’ve been harping on C being trash for shared-code APIs in these comments: bad decisions are gonna happen, and irresponsible users are forever, so can we maybe use tools that require you to put on a seat belt (mandatory error handling) before starting the car?

> 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.

That’s why I’ve been harping on C being trash for shared-code APIs in these comments: bad decisions are gonna happen, and irresponsible users are forever, so can we maybe use tools that require you to put on a seat belt (mandatory error handling) before starting the car?

Firm, substantive pushback that explains the maintainer incentives behind musl's choice, drawing on first-hand experience and without any real swipe at the parent.

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.

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.

Firm disagreement with a substantive reductio argument aimed at the design decision, not at a person.

This sounds like a program problem not a library problem. Why did the library try to solve it?

This sounds like a program problem not a library problem. Why did the library try to solve it?

A genuine, terse question challenging the design decision in good faith.
za_creature 2026-11-05 20:23 | removed0/10jokelow_effort | root | parent | prev [–]

Oh, it was magic alright.

Oh, it was magic alright.

A one-line quip with no substance beyond the pun on 'magic'.

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?"

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?"

Substantive, civil observation about why the chosen string is risky when a caller treats it as a regex.
JdeBP 2026-11-05 20:1710/10insightfulinformative | root | parent | prev | next [–]

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_unix/pam_unix.c#n142

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/Attic/fcrypt.c/?rev=1.2

It's worth pointing out that musl tried to do the same as SSLeay, and deliberately returns something different to the original salt so that they will not compare equal. It does not return "" if the input is actually "" in the first place, contrary to the headlined cartoon explanation. (The locus of the problem is the caller calling it with a salt that is not "" and then comparing it with "".)

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_unix/pam_unix.c#n142

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/Attic/fcrypt.c/?rev=1.2

It's worth pointing out that musl tried to do the same as SSLeay, and deliberately returns something different to the original salt so that they will not compare equal. It does not return "" if the input is actually "" in the first place, contrary to the headlined cartoon explanation. (The locus of the problem is the caller calling it with a salt that is not "" and then comparing it with "".)

Expert history of crypt() error conventions with source links, correcting the article's explanation.

> 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.

> 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.

Short but sincere reaction that flags a real bug and suggests reporting it; nothing to moderate.
JdeBP 2026-11-06 00:349/10insightfulinformative | root | parent [–]

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/Research-V7-Snapshot-Development/usr/src/libc/gen/crypt.c#L330

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?annotate=1.109

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/Research-V7-Snapshot-Development/usr/src/libc/gen/crypt.c#L330

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?annotate=1.109

Expert, well-sourced explanation of why crypt() callers historically ignore NULL, with a polite and lightly wry correction.

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-blowfish.c

Failing to check result of crypt() in init cannot do much harm, doing so is equivalent of going system reboot (crash init :-)). Besides, whoever has access to system console can do whatever they want with the system, no password can prevent from that.

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-blowfish.c

Failing to check result of crypt() in init cannot do much harm, doing so is equivalent of going system reboot (crash init :-)). Besides, whoever has access to system console can do whatever they want with the system, no password can prevent from that.

Substantive technical reply citing crypt_blowfish's -1 returns and the future-maintenance risk, with a light joke and no hostility.
sandy_skink70 2026-11-05 21:15 | rewritten6/10informative | original | root | parent | prev [–]

This is a bad call by the musl authors.

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.

original comment by za_creature

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.

Replaced the charge of incompetence against the musl authors with a plain criticism, keeping the technical argument about the sentinel value.

Returning a string on error honestly is defensible (especially in a language like C where the type system doesn’t have exceptions or result types), the question is why the string was “*” instead of like “crypt ERROR: input password cannot be longer than 256 characters” lol

Returning a string on error honestly is defensible (especially in a language like C where the type system doesn’t have exceptions or result types), the question is why the string was “*” instead of like “crypt ERROR: input password cannot be longer than 256 characters” lol

Substantive point about error handling in C with light humor.

No. Musl claims (claimed?) to be strictly POSIX compliant, and POSIX requires crypt to return NULL on errors.

No. Musl claims (claimed?) to be strictly POSIX compliant, and POSIX requires crypt to return NULL on errors.

Firm disagreement backed by a concrete fact about POSIX's requirement for crypt.
tredre3 2026-11-05 19:067/10informativeinsightful | root | parent | prev | next [–]

"*" 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.

"*" 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.

Substantive historical context on why "*" was used and how it could collide with shadow file comparisons.

No, it's absolutely not defensible.

No, it's absolutely not defensible.

Firm plain disagreement, which is kept verbatim even though it gives no reasoning.
dboreham 2026-11-05 18:21 | removed0/10low_effort | root | parent | prev [–]

Precisely.

Precisely.

A bare one-word agreement that adds nothing to the discussion.
paparazzi678 2026-11-05 20:05 | removed0/10low_effort | parent | prev | next [–]

weird chain of events and could have been handled in a better way for sure

weird chain of events and could have been handled in a better way for sure

Generic one-line reaction that adds nothing to the parent's detailed analysis.
1vuio0pswjnm7 2026-11-05 20:483/10questionlow_effort | parent | prev | next [–]

"The OpenSSL adjustment ensures a minimum hash length for the user before logging them in, even if crypt() succeeds somehow."

OpenSSL??

"The OpenSSL adjustment ensures a minimum hash length for the user before logging them in, even if crypt() succeeds somehow."

OpenSSL??

Short but genuine question flagging an apparent OpenSSL/OpenSSH mix-up in the quoted text.

Sorry, should have written OpenSSH.

Sorry, should have written OpenSSH.

Short good-faith correction that answers the parent's question and clarifies the earlier claim.

Great explanation. This affirms my biases that shell scripting and everything-is-strings is a mistake. Horrific.

Great explanation. This affirms my biases that shell scripting and everything-is-strings is a mistake. Horrific.

Strong opinion on stringly-typed design that ties to the explanation, with no personal attack; kept verbatim.
BoppreH 2026-11-05 13:168/10insightfulinformative | prev | next [–]

In-band signaling[1] strikes again. should not have been used as special value, neither for "no password" in /etc/passwd nor* "error while hashing the password" in crypt. I'm afraid we'll find many more of these abuses, specially in string manipulations. The null was ok, an exception (or equivalent) better, and Result<T> (or equivalent) best.

[1] https://en.wikipedia.org/wiki/In-band_signaling#Computer_data_serialization

In-band signaling[1] strikes again. should not have been used as special value, neither for "no password" in /etc/passwd nor* "error while hashing the password" in crypt. I'm afraid we'll find many more of these abuses, specially in string manipulations. The null was ok, an exception (or equivalent) better, and Result<T> (or equivalent) best.

[1] https://en.wikipedia.org/wiki/In-band_signaling#Computer_data_serialization

Substantive technical analysis tying the bug to in-band signaling, with a useful reference and no hostility.
hedora 2026-11-05 16:157/10informativeinsightful | parent | next [–]

Exceptions are almost never the right choice. They create extremely complicated control flow extremely quickly, and then you have to test all the different control flows. Java software is an example of how this goes sideways fast. There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor), and objects are default initialized, so all fields (even static ones that are assigned in the constructor) can be null.

So, any field de-reference in Java can produce a null pointer exception. Try testing all paths in something like:

try { foo.sideEffect1().sideEffect2()...sideEffectN(); } catch { bar.sideEffect1().sideEffect2()...sideEffectM(); } finally { quuz.sideEffect1().sideEffect2()...sideEffectP(); }

There are M N P paths. The only way to write testable code with exceptions is to panic on uncaught exception, and to never catch unexpected exceptions. Even then, you're relying on nothing unexpectedly throwing the exception type you caught.

I'd argue Result<T> >> NULL >> sentries >> exceptions for this reason. Sentries are terrible, but at least they're not computationally hard to use correctly.

Exceptions are almost never the right choice. They create extremely complicated control flow extremely quickly, and then you have to test all the different control flows. Java software is an example of how this goes sideways fast. There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor), and objects are default initialized, so all fields (even static ones that are assigned in the constructor) can be null.

So, any field de-reference in Java can produce a null pointer exception. Try testing all paths in something like:

try { foo.sideEffect1().sideEffect2()...sideEffectN(); } catch { bar.sideEffect1().sideEffect2()...sideEffectM(); } finally { quuz.sideEffect1().sideEffect2()...sideEffectP(); }

There are M N P paths. The only way to write testable code with exceptions is to panic on uncaught exception, and to never catch unexpected exceptions. Even then, you're relying on nothing unexpectedly throwing the exception type you caught.

I'd argue Result<T> >> NULL >> sentries >> exceptions for this reason. Sentries are terrible, but at least they're not computationally hard to use correctly.

A substantive argument against exceptions, with a concrete Java example and a clear ranking of error-handling approaches.

I'd argue that's the fault of having default nullability, not the exceptions. You would have the same problem if every attribute access returned a Result<T> too.

And I'm not a fan of breaking control flow, but I still find it preferable to nulls. A null may travel far from the place where it was generated before blowing up, making troubleshooting harder, or it might come from a function you called for its side effect but forgot to check the return value. I gotta say I'm also a careful enthusiast of checked exceptions, it's great knowing which ways a function can fail.

But I think the consensus is growing that Result<T> (plus warnings for unused return values) is simply the superior choice.

I'd argue that's the fault of having default nullability, not the exceptions. You would have the same problem if every attribute access returned a Result<T> too.

And I'm not a fan of breaking control flow, but I still find it preferable to nulls. A null may travel far from the place where it was generated before blowing up, making troubleshooting harder, or it might come from a function you called for its side effect but forgot to check the return value. I gotta say I'm also a careful enthusiast of checked exceptions, it's great knowing which ways a function can fail.

But I think the consensus is growing that Result<T> (plus warnings for unused return values) is simply the superior choice.

Substantive disagreement separating nullability from exceptions and comparing error-handling approaches.

C has what you are asking for, mostly:

   int foo f() __attribute__((warn_unused_result))

As long as an error condition on the return value causes a crash on use, then it's mostly the same as result. (The main remaining difference being that you do an implicit `.unwrap()` in C, and in things like rust that's an explicit code smell.)

C has what you are asking for, mostly:

   int foo f() __attribute__((warn_unused_result))

As long as an error condition on the return value causes a crash on use, then it's mostly the same as result. (The main remaining difference being that you do an implicit `.unwrap()` in C, and in things like rust that's an explicit code smell.)

Substantive technical note on C's warn_unused_result and how it compares to Result types.
gf000 2026-11-05 19:387/10insightfulinformative | root | parent | prev | next [–]

> There's no way to create a class that prevents callers from reading the object before the constructor runs

That can only happen if you leak the reference (given we are talking about java language level semantics and not byte code), which I give you is unfortunately not that hard to do.

As for exceptions, I think they would pair beautifully with software transactional memory, which would work well with effects. In that case (and no side effects) it would not matter what code path we took, we could just go back to a known safe point.

Because ergonomy-wise I still think exceptions win (even though I do like Maybe types, and Rust's ? macro is plenty good): - they let one focus on the actual happy path code - no error is ever swallowed, unless explicitly written so (looking at you go with useless if errs that fail to properly handle the error), they are always handled somewhere - they automatically "unwrap" the Ok result, and automatically bubble up the Err side. - bubbling up is especially useful because most errors can't be handled at their source (e.g. IO error is meaningless, how you handle it depends on what you want to do in the first place, which is done several layers higher) - they automatically attach a stackframe/source, while with result types you may get a list of Ok Ok Ok Err and have no idea how that Err got there.

> There's no way to create a class that prevents callers from reading the object before the constructor runs

That can only happen if you leak the reference (given we are talking about java language level semantics and not byte code), which I give you is unfortunately not that hard to do.

As for exceptions, I think they would pair beautifully with software transactional memory, which would work well with effects. In that case (and no side effects) it would not matter what code path we took, we could just go back to a known safe point.

Because ergonomy-wise I still think exceptions win (even though I do like Maybe types, and Rust's ? macro is plenty good): - they let one focus on the actual happy path code - no error is ever swallowed, unless explicitly written so (looking at you go with useless if errs that fail to properly handle the error), they are always handled somewhere - they automatically "unwrap" the Ok result, and automatically bubble up the Err side. - bubbling up is especially useful because most errors can't be handled at their source (e.g. IO error is meaningless, how you handle it depends on what you want to do in the first place, which is done several layers higher) - they automatically attach a stackframe/source, while with result types you may get a list of Ok Ok Ok Err and have no idea how that Err got there.

Substantive, good-faith pushback on Java construction and a reasoned case for exceptions over result types.

> There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor)

Which is why the pattern of double-checked locking doesn't work.

https://en.wikipedia.org/wiki/Double-checked_locking

> There's no way to create a class that prevents callers from reading the object before the constructor runs (in multi-threaded software, new first hands a reference to the caller, which can put it on the heap, then it calls the constructor)

Which is why the pattern of double-checked locking doesn't work.

https://en.wikipedia.org/wiki/Double-checked_locking

Brief, on-point connection to double-checked locking with a useful reference link.

It's wrong. The caller doesn't get the reference until the constructor runs. Although it looks like two operations in bytecode, the only thing you can legally do with an uninitialised object reference is call its constructor.

It's wrong. The caller doesn't get the reference until the constructor runs. Although it looks like two operations in bytecode, the only thing you can legally do with an uninitialised object reference is call its constructor.

Firm, technical disagreement that explains a JVM bytecode verification detail.

> never catch unexpected exceptions

Yes, broad exception catch clauses are simply incorrect code.

Panic is maybe an overreaction, "scratch everything and orderly abort" is sufficient.

> never catch unexpected exceptions

Yes, broad exception catch clauses are simply incorrect code.

Panic is maybe an overreaction, "scratch everything and orderly abort" is sufficient.

A firm, on-topic opinion on exception handling that adds a distinction between panicking and orderly abort.

I'm inclined to say a Panic is absolutely preferable to root auth.

I'm inclined to say a Panic is absolutely preferable to root auth.

Short, real opinion on the tradeoff between crashing and an auth bypass.

Exceptions work fine and enable a clean separation of business logic and error handling. Visibility into partially-initialized objects is a language design issue independent of exceptions. In a non-exceptional language, the corresponding access to uninitialized fields would just be bugs, not errors you can see.

Nothing in your post actually supports the idea that exceptions are "computationally hard". The CFG error edges exist however the errors are spelled

Exceptions work fine and enable a clean separation of business logic and error handling. Visibility into partially-initialized objects is a language design issue independent of exceptions. In a non-exceptional language, the corresponding access to uninitialized fields would just be bugs, not errors you can see.

Nothing in your post actually supports the idea that exceptions are "computationally hard". The CFG error edges exist however the errors are spelled

Firm, substantive disagreement arguing that error edges exist regardless of mechanism and that partial initialization is a separate language design issue.

Unfortunately we’re moving to llm based systems which rely entirely on in-band signaling.

Unfortunately we’re moving to llm based systems which rely entirely on in-band signaling.

Terse, wry observation that connects in-band signaling to LLM systems, adding a real point.

PSA: you can typically declare different types of string so that your type system objects to mixing data of different types even though deep down we know they're both strings.

PSA: you can typically declare different types of string so that your type system objects to mixing data of different types even though deep down we know they're both strings.

Practical tip on using distinct string types to catch mixed-up data.
BoppreH 2026-11-05 15:287/10informativeinsightful | root | parent | next [–]

If you mean type aliases, that's a good idea within a single codebase, but the distinction between different types of strings doesn't survive serialization so it wouldn't have prevented this error. What they need is a way to signal errors out-of-band, like Result<T> or exceptions.

If you mean type aliases, that's a good idea within a single codebase, but the distinction between different types of strings doesn't survive serialization so it wouldn't have prevented this error. What they need is a way to signal errors out-of-band, like Result<T> or exceptions.

Substantive technical point that string type distinctions don't survive serialization, and proposes out-of-band error signaling.

[deleted]

In python, aliases are separate from typing.NewType, but you're right: it's only enforced within a codebase and then only if the dev cares to run the typechecker.

So yeah, I guess it either needs to happen at runtime, or we just do signaling and user data on separate file descriptors.

In python, aliases are separate from typing.NewType, but you're right: it's only enforced within a codebase and then only if the dev cares to run the typechecker.

So yeah, I guess it either needs to happen at runtime, or we just do signaling and user data on separate file descriptors.

Adds a useful Python typing distinction and concedes the point, proposing runtime checks or separate file descriptors.

This is presuming that the type system is nomitive rather than structural and there is not serialisation/deserialisation or type refinement occurring. A structural difference is much more preferable.

This is presuming that the type system is nomitive rather than structural and there is not serialisation/deserialisation or type refinement occurring. A structural difference is much more preferable.

Substantive technical caveat about nominal vs structural typing and serialization, stated without any swipe.

This is a side-effect of Unix-y "it's all plain text" design that leans extremely heavily upon in-band signalling (among other design decisions).

If /etc/shadow was a JSON (or XML, even, although that would make me feel gross), this bug would have been much easier to see, because a field that reads '"hash": ""' sticks out a lot* more than a custom microformat that you don't understand.

With data structures, there's never "plain text" - it's all structured. The question is whether you want to use existing formats and tools, or create a new one for every tool and literally make footguns like this.

This is a side-effect of Unix-y "it's all plain text" design that leans extremely heavily upon in-band signalling (among other design decisions).

If /etc/shadow was a JSON (or XML, even, although that would make me feel gross), this bug would have been much easier to see, because a field that reads '"hash": ""' sticks out a lot* more than a custom microformat that you don't understand.

With data structures, there's never "plain text" - it's all structured. The question is whether you want to use existing formats and tools, or create a new one for every tool and literally make footguns like this.

Substantive argument linking the bug to Unix plain-text formats and in-band signalling.
__MatrixMan__ 2026-11-05 14:547/10first_handinsightful | root | parent | next [–]

A few years back I un-bashed my life as much as possible in favor of nushell (though it's not the only shell to encourage structure). I feel like it has really helped me notice a lot of this-needs-more-structure type problems outside of my shell that I otherwise wouldn't have.

The elegance of the unix-y approach is actually that it's all bits. You can just join things together and shove the bits through, that's a wild simplification compared with having vendor-specific kinds of plugs. But we could have that kind of compatibility while also benefiting from structure.

So I encourage everybody to give a shell with structured output a try, let it rewire your brain, and then ask yourself: how is the OS holding this back from being everything it could be? Because if we build that OS, it won't encourage these problems.

When I imagine it, it would probe either side of a pipe to see if they support negotiation. If not, send the bits through in the same old unixy way. If so, use the newest schema for structured data that is supported by both sides.

A few years back I un-bashed my life as much as possible in favor of nushell (though it's not the only shell to encourage structure). I feel like it has really helped me notice a lot of this-needs-more-structure type problems outside of my shell that I otherwise wouldn't have.

The elegance of the unix-y approach is actually that it's all bits. You can just join things together and shove the bits through, that's a wild simplification compared with having vendor-specific kinds of plugs. But we could have that kind of compatibility while also benefiting from structure.

So I encourage everybody to give a shell with structured output a try, let it rewire your brain, and then ask yourself: how is the OS holding this back from being everything it could be? Because if we build that OS, it won't encourage these problems.

When I imagine it, it would probe either side of a pipe to see if they support negotiation. If not, send the bits through in the same old unixy way. If so, use the newest schema for structured data that is supported by both sides.

Thoughtful first-hand take on structured shells, with a concrete idea for pipe negotiation that preserves Unix compatibility.
jonhohle 2026-11-05 15:246/10first_handinformative | root | parent [–]

At Amazon we had RecordStream which implemented most of the standard Unix tools on structured streams. It was pretty easy to write filters, aggregators, and formatted output in something not much more complicated than sed/awk/cut/join/sort pipelines. There was a convenient structured log format for services that was often used as input.

https://github.com/benbernard/RecordStream

At Amazon we had RecordStream which implemented most of the standard Unix tools on structured streams. It was pretty easy to write filters, aggregators, and formatted output in something not much more complicated than sed/awk/cut/join/sort pipelines. There was a convenient structured log format for services that was often used as input.

https://github.com/benbernard/RecordStream

First-hand account of a structured-stream Unix toolset with a useful link.
hedora 2026-11-05 16:276/10first_handquestion | root | parent [–]

That looks really cool. I've been heading the opposite direction: posix shell for everything.

I have a bunch of management scripts, etc, that need to run on everything from a potato to a T-1000, and that's the only language that's always available. (OpenBSD sh, FreeBSD sh, busybox, bash, dash, zsh all can cope with it).

I'd love something that's at least JSON-typed (or equivalent), but that can be bootstrapped up on least-common-denominator posix.

Has anyone built something similar? (Yes, I'm being picky -- bun / ts is already a pretty low bar for cross platform support, but these scripts do stuff like install node, so I'm not totally crazy for asking.)

That looks really cool. I've been heading the opposite direction: posix shell for everything.

I have a bunch of management scripts, etc, that need to run on everything from a potato to a T-1000, and that's the only language that's always available. (OpenBSD sh, FreeBSD sh, busybox, bash, dash, zsh all can cope with it).

I'd love something that's at least JSON-typed (or equivalent), but that can be bootstrapped up on least-common-denominator posix.

Has anyone built something similar? (Yes, I'm being picky -- bun / ts is already a pretty low bar for cross platform support, but these scripts do stuff like install node, so I'm not totally crazy for asking.)

Shares real portability constraints and asks a genuine question about typed shell tooling.
cowlevel 2026-11-06 03:092/10low_effortquestion | root | parent [–]

Python?

Python?

A terse but genuine suggestion in answer to the parent's question about a portable typed scripting option.
dns_snek 2026-11-05 14:208/10insightfulinformative | root | parent | prev | next [–]

The issue isn't the config, but the fact that crypt() doesn't have any way to signal failure. In better designed languages you would have exceptions, result types, or something. Even in C you have "return integer indicating success or error" which would've prevented the confusion.

Now the public API is hard to change but you could still avoid the issue by returning a longer token like "==DANGEROUS_CRYPT_ERROR_length==" or "==DANGEROUS_CRYPT_ERROR_unsupported_alg==" which would never match the password field unless someone was really trying to induce the issue.

The issue isn't the config, but the fact that crypt() doesn't have any way to signal failure. In better designed languages you would have exceptions, result types, or something. Even in C you have "return integer indicating success or error" which would've prevented the confusion.

Now the public API is hard to change but you could still avoid the issue by returning a longer token like "==DANGEROUS_CRYPT_ERROR_length==" or "==DANGEROUS_CRYPT_ERROR_unsupported_alg==" which would never match the password field unless someone was really trying to induce the issue.

Substantive technical disagreement pointing at crypt()'s error signaling, with a concrete mitigation.
knome 2026-11-05 15:118/10informativeinsightful | root | parent | next [–]

>but the fact that crypt() doesn't have any way to signal failure

https://pubs.opengroup.org/onlinepubs/009604599/functions/crypt.html

>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.

edit: apparently crypt implementations have been trash ignoring the NULL return for a long time. this isn't only on MUSL, but it's still because people ignored and tried to work around the existing out-of-band error mechanism.

>but the fact that crypt() doesn't have any way to signal failure

https://pubs.opengroup.org/onlinepubs/009604599/functions/crypt.html

>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.

edit: apparently crypt implementations have been trash ignoring the NULL return for a long time. this isn't only on MUSL, but it's still because people ignored and tried to work around the existing out-of-band error mechanism.

Substantive correction citing the POSIX spec on crypt() error handling; its bluntness about developers is criticism of practice, not a personal attack.
zbentley 2026-11-05 17:367/10insightfulinformative | root | parent | next [–]

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

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

Explains why the sentinels exist and argues C is a poor choice for public APIs; strong opinion aimed at a language.

> 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

I meant there's no mechanism besides returning NULL that would make it hard to ignore the issue (exceptions need to be caught, results need to be unwrapped and their failure handled, an integer return value makes it clearer that there's something there that you need to check). I also missed errno codes when glancing at the manpage earlier which should illustrate the problem.

And yes, whoever first had the brilliant idea to start returning garbage never should've done that, I'm not disputing that.

> 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

I meant there's no mechanism besides returning NULL that would make it hard to ignore the issue (exceptions need to be caught, results need to be unwrapped and their failure handled, an integer return value makes it clearer that there's something there that you need to check). I also missed errno codes when glancing at the manpage earlier which should illustrate the problem.

And yes, whoever first had the brilliant idea to start returning garbage never should've done that, I'm not disputing that.

Clarifies the API design argument that NULL is easy to ignore compared with other error mechanisms, and owns a mistake about errno.

The spec implies a string of at least two characters in the success case, am I reading that right?

The spec implies a string of at least two characters in the success case, am I reading that right?

A genuine, on-topic question about what the spec text implies for the success case.

If this is true, then the entire thread branch here on HN is entirely wrong. What is going on??

If this is true, then the entire thread branch here on HN is entirely wrong. What is going on??

Short but genuine reaction questioning the thread's premise in light of the cited spec.
fc417fc802 2026-11-06 04:468/10informativeinsightful | root | parent | next [–]

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.)

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.)

Explains from the spec why NULL isn't an acceptable generic error return for crypt; the wry aside is mild and the correction is substantive.

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.

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.

Blunt but substantive point that no error handling scheme can force implementers to follow the spec, with a concrete Rust example.
zbentley 2026-11-05 17:427/10insightfulinformative | root | parent | next [–]

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.

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.

Substantive argument that tagged unions make error handling mandatory in a way C APIs cannot, with a real-world link.

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.

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.

Substantive technical disagreement about NULL handling and unwrap(); the generic jab at bad programmers isn't aimed at anyone here.

> 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!"); }

And for sure there are much better ways to write both kinds of code! But people routinely do not do that.

> 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!"); }

And for sure there are much better ways to write both kinds of code! But people routinely do not do that.

Substantive technical reply with concrete C examples of NULL-handling pitfalls; the profanity is aimed at code, not a person.

> I can always write code like `if(ENOSYS) { return Ok("*") } instead of returning Err(ENOSYS)

You can do that but it's going to feel bad to write that and it's going to stand out like a sore thumb to anyone who reads it. But "errno" is just ambient state that's completely invisible unless you happen to have the manpage fresh in your memory.

> I can always write code like `if(ENOSYS) { return Ok("*") } instead of returning Err(ENOSYS)

You can do that but it's going to feel bad to write that and it's going to stand out like a sore thumb to anyone who reads it. But "errno" is just ambient state that's completely invisible unless you happen to have the manpage fresh in your memory.

Substantive, good-faith point that explicit error types make misuse visible while errno stays hidden ambient state.

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.

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.

Substantive reply noting the implementers knowingly deviated from the spec's NULL requirement; wry but not aimed at a person.

A lazy attitude like "if we just used <n> language, all our problems would be fixed".

A lazy attitude like "if we just used <n> language, all our problems would be fixed".

Blunt criticism of an idea rather than a person, and it answers the parent's question.
eqvinox 2026-11-05 19:277/10insightfulinformative | root | parent | prev | next [–]

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 hash 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?

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 hash 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?

Wry but substantive technical walkthrough of OpenSSH's fake-salt logic that offers a counter-explanation; the jab targets the framing, not a person.
FLeXMurphy 2026-11-05 16:54 | removed0/10jokesnarkinsinuationlow_effort | root | parent | prev [–]

Except it's not trash. It's a backdoor funded by our glorious leader Kim Jong Un.

Except it's not trash. It's a backdoor funded by our glorious leader Kim Jong Un.

A one-line joke insinuating a North Korean backdoor, with no substantive point to preserve.

Personally I think there was nothing wrong with returning NULL. "But if people don't check for it it crashes their programs!" Good! Let it crash loudly! Then they notice and fix the issue.

Also I think it could have returned ":". Given the file format of /etc/shadow and /etc/passwd I don't think it's possible to have a : as a value for anything.

Personally I think there was nothing wrong with returning NULL. "But if people don't check for it it crashes their programs!" Good! Let it crash loudly! Then they notice and fix the issue.

Also I think it could have returned ":". Given the file format of /etc/shadow and /etc/passwd I don't think it's possible to have a : as a value for anything.

Substantive design opinion on crypt() failure handling with a concrete alternative sentinel.

> Also I think it could have returned ":". Given the file format of /etc/shadow and /etc/passwd I don't think it's possible to have a : as a value for anything.

I wonder if somebody else would have thought the exact same thing in a different (non-/etc/*) context, and then you get the same effect in a more obscure manner.

> Also I think it could have returned ":". Given the file format of /etc/shadow and /etc/passwd I don't think it's possible to have a : as a value for anything.

I wonder if somebody else would have thought the exact same thing in a different (non-/etc/*) context, and then you get the same effect in a more obscure manner.

Good-faith point that an in-band sentinel can collide in other contexts.

Indeed. Let's keep NULL.

Indeed. Let's keep NULL.

Brief but sincere agreement that takes a position on the design question: keep returning NULL.

> The issue isn't the config

That's a meaningless statement. There isn't a singular "the" issue like you said. Any one of several different technical decisions would have completely prevented the issue. Including, to my point, /etc/shadow being a JSON file where an account being locked is an `"active": false` field instead of an asterisk with no intrinsic meaning.

> The issue isn't the config

That's a meaningless statement. There isn't a singular "the" issue like you said. Any one of several different technical decisions would have completely prevented the issue. Including, to my point, /etc/shadow being a JSON file where an account being locked is an `"active": false` field instead of an asterisk with no intrinsic meaning.

Firm disagreement aimed at the claim, not the person, with a substantive point about multiple failure points and structured config formats.
BoppreH 2026-11-05 14:337/10informativeinsightful | root | parent | prev [–]

No, that's just more in-band signaling!

Some badly designed password hashing algorithms (cough bcrypt cough) have maximum password lengths, ignoring everything after X characters. So it could happen that I set my password to a 100-character string, it gets saved as "==DANGEROUS_CRYPT_ERROR_length==", and then anybody can log in to my account by entering an unrelated long string that returns the same "error".

If a function promises a string result, it should only provide a string if the function succeeded. The null was much better than magic strings.

No, that's just more in-band signaling!

Some badly designed password hashing algorithms (cough bcrypt cough) have maximum password lengths, ignoring everything after X characters. So it could happen that I set my password to a 100-character string, it gets saved as "==DANGEROUS_CRYPT_ERROR_length==", and then anybody can log in to my account by entering an unrelated long string that returns the same "error".

If a function promises a string result, it should only provide a string if the function succeeded. The null was much better than magic strings.

Firm, substantive disagreement with a concrete bcrypt failure case showing why magic error strings are unsafe.

I know, it's bad. I agree that it should've stayed NULL. But if you really really really think you have an acceptable reason to do something like this (and you probably don't), the least you can do is use a non-trivial token.

I know, it's bad. I agree that it should've stayed NULL. But if you really really really think you have an acceptable reason to do something like this (and you probably don't), the least you can do is use a non-trivial token.

Concedes the point and adds a minor but real argument for non-trivial sentinel tokens.

If I was really desperate and could only return a valid string, I would probably use a message and add a random component to avoid ever matching two errors. Like SQL, where null != null.

If I was really desperate and could only return a valid string, I would probably use a message and add a random component to avoid ever matching two errors. Like SQL, where null != null.

Substantive, good-faith design suggestion with a neat SQL null analogy.

Yes, absolutely. Take for example this cURL command:

  NO_PROXY=1 curl -qs --data @./my\ message.txt -- \
  "https://user:[email protected]/user/${userid}/my%20message.txt"

Random parts of it will be interpreted by the shell, others by cURL, or the filesystem, or the remote server. They each have their own encoding rules, nested. I count ten distinct separators and nine ways of escaping/tagging. It's one command. No, just no.

We have seriously overused strings and we're now paying the price.

Yes, absolutely. Take for example this cURL command:

  NO_PROXY=1 curl -qs --data @./my\ message.txt -- \
  "https://user:[email protected]/user/${userid}/my%20message.txt"

Random parts of it will be interpreted by the shell, others by cURL, or the filesystem, or the remote server. They each have their own encoding rules, nested. I count ten distinct separators and nine ways of escaping/tagging. It's one command. No, just no.

We have seriously overused strings and we're now paying the price.

Concrete example of nested encoding layers in one command, supporting the parent's argument; blunt but substantive.

Also: free text is the input format of LLMs...

Also: free text is the input format of LLMs...

Terse but on-point observation extending the critique of overused strings to LLM inputs.

Returning “*” instead of NULL to signal failure is the real footgun. Don’t blame the format.

For historical reasons, we could’ve ended up JSON as “hash”: “*” just the same. The /etc/shadow format lets you skip values completely, like “::”, so your argument doesn’t hold.

Terminating an app because of a NULL dereference is the other footgun. Sadly, we still have “panic” in modern languages like Go and Rust.

Returning “*” instead of NULL to signal failure is the real footgun. Don’t blame the format.

For historical reasons, we could’ve ended up JSON as “hash”: “*” just the same. The /etc/shadow format lets you skip values completely, like “::”, so your argument doesn’t hold.

Terminating an app because of a NULL dereference is the other footgun. Sadly, we still have “panic” in modern languages like Go and Rust.

Firm, substantive disagreement arguing that the sentinel value, not the file format, is the real footgun.
hedora 2026-11-05 16:457/10informativeinsightful | root | parent | next [–]

"panic" is a good thing. There's always some error condition that your code cannot handle. At some point, the least bad thing to do is crash.

For instance, what if your error handling code throws?

One spectacular example of panic saving the day was the Apollo 11 1202 error. It was basically a watchdog timer that fired if the landing computer was starved of CPU time. A misconfiguration caused the first moon landing to hit CPU overload. When that happened, the computer displayed 1202 and rebooted, then ran things in priority order. Apollo 11 landed in the middle of a tight 'panic, reboot, adjust thrusters' loop. Thanks to core memory, most of the heap survived the reboot, so the reboot step was fast enough for it to mostly land without human interaction. The pilot had to make ~1 adjustment to get it to actually hit the landing site, but the panic-ridden computer handled the rest of the descent.

https://dpma.de/english/our_office/publications/ingeniouswomen/apollosfrauen/margarethamilton/index.html

"panic" is a good thing. There's always some error condition that your code cannot handle. At some point, the least bad thing to do is crash.

For instance, what if your error handling code throws?

One spectacular example of panic saving the day was the Apollo 11 1202 error. It was basically a watchdog timer that fired if the landing computer was starved of CPU time. A misconfiguration caused the first moon landing to hit CPU overload. When that happened, the computer displayed 1202 and rebooted, then ran things in priority order. Apollo 11 landed in the middle of a tight 'panic, reboot, adjust thrusters' loop. Thanks to core memory, most of the heap survived the reboot, so the reboot step was fast enough for it to mostly land without human interaction. The pilot had to make ~1 adjustment to get it to actually hit the landing site, but the panic-ridden computer handled the rest of the descent.

https://dpma.de/english/our_office/publications/ingeniouswomen/apollosfrauen/margarethamilton/index.html

Firm, civil disagreement defending panics, with a concrete historical example and a source.
zbentley 2026-11-05 17:528/10insightfulinformative | root | parent [–]

sshole-type APIs < panicking APIs < required error handling.

Panic is fine, but fundamentally a whole-program crash is undesirable in a really wide range of use-cases. I don't want other SSH connections to crash (or the server to stop accepting valid logins until someone restarts sshd) if one person tries to log in with a credential that triggers a panicking bug in crypt(). Same for crashes in request handlers and so on.

The language/runtime itself doesn't (and probably cannot, at least if it's a general purpose language) distinguish between local and global error conditions, so aborts end up being either unnecessarily damaging, or languages like Go end up normalizing panic "localization"/handling in dangerous ways. Isolation using threads isn't robust enough. Isolation using processes is far too fiddly, slow, and opaque.

The better solution is required error handling. If user code must engage with the possibility of an error state in order to access a result, that's a huge step forward. They can do the error handling wrong, but if you make most people have to do it at all most of the problems will go away.

There are other good solutions too, like Erlang's process boundaries, but those tend to have drawbacks that make them inapplicable to a lot of programming areas. Tagged unions, on the other hand, are only inapplicable in C library code because of language defects and terrible community conventions around ABIs.

"I forgot to check for an error" should not be implicated in bugs in 2026; we have had a large menu of possible alternatives (checked exceptions, unchecked exceptions, tagged unions, etc.) for decades. But we're still over here writing C (and Go), languages pathologically incapable of providing this ability.

sshole-type APIs < panicking APIs < required error handling.

Panic is fine, but fundamentally a whole-program crash is undesirable in a really wide range of use-cases. I don't want other SSH connections to crash (or the server to stop accepting valid logins until someone restarts sshd) if one person tries to log in with a credential that triggers a panicking bug in crypt(). Same for crashes in request handlers and so on.

The language/runtime itself doesn't (and probably cannot, at least if it's a general purpose language) distinguish between local and global error conditions, so aborts end up being either unnecessarily damaging, or languages like Go end up normalizing panic "localization"/handling in dangerous ways. Isolation using threads isn't robust enough. Isolation using processes is far too fiddly, slow, and opaque.

The better solution is required error handling. If user code must engage with the possibility of an error state in order to access a result, that's a huge step forward. They can do the error handling wrong, but if you make most people have to do it at all most of the problems will go away.

There are other good solutions too, like Erlang's process boundaries, but those tend to have drawbacks that make them inapplicable to a lot of programming areas. Tagged unions, on the other hand, are only inapplicable in C library code because of language defects and terrible community conventions around ABIs.

"I forgot to check for an error" should not be implicated in bugs in 2026; we have had a large menu of possible alternatives (checked exceptions, unchecked exceptions, tagged unions, etc.) for decades. But we're still over here writing C (and Go), languages pathologically incapable of providing this ability.

Substantive, firmly argued case for required error handling over panics, with concrete tradeoffs; strong language is aimed at languages, not people.
hedora 2026-11-05 18:087/10informativeinsightful | root | parent [–]

SSH specifically has separate processes per connection the last time I checked, so a panic does the right thing: The attacker's connection drops uncleanly.

This is also why you can upgrade SSH over SSH without having the connection drop mid-stream.

Again: You cannot avoid "I forgot to check for an error". What happens if you get a sigpipe on stdout when printing the "I crashed" line? If Linux overcommits memory? If you turn off overcommit, port all software to check for + cleanly handle NULL from malloc (a substantial effort!), but then get a machine check exception from a double bit ECC error during an infallible read?

SSH specifically has separate processes per connection the last time I checked, so a panic does the right thing: The attacker's connection drops uncleanly.

This is also why you can upgrade SSH over SSH without having the connection drop mid-stream.

Again: You cannot avoid "I forgot to check for an error". What happens if you get a sigpipe on stdout when printing the "I crashed" line? If Linux overcommits memory? If you turn off overcommit, port all software to check for + cleanly handle NULL from malloc (a substantial effort!), but then get a machine check exception from a double bit ECC error during an infallible read?

Substantive technical reply on SSH's per-connection processes and the limits of error checking.
zbentley 2026-11-05 18:217/10informativeinsightful | root | parent [–]

Sort of. It does some of the validation/auth code in the sshd parent daemon.

It has to: if it did 100% of the post-accept(2) work in a subprocess it would be pretty easy to remotely forkbomb/PID-exhaust a system by spamming invalid logins.

> You cannot avoid "I forgot to check for an error"

Yes you can. Checked exceptions and tagged unions both force calling code to be written with error-awareness.

In SSH's case, "I forgot to check" might lead to crashes (which might knock over sshd so new connections are impossible), or it might lead to false-positives via comparing NULL == NULL.

I'd much rather that boneheaded API users have to write code like

    resultstruct x = authenticate_user(user)
    if x.haserror {
        rv = { .user = user, .result = "successfully authenticated" } 
    } else {
        rv = { .user = user, .result = x.result }
    }
    return rv

At least that way some keystrokes are required to do the stupid thing. People still will (https://www.schneier.com/blog/archives/2008/05/random_number_b.html), but the harder we can make it, the better.

Sort of. It does some of the validation/auth code in the sshd parent daemon.

It has to: if it did 100% of the post-accept(2) work in a subprocess it would be pretty easy to remotely forkbomb/PID-exhaust a system by spamming invalid logins.

> You cannot avoid "I forgot to check for an error"

Yes you can. Checked exceptions and tagged unions both force calling code to be written with error-awareness.

In SSH's case, "I forgot to check" might lead to crashes (which might knock over sshd so new connections are impossible), or it might lead to false-positives via comparing NULL == NULL.

I'd much rather that boneheaded API users have to write code like

    resultstruct x = authenticate_user(user)
    if x.haserror {
        rv = { .user = user, .result = "successfully authenticated" } 
    } else {
        rv = { .user = user, .result = x.result }
    }
    return rv

At least that way some keystrokes are required to do the stupid thing. People still will (https://www.schneier.com/blog/archives/2008/05/random_number_b.html), but the harder we can make it, the better.

Substantive correction on sshd's process model and a concrete argument for forcing error handling through types; the bluntness is aimed at a coding pattern.
frank_beaver27 2026-11-06 04:17 | rewritten5/10bad_faith | original | root | parent | prev [–]

> is the real footgun

This contains a false snuck premise - that one and only one thing was the footgun. This is categorically not true. An obtuse, poorly-understood, purpose-built text-based DSL is absolutely a footgun and if it was replaced with a JSON and inactive accounts had been indicated by `"active": false` instead of a freaking asterisk then this would absolutely would not have happened.

> For historical reasons, we could’ve ended up JSON as “hash”: “*” just the same.

That's a vanishingly unlikely scenario.

People have brains that, factually, are far more likely to see a single context-less character "" and not realize the implications of it than to see `"hash": ""` and think "oh, that's normal".

This is just straight-up psychologically true.

> The /etc/shadow format lets you skip values completely, like “::”

Totally irrelevant.

> so your argument doesn’t hold.

False `::` is both (1) still harder to understand than `"active": false` and (2) even though the syntax existed it wasn't used here because everyone thought they understood the system.

This insane text syntax is, factually, harder to understand than a decently-written JSON format.

original comment by throw10920

> is the real footgun

This contains a false snuck premise - that one and only one thing was the footgun. This is categorically not true. An obtuse, poorly-understood, purpose-built text-based DSL is absolutely a footgun and if it was replaced with a JSON and inactive accounts had been indicated by `"active": false` instead of a freaking asterisk then this would absolutely would not have happened.

> For historical reasons, we could’ve ended up JSON as “hash”: “*” just the same.

That's dishonest, because it intentionally conceals the fact that that's a vanishingly unlikely scenario.

People have brains that, factually, are far more likely to see a single context-less character "" and not realize the implications of it than to see `"hash": ""` and think "oh, that's normal".

This is just straight-up psychologically true.

> The /etc/shadow format lets you skip values completely, like “::”

Totally irrelevant.

> so your argument doesn’t hold.

False `::` is both (1) still harder to understand than `"active": false` and (2) even though the syntax existed it wasn't used here because everyone thought they understood the system.

Don't make excuses for this insane text syntax. It is, factually, harder to understand than a decently-written JSON format.

Removed the accusation of dishonesty and the 'making excuses' swipe, keeping the argument that the format itself was a footgun.

At this point I’m resigning myself to think that we’ve just gotta wait for some people to die. It feels very dramatic and glib (no pun intended lol) and whatever to say, but at this point it feels like there’s a certain hostility in some parts to changing anything from the platonic ideals of however computing happened to work in 2004. “Oh we don’t need to mess with it!”

At this point I’m resigning myself to think that we’ve just gotta wait for some people to die. It feels very dramatic and glib (no pun intended lol) and whatever to say, but at this point it feels like there’s a certain hostility in some parts to changing anything from the platonic ideals of however computing happened to work in 2004. “Oh we don’t need to mess with it!”

A self-aware, cynical take on resistance to change in computing; it's a Planck-style generational quip rather than a real attack, so it stays.

How did you arrive at that conclusion? If anything, this vulnerability happened because someone decided to change the behaviour of crypt() without fully understanding the consequences. This specific vulnerability did not exist in 2004.

How did you arrive at that conclusion? If anything, this vulnerability happened because someone decided to change the behaviour of crypt() without fully understanding the consequences. This specific vulnerability did not exist in 2004.

Firm disagreement backed by a substantive point about the vulnerability's origin in a crypt() behaviour change.
timhh 2026-11-05 18:556/10first_handinformative | root | parent [–]

I agree with him. I only managed to get asterisks enabled by default in sudo because sudo-rs isn't owned by people who have decided that the 70s was the perfect decade and nothing should ever change. And look at the insane pushback when it did!

I can't even get an obvious UX improvement through PAM because they like it broken.

https://github.com/linux-pam/linux-pam/issues/778

I agree with him. I only managed to get asterisks enabled by default in sudo because sudo-rs isn't owned by people who have decided that the 70s was the perfect decade and nothing should ever change. And look at the insane pushback when it did!

I can't even get an obvious UX improvement through PAM because they like it broken.

https://github.com/linux-pam/linux-pam/issues/778

First-hand experience of pushing UX changes through sudo and PAM; its bluntness is aimed at maintainers' conservatism and stays within keep rules.

I think this is a bit unfair. The way I see it, a lot of people are saying that changing breaks lots of old stuff, and risks complete fragmentation. Those are real costs to change. It might still be worth it, but let's not pretend that everyone is resisting change just because it's change.

I think this is a bit unfair. The way I see it, a lot of people are saying that changing breaks lots of old stuff, and risks complete fragmentation. Those are real costs to change. It might still be worth it, but let's not pretend that everyone is resisting change just because it's change.

Civil pushback that names the real costs of change, breakage and fragmentation, without dismissing the parent.

The costs to change are dropping significantly with AI.

The costs to change are dropping significantly with AI.

A terse but genuine opinion on the cost of change.
gspr 2026-11-05 14:482/10low_effortquestion | root | parent [–]

Citation needed.

Citation needed.

A terse but genuine request for evidence, which the keep rules cover.


All I see is sausage makers advocating for a sausage-based diet.

All I see is sausage makers advocating for a sausage-based diet.

A wry but real point that the linked source has a vested interest in what it advocates.

> "it's all plain text" design that leans extremely heavily upon in-band signalling

Honestly I've been shaking my fist at these since 0 becomes True/Success in Bash and the opposite everywhere else

> "it's all plain text" design that leans extremely heavily upon in-band signalling

Honestly I've been shaking my fist at these since 0 becomes True/Success in Bash and the opposite everywhere else

A wry, on-topic gripe about shell conventions with no attack on anyone; kept verbatim.

Error code 0 means success in a lot more places than just bash.

Error code 0 means success in a lot more places than just bash.

Short, firm factual correction of the parent's claim.

That's not specifically bash though, that's process exit codes

That's not specifically bash though, that's process exit codes

Terse but accurate correction that the convention comes from process exit codes, not bash.

Yes. But it's still contradictory

Yes. But it's still contradictory

Short but plain, good-faith disagreement that concedes the parent's point; kept verbatim.

The question you are asking when querying a process exit code is "did this program exit with an error?". The default assumption is "no, it completed successfully", so 0 (false) is the logical expected default return.

A function is expected to produce a result - so the question is "what was the result?". !0 (something) is the logical expected default return.

I've never found it to be contradictory.

The question you are asking when querying a process exit code is "did this program exit with an error?". The default assumption is "no, it completed successfully", so 0 (false) is the logical expected default return.

A function is expected to produce a result - so the question is "what was the result?". !0 (something) is the logical expected default return.

I've never found it to be contradictory.

Civil, substantive disagreement that offers a clear framing of why exit codes and function returns differ.

I'd say another UNIX-y thing - "C is good enough" - is part of the problem. C makes programming painful which results in all sorts of hacks.

E.g. in Pascal you can can define a record and then defined file of record to have typed I/O. I.e. it comes with built-in serialization format supported by compiler. Not a good one - but perhaps a step in a good direction.

OTOH C people starving for serialization often believe that `struct` byte layout made by a specific version of a compiler is a serialization format - a view not endorsed by the standard.

I'd say another UNIX-y thing - "C is good enough" - is part of the problem. C makes programming painful which results in all sorts of hacks.

E.g. in Pascal you can can define a record and then defined file of record to have typed I/O. I.e. it comes with built-in serialization format supported by compiler. Not a good one - but perhaps a step in a good direction.

OTOH C people starving for serialization often believe that `struct` byte layout made by a specific version of a compiler is a serialization format - a view not endorsed by the standard.

Substantive point about C, Pascal typed files and struct layouts as serialization; firm opinion is fine.

Pascal's record typed files have the exact same problems regarding endianness and word size as directly writing structs to files in C, so I don't see how they're any better. They're also a complete deadend and unusable for pretty much any file format from the last 40 years+, since you can't even handle a header or magic bytes at the start.

Pascal's record typed files have the exact same problems regarding endianness and word size as directly writing structs to files in C, so I don't see how they're any better. They're also a complete deadend and unusable for pretty much any file format from the last 40 years+, since you can't even handle a header or magic bytes at the start.

Plain, substantive rebuttal explaining the portability and format limits of Pascal typed files.

It also looked to me a bit like a semipredicate problem:

https://en.wikipedia.org/wiki/Semipredicate_problem

It also looked to me a bit like a semipredicate problem:

https://en.wikipedia.org/wiki/Semipredicate_problem

Adds a relevant concept and link that names the underlying class of bug.

Even using "no_password" and "error_while_hashing" would have prevented it. The problem was picking a generic, meaningless signal string.

Even using "no_password" and "error_while_hashing" would have prevented it. The problem was picking a generic, meaningless signal string.

Concise, on-point argument that the flaw lay in choosing a generic sentinel value.
mort96 2026-11-05 15:157/10insightfulinformative | root | parent [–]

That would've happened to fix it, because "error_while_hashing" and "no_password" happen to not be valid password hashes. The right way would've been to have different syntax for hashes and non-hashes; you could've made it so that a hash starts with a '#' and if it doesn't start with a '#', the rest of the string should be ignored.

Or, for backwards compatibility, start non-hash strings with a character which can't appear in a hash, such as '@'. Your examples would then become '@no_password' and '@error_while_hashing'.

That would've happened to fix it, because "error_while_hashing" and "no_password" happen to not be valid password hashes. The right way would've been to have different syntax for hashes and non-hashes; you could've made it so that a hash starts with a '#' and if it doesn't start with a '#', the rest of the string should be ignored.

Or, for backwards compatibility, start non-hash strings with a character which can't appear in a hash, such as '@'. Your examples would then become '@no_password' and '@error_while_hashing'.

Substantive design critique explaining why in-band signal values should be structurally distinct from valid hashes.

Well neither * nor ! (the two characters I've seen to indicate "no password") will appear in a valid hash. The problem was using a character that could also be returned as an error indicator, IIUC.

Well neither * nor ! (the two characters I've seen to indicate "no password") will appear in a valid hash. The problem was using a character that could also be returned as an error indicator, IIUC.

Substantive technical correction about no-password markers and where the actual flaw lay.
mort96 2026-11-05 19:107/10insightfulinformative | root | parent [–]

Yeah but it's not forwards compatible with future hashes where '' is suddenly a legal hash, which is what happened here. You need a grammar which lets you tell the difference at the parsing stage between "this is a password hash" and "this account has no password". That's not the same as letting the value '' flow through the system as normal and hope it doesn't happen to match the hash.

Yeah but it's not forwards compatible with future hashes where '' is suddenly a legal hash, which is what happened here. You need a grammar which lets you tell the difference at the parsing stage between "this is a password hash" and "this account has no password". That's not the same as letting the value '' flow through the system as normal and hope it doesn't happen to match the hash.

Substantive point about in-band sentinels and needing the grammar to separate 'no password' from a hash at parse time.

True, but if they would have picked ?, or #, or any character other than * with its designated special meaning in the /etc/shadow file, then we would not be in this pickle.

True, but if they would have picked ?, or #, or any character other than * with its designated special meaning in the /etc/shadow file, then we would not be in this pickle.

Civil, on-topic point that the choice of '*' collided with its existing special meaning in /etc/shadow.

hashes can't start with * and there's no reason to think they ever will

hashes can't start with * and there's no reason to think they ever will

Terse but firm technical disagreement with the parent, with no personal swipe.

Technically they did use a character which can't appear in a hash, '*'.

Technically they did use a character which can't appear in a hash, '*'.

Short, factual correction noting that '*' already can't appear in a hash.
mort96 2026-11-05 18:368/10insightfulinformative | root | parent [–]

Right but not in a "rigorous" way. They kept the format of the file the same: the string between the colon characters is a hash. They exploited the fact that an asterisk happens to never appear in a hash, and just let the system believe the asterisk was supposed to be a hash to be matched again.

In a sense, they should've changed the parsing logic to return a struct with a field 'password_hash: Option<String>', and defined the grammar of the field to be: "a hash string, or '' for no login". The bug is, in part, caused by the haphazard way they don't change any formal grammar or contract and just relied on the fact that '' happens to never be a valid hash.

It's the difference between "happens to work" and "defined to work by construction". The former approach breaks once a hash function is invented where '' is a legal hash (such as this new musl crypt() hash function where all strings longer than 256 bytes hash to ''). The latter keeps working regardless because the asterisk is treated as "no password" already at the parser stage.

Right but not in a "rigorous" way. They kept the format of the file the same: the string between the colon characters is a hash. They exploited the fact that an asterisk happens to never appear in a hash, and just let the system believe the asterisk was supposed to be a hash to be matched again.

In a sense, they should've changed the parsing logic to return a struct with a field 'password_hash: Option<String>', and defined the grammar of the field to be: "a hash string, or '' for no login". The bug is, in part, caused by the haphazard way they don't change any formal grammar or contract and just relied on the fact that '' happens to never be a valid hash.

It's the difference between "happens to work" and "defined to work by construction". The former approach breaks once a hash function is invented where '' is a legal hash (such as this new musl crypt() hash function where all strings longer than 256 bytes hash to ''). The latter keeps working regardless because the asterisk is treated as "no password" already at the parser stage.

Substantive explanation of how an implicit sentinel value differs from a grammar-level design, with a concrete failure case.

[deleted]
amluto 2026-11-05 15:479/10insightfulinformative | prev | next [–]

The whole story is a bit worse than this. Musl already worked around this type of failure. See the second-to-last line here:

https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_des.c

(hey, this workaround is wrong if the salt is "x"!)

but they forgot to work around it here:

https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_sha512.c#n369

The vulnerable path in musl is only a little bit vulnerable, because "" is not actually a sha256 encrypted configuration and should not reach this code: musl's crypt(..., "") will not call that code! Turns out that sshd detects that the password hash is too short and does not forward the salt!

https://github.com/openssh/openssh-portable/blob/master/auth-passwd.c#L223

Look, no else statement! If that if doesn't get taken, then salt remains NULL. And the sshd code is really quite messy and doesn't keep track of the fact that this code path is inappropriate for validating an existing password hash.

A workaround in the sha256 path in musl wouldn't even have prevented the bug!

I'm going to give some credit to musl for unwisely using "*" and some credit to OpenSSH for having nonsensical control and data flow.

The whole story is a bit worse than this. Musl already worked around this type of failure. See the second-to-last line here:

https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_des.c

(hey, this workaround is wrong if the salt is "x"!)

but they forgot to work around it here:

https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_sha512.c#n369

The vulnerable path in musl is only a little bit vulnerable, because "" is not actually a sha256 encrypted configuration and should not reach this code: musl's crypt(..., "") will not call that code! Turns out that sshd detects that the password hash is too short and does not forward the salt!

https://github.com/openssh/openssh-portable/blob/master/auth-passwd.c#L223

Look, no else statement! If that if doesn't get taken, then salt remains NULL. And the sshd code is really quite messy and doesn't keep track of the fact that this code path is inappropriate for validating an existing password hash.

A workaround in the sha256 path in musl wouldn't even have prevented the bug!

I'm going to give some credit to musl for unwisely using "*" and some credit to OpenSSH for having nonsensical control and data flow.

Detailed code-level analysis of the musl and OpenSSH paths behind the bug, with direct source links.
tchebb 2026-11-05 19:089/10insightfulinformative | parent | next [–]

> (hey, this workaround is wrong if the salt is "x"!)

When the salt is "x", it'll return "*" and still not match, won't it? The code is correct as long as the second argument is the hash that's being checked. And like you say, the sha512 path isn't relevant since you only get there if the salt started with "$".

> Turns out that sshd detects that the password hash is too short and does not forward the salt!

I think you're misunderstanding the intent of that check. It was added by the 2016 change linked in the writeup[1] and causes OpenSSH's xcrypt() to select and use a salt from a different line of /etc/shadow to prevent timing attacks. It is correct for a crypt() implementation that returns NULL on error.

[1] https://lists.mindrot.org/pipermail/openssh-commits/2016-July/005428.html

> (hey, this workaround is wrong if the salt is "x"!)

When the salt is "x", it'll return "*" and still not match, won't it? The code is correct as long as the second argument is the hash that's being checked. And like you say, the sha512 path isn't relevant since you only get there if the salt started with "$".

> Turns out that sshd detects that the password hash is too short and does not forward the salt!

I think you're misunderstanding the intent of that check. It was added by the 2016 change linked in the writeup[1] and causes OpenSSH's xcrypt() to select and use a salt from a different line of /etc/shadow to prevent timing attacks. It is correct for a crypt() implementation that returns NULL on error.

[1] https://lists.mindrot.org/pipermail/openssh-commits/2016-July/005428.html

Technically precise correction of the parent with a linked commit, and the disagreement is civil.
amluto 2026-11-05 19:388/10insightfulinformative | root | parent [–]

> When the salt is "x", it'll return "*" and still not match, won't it? The code is correct as long as the second argument is the hash that's being checked. And like you say, the sha512 path isn't relevant since you only get there if the salt started with "$".

I think you're right. That's what I get for doing a very very quick read.

> I think you're misunderstanding the intent of that check. It was added by the 2016 change linked in the writeup[1] and causes OpenSSH's xcrypt() to select and use a salt from a different line of /etc/shadow to prevent timing attacks. It is correct for a crypt() implementation that returns NULL on error.

I don't care what the intent of the check is. If you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in! Don't deliberately pass nonsense into the live check and rely on the nonsense check failing to prevent the fake check from accidentally working. And don't have a code path that thinks it's checking the real password but passes in the wrong second parameter to crypt().

Or, better yet, have an entirely separate function fake_check_password() that does the fake check, returns void, and cannot possibly be confused with the actual password check code.

> When the salt is "x", it'll return "*" and still not match, won't it? The code is correct as long as the second argument is the hash that's being checked. And like you say, the sha512 path isn't relevant since you only get there if the salt started with "$".

I think you're right. That's what I get for doing a very very quick read.

> I think you're misunderstanding the intent of that check. It was added by the 2016 change linked in the writeup[1] and causes OpenSSH's xcrypt() to select and use a salt from a different line of /etc/shadow to prevent timing attacks. It is correct for a crypt() implementation that returns NULL on error.

I don't care what the intent of the check is. If you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in! Don't deliberately pass nonsense into the live check and rely on the nonsense check failing to prevent the fake check from accidentally working. And don't have a code path that thinks it's checking the real password but passes in the wrong second parameter to crypt().

Or, better yet, have an entirely separate function fake_check_password() that does the fake check, returns void, and cannot possibly be confused with the actual password check code.

Concedes an error and makes a firm, substantive design argument for keeping fake timing checks separate from real authentication.

> If you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in!

I think this is the crux. Instead of doing the right thing, they tried to codegolf to get the desired behavior and it got real subtle, real fast*.

* this is a little flip: maintaining a security-critical program naturally pushes one to minimize code changes because a 30-line change is usually easier to review than a 100-line change. Except when it's not.

> If you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in!

I think this is the crux. Instead of doing the right thing, they tried to codegolf to get the desired behavior and it got real subtle, real fast*.

* this is a little flip: maintaining a security-critical program naturally pushes one to minimize code changes because a 30-line change is usually easier to review than a 100-line change. Except when it's not.

Substantive point about how minimizing code changes in security-critical software can create subtle bugs; criticism is aimed at the code, not a person.

> you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in

That would be a much better approach. I see your point now.

> you're doing a fake password hash to obscure timing, remember that it's fake and don't let the user log in

That would be a much better approach. I see your point now.

A good-faith concession that endorses the better design for handling fake password hashes.
NewJazz 2026-11-05 15:54 | removed0/10metatangential_annoyancelow_effort | parent | prev [–]

The formatting in your comment got messed btw

The formatting in your comment got messed btw

A note about comment formatting adds nothing to the discussion.
amluto 2026-11-05 16:12 | removed0/10low_effortmeta | root | parent [–]

Thanks, fixed

Thanks, fixed

Only acknowledges a formatting fix and adds nothing for readers.

Above all, I’m a little sad that the musl developers decided to make a lot of “well it’s convenient” changes to core crypto functions — at least without the level of auditing that would have caught this! It’s tarnished their reputation in my eyes a little.

Above all, I’m a little sad that the musl developers decided to make a lot of “well it’s convenient” changes to core crypto functions — at least without the level of auditing that would have caught this! It’s tarnished their reputation in my eyes a little.

Opinionated criticism of musl's crypto changes, aimed at decisions rather than people, with no swipe to remove.

I think everyone is over fixated on AI finding complex bugs beyond a typical person's understanding either to locate or exploit.

I think for any one of those bugs there might simply be half a dozen footguns or bugs that once it's spelled out you wonder how anyone let it slide.

I think that's the case where LLMs might indentify most.

It's challenging though as the current AI audits produce so many false positives it's hard to know when it's being pedantic or when it's genuinely caught a human being a bone head

I think everyone is over fixated on AI finding complex bugs beyond a typical person's understanding either to locate or exploit.

I think for any one of those bugs there might simply be half a dozen footguns or bugs that once it's spelled out you wonder how anyone let it slide.

I think that's the case where LLMs might indentify most.

It's challenging though as the current AI audits produce so many false positives it's hard to know when it's being pedantic or when it's genuinely caught a human being a bone head

Substantive point that LLM audits may best catch overlooked simple bugs, with a caveat about false positives; 'bone head' is mild and generic.

Finding more and more that these complex bugs people gloss over like you are now are being used in exploit chains and the end result is still my clients getting hacked

Finding more and more that these complex bugs people gloss over like you are now are being used in exploit chains and the end result is still my clients getting hacked

Firm disagreement backed by first-hand client experience of exploit chains; the directness is not a real attack.
svnt 2026-11-05 15:345/10informative | root | parent | prev [–]

This one might be hard to find using the author’s approach of throwing everything at various builds of ssh.

It is not really that hard to find in the wild if you had a device built this way, when overflowing buffers on embedded devices is a standard attack vector.

It might be hard to root cause, but it would not be that hard to find.

This one might be hard to find using the author’s approach of throwing everything at various builds of ssh.

It is not really that hard to find in the wild if you had a device built this way, when overflowing buffers on embedded devices is a standard attack vector.

It might be hard to root cause, but it would not be that hard to find.

Substantive point distinguishing finding the bug from root-causing it, noting buffer overflows are a standard embedded attack vector.

Everything in this industry is about convenience.

Everything in this industry is about convenience.

Terse, cynical remark about the industry that stays on the parent's point and is not aimed at anyone.

Honestly the ideas around musl are interesting, especially being able to statically link, but given the sort of nonsense around how they handled pthreads, and generally other trade offs, it's hard to imagine wanting to use it over glibc (which everyone shits on) if you don't absolutely need static linking.

Honestly the ideas around musl are interesting, especially being able to statically link, but given the sort of nonsense around how they handled pthreads, and generally other trade offs, it's hard to imagine wanting to use it over glibc (which everyone shits on) if you don't absolutely need static linking.

Blunt technical opinion on musl versus glibc trade-offs, aimed at design decisions rather than people.

Musl has a history of introducing (and not fixing) major bugs in their library that aren't in glibc; I would not depend on it for production

Musl has a history of introducing (and not fixing) major bugs in their library that aren't in glibc; I would not depend on it for production

Firm criticism of a project with a practical recommendation; it is unsourced but stays within bounds.
trebligdivad 2026-11-05 13:157/10insightfulinformative | parent | prev | next [–]

In many ways each individual decision makes some sense; wanting to stop loads of programs being affected by a (remotely triggerable?) crash wanting to avoid time based attacks wanting to be able to set accounts to not have a password

Each one relies on a perfectly valid argument - that * can't match because we all know it'll be rejected by the other thing.

In many ways each individual decision makes some sense; wanting to stop loads of programs being affected by a (remotely triggerable?) crash wanting to avoid time based attacks wanting to be able to set accounts to not have a password

Each one relies on a perfectly valid argument - that * can't match because we all know it'll be rejected by the other thing.

Substantive analysis of how individually reasonable decisions combined into the flaw.

I would be more concerned about the lack of explicit return types, one of the arguments for strongly typed languages is that you should not have ambiguous data and yet…

Granted I mostly work with high level programming languages so I may be missing something.

I would be more concerned about the lack of explicit return types, one of the arguments for strongly typed languages is that you should not have ambiguous data and yet…

Granted I mostly work with high level programming languages so I may be missing something.

Good-faith observation about ambiguous return values in C, with an honest admission of limited low-level experience.

Yes this is C, C is not strongly typed at all and the type system is very primitive. NULL ptr is the return value you should probably use if something goes wrong. But you can’t, say, return a sum type with the result and an error. It’s not uncommon at all to use magic sentinel values in C to indicate error conditions.

Yes this is C, C is not strongly typed at all and the type system is very primitive. NULL ptr is the return value you should probably use if something goes wrong. But you can’t, say, return a sum type with the result and an error. It’s not uncommon at all to use magic sentinel values in C to indicate error conditions.

Substantive explanation of C's weak type system and the use of sentinel values for errors.

> Each one relies on a perfectly valid argument - that * can't match because we all know it'll be rejected by the other thing.

But that's not a perfectly valid argument, and it never was.

> Each one relies on a perfectly valid argument - that * can't match because we all know it'll be rejected by the other thing.

But that's not a perfectly valid argument, and it never was.

Plain, firm disagreement with the parent's reasoning, which is kept as is even though it is terse.

I guarantee that every single developer of musl in 2012 had at least account on their machine with "*" as the password hash. Also crypt() is a standardized function and the standard states that a null pointer is returned on error.

That being said, OpenSSH did mismanage their attempts at handling a garbage return-value from crypt(), so there's definitely some blame to go around.

I guarantee that every single developer of musl in 2012 had at least account on their machine with "*" as the password hash. Also crypt() is a standardized function and the standard states that a null pointer is returned on error.

That being said, OpenSSH did mismanage their attempts at handling a garbage return-value from crypt(), so there's definitely some blame to go around.

Substantive point citing the crypt() standard and apportioning blame between musl and OpenSSH.

If they had just all used a different garbage symbol would this have been avoided?

If they had just all used a different garbage symbol would this have been avoided?

Genuine, on-topic short question about whether distinct sentinel values would have prevented the bug.

The real problem here is the C language. Our industry should have dumped it a long time ago and we’re now paying the price of prevarication.

The real problem here is the C language. Our industry should have dumped it a long time ago and we’re now paying the price of prevarication.

A firm opinion about C aimed at a language, not a person; kept, though it is a generic tangent with little detail.

Which of the several flaws in this bug chain would have been materially different with a language other than C?

Which of the several flaws in this bug chain would have been materially different with a language other than C?

A genuine, on-point question challenging the parent's claim by asking which specific flaws the language choice would have changed.
nh2 2026-11-05 13:477/10informativeinsightful | root | parent | next [–]

Any use of sum types would have prevented this immediately.

The whole thing exists because APIs that return a NULL value that "nobody checks", plus a string that you shouldn't use if you actually did check the null value.

In languages with sum types, this cannot happen.

Either you get your hash, or no result at all. You couldn't get eg NULL and *.

Any use of sum types would have prevented this immediately.

The whole thing exists because APIs that return a NULL value that "nobody checks", plus a string that you shouldn't use if you actually did check the null value.

In languages with sum types, this cannot happen.

Either you get your hash, or no result at all. You couldn't get eg NULL and *.

Directly answers the question with a specific type-system explanation of how the bug would be prevented.

C has unions. C has sum types. People have been doing tagged unions in C for longer than rust has been a language

C has unions. C has sum types. People have been doing tagged unions in C for longer than rust has been a language

Firm disagreement with a real point that tagged unions are available in C.

It is extremely unidiomatic to use tagged unions in C for error handling. It's definitely possible, but awkward, and the compiler doesn't check that you checked the tag.

It is extremely unidiomatic to use tagged unions in C for error handling. It's definitely possible, but awkward, and the compiler doesn't check that you checked the tag.

Civil, substantive disagreement explaining why tagged unions fall short for C error handling.

I don't think I've ever seen a C function that uses unions for this purpose though?

I can think of two common patterns that I've seen frequently:

* Return NULL for errors (sometimes with a global errorno() function).

* Return an integer error with any return values as out parameters.

So if it is possible to improve, why doesn't anyone do it?

I don't think I've ever seen a C function that uses unions for this purpose though?

I can think of two common patterns that I've seen frequently:

* Return NULL for errors (sometimes with a global errorno() function).

* Return an integer error with any return values as out parameters.

So if it is possible to improve, why doesn't anyone do it?

Good-faith pushback describing the common C error-handling patterns and asking why tagged unions aren't used.

Yet the authors of crypt did not use them. The C language is not designed in a way to encourage people to write code that correctly uses tagged unions (or nullable pointers for that matter).

Yet the authors of crypt did not use them. The C language is not designed in a way to encourage people to write code that correctly uses tagged unions (or nullable pointers for that matter).

Substantive point that language design shapes whether tagged unions actually get used correctly.
fern_plover38 2026-11-05 22:19 | rewritten8/10informativeinsightful | original | root | parent | next [–]

because POSIX dictates the shape of crypt from a function written in 1973 and unions/structs were only introduced to the C language in 1976.

the fault for sshhole isn't just "C bad", it's a series of poor choices in workarounds falling together to create a vulnerability.

faulting decisions made 50 years ago, before any of the better languages even existed, is silly.

if you want to lay blame, lay it at the feet of more recent devs that started returning values instead of NULL because people were using crypt incorrectly. likely sha512 returned a fake value instead of a real one because prior hashes couldn't fail. the interface should have been updated then, but instead they worked around it.

original comment by knome

because POSIX dictates the shape of crypt from a function written in 1973 and unions/structs were only introduced to the C language in 1976.

the fault for sshhole isn't just "C bad", it's a series of poor choices in workarounds falling together to create a vulnerability.

being condescending about decisions made 50 years ago, before any of the better languages you're mad the developer didn't use even existed, is silly.

if you want to lay blame, lay it at the feet of more recent devs that started returning values instead of NULL because people were using crypt incorrectly. likely sha512 returned a fake value instead of a real one because prior hashes couldn't fail. the interface should have been updated then, but instead they worked around it.

Valuable history of crypt's interface; removed only the accusation that the parent was condescending and mad.
steady_jackal26 2026-11-05 15:02 | rewritten2/10snarklow_effortkept_for_replies | original | root | parent | prev [–]

It seems like the goal posts are moving here. Where are you taking this argument?

original comment by jppittma

Where are you going with those goal posts?

Sarcastic goal-post jab restated as a plain objection so the substantive reply still makes sense.

Nowhere. It's the same argument as upthread. A language that forces downstream consumers of an API to correctly deal with all possible outcomes would have prevented this.

Nowhere. It's the same argument as upthread. A language that forces downstream consumers of an API to correctly deal with all possible outcomes would have prevented this.

A firm, on-topic restatement of the argument that exhaustive outcome handling in the language would have prevented this.

Goalposts are fine. If crypt did return union type, C still allows cowboy programmers to straight extract the char* without any check whether it's valid. So we'd end up in exactly the same situation.

Goalposts are fine. If crypt did return union type, C still allows cowboy programmers to straight extract the char* without any check whether it's valid. So we'd end up in exactly the same situation.

Firm but substantive point that C's union types wouldn't enforce checking; "cowboy programmers" is mild and generic.

I don't think the goal posts were moved. The comment you replied to referred to the "use"--not the existence--of sum types.

I don't think the goal posts were moved. The comment you replied to referred to the "use"--not the existence--of sum types.

Politely clarifies the disputed point by distinguishing the use of sum types from their existence.
xorcist 2026-11-05 17:477/10informativeinsightful | root | parent | prev | next [–]

This argument is weak. C, and specifically libc, has errno. The error code is there. Not in band.

But there has to be something in the payload field when it is not applicable. A null is perfectly fine.

The argument for changing that to "*", if I understand the above linked discussion correctly, was that some users of this api threw away errors and a dummy value would be harder to ignore. Maybe one could argue that these behaviours are less common in Rust or Go, but not because of a technical difference.

The world is not perfect and sometimes you really need to work around bad users. Even in Rust or Go. I am not convinced this was such a case. If you are convinced it was it would still have been better to select a less trivial dummy value (such as ":ERROR:").

This argument is weak. C, and specifically libc, has errno. The error code is there. Not in band.

But there has to be something in the payload field when it is not applicable. A null is perfectly fine.

The argument for changing that to "*", if I understand the above linked discussion correctly, was that some users of this api threw away errors and a dummy value would be harder to ignore. Maybe one could argue that these behaviours are less common in Rust or Go, but not because of a technical difference.

The world is not perfect and sometimes you really need to work around bad users. Even in Rust or Go. I am not convinced this was such a case. If you are convinced it was it would still have been better to select a less trivial dummy value (such as ":ERROR:").

Firm, substantive disagreement about errno, dummy values and API misuse; "this argument is weak" targets the argument, not the person.

> But there has to be something in the payload field when it is not applicable

Not in languages with ergonomic sum types. A `Result` or `Either` will have no payload field on the error type. The compiler won't let you dereference it.

> But there has to be something in the payload field when it is not applicable

Not in languages with ergonomic sum types. A `Result` or `Either` will have no payload field on the error type. The compiler won't let you dereference it.

Clear technical rebuttal explaining how sum types eliminate the payload field on errors.

This is wrong. errno is not to be used to detect errors, only to get the cause of the error once an error has been detected.

This is wrong. errno is not to be used to detect errors, only to get the cause of the error once an error has been detected.

Firm, direct correction with a real technical point about how errno is meant to be used.

[deleted]
dolmen 2026-11-05 15:067/10insightfulinformative | root | parent | prev | next [–]

Nope.

Because in fact all the changes were about changing the function signatures. The function signature is enforced by the linker. The semantic of strings that go through isn't.

Returning 2 values is possible in C (via pointers input parameters). The main issue here is that libc is a strong API contract based on C function signatures, that nobody can change, only augment with new symbols. Such strong API compatibility constraints happen in any programming language, and workaround to fit new behaviours in old APIs happens everywhere.

Nope.

Because in fact all the changes were about changing the function signatures. The function signature is enforced by the linker. The semantic of strings that go through isn't.

Returning 2 values is possible in C (via pointers input parameters). The main issue here is that libc is a strong API contract based on C function signatures, that nobody can change, only augment with new symbols. Such strong API compatibility constraints happen in any programming language, and workaround to fit new behaviours in old APIs happens everywhere.

Firm, substantive disagreement explaining how ABI and libc compatibility constraints, not type systems alone, drive the problem.

I don't think nh2 is saying that this is how the "issue" should've been solved here, but that it's a fatal flaw of C itself (yes I know, unions, but no one uses them).

But also, its not like there were many alternative languages when the POSIX standards were designed so it's fair that it works like this.

I don't think nh2 is saying that this is how the "issue" should've been solved here, but that it's a fatal flaw of C itself (yes I know, unions, but no one uses them).

But also, its not like there were many alternative languages when the POSIX standards were designed so it's fair that it works like this.

Civil disagreement that clarifies another commenter's argument and adds historical context.

Even C has a better type system than C: All modern C compilers support an extension that lets you flag a function's return-value as requiring a null check.

Even C has a better type system than C: All modern C compilers support an extension that lets you flag a function's return-value as requiring a null check.

Wry but substantive point that C compilers offer an extension to enforce checking return values.
muvlon 2026-11-05 13:528/10informativeinsightful | root | parent | prev | next [–]

C's lack of all of

- sum/union types

- multiple return values/tuple types

- exceptions

means that most ways of returning an error from a function are not available in C. Because of this, many C APIs, including `crypt` in this case, resort to in-band error signalling by using a sentinel value (like NULL, a negative integer, "*" etc.).

Most other languages lack one or two of the above, but very few lack all three, and so most have developed a different culture around error reporting (Result in Rust, multiple return values in Go, exceptions in Java, Python etc.).

C's lack of all of

- sum/union types

- multiple return values/tuple types

- exceptions

means that most ways of returning an error from a function are not available in C. Because of this, many C APIs, including `crypt` in this case, resort to in-band error signalling by using a sentinel value (like NULL, a negative integer, "*" etc.).

Most other languages lack one or two of the above, but very few lack all three, and so most have developed a different culture around error reporting (Result in Rust, multiple return values in Go, exceptions in Java, Python etc.).

Clearly explains how C's missing language features lead to in-band error sentinels like the one in crypt.
tialaramex 2026-11-05 14:458/10insightfulinformative | root | parent | next [–]

One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

And really often when your "in-band error signalling" in Rust is more expensive you realise (perhaps to your horror) that your C wouldn't be correct, so you just dodged a bullet. For example Option<OwnedFd> is, like OwnedFd, a 32-bit value, Unix has always reserved -1 to mean invalid, so Rust can treat that as a niche. But the Windows equivalent Option<OwnedHandle> can't do this because, well, cue Raymond Chen: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443/

One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

And really often when your "in-band error signalling" in Rust is more expensive you realise (perhaps to your horror) that your C wouldn't be correct, so you just dodged a bullet. For example Option<OwnedFd> is, like OwnedFd, a 32-bit value, Unix has always reserved -1 to mean invalid, so Rust can treat that as a niche. But the Windows equivalent Option<OwnedHandle> can't do this because, well, cue Raymond Chen: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443/

Teaches how Rust niche optimisation matches C's in-band signalling and exposes real pitfalls, with a concrete fd/handle example.

> One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

But aside from NonZero- and NonNull-based layout optimizations, I don't think Rust is doing anything special to obtain that machine code - it's just relying on LLVM to optimize things.

> One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

But aside from NonZero- and NonNull-based layout optimizations, I don't think Rust is doing anything special to obtain that machine code - it's just relying on LLVM to optimize things.

Civil, substantive technical disagreement about where Rust's codegen advantage comes from.

The first 2 it has, and the 3rd, you really don't want.

The first 2 it has, and the 3rd, you really don't want.

Terse but genuine disagreement with the parent's claim about C's features.

The first 2 C has in the same way that I don't need Dropbox if I just have an FTP account and curlftpfs (https://news.ycombinator.com/item?id=9224).

The first 2 C has in the same way that I don't need Dropbox if I just have an FTP account and curlftpfs (https://news.ycombinator.com/item?id=9224).

Wry analogy that makes a real point: having a workaround in C isn't the same as first-class support.

[deleted]
sscaryterry 2026-11-05 16:41 | removed0/10low_effortcurmudgeonly | root | parent | prev [–]

Your analogy is way off...

Your analogy is way off...

Bare dismissal of the analogy with no explanation of why it fails, so there is no substance to preserve.

C has sum types/unions. You don't need multiple return. You can use a struct and it's the same thing.

original comment by jppittma

C has sum types/unions? What are you talking about? You don't need multiple return. You can use a struct and it's the same thing.

Dropped the incredulous 'What are you talking about?' while keeping the disagreement about C's features.

not in a way that forces the user to actually handle them properly. C is a weakly typed language and as such is pretty badly suited to systems programming.

not in a way that forces the user to actually handle them properly. C is a weakly typed language and as such is pretty badly suited to systems programming.

Firm technical disagreement with a real point about C's unions not enforcing correct handling.

C is not a weakly typed language, this is just nonsense. There is also [[nodiscard]] which you can use to make sure a return value is handled.

C is not a weakly typed language, this is just nonsense. There is also [[nodiscard]] which you can use to make sure a return value is handled.

Firm disagreement aimed at the claim, not the person, with a concrete pointer to [[nodiscard]].
muvlon 2026-11-05 16:517/10informativeinsightfulquestion | root | parent [–]

What is a weakly typed language to you? To me, implicit type conversions are a big part of the definition, and C is flush with those (void* converts to any pointer, all kinds of number conversions, arrays decay to pointers, functions convert to pointers, lvalue conversion strips const/volatile etc.).

However, I also think "weakly typed" is not a well-defined concept, everyone seems to apply it differently. In particular, it seems to mostly mean "playing it loose with types in a way that I don't like".

What is a weakly typed language to you? To me, implicit type conversions are a big part of the definition, and C is flush with those (void* converts to any pointer, all kinds of number conversions, arrays decay to pointers, functions convert to pointers, lvalue conversion strips const/volatile etc.).

However, I also think "weakly typed" is not a well-defined concept, everyone seems to apply it differently. In particular, it seems to mostly mean "playing it loose with types in a way that I don't like".

Substantive, good-faith disagreement listing C's implicit conversions and questioning how well-defined 'weakly typed' is.

Yes, it is not well-defined. But most conversions, function calls, and accesses are strongly typed in C, so I would not call it weakly typed.

Implicit conversion could be seen as weak typing, but it is mostly limited to specific arithmetic operations in C. I would see implicit conversions as problematic if they allowed operations that make no sense. Decay of arrays and function to pointer conversion also do not seem problematic to me for this reason.

Void pointers are also weakly typed, but they are specifically designed for this. So if you do not want this behavior, one would simply not use them. It is a bit like using "unsafe" in Rust.

There are certainly stronger typed languages, but on the full range C is more on the strongly than weakly typed side in my opinion. A language that allows arbitrary implicit conversions between pointers, would be weakly typed, or which allows calling functions with wrongly typed arguments (which was true in the past for C a long time ago)

Yes, it is not well-defined. But most conversions, function calls, and accesses are strongly typed in C, so I would not call it weakly typed.

Implicit conversion could be seen as weak typing, but it is mostly limited to specific arithmetic operations in C. I would see implicit conversions as problematic if they allowed operations that make no sense. Decay of arrays and function to pointer conversion also do not seem problematic to me for this reason.

Void pointers are also weakly typed, but they are specifically designed for this. So if you do not want this behavior, one would simply not use them. It is a bit like using "unsafe" in Rust.

There are certainly stronger typed languages, but on the full range C is more on the strongly than weakly typed side in my opinion. A language that allows arbitrary implicit conversions between pointers, would be weakly typed, or which allows calling functions with wrongly typed arguments (which was true in the past for C a long time ago)

Substantive, good-faith disagreement on whether C is weakly typed, with concrete reasoning.
adgjlsfhk1 2026-11-05 20:097/10informativeinsightful | root | parent [–]

C is definitely not the most weakly typed (e.g. Javascript/PHP are both weaker), but IMO C is weaker than python in that python will give you a runtime type error if you do something invalid, while in C you cast something and just get UB. Most higher level strongly typed languages are able to make their casts checked because they have generics to remove the need for 99% of them, but C is missing those features and thus has to rely much more heavily on unchecked and unsafe runtime casts.

C is definitely not the most weakly typed (e.g. Javascript/PHP are both weaker), but IMO C is weaker than python in that python will give you a runtime type error if you do something invalid, while in C you cast something and just get UB. Most higher level strongly typed languages are able to make their casts checked because they have generics to remove the need for 99% of them, but C is missing those features and thus has to rely much more heavily on unchecked and unsafe runtime casts.

Substantive comparison of C's type system with Python and generics-based languages, with firm but civil disagreement.

Casts are escape hatches just like "unsafe" in Rust. You can write safe generics in C that either do not use casts or wrap all unsafe operations behind type-safe interfaces.

Casts are escape hatches just like "unsafe" in Rust. You can write safe generics in C that either do not use casts or wrap all unsafe operations behind type-safe interfaces.

Substantive, good-faith disagreement arguing that C casts are escape hatches and safe generics are possible.
muvlon 2026-11-05 15:588/10insightfulinformative | root | parent | prev [–]

C unions are sum types if you just "draw the rest of the fucking owl". C has sum types the way x86 assembly has function calls. You see, there are "call" and "ret" instructions. Just figure out calling convention and the stack yourself, it's easy. And yes I can stick the tag next to the union myself in C and write a bunch of functions that hopefully deal with it correctly. I can even write some macros that look like pattern matching and maybe convince myself that it's a language feature, but it just isn't.

And it's very evident why the difference matters. People gravitate to what's readily available and ergonomic. That's why every C API under the sun (most definitely including libc itself) does janky stuff like returning -1 or "*" on error instead of building their own bespoke Result-style abstraction. A programming language is not just the things that are in some way possible within it, but more importantly the things that are easy in it.

C unions are sum types if you just "draw the rest of the fucking owl". C has sum types the way x86 assembly has function calls. You see, there are "call" and "ret" instructions. Just figure out calling convention and the stack yourself, it's easy. And yes I can stick the tag next to the union myself in C and write a bunch of functions that hopefully deal with it correctly. I can even write some macros that look like pattern matching and maybe convince myself that it's a language feature, but it just isn't.

And it's very evident why the difference matters. People gravitate to what's readily available and ergonomic. That's why every C API under the sun (most definitely including libc itself) does janky stuff like returning -1 or "*" on error instead of building their own bespoke Result-style abstraction. A programming language is not just the things that are in some way possible within it, but more importantly the things that are easy in it.

Vivid, firm argument that language ergonomics matter, with profanity that is an idiom rather than aimed at a person.

While I agree that this should come out of the box, the "rest of the fucking owl" is a tiny macro or function, so not exactly something difficult to do complex as "drawing an owl" would imply.

While I agree that this should come out of the box, the "rest of the fucking owl" is a tiny macro or function, so not exactly something difficult to do complex as "drawing an owl" would imply.

Civil, substantive disagreement that the missing sum type support is only a small macro or function away.
dvdkon 2026-11-05 18:156/10first_handinsightful | root | parent | next [–]

Sure, when I last wrote a sizeable chunk of C, I wrote those macros and with a bit of help from new-ish compiler warnings, I got a reasonable error handling system in C. But whenever I interacted with anyone else's code, they apparently didn't get the memo that this is possible and desirable.

A language should encourage writing robust/secure code, not just make it possible.

Sure, when I last wrote a sizeable chunk of C, I wrote those macros and with a bit of help from new-ish compiler warnings, I got a reasonable error handling system in C. But whenever I interacted with anyone else's code, they apparently didn't get the memo that this is possible and desirable.

A language should encourage writing robust/secure code, not just make it possible.

Substantive first-hand point that languages should encourage robust code, with only mild wryness aimed at no one in particular.

While I see the point, I am not entirely sure I agree. I kind of like that C is not an opinionated language which tells you exactly how to write things. This comes at the cost that many people will do things in a bad way, and this is difficult to fix, but still probably easier than getting them to move to a different language.

In any case, I do not think the issue here is caused by not having a pre-defined result type. Checking for null would have been fine. The issue was that to accommodate old and broken code a compromise was made. But as long such compromises are done even where fixing such old code would be quite easy, it is clear that changing to another language is completely unrealistic. So what are we even talking about? The real issue is that nobody wants to spend money or effort maintaining the old code. No language can fix this.

While I see the point, I am not entirely sure I agree. I kind of like that C is not an opinionated language which tells you exactly how to write things. This comes at the cost that many people will do things in a bad way, and this is difficult to fix, but still probably easier than getting them to move to a different language.

In any case, I do not think the issue here is caused by not having a pre-defined result type. Checking for null would have been fine. The issue was that to accommodate old and broken code a compromise was made. But as long such compromises are done even where fixing such old code would be quite easy, it is clear that changing to another language is completely unrealistic. So what are we even talking about? The real issue is that nobody wants to spend money or effort maintaining the old code. No language can fix this.

Thoughtful, civil disagreement arguing the real problem is unfunded maintenance rather than language design.

There are different amounts of effort you can pour in to this depending on your requirements, and we can argue about those, but to me the evidence is clear: It's too much of a hassle for library authors to actually go and do it. Everyone just uses sentinel values instead.

There are different amounts of effort you can pour in to this depending on your requirements, and we can argue about those, but to me the evidence is clear: It's too much of a hassle for library authors to actually go and do it. Everyone just uses sentinel values instead.

Substantive reply arguing that library authors' actual behavior shows the approach is too much hassle.

[deleted]
K0nserv 2026-11-05 13:547/10informativeinsightful | root | parent | prev | next [–]

For the other part of the issue (the truncation in SHA512), in Rust you could've had:

    struct Input<'a>(&'a [u8]);
    

    impl<'a> Input<'a> {
      fn new(input: &'a [u8]) -> Result<Self, Error> {
        if input.len() >= 256 {
           return Err(Errro...);
        }
        
        Ok(Self(input))
      }
    }

Now the truncation is a terrible idea in the first place, but at least with this it would have been entirely obvious to the caller that it was there.

For the other part of the issue (the truncation in SHA512), in Rust you could've had:

    struct Input<'a>(&'a [u8]);
    

    impl<'a> Input<'a> {
      fn new(input: &'a [u8]) -> Result<Self, Error> {
        if input.len() >= 256 {
           return Err(Errro...);
        }
        
        Ok(Self(input))
      }
    }

Now the truncation is a terrible idea in the first place, but at least with this it would have been entirely obvious to the caller that it was there.

Concrete code example showing how a type-based API would make the truncation explicit to callers.
uecker 2026-11-05 16:087/10informativeinsightful | root | parent [–]

In C you could do just the same: https://uecker.codeberg.page/2026-10-24.html Whether you should is a different question.

In any case, you can not change the API of an existing function. But returning NULL for error has required by POSIX would have been just fine.

In C you could do just the same: https://uecker.codeberg.page/2026-10-24.html Whether you should is a different question.

In any case, you can not change the API of an existing function. But returning NULL for error has required by POSIX would have been just fine.

Substantive counterpoint with a link and a concrete point about API compatibility and POSIX behavior.
dmantis 2026-11-05 13:446/10questioninformative | root | parent | prev [–]

Wouldn't Rust's Result or Go's error checking prevent that?

While not mandatory, a person would probably use it instead of the * hack.

Simply because the calling side wouldn't have an option to forget the check like with the NULL case

Wouldn't Rust's Result or Go's error checking prevent that?

While not mandatory, a person would probably use it instead of the * hack.

Simply because the calling side wouldn't have an option to forget the check like with the NULL case

A substantive, good-faith answer about how Result-style error handling could have changed the bug.
ninkendo 2026-11-05 13:567/10informativeinsightful | root | parent [–]

Rust and go still make it pretty easy to not check results if you don’t care about the success value (lots of code just does `let _ = foo()`), but if you do want the value (like with a hashing function), Rust makes it really hard to not check the error too. There’s no syntax to say “assume this worked and give me the success value, including whatever garbage is there if it’s not set”:

- result.unwrap() will panic if it failed

- result.ok() throws away the error but gives you a None value instead (still can’t use it as if it’s a success)

- matching on the Ok variant and doing nothing on the Err variant, you only get to use the success value inside the Ok arm

Etc etc.

The problem is when the compiler says “NULL is a valid instance of T”, which go lets you do, and rust doesn’t (without using the unsafe keyword and intentionally casting a null pointer, which you don’t do by accident.) Go lets you write code like `var hash string; hash, _ = hashfn()` and it compiles and now you have null instead of a valid hash.

Rust and go still make it pretty easy to not check results if you don’t care about the success value (lots of code just does `let _ = foo()`), but if you do want the value (like with a hashing function), Rust makes it really hard to not check the error too. There’s no syntax to say “assume this worked and give me the success value, including whatever garbage is there if it’s not set”:

- result.unwrap() will panic if it failed

- result.ok() throws away the error but gives you a None value instead (still can’t use it as if it’s a success)

- matching on the Ok variant and doing nothing on the Err variant, you only get to use the success value inside the Ok arm

Etc etc.

The problem is when the compiler says “NULL is a valid instance of T”, which go lets you do, and rust doesn’t (without using the unsafe keyword and intentionally casting a null pointer, which you don’t do by accident.) Go lets you write code like `var hash string; hash, _ = hashfn()` and it compiles and now you have null instead of a valid hash.

Substantive comparison of how Rust and Go handle ignored errors and null values.

> NULL is a valid instance of T

This by the way is Tony Hoare's famous "Billion Dollar Mistake"

> NULL is a valid instance of T

This by the way is Tony Hoare's famous "Billion Dollar Mistake"

Short, relevant historical reference to the quoted point about NULL.

A NULL pointer for error would be a safe interface where you get a trap when there is no value.

A NULL pointer for error would be a safe interface where you get a trap when there is no value.

Terse but genuine technical point about NULL as a trapping error interface.

I understand that other languages could help avoiding the semi-predicate problem. That being said, in C there are better ways around semi-predicates, as inelegant as they may be, is possble, is not an inherent problem of the language, but may a bad habit/idiom of it.

I understand that other languages could help avoiding the semi-predicate problem. That being said, in C there are better ways around semi-predicates, as inelegant as they may be, is possble, is not an inherent problem of the language, but may a bad habit/idiom of it.

Substantive pushback that the semi-predicate problem is a C idiom rather than an inherent language flaw.
randomNumber7 2026-11-05 14:56 | removed0/10snarklow_effort | root | parent | prev | next [–]

Do you work in industry or academia?

Do you work in industry or academia?

A veiled dig at the commenter's background that doesn't engage with the argument about C.

There is no industry, it's just people

There is no industry, it's just people

Terse but genuine pushback on the idea of a collective 'industry' that could have dumped C.

I am sorry, a little? I am deeply disappointed that libc decided to do the same just with another sequence.

And why because some dude was complaining sshd or whatever was crashing because it didn't handle nulls? Just handle the null my guy you already know why your bullshit is crashing.

I am sorry, a little? I am deeply disappointed that libc decided to do the same just with another sequence.

And why because some dude was complaining sshd or whatever was crashing because it didn't handle nulls? Just handle the null my guy you already know why your bullshit is crashing.

Blunt, casual disagreement with a real point about libc's change; the profanity targets software rather than a person.
dapper_sable54 2026-11-05 13:56 | rewritten5/10first_handpersonal_attacksnark | original | parent | prev | next [–]

While I applauded the idea of musl when it first came out (coincidentally, glibc wasn't having the best time in its development then), the developer's arguments why <some behavior drastically different from every libc under the sun> was correct, which was why you had to fix a program that hadn't been broken for 30 years and now suddenly was, made me never look at musl seriously again.

When later it turned out that musl's procrustian stack size made Python not really runnable under it, it cemented my resolve to not go near musl.

And now we have this.

original comment by WesolyKubeczek

You must be new here. While I applauded the idea of musl when it first came out (coincidentally, glibc wasn't having the best time in its development then), the developer's arguments why <some behavior drastically different from every libc under the sun> was akchyually correct, which was why you had to fix a program that hadn't been broken for 30 years and now suddenly was, made me put him under the "clowns" category and never look at musl seriously again.

When later it turned out that musl's procrustian stack size made Python not really runnable under it, it cemented my resolve to not go near musl.

And now we have this.

Removed the jab at the parent and the "clowns" insult, keeping the author's compatibility criticisms of musl.
gilded_newt4 2026-11-05 14:08 | rewritten6/10first_handinformative | original | root | parent | next [–]

I’m also reminded of people pushing “it’s smaller!” container images based on social media vibes rather than analysis where, yes, not using Debian+glibc saved tens of megabytes on the base OS but did so at the expense of measurable performance reductions & compatibility issues like the one you mentioned, and literally every single one of those dudes I worked with was shipping containers pushing a gigabyte so the size reduction had no measurable impact on deployments.

original comment by acdha

I’m also reminded of the clowns pushing “it’s smaller!” container images based on social media vibes rather than analysis where, yes, not using Debian+glibc saved tens of megabytes on the base OS but did so at the expense of measurable performance reductions & compatibility issues like the one you mentioned, and literally every single one of those dudes I worked with was shipping containers pushing a gigabyte so the size reduction had no measurable impact on deployments.

Useful first-hand point about musl-based images; only the name-calling 'clowns' was replaced.

Uh, isn't this the case of the opposite? The musl devs trying to accomodate broken programs?

Uh, isn't this the case of the opposite? The musl devs trying to accomodate broken programs?

A genuine, on-point challenge to the parent's framing of musl's behavior.

Let's be real here, the main culprit is C. People love to say "but you can still get logic errors with Rust", and no shit of course you can. But it's much less likely. You wouldn't have gotten this bug.

Let's be real here, the main culprit is C. People love to say "but you can still get logic errors with Rust", and no shit of course you can. But it's much less likely. You wouldn't have gotten this bug.

Blunt, strongly opinionated language-safety argument with mild profanity aimed at no one; it makes a real point.
warm_kiwi23 2026-11-05 13:14 | rewritten2/10curmudgeonlylow_effort | original | parent | prev [–]

A little?!? I’m sorry, but this is yet another warning sign about musl, and I have no idea why anyone uses it in production after the many before it.

original comment by semiquaver

A little?!? I’m sorry, but musl is a joke, this is yet another proof of that fact, and I have no idea why anyone uses it in production, they have only themselves to blame after the many warning signs.

Kept the firm disagreement about musl but dropped the sneer at the project and the blame aimed at its users.
aljgz 2026-11-05 20:107/10insightfulinformative | prev | next [–]

My general rule of thumb is: "no magic strings". This is much easier to implement in higher-level languages and when writing application/web code than system programming.

Don't return empty string/asterisk/anything else. Instead, return a failure value.

When absolutely needed, choose the magic string with more caution:

One string showing the SHA-256 of a non-existing hash? It's a good idea for it to be a valid hash. Now we have a new risk: if it's broken once, it's broken for all. So it's a good idea for it to be a random one for each machine and/or account. Even this might have problems I can't think of, but it seems to be as secure as an account with a real password.

If this is not a good decision for some reason, choose something unmistakable: "--ACCOUNT-HAS-NO-PASSWORD--". This is much less likely to be mistakenly used in a different context, is much easier to look up in a code base than "*", and is self-documenting.

My general rule of thumb is: "no magic strings". This is much easier to implement in higher-level languages and when writing application/web code than system programming.

Don't return empty string/asterisk/anything else. Instead, return a failure value.

When absolutely needed, choose the magic string with more caution:

One string showing the SHA-256 of a non-existing hash? It's a good idea for it to be a valid hash. Now we have a new risk: if it's broken once, it's broken for all. So it's a good idea for it to be a random one for each machine and/or account. Even this might have problems I can't think of, but it seems to be as secure as an account with a real password.

If this is not a good decision for some reason, choose something unmistakable: "--ACCOUNT-HAS-NO-PASSWORD--". This is much less likely to be mistakenly used in a different context, is much easier to look up in a code base than "*", and is self-documenting.

Substantive design advice on avoiding magic strings, with concrete reasoning about sentinel values for password hashes.

Agreed, unfortunately there is so much shit programming that does this that AI is trained on that I continually see it generating it in my code.

My AI usage involves telling it to do things and setting up lint rules to catch all the bad programming it’s learnt.

Agreed, unfortunately there is so much shit programming that does this that AI is trained on that I continually see it generating it in my code.

My AI usage involves telling it to do things and setting up lint rules to catch all the bad programming it’s learnt.

Blunt profanity is aimed at code, not people, and it shares a real practice of using lint rules to catch bad AI-generated patterns.

This is why I don’t fully buy the argument that software that’s been around for a long time is more secure and reliable because it’s been battle-tested. There’s been a lot of time for cruft to accumulate until no-one can ever fix it because everyone is expecting it to function in the broken manner it’s always functioned. And some of the more obscure configuration options get a lot less scrutiny.

This is why I don’t fully buy the argument that software that’s been around for a long time is more secure and reliable because it’s been battle-tested. There’s been a lot of time for cruft to accumulate until no-one can ever fix it because everyone is expecting it to function in the broken manner it’s always functioned. And some of the more obscure configuration options get a lot less scrutiny.

Substantive, thoughtful pushback on the idea that old software is more secure because it is battle-tested.

Musl, which is vulnerable, is much younger and less battle tested than glibc, which is not vulnerable though

Musl, which is vulnerable, is much younger and less battle tested than glibc, which is not vulnerable though

Terse, factual counterexample that directly challenges the parent's argument.
aidenn0 2026-11-05 22:497/10informativeinsightful | parent | prev [–]

This is not an obscure configuration option. Your password file almost certainly has at least one account with "*" as the password hash.

Integration of different software is the stage most likely to introduce uncaught bugs. That is what happened here: musl intentionally broke the documented API of a function used by OpenSSH. Then Alpine integrated this incorrect implementation with OpenSSH.

This is not an obscure configuration option. Your password file almost certainly has at least one account with "*" as the password hash.

Integration of different software is the stage most likely to introduce uncaught bugs. That is what happened here: musl intentionally broke the documented API of a function used by OpenSSH. Then Alpine integrated this incorrect implementation with OpenSSH.

Firm, substantive disagreement that explains the bug as a musl/OpenSSH integration failure, with no swipe at anyone.
wowczarek 2026-11-05 17:058/10first_handinsightfulinformative | prev | next [–]

In my last org I was in charge of internal testing and qualifying software releases for network equipment, and designing test harnesses and tests for these. Significant install base. All the major vendors. Many years. Basically doing the vendor QA for them, because we couldn't afford things to fail. As you do - I wish.

This was not security testing as such - mostly latency, performance and hardcore network "can we break it" and "does it comply" stuff, but also a lot of cable and module / line card pulls, pluggable and fiber breaks, random reboots and pre-empting a lot of possible operator inventiveness. At one point we considered SEU bit flip testing on rented cyclotron time.

And always we battled against the top brass urging us to speed up the process and don't bother with "really basic" testing, because we observed software quality going down over the years as the push for release and time to market stared to dominate.

So we always started with sanity testing - and that included oversized password and CLI input strings. Always. And we caught things more than once, but never like this.

We were right after all.

In my last org I was in charge of internal testing and qualifying software releases for network equipment, and designing test harnesses and tests for these. Significant install base. All the major vendors. Many years. Basically doing the vendor QA for them, because we couldn't afford things to fail. As you do - I wish.

This was not security testing as such - mostly latency, performance and hardcore network "can we break it" and "does it comply" stuff, but also a lot of cable and module / line card pulls, pluggable and fiber breaks, random reboots and pre-empting a lot of possible operator inventiveness. At one point we considered SEU bit flip testing on rented cyclotron time.

And always we battled against the top brass urging us to speed up the process and don't bother with "really basic" testing, because we observed software quality going down over the years as the push for release and time to market stared to dominate.

So we always started with sanity testing - and that included oversized password and CLI input strings. Always. And we caught things more than once, but never like this.

We were right after all.

First-hand account of network equipment QA and why basic oversized-input sanity tests caught real bugs despite release pressure.

> We were right after all.

Exhilarating, isn't it?

> We were right after all.

Exhilarating, isn't it?

A brief, wry note of solidarity with the parent's experience, aimed at no one.

Hardly. I say this with a faint, bored "huh" note rather than with a triumphant "I told you so", and I move on.

It's about maintaining solid engineering practices and far above proving who's right or wrong.

Hardly. I say this with a faint, bored "huh" note rather than with a triumphant "I told you so", and I move on.

It's about maintaining solid engineering practices and far above proving who's right or wrong.

Civil, plain disagreement with the parent, making a minor point about engineering practice over being right.
scandinavian 2026-11-05 12:597/10first_handquestioninformative | prev | next [–]

Did anyone get it to work? I tried on the official alpine docker image, enabled password login for root. The shadow entry has ! as password by default, this didn't work with a > 256 character password (i also did passwd -l which also does !). I manually changed it to * and it still didn't work.

Am I doing anything wrong here?

OpenSSH_10.3p1, OpenSSL 3.5.9 29 Sep 2026

PRETTY_NAME="Alpine Linux v3.24"

Did anyone get it to work? I tried on the official alpine docker image, enabled password login for root. The shadow entry has ! as password by default, this didn't work with a > 256 character password (i also did passwd -l which also does !). I manually changed it to * and it still didn't work.

Am I doing anything wrong here?

OpenSSH_10.3p1, OpenSSL 3.5.9 29 Sep 2026

PRETTY_NAME="Alpine Linux v3.24"

Genuine first-hand attempt to reproduce the issue, with specific versions and steps.

It's because alpine has updated all openssh-server packages already. Even from EOL alpine versions.

It's because alpine has updated all openssh-server packages already. Even from EOL alpine versions.

Directly answers the parent's question with a useful fact about Alpine's patched packages.
bonyt 2026-11-05 14:067/10first_handinformative | root | parent | next [–]

Ahh! Wasn't working on 3.16, which does indeed have an updated package.

But, got it to work on 3.15 - which requires changing the ! to a * in the shadow file in addition to other steps (making another user with a hash, setting PermitRootLogin to yes, etc.).

https://pkgs.alpinelinux.org/package/v3.16/main/x86_64/openssh Build time 2026-11-05 06:53:46

Ahh! Wasn't working on 3.16, which does indeed have an updated package.

But, got it to work on 3.15 - which requires changing the ! to a * in the shadow file in addition to other steps (making another user with a hash, setting PermitRootLogin to yes, etc.).

https://pkgs.alpinelinux.org/package/v3.16/main/x86_64/openssh Build time 2026-11-05 06:53:46

First-hand test results across Alpine versions, with the specific steps needed and a package link.
markuman123 2026-11-05 14:215/10first_handinformative | root | parent [–]

Terse but useful link to a reproduction on another Alpine version.

oooh, that'll do it.

Didn't notice that the openssh-server-10.3_p1-r2 was released today.

oooh, that'll do it.

Didn't notice that the openssh-server-10.3_p1-r2 was released today.

A casual reply that adds the specific patched package version and its release timing.

Nope, wasn’t able to reproduced it in my firecracker VM, which isn’t publicly accessible anyway.

Nope, wasn’t able to reproduced it in my firecracker VM, which isn’t publicly accessible anyway.

Brief but useful report of a failed reproduction, answering the parent's question.

As far as I understand, it wont work on default setups. You need musl-c, no pam and openssh.

As far as I understand, it wont work on default setups. You need musl-c, no pam and openssh.

Short, on-topic answer explaining the conditions needed for the exploit to work.

In Alpine, these would all be the case for an out of the box install with openssh added unless you specifically install openssh-server-pam and configure sshd_config to use PAM (Alpine is already musl-c based and doesn't default to PAM).

In Alpine, these would all be the case for an out of the box install with openssh added unless you specifically install openssh-server-pam and configure sshd_config to use PAM (Alpine is already musl-c based and doesn't default to PAM).

Concrete correction showing Alpine's out-of-the-box setup meets the vulnerable conditions.
Aachen 2026-11-05 14:255/10questioninformative | root | parent [–]

Doesn't it also need a user to be added with a password entry?

I only ever see Alpine Linux used for containers which don't need accounts with interactive user logins, which is not to say it doesn't exist, just that it doesn't seem like Alpine comes with all preconditions met out of the box

Doesn't it also need a user to be added with a password entry?

I only ever see Alpine Linux used for containers which don't need accounts with interactive user logins, which is not to say it doesn't exist, just that it doesn't seem like Alpine comes with all preconditions met out of the box

Good-faith technical question that adds a relevant point about whether Alpine meets the exploit's preconditions by default.
zamadatix 2026-11-05 15:208/10informativefirst_handinsightful | root | parent [–]

Isn't it the opposite, accounts which have no password and hence had * instead of a hash?

> accounts that should never take a password at all (root, your key-only deploy user, ldap users, the default user on alpine's aws and azure images) take any password longer than 256 characters.

But yeah, Alpine for persistant installs isn't unheard of - just not as popular as the container use case. They offer official VM, Xen, Pi, and bare metal images users deploy as well. I actually had a fleet of VMs to update!

Edit: The Alpine notice made it clearer to me that at least one other account has to have a password, it's just the target account that doesn't. So if you truly have no users with passwords in your container deployment then those passwordless users aren't vulnerable to this bug, but any of the image types which have a user will likely have root exposed.

> The attacker needs the name of an account whose password field is * (an account with no password set, which is normal for key-only accounts), and needs the machine to have at least one other account with a $5$ or $6$ password hash.

https://alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/

Isn't it the opposite, accounts which have no password and hence had * instead of a hash?

> accounts that should never take a password at all (root, your key-only deploy user, ldap users, the default user on alpine's aws and azure images) take any password longer than 256 characters.

But yeah, Alpine for persistant installs isn't unheard of - just not as popular as the container use case. They offer official VM, Xen, Pi, and bare metal images users deploy as well. I actually had a fleet of VMs to update!

Edit: The Alpine notice made it clearer to me that at least one other account has to have a password, it's just the target account that doesn't. So if you truly have no users with passwords in your container deployment then those passwordless users aren't vulnerable to this bug, but any of the image types which have a user will likely have root exposed.

> The attacker needs the name of an account whose password field is * (an account with no password set, which is normal for key-only accounts), and needs the machine to have at least one other account with a $5$ or $6$ password hash.

https://alpinelinux.org/posts/2026-11-05-openssh-password-authentication-bypass/

Corrects the precondition with quotes from the advisory, adds first-hand Alpine deployment experience and a useful link.
q3k 2026-11-05 13:297/10first_handinformative | parent | prev | next [–]

EDIT: works after downgrading OpenSSH, turns out even EOL Alpine version got updated packages!

Also didn't have much luck on Alpine 3.20.10 with OpenSSH_9.7p1 which I've freshly installed from an official ISO.

Last two entries in /etc/shadow:

    test:*:20762:0:99999:7:::
    q3k:$6$snip.GgDQNTrqqX.A0:20762:0:99999:7:::

SSHing into test@ with one 'wrong' password then one 'long' password doesn't work. I have UsePAM no and PasswordAuthentication yes in my sshd_config.

EDIT: works after downgrading OpenSSH, turns out even EOL Alpine version got updated packages!

Also didn't have much luck on Alpine 3.20.10 with OpenSSH_9.7p1 which I've freshly installed from an official ISO.

Last two entries in /etc/shadow:

    test:*:20762:0:99999:7:::
    q3k:$6$snip.GgDQNTrqqX.A0:20762:0:99999:7:::

SSHing into test@ with one 'wrong' password then one 'long' password doesn't work. I have UsePAM no and PasswordAuthentication yes in my sshd_config.

First-hand reproduction attempt with concrete versions and config, plus the useful finding that even EOL Alpine got patched packages.

[deleted]
hagbard_c 2026-11-05 13:356/10first_handinformative | parent | prev | next [–]

No, didn't work for me open OpenWRT x64 running in a container but that is probably 'cause I'm using dropbear instead of openssh-server. That's what OpenWRT uses by default so the impact there will be minimal assuming that most installations don't change to openssh-server - it isn't installed by default.

No, didn't work for me open OpenWRT x64 running in a container but that is probably 'cause I'm using dropbear instead of openssh-server. That's what OpenWRT uses by default so the impact there will be minimal assuming that most installations don't change to openssh-server - it isn't installed by default.

First-hand test report noting OpenWRT's default dropbear limits the exposure.
necovek 2026-11-05 22:236/10first_handinformative | root | parent [–]

On top of OpenWRT on my router (running 25.12) running dropbear instead of openssh-server, I also do not have any accounts in /etc/passwd|/etc/shadow with "*" for a password hash and an actual shell (all are set to /bin/false).

On top of OpenWRT on my router (running 25.12) running dropbear instead of openssh-server, I also do not have any accounts in /etc/passwd|/etc/shadow with "*" for a password hash and an actual shell (all are set to /bin/false).

Substantive first-hand detail on why the issue doesn't apply to a default OpenWRT setup.

Did you try one incorrect standard password fail first?

Did you try one incorrect standard password fail first?

Short, genuine troubleshooting question that suggests a specific step to try.
justinsaccount 2026-11-05 14:008/10first_handinformative | parent | prev | next [–]

I can't reproduce it either. I used alpine:3.24.1 and openssh is openssh-server-10.3_p1-r2

I tried adding PermitRootLogin yes and PasswordAuthentication yes. Tried adding another user with a valid password.

Verified shadow entry has a *. Can't login as root or a test user. Tried another incorrect password first as well.

edit: can reproduce after `git revert 384e25e6bd1f2e7b30095900e48b570d46953510` in aports and rebuilding openssh without the fix. It also accepts the password on the 2nd+ attempt, NOT the first.

I can't reproduce it either. I used alpine:3.24.1 and openssh is openssh-server-10.3_p1-r2

I tried adding PermitRootLogin yes and PasswordAuthentication yes. Tried adding another user with a valid password.

Verified shadow entry has a *. Can't login as root or a test user. Tried another incorrect password first as well.

edit: can reproduce after `git revert 384e25e6bd1f2e7b30095900e48b570d46953510` in aports and rebuilding openssh without the fix. It also accepts the password on the 2nd+ attempt, NOT the first.

Hands-on reproduction attempt that pins down the fix commit and the second-attempt behavior.

[flagged]

[flagged]

[flagged]

Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option because it means people actually notice that there's a problem. Silent compatibility hacks are the worst, they guarantee problems never get fixed and the stack of hacks never gets smaller.

Microsoft has historically been one of the leaders on the wrong side of this one, but they have also demonstrated how effective it is to call out badly behaved software and make it annoy the users with UAC. When Vista came out, so many applications needed elevated privileges to run because they were badly designed to write user data in system locations, but now their application was more annoying to use and came with a scary dialog about it making changes to your computer that other better-designed software didn't get.

More often than not if a program didn't actually intend to change system settings it was just a matter of badly chosen default paths which are pretty trivial to fix, but so many vendors just would not do it despite years of complaints from NT admins wanting to not have to spend hours with Process Explorer figuring out specific permissions to grant limited users enough access. Once their failures were put front and center, calling their application out as the one getting things wrong every time the user ran it, they suddenly had motivation to fix it.

Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option because it means people actually notice that there's a problem. Silent compatibility hacks are the worst, they guarantee problems never get fixed and the stack of hacks never gets smaller.

Microsoft has historically been one of the leaders on the wrong side of this one, but they have also demonstrated how effective it is to call out badly behaved software and make it annoy the users with UAC. When Vista came out, so many applications needed elevated privileges to run because they were badly designed to write user data in system locations, but now their application was more annoying to use and came with a scary dialog about it making changes to your computer that other better-designed software didn't get.

More often than not if a program didn't actually intend to change system settings it was just a matter of badly chosen default paths which are pretty trivial to fix, but so many vendors just would not do it despite years of complaints from NT admins wanting to not have to spend hours with Process Explorer figuring out specific permissions to grant limited users enough access. Once their failures were put front and center, calling their application out as the one getting things wrong every time the user ran it, they suddenly had motivation to fix it.

Substantive argument for failing loudly, with a useful historical example from Vista's UAC; its bluntness targets software and vendors.
simias 2026-11-05 14:029/10first_handinsightfulinformative | parent | next [–]

For security it's better, but in many cases if you're not writing security-oriented software it can be worse from a user perspective. I remember the first job I had over 20 years ago was working on VoD streaming and one of the "tricks" to improve stability was mmap'ing the 0 page so that NULL dereferences wouldn't immediately segfault. Ugly as hell but when you were working with all sorts of more or less well coded media codecs and security wasn't a concern at all (worst case scenario the user would stream movies without paying) it made some amount of sense.

I suspect that Microsoft has the same history: the entire Windows platform was built with single-user PCs in mind, mostly only dealing with trusted input. The users were also expected to be tech-illiterate for the most part and unable to troubleshoot deep technical issues.

More recently I'm still working on video transmission, but on the production side this time. I also originally adopted the mentality that I should crash early if some unexpected situation was encountered, but I ended up walking that back a lot, because having a critical piece of live media production reboot mid FIFA worldcup final is generally Not Great. Degraded functionality is usually preferable. I still crash early in dev builds, but the production code just logs an error and continues if at all possible.

That said, I definitely agree with you for something like password handling. In this case, I do think it's warranted to crash the program without survivors if something is amiss.

For security it's better, but in many cases if you're not writing security-oriented software it can be worse from a user perspective. I remember the first job I had over 20 years ago was working on VoD streaming and one of the "tricks" to improve stability was mmap'ing the 0 page so that NULL dereferences wouldn't immediately segfault. Ugly as hell but when you were working with all sorts of more or less well coded media codecs and security wasn't a concern at all (worst case scenario the user would stream movies without paying) it made some amount of sense.

I suspect that Microsoft has the same history: the entire Windows platform was built with single-user PCs in mind, mostly only dealing with trusted input. The users were also expected to be tech-illiterate for the most part and unable to troubleshoot deep technical issues.

More recently I'm still working on video transmission, but on the production side this time. I also originally adopted the mentality that I should crash early if some unexpected situation was encountered, but I ended up walking that back a lot, because having a critical piece of live media production reboot mid FIFA worldcup final is generally Not Great. Degraded functionality is usually preferable. I still crash early in dev builds, but the production code just logs an error and continues if at all possible.

That said, I definitely agree with you for something like password handling. In this case, I do think it's warranted to crash the program without survivors if something is amiss.

Nuanced first-hand experience from video streaming and live production on when degraded operation beats crashing, with a clear concession for password handling.

> because having a critical piece of live media production reboot mid

It's not great, but these days a trickster with AI will find some way to start broadcasting porn to a billion people because of the flaw, so may end up being the better option.

> because having a critical piece of live media production reboot mid

It's not great, but these days a trickster with AI will find some way to start broadcasting porn to a billion people because of the flaw, so may end up being the better option.

Short but real point that AI-assisted exploitation changes the tradeoff of tolerating faults over crashing.

[flagged]

> if you're not writing security-oriented software

It's hard to define what's security oriented and what is not. For example, is this HN web app security oriented? Not in the way of sshd but it must keep our accounts data separated and it must not let people exfiltrate data from the db, where for example it is storing the emails of some of us.

> if you're not writing security-oriented software

It's hard to define what's security oriented and what is not. For example, is this HN web app security oriented? Not in the way of sshd but it must keep our accounts data separated and it must not let people exfiltrate data from the db, where for example it is storing the emails of some of us.

Substantive point that the line around security-oriented software is blurry, illustrated with HN itself.

It depends on the time. Twenty or thirty years most software was not security-oriented. Nowadays, everything is online and therefore everything needs to be vigilant about security.

It depends on the time. Twenty or thirty years most software was not security-oriented. Nowadays, everything is online and therefore everything needs to be vigilant about security.

A brief, civil point that security expectations have shifted now that everything is online.
stackghost 2026-11-05 14:472/10questionlow_effort | root | parent | prev [–]

> because having a critical piece of live media production reboot mid FIFA worldcup final is generally Not Great.

Story time?

> because having a critical piece of live media production reboot mid FIFA worldcup final is generally Not Great.

Story time?

A short, genuine invitation for the commenter to share a first-hand story.
simias 2026-11-05 21:526/10first_handinformative | root | parent [–]

There's not much else to say honestly, for these very important events there will usually be several layers of redundancy for the most critical feeds, so the production is always able to send something to the satellite uplink even while our devices reboot due to a crash, but brutally losing 90% of your inputs for ~1 minute while the machine reboots is not a pleasant experience for anyone...

There's not much else to say honestly, for these very important events there will usually be several layers of redundancy for the most critical feeds, so the production is always able to send something to the satellite uplink even while our devices reboot due to a crash, but brutally losing 90% of your inputs for ~1 minute while the machine reboots is not a pleasant experience for anyone...

First-hand account of redundancy in live broadcast production and what a device crash feels like mid-event.
pizlonator 2026-11-05 15:11 | removed0/10low_effort | parent | prev | next [–]

> Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option because it means people actually notice that there's a problem.

100%

> Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option because it means people actually notice that there's a problem.

100%

Quotes the parent and adds only "100%", which is a bare +1 with no new content.

Agreed. I feel like this attitude has been holding Python back, too. It's one thing to criticize "move fast and break things", but it's also not good to ignore already existing breakage just because the pieces still fit together neatly, and it's not good to refuse to move at all.

Agreed. I feel like this attitude has been holding Python back, too. It's one thing to criticize "move fast and break things", but it's also not good to ignore already existing breakage just because the pieces still fit together neatly, and it's not good to refuse to move at all.

Substantive agreement extending the point to Python's compatibility culture, with no swipe at anyone.

> letting badly programmed software die when unexpected things happen

Program a flight controller this way and something bad will happen.

original comment by 0xbadcafebee

> letting badly programmed software die when unexpected things happen

Go ahead and program a flight controller this way, I'm sure nothing bad will happen.

Valid counterexample, but the sarcastic 'go ahead' framing was aimed at the parent, so it is stated plainly.

I'd still rather have the software crash and alert a human that to continue in a faulty state. The way to add robustness in this case is to add backup systems, not "ON ERROR RESUME NEXT".

I'd still rather have the software crash and alert a human that to continue in a faulty state. The way to add robustness in this case is to add backup systems, not "ON ERROR RESUME NEXT".

Firm, substantive disagreement arguing for redundant systems over continuing in a faulty state.

[deleted]

Now everything installs to a user local folder, runs as the user, there are no UAC prompts, and malware can just freely tamper with other applications, act as the user, and harvest all data the user has access to.

Hey, but at least it can’t touch the System32 folder.

Great job guys.

Now everything installs to a user local folder, runs as the user, there are no UAC prompts, and malware can just freely tamper with other applications, act as the user, and harvest all data the user has access to.

Hey, but at least it can’t touch the System32 folder.

Great job guys.

Wry and cynical about the industry, but makes a real point that the user-local install model leaves user data exposed.

> Microsoft has historically been one of the leaders on the wrong side of this one

Objective-C ahem nil messaging...

> Microsoft has historically been one of the leaders on the wrong side of this one

Objective-C ahem nil messaging...

Terse but real counterexample: Objective-C's silent nil messaging as another case of failing quietly.

Objective-C is an Apple thingie :)

Objective-C is an Apple thingie :)

Short, friendly factual note about Objective-C's association with Apple; harmless and on topic.

Yes and thus I dispute that Microsoft leads on this front :P

Yes and thus I dispute that Microsoft leads on this front :P

Light, good-natured reply that makes a small point in context.

By way of NeXTSTEP

By way of NeXTSTEP

Short but accurate historical correction adding that Objective-C reached Apple via NeXTSTEP.

NeXT certainly used it extensively but they didn't invent it. Originally developed by Brad Cox and Tom Love in the early 1980s, per !w.

NeXT certainly used it extensively but they didn't invent it. Originally developed by Brad Cox and Tom Love in the early 1980s, per !w.

Brief, factual correction about Objective-C's origins.

> Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option

Sadly, we will never learn this because failing fast runs counter to intuition, and a class of person will never override it.

> Also how many times do we have to learn that letting badly programmed software die when unexpected things happen is the better option

Sadly, we will never learn this because failing fast runs counter to intuition, and a class of person will never override it.

A cynical but real opinion about why fail-fast is hard to adopt; the generalization isn't aimed at a commenter.

[deleted]

[flagged]
sidneythekidney 2026-11-05 13:568/10first_handinformativeinsightful | prev | next [–]

Fun!

I was able to reproduce this on Alpine 3.23.7 with OpenSSH_10.2p1.

Luckily PermitRootLogin [0] hasn't allowed logins with passwords for root since 2015 [1]. I'm guessing that's at least part of why this went undiscovered for so long.

0: https://man.openbsd.org/sshd_config#PermitRootLogin

1: https://cvsweb.openbsd.org/diff/src/usr.bin/ssh/sshd_config?rev=1.96&prev=1.95

Fun!

I was able to reproduce this on Alpine 3.23.7 with OpenSSH_10.2p1.

Luckily PermitRootLogin [0] hasn't allowed logins with passwords for root since 2015 [1]. I'm guessing that's at least part of why this went undiscovered for so long.

0: https://man.openbsd.org/sshd_config#PermitRootLogin

1: https://cvsweb.openbsd.org/diff/src/usr.bin/ssh/sshd_config?rev=1.96&prev=1.95

Independent reproduction with version details and sourced context on why the bug may have gone unnoticed.
evgpbfhnr 2026-11-05 14:258/10informativeinsightful | prev | next [–]

So to recap, with a non-upgraded sshd you need:

- an account with /etc/shadow hash set to '*'

- (note root won't work by default because of PermitRootLogin prohibit-password default)

- attempt twice, first attempt fails second long password is accepted (that took me a while to understand)

In practice passwd -l seems to lock with a ! prefix, and new account without passwords are created with a single ! which is also not affected, I'm not sure how big the impact actually will be in practice and don't understand the recommendation to upgrade urgently right now... Just don't use `*`?

EDIT: ah, some default container images apparently have a default alpine user with that hash, so I guess that's bad for them. This makes a bit more sense now.

So to recap, with a non-upgraded sshd you need:

- an account with /etc/shadow hash set to '*'

- (note root won't work by default because of PermitRootLogin prohibit-password default)

- attempt twice, first attempt fails second long password is accepted (that took me a while to understand)

In practice passwd -l seems to lock with a ! prefix, and new account without passwords are created with a single ! which is also not affected, I'm not sure how big the impact actually will be in practice and don't understand the recommendation to upgrade urgently right now... Just don't use `*`?

EDIT: ah, some default container images apparently have a default alpine user with that hash, so I guess that's bad for them. This makes a bit more sense now.

Concise, hands-on breakdown of the exploit's preconditions and real-world impact, including a self-correction.
jack1142 2026-11-05 18:428/10informativequestioninsightful | parent | next [–]

> some default container images apparently have a default alpine user with that hash

Do you have an example? Looking at Alpine's advisory, I can only see that their cloud images (i.e. what you use to run a VM, not a container) have a default alpine user, no container images. And even with those, cloud images with cloud-init are unaffected with default config due to `PasswordAuthentication no` being inserted by cloud-init into sshd_config. So that leaves... Cloud images that use Tiny Cloud for provisioning. That's an instance bootstrapper authored by Alpine from the looks of it so I'm not sure how many people would prefer that over cloud-init (which a lot of VM/bare metal management software may already make easy to use) or just a non-cloud image.

> some default container images apparently have a default alpine user with that hash

Do you have an example? Looking at Alpine's advisory, I can only see that their cloud images (i.e. what you use to run a VM, not a container) have a default alpine user, no container images. And even with those, cloud images with cloud-init are unaffected with default config due to `PasswordAuthentication no` being inserted by cloud-init into sshd_config. So that leaves... Cloud images that use Tiny Cloud for provisioning. That's an instance bootstrapper authored by Alpine from the looks of it so I'm not sure how many people would prefer that over cloud-init (which a lot of VM/bare metal management software may already make easy to use) or just a non-cloud image.

Checks a claim against Alpine's advisory and narrows the real exposure to a specific provisioning setup.

Why do you need to attempt twice?

Why do you need to attempt twice?

Short genuine question about the mechanics of the exploit.

> Just don't use `*`

Well but, alas, a non-zero number of user probably do use it, though and "upgrade X" is a clearer and better instruction to communicate than "change this setting where maybe you don't even know what it means and it might break your workflow, but saves you an upgrade".

> Just don't use `*`

Well but, alas, a non-zero number of user probably do use it, though and "upgrade X" is a clearer and better instruction to communicate than "change this setting where maybe you don't even know what it means and it might break your workflow, but saves you an upgrade".

Civil, substantive point that a blanket upgrade instruction communicates better than a config workaround.

In some contexts (e.g. embedded) you'd be surprised at how reluctant some people are to upgrade when they can run through a dozen of hoops for workarounds such as changing hash instead...

In some contexts (e.g. embedded) you'd be surprised at how reluctant some people are to upgrade when they can run through a dozen of hoops for workarounds such as changing hash instead...

Adds a real observation that embedded users often prefer workarounds over upgrades.

[deleted]

Offtopic: this is a very cool way to present commits and code excerpts. Love classic Windows styling.

Were those pictures with links generated using a publicly available tool that we could use, or were it custom (vibe)coded specifically to create this page?

Offtopic: this is a very cool way to present commits and code excerpts. Love classic Windows styling.

Were those pictures with links generated using a publicly available tool that we could use, or were it custom (vibe)coded specifically to create this page?

A genuine question about how the page's presentation was made, and self-labeled as off-topic.

This looks entirely vibe-coded to me. No one is going to put so much effort for a vuln.

This looks entirely vibe-coded to me. No one is going to put so much effort for a vuln.

Blunt but on-topic answer to the question about how the page was made.

To be honest, I love this sort of vibe-coding. Explains it really well, in an interesting way. If this is vibe-coding, then the AI has done a great job.

To be honest, I love this sort of vibe-coding. Explains it really well, in an interesting way. If this is vibe-coding, then the AI has done a great job.

Civil, on-topic opinion on the write-up's quality that offers a mild counterpoint to the parent.

It was fun to see this type of style around the release of Opus 4.6, but its really tired now. I've seen countless landing pages like this...its basically ritual

It was fun to see this type of style around the release of Opus 4.6, but its really tired now. I've seen countless landing pages like this...its basically ritual

A blunt but genuine counterpoint about the generic style of AI-generated landing pages.

There's nothing great about this site. It should give you a succinct summary of the vulnerability right up front, so you can determine if it applies to you. The history lesson is not relevant to the vast majority of visitors.

There's nothing great about this site. It should give you a succinct summary of the vulnerability right up front, so you can determine if it applies to you. The history lesson is not relevant to the vast majority of visitors.

Blunt disagreement with a concrete usability critique of the site; no personal swipe.

That's a well-made site, yet I feel nothing.

That's a well-made site, yet I feel nothing.

Terse personal reaction, wry but not hostile, and it credits the site's craft.

I thought it was a great write up. It did feel a bit AI-coded, but whether it actually was or not, it's still a good bit of work imo.

I thought it was a great write up. It did feel a bit AI-coded, but whether it actually was or not, it's still a good bit of work imo.

A civil opinion that offers a mild counterpoint to the parent, with no swipe at anyone.

I dunno, there were some strange discrepancies in the explanation (and captions). Didn't really hang together.

I dunno, there were some strange discrepancies in the explanation (and captions). Didn't really hang together.

Plain, civil disagreement with a real if unspecific observation about the write-up.

fwiw pangram believes the text is AI as well

fwiw pangram believes the text is AI as well

A brief factual note citing an AI-detection tool, relevant to the parent's impression and not an attack.

Looks good overall, but I was wondering if eg. "the timer learns nothing" and "no X, no Y, no Z" was slop rather than a deliberate choice.

Looks good overall, but I was wondering if eg. "the timer learns nothing" and "no X, no Y, no Z" was slop rather than a deliberate choice.

A mild, genuine observation about the writing style of the piece, with specific examples.

Slop, no question. There's at least 10 Claudisms on the page, and I'm not that good at smelling them. Some of the sentences might have been human-written.

Slop, no question. There's at least 10 Claudisms on the page, and I'm not that good at smelling them. Some of the sentences might have been human-written.

Blunt opinion answering the parent's question about AI-written prose; no attack on a person.

It would be weird to spend this much effort doing the site by hand, for a bug that the author says was found by a swarm of agents.

It would be weird to spend this much effort doing the site by hand, for a bug that the author says was found by a swarm of agents.

Mild, reasonable observation on why the site was likely AI-built; no swipe at anyone.

LLM output fatigue?

LLM output fatigue?

A short, genuine question offering a possible explanation for the parent's reaction.
amelius 2026-11-05 13:312/10jokelow_effortkept_for_replies | root | parent | next [–]

Apple sideways traffic light fatigue.

Apple sideways traffic light fatigue.

A light, non-hostile quip about Apple's window controls that the kept reply depends on for context.

[deleted]

Good to know I'm not the only one annoyed by that superfluous piece of ${whatever} signalling.

Good to know I'm not the only one annoyed by that superfluous piece of ${whatever} signalling.

Mild annoyance aimed at a design trend, not a person; it adds little but breaks no rules.

I didn't hate the writing even if it's AI, but I deeply disliked the animations. I don't know if that's LLM fatigue or not.

I didn't hate the writing even if it's AI, but I deeply disliked the animations. I don't know if that's LLM fatigue or not.

Civil personal reaction distinguishing the writing from the animations; minor but real.

I left after trying to scroll to a concise description of the issue. I don't want a theatrical presentation.

I left after trying to scroll to a concise description of the issue. I don't want a theatrical presentation.

A blunt but genuine complaint that the site's presentation hides a concise description of the issue; no personal swipe.

It has a crap load of text but says very little and is very bad at conveying the details because it thinks it has to tell some weird story.

It has a crap load of text but says very little and is very bad at conveying the details because it thinks it has to tell some weird story.

Blunt criticism of the site's writing, with a specific complaint about storytelling obscuring the details.

I had this classmate that had their ChatGPT instructions to respond in this same laidback, lowercase, gen-z style. i recognized the output as a lot of llm writing almost immediately

I had this classmate that had their ChatGPT instructions to respond in this same laidback, lowercase, gen-z style. i recognized the output as a lot of llm writing almost immediately

A small first-hand observation about recognizing styled LLM output; no hostility.

Yes. Reading this feels like a LLM pretending to write carelessly. And failing miserably

Yes. Reading this feels like a LLM pretending to write carelessly. And failing miserably

A blunt opinion that the writing reads as LLM-generated; it criticizes the text and adds a small observation to the parent's point.

I enjoyed it, though the discrepancy between the `*sshole` name and the `sshhole.com` domain made me twitch. How is anyone[0] obsessive enough to find this and write it up and yet not obsessive enough to make the amount of `h` consistent?

[0]: Be they human or LLM. Or an entity with any proportion of the two. I do not judge nor discriminate.

original comment by tasuki

Maybe that's on you though. I enjoyed it, though the discrepancy between the `*sshole` name and the `sshhole.com` domain made me twitch. How is anyone[0] obsessive enough to find this and write it up and yet not obsessive enough to make the amount of `h` consistent?

[0]: Be they human or LLM. Or an entity with any proportion of the two. I do not judge nor discriminate.

Dropped the personal jab at the parent commenter and kept the wry observation about the name.

Look closely. The second h is behind the word and almost as dark as the background

Look closely. The second h is behind the word and almost as dark as the background

Friendly, useful correction explaining the apparent name and domain mismatch.
tasuki 2026-11-05 13:14 | removed0/10low_effort | root | parent [–]

Oh. Thank you!

Oh. Thank you!

A bare thank-you with nothing for other readers.

It has very low information density. This would read 10x if it were just a few paragraphs of text. Instead we need to render an animation every few sentences. Not a fan.

It has very low information density. This would read 10x if it were just a few paragraphs of text. Instead we need to render an animation every few sentences. Not a fan.

Blunt but concrete critique of the site's format and information density.

What is dead can never die. All blueteamers are ironborn these days.

What is dead can never die. All blueteamers are ironborn these days.

A wry pop-culture line with a real point underneath: defenders are numb to yet another vulnerability.
BeetleB 2026-11-05 20:31 | removed0/10metasnarkcurmudgeonly | parent | prev | next [–]

I think at some point I'm going to get banned from HN for repeatedly saying this, but here we go:

This thread highlights why I often say that at some point in the future, the obsession with whether something was written by AI or not will be classified as a mental disorder.

It is tiring to virtually every day see this discussion play out in some HN thread or other. It's also just noise. I don't think your comment, nor the thread it inspired, added anything of value to the discussion.

I think at some point I'm going to get banned from HN for repeatedly saying this, but here we go:

This thread highlights why I often say that at some point in the future, the obsession with whether something was written by AI or not will be classified as a mental disorder.

It is tiring to virtually every day see this discussion play out in some HN thread or other. It's also just noise. I don't think your comment, nor the thread it inspired, added anything of value to the discussion.

Meta complaint about the thread that likens a recurring discussion to a mental disorder and tells the parent they added nothing, with no substance about the topic.

I do actually. My guess is that this was not just one-prompted, but someone took the time to refine it based on their tastes and expectations until it matched something they would actually like.

However, if someone already developed a strong AI-slop-tingle it will still be triggered by some telltale signs and let their subconsciousness know that they can't possible enjoy this.

I do actually. My guess is that this was not just one-prompted, but someone took the time to refine it based on their tastes and expectations until it matched something they would actually like.

However, if someone already developed a strong AI-slop-tingle it will still be triggered by some telltale signs and let their subconsciousness know that they can't possible enjoy this.

A genuine disagreement with a speculative but civil take on how AI-slop detection shapes people's reactions; no attack on anyone.
ivory_stork84 2026-11-05 17:48 | rewritten4/10question | original | prev | next [–]

From the advisory: musl’s crypt() returns the string * as an error indicator when the key is over 256 bytes long

Why on Earth would anybody indicate error like this?

original comment by dev_l1x_be

From the advisory: musl’s crypt() returns the string * as an error indicator when the key is over 256 bytes long

Are these people mad? Why on Earth would anybody indicate error like this?

Dropped the 'are these people mad' swipe at the developers; kept the advisory quote and the genuine question.

[deleted]

[deleted]
0812037123158 2026-11-05 14:237/10informativeinsightful | prev | next [–]

It's amazing how similar this exploit payload is to the 20+ year-old Quake 3 vulnerabilities found by Luigi Auriemma (aluigi), which also used the exact same 256-to-1024 character range and nothing more than a long string of AAAA.... Here you're feeding >256 A's up to a 1024-byte limit to trip musl's defensive length cap, whereas back in the mid-2000s, Luigi was famous for publishing one-line .cfg scripts where the entire exploit was literally "say aaaaa..." repeated a few hundred times overflowing server's memory.¹ ²

A great example is Jedi Academy, a 2003 Quake 3 engine game whose multiplayer is still actively played today (its open-source engine port, openjk, is even in the Debian repos btw). Luigi had two classic scripts for it on his site: Jedi Academy <= 1.011 in-game server buffer-overflow (jamsgbof)¹, which crashed the server just by pushing a string of as past the 1024-byte console buffer, and Quake 3 engine in-game players kicking (q3msgboom)², where a wall of as hit the engine's 1022-char truncation limit and the leftover bytes confused the parser into disconnecting everyone on the server.

Kind of wild that 20 years later, the exact same payload goes from crashing Quake 3 matches from the tilde console to getting root on some SSH servers.

[1] https://web.archive.org/web/20101227181948/http://aluigi.altervista.org/poc/jamsgbof.cfg [2] https://web.archive.org/web/20060522175643/http://aluigi.altervista.org/poc/q3msgboom.cfg

It's amazing how similar this exploit payload is to the 20+ year-old Quake 3 vulnerabilities found by Luigi Auriemma (aluigi), which also used the exact same 256-to-1024 character range and nothing more than a long string of AAAA.... Here you're feeding >256 A's up to a 1024-byte limit to trip musl's defensive length cap, whereas back in the mid-2000s, Luigi was famous for publishing one-line .cfg scripts where the entire exploit was literally "say aaaaa..." repeated a few hundred times overflowing server's memory.¹ ²

A great example is Jedi Academy, a 2003 Quake 3 engine game whose multiplayer is still actively played today (its open-source engine port, openjk, is even in the Debian repos btw). Luigi had two classic scripts for it on his site: Jedi Academy <= 1.011 in-game server buffer-overflow (jamsgbof)¹, which crashed the server just by pushing a string of as past the 1024-byte console buffer, and Quake 3 engine in-game players kicking (q3msgboom)², where a wall of as hit the engine's 1022-char truncation limit and the leftover bytes confused the parser into disconnecting everyone on the server.

Kind of wild that 20 years later, the exact same payload goes from crashing Quake 3 matches from the tilde console to getting root on some SSH servers.

[1] https://web.archive.org/web/20101227181948/http://aluigi.altervista.org/poc/jamsgbof.cfg [2] https://web.archive.org/web/20060522175643/http://aluigi.altervista.org/poc/q3msgboom.cfg

Substantive historical comparison to Quake 3 engine exploits, with specific examples and archived sources.
0x0 2026-11-05 13:275/10informative | prev | next [–]

For OpenWRT, AsusWRT etc, I believe they use dropbear ssh by default instead of OpenSSH, which apparently isn't affected?

For OpenWRT, AsusWRT etc, I believe they use dropbear ssh by default instead of OpenSSH, which apparently isn't affected?

Useful, on-topic note about which embedded SSH servers may be unaffected.
dhx 2026-11-05 13:537/10informativefirst_hand | parent | next [–]

Yes, correct for OpenWrt. OpenWrt defaults to dropbear for both server and client. However, more advanced users may optionally choose to replace it with OpenSSH, or install OpenSSH side-by-side, to gain additional features of OpenSSH.

  > root@OpenWrt:~# cat /etc/openwrt_release | grep DISTRIB_RELEASE
  > DISTRIB_RELEASE='25.12.6'

  > root@OpenWrt:~# which ssh
  > /usr/bin/ssh
  > root@OpenWrt:~# ssh -V
  > Dropbear v2025.89
  > root@OpenWrt:~# ldd /usr/bin/ssh
  >  /lib/ld-musl-aarch64.so.1 (0x7f8e120000)
  >  libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x7f8e0b8000)
  >  libc.so => /lib/ld-musl-aarch64.so.1 (0x7f8e120000)

  > root@OpenWrt:~# which dropbear
  > /usr/sbin/dropbear
  > root@OpenWrt:~# dropbear -V
  > Dropbear v2025.89
  > root@OpenWrt:~# ldd /usr/sbin/dropbear
  >  /lib/ld-musl-aarch64.so.1 (0x7fbb900000)
  >  libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x7fbb898000)
  >  libc.so => /lib/ld-musl-aarch64.so.1 (0x7fbb900000)

  > root@OpenWrt:~# apk search openssh
  > openssh-client-10.6_p1-r2
  > openssh-client-utils-10.6_p1-r2
  > openssh-keygen-10.6_p1-r2
  > openssh-moduli-10.6_p1-r2
  > openssh-server-10.6_p1-r2
  > openssh-server-pam-10.6_p1-r2
  > openssh-sftp-avahi-service-10.6_p1-r2
  > openssh-sftp-client-10.6_p1-r2
  > openssh-sftp-server-10.6_p1-r2
  > openssh-sk-helper-10.6_p1-r2

Yes, correct for OpenWrt. OpenWrt defaults to dropbear for both server and client. However, more advanced users may optionally choose to replace it with OpenSSH, or install OpenSSH side-by-side, to gain additional features of OpenSSH.

  > root@OpenWrt:~# cat /etc/openwrt_release | grep DISTRIB_RELEASE
  > DISTRIB_RELEASE='25.12.6'

  > root@OpenWrt:~# which ssh
  > /usr/bin/ssh
  > root@OpenWrt:~# ssh -V
  > Dropbear v2025.89
  > root@OpenWrt:~# ldd /usr/bin/ssh
  >  /lib/ld-musl-aarch64.so.1 (0x7f8e120000)
  >  libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x7f8e0b8000)
  >  libc.so => /lib/ld-musl-aarch64.so.1 (0x7f8e120000)

  > root@OpenWrt:~# which dropbear
  > /usr/sbin/dropbear
  > root@OpenWrt:~# dropbear -V
  > Dropbear v2025.89
  > root@OpenWrt:~# ldd /usr/sbin/dropbear
  >  /lib/ld-musl-aarch64.so.1 (0x7fbb900000)
  >  libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x7fbb898000)
  >  libc.so => /lib/ld-musl-aarch64.so.1 (0x7fbb900000)

  > root@OpenWrt:~# apk search openssh
  > openssh-client-10.6_p1-r2
  > openssh-client-utils-10.6_p1-r2
  > openssh-keygen-10.6_p1-r2
  > openssh-moduli-10.6_p1-r2
  > openssh-server-10.6_p1-r2
  > openssh-server-pam-10.6_p1-r2
  > openssh-sftp-avahi-service-10.6_p1-r2
  > openssh-sftp-client-10.6_p1-r2
  > openssh-sftp-server-10.6_p1-r2
  > openssh-sk-helper-10.6_p1-r2
Confirms OpenWrt's dropbear default with concrete command output and notes the optional OpenSSH packages.
krekr 2026-11-05 20:295/10first_handinformative | parent | prev | next [–]

I've just tried with my AsusWRT (3.0.0.4.386_69196) and didn't get it to work

gregor@krekrm3 ~ % ssh [email protected] [email protected]'s password: Permission denied, please try again. [email protected]'s password: Permission denied, please try again. [email protected]'s password: Connection closed by 192.168.1.1 port 22

I've just tried with my AsusWRT (3.0.0.4.386_69196) and didn't get it to work

gregor@krekrm3 ~ % ssh [email protected] [email protected]'s password: Permission denied, please try again. [email protected]'s password: Permission denied, please try again. [email protected]'s password: Connection closed by 192.168.1.1 port 22

First-hand test result on a specific AsusWRT firmware version, directly answering the parent's question.
mkj 2026-11-05 14:438/10informativeinsightful | parent | prev [–]

Dropbear added the 100 character password limit in 2019.77, so earlier versions might be susceptible.

Dropbear added the 100 character password limit in 2019.77, so earlier versions might be susceptible.

Specific version fact from a knowledgeable commenter that directly answers whether Dropbear is affected.
mkj 2026-11-06 03:005/10first_handinformative | root | parent [–]

Nope, they are OK.

Nope, they are OK.

Terse self-correction from the Dropbear author that gives a useful factual update.

Its kind of cool to see such simple to follow logic bugs instead of usual memory safety issues. Each step made sense in isolation.

Its kind of cool to see such simple to follow logic bugs instead of usual memory safety issues. Each step made sense in isolation.

Short but genuine observation about the nature of the logic bugs.
kibwen 2026-11-05 18:447/10insightfulinformative | parent [–]

The terrifying decision for the API to return "*" on failure is a second-order consequence of memory unsafety, because it was a half-baked attempt to mitigate a common source of UB that people were encountering in the wild.

The terrifying decision for the API to return "*" on failure is a second-order consequence of memory unsafety, because it was a half-baked attempt to mitigate a common source of UB that people were encountering in the wild.

Substantive point tying the logic bug back to a historical mitigation for memory unsafety.

i'd say it is more a consequence of not having exceptions or other way of being explicit about error values.

i'd say it is more a consequence of not having exceptions or other way of being explicit about error values.

Short, civil alternative explanation of the root cause of the API design.

That was a contributing factor, but there are plenty of other instances of C APIs that make use of in-band signaling like this. That the API was changed from returning one in-band signal to another is because the original value was causing programs to crash unpredictably due to UB.

That was a contributing factor, but there are plenty of other instances of C APIs that make use of in-band signaling like this. That the API was changed from returning one in-band signal to another is because the original value was causing programs to crash unpredictably due to UB.

Civil, substantive disagreement that adds context on in-band signaling and why the API changed.

Wait so musl, famously a stickler for standards, just changed the behaviour of a POSIX function?

Wait so musl, famously a stickler for standards, just changed the behaviour of a POSIX function?

Genuine, on-topic question about musl changing the behaviour of a POSIX function.

[deleted]
traceroute66 2026-11-05 13:116/10first_handinformative | prev | next [–]

To be fair, I have less than zero sympathy for anyone who runs SSH on any platform without `PasswordAuthentication=no` in 2026.

Its not asking for trouble, it is there will be trouble, not if but when.

As for the people who say "well, I run SSH on port 942329053052". Well guess what, the bots make short work on that. We tried it at $work as an experiment, multiple bots found it in less than a couple of hours. That's why your SSH config must be hardened, preferably running on a hardened OS too.

To be fair, I have less than zero sympathy for anyone who runs SSH on any platform without `PasswordAuthentication=no` in 2026.

Its not asking for trouble, it is there will be trouble, not if but when.

As for the people who say "well, I run SSH on port 942329053052". Well guess what, the bots make short work on that. We tried it at $work as an experiment, multiple bots found it in less than a couple of hours. That's why your SSH config must be hardened, preferably running on a hardened OS too.

Blunt but substantive hardening advice, backed by a first-hand experiment showing bots quickly find non-standard SSH ports.

> Its not asking for trouble, it is there will be trouble, not if but when.

Did any of those bots brute force a password? Because a 20 character random password is comparable in brute force complexity to an SSH key. The experiment you ran was just as valid for SSH keys on that port, and had no bearing on password vs key.

> Its not asking for trouble, it is there will be trouble, not if but when.

Did any of those bots brute force a password? Because a 20 character random password is comparable in brute force complexity to an SSH key. The experiment you ran was just as valid for SSH keys on that port, and had no bearing on password vs key.

Firm, substantive disagreement showing the experiment says nothing about passwords versus keys.
solar_walrus42 2026-11-05 14:10 | rewritten6/10informative | original | root | parent [–]

> Did any of those bots brute force a password?

The point is they found the security by obscurity so-called "hidden" port in less than a couple of hours.

At that point, what they did is irrelevant. The point is that security by obscurity does not work in 2026.

> Because a 20 character random password is comparable in brute force complexity to an SSH key.

No its not.

And even then, SSH key is asymmetric, you're not sending the private key to the server. But with a password you are, which leaves you vulnerable to many attack vectors that simply do not exist with keys.

original comment by traceroute66

> Did any of those bots brute force a password?

The point is they found the security by obscurity so-called "hidden" port in less than a couple of hours.

At that point, what they did is irrelevant. The point is that security by obscurity does not work in 2026.

> Because a 20 character random password is comparable in brute force complexity to an SSH key.

You can't be serious with your comment.

No its not.

And even then, SSH key is asymmetric, you're not sending the private key to the server. But with a password you are, which leaves you vulnerable to many attack vectors that simply do not exist with keys.

Dropped the 'You can't be serious' swipe while keeping the firm disagreement and the asymmetric-key argument.

Other points are fine, but why do you say a 20-character random password isn’t comparable in brute force complexity to an SSH key?

Other points are fine, but why do you say a 20-character random password isn’t comparable in brute force complexity to an SSH key?

Genuine, civil question challenging a specific technical claim.
sandy_bison76 2026-11-05 14:22 | rewritten5/10ideological | original | root | parent | prev | next [–]

> The point is that security by obscurity does not work in 2026.

I feel the opposite, note how most all AI vulns discovered use source code level analysis, empirically keeping source code hidden is more secure.

kerchoff’s principle is misconstrued by an internet that wants to see source code

original comment by TZubiri

> The point is that security by obscurity does not work in 2026.

I feel the opposite, note how most all AI vulns discovered use source code level analysis, empirically keeping source code hidden is more secure.

kerchoff’s principle is misconstrued by a voyeuristic internet that wants to see source code

Kept the contrarian argument about hidden source; dropped the 'voyeuristic' swipe at people who want to read source.

This is a sampling error - we're only hearing about the public disclosures.

(almost) No company actually has secret source code, only source code not publicly viewable. And the LLMs crawling those don't belong to people who make public disclosures.

This is a sampling error - we're only hearing about the public disclosures.

(almost) No company actually has secret source code, only source code not publicly viewable. And the LLMs crawling those don't belong to people who make public disclosures.

Firm, substantive disagreement pointing out survivorship in public disclosures and that private source isn't truly secret.

> LLMs crawling those don't belong to people who make public disclosures

Using LLMs is a great way to empower the governments behind them (US and PRC) to cultivate a garden of undisclosed bugs in your closed commercial codebases.

> LLMs crawling those don't belong to people who make public disclosures

Using LLMs is a great way to empower the governments behind them (US and PRC) to cultivate a garden of undisclosed bugs in your closed commercial codebases.

A strongly cynical but substantive claim about state access to LLM-processed code, aimed at governments rather than at a person.

> > Because a 20 character random password is comparable in brute force complexity to an SSH key.

> You can't be serious with your comment. \n\n No its not.

If you differ in opinion, maybe provide evidence? Especially for a claim like long randomly generated value 1 being absurdly ("can't be serious"-level) weak as compared to long randomly generated value 2

If you mean it's log(62^20,2)≈119 bits of entropy instead of 128+ and so it's theoretically weaker when we build a Dyson sphere around the sun to brute force that key (after somehow obtaining the shadow file, since an online attack would be way too slow), then sure

> > Because a 20 character random password is comparable in brute force complexity to an SSH key.

> You can't be serious with your comment. \n\n No its not.

If you differ in opinion, maybe provide evidence? Especially for a claim like long randomly generated value 1 being absurdly ("can't be serious"-level) weak as compared to long randomly generated value 2

If you mean it's log(62^20,2)≈119 bits of entropy instead of 128+ and so it's theoretically weaker when we build a Dyson sphere around the sun to brute force that key (after somehow obtaining the shadow file, since an online attack would be way too slow), then sure

Firm disagreement backed by a concrete entropy calculation; the Dyson sphere line is wry, not a personal swipe.

How is password auth weaker than key auth? Bugs in auth code can happen in both pathways. And apart from stupidly short passwords both should be equally impossible to brute-force.

In other words, conceptually, at the end if the day, isn’t a key just another name for a password, really?

How is password auth weaker than key auth? Bugs in auth code can happen in both pathways. And apart from stupidly short passwords both should be equally impossible to brute-force.

In other words, conceptually, at the end if the day, isn’t a key just another name for a password, really?

A genuine, on-topic question challenging the premise that key auth is stronger than password auth.
minitech 2026-11-05 14:008/10informativeinsightful | root | parent | next [–]

Password gets sent to the server. Private key does not get sent to the server, and authentication protocol can be forwarded safely, enabling things like ssh-agent, FIDO SSH keys. This is solvable with passwords (see: augmented PAKE), but `PasswordAuthentication` isn’t that. But there are also settings where none of this really matters, yes, to be fair if that’s what you’re referring to.

SSH certificates are cool too.

Password gets sent to the server. Private key does not get sent to the server, and authentication protocol can be forwarded safely, enabling things like ssh-agent, FIDO SSH keys. This is solvable with passwords (see: augmented PAKE), but `PasswordAuthentication` isn’t that. But there are also settings where none of this really matters, yes, to be fair if that’s what you’re referring to.

SSH certificates are cool too.

Clearly explains why key auth differs from password auth, with fair caveats.

password doesn’t get sent to the server, hash does.

but I guess that the hash is always the same unlike with pub priv key mechanism.

password doesn’t get sent to the server, hash does.

but I guess that the hash is always the same unlike with pub priv key mechanism.

A good-faith technical reply and self-correction, kept as written even though the claim is disputable.
mkj 2026-11-05 14:446/10informative | root | parent | next [–]

No. The password gets sent to the server. That said, I still enable passwords on servers, but don't ever share passwords between trust domains.

No. The password gets sent to the server. That said, I still enable passwords on servers, but don't ever share passwords between trust domains.

Firm, correct factual disagreement plus the author's own practice on password reuse across trust domains.

> password doesn’t get sent to the server, hash does.

Kind of irrelevant what gets sent to the server and what happens on the server.

Password is already easily defeatable long before it even leaves your computer, thanks to keyloggers and/or password manager breaches and/or old-school shoulder surfing (assisted in the 2026 era by covert filming and AI).

All of which are not vulnerabilities with hardware-backed keys which you don't even need a Yubikey for these days since most modern computers and smartphones have secure enclaves and/or TPMs. (Yubikeys are still great though).

> password doesn’t get sent to the server, hash does.

Kind of irrelevant what gets sent to the server and what happens on the server.

Password is already easily defeatable long before it even leaves your computer, thanks to keyloggers and/or password manager breaches and/or old-school shoulder surfing (assisted in the 2026 era by covert filming and AI).

All of which are not vulnerabilities with hardware-backed keys which you don't even need a Yubikey for these days since most modern computers and smartphones have secure enclaves and/or TPMs. (Yubikeys are still great though).

Firm, substantive disagreement explaining why client-side threats matter and how hardware-backed keys avoid them.

> How is password auth weaker than key auth?

Well, it's a heck of a lot weaker in this instance (at least when `PasswordAuthentication no`)

> How is password auth weaker than key auth?

Well, it's a heck of a lot weaker in this instance (at least when `PasswordAuthentication no`)

A wry but substantive reply noting that disabling password auth matters for this specific vulnerability; no swipe at anyone.

What people think of as a very strong password often has <100 "security bits" of entropy.

Keys are usually at least 128 bits of entropy, and it's easy to take them up to 256.

Yes, both are a shared secret, but password auth - even with challenge-response - is weaker than key auth exactly because key auth provides more guess resistance.

What people think of as a very strong password often has <100 "security bits" of entropy.

Keys are usually at least 128 bits of entropy, and it's easy to take them up to 256.

Yes, both are a shared secret, but password auth - even with challenge-response - is weaker than key auth exactly because key auth provides more guess resistance.

Civil, substantive answer comparing the entropy of passwords and keys.

But the SSH keys are based on asymmetric crypto, not shared secrets.

But the SSH keys are based on asymmetric crypto, not shared secrets.

Short, on-point correction that SSH keys are asymmetric rather than shared secrets.

That makes it impossible to steal the key if you MITM, but there is still the same chance of guessing it. (Assuming password has as many bits of pure randomness, such as machine generated password.)

That makes it impossible to steal the key if you MITM, but there is still the same chance of guessing it. (Assuming password has as many bits of pure randomness, such as machine generated password.)

Civil, substantive point distinguishing MITM key theft from brute-force guessing odds.

> What people think of as a very strong password often has <100 "security bits" of entropy.

The best password in the world will still be defeated by a simple keylogger or breach of your password manager.

> What people think of as a very strong password often has <100 "security bits" of entropy.

The best password in the world will still be defeated by a simple keylogger or breach of your password manager.

Terse but on-topic point that password strength doesn't help against keyloggers or manager breaches.

2^100 is a lot of things to try though

2^100 is a lot of things to try though

Short but genuine pushback that even 100 bits of entropy is hard to brute-force.

Especially if you have Fail2Ban set up.

Especially if you have Fail2Ban set up.

Short but real point that rate limiting makes brute force even less practical.

> if you have Fail2Ban set up.

Worth noting that if you are running a remotely recent version of SSH it has everything you need built-in. No need for Fail2Ban on SSH these days if your config is fully modernised.

> if you have Fail2Ban set up.

Worth noting that if you are running a remotely recent version of SSH it has everything you need built-in. No need for Fail2Ban on SSH these days if your config is fully modernised.

Useful note that modern OpenSSH has built-in penalties that can replace Fail2Ban.

Fail2Ban is kind of required unless one like running out of sockets and are unburden by high cpu and disk load.

Fail2Ban is kind of required unless one like running out of sockets and are unburden by high cpu and disk load.

Wry but substantive point about why Fail2Ban matters for resource exhaustion from brute-force attempts.

There is regular bot traffic attacking SSH, but it's not "saturate your downlink" level of traffic. If you're getting attacked that heavily then you're being specifically targeted.

There is regular bot traffic attacking SSH, but it's not "saturate your downlink" level of traffic. If you're getting attacked that heavily then you're being specifically targeted.

Plain, on-topic disagreement that distinguishes routine bot traffic from targeted attacks.
Aachen 2026-11-05 14:426/10first_handinformative | root | parent | prev [–]

Huh? I run ssh (OpenSSH) on port 22 with root login enabled and no fail2ban on a single-core laptop cpu from fifteen years ago. The load average is 0.00 even with the small mail and web server that runs on it

What cpu and especially what disk load are you talking about? Also not running out of sockets, not sure if that's RAM-dependent (the box has 2GB)

Huh? I run ssh (OpenSSH) on port 22 with root login enabled and no fail2ban on a single-core laptop cpu from fifteen years ago. The load average is 0.00 even with the small mail and web server that runs on it

What cpu and especially what disk load are you talking about? Also not running out of sockets, not sure if that's RAM-dependent (the box has 2GB)

Firm, first-hand counterexample with a genuine question about the claimed load.
belorn 2026-11-06 03:147/10first_handinformative | root | parent [–]

Working as a sysadmin, and with disk encryption the constant log writes generated from ssh was enough to cause heavy load on severs. Long lasting connections from bots also forced us to increase the maximum number of sockets, as some scan waves was enough to hit the maximum and block other connections. Fail2ban has mitigated those issues to the point where neither has been an issue.

Working as a sysadmin, and with disk encryption the constant log writes generated from ssh was enough to cause heavy load on severs. Long lasting connections from bots also forced us to increase the maximum number of sockets, as some scan waves was enough to hit the maximum and block other connections. Fail2ban has mitigated those issues to the point where neither has been an issue.

First-hand sysadmin experience explaining how SSH bot traffic caused disk and socket load, and how fail2ban helped.

Password reuse is the problem. The password hash is stolen from some other site and brute forced offline.

Password reuse is the problem. The password hash is stolen from some other site and brute forced offline.

Gives a concise, concrete answer: password reuse exposes hashes to offline cracking.

Same issue with key reuse. Unless you have a policy to rotate them and verify which host they login from, someone's unencrypted hard drive from 5 years ago is more likely to have a key than password.

Same issue with key reuse. Unless you have a policy to rotate them and verify which host they login from, someone's unencrypted hard drive from 5 years ago is more likely to have a key than password.

Substantive counterpoint that stale SSH keys carry the same reuse risk as passwords.

> someone's unencrypted hard drive from 5 years ago is more likely to have a key than password.

Even 5+ years ago, hardware-backed SSH keys were a thing (Yubikey). The only difference today is that its 100% easier to do keys properly because of the widespread commoditisation of secure enclaves in desktops and smartphones to support FIDO, its basically free of charge now because you're not obliged to buy a Yubikey.

> someone's unencrypted hard drive from 5 years ago is more likely to have a key than password.

Even 5+ years ago, hardware-backed SSH keys were a thing (Yubikey). The only difference today is that its 100% easier to do keys properly because of the widespread commoditisation of secure enclaves in desktops and smartphones to support FIDO, its basically free of charge now because you're not obliged to buy a Yubikey.

Substantive, civil reply noting hardware-backed keys and how secure enclaves make them cheap and accessible.
ant0ni 2026-11-05 14:37 | removed0/10jokeoff_topiclow_effort | root | parent | prev | next [–]

you claimed my favorite game's character name!

you claimed my favorite game's character name!

A quip about a username that doesn't engage with the password-versus-key question.

[deleted]

100% and with local tool calls many AI systems will do ssh "for you" and end run around any security theatre that you may have going on with LLMs.

100% and with local tool calls many AI systems will do ssh "for you" and end run around any security theatre that you may have going on with LLMs.

Adds a brief but real point that LLM tool calls can use SSH and bypass safeguards; the blunt wording is fine.

I run SSH with password authentication on but the port binds to a ULA address. It’s fine. No need to be so absolutist.

I run SSH with password authentication on but the port binds to a ULA address. It’s fine. No need to be so absolutist.

Firm disagreement backed by a concrete setup: password auth stays on but SSH is reachable only on a ULA address.

> No need to be so absolutist.

Why not ?

Even if you don't want to go all the way to SSH certificates, SSH public-key authentication is laughably easy to do.

I also don't buy the argument "but its on a ULA address". Attacker's old favourite these days is finding a way in (North–South) and then using that as a stepping stone (East–West). Also the general concept of "lower security is OK because its on the LAN" is so 1997.

> No need to be so absolutist.

Why not ?

Even if you don't want to go all the way to SSH certificates, SSH public-key authentication is laughably easy to do.

I also don't buy the argument "but its on a ULA address". Attacker's old favourite these days is finding a way in (North–South) and then using that as a stepping stone (East–West). Also the general concept of "lower security is OK because its on the LAN" is so 1997.

Firm disagreement backed by a real argument about lateral movement and LAN security assumptions.
kccqzy 2026-11-05 14:156/10first_handinformative | root | parent [–]

Yeah because if attackers are already finding a way in, they can easily read my ~/.ssh/id_ed25519. A password is strictly better, although not by much because lateral movement is very easy anyways. The only exception is if the private key is actually on a HSM, which is the case for all my SSH private keys on iOS.

Yeah because if attackers are already finding a way in, they can easily read my ~/.ssh/id_ed25519. A password is strictly better, although not by much because lateral movement is very easy anyways. The only exception is if the private key is actually on a HSM, which is the case for all my SSH private keys on iOS.

Firm disagreement with a real argument about on-disk keys versus passwords and HSM-backed keys; the 'Yeah because' framing is mild.

or password protect your key, at least.

or password protect your key, at least.

Short but relevant suggestion that a passphrase mitigates the stolen key file risk.
solar_walrus42 2026-11-05 14:22 | rewritten5/10informative | original | root | parent | prev [–]

> read my ~/.ssh/id_ed25519.

No excuse for doing that these days.

You can use a Yubikey or you can use your computer's secure enclave or TPM with FIDO at no extra cost.

A password is never, ever "strictly better".

original comment by traceroute66

> read my ~/.ssh/id_ed25519.

No excuse for doing that these days.

You can use a Yubikey or you can use your computer's secure enclave or TPM with FIDO at no extra cost.

A password is never, ever "strictly better". Seriously, you're just making shit up now.

Useful advice and firm disagreement kept; removed the accusation of making things up.

Your comment is self-contradictory. In one sentence you say “these days” and in another you say “never, ever.” These are contradictory. What if your computer is so old that it doesn’t have a TPM?

I was already pointing out that if you have secure enclave for your private key you should use key-based authentication. That’s what I do on iOS. Otherwise a password is better than your private key in plain text on disk. So much malware steal your private SSH keys (along with your other saved passwords and browser cookies); no malware that I know of performs social engineering and tricks you into giving up your SSH passwords.

Your comment is self-contradictory. In one sentence you say “these days” and in another you say “never, ever.” These are contradictory. What if your computer is so old that it doesn’t have a TPM?

I was already pointing out that if you have secure enclave for your private key you should use key-based authentication. That’s what I do on iOS. Otherwise a password is better than your private key in plain text on disk. So much malware steal your private SSH keys (along with your other saved passwords and browser cookies); no malware that I know of performs social engineering and tricks you into giving up your SSH passwords.

Firm disagreement with a substantive threat-model argument: malware steals plaintext keys, while password phishing via malware is rare.

Running on that port would be perfectly safe as it's outside the range of valid port numbers.

Running on that port would be perfectly safe as it's outside the range of valid port numbers.

A wry but accurate correction that port numbers top out at 65535, with no personal jab.

This particular bug works even with password authentication turned off.

This particular bug works even with password authentication turned off.

Short, direct correction that adds an important fact about the bug's scope.

Where did you get that idea? The entire bug is about password hashing. With password auth off, that's not even attempted.

Where did you get that idea? The entire bug is about password hashing. With password auth off, that's not even attempted.

Firm, direct disagreement with a concrete technical correction about the bug's scope.
kijin 2026-11-05 14:077/10informativeinsightful | root | parent [–]

The exploit works for a user account that has no password. This is often confused with turning off password auth, especially in containers that have only one usable account.

It's not just the parent commenter. I'm sure somebody out there is going to remove their account password instead of editing their sshd_config in an attempt to "patch" this vulnerability, actually exacerbating the problem.

The exploit works for a user account that has no password. This is often confused with turning off password auth, especially in containers that have only one usable account.

It's not just the parent commenter. I'm sure somebody out there is going to remove their account password instead of editing their sshd_config in an attempt to "patch" this vulnerability, actually exacerbating the problem.

Clarifies the difference between no password and disabled password auth, and warns of a counterproductive fix.

Bug > are password logins still on? they are by default

You > This particular bug works even with password authentication turned off.

No. The bug is related to password authentication. If you do not have password authentication then you are not affected.

What freaks some people out is they chose parameters where they do have password authentication but they expect to always fail. That's a bad choice, like OK you did fit a cheap manual keyhole, but you threw the keys away thinking now nobody can open that - you realise somebody with a cheap jiggler can open it right ?

Bug > are password logins still on? they are by default

You > This particular bug works even with password authentication turned off.

No. The bug is related to password authentication. If you do not have password authentication then you are not affected.

What freaks some people out is they chose parameters where they do have password authentication but they expect to always fail. That's a bad choice, like OK you did fit a cheap manual keyhole, but you threw the keys away thinking now nobody can open that - you realise somebody with a cheap jiggler can open it right ?

Firm disagreement with a clear explanation of the misconfiguration and an apt analogy; 'you realise' is part of the analogy rather than a swipe.
znnajdla 2026-11-05 14:27 | removed0/10snarklow_effort | root | parent [–]

read the article again

read the article again

A curt brush-off that says nothing about what the article claims, so there is no point left to preserve.

> This particular bug works even with password authentication turned off.

Does it ?

Somebody here has already said "You can set PasswordAuthentication=no or update to remediate"

We don't use musl at $work anyway, so I've no means to test.

> This particular bug works even with password authentication turned off.

Does it ?

Somebody here has already said "You can set PasswordAuthentication=no or update to remediate"

We don't use musl at $work anyway, so I've no means to test.

Genuine, good-faith question citing a conflicting claim in the thread about the mitigation.

I mean there are 65k possible ports so it's the equivalent of guessing an 16 bit password, i.e. two (extended) ascii characters. It's not very surprising that 'multiple bots' found it: a single `nmap` command will give you the open ports in a few seconds.

I mean there are 65k possible ports so it's the equivalent of guessing an 16 bit password, i.e. two (extended) ascii characters. It's not very surprising that 'multiple bots' found it: a single `nmap` command will give you the open ports in a few seconds.

Civil, substantive point framing port obscurity as about 16 bits of search space, easily scanned.

This is an unpleasant era to live through, but at least on the other side we may actually have real tools for writing secure software.

This is an unpleasant era to live through, but at least on the other side we may actually have real tools for writing secure software.

A brief, sincere opinion on where software security is heading, with nothing a moderator would ask the author to drop.
hypfer 2026-11-05 12:416/10questioninsightful | prev | next [–]

What is out there that uses musl without PAM and hosts an OpenSSH SSHd with PasswordAuthentication?

As in: "Actually out there"

The hypothetical Alpine exists, yes, but what runs that like that?

What is out there that uses musl without PAM and hosts an OpenSSH SSHd with PasswordAuthentication?

As in: "Actually out there"

The hypothetical Alpine exists, yes, but what runs that like that?

Genuine, substantive question probing the real-world exposure of the vulnerability's preconditions.
Bender 2026-11-05 12:518/10first_handinformative | parent | next [–]

What is out there that uses musl without PAM and hosts an OpenSSH SSHd with PasswordAuthentication?

Possibly public SFTP servers. I have allowed null passwords in the past for anonymous public SFTP and I run Alpine Linux exclusively for my servers both VM's and physical. The shadow package (PAM) did not get installed by default. I installed it but I had to specify it in the installation so there are likely people without it. There is probably a way to find them on shodan.io.

I will activate one for people to play around with for today:

    sftp [email protected]

There's no firewall and no password. Hack away HN.

    PasswordAuthentication yes
    PermitEmptyPasswords yes

cd /pub

get so*

What is out there that uses musl without PAM and hosts an OpenSSH SSHd with PasswordAuthentication?

Possibly public SFTP servers. I have allowed null passwords in the past for anonymous public SFTP and I run Alpine Linux exclusively for my servers both VM's and physical. The shadow package (PAM) did not get installed by default. I installed it but I had to specify it in the installation so there are likely people without it. There is probably a way to find them on shodan.io.

I will activate one for people to play around with for today:

    sftp [email protected]

There's no firewall and no password. Hack away HN.

    PasswordAuthentication yes
    PermitEmptyPasswords yes

cd /pub

get so*

First-hand answer with a live demo server and concrete config, directly addressing the question.

A large number of Docker containers?

Edit: Sure, a small minority presumably run sshd under these conditions. But a small minority times the number of Alpine containers running in the world gets to a large absolute number.

Edit 2: You really don't need to look very hard to find public container examples at million-download scale.

A large number of Docker containers?

Edit: Sure, a small minority presumably run sshd under these conditions. But a small minority times the number of Alpine containers running in the world gets to a large absolute number.

Edit 2: You really don't need to look very hard to find public container examples at million-download scale.

Substantive answer arguing that a small share of a huge Alpine container population is still a large exposure.

Many, many Docker containers rely on "docker exec" or the kubectl equivalent to gain CLI access when needed. To have sshd in an Alpine Docker container, you're generally need to be running init and a bunch of demons, not just a single standalone service.

I'm guessing in practice relatively few people are actually exposed. But if they are, it's catastrophic.

Many, many Docker containers rely on "docker exec" or the kubectl equivalent to gain CLI access when needed. To have sshd in an Alpine Docker container, you're generally need to be running init and a bunch of demons, not just a single standalone service.

I'm guessing in practice relatively few people are actually exposed. But if they are, it's catastrophic.

Substantive explanation of why sshd is uncommon in Alpine containers and how that limits exposure.

Docker containers run sshd?

Docker containers run sshd?

Short, genuine question that pushes back on the parent's suggestion.
civic_tapir78 2026-11-05 14:56 | rewritten3/10questionlow_effort | original | root | parent | prev | next [–]

> Edit 2: You really don't need to look very hard to find public container examples at million-download scale.

It would be super helpful if you would answer a "what, specifically" question with a "this, specifically" itemized list.

original comment by hypfer

> Edit 2: You really don't need to look very hard to find public container examples at million-download scale.

It would be super helpful if you would answer a "what, specifically" question with a "this, specifically" itemized list or not answer at all.

This is not helpful.

Kept the fair request for specific examples and dropped the rebuke telling the commenter not to answer at all.

Docker containers that run sshds?

Docker containers that run sshds?

A short, genuine question that pushes back on the parent's claim.

Agent sandboxes today probably

Agent sandboxes today probably

Terse but direct, plausible answer to the parent's question about real-world deployments.

From the other thread’s source [1], it affects musl without PAM. You can set PasswordAuthentication=no or update to remediate.

[1] https://hachyderm.io/@damienmiller/117563026759098135

From the other thread’s source [1], it affects musl without PAM. You can set PasswordAuthentication=no or update to remediate.

[1] https://hachyderm.io/@damienmiller/117563026759098135

Concise, sourced details on affected configurations and remediation.

Wow. I wonder which of the common fuzzers would have found this a decade ago. If literally any password over 256 chars works, that means nobody has ever fuzzed openssh on musl linux

Wow. I wonder which of the common fuzzers would have found this a decade ago. If literally any password over 256 chars works, that means nobody has ever fuzzed openssh on musl linux

Makes a substantive observation about the gap in fuzzing coverage of OpenSSH on musl.

I expected let another AI slope website and instead that was a real human writing about what they found. Sure they used LLMs to find this, but they used their real fingers to write this. See, this isn’t that difficult, and it’s a lot more enjoyable to read.

I expected let another AI slope website and instead that was a real human writing about what they found. Sure they used LLMs to find this, but they used their real fingers to write this. See, this isn’t that difficult, and it’s a lot more enjoyable to read.

Mild opinion praising the human-written style; a minor but genuine reaction with no personal attack.

It's definitely an AI slop website even if a human wrote the text. I found it pretty hard to get through the garbage animations.

It's definitely an AI slop website even if a human wrote the text. I found it pretty hard to get through the garbage animations.

Blunt criticism aimed at the site's animations, not a person, and it answers the parent's claim.

I don't think a human wrote the text either, it's likely edited after generation: https://paste.c-net.org/ExtortSincere.

> mostly the boring parts where it talks to libc, like the password code

Is clearly not human.

I don't think a human wrote the text either, it's likely edited after generation: https://paste.c-net.org/ExtortSincere.

> mostly the boring parts where it talks to libc, like the password code

Is clearly not human.

A firm claim that the text is AI-generated, backed by a link and a quoted example; aimed at the text, not a person.

I don't trust Pangram, but the writing is a bit weird.

I don't trust Pangram, but the writing is a bit weird.

Short but genuine disagreement about the AI detector's reliability, with no swipe at anyone.

Pangram false positives

Pangram false positives

Terse but real disagreement: it says the AI-detector result linked by the parent may be a false positive.
deft_dingo9 2026-11-06 04:12 | rewritten2/10snarklow_effort | original | root | parent | prev [–]

It's just a LLM instructed to write everything lowercase.

original comment by efilife

It's just a LLM instructed to write everything lowercase. Fooled you

Keeps the claim that the text is LLM-written and drops the "Fooled you" jab at the parent commenter.

At this point it feels to me we aren’t discovering security bugs but a decade of planted backdoors.

At this point it feels to me we aren’t discovering security bugs but a decade of planted backdoors.

A terse, cynical opinion about the origin of security bugs, aimed at no specific person, so it stays as is.

Never attribute to malice that which yada yada yada

And like who even needed to plant back doors? If you're the NSA you already had an agent swarm in 1993, but it was made out of dudes from Berkeley and New Haven instead of racks in Prince William County and 印西 (and they were literal secret agents I guess, pretty metal). You can get back doors just by foraging, no need for agriculture.

Never attribute to malice that which yada yada yada

And like who even needed to plant back doors? If you're the NSA you already had an agent swarm in 1993, but it was made out of dudes from Berkeley and New Haven instead of racks in Prince William County and 印西 (and they were literal secret agents I guess, pretty metal). You can get back doors just by foraging, no need for agriculture.

Wry but substantive reply arguing that intelligence agencies could find existing bugs rather than plant backdoors.

No the biggest lesson of all this AI stuff to me is that we are just not smart enough as programmers

If you think about it that’s the whole idea of neural nets

We aren’t smart enough to design the function we want by hand as programmers so we need to learn it through weights

This lesson just keeps recurring in many different ways

No the biggest lesson of all this AI stuff to me is that we are just not smart enough as programmers

If you think about it that’s the whole idea of neural nets

We aren’t smart enough to design the function we want by hand as programmers so we need to learn it through weights

This lesson just keeps recurring in many different ways

A firm disagreement with a broad but real argument about the limits of hand-written programs; nothing to moderate.

[deleted]
octoberfranklin 2026-11-05 14:291/10metalow_effortkept_for_replies | parent | prev | next [–]

Be careful, saying this gets you downvoted like https://news.ycombinator.com/item?id=50364646

Be careful, saying this gets you downvoted like https://news.ycombinator.com/item?id=50364646

A thin meta remark about downvotes, but not hostile, and the reply needs it to make sense.

[deleted]

You have considerably underestimated both how fallible humans are, and how high their tolerance is for risks which aren't immediately visible.

This isn't even just the software industry, humans are like this about everything. Imagine explaining to something rational that (a) humans didn't realise that they won't be able to competently operate a motor vehicle while watching TokTok dances, and (b) when we finally made that illegal because it's such an obviously bad idea they would keep doing it anyway.

This is why those "What if it builds SkyNet?" conspiracies were so stupid. Why on Earth would you build SkyNet when you can just invent Weeaboo and the idiot humans will die playing ?

You have considerably underestimated both how fallible humans are, and how high their tolerance is for risks which aren't immediately visible.

This isn't even just the software industry, humans are like this about everything. Imagine explaining to something rational that (a) humans didn't realise that they won't be able to competently operate a motor vehicle while watching TokTok dances, and (b) when we finally made that illegal because it's such an obviously bad idea they would keep doing it anyway.

This is why those "What if it builds SkyNet?" conspiracies were so stupid. Why on Earth would you build SkyNet when you can just invent Weeaboo and the idiot humans will die playing ?

Firm disagreement with the backdoor theory, with wry humor aimed at humanity in general rather than at a commenter.
ImPostingOnHN 2026-11-05 14:177/10informativeinsightful | root | parent | next [–]

True, but also, that is exactly the pretext that would allow someone to maliciously, intentionally commit a backdoor.

There was even a contest for hiding/obfuscating malicious code, e.g. as an honest mistake: https://www.underhanded-c.org/ . And what do you know, the most recent, common approach shown on the page, is: improper comparison inputs that give unexpected comparison results, which is... well... this submission.

True, but also, that is exactly the pretext that would allow someone to maliciously, intentionally commit a backdoor.

There was even a contest for hiding/obfuscating malicious code, e.g. as an honest mistake: https://www.underhanded-c.org/ . And what do you know, the most recent, common approach shown on the page, is: improper comparison inputs that give unexpected comparison results, which is... well... this submission.

Makes a substantive point that honest-looking bugs give cover for backdoors, with a relevant link to the Underhanded C Contest.

Yes, "this looks like an underhanded c challenge" was my first thought when reading this. See one mistake by one project, inject the matching intentional mistake into another project (that also conveniently fixes some known bug), and boom - plausibly deniable backdoor.

If I had a free afternoon, I'd ask a bunch of agents to track the people responsible and their other contributions (but usually the scheme is "contribute valuable patches to build reputation, and only inject an exploit when safe to do so").

Yes, "this looks like an underhanded c challenge" was my first thought when reading this. See one mistake by one project, inject the matching intentional mistake into another project (that also conveniently fixes some known bug), and boom - plausibly deniable backdoor.

If I had a free afternoon, I'd ask a bunch of agents to track the people responsible and their other contributions (but usually the scheme is "contribute valuable patches to build reputation, and only inject an exploit when safe to do so").

Speculates about how plausibly deniable backdoors work as a technique, without accusing any named person.

[flagged]

actually this looks very underhanded indeed, what are the odds they pick the same sentinel character?!

actually this looks very underhanded indeed, what are the odds they pick the same sentinel character?!

Wry speculation about a technical detail, the shared sentinel character, and it engages the backdoor suggestion without attacking anyone.

Like 1 in 5? The set of go-to characters for that kind of thing is pretty small.

Like 1 in 5? The set of go-to characters for that kind of thing is pretty small.

Short, reasonable counterpoint with a plain estimate explaining why the coincidence isn't suspicious.

Why restrict yourself to a single sentinel character?

Why restrict yourself to a single sentinel character?

Short genuine question that suggests a real alternative to single-character sentinels.

Pretty big? For this purpose, my instinct would be to select an ASCII character that's not alphanumeric, not directional, and not a common separator, and that's like, what, a set of 11 characters?

Pretty big? For this purpose, my instinct would be to select an ASCII character that's not alphanumeric, not directional, and not a common separator, and that's like, what, a set of 11 characters?

Good-faith counterargument with simple reasoning about how few plausible sentinel characters exist.

The musl vulnerability looks a lot like an intentional backdoor, doesn’t it?

The musl vulnerability looks a lot like an intentional backdoor, doesn’t it?

A short, speculative question about the vulnerability's origin; it is on-topic and aims at no person in the thread.
steady_swift32 2026-11-05 15:56 | rewritten3/10insinuation | original | parent [–]

Choosing * instead of NULL is a very odd choice. Of all the return values they could've picked...

original comment by Cakez0r

Choosing * instead of NULL is indeed very fishy coincidence. Of all the return values they could've picked... What are the odds?

Kept the technical oddity but dropped the insinuation that the maintainers planted a backdoor.

Also the coincidence of 3 weeks later adding "oh, no passwords above 256 chars" making it easy to trigger

Also the coincidence of 3 weeks later adding "oh, no passwords above 256 chars" making it easy to trigger

Adds a specific timeline detail to the discussion of the suspicious change, with no attack on anyone in the thread.
gquere 2026-11-05 14:154/10questioninformative | prev | next [–]

Why is this not yet published on the official website and why is there no CVE? https://www.openssh.org/security.html

Edit: My bad as pointed below this is not in OpenSSH but in musl and it appears in the relase notes https://www.openssh.org/releasenotes.html#10.6p2. I had scrolled straight to the bottom of the website because the animations annoyed me. Still would like a CVE for proper tracking though!

Why is this not yet published on the official website and why is there no CVE? https://www.openssh.org/security.html

Edit: My bad as pointed below this is not in OpenSSH but in musl and it appears in the relase notes https://www.openssh.org/releasenotes.html#10.6p2. I had scrolled straight to the bottom of the website because the animations annoyed me. Still would like a CVE for proper tracking though!

Genuine question with a self-correction pointing to the musl release notes and a fair request for a CVE.

OpenSSH considers this to be a vulnerability in musl and not OpenSSH, but shipped a fix anyways since that helps mitigate impact... Whether you agree with that or not, it's up to you :)

OpenSSH considers this to be a vulnerability in musl and not OpenSSH, but shipped a fix anyways since that helps mitigate impact... Whether you agree with that or not, it's up to you :)

Directly answers the question by explaining OpenSSH's position that the flaw lies in musl, which explains the missing advisory.
mkj 2026-11-05 15:094/10informative | parent | prev [–]

Probably because mitre take days to weeks to give out CVE numbers (if ever). Just get the fix out there.

Probably because mitre take days to weeks to give out CVE numbers (if ever). Just get the fix out there.

Terse but real explanation of CVE assignment delays and a case for shipping the fix first.

A common complaint I've seen (and I experience on occasion) is AI agents being a bit silly in their overprotective coding style. They nit pick and catch every single little error. This can be frustrating since a lot of the time they are trying to handle edge cases that are so unlikely to happen, yet to cover them requires significant additional code.

This might be the beginning of the reason we shouldn't be reading the code. It's like in chess engines where the players complain about how weird, alien and unintuitive the moves are. But 100 steps down the line the engine finds some edge case exploit that gives it enough advantage to win.

So we may be crossing a line, right now, where the non-intuitive alien code being generated by agents is seeing edge cases down the line that might allow exploits. And if we "correct" the agent, we may be leaving ourselves open to exploits we don't see and have difficulty understanding.

A common complaint I've seen (and I experience on occasion) is AI agents being a bit silly in their overprotective coding style. They nit pick and catch every single little error. This can be frustrating since a lot of the time they are trying to handle edge cases that are so unlikely to happen, yet to cover them requires significant additional code.

This might be the beginning of the reason we shouldn't be reading the code. It's like in chess engines where the players complain about how weird, alien and unintuitive the moves are. But 100 steps down the line the engine finds some edge case exploit that gives it enough advantage to win.

So we may be crossing a line, right now, where the non-intuitive alien code being generated by agents is seeing edge cases down the line that might allow exploits. And if we "correct" the agent, we may be leaving ourselves open to exploits we don't see and have difficulty understanding.

A substantive, good-faith speculation on whether agents' defensive code guards against exploits humans can't anticipate.

The answer is not to reduce our understanding. That is the worst possible conclusion we could draw from what we're seeing. The answer is to increase it.

The answer is not to reduce our understanding. That is the worst possible conclusion we could draw from what we're seeing. The answer is to increase it.

Firm, on-topic disagreement with the parent's suggestion that we stop reading code; no swipe at anyone.

> non-intuitive alien code being generated by agents

You're saying it as if humans can't write that too.

> non-intuitive alien code being generated by agents

You're saying it as if humans can't write that too.

Terse but real pushback: humans also write non-intuitive code, with no attack on the parent.
draxen 2026-11-05 13:085/10first_handinformative | prev | next [–]

I avoided using alpine-based containers ever since finding out a lot of Linux server software is actually Linux + SystemD software. The union set API of both Linux and SystemD seems to be the default "Linux API" today.

I avoided using alpine-based containers ever since finding out a lot of Linux server software is actually Linux + SystemD software. The union set API of both Linux and SystemD seems to be the default "Linux API" today.

A substantive, good-faith observation from experience about software depending on systemd APIs beyond Linux itself.

Container images rarely ship with an init service of any kind (systemd or otherwise); the expectation is that your application is the root process. This includes Alpine-based images.

Container images rarely ship with an init service of any kind (systemd or otherwise); the expectation is that your application is the root process. This includes Alpine-based images.

Substantive factual correction explaining that container images generally run without an init system.

but alpine famously does not use systemd but openrc?

but alpine famously does not use systemd but openrc?

Short, genuine question pointing out a relevant fact about Alpine's init system.

That's their point. The software they want to use often assumes its being run on a platform with systemd, so running that software on Alpine which makes a point to not use systemd causes challenges for them compared to using a different distro.

That's their point. The software they want to use often assumes its being run on a platform with systemd, so running that software on Alpine which makes a point to not use systemd causes challenges for them compared to using a different distro.

Clears up a misreading of the earlier comment in good faith.

I use alpine-based containers whenever I can just to keep the world safer from SystemD rotting Linux.

Never enabled sshd there... except on my PostmarketOS PineBook Pro... oh shit!

I use alpine-based containers whenever I can just to keep the world safer from SystemD rotting Linux.

Never enabled sshd there... except on my PostmarketOS PineBook Pro... oh shit!

Opinionated, wry anti-SystemD aside aimed at software rather than people, with a self-deprecating punchline.

What is the argument against systemd?

Is it mainly that it is too big?

I feel like everybody still loves the unix philosophy, but the previous tools were too bad, systemd just filled user demand for easier tools.

The moment someone makes a pid 1 software that is good and still modular, open and pluggable I think that will be an instant success.

What is the argument against systemd?

Is it mainly that it is too big?

I feel like everybody still loves the unix philosophy, but the previous tools were too bad, systemd just filled user demand for easier tools.

The moment someone makes a pid 1 software that is good and still modular, open and pluggable I think that will be an instant success.

Genuine question about the case against systemd plus a good-faith view, though it drifts from the story.

Yes the other tools at the time were pretty bad, but after and around the time systemd came out, they did get much better than the sysv-init days.

Check out dinit, it's basically the same declarative philosophy as systemd, but without the bloat, and with a much simpler config API. Or s6 if you specifically care about "modular", though unless you're an embedded dev, you probably don't actually want that.

Yes the other tools at the time were pretty bad, but after and around the time systemd came out, they did get much better than the sysv-init days.

Check out dinit, it's basically the same declarative philosophy as systemd, but without the bloat, and with a much simpler config API. Or s6 if you specifically care about "modular", though unless you're an embedded dev, you probably don't actually want that.

Substantive reply with concrete alternatives to systemd (dinit, s6) and when each one fits.
sandy_vole67 2026-11-05 17:10 | rewritten4/10ideological | original | root | parent | prev | next [–]

Parts of it are good, like systemd-boot, parts of it are horrendous, like systemd-logind.

But systemd is not a technical problem, it's Redhat (and IBM) having too much control over what happens to the init system.

And of course there is Pottering and his Microsoft stint, then another stint to enforce age verification on Linux.

original comment by sam_lowry_

Parts of it are good, like systemd-boot, parts of it are horrendous, like systemd-logind.

But systemd is not a technical problem, it's Redhat (and IBM) having too much control over what happens to the init system.

And of course there is Pottering and his vile Microsoft stint, then another vile stint to enforce age verification on Linux.

Dropped the 'vile' swipes at a named person while keeping the governance argument and component opinions.

Eh, HNers are just noisier and crankier than most. The silent majority are fine with (and even like) systemd. I have no desire to go back to SysV init.

Eh, HNers are just noisier and crankier than most. The silent majority are fine with (and even like) systemd. I have no desire to go back to SysV init.

Blunt opinion on systemd's reception with a real position; the mild generalization about HN is not a swipe worth editing.
temp123477 2026-11-06 04:412/10questionlow_effort | prev | next [–]

One h or two?

One h or two?

Short, genuine question about the mismatch between the title's spelling and the URL's.

The last few weeks have made me feel very good about my policy of zero internet-accessible ssh ports, ever. On AWS, use SSM. For a VPS or home server, access everything over WireGuard.

The last few weeks have made me feel very good about my policy of zero internet-accessible ssh ports, ever. On AWS, use SSM. For a VPS or home server, access everything over WireGuard.

Practical, concrete policy for reducing SSH exposure, relevant to the story.

Such solutions always felt like swapping one risk for another. With good separation (running the VPN on a separate host from the SSH login, otherwise a bug in the VPN solution still might result in a shell on the box), at best you'd need two vulnerabilities before someone gets in, but it's not fundamentally different unless you find a way simpler protocol where you can be more certain that it's secure than ssh is (for all its scrutiny and track record of no pre-auth bugs for decades)

Putting services behind ssh forwarding was the way to secure something you're worried about rather than the other way around, but Wireguard actually makes a good argument nowadays. Iirc they did proofs for the cryptography and it has good design in terms of attack surface, and it's old/mature enough now that I'll say it's probably better than exposing SSH. On the other hand, when this thing running in your kernel gets compromised, you're slightly worse off than with someone logging into a passwordless service account as in the current bug

Such solutions always felt like swapping one risk for another. With good separation (running the VPN on a separate host from the SSH login, otherwise a bug in the VPN solution still might result in a shell on the box), at best you'd need two vulnerabilities before someone gets in, but it's not fundamentally different unless you find a way simpler protocol where you can be more certain that it's secure than ssh is (for all its scrutiny and track record of no pre-auth bugs for decades)

Putting services behind ssh forwarding was the way to secure something you're worried about rather than the other way around, but Wireguard actually makes a good argument nowadays. Iirc they did proofs for the cryptography and it has good design in terms of attack surface, and it's old/mature enough now that I'll say it's probably better than exposing SSH. On the other hand, when this thing running in your kernel gets compromised, you're slightly worse off than with someone logging into a passwordless service account as in the current bug

Substantive analysis of the security tradeoffs between VPN-gating and exposing SSH directly.

One thing that makes me feel a lot more confident about exposing a WireGuard port is it’s completely invisible unless you have the host’s public key. Nothing will show up on a port scan.

Its security model doesn’t rely on that fact of course, but it’s the cherry on top.

One thing that makes me feel a lot more confident about exposing a WireGuard port is it’s completely invisible unless you have the host’s public key. Nothing will show up on a port scan.

Its security model doesn’t rely on that fact of course, but it’s the cherry on top.

Makes a substantive point about WireGuard's silence to port scans as defense in depth.
Kotlopou 2026-11-05 14:563/10off_topicinformative | prev | next [–]

Tangent: Pangram says the text is 60-ish% AI-generated. Not that surprising given the look of the page and the work being done with AI in the first place, but it is interesting to me because this doesn't look like your standard wall-of-text AI purple prose. So it's notable that a) you can generate convincing text in this style and b) Pangram detects it anyway.

Tangent: Pangram says the text is 60-ish% AI-generated. Not that surprising given the look of the page and the work being done with AI in the first place, but it is interesting to me because this doesn't look like your standard wall-of-text AI purple prose. So it's notable that a) you can generate convincing text in this style and b) Pangram detects it anyway.

A self-declared tangent, but a civil and genuine observation about AI text detection.

Panagram is just guessing.

Panagram is just guessing.

Terse but plain disagreement about the AI detector's reliability.
chekibreki 2026-11-05 14:186/10informativefirst_hand | prev | next [–]

PSA for OpenWrt users: If you are getting the post quantum message on login which points to openssh.com, you are likely using dropbear nonetheless. Check in System->Software. I was confused and worried why I seemingly had the vulnerable OpenSSH installed, but its just dropbear using an OpenSSH link.

So AFAIU, you are fine.

PSA for OpenWrt users: If you are getting the post quantum message on login which points to openssh.com, you are likely using dropbear nonetheless. Check in System->Software. I was confused and worried why I seemingly had the vulnerable OpenSSH installed, but its just dropbear using an OpenSSH link.

So AFAIU, you are fine.

Practical, first-hand tip that helps OpenWrt users tell whether they are actually affected.

The bugs and commits that "fix" them reveal somewhat of a shitshow for how musl is managed. All projects have bugs, but I can't help but question why the wheel was reinvented here. Many toes have been stubbed along the way.

The bugs and commits that "fix" them reveal somewhat of a shitshow for how musl is managed. All projects have bugs, but I can't help but question why the wheel was reinvented here. Many toes have been stubbed along the way.

Strong criticism of how the musl project is managed, aimed at the project rather than a person, and it raises a real question about reinventing libc.
p4bl0 2026-11-05 15:286/10first_handinformative | prev | next [–]

Nice web site. However, I've got a server with 14 alpine-based docker containers in production. Among them 13 are widely distributed images (like web server, databases, etc.) and one is a custom build image. Only one of them have an SSH server publicly exposed, Forgejo, but it's its own internal SSH server which only accept public keys and Git commands. On the internal network, only 3 of respond to SSH. I haven't tweaked their default SSH config. None were vulnerable to this attack.

Nice web site. However, I've got a server with 14 alpine-based docker containers in production. Among them 13 are widely distributed images (like web server, databases, etc.) and one is a custom build image. Only one of them have an SSH server publicly exposed, Forgejo, but it's its own internal SSH server which only accept public keys and Git commands. On the internal network, only 3 of respond to SSH. I haven't tweaked their default SSH config. None were vulnerable to this attack.

First-hand test results from a real production setup that add useful context on how far the vulnerability reaches.
dwedge 2026-11-06 00:018/10first_handinformative | parent [–]

I tested an unpatched forgejo (enabling passwords temporarily, and even adding root to permitted users) and couldn't trigger this.

ETA: I could trigger it. You need to enable passwords and add a second user with a valid password, then git@ works. Obviously this still means the default forgejo is safe as you say.

I tested an unpatched forgejo (enabling passwords temporarily, and even adding root to permitted users) and couldn't trigger this.

ETA: I could trigger it. You need to enable passwords and add a second user with a valid password, then git@ works. Obviously this still means the default forgejo is safe as you say.

First-hand test of the exploit against Forgejo, with specific conditions for triggering it and a self-correction.

They list OpenWRT as affected -- but by default OpenWRT runs dropbear not OpenSSH.

Dropbear is unaffected.

They list OpenWRT as affected -- but by default OpenWRT runs dropbear not OpenSSH.

Dropbear is unaffected.

Concise, substantive factual correction about OpenWRT's default SSH server.

Did we declare "break the world Thursday" and nobody told me? I feel like it's three weeks in a row that we've had a once-in-a-decade security issue pop up.

Did we declare "break the world Thursday" and nobody told me? I feel like it's three weeks in a row that we've had a once-in-a-decade security issue pop up.

Lightly joking but makes a real observation about the recent run of major security issues.

I think we should get used to eternal "break the world Thursday", just like our predecessors had to come to terms with Eternal September [0].

[0] https://en.wikipedia.org/wiki/Eternal_September

I think we should get used to eternal "break the world Thursday", just like our predecessors had to come to terms with Eternal September [0].

[0] https://en.wikipedia.org/wiki/Eternal_September

A wry but on-topic observation with a useful historical reference.
pluc 2026-11-05 12:361/10low_effort | prev | next [–]

A+ name

A+ name

Terse, harmless appreciation of the name; nothing a moderator would object to.
hypfer 2026-11-05 12:44 | removed0/10jokelow_effort | parent [–]

A* name

A* name

A pun on the parent comment with no substantive content.
kleiba2 2026-11-05 12:54 | removed0/10jokelow_effort | root | parent [–]

A{256,} name

A{256,} name

A regex pun on the parent's joke with no substance beyond the wordplay.
henry700 2026-11-05 13:01 | removed0/10jokelow_effort | root | parent [–]

*

*

A lone asterisk riffing on the parent's regex joke adds no content.
mceachen 2026-11-05 15:04 | removed0/10low_effort | root | parent [–]

*0

*0

A bare typo-style correction with no content a reader can use.

We use Alpine quite a bit so I built our team ssh-audit[1]. Feel free to use it if you want.

1. https://news.ycombinator.com/item?id=50383006

We use Alpine quite a bit so I built our team ssh-audit[1]. Feel free to use it if you want.

1. https://news.ycombinator.com/item?id=50383006

Shares a relevant tool built from first-hand need, offered in good faith.

It's time to switch to OpenBSD

It's time to switch to OpenBSD

Terse but a real opinion on the vulnerability's implications, so it stays under the keep rules.

Thanks you to the team that found this. Exceptionally well explained and funny at the same time. What is most important is how people just checking stuff( remember the .xz hack that was tried?) has save the internets ass so many times. We should give good security researchers UBI for life for just doing good work.

Thanks you to the team that found this. Exceptionally well explained and funny at the same time. What is most important is how people just checking stuff( remember the .xz hack that was tried?) has save the internets ass so many times. We should give good security researchers UBI for life for just doing good work.

Good-faith appreciation with a real point about the value of independent security research, referencing the xz backdoor.
mkj 2026-11-05 15:287/10informativeinsightful | prev | next [–]

It would be nice if the passwd crypt functions didn't have timing dependence on password length. Then there'd never need to be hacky workarounds like what musl did, and various tricks to avoid exposing whether users exist on systems.

No idea if it's likely to happen though.

It would be nice if the passwd crypt functions didn't have timing dependence on password length. Then there'd never need to be hacky workarounds like what musl did, and various tricks to avoid exposing whether users exist on systems.

No idea if it's likely to happen though.

Substantive technical point about password hashing timing leaks and user enumeration.

So, nobody fuzz tested sshd before?!?!

So, nobody fuzz tested sshd before?!?!

Short but genuine question about whether sshd had been fuzz tested.

> So, nobody fuzz tested sshd before?!?!

OpenBSD probably did (which doesn't use musl), RedHat probably did (or some other distro using glibc), but they all don't use musl.

> So, nobody fuzz tested sshd before?!?!

OpenBSD probably did (which doesn't use musl), RedHat probably did (or some other distro using glibc), but they all don't use musl.

Answers the parent by explaining the bug sits in a musl-specific path that big fuzzing users wouldn't hit.

Yeah, I am mystified as to how this could have happened.

Yeah, I am mystified as to how this could have happened.

A short, sincere expression of surprise that breaks no guidelines, though it adds little.

I learned maybe a year in software, should not match/happen are famous last words.

This is just negligent. Not a fan of rewrite in rust, but I find it highly unlikely this would happen in that community.

I learned maybe a year in software, should not match/happen are famous last words.

This is just negligent. Not a fan of rewrite in rust, but I find it highly unlikely this would happen in that community.

Blunt criticism of the code's negligence and a mild opinion about Rust culture is allowed, though it adds little detail.

Rust tells you off if you don’t handle Results. Sure, you can still just unwrap() it and have it panic, but at least that’s a specific decision instead of forgetting to check for NULL like in this case.

Rust tells you off if you don’t handle Results. Sure, you can still just unwrap() it and have it panic, but at least that’s a specific decision instead of forgetting to check for NULL like in this case.

Substantive point on how Rust's Result handling makes ignoring errors an explicit decision.

it's like the reverse of the Max Levchin story about almost losing the master key for PayPal's payment credentials (not gonna spoil further than that)

https://max.levch.in/post/724289457144070144/shamir-secret-sharing-its-3am-paul-the-head-of

it's like the reverse of the Max Levchin story about almost losing the master key for PayPal's payment credentials (not gonna spoil further than that)

https://max.levch.in/post/724289457144070144/shamir-secret-sharing-its-3am-paul-the-head-of

Adds a relevant, interesting link connecting the story to a known key-management anecdote.
leonjza 2026-11-05 15:048/10informativefirst_hand | prev | next [–]

Managed to reproduce this, with a lab[0] you can play with.

[0] https://github.com/leonjza/sshhole-lab

Managed to reproduce this, with a lab[0] you can play with.

[0] https://github.com/leonjza/sshhole-lab

Independently reproduced the issue and shared a lab readers can try themselves.

A useful link to a Docker reproduction of the issue, with nothing to moderate.

> except a lot of programs never check for NULL. they just compare whatever crypt() gives back with the stored field, hit the NULL, and crash

> so musl (the libc under alpine) goes ok fine, crypt() just won't return NULL anymore. if it fails, it returns a single character instead.

Considering it's been accepted that crashing if malloc returns a NULL is your fault, did musl really feel the need to accommodate bad implementations like this or is this just AI speculation?

> except a lot of programs never check for NULL. they just compare whatever crypt() gives back with the stored field, hit the NULL, and crash

> so musl (the libc under alpine) goes ok fine, crypt() just won't return NULL anymore. if it fails, it returns a single character instead.

Considering it's been accepted that crashing if malloc returns a NULL is your fault, did musl really feel the need to accommodate bad implementations like this or is this just AI speculation?

A genuine, pointed question about whether musl really made this design choice, and about the article's reliability.

"unfortunately, a large portion of programs which call crypt are not prepared for its failure and do not check that the return value is non-null before using it. thus, always "succeeding" but giving an unmatchable hash is reportedly a better behavior than failing on error." [1]

[1] https://git.musl-libc.org/cgit/musl/commit/?id=b3c4cc121f70faea45389fe7ddc1127ed5cbd8bb

"unfortunately, a large portion of programs which call crypt are not prepared for its failure and do not check that the return value is non-null before using it. thus, always "succeeding" but giving an unmatchable hash is reportedly a better behavior than failing on error." [1]

[1] https://git.musl-libc.org/cgit/musl/commit/?id=b3c4cc121f70faea45389fe7ddc1127ed5cbd8bb

Directly answers the parent's question by quoting the primary-source musl commit message and linking to it.

Such a questionable decision! "Rather than let a bug in a caller (not handling the valid null output and thus crashing) surface, let's just silence it and pretend that the caller is correct! A potential clash in some situations (resulting in this vulnerability) is better than a crash!"

Maybe I should update my plea to fail loudly (https://alejo.ch/3gk) to list this as an example.

Such a questionable decision! "Rather than let a bug in a caller (not handling the valid null output and thus crashing) surface, let's just silence it and pretend that the caller is correct! A potential clash in some situations (resulting in this vulnerability) is better than a crash!"

Maybe I should update my plea to fail loudly (https://alejo.ch/3gk) to list this as an example.

Firm criticism of a design decision (not a person), arguing failures should surface loudly rather than being masked.

It's always a people problem. Doing a bad patch on my side is easier than bothering anyone else to fix their code

It's always a people problem. Doing a bad patch on my side is easier than bothering anyone else to fix their code

Short, wry but real observation about the social incentives behind bad patches.

> did musl really feel the need to accommodate bad implementations like this

I don't know about this specific case but in general? Yes, totally. No one is going to fix all existing software that depends on libs behaving exactly as glibc, you have to accept some trade-offs. Either your new implementation stops being perfectly simple and clean, or no one uses it.

> did musl really feel the need to accommodate bad implementations like this

I don't know about this specific case but in general? Yes, totally. No one is going to fix all existing software that depends on libs behaving exactly as glibc, you have to accept some trade-offs. Either your new implementation stops being perfectly simple and clean, or no one uses it.

Substantive point about compatibility trade-offs for libc implementations, stated firmly without any swipe.

it's not "glibc behavior", returning null on error is in the spec

it's not "glibc behavior", returning null on error is in the spec

A terse, substantive correction noting that returning null on error is spec behavior, not a glibc quirk.

To be fair the API is older than the POSIX spec.

To be fair the API is older than the POSIX spec.

Short but civil factual point adding historical context about the API predating POSIX.

See also: Hyrum's Law

See also: Hyrum's Law

A terse but relevant pointer to Hyrum's Law, which names the dependency-on-behavior problem the parent describes.

Not the worst site, but it's clearly a publicity machine from wakefield.co, whom I haven't heard of. Also, exploitation conditions are super narrow, albeit, any password is wild.

Not the worst site, but it's clearly a publicity machine from wakefield.co, whom I haven't heard of. Also, exploitation conditions are super narrow, albeit, any password is wild.

Mildly cynical about the company's publicity motive, but adds a real assessment of how narrow the exploit conditions are.

Horrors behind comprehension all around. The code, the error case being “*”, the loose patches plastered over. musl and C are unserious

Horrors behind comprehension all around. The code, the error case being “*”, the loose patches plastered over. musl and C are unserious

Blunt criticism aimed at code and tools rather than people, with a specific reference to the "*" error case.

A brief pointer to a relevant historical OpenSSH advisory.

how come such a thing can exist for more than a decade and get unnoticed? Weird times.

how come such a thing can exist for more than a decade and get unnoticed? Weird times.

A short but genuine question about how the issue stayed unnoticed for so long; nothing a moderator would ask to drop.

Magic characters are awesome.

Magic characters are awesome.

Short, friendly reaction with no hostility, so it stays verbatim despite adding little.

Of all the named vulnerabilities, I appreciate the history lesson and screenshots of how it got there

Of all the named vulnerabilities, I appreciate the history lesson and screenshots of how it got there

Short but sincere appreciation of what the write-up does differently.

Am I the only one who is shocked that such important and security-relevant software just does unorthodox stuff like "well let's just return `*` if it fails" and other kind of "hacks" ?! The developers must know that this is very bad software engineering and will definitely cause issues at _some_ point in time, no?!

Am I the only one who is shocked that such important and security-relevant software just does unorthodox stuff like "well let's just return `*` if it fails" and other kind of "hacks" ?! The developers must know that this is very bad software engineering and will definitely cause issues at _some_ point in time, no?!

Firm criticism of OpenSSH's engineering choices with a genuine question; blunt but aimed at the practice, not a person.
ChrisRR 2026-11-05 12:58 | removed0/10tangential_annoyancecurmudgeonly | prev | next [–]

Can we just have a site with the text and images without the awkward scrolling crap? Hijacking basic features is an instant click away from sites for me

Edit: It seems to work fine on mobile. I'm not sure why the scrolling behaviour was added on the desktop version of the site

Can we just have a site with the text and images without the awkward scrolling crap? Hijacking basic features is an instant click away from sites for me

Edit: It seems to work fine on mobile. I'm not sure why the scrolling behaviour was added on the desktop version of the site

Complaint about the site's scrolling behavior, a tangential annoyance that doesn't engage with the content.

[deleted]
krick 2026-11-06 02:14 | removed0/10tangential_annoyancecurmudgeonlysnarklow_effort | prev | next [–]

What is this idiotic manner of typing full articles in lowercase? Don't people learn basic orthography in school anymore? This is fucking unreadable.

What is this idiotic manner of typing full articles in lowercase? Don't people learn basic orthography in school anymore? This is fucking unreadable.

A complaint about the article's lowercase formatting with a swipe at its author, and no engagement with the content.

That's kind of shocking. I guess the swarm of AI related vulnerability findings continues.

That's kind of shocking. I guess the swarm of AI related vulnerability findings continues.

Brief but genuine reaction linking the finding to the trend of AI-discovered vulnerabilities.

It's shocking that it took so long with the amount of investment and hype you'd expect this kind of bugs to be already fixed, but it still need a human to tell the agents what to do, which could also work with any fuzzing framework

It's shocking that it took so long with the amount of investment and hype you'd expect this kind of bugs to be already fixed, but it still need a human to tell the agents what to do, which could also work with any fuzzing framework

Opinionated take on AI bug-finding hype versus existing fuzzing, with a real argument.

Am I missing something or is this written up to seem a lot worse than it actually is?

It starts with a bold claim: "ssh accounts that should not accept a password will accept any password between 256 and 1024 characters". But then it keeps adding qualifiers:

- sshd compiled with musl

- not using PAM

- password logins enabled

- at least one account with '*' as the password "hash"

- at least one OTHER account with a valid password

- server is aligned with 2 ley lines

- hacker is wearing a blue t-shirt and green socks

Ok, the last two are an exaggeration, but still. The conditions are all ANDed, miss a single one and you're fine.

On top of that, as someone else in these comments reminded, the target account needs to have a valid login shell or other exploitable program assigned to it, and these passwordless accounts tend to have nologin or something similar.

To me this seems to fall on the "technically possible, practically very unlikely" bucket, kinda like the PHP vulnerability from the other day. Maybe this has gone unnoticed for so long because it just doesn't happen.

Am I missing something or is this written up to seem a lot worse than it actually is?

It starts with a bold claim: "ssh accounts that should not accept a password will accept any password between 256 and 1024 characters". But then it keeps adding qualifiers:

- sshd compiled with musl

- not using PAM

- password logins enabled

- at least one account with '*' as the password "hash"

- at least one OTHER account with a valid password

- server is aligned with 2 ley lines

- hacker is wearing a blue t-shirt and green socks

Ok, the last two are an exaggeration, but still. The conditions are all ANDed, miss a single one and you're fine.

On top of that, as someone else in these comments reminded, the target account needs to have a valid login shell or other exploitable program assigned to it, and these passwordless accounts tend to have nologin or something similar.

To me this seems to fall on the "technically possible, practically very unlikely" bucket, kinda like the PHP vulnerability from the other day. Maybe this has gone unnoticed for so long because it just doesn't happen.

Substantive severity analysis listing the preconditions; the ley-lines joke is harmless and aimed at the write-up.
pizlonator 2026-11-05 15:197/10informativeinsightful | parent | next [–]

There are a lot of systems that meet those requirements.

> - sshd compiled with musl

So alpine as well as a bunch of other embedded Linuxes. Huge install base.

> - not using PAM

What you get if you try to have a minimal setup. Default sshd config if you install from source.

> - password logins enabled

The default. Super common.

> - at least one account with '*' as the password "hash"

Almost every Linux system.

> - at least one OTHER account with a valid password

Almost every Linux system.

So sure, there are caveats, but they cover a massive install base.

This is a super bad bug

There are a lot of systems that meet those requirements.

> - sshd compiled with musl

So alpine as well as a bunch of other embedded Linuxes. Huge install base.

> - not using PAM

What you get if you try to have a minimal setup. Default sshd config if you install from source.

> - password logins enabled

The default. Super common.

> - at least one account with '*' as the password "hash"

Almost every Linux system.

> - at least one OTHER account with a valid password

Almost every Linux system.

So sure, there are caveats, but they cover a massive install base.

This is a super bad bug

Firm, substantive disagreement that walks through each precondition and explains why the affected install base is large.

How many alpine installs are running sshd? Most containers using it as a base image aren't. Embedded Linux is unlikely to be running it, too. And if one IS running sshd, how often is it with unchanged defaults? I don't buy the inference here.

I'll concede that embedded linux might be my blindspot here. Maybe cheap web hosting, too, but that seems much less likely to be using musl.

How many alpine installs are running sshd? Most containers using it as a base image aren't. Embedded Linux is unlikely to be running it, too. And if one IS running sshd, how often is it with unchanged defaults? I don't buy the inference here.

I'll concede that embedded linux might be my blindspot here. Maybe cheap web hosting, too, but that seems much less likely to be using musl.

Firm, substantive disagreement about real-world exposure, with an honest acknowledgement of blind spots.

Embedded linux is your blindspot here.

Embedded linux is your blindspot here.

Terse but plain disagreement that directly answers the parent's stated uncertainty, with no swipe.

Rumor has it that Cisco has been hurriedly patching something and refusing to tell their customers what it is. I'm thinking it's this.

Rumor has it that Cisco has been hurriedly patching something and refusing to tell their customers what it is. I'm thinking it's this.

Openly labeled speculation that adds a relevant data point about embedded vendors.
tchebb 2026-11-05 15:596/10informativefirst_hand | root | parent | prev | next [–]

Most embedded systems I've seen use glibc or uClibc. I agree it would make sense for them to use musl, but the embedded space moves very slowly.

Most embedded systems I've seen use glibc or uClibc. I agree it would make sense for them to use musl, but the embedded space moves very slowly.

Substantive, civil pushback based on the commenter's experience with embedded libc choices.

Yes

Yes

A terse but sincere concession to the parent's point, which is plain agreement rather than snark.

A lot of embedded systems use musl and no PAM. Some of them might use Alpine, but the distribution doesn't matter that much.

A lot of embedded systems use musl and no PAM. Some of them might use Alpine, but the distribution doesn't matter that much.

Adds a concrete point that embedded systems commonly use musl without PAM, regardless of distribution.
3m4r 2026-11-05 15:555/10informative | root | parent | next [–]

Most of those would be using dropbear rather than openssh.

Most of those would be using dropbear rather than openssh.

Short, on-topic factual point that embedded systems typically run dropbear rather than openssh.

And ssh? I'd assume most would use proprietary or industry-specific standard for data IO, or just https, for domestic IoT stuff

And ssh? I'd assume most would use proprietary or industry-specific standard for data IO, or just https, for domestic IoT stuff

Genuine question about whether embedded systems actually run ssh, with a brief reasoned assumption.

And also ssh in many cases.

And also ssh in many cases.

Terse but genuine answer to the parent's question; nothing a moderator would ask to drop.

I thought Dropbear was pretty much the defacto SSH server for embedded applications, is OpenSSH+musl actually a common combination for these scenarios?

I thought Dropbear was pretty much the defacto SSH server for embedded applications, is OpenSSH+musl actually a common combination for these scenarios?

Genuine, on-topic question about how common OpenSSH with musl is in embedded systems.

Well, OpenWrt is OpenSSH + musl.

Well, OpenWrt is OpenSSH + musl.

Short, on-point answer to the parent's question with a concrete example.

Concise factual correction with a supporting documentation link.

We don’t know that dropbear isn’t vulnerable. The bug here is in musl and OpenSSH is just being a bro by working around it

We don’t know that dropbear isn’t vulnerable. The bug here is in musl and OpenSSH is just being a bro by working around it

Substantive, casual technical point that the bug lies in musl, so Dropbear's safety is unproven.
hnfong 2026-11-05 19:508/10insightfulinformative | root | parent | next [–]

I wouldn't say the bug is only in musl. Musl is probably the "worst" offender here (at least choose something other than "*"!), but the change in sshd was also a little suspect. i.e. the "let's find a user and take their salt (and hashing algorithm) and use it to compute and compare the hash to avoid timing attacks" change.

I haven't thought it through, but I suspect in an ideal world, sshd shouldn't have accepted the user password regardless of the outcome of crypt() if the user did not exist and it only pretended to go through the motions to avoid a timing attack.

Did look into their patch yesterday(?) which seemed to mitigate this issue which they (rightfully IMHO) consider caused by musl. But it seems their fix is to do a sanity check on the supposed hashed password char length, and thus still theoretically still allowing the possibility of those "!!!INVALID_PASSWORD!!!" suggestions to fall through....

I wouldn't say the bug is only in musl. Musl is probably the "worst" offender here (at least choose something other than "*"!), but the change in sshd was also a little suspect. i.e. the "let's find a user and take their salt (and hashing algorithm) and use it to compute and compare the hash to avoid timing attacks" change.

I haven't thought it through, but I suspect in an ideal world, sshd shouldn't have accepted the user password regardless of the outcome of crypt() if the user did not exist and it only pretended to go through the motions to avoid a timing attack.

Did look into their patch yesterday(?) which seemed to mitigate this issue which they (rightfully IMHO) consider caused by musl. But it seems their fix is to do a sanity check on the supposed hashed password char length, and thus still theoretically still allowing the possibility of those "!!!INVALID_PASSWORD!!!" suggestions to fall through....

Substantive analysis of shared responsibility between musl and sshd, including a critique of the patch.
gsich 2026-11-05 20:093/10low_effortfirst_hand | root | parent | prev [–]

Tried it, doesn't work.

Tried it, doesn't work.

Terse but reports an actual test result relevant to the claim about dropbear.

> Most containers using it as a base image aren't.

On top of the embedded linux blindspot, this statement seems unfounded.

> if one IS running sshd, how often is it with unchanged defaults?

By definition, I would imagine the answer to this is most of the time.

> Most containers using it as a base image aren't.

On top of the embedded linux blindspot, this statement seems unfounded.

> if one IS running sshd, how often is it with unchanged defaults?

By definition, I would imagine the answer to this is most of the time.

Firm, plain disagreement that challenges the parent's unsupported claims without any personal attack.
qwm 2026-11-05 16:077/10informativefirst_hand | root | parent | prev | next [–]

For what it's worth, default Alpine installs don't have an account with * as its password hash if you use the root account as your main one. Not sure if that's also true for ones where you set up a user, maybe those ones are vulnerable.

For what it's worth, default Alpine installs don't have an account with * as its password hash if you use the root account as your main one. Not sure if that's also true for ones where you set up a user, maybe those ones are vulnerable.

Adds a specific, useful caveat about default Alpine installs and honestly flags what remains uncertain.
utopiah 2026-11-05 16:317/10questioninsightful | root | parent | prev | next [–]

I'm genuinely curious what's the Venn diagram here because I feel a lot of embedded systems precisely have one condition but not the other, e.g. password yes but not necessarily multiple accounts, e.g root only, while using musl. Namely almost all for almost all, but if only one is not overlapping another then it's actually very few.

I'm genuinely curious what's the Venn diagram here because I feel a lot of embedded systems precisely have one condition but not the other, e.g. password yes but not necessarily multiple accounts, e.g root only, while using musl. Namely almost all for almost all, but if only one is not overlapping another then it's actually very few.

Good-faith challenge to the install-base claim, noting that all the conditions must overlap and that embedded root-only systems may not qualify.
yjftsjthsd-h 2026-11-05 16:396/10informativefirst_hand | root | parent | prev | next [–]

>> - at least one account with '*' as the password "hash"

> Almost every Linux system.

But not, notably, on Alpine, which uses '!' instead.

Edit: or maybe yes on Alpine? I checked my desktop and it has zero s in shadow, but Alpine's post seems to say they do use sometimes

>> - at least one account with '*' as the password "hash"

> Almost every Linux system.

But not, notably, on Alpine, which uses '!' instead.

Edit: or maybe yes on Alpine? I checked my desktop and it has zero s in shadow, but Alpine's post seems to say they do use sometimes

Substantive check of a claimed precondition on Alpine, with an honest self-correction.

I thought they had a difference in meaning, i.e. * means the user has no password and ! means that password logins are disabled for this user, but the man page does not make it clear if there's a distinction.

From man 5 shadow:

> A password field which starts with an exclamation mark means that the password is locked.

> 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).

I thought they had a difference in meaning, i.e. * means the user has no password and ! means that password logins are disabled for this user, but the man page does not make it clear if there's a distinction.

From man 5 shadow:

> A password field which starts with an exclamation mark means that the password is locked.

> 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).

Adds a useful man page citation on the meaning of * versus ! in shadow password fields.

does this really matter? It could be used to do something devastating. The number of systems that match those requirements is only one factor. If just a handful of those are highly critical for humanity it could lead to disaster. Eg. hacks into fridges, centrifuges etc.

does this really matter? It could be used to do something devastating. The number of systems that match those requirements is only one factor. If just a handful of those are highly critical for humanity it could lead to disaster. Eg. hacks into fridges, centrifuges etc.

A genuine point that criticality matters more than install base, though general and speculative.
rhet0rica 2026-11-05 16:282/10jokeoff_topickept_for_replies | root | parent | prev | next [–]

Updates since this was posted:

- blue t-shirts and green socks have been added to the default loadout for new hackers, since it's optimal according to AI and costs nothing to the attacker in terms of compute

- there are so many ley line "maps" out there that basically any server placement counts as aligned with at least two, so this one's free also

Updates since this was posted:

- blue t-shirts and green socks have been added to the default loadout for new hackers, since it's optimal according to AI and costs nothing to the attacker in terms of compute

- there are so many ley line "maps" out there that basically any server placement counts as aligned with at least two, so this one's free also

A light satirical joke about the bug's long list of preconditions, not hostile, and the reply needs it to make sense.

Blue t shirts and green socks?

Blue t shirts and green socks?

A short, genuine clarifying question about a detail in the parent comment.

[flagged]

Vulnerability-branded merch? Unsure.

Vulnerability-branded merch? Unsure.

Short, good-natured reply to a question in the thread; harmless.

Someone owns a blue shirt/green socks clearance warehouse, this is all just and ad? I've seen weirder. I can imagine visiting SanFran in a few months, and it looks like a weird sci-fi movie scene, everyone dressed the same.

Someone owns a blue shirt/green socks clearance warehouse, this is all just and ad? I've seen weirder. I can imagine visiting SanFran in a few months, and it looks like a weird sci-fi movie scene, everyone dressed the same.

Lighthearted riff on the parent's merch comment, not aimed at anyone and harmless.

SF has puffer jackets. Milan has men in blue suits. Paris has women whose wardrobes consist of black and white.

SF has puffer jackets. Milan has men in blue suits. Paris has women whose wardrobes consist of black and white.

A light, harmless observation about city dress uniforms that responds to the parent in good faith.

> password logins enabled

Disabling sshd password logins is security 101, and the default for some cloud provided images these days. If password logins haven't been disabled, what other doors have been left open anyway?

> password logins enabled

Disabling sshd password logins is security 101, and the default for some cloud provided images these days. If password logins haven't been disabled, what other doors have been left open anyway?

Firm disagreement that password logins are a realistic condition, with a concrete point about cloud image defaults.

Now the question for the man: would fil-c prevent it :D

Now the question for the man: would fil-c prevent it :D

A genuine, lightly playful question to pizlonator, the Fil-C author, about whether his memory-safe C implementation would stop this bug.

[deleted]

> > - password logins enabled

> The default. Super common.

This always seems bonkers to me. I think SSHD should give you ONE password free connection of setup to copy your key over and then it just shuts the door behind it.

> > - password logins enabled

> The default. Super common.

This always seems bonkers to me. I think SSHD should give you ONE password free connection of setup to copy your key over and then it just shuts the door behind it.

Strong opinion with a concrete design idea for sshd's default password login behavior.

I think it has to be a time limited FREE offer. (like, seriously, what if the user doesn't take the offer?)

It might actually a good idea, but I would likely avoid it since my built-in Bayesian filter would just mark it as spam.

I think it has to be a time limited FREE offer. (like, seriously, what if the user doesn't take the offer?)

It might actually a good idea, but I would likely avoid it since my built-in Bayesian filter would just mark it as spam.

Light humor with a real point: a one-time password offer needs a time limit in case it's never used.

Not sure why you're getting downvoted as from security standpoint that would be the right thing to do; but actual good server providers take it up a notch and allow you to upload the public key through their admin panel during server set up and the password you get only works locally for sudo etc. There's no reason why a fresh Linux setup should ever offer a publicly available SSH server with password based auth out of the box.

Not sure why you're getting downvoted as from security standpoint that would be the right thing to do; but actual good server providers take it up a notch and allow you to upload the public key through their admin panel during server set up and the password you get only works locally for sudo etc. There's no reason why a fresh Linux setup should ever offer a publicly available SSH server with password based auth out of the box.

Gives a useful point about providers injecting public keys at setup; the passing downvote remark is minor and not the comment's substance.

Alpine Linux (which uses musl) images are popularly deployed by those who use docker, and apparently the issue is there on the default install.

Not sure whether the PHP vulnerability actually had been exploited, but I'm pretty sure there are literally thousands of machines out there affected by this ssh issue right now as we speak.

This isn't going to end well.

---

Addendum (which may or may not be related to the above) --

Everyone has heard of the tale of the boy who cried wolf. The main moral of the story is not to troll your fellow townsfolks so they don't distrust you. But everyone with a sane mind gets that.

The (IMHO) under-appreciated lesson is actually: don't be those villagers. Believe stories you are given, even if you might look like a fool for believing in them. Don't assume what happens is the same as what happened in the past. Look at the facts yourself. Trust and verify.

Alpine Linux (which uses musl) images are popularly deployed by those who use docker, and apparently the issue is there on the default install.

Not sure whether the PHP vulnerability actually had been exploited, but I'm pretty sure there are literally thousands of machines out there affected by this ssh issue right now as we speak.

This isn't going to end well.

---

Addendum (which may or may not be related to the above) --

Everyone has heard of the tale of the boy who cried wolf. The main moral of the story is not to troll your fellow townsfolks so they don't distrust you. But everyone with a sane mind gets that.

The (IMHO) under-appreciated lesson is actually: don't be those villagers. Believe stories you are given, even if you might look like a fool for believing in them. Don't assume what happens is the same as what happened in the past. Look at the facts yourself. Trust and verify.

Substantive point that Alpine's default install makes the conditions common, with an opinionated aside on verifying claims; no swipe at anyone.

You don't typically run sshd in containers, instead you only run whatever single service you set up your container for.

Sure there are native Alpine installs too, but that is far less common of a use case.

You don't typically run sshd in containers, instead you only run whatever single service you set up your container for.

Sure there are native Alpine installs too, but that is far less common of a use case.

Civil, substantive correction that narrows the claimed exposure by noting containers rarely run sshd.

Who's out there running sshd inside a docker container? It's almost MORE work than exec-ing into the container.

Who's out there running sshd inside a docker container? It's almost MORE work than exec-ing into the container.

Blunt but relevant challenge to the assumption that Docker Alpine images expose sshd.
hylaride 2026-11-05 18:257/10first_handinformative | root | parent | next [–]

I've done it in the past with "serverless" sftp servers, which is how a lot of inter-company batch transfers happen for historical reasons, especially in the financial sector. I don't recall what images I used, but it was far easier to patch with a CI/CD pipeline. We did use keys, though.

I've done it in the past with "serverless" sftp servers, which is how a lot of inter-company batch transfers happen for historical reasons, especially in the financial sector. I don't recall what images I used, but it was far easier to patch with a CI/CD pipeline. We did use keys, though.

First-hand answer explaining a real use case for sshd in containers: SFTP for financial batch transfers.

Probably a lot of companies which have their own k8s cluster and segregate kubectl access to operations only. Developers still need a way to their app backend in some cases.

Probably a lot of companies which have their own k8s cluster and segregate kubectl access to operations only. Developers still need a way to their app backend in some cases.

Gives a plausible real-world reason for running sshd in containers.

Kubernetes already comes with better solutions for that sort of thing. I could see a ssh bastion container for bridging to legacy infrastructure though. Manage its keys right and it's probably more secure than a lot of alternatives.

Kubernetes already comes with better solutions for that sort of thing. I could see a ssh bastion container for bridging to legacy infrastructure though. Manage its keys right and it's probably more secure than a lot of alternatives.

Substantive disagreement with a practical suggestion about SSH bastions for legacy infrastructure.

Even if you do that why would you allow it outside VPN?

Even if you do that why would you allow it outside VPN?

A short, genuine question that challenges the parent's scenario on security grounds.

Several CICD solutions which offer "shell into container to debug a build failure".

You'd think they'd all do the smart thing and exec a shell into the container's namespace. You'd be wrong.

Several CICD solutions which offer "shell into container to debug a build failure".

You'd think they'd all do the smart thing and exec a shell into the container's namespace. You'd be wrong.

Directly answers the parent with a real-world example; the wry tone is aimed at vendors, not a person.

I have done that in the past, in order to be able to automate copying files to the container during testing or data migration. It was an ugly hack and now I need to make double sure none of those containers are still running…

I have done that in the past, in order to be able to automate copying files to the container during testing or data migration. It was an ugly hack and now I need to make double sure none of those containers are still running…

Answers the parent's question with a first-hand example of why someone ran sshd in a container.

Everyone who has access to their container and not the host except by some terrible vnc or web console.

Everyone who has access to their container and not the host except by some terrible vnc or web console.

Terse but direct answer giving a real use case for running sshd in a container.

Alpine in containers is usually not affected. Most images/containers haven't ssh/sshd installed at all. Remote access is usually done from the host system.

Alpine in containers is usually not affected. Most images/containers haven't ssh/sshd installed at all. Remote access is usually done from the host system.

Substantive, polite correction that Alpine containers typically lack sshd, so the exposure is smaller than suggested.
brisk_ibis6 2026-11-05 16:10 | rewritten5/10informative | original | root | parent | prev [–]

The popular usage of alpine in docker containers doesn't run sshd.

original comment by 63stack

You could start with trust and verifying what you wrote first, the popular usage of alpine in docker containers don't run sshd.

Keeps the useful factual correction about sshd in Alpine containers while dropping the condescending swipe at the parent.

Even if 0.1% of those machines run sshd I guesstimate we'd still be talking thousands.

Even if 0.1% of those machines run sshd I guesstimate we'd still be talking thousands.

A short, fair counterpoint estimating that the exposed population is still meaningful.

[deleted]

Really, "password auth enabled" should be enough to be a non starter these days.

Really, "password auth enabled" should be enough to be a non starter these days.

Terse but firm opinion on password auth that is on topic and aimed at a practice, not a person.

you'd be surprised

you'd be surprised

Terse but a real, wry disagreement that password auth is still common; not hostile.

only 1) is a narrowing filter. the rest is basically the majority.

only 1) is a narrowing filter. the rest is basically the majority.

Terse but real disagreement: argues that only the musl condition actually narrows exposure.

Most linux distros use PAM, so 2 is also a narrowing filter. Debian, Ubuntu, Fedora, Arch, etc all use PAM.

Most linux distros use PAM, so 2 is also a narrowing filter. Debian, Ubuntu, Fedora, Arch, etc all use PAM.

Adds a concrete correction: mainstream distros use PAM, so that condition also narrows exposure.

What if you narrow it down to musl-based distros (which none of the above are). Do most musl-based distros use PAM?

What if you narrow it down to musl-based distros (which none of the above are). Do most musl-based distros use PAM?

Genuine on-topic question that narrows the parent's point about PAM to musl-based distros.
VorpalWay 2026-11-05 16:327/10informativeinsightful | root | parent [–]

Alpine is mostly used in containers, where you don't run sshd anyway. It apparently doesn't use PAM by default though.

OpenWRT also doesn't use PAM by default apparently, and there is a risk of people using password login, which you should never do past initial setup.

But outside specific niches, musl is rare, with good reason. Performance is a joke compared to glibc, especially the allocator. The only reason to choose musl in my opinion is for memory constrained devices (OpenWRT etc) or for building static binaries where performance doesn't matter but portability does. I consider the "small container" use case questionable: performance will suffer, and you could share large base layers instead which would amortize the cost over many different images.

Alpine is mostly used in containers, where you don't run sshd anyway. It apparently doesn't use PAM by default though.

OpenWRT also doesn't use PAM by default apparently, and there is a risk of people using password login, which you should never do past initial setup.

But outside specific niches, musl is rare, with good reason. Performance is a joke compared to glibc, especially the allocator. The only reason to choose musl in my opinion is for memory constrained devices (OpenWRT etc) or for building static binaries where performance doesn't matter but portability does. I consider the "small container" use case questionable: performance will suffer, and you could share large base layers instead which would amortize the cost over many different images.

Substantive, opinionated overview of musl's niches and PAM defaults; the blunt take on performance targets software, not people.

AFAIK OpenWRT doesn't use openssh

AFAIK OpenWRT doesn't use openssh

Short factual correction relevant to the parent's point about OpenWRT.

Yeah, OpenWRT doesn't use openssh by default - it uses dropbear. You can install openssh, if you want more features and there seem to be 2 packages - one with PAM support (openssh-server-pam) and one without it (openssh-server). PAM-less one is the one without the suffix so perhaps it's more likely you'd install that one out of the two, but only if you were trying to switch to using openssh in the first place.

Yeah, OpenWRT doesn't use openssh by default - it uses dropbear. You can install openssh, if you want more features and there seem to be 2 packages - one with PAM support (openssh-server-pam) and one without it (openssh-server). PAM-less one is the one without the suffix so perhaps it's more likely you'd install that one out of the two, but only if you were trying to switch to using openssh in the first place.

Factual, on-topic detail about OpenWRT's default SSH server and its OpenSSH package options.

Those distros are all filtered by 1, no?

Those distros are all filtered by 1, no?

Short, genuine question that clarifies the exploit's preconditions.

Yes - immediately had me in a mild panic that I’d missed another AI discovered crit bug!

Had to quickly scan the rest of the homepage of HN to confirm.

Yes - immediately had me in a mild panic that I’d missed another AI discovered crit bug!

Had to quickly scan the rest of the homepage of HN to confirm.

Mild first-hand reaction agreeing with the parent; harmless and kept verbatim.

I suppose there is the chance you get to prep a system setup at one time, so you can access it at a later time?

I suppose there is the chance you get to prep a system setup at one time, so you can access it at a later time?

A genuine suggestion that an attacker could prepare the required conditions in advance for later access.
clever_dingo54 2026-11-06 00:37 | rewritten3/10low_effortinsinuation | original | parent | prev | next [–]

Yes, 100% written to oversell it as hard as possible. Still a vuln though.

original comment by ShinyLeftPad

Yes, 100% written to oversell it as hard as possible. Still a vuln but karma farming too.

Dropped the 'karma farming' insinuation about the author's motives and kept the view that the writeup is oversold.
tidy_turtle4 2026-11-05 19:12 | rewritten2/10snarklow_effort | original | parent | prev | next [–]

Yes, the rest of the world, which does not conform to your own setup.

original comment by CodeWriter23

Yes, missing the rest of the world that does not conform to your self-references.

Keeps the thin point that other environments differ, but drops the personal swipe about the parent's self-references.
arunc 2026-11-05 18:18 | removed0/10jokelow_effort | parent | prev | next [–]

> - hacker is wearing a blue t-shirt and green socks

You had me there :D

> - hacker is wearing a blue t-shirt and green socks

You had me there :D

Just quotes a joke line and laughs; adds no content to the discussion.

I think you're missing the reason why it's relevant now.

LLMs will find it.

They work endlessly at finding the 'and' scenarios.

This should have been closed up a long time ago.

I think you're missing the reason why it's relevant now.

LLMs will find it.

They work endlessly at finding the 'and' scenarios.

This should have been closed up a long time ago.

A firm, civil disagreement offering a real argument that LLMs make chained-condition bugs more findable.
bigbuppo 2026-11-05 18:20 | removed0/10snarklow_effortcurmudgeonly | parent | prev | next [–]

Every time I see one of these named exploit sites I always think, "Hey look at this guy, he wants attention."

Every time I see one of these named exploit sites I always think, "Hey look at this guy, he wants attention."

One-line dig at the researcher's motives that teaches nothing about the vulnerability.

> kinda like the PHP vulnerability from the other day

What? Pretty sure Cloudflare detected widespread attacks almost immediately after it was published. We are probably lucky that not much more happened because of responsible disclosure and the large media attention led to widespread admin awareness.

> kinda like the PHP vulnerability from the other day

What? Pretty sure Cloudflare detected widespread attacks almost immediately after it was published. We are probably lucky that not much more happened because of responsible disclosure and the large media attention led to widespread admin awareness.

Firm disagreement backed by a concrete point about Cloudflare detecting attacks; the bare 'What?' is not a personal swipe.

It’s irrelevant how widespread it is. It’s a bad bug that should be fixed. Why do people always say stuff like this? If you’re running a system that is exploitable then you deserve to know about it and to be provided a fix. It doesn’t matter all that much how widespread it is. The CVE system doesn’t exist to cater only to highly exposed systems.

It’s irrelevant how widespread it is. It’s a bad bug that should be fixed. Why do people always say stuff like this? If you’re running a system that is exploitable then you deserve to know about it and to be provided a fix. It doesn’t matter all that much how widespread it is. The CVE system doesn’t exist to cater only to highly exposed systems.

Firm disagreement arguing that exploitable systems deserve fixes and CVEs regardless of how widespread they are; it has an exasperated tone but no personal attack.

It matters how widespread it is because humans have limited time and resources to solve problems. If these conditions only exist in 0.001% of installs, is it worth the man hours to fix it? Alpine may think not, because like others have mentioned, there are likely other higher probability vulnerabilities being exposed in order to meet the conditions for this to even matter. A judgement call can (and clearly has?) be made to prioritize other work.

The good news is that considering Alpine is open-source, you can be the one to make the judgement call that it is worth fixing and go out there and fix it ;)

It matters how widespread it is because humans have limited time and resources to solve problems. If these conditions only exist in 0.001% of installs, is it worth the man hours to fix it? Alpine may think not, because like others have mentioned, there are likely other higher probability vulnerabilities being exposed in order to meet the conditions for this to even matter. A judgement call can (and clearly has?) be made to prioritize other work.

The good news is that considering Alpine is open-source, you can be the one to make the judgement call that it is worth fixing and go out there and fix it ;)

Substantive disagreement about prioritizing limited maintainer time; the closing 'fix it yourself' is a light, good-natured nudge.

A trivial-to-exploit auth bypass is always worth fixing, no matter how few systems are affected right now in practice. My question is whether it's worth giving the vulnerability a name and making a webpage for it.

A trivial-to-exploit auth bypass is always worth fixing, no matter how few systems are affected right now in practice. My question is whether it's worth giving the vulnerability a name and making a webpage for it.

Firm, civil disagreement on fix priority plus a fair question about branding vulnerabilities.
its_ethan 2026-11-06 02:25 | removed0/10snarklow_effort | root | parent [–]

Sounds like you should go fix it then!

Sounds like you should go fix it then!

One-line snark that dodges the parent's question about naming the vulnerability and adds no substance.

0.001% of all Linux installs (including containers, CI workers, embedded, etc) is still a huge huge number. It should be taken seriously until proven otherwise

0.001% of all Linux installs (including containers, CI workers, embedded, etc) is still a huge huge number. It should be taken seriously until proven otherwise

Firm, civil disagreement with a real point about scale; nothing to edit.

The 0.001% is an entirely made up number, so it doesn't actually matter.

original comment by its_ethan

ok then, you should go fix it! and thanks for pretending the entirely made up number of 0.001% actually matters

Dropped the sarcastic jab at the commenter and kept the objection that the figure is made up.
stevenhubertron 2026-11-05 15:122/10curmudgeonlylow_effortkept_for_replies | prev | next [–]

I’m a smart person and none of this makes any sense to me. It’s like purposefully written poorly.

I’m a smart person and none of this makes any sense to me. It’s like purposefully written poorly.

Blunt criticism of the writing that isn't hostile to any person, and the reply's "Agreed" needs it for context.
js2 2026-11-05 15:244/10informative | parent [–]

Agreed. Much better explained here: https://news.ycombinator.com/item?id=50379342

Agreed. Much better explained here: https://news.ycombinator.com/item?id=50379342

Short agreement that adds a useful link to a clearer explanation.

I'm starting to realize that trusting some random people who wrote the gruddiest, boringest, most torturous low-level stuff might have been a bad plan.

At this point I feel embarrassed in front of the AIs.

I'm starting to realize that trusting some random people who wrote the gruddiest, boringest, most torturous low-level stuff might have been a bad plan.

At this point I feel embarrassed in front of the AIs.

Wry reflection on trusting low-level infrastructure code, not aimed at anyone.

What this tells me is that musl+openssh-server is an incredibly uncommon setup, as "at least 257 character string" is literally the third thing I try after "admin" and "12345" when greeted with a password input.

What this tells me is that musl+openssh-server is an incredibly uncommon setup, as "at least 257 character string" is literally the third thing I try after "admin" and "12345" when greeted with a password input.

Wry joke that still makes a real observation about how rare the musl+openssh-server setup is.
oefrha 2026-11-05 13:114/10tangential_annoyancefirst_hand | prev | next [–]

This is a super duper annoying site for people who just want to know if their systems are vulnerable, after patching like a dozen critical vulnerabilities in other software over the last week and are almost at the breaking point of resigning.

Can we have a “don’t treat me like five/toddler” / “idgaf about the explanation” quick link to a section for adults who need to patch systems?

(And if your reaction is “just scroll down, stupid”, I’m happy to report that with the scroll jacking, I scroll not enough / past the critical sections nine out of ten times.)

Edit: You may want to read https://sshhole.com/#exposure.

This is a super duper annoying site for people who just want to know if their systems are vulnerable, after patching like a dozen critical vulnerabilities in other software over the last week and are almost at the breaking point of resigning.

Can we have a “don’t treat me like five/toddler” / “idgaf about the explanation” quick link to a section for adults who need to patch systems?

(And if your reaction is “just scroll down, stupid”, I’m happy to report that with the scroll jacking, I scroll not enough / past the critical sections nine out of ten times.)

Edit: You may want to read https://sshhole.com/#exposure.

A blunt complaint about the site's format, but it's aimed at the site, comes from someone patching systems, and links directly to the exposure section.

Thanks for mentioning that before I tried to scroll down. Forewarned, I was able to click on the "2026" link once the left-handed "scrollbar" (not the right word but I can't figure out what the right word should be) appeared, which let me jump past most of the verbiage and get to the "are you affected?" section just a couple pages later.

Thanks for mentioning that before I tried to scroll down. Forewarned, I was able to click on the "2026" link once the left-handed "scrollbar" (not the right word but I can't figure out what the right word should be) appeared, which let me jump past most of the verbiage and get to the "are you affected?" section just a couple pages later.

Practical navigation tip for reaching the 'are you affected?' section quickly.

[flagged]
thadt 2026-11-05 22:034/10informative | prev | next [–]

Theory: “With enough eyes, all bugs are shallow.”

Corollary: “Nobody looks”

The proof of this corollary is left as an exercise for the reader.

Theory: “With enough eyes, all bugs are shallow.”

Corollary: “Nobody looks”

The proof of this corollary is left as an exercise for the reader.

Wry but on-topic point that Linus's law fails when nobody actually audits the code; humor isn't aimed at a person.

Good thing my yocto images use glibc...

Good thing my yocto images use glibc...

Terse, wry comment with a real point: the issue's reach depends on the libc in embedded images.

I really like creativity in the name, the way website built and the content

"tl;dr" was a bit of a downgrade though, but that's fine

it's a nice way to promote the company behind it

I really like creativity in the name, the way website built and the content

"tl;dr" was a bit of a downgrade though, but that's fine

it's a nice way to promote the company behind it

Mild, good-natured praise with a small criticism; no problems to fix.
bombcar 2026-11-05 12:361/10jokelow_effortkept_for_replies | prev | next [–]

The real name of this is sshole - the is the "star" of the show, after all.

The real name of this is sshole - the is the "star" of the show, after all.

A harmless pun that isn't hostile, and the reply about escaping formatting needs it for context.

sshole, the is the star of the show

You can use \ to escape formatting.

sshole, the is the star of the show

You can use \ to escape formatting.

A light, harmless reply with a small useful tip about escaping formatting.
bombcar 2026-11-05 15:26 | removed0/10jokelow_effortmeta | root | parent | next [–]

Ugh! HN got me, too

Ugh! HN got me, too

A brief quip about HN's formatting swallowing an asterisk, with nothing for readers to learn.

[deleted]
muddi900 2026-11-05 13:58 | removed0/10tangential_annoyancelow_effort | prev | next [–]

I would like to read this but the scrolling is completely broken on iPad Safari

I would like to read this but the scrolling is completely broken on iPad Safari

A complaint about website scrolling that adds nothing about the topic.

Love the website

Love the website

Brief, harmless, sincere praise of the site; adds little but breaks no rule.
hinkley 2026-11-05 21:17 | removed0/10jokelow_effort | prev | next [–]

What did you just call me?

What did you just call me?

Pure joke on the project's name with no underlying contribution.

Was this exploited in the wild?

Was this exploited in the wild?

A short, genuine question about real-world exploitation of the vulnerability.
amber_crane60 2026-11-05 17:39 | rewritten3/10curmudgeonly | original | prev | next [–]

This AI slop write-up is so fucking annoying to read. I don't even mind the AI generated website. But the exploit deserves an hour of someone properly summing it up themselves.

original comment by nialv7

This AI slop write-up is so fucking annoying to read. I don't even mind the AI generated website. Why can't you even bother to spend like an hour to properly sum up the exploit yourself?

Keeps the complaint about the AI-written explanation and drops the rhetorical jab at the author.
shaolinspirit 2026-11-05 13:37 | removed0/10low_efforttrope | prev | next [–]

came for the name, stayed for the story

came for the name, stayed for the story

A stock one-liner about the name that adds nothing about the project or story.
harrouet 2026-11-05 16:36 | removed0/10jokelow_effort | prev | next [–]

I need to check my fridge...

I need to check my fridge...

A one-line quip with no substantive point to preserve.

something interesting to note here is that while this isn't a memory safety vulnerability, a modern systems programming language with better error handling could have easily avoided this whole situation. error handling in C is a PITA, and we're paying the cost of decades of pretending that it's not a problem

something interesting to note here is that while this isn't a memory safety vulnerability, a modern systems programming language with better error handling could have easily avoided this whole situation. error handling in C is a PITA, and we're paying the cost of decades of pretending that it's not a problem

A real argument about C error handling as the root cause, stated firmly but without attacking anyone.
MarkBlah 2026-11-05 13:32 | removed0/10low_effort | prev | next [–]

Haha insane, great find and write up

Haha insane, great find and write up

Bare praise with no substance; it adds nothing beyond a '+1'.
gabucino 2026-11-05 15:14 | removed0/10personal_attacksnarklow_effort | prev | next [–]

rich felker was such a f* (funny) guy back in the mplayer days too

never changed i guess

rich felker was such a f* (funny) guy back in the mplayer days too

never changed i guess

A veiled personal dig at a named developer, with no substantive point to preserve.

I refuse to believe this is not a backdoor.

I refuse to believe this is not a backdoor.

Terse, wry suspicion about the tool's security is allowed, though it offers no specifics.
ubermonkey 2026-11-05 19:20 | removed0/10jokelow_effort | prev | next [–]

I desperately want someone to create a single-letter subdomain here.

I desperately want someone to create a single-letter subdomain here.

Pure pun on the domain name with no substantive contribution.

It’s dumb and negligent to be missing “PasswordAuthentication no” in 2026. It’s been so for at least ten years. OpenSSH should have turned off password auth in the defaults years ago.

It’s dumb and negligent to be missing “PasswordAuthentication no” in 2026. It’s been so for at least ten years. OpenSSH should have turned off password auth in the defaults years ago.

Blunt, strongly opinionated take on SSH defaults aimed at a practice rather than a person, with a concrete recommendation.
andreagrandi 2026-11-05 14:47 | removed0/10jokelow_efforttrope | prev | next [–]

This is fine :)

This is fine :)

A meme quip with no substantive content.
lclc 2026-11-05 12:574/10informative | prev | next [–]

One more reason to build your (own) Docker images on 'FROM scratch' (basically nothing).

One more reason to build your (own) Docker images on 'FROM scratch' (basically nothing).

A brief but relevant practical suggestion about minimizing attack surface in container images.

No, the Alpine containers don't come with sshd preinstalled. You'd have to manually install via dockerfile and leave it unconfigured.

No, the Alpine containers don't come with sshd preinstalled. You'd have to manually install via dockerfile and leave it unconfigured.

Plain, factual disagreement noting that Alpine images don't ship sshd by default.

No one runs sshd in containers, at least I hope so. What would even be the point when you have docker/kubectl commands available?

No one runs sshd in containers, at least I hope so. What would even be the point when you have docker/kubectl commands available?

Firm, on-topic pushback with a genuine question about running sshd in containers.

Using LXC (not the same as docker) and running openssh inside the container.

Using LXC (not the same as docker) and running openssh inside the container.

Terse but real counterexample showing where sshd does run in containers.

just thank god for dropbear

just thank god for dropbear

Terse but meaningful: points to Dropbear, an alternative SSH implementation, as unaffected or preferable.
qwel-1asj 2026-11-05 16:00 | removed0/10tangential_annoyancecurmudgeonlysnark | prev | next [–]

All that site is lacking is marquee and furry creatures. AI slop sites are so horrible with their blinking animations, l33t dark background and chaotic text.

All that site is lacking is marquee and furry creatures. AI slop sites are so horrible with their blinking animations, l33t dark background and chaotic text.

Only complains about the site's visual style and dismisses it as AI slop, without engaging with the project.

This one feels negligent. How does this go uncaught for 10 years? How did it make it into production period?

We need to re-think EVERY assumption about security.

This one feels negligent. How does this go uncaught for 10 years? How did it make it into production period?

We need to re-think EVERY assumption about security.

Blunt criticism and genuine questions aimed at the failure, not a person, so it stays as written.

at this point my only reaction is "are you fucking kidding me"

at this point my only reaction is "are you fucking kidding me"

A terse, emotional reaction aimed at the situation rather than at a person; it adds little but breaks no rule.

[deleted]
DonHopkins 2026-11-05 13:58 | removed0/10jokelow_effort | prev | next [–]

This is bottom-up branding I can get behind. ()*()

This is bottom-up branding I can get behind. ()*()

A pure crude pun on the name with no underlying point.

I just created an Alpine container, did "apk add openssh" and this doesn't work for me. I assume it's been updated already.

I just created an Alpine container, did "apk add openssh" and this doesn't work for me. I assume it's been updated already.

Short but real reproduction attempt that reports a useful result.

[deleted]
kevinfiol 2026-11-05 19:28 | removed0/10metalow_effortinsinuation | prev | next [–]

Are articles like this deliberately designed to make it to the frontpage of HN?

Are articles like this deliberately designed to make it to the frontpage of HN?

Meta speculation about HN front-page bait that says nothing about the project itself.

I liked that this site explained the vulnerability in an easy-to-understand way.

I liked that this site explained the vulnerability in an easy-to-understand way.

Brief, sincere appreciation of the site's explanation; harmless and on-topic, though light on substance.
ordu 2026-11-05 13:143/10tangential_annoyancekept_for_replies | parent [–]

I can't understand it. All this scrolling and pictures are distracting me, can't concentrate really.

I can't understand it. All this scrolling and pictures are distracting me, can't concentrate really.

A sincere, non-hostile reaction to the site's format, and the kept reply needs it for context.

Yep, scrolling through my phone and reading slowly to understand. I'm seeing distracting animations below what I'm reading. And when I go to them, there is no way to restart them. So it completely misses the point.

Yep, scrolling through my phone and reading slowly to understand. I'm seeing distracting animations below what I'm reading. And when I go to them, there is no way to restart them. So it completely misses the point.

Concrete usability feedback on the site's animations, blunt but not aimed at anyone.

I think it varies from person to person. Since I'm kind of from the TikTok generation, I don't have much trouble reading this kind of animation, but some people prefer static sites. That's just how it is.

I also found scroll-triggered animations hard to read at first, but these days a lot of sites use them, so I've gotten used to them.

I think it varies from person to person. Since I'm kind of from the TikTok generation, I don't have much trouble reading this kind of animation, but some people prefer static sites. That's just how it is.

I also found scroll-triggered animations hard to read at first, but these days a lot of sites use them, so I've gotten used to them.

Good-faith personal experience on how readers differ and adapt to scroll animations.

There is a myth that TikTok generation has ADHD and can't concentrate, if it were true I'd expect static sites would be easier for you, like to me. I mean, I struggle sometimes to keep attention and not to wander off mentally. With static text it is easier to fix, you have just to jump back a bit, when you notice that you've forgot to read while you were reading. Or when you are lost at too many trees you can kind of look at the forest, at the text as a whole and find your place there. But with all this scrolling it is hard to jump backward/forward at will. It feels tiring and annoying.

There is a myth that TikTok generation has ADHD and can't concentrate, if it were true I'd expect static sites would be easier for you, like to me. I mean, I struggle sometimes to keep attention and not to wander off mentally. With static text it is easier to fix, you have just to jump back a bit, when you notice that you've forgot to read while you were reading. Or when you are lost at too many trees you can kind of look at the forest, at the text as a whole and find your place there. But with all this scrolling it is hard to jump backward/forward at will. It feels tiring and annoying.

Thoughtful personal account of why static text is easier to navigate when attention wanders.
rvz 2026-11-05 12:323/10informative | prev | next [–]

A plain link to a related discussion is useful to readers and has nothing to fix.
cowlevel 2026-11-05 15:042/10low_effortoff_topic | prev | next [–]

This is a pretty well done AI generated website, congrats you have proven it's not solely for slop.

This is a pretty well done AI generated website, congrats you have proven it's not solely for slop.

A wry, backhanded compliment aimed at the site rather than a person; it stays, but says little about the topic.

> how did I ran into it? [...] i had agent swarms. a lot of them. let's not get into it

Can we please now normalize AI usage in development? Just some other day people asked for "NO AI used" badges for software as measure for quality, and praised i.e. NetBSD for being AI-free. Yes, we come from a phase of AI slop, but it has become obvious that there are plenty of legit usecases where AI beats human-only effort nowadays.

> how did I ran into it? [...] i had agent swarms. a lot of them. let's not get into it

Can we please now normalize AI usage in development? Just some other day people asked for "NO AI used" badges for software as measure for quality, and praised i.e. NetBSD for being AI-free. Yes, we come from a phase of AI slop, but it has become obvious that there are plenty of legit usecases where AI beats human-only effort nowadays.

A firm opinion on normalizing AI in development with a real argument and no personal swipe, though it drifts from the story itself.

You've got a great point. But let's also acknowledge that there's a pretty stark contrast between these:

- I found a way to get AI to help me while I made this

- I asked AI to make this

The people you're objecting to are complaining about the latter, this is an example of the former.

You've got a great point. But let's also acknowledge that there's a pretty stark contrast between these:

- I found a way to get AI to help me while I made this

- I asked AI to make this

The people you're objecting to are complaining about the latter, this is an example of the former.

Civil, substantive distinction between AI-assisted work and AI-generated work that refines the parent's point.

There's a decent-size "any AI usage is theft" contingent that reacts dramatically to any usage of AI whatsoever.

There's a decent-size "any AI usage is theft" contingent that reacts dramatically to any usage of AI whatsoever.

A blunt characterization of a viewpoint that adds minor context to the AI discussion.

Not sure why but a lot of people seem to be missing that you don't have to look at AI as a binary "you're either with us or you're with the terrorists."

Just do HITL (human in the loop). Use an LLM to help you author, but steer the AI in whatever direction you want. It's like having a slightly delusional copy of yourself to pair program with. If you take the time to provide an LLM with some additional skills files and the like, with you in the pilot seat, you can get solid results (it doesn't look as cool as a hands free agent setup but the quality far surpasses anything they can do solo).

Not sure why but a lot of people seem to be missing that you don't have to look at AI as a binary "you're either with us or you're with the terrorists."

Just do HITL (human in the loop). Use an LLM to help you author, but steer the AI in whatever direction you want. It's like having a slightly delusional copy of yourself to pair program with. If you take the time to provide an LLM with some additional skills files and the like, with you in the pilot seat, you can get solid results (it doesn't look as cool as a hands free agent setup but the quality far surpasses anything they can do solo).

Offers a practical middle-ground view on human-in-the-loop AI use; the terrorists line is a wry idiom aimed at the binary framing, not a person.

It's a stupid and wasteful way to automate the process.

It's a stupid and wasteful way to automate the process.

Blunt disagreement aimed at the method rather than a person, so it stays, though it gives no supporting detail.

> Can we please now normalize AI usage in development?

1. It is already normalized outside of a tiny, but very vocal bubble that lives mostly on Bluesky (sometimes also on Reddit/HN/Mastodon).

2. I don't think that even the most staunch AI hater would object to using AI for security research as long as all the artifacts (code, docs) that were produced based on the research are human-written. "Yes, there is a bug in my program, but I refuse to fix it, because I know that you found it using AI" would be a pretty weird position. Not sure, how would you defend it.

> Can we please now normalize AI usage in development?

1. It is already normalized outside of a tiny, but very vocal bubble that lives mostly on Bluesky (sometimes also on Reddit/HN/Mastodon).

2. I don't think that even the most staunch AI hater would object to using AI for security research as long as all the artifacts (code, docs) that were produced based on the research are human-written. "Yes, there is a bug in my program, but I refuse to fix it, because I know that you found it using AI" would be a pretty weird position. Not sure, how would you defend it.

Firm opinion with a real argument about AI in security research; strong views are fine.
deft_badger22 2026-11-05 15:23 | rewritten4/10ideologicalfirst_hand | original | root | parent [–]

I feel like there is a lot of pushback from engineers that won't accept their coding skills have been rendered useless - so they come up with tons of reasons to avoid AI, because it just has to be bad. Anything to hold up the ilusion you are an well-paid white-collar worker that can pick employers anytime. And I say this myself coding since the late 90s and making a living off it - it is a painful pill to swallow, but the genie does not go back in the box so just accept it and move on.

original comment by littlecranky67

I feel like there is a lot of pushback from delusional engineers, that won't accept their coding skills have been rendered useless - so they come up with tons of reasons to avoid AI, because it just has to be bad. Anything to hold up the ilusion you are an well-paid white-collar worker that can pick employers anytime. And I say this myself coding since the late 90s and making a living off it - it is a painful pill to swallow, but the genie does not go back in the box so just accept it and move on.

Dropped the word "delusional" aimed at a group of engineers, keeping the author's argument and experience.

what was done to report & patch the vuln? Why publish it in this format? It’s presented like a whistleblower who’s exhausted every approach, but the effort to do the right thing is not clear.

what was done to report & patch the vuln? Why publish it in this format? It’s presented like a whistleblower who’s exhausted every approach, but the effort to do the right thing is not clear.

Genuine, pointed questions about responsible disclosure; criticism without a personal attack.
karel-3d 2026-11-05 14:023/10questionlow_effort | prev | next [–]

does this have CVE or something?

does this have CVE or something?

Short genuine question about whether the issue has a CVE.

cve-2026-123158

cve-2026-123158

Terse but directly answers the parent's question with the CVE identifier.
steady_swift90 2026-11-05 19:04 | rewritten6/10cross_examinationsnarkinsightful | original | prev | next [–]

Can we hear something from those who say that using port-knocking is "security through obscurity"?

Really: from the XZ backdoor in SSH (which went live on some distros) to this...

What's you rationale for calling port-knocking "security through obscurity" and not "defense in depth"?

Put it this way: if I advertize that my server is using port-knocking, that the sequence changes every 30 seconds (like TOTP) and that as soon one sequence is used, it's unusable (so you've only got one attempt to SSH in every 30 seconds, even if you know the sequence)...

Where's the security through obscurity?

But really: even if had musl and PasswordAuthentication allowed (I don't have neither), how would an attacker gets through the port knocking?

More fundamentally the question is: in which way is your system that does not use port knocking more secure than the one who do?

(remember: we're talking about remote exploits by simply having exposed TCP ports open or SSH listening)

Also, the solutions proposed here "because SSH is too old" happen to be crufts on top of SSH that had remote root exploitable holes.

original comment by TacticalCoder

Can we hear something from those who say that using port-knocking is "security through obscurity"?

Really: from the XZ backdoor in SSH (which went live on some distros) to this...

What's you rationale for calling port-knocking "security through obscurity" and not "defense in depth"?

Put it this way: if I advertize that my server is using port-knocking, that the sequence changes every 30 seconds (like TOTP) and that as soon one sequence is used, it's unusable (so you've only got one attempt to SSH in every 30 seconds, even if you know the sequence)...

Where's the security through obscurity?

But really: even if had musl and PasswordAuthentication allowed (I don't have neither), how would an attacker gets through the port knocking?

More fundamentally the question is: in which way is your system that does not use port knocking more secure than the one who do?

(remember: we're talking about remote exploits by simply having exposed TCP ports open or SSH listening)

Wanna have a talk too about the "solutions" proposed by "security experts" here "because SSH is too old" and that happen to be crufts on top of SSH that had remote root exploitable backdoors? (sorry, holes)

Solid defense-in-depth argument for port knocking; only the closing sneer at "security experts" was dropped.

> on alpine's official aws and azure images that's the default alpine user, and it has passwordless doas. so it's root

good fucking god. if I get this correctly... you can insta-own any aws/azure instance that was set up with alpine as long as it has ssh enabled, which it probably has.

> on alpine's official aws and azure images that's the default alpine user, and it has passwordless doas. so it's root

good fucking god. if I get this correctly... you can insta-own any aws/azure instance that was set up with alpine as long as it has ssh enabled, which it probably has.

Quotes a key detail and draws out its practical impact; the profanity is aimed at the situation, not a person.

Tbh the most disappointing part of this is musl (and later glibc) changing the function to stop callers crashing because they didn't RTFM.

Tbh the most disappointing part of this is musl (and later glibc) changing the function to stop callers crashing because they didn't RTFM.

Blunt opinion on libc behavior changes with real technical substance; RTFM here is aimed at callers generally and is fine.
sliqqq 2026-11-05 19:35 | removed0/10low_effort | prev | next [–]

jesus christ wtf

jesus christ wtf

Bare exclamation with no content or point to preserve.

[flagged]

[flagged]

[flagged]

[flagged]

[flagged]

[flagged]
mari_dtfy 2026-11-05 15:119/10informativefirst_handinsightful | prev | next [–]

Disclosure: I work at Detectify. A few practical notes for anyone checking their own machines:

- It affects Alpine's openssh-server (built without PAM), or openssh-server-pam with UsePAM no, when PasswordAuthentication is enabled. Alpine says every release they checked is affected, 3.16 through 3.24 and edge.

- Fixed openssh packages are out for all of those, including the older 3.16-3.20 releases. Upgrade and restart sshd (rc-service sshd restart). If you can't upgrade yet, set PasswordAuthentication no.

- To check a box: apk info -v openssh-server for the version, and sshd -T | grep -iE 'usepam|passwordauthentication' for the effective config.

- Containers built FROM alpine that run sshd are easy to miss in an inventory. So are the official tiny-cloud images (3.18.11 through 3.24.3), which ship an alpine user with no password and passwordless doas, so a bypass there means root.

- After patching, look in /var/log/messages for sshd lines saying "Accepted password for" an account that has no password.

Disclosure: I work at Detectify. A few practical notes for anyone checking their own machines:

- It affects Alpine's openssh-server (built without PAM), or openssh-server-pam with UsePAM no, when PasswordAuthentication is enabled. Alpine says every release they checked is affected, 3.16 through 3.24 and edge.

- Fixed openssh packages are out for all of those, including the older 3.16-3.20 releases. Upgrade and restart sshd (rc-service sshd restart). If you can't upgrade yet, set PasswordAuthentication no.

- To check a box: apk info -v openssh-server for the version, and sshd -T | grep -iE 'usepam|passwordauthentication' for the effective config.

- Containers built FROM alpine that run sshd are easy to miss in an inventory. So are the official tiny-cloud images (3.18.11 through 3.24.3), which ship an alpine user with no password and passwordless doas, so a bypass there means root.

- After patching, look in /var/log/messages for sshd lines saying "Accepted password for" an account that has no password.

Disclosed, practical guidance on affected versions, detection, mitigation and easily missed exposure.

[flagged]

[flagged]

[flagged]

[flagged]

[flagged]

[flagged]

[flagged]
TZubiri 2026-11-05 14:17 | removed0/10joketropelow_effort | prev | next [–]

what’s the moral of the story?

alpine stocks are crashing?

musl stocks are crashing?

glibc stonks are up

alternative implementations in general slightly down

what’s the moral of the story?

alpine stocks are crashing?

musl stocks are crashing?

glibc stonks are up

alternative implementations in general slightly down

A stonks-meme joke with no substantive point to preserve.

[flagged]
BugsJustFindMe 2026-11-05 13:062/10jokekept_for_replies | parent | next [–]

> Also, what's with this damn trend of typing in lowercase only?

Anticapitalism.

> Also, what's with this damn trend of typing in lowercase only?

Anticapitalism.

A light, non-hostile pun on capital letters that the kept reply needs in order to make sense.
dsunds 2026-11-05 13:50 | removed0/10jokelow_effort | root | parent | next [–]

You made my morning. Got a new dad joke.

You made my morning. Got a new dad joke.

Pure reaction to a pun with no substantive content.
DonHopkins 2026-11-05 14:23 | removed0/10jokelow_effort | root | parent | prev | next [–]

you could make a strong case for that!!!

you could make a strong case for that!!!

A pure pun on 'upper case' with no substantive content beyond the wordplay.
account42 2026-11-05 13:482/10snarkjokeoff_topickept_for_replies | root | parent | prev [–]

Anticapitalism that uses an army of clankers?

Anticapitalism that uses an army of clankers?

A light joke rather than a hostile comment, and the reply needs it for context.

It's a pun

It's a pun

A terse but sincere clarification that answers the parent's question.

[flagged]
chris_wot 2026-11-05 16:30 | removed0/10low_effortsnark | root | parent [–]

wtf did I just read?

wtf did I just read?

One-line dismissal with no underlying point.



About | Details | Examples | Prompts | This page on Hacker News | HN Guidelines | Data: HN API

Curious Hacker News is an unofficial, experimental mirror and is not affiliated with, endorsed by, or operated by Hacker News or Y Combinator. Original comments are by their authors on news.ycombinator.com; rewritten text is AI-generated and was not written by the commenter.