https://hachyderm.io/@damienmiller/117563026759098135 / https://news.ycombinator.com/item?id=50377221
https://www.openssh.org/releasenotes.html#10.6p2
https://alpinelinux.org/posts/2026-11-05-openssh-password-au...
[–] mpyne 2 days ago
[–] chuckadams 2 days ago
[–] markasoftware 2 days ago
[–] GuB-42 2 days ago
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.
[–] LordDragonfang 2 days ago
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)
[–] cowlevel 2 days ago
I've heard nasal demons invoked as a literal possibility. No they are not a literal possibility.
[–] Xirdus 2 days ago
[–] annex-winged-cr 1 day ago
You don't even need a fancy result type. A simple Option<String> would've forced downstream users to consider the failure case, avoiding this situation in the first place.
[–] kazinator 1 day ago
Someone coded a defense against long inputs which ignored this important principle.
What turned the bug into a security disaster is that Musl stupidly chose two symbols such that one of them is exactly the same as a convention for disabled logins. So that the moment someone makes a change to the function which allows crypt("something", "*") to return "*", you have an incident.
If the same bug were made in Glibc, it would have limited impact because crypt("long .... string", "*0") returning "*0", while incorrect, wouldn't do anything in a system that doesn't use *0 in password files. *0 does not correspond to any widespread password field convention.
[–] f33d5173 1 day ago
Now what?
[–] ahepp 1 day ago
Whereas if the library returns something that is unambiguously an option type, consumers unambiguously call unwrap at their own risk.
Of course you do have a point (assuming I'm picking up what you're suggesting correctly) that all pointers in C are option types. But I think there's a cognitive ergonomics aspect to this where an option type more clearly relays the library authors' intent.
[–] eviks 15 hours ago
[–] miki123211 1 day ago
If NASA were to use the equivalent of `pub struct Newtons(f32); pub struct PoundsForce(f32);`, where the types Newton and PoundsForce were not interchangeable, this would never have happened[1].
[1] I'm aware that that story is a bit more complicated than that, that CSV files were involved (which are, again, not type safe), and that, in that particular scenario, more would have to be done to preserve type safety across formats, vendors and codebases, but my point still stands.
[–] gugagore 1 day ago
https://lexi-lambda.github.io/blog/2020/11/01/names-are-not-...
> The newtype is opaque, so the only way to observe it is to convert it back to a [f32].
[–] poly2it 2 days ago
[–] eru 2 days ago
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?
[–] itishappy 2 days ago
These types are not the same:
Ok<String>
Err<String>[–] kazinator 1 day ago
If you have literally the above convention where a valid result is a String, and an error result is also a String (e.g. error message) that is still type safe.
match (whatever()) {
Ok(s) => println!("good result string: {s}"),
Err(s) => println!("error string: {s}"),
}
The success type and error type having to be different is not a good way to express the idea; rather that success and error cases are distinguished by type (even if the success and error data units are of the same type).In other words, the "Ok" and "Err" entities are an ingredient of the type and that effectively makes the result and error be distinguished at the type level.
But we could also argue that Rust still allows you to write an integer-returning function such that -1 means error.
[–] quotemstr 2 days ago
[–] poly2it 2 days ago
[–] majorchord 2 days ago
[–] alerighi 1 day ago
However a better API (to me) that I would have written was:
crypt_error_t crypt(...args, char *result, size_t resultLength)
That way btw the function doesn't need to allocate memory inside, which is (being a mostly embedded programmer) something I don't like at all. Plus returning an error enum makes it clear instead of having to look at errno.But POSIX API is not something well designed, it dates back to the '80 so... probably today would be written completely differently
[–] kazinator 1 day ago
It is not immune to the present issue. If our reentrant crypt is given a long password and the salt "", and it decides this is an error, placing the output "" into the result buffer, then it has the problem. It has the problem in a thread-safe way, but it has the problem. :)
[–] itishappy 2 days ago
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.
[–] SkiFire13 2 days ago
That depends on how you interpret the pointer type, but in any case there are multiple interpretation and just the fact that these exist will create confusion and type unsafety.
[–] rjbwork 2 days ago
"Make invalid states unrepresentable" is such a useful adage when designing even basic systems, much less the gates to control your server.
[–] citrin_ru 2 days ago
[–] shess 2 days ago
[–] jeremyjh 2 days ago
[–] creata 2 days ago
/// 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.
[–] markasoftware 2 days ago
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.
[–] gf000 2 days ago
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.
[–] speedstyle 1 day ago
But in your version, you can't preserve backwards compatibility, making an infallible function fallible is a breaking change. For something comparable to libc you might want to (eg Rust std still has mem::uninitialized), but even with security issues, I feel like people would be more inclined to deprecate for a new function (or panic, which does have its own problems).
A good type system also means things are written with better forwards compatibility, and can be safely changed later. The Rust equivalent to `crypt` likely wouldn't exist: a low-level library would just provide hash functions, leaving callers to mitigate DOS etc; a broader library might return some opaque handle with equality/serialization, which you couldn't try to compare to a passwordless user; a high-level library would parse the account store itself. None of these really need to change in incompatible ways, they have all the information to add features and fixes at their own level of abstraction.
[–] eru 2 days ago
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.
[–] skew-aberration 2 days ago
[–] cowlevel 2 days ago
[–] zbentley 2 days ago
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.
[–] cozzyd 2 days ago
[–] cowlevel 2 days ago
[–] chuckadams 1 day ago
[–] eru 2 days ago
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.
[–] cowlevel 2 days ago
[–] justmedep 2 days ago
[–] kazinator 1 day ago
Firstly, why do crypt functions return a valid string to indicate an error? The problem is that the classic null return makes some programs crash which are not expecting it.
musl has a correct protocol for the error string, similar to the *0/*1 convention. It also uses two symbols, * and +. If the salt string is *, then it uses \+ specifically to thwart this issue: the program blindly assuming that the password is correct because the output of the function matches the hashed password field.
Where it seems they screwed up is this:
crypt("long .... string", "*") -> "*"
I.e. in this special anti-DDoS measure that checks for a too long input, they forgot to implement the key piece of logic that the output of the function, when an error is being reported, must be different from its second argument (the "salt", which is usually the entire hashed password or the contents of that field from a password file).What we want is:
crypt("long .... string", "*") -> "+"
crypt("long .... string", "+") -> "*"
Thus the application ends up comparing "*" and "+", or "+" and "*" so the authentication fails.Under no circumstances should "*" be returned when the second argument is "*", or "+" when it is "+", because it must be assumed that the app doesn't do any sanity checking, and blindly compares the second argument to the return value.
[–] mpyne 17 hours ago
However OpenSSH passes a different hash to musl’s crypt() than what it later uses to compare, so musl doesn’t switch to the alternate return output because from its perspective, it’s already guaranteed not to accidentally compare the same as the input hash.
[–] selfmodruntime 1 day ago
[–] l33tman 2 days ago
[–] sgerenser 2 days ago
[–] amluto 2 days ago
> $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.
[–] mpyne 2 days ago
[–] __david__ 2 days ago
[–] idlewords 2 days ago
[–] markasoftware 2 days ago
[–] numeri 2 days ago
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.
[–] lukax 2 days ago
[–] ghostly_s 2 days ago
[–] alexgoodhart 2 days ago
[–] pizzafeelsright 2 days ago
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?
[–] endymi0n 2 days ago
[–] ericbarrett 2 days ago
[–] yearolinuxdsktp 2 days ago
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.
[–] xnorswap 2 days ago
< 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.[–] brabel 2 days ago
[–] phyzome 2 days ago
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.)
[–] byronsharman 2 days ago
[–] dboreham 2 days ago
wtaf ?
[–] za_creature 2 days ago
[–] mmoll 2 days ago
[–] zbentley 2 days ago
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!
[–] ghostly_s 2 days ago
[–] pdw 2 days ago
> 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_.
[–] crunchatized 11 hours ago
And that's also only in Debian 11 and later. That man page said something entirely different in Debian 10 and earlier, when they shipped glibc's libcrypt. Now they ship libxcrypt instead.
Fedora asked libxcrypt to give them back glibc's behavior, returning NULL, and they obliged by adding ./configure --disable-failure-tokens. But it's an option that Debian never enabled.
https://github.com/besser82/libxcrypt/issues/25
https://salsa.debian.org/md/libxcrypt/-/blob/master/debian/r...
[–] cloudfudge 2 days ago
[–] bryan_w 2 days ago
That seems orchestrated.
[–] bch 2 days ago
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;
}[–] xp84 2 days ago
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.
[–] oasisaimlessly 1 day ago
[–] zbentley 2 days ago
I dig it.
[–] PokestarFan 2 days ago
[–] creata 2 days ago
> 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.
[–] za_creature 2 days ago
Why * specifically? Why not some random guid generated before committing, if it's so important to not let buggy software crash?
[–] zbentley 2 days ago
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.
[–] dboreham 2 days ago
[–] cowlevel 2 days ago
[–] nextaccountic 1 day ago
[–] dboreham 2 days ago
[–] JdeBP 2 days ago
So the knock-on effect is that libraries that do have errors other than ENOSYS to accommodate, have to return magic 'impossible' values instead of setting errno and returning NULL, because of the weight of existing applications that expect nothing but the standard, traditional AT&T Unix (and indeed old BSD), behaviour.
Check the GNU libc crypt() doco. It defines a whole family of 'impossible' values that signal errors, by dint of listing a load of magic characters that should never be in the encrypted string. Including asterisk.
Also check FreeBSD's OpenPAM for a case in point. Even today, it does not check for a NULL return from crypt() and leaps straight to strcmp().
* https://cgit.freebsd.org/src/tree/lib/libpam/modules/pam_uni...
If you want to see real history here, check out the 1992 comment by Eric Andrew Young, that is still in the source for OpenBSD today (in des/des_fcrypt.c), on how SSLeay's crypt() had to be adjusted so that any single character password field, including "*", did not end up successfully matching itself.
* https://cvsweb.openbsd.org/annotate/src/lib/libcrypto/des/At...
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 "*".)
[–] boomlinde 2 days ago
No. https://man7.org/linux/man-pages/man3/crypt.3.html
My guess is that you are reading the libxcrypt crypt(3) manual: https://www.mankier.com/3/crypt
That's not GNU libc, but frequently deployed on Linux systems.
[–] JdeBP 1 day ago
* https://manpages.debian.org/trixie/libcrypt-dev/crypt.3.en.h...
Although that's a fine distinction for the point being made, as it's still the manual page for the crypt() function that one gets on the operating system, which that other manual page is not.
[–] boomlinde 1 day ago
[–] JdeBP 1 day ago
* https://sourceware.org/git/?p=glibc.git;a=commit;h=e6e3c6668...
Notice that Debian didn't list your manual page, which doesn't accompany a crypt() function at all, since it isn't actually the GNU libc manual but a separate independent manual for Linux. The GNU libc manual nowadays acknowledges Michael Glad's work but no longer documents crypt(). It used to, and it used to explain some of the 'impossible' encrypted string values:
* https://sourceware.org/glibc/manual/2.38/html_node/Passphras...
You noticed that I'd not got the GNU libc manual. Neither had you. (-:
[–] ruslan 2 days ago
Damn, that's a bug! Please file bug report.
[–] JdeBP 2 days ago
* https://cgit.freebsd.org/src/tree/lib/libcrypt
As I said, the callers of crypt() have for a long time been using the knowledge that crypt() on their platform simply never returns NULL. Unix has never been returning NULL from crypt() as far back as 7th Edition, when it simply always returned a pointer to a single internal static buffer.
* https://github.com/dspinellis/unix-history-repo/blob/Researc...
The reality is that all C library people have to work with a corpus of Unix applications that do not check for crypt() returning NULL that goes back almost 50 years, and an error return mechanism that barely exists outwith the pages of the SUS.
That includes callers in single-user mode in process 1 in some configurations.
* https://cvsweb.netbsd.org/bsdweb.cgi/src/sbin/init/init.c?an...
[–] ruslan 2 days ago
* https://cgit.freebsd.org/src/tree/secure/lib/libcrypt/crypt-...
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.
[–] JdeBP 1 day ago
* https://man.freebsd.org/cgi/man.cgi?query=crypt&sektion=3&fo...
NetBSD's manual is more detailed on the subject, and explains the bind that library implementors have been in for decades, thanks to almost 50 years of applications knowing what crypt() 'really' does.
* https://man.netbsd.org/crypt.3#RETURN%20VALUES
And this despite the SUS trying to shoehorn an error return mechanism into crypt(3) in order to accommodate the problems that operating systems had exporting encryption code, two decades afterwards. (The 1997 GoSolo book explains that this is why there's an ENOSYS error.)
[–] za_creature 2 days ago
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.
[–] odo1242 2 days ago
[–] mmoll 2 days ago
[–] tredre3 2 days ago
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.
[–] gf000 2 days ago
[–] dboreham 2 days ago
[–] 1vuio0pswjnm7 2 days ago
OpenSSL??
[–] mpyne 2 days ago
[–] smnc 2 days ago
A typo: `$ret = crypt($pw, $salt)`
[–] paparazzi678 2 days ago
[–] forrestthewoods 2 days ago
[–] Woodi 2 days ago
Not to programmatically parse 'ls' output or some /proc/some/file.
Eg. git started as Perl scripts then moved to C to realy have data straight from C calls from system.
So for example all Internet things calling "openssl -some -options ...." are pure trash :>>
[–] BoppreH 2 days ago
[1] https://en.wikipedia.org/wiki/In-band_signaling#Computer_dat...
[–] hedora 2 days ago
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.
[–] BoppreH 2 days ago
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.
[–] hedora 2 days ago
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.)[–] gf000 2 days ago
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.
[–] hedora 1 day ago
Software transactional memory doesn't really work with exceptions, since it precludes the use of catch + finally. It looks more like a top-level panic handler (where the throwing code cannot permute the heap, which is cool). To get exceptions, you'd need nested software transactional memory, which gets into obscure transaction processing theory. There's also the problem that your transaction could side-effect the outside world, then throw and be aborted, so you need to rework how I/O works if you walk down that path.
Exceptions are basically write-only, so I agree that their ergonomics for that use case are pretty good. Panic is better though, since it's really no error handling. Panic is easier to test, and easier to read / understand.
[–] Sohcahtoa82 2 days ago
Which is why the pattern of double-checked locking doesn't work.
[–] cowlevel 2 days ago
[–] hedora 1 day ago
this.dangerous = new Lol();
and then have Lol()'s constructor sleep for 10 sec then set done = true at the end of the contstructor. In another thread: while(!this.dangerous) { }
assert(this.done) // will throw.
I guess you could use double-checked locking as a class author by having every public method on your class do a if (!this.done) { throw; }, and make sure there are no public fields.That's pretty lame though.
[–] quotemstr 2 days ago
Nothing in your post actually supports the idea that exceptions are "computationally hard". The CFG error edges exist however the errors are spelled
[–] hamilyon2 2 days ago
Yes, broad exception catch clauses are simply incorrect code.
Panic is maybe an overreaction, "scratch everything and orderly abort" is sufficient.
[–] Ey7NFZ3P0nzAe 2 days ago
[–] z3t4 1 day ago
[–] za3faran 1 day ago
Doesn't this address the issue? https://openjdk.org/jeps/539
[–] jedbrooke 2 days ago
[–] __MatrixMan__ 2 days ago
[–] BoppreH 2 days ago
[–] __MatrixMan__ 2 days ago
So yeah, I guess it either needs to happen at runtime, or we just do signaling and user data on separate file descriptors.
[–] Normal_gaussian 2 days ago
[–] throw10920 2 days ago
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.
[–] __MatrixMan__ 2 days ago
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.
[–] jonhohle 2 days ago
[–] hedora 2 days ago
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.)
[–] cowlevel 2 days ago
[–] yearolinuxdsktp 2 days ago
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.
[–] hedora 2 days ago
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/ingeniouswom...
[–] zbentley 2 days ago
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.
[–] hedora 2 days ago
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?
[–] zbentley 2 days ago
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...), but the harder we can make it, the better.[–] throw10920 2 days ago
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.
[–] dns_snek 2 days ago
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.
[–] knome 2 days ago
https://pubs.opengroup.org/onlinepubs/009604599/functions/cr...
>Upon successful completion, crypt() shall return a pointer to the encoded string. The first two characters of the returned value shall be those of the salt argument. Otherwise, it shall return a null pointer and set errno to indicate the error.
returning a sentinel value of NULL and setting errno is pretty standard C, and shouldn't cause an error.
OP's sshhole link specifically notes that it _should_ return NULL, but some MUSL devs decided they wanted it to return trash instead, and the trash they returned instead of NULL caused this.
All because some devs were too lazy to check the result and another dev decided to be clever and "fix" NULL errors from crypt by sending back 'unmatchable' trash banking on devs not checking NULL.
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.
[–] dns_snek 2 days ago
> 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.
[–] zbentley 2 days ago
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
[–] n_plus_1_acc 2 days ago
[–] mafuy 2 days ago
[–] fc417fc802 2 days ago
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.)
[–] hedora 2 days ago
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.
[–] zbentley 2 days ago
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.
[–] dns_snek 2 days ago
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.
[–] slashdave 2 days ago
[–] eqvinox 2 days ago
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?
[–] FLeXMurphy 2 days ago
[–] panzi 2 days ago
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.
[–] hnfong 2 days ago
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.
[–] panzi 2 days ago
[–] throw10920 2 days ago
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.
[–] BoppreH 2 days ago
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.
[–] dns_snek 2 days ago
[–] BoppreH 2 days ago
[–] BoppreH 2 days ago
NO_PROXY=1 curl -qs --data @./my\ message.txt -- \
"https://user:password@example.com/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.
[–] dolmen 2 days ago
[–] UqWBcuFx6NV4r 2 days ago
[–] xorcist 2 days ago
[–] timhh 2 days ago
I can't even get an obvious UX improvement through PAM because they like it broken.
[–] gspr 2 days ago
[–] abirch 2 days ago
[–] gspr 2 days ago
[–] raverbashing 2 days ago
Honestly I've been shaking my fist at these since 0 becomes True/Success in Bash and the opposite everywhere else
[–] xorcist 2 days ago
[–] cwillu 2 days ago
[–] raverbashing 2 days ago
[–] OnlineTux 2 days ago
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.
[–] xigoi 2 days ago
[–] killerstorm 2 days ago
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.
[–] adwn 2 days ago
[–] sepeth 2 days ago
[–] mkl 2 days ago
[–] mort96 2 days ago
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'.
[–] SoftTalker 2 days ago
[–] mort96 2 days ago
[–] SoftTalker 2 days ago
[–] cowlevel 2 days ago
[–] deniska 2 days ago
[–] mort96 2 days ago
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.
[–] amluto 2 days ago
https://git.musl-libc.org/cgit/musl/tree/src/crypt/crypt_des...
(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_sha...
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...
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.
[–] tchebb 2 days ago
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-Jul...
[–] amluto 2 days ago
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.
[–] echion 2 days ago
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.
[–] tchebb 2 days ago
That would be a much better approach. I see your point now.
[–] NewJazz 2 days ago
[–] amluto 2 days ago
[–] aljgz 2 days ago
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.
[–] sunnybeetroot 2 days ago
My AI usage involves telling it to do things and setting up lint rules to catch all the bad programming it’s learnt.
[–] saguntum 2 days ago
One potential point of disagreement: it's easy to implement structural prohibition of magic strings in low-level languages too if you consider Rust a low-level language (which I've heard before, even though I didn't really consider it to be one for our application of it).
I'm not a Rust expert but do run a few thousand lines of it in prod for some perf/correctness critical code, and I wish it didn't have the reputation of being so difficult to comprehend. There's so much resistance to introducing it or new languages in our large org, that I'm surprised we were able to introduce our thin slice at all. I am not sure I'd use it where you need easy async primitives like goroutines, but it prevents whole swathes of bugs that are repeated over and over again with devastating impact, like the OP's bug, via thoughtful language design.
Maybe this resistance is an artifact of where I work more than large organizations generally, though. Curious about others' experiences. I'm not really an evangelist for the language but would really like to work in a more polyglot environment one day.
[–] nxobject 2 days ago
[–] rustyhancock 2 days ago
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
[–] jamal-kumar 2 days ago
[–] svnt 2 days ago
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.
[–] 0xbadcafebee 2 days ago
[–] rpcope1 2 days ago
[–] pelagicAustral 2 days ago
[–] trebligdivad 2 days ago
Each one relies on a perfectly valid argument - that * can't match because we all know it'll be rejected by the other thing.
[–] yangm97 2 days ago
Granted I mostly work with high level programming languages so I may be missing something.
[–] preg_match 2 days ago
[–] Retr0id 2 days ago
But that's not a perfectly valid argument, and it never was.
[–] aidenn0 2 days ago
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.
[–] woah 2 days ago
[–] zarzavat 2 days ago
[–] oneeyedpigeon 2 days ago
[–] nh2 2 days ago
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 *.
[–] jppittma 2 days ago
[–] creata 2 days ago
[–] K0nserv 2 days ago
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?
[–] aliceryhl 2 days ago
[–] xorcist 2 days ago
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:*").
[–] forresthopkinsa 2 days ago
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.
[–] lemonwaterlime 1 day ago
Maybe
——————
Just hash
Nothing
Either
——————————
Left err
Right hash
The program would be designed around not allowing this failure mode to even be possible.[–] aidenn0 2 days ago
[–] dolmen 2 days ago
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.
[–] K0nserv 2 days ago
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.
[–] aidenn0 2 days ago
[–] muvlon 2 days ago
- 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.).
[–] tialaramex 2 days ago
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=40...
[–] creata 2 days ago
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.
[–] Gibbon1 1 day ago
I think with C23 you got half a banana now. Still can't do it because C23 didn't adopt proposals to allow returns of untagged/anonymous structs.
If you could then you could write
auto rtn = Foo();
if(rtn.err)
printf("err=%i", rtn.err);
else
printf("Foo=%i\n", rtn.foo);[–] sscaryterry 2 days ago
[–] muvlon 2 days ago
[–] jppittma 2 days ago
[–] adgjlsfhk1 2 days ago
[–] muvlon 2 days ago
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.
[–] K0nserv 2 days ago
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.[–] uecker 2 days ago
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.
[–] dmantis 2 days ago
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
[–] ninkendo 2 days ago
- 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.
[–] tialaramex 2 days ago
This by the way is Tony Hoare's famous "Billion Dollar Mistake"
[–] 65a 1 day ago
[–] uecker 2 days ago
[–] f1shy 2 days ago
[–] 01HNNWZ0MV43FF 2 days ago
[–] randomNumber7 2 days ago
[–] WesolyKubeczek 2 days ago
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.
[–] acdha 2 days ago
[–] NewJazz 2 days ago
[–] throwawayffffas 2 days ago
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.
[–] IshKebab 2 days ago
[–] semiquaver 2 days ago
[–] wowczarek 2 days ago
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.
[–] Kuraj 2 days ago
Exhilarating, isn't it?
[–] wowczarek 2 days ago
It's about maintaining solid engineering practices and far above proving who's right or wrong.
[–] dabinat 2 days ago
[–] procaryote 2 days ago
[–] aidenn0 2 days ago
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.
[–] sidneythekidney 2 days ago
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?...
[–] scandinavian 2 days ago
Am I doing anything wrong here?
OpenSSH_10.3p1, OpenSSL 3.5.9 29 Sep 2026
PRETTY_NAME="Alpine Linux v3.24"
[–] markuman123 2 days ago
[–] bonyt 2 days ago
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/opens... Build time 2026-11-05 06:53:46
[–] markuman123 2 days ago
on 3.21 with the previous version ...
[–] justinsaccount 2 days ago
Didn't notice that the openssh-server-10.3_p1-r2 was released today.
[–] q3k 2 days ago
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.[–] hagbard_c 2 days ago
[–] necovek 2 days ago
[–] atmosx 2 days ago
[–] f311a 2 days ago
[–] zamadatix 2 days ago
[–] Aachen 2 days ago
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
[–] zamadatix 2 days ago
> 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-au...
[–] emayljames 2 days ago
[–] justinsaccount 2 days ago
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.
[–] evgpbfhnr 2 days ago
- 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.
[–] jack1142 2 days ago
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.
[–] cowlevel 2 days ago
[–] babagan0ush 2 days ago
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".
[–] evgpbfhnr 2 days ago
[–] wolrah 2 days ago
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.
[–] simias 2 days ago
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.
[–] pmontra 2 days ago
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.
[–] funcDropShadow 2 days ago
[–] pixl97 2 days ago
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.
[–] stackghost 2 days ago
Story time?
[–] simias 2 days ago
[–] pizlonator 2 days ago
100%
[–] zahlman 2 days ago
[–] quotemstr 2 days ago
Sadly, we will never learn this because failing fast runs counter to intuition, and a class of person will never override it.
[–] bottlepalm 2 days ago
Is there a test or check I can use to determine that software is 'good'?
Oh there isn't? Good/bad is subjective? We need to scape goating hacks as bad software, as if anyone is capable of perfect code. Perfect code would require knowing the system inside and out, the stack up and down - no one knows all that, no one is perfect. Sufficiently advanced AI though can rip through all of that, find the weakest spot and zero day it, bringing the entire house of cards down.
Software you thought was good can still be hacked. There is no way to create provably unhackable software.
Which means all software is packed with vulnerabilities just waiting to be discovered, no matter how good you think it is.
[–] drdexebtjl 2 days ago
Hey, but at least it can’t touch the System32 folder.
Great job guys.
[–] 0xbadcafebee 2 days ago
Go ahead and program a flight controller this way, I'm sure nothing bad will happen.
[–] dvdkon 2 days ago
[–] hnfong 2 days ago
Objective-C ahem nil messaging...
[–] sscaryterry 2 days ago
[–] hnfong 2 days ago
[–] usefulcat 2 days ago
[–] SoftTalker 2 days ago
[–] 0812037123158 2 days ago
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.alt... [2] https://web.archive.org/web/20060522175643/http://aluigi.alt...
[–] vova_hn2 2 days ago
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?
[–] dpacmittal 2 days ago
[–] chris_wot 2 days ago
[–] marcelox86 2 days ago
[–] bigstrat2003 2 days ago
[–] etatester 2 days ago
[–] dev_l1x_be 2 days ago
Are these people mad? Why on Earth would anybody indicate error like this?
[–] kykeonaut 1 day ago
I fail to see how the attack complexity of this vulnerability is high, as all it requires is attempting an ssh login with a password longer than 256 characters to an affected alpine image with port 22 open. Am I in the wrong?
[–] neuroticnews25 2 days ago
[–] oneeyedpigeon 2 days ago
[–] phyzome 2 days ago
[–] user43928 2 days ago
[–] fwip 2 days ago
[–] aphysically 2 days ago
[–] xnorswap 2 days ago
[–] cpach 2 days ago
[–] amelius 2 days ago
[–] hagbard_c 2 days ago
[–] riffraff 2 days ago
[–] gblargg 2 days ago
[–] mrguyorama 2 days ago
[–] marcelox86 2 days ago
[–] efilife 2 days ago
[–] tasuki 2 days ago
[0]: Be they human or LLM. Or an entity with any proportion of the two. I do not judge nor discriminate.
[–] glibg10b 2 days ago
[–] QuantumNomad_ 2 days ago
<title>*sshole · sshhole.com</title>
Strange and confusing.I mean I understand that in “*sshole” it’s referencing the * that is central to the issue and putting it so it looks like a censored version of “asshole” while also referencing that it’s an ssh hole. And the domain is more simply ssh hole instead of sshole. Is a way maybe it does makes sense to do both. But it did seem at least at first strange to me that the domain is one thing and the name is a slightly different but almost same thing. Like there was a typo when registering the domain, or like the author changed their mind about the name slightly after registering the domain.
[–] tasuki 2 days ago
[–] Levitating 2 days ago
[–] jonstewart 2 days ago
[–] BeetleB 2 days ago
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.
[–] bonplan23 2 days ago
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.
[–] 0x0 2 days ago
[–] dhx 2 days ago
> 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[–] krekr 2 days ago
gregor@krekrm3 ~ % ssh root@192.168.1.1 root@192.168.1.1's password: Permission denied, please try again. root@192.168.1.1's password: Permission denied, please try again. root@192.168.1.1's password: Connection closed by 192.168.1.1 port 22
[–] mkj 2 days ago
[–] mkj 2 days ago
[–] RVuRnvbM2e 2 days ago
[–] traceroute66 2 days ago
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.
[–] dwedge 2 days ago
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.
[–] traceroute66 2 days ago
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.
[–] minitech 2 days ago
[–] TZubiri 2 days ago
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
[–] Ardon 2 days ago
(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.
[–] ShinyLeftPad 2 days ago
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.
[–] Aachen 2 days ago
> 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
[–] Ecco 2 days ago
In other words, conceptually, at the end if the day, isn’t a key just another name for a password, really?
[–] minitech 2 days ago
SSH certificates are cool too.
[–] TZubiri 2 days ago
but I guess that the hash is always the same unlike with pub priv key mechanism.
[–] mkj 2 days ago
[–] traceroute66 2 days ago
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).
[–] Borealid 2 days ago
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.
[–] Elucalidavah 2 days ago
[–] dagss 2 days ago
[–] traceroute66 2 days ago
The best password in the world will still be defeated by a simple keylogger or breach of your password manager.
[–] Borealid 20 hours ago
The comparison here is between in-memory cryptographic keys and in-memory passwords. Yeah typing a password into a keyboard adds a vulnerability in between the keyboard and the computer, but it's not a huge difference in security posture. If the device using the secret is compromised, so is the secret, whether it came from the user's mind or not, whether it was stored encrypted before the user used it or not.
A physically separate hardware security module does add protection but I think that's a different conversation.
[–] scotty79 2 days ago
[–] jandrese 2 days ago
[–] traceroute66 2 days ago
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.
[–] belorn 2 days ago
[–] jandrese 2 days ago
[–] Aachen 2 days ago
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)
[–] 8organicbits 2 days ago
[–] dwedge 2 days ago
[–] traceroute66 2 days ago
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.
[–] epihelix 2 days ago
Well, it's a heck of a lot weaker in this instance (at least when `PasswordAuthentication no`)
[–] ant0ni 2 days ago
[–] ghm2180 2 days ago
[–] kccqzy 2 days ago
[–] traceroute66 2 days ago
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.
[–] kccqzy 2 days ago
[–] cozzyd 2 days ago
[–] traceroute66 2 days ago
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.
[–] kccqzy 2 days ago
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.
[–] cozzyd 2 days ago
[–] znnajdla 2 days ago
[–] sjamaan 2 days ago
[–] kijin 2 days ago
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.
[–] tialaramex 2 days ago
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 ?
[–] znnajdla 2 days ago
[–] traceroute66 2 days ago
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.
[–] unglaublich 2 days ago
[–] hypfer 2 days ago
As in: "Actually out there"
The hypothetical Alpine exists, yes, but what runs that like that?
[–] Bender 2 days ago
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 hn@ping.ohftp.org
There's no firewall and no password. Hack away HN. PasswordAuthentication yes
PermitEmptyPasswords yes
cd /pubget so*
[–] jbyers 2 days ago
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.
[–] ekidd 2 days ago
I'm guessing in practice relatively few people are actually exposed. But if they are, it's catastrophic.
[–] stingraycharles 2 days ago
[–] hypfer 2 days ago
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.
[–] hypfer 2 days ago
[–] figassis 2 days ago
[–] illusive4080 2 days ago
[–] bawolff 2 days ago
[–] kibwen 2 days ago
[–] bawolff 2 days ago
[–] kibwen 2 days ago
[–] NavinF 2 days ago
[–] thewillowcat 2 days ago
[–] mmoll 2 days ago
[–] Cakez0r 2 days ago
[–] procaryote 2 days ago
[–] tomaskafka 2 days ago
[–] jephs 2 days ago
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.
[–] ex-aws-dude 2 days ago
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
[–] octoberfranklin 2 days ago
[–] TZubiri 2 days ago
[–] jephs 2 days ago
[–] phyzome 2 days ago
[–] robinpie 2 days ago
[–] tialaramex 2 days ago
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 ?
[–] ImPostingOnHN 2 days ago
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.
[–] tomaskafka 2 days ago
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").
[–] grumpymuppet 1 day ago
Signatures/verification (typically) only provide a boolean guard on what you want to be controlled behavior.
To properly protect against cla controlled behavior, the check must be incorporated into the EXECUTION of the behavior (with a safe fail).
This is hard and I think a lot of pentesters will continue to have lucrative work hunting issues like this down for a long time.
Fundamentally, the problem is the "type" of the verification and the execution.
[–] kazinator 1 day ago
I settled on this. There is a block comment on the error handling, dating back to just 2023. There is a tiny infelicity in the wrapper in that we rely on the value of errno in the error path, but we do not set errno to zero and check for it being nonzero (evidence that the function set the value).
Applications using crypt directly can do the same thing: if it returns null, that's an error, of course, but don't use the result if it is less than 13 characters: that's also some kind of error token. The classic crypt() puts out a 13 character hash. Everything else is longer.
static val crypt_wrap(val wkey, val wsalt)
{
val self = lit("crypt");
const wchar_t *cwkey = c_str(wkey, self);
const wchar_t *cwsalt = c_str(wsalt, self);
char *key = utf8_dup_to(cwkey);
char *salt = utf8_dup_to(cwsalt);
#if HAVE_CRYPT_R
struct crypt_data *cd = coerce(struct crypt_data *, chk_malloc(sizeof *cd));
char *hash = (cd->initialized = 0, crypt_r(key, salt, cd));
#else
char *hash = crypt(key, salt);
#endif
free(key);
free(salt);
/* libraries cannot agree on how to report unrecognized or bad hashes:
*
* - older glibc versions, other libraries return null
* - libxcrypt, integrated into newer glibc puts out two
* possible failure tokens "*0" or "*1", documenting
* that an error token starts with "*" and is less than 13
* characters long.
* - musl uses "*" and "x", the latter being in the valid hash charset!
*
* let's go with: null or less than 13 chars means error.
*/
if (hash != 0 && memchr(hash, 0, 13) == 0) {
val ret = string_utf8(hash);
#if HAVE_CRYPT_R
free(cd);
#endif
return ret;
}
#if HAVE_CRYPT_R
free(cd);
#endif
uw_ethrowf(error_s, lit("crypt failed: ~d/~s"), num(errno),
errno_to_str(errno), nao);
}[–] hk__2 2 days ago
[–] phyzome 2 days ago
[–] epestr 2 days ago
> mostly the boring parts where it talks to libc, like the password code
Is clearly not human.
[–] phyzome 2 days ago
[–] ShinyLeftPad 2 days ago
[–] hk__2 11 hours ago
What a weird wording. Pretty much 100% of any text is *possibly* AI.
[–] efilife 2 days ago
[–] draxen 2 days ago
[–] otterley 2 days ago
[–] puzzlingcaptcha 2 days ago
[–] vel0city 2 days ago
[–] sam_lowry_ 2 days ago
Never enabled sshd there... except on my PostmarketOS PineBook Pro... oh shit!
[–] andunie 2 days ago
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.
[–] pona-a 2 days ago
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.
[–] sam_lowry_ 2 days ago
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.
[–] otterley 2 days ago
[–] gquere 2 days ago
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!
[–] doodlesdev 2 days ago
[–] mkj 2 days ago
[–] bspammer 2 days ago
[–] Aachen 2 days ago
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
[–] bspammer 2 days ago
Its security model doesn’t rely on that fact of course, but it’s the cherry on top.
[–] stogot 2 days ago
[–] sbrjdfgk 1 day ago
[–] stillpointlab 2 days ago
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.
[–] akiarie 2 days ago
[–] stillpointlab 1 day ago
The point of my comment is, no amount of "increased understanding" puts a human on equal footing to a chess engine.
[–] ShinyLeftPad 2 days ago
You're saying it as if humans can't write that too.
[–] stillpointlab 1 day ago
I am arguing that, much like the "best" chess move is unintelligible to best chess players, truly secure code will be unintelligible to humans.
[–] ShinyLeftPad 1 day ago
[–] Kotlopou 2 days ago
[–] yuck39 2 days ago
[–] chekibreki 2 days ago
So AFAIU, you are fine.
[–] throooooo 2 days ago
[–] cdilld 1 day ago
which is not to say that crypt() is well designed or specified - it's clearly deficient. but I can't fathom how the OpenSSH maintainers could come to the conclusion that the root cause is "outside OpenSSH itself" when the root cause is that OpenSSH's mitigation of timing attacks rested on faulty assumptions about the return value of crypt().
[–] p4bl0 2 days ago
[–] dwedge 2 days ago
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.
[–] passive 2 days ago
[–] vova_hn2 2 days ago
[–] bschne 2 days ago
https://max.levch.in/post/724289457144070144/shamir-secret-s...
[–] philipwhiuk 2 days ago
[–] matevskial 1 day ago
[–] matevskial 1 day ago
[–] cube00 2 days ago
> 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?
[–] red75prime 2 days ago
[1] https://git.musl-libc.org/cgit/musl/commit/?id=b3c4cc121f70f...
[–] afc 2 days ago
Maybe I should update my plea to fail loudly (https://alejo.ch/3gk) to list this as an example.
[–] boomlinde 2 days ago
NetBSD and libxcrypt both have similar workarounds. NetBSD has a paragraph on the reasoning.
The behavior of crypt() on errors isn't well standardized. Some imple-
mentations simply can't fail (unless the process dies, in which case they
obviously can't return), others return NULL or a fixed string. Most
implementations don't set errno, but some do. Version 2 of the Single
UNIX Specification (``SUSv2'') specifies only returning NULL and setting
errno as a valid behavior, and defines only one possible error (ENOSYS,
``The functionality is not supported on this implementation.'') Unfortu-
nately, most existing applications aren't prepared to handle NULL returns
from crypt(). The description below corresponds to this implementation
of crypt() only. The behavior may change to match standards, other
implementations or existing applications.
OpenBSD and glibc both have similar simple, stupid-free return value implementations. OpenBSD: The function crypt() returns a pointer to the encrypted value on success, and NULL on failure.
GNU: On success, a pointer to the hashed password is returned. On
error, NULL is returned.
The distro I use on the desktop uses libxcrypt, though. Probably more distros are. Its, NetBSD's and musl's workarounds probably make sense in some way if you have no clue what goes into your userland and are willing to write stupid software to accommodate for that.[–] 01HNNWZ0MV43FF 2 days ago
[–] Good4boothee 2 days ago
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.
[–] Palomides 2 days ago
[–] NewJazz 2 days ago
[–] vova_hn2 2 days ago
[–] ghm2180 2 days ago
[–] mrleblanc101 1 day ago
[–] mkj 2 days ago
No idea if it's likely to happen though.
[–] drob518 2 days ago
[–] jsiepkes 2 days ago
OpenBSD probably did (which doesn't use musl), RedHat probably did (or some other distro using glibc), but they all don't use musl.
[–] bensyverson 2 days ago
[–] glennfiedler 1 day ago
[–] sathackr 2 days ago
Dropbear is unaffected.
[–] glennfiedler 2 days ago
[–] schappim 2 days ago
[–] sneak 2 days ago
[–] throwawayffffas 2 days ago
This is just negligent. Not a fan of rewrite in rust, but I find it highly unlikely this would happen in that community.
[–] dabinat 2 days ago
[–] leonjza 2 days ago
[–] predkambrij 2 days ago
[–] paaloeye 2 days ago
[–] jaekwon 1 day ago
the sshd/musl part dates to the 2016 CVE-2016-6210 fix, so other * accounts (key-only users, LDAP users) have been open for 10 years.
the website is amazing, I just think these details would clarify things.
[–] ronfriedhaber 2 days ago
[–] rob 1 day ago
[–] dcchambers 2 days ago
We need to re-think EVERY assumption about security.
[–] iknowstuff 2 days ago
[–] HackerThemAll 2 days ago
[–] rvz 2 days ago
[–] ChrisRR 2 days ago
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
[–] ctrlaltdileep 2 days ago
[–] vdfs 2 days ago
[–] israrkhan 2 days ago
[–] gosen 2 days ago
"tl;dr" was a bit of a downgrade though, but that's fine
it's a nice way to promote the company behind it
[–] tonnydourado 2 days ago
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.
[–] pizlonator 2 days ago
> - 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
[–] tonnydourado 2 days ago
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.
[–] svnt 2 days ago
[–] __MatrixMan__ 2 days ago
[–] tchebb 2 days ago
[–] pizlonator 2 days ago
[–] andix 2 days ago
[–] 3m4r 2 days ago
[–] tonnydourado 2 days ago
[–] andix 2 days ago
[–] pera 2 days ago
[–] lucideer 2 days ago
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.
[–] qwm 2 days ago
[–] utopiah 2 days ago
[–] yjftsjthsd-h 2 days ago
> 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
[–] tremon 2 days ago
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).
[–] LinaLauneBaer 2 days ago
[–] rhet0rica 2 days ago
- 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
[–] Onavo 2 days ago
[–] petecooper 2 days ago
[–] b112 2 days ago
[–] dcrazy 2 days ago
[–] alexchamberlain 2 days ago
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?
[–] twothreeone 2 days ago
[–] vadansky 2 days ago
> 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.
[–] hnfong 2 days ago
It might actually a good idea, but I would likely avoid it since my built-in Bayesian filter would just mark it as spam.
[–] diath 2 days ago
[–] hnfong 2 days ago
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.
[–] VorpalWay 2 days ago
Sure there are native Alpine installs too, but that is far less common of a use case.
[–] tonnydourado 2 days ago
[–] hylaride 2 days ago
[–] cluckindan 2 days ago
[–] chuckadams 2 days ago
[–] jbvlkt 2 days ago
[–] cluckindan 1 day ago
[–] zbentley 2 days ago
You'd think they'd all do the smart thing and exec a shell into the container's namespace. You'd be wrong.
[–] Jolter 2 days ago
[–] Brian_K_White 2 days ago
[–] andix 2 days ago
[–] 63stack 2 days ago
[–] hnfong 2 days ago
[–] tcdent 2 days ago
[–] qwm 2 days ago
[–] itemize123 2 days ago
[–] VorpalWay 2 days ago
[–] jack1142 2 days ago
[–] VorpalWay 2 days ago
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.
[–] kasabali 2 days ago
[–] jack1142 2 days ago
[–] anamexis 2 days ago
[–] j45 2 days ago
LLMs will find it.
They work endlessly at finding the 'and' scenarios.
This should have been closed up a long time ago.
[–] edf13 2 days ago
Had to quickly scan the rest of the homepage of HN to confirm.
[–] alienbaby 2 days ago
[–] ShinyLeftPad 2 days ago
[–] CodeWriter23 2 days ago
[–] arunc 2 days ago
You had me there :D
[–] cubefox 2 days ago
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.
[–] bigbuppo 2 days ago
[–] huslage 2 days ago
[–] its_ethan 2 days ago
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 ;)
[–] tchebb 2 days ago
[–] its_ethan 2 days ago
[–] gf000 2 days ago
[–] its_ethan 2 days ago
[–] stevenhubertron 2 days ago
[–] js2 2 days ago
[–] usr1106 22 hours ago
(Of course I could post it a bit obfuscated)
[–] oefrha 2 days ago
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.
[–] rmunn 2 days ago
[–] titzer 2 days ago
At this point I feel embarrassed in front of the AIs.
[–] peanut-walrus 2 days ago
[–] alper 2 days ago
[–] thadt 2 days ago
Corollary: “Nobody looks”
The proof of this corollary is left as an exercise for the reader.
[–] anilakar 2 days ago
[–] nialv7 2 days ago
[–] angry_octet 2 days ago
[–] bombcar 2 days ago
[–] yreg 2 days ago
You can use \ to escape formatting.
[–] bombcar 2 days ago
[–] thatxliner 1 day ago
[–] tonymet 2 days ago
[–] muddi900 2 days ago
[–] jdw64 2 days ago
[–] ordu 2 days ago
[–] jdw64 2 days ago
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.
[–] ordu 2 days ago
[–] vavkamil 2 days ago
[–] Cort3z 2 days ago
[–] ubermonkey 2 days ago
[–] jbirnick 2 days ago
[–] DonHopkins 2 days ago
[–] cozzyd 2 days ago
[–] shaolinspirit 2 days ago
[–] MarkBlah 2 days ago
[–] hoppp 2 days ago
[–] nasso_dev 2 days ago
[–] hinkley 2 days ago
[–] harrouet 2 days ago
[–] qwel-1asj 2 days ago
[–] gabucino 2 days ago
never changed i guess
[–] crunchatized 1 day ago
He mentions that circa 2019, Debian's libcrypt stopped returning NULL and began returning the 2-character string '*0' - he doesn't elaborate, but this was part of a larger migration by many distros. glibc deprecated its own libcrypt so a more agile project that 'could move faster' would take its place [0]. This migration happened in Fedora 28 [1], Arch [2], and Debian bullseye [3].
Unless you specifically compile it with --disable-failure-tokens, on an error libxcrypt will return that 2-character failure token string. [4] [5] This behavior is very old [6], and hasn't been POSIX-compliant since at least POSIX.1-2001, which is based on SUSv2, which is from 1997.
So they've knowingly kept this non-standard backwards compatibility cludge for almost 30 years, and continue to claim it "isn't well standardized" in their man page despite POSIX and glibc existing, but at least it didn't bite them in the asterisk like it did musl. (Yet.) There's a new issue about removing this behavior now. [7]
[0] https://sourceware.org/legacy-ml/libc-alpha/2017-08/msg01257...
[1] https://fedoraproject.org/wiki/Changes/Replace_glibc_libcryp...
[2] https://bugs.archlinux.org/task/67312
[3] https://bugs.debian.org/941853
[4] https://manpages.debian.org/stretch/manpages-dev/crypt.3.en....
[5] https://manpages.debian.org/bullseye/libcrypt-dev/crypt.3.en...
[6] https://github.com/besser82/libxcrypt/commit/a1a3cba626843f3...
[–] TacticalCoder 2 days ago
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)
[–] mschuster91 2 days ago
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.
[–] Eleg007 2 days ago
[–] temp123477 2 days ago
[–] lclc 2 days ago
[–] blueflow 2 days ago
[–] Good4boothee 2 days ago
[–] Dwedit 2 days ago
[–] andreagrandi 2 days ago
[–] DANmode 2 days ago
[–] meindnoch 2 days ago
[–] krick 2 days ago
[–] henry700 2 days ago
[–] varjag 2 days ago
[–] globular-toast 2 days ago
[–] littlecranky67 2 days ago
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.
[–] __MatrixMan__ 2 days ago
- 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.
[–] neuronexmachina 2 days ago
[–] rglover 2 days ago
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).
[–] SrslyJosh 2 days ago
[–] vova_hn2 2 days ago
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.
[–] littlecranky67 2 days ago
[–] cowlevel 2 days ago
[–] tgml 2 days ago
[–] kevinfiol 2 days ago
[–] sliqqq 2 days ago
[–] mari_dtfy 2 days ago
- 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.
[–] TZubiri 2 days ago
alpine stocks are crashing?
musl stocks are crashing?
glibc stonks are up
alternative implementations in general slightly down
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.