News for hackers. Stuff that matters.
Re:Sshole by zarzavat on Thursday November 05, 2026 @08:29AM (#50378830)
The real problem here is the C language. Our industry should have dumped it a long time ago and we’re now paying the price of prevarication.
Re:Sshole by 01HNNWZ0MV43FF on Thursday November 05, 2026 @08:38AM (#50378924)
There is no industry, it's just people
Re:Sshole by oneeyedpigeon on Thursday November 05, 2026 @08:40AM (#50378939)
Which of the several flaws in this bug chain would have been materially different with a language other than C?
Re:Sshole by dmantis on Thursday November 05, 2026 @08:44AM (#50378973)
Wouldn't Rust's Result or go Error checking prevent that?

I mean a person would probably use it instead of the * hack.

Re:Sshole by ninkendo on Thursday November 05, 2026 @08:56AM (#50379095)
[delayed]
Re:Sshole by tialaramex on Thursday November 05, 2026 @09:22AM (#50379409)
> NULL is a valid instance of T

This by the way is Tony Hoare's famous "Billion Dollar Mistake"

Re:Sshole by uecker on Thursday November 05, 2026 @11:29AM (#50381140)
A NULL pointer for error would be a safe interface where you get a trap when there is no value.
Re:Sshole by 65a on Friday November 06, 2026 @09:33AM (#50392751)
Minor nit, strings are never nil in Go, so that would be "" instead. The point stands though, a common pattern is "defer foo.Close()", the Close() signature almost always returns an error and almost no one checks it...which is probably fine, but not automatically fine.
Re:Sshole by nh2 on Thursday November 05, 2026 @08:47AM (#50379008)
Any use of sum types would have prevented this immediately.

The whole thing exists because APIs that return a NULL value that "nobody checks", plus a string that you shouldn't use if you actually did check the null value.

In languages with sum types, this cannot happen.

Either you get your hash, or no result at all. You couldn't get eg NULL and *.

Re:Sshole by win311fwg on Thursday November 05, 2026 @08:56AM (#50379100)
> In languages with sum types, this cannot happen.

Funny, C has sum types (coproduct is a sum type) and it still happened.

The use of sum types may have helped, especially when combined with a tag which is trivial to do in C, but a language having sum types does not imply that the authors will use them. No language can save a reckless developer.

Re:Sshole by jppittma on Thursday November 05, 2026 @09:30AM (#50379519)
C has unions. C has sum types. People have been doing tagged unions in C for longer than rust has been a language
Re:Sshole by aliceryhl on Thursday November 05, 2026 @09:48AM (#50379758)
Yet the authors of crypt did not use them. The C language is not designed in a way to encourage people to write code that correctly uses tagged unions (or nullable pointers for that matter).
Re:Sshole by jppittma on Thursday November 05, 2026 @10:02AM (#50379968)
Where are you going with those goal posts?
Re:Sshole by Philpax on Thursday November 05, 2026 @10:48AM (#50380608)
Nowhere. It's the same argument as upthread. A language that forces downstream consumers of an API to correctly deal with all possible outcomes would have prevented this.
Re:Sshole by jppittma on Saturday November 07, 2026 @01:54PM (#50406966)
You can’t force downstream to do anything. I can write a library in C that hints just as hard as rust does though, using basically the same tools/apis, since they’re all there. The only difference is that it’s less conventional in C. That’s probably a smaller lift than rewrite it in rust.

Memory is where C is broken. I feel like without `defer` or destructors the language meaningfully is incomplete.

Re:Sshole by rini17 on Thursday November 05, 2026 @10:58AM (#50380768)
Goalposts are fine. If crypt did return union type, C still allows cowboy programmers to straight extract the char* without any check whether it's valid.
Re:Sshole by cannonpalms on Thursday November 05, 2026 @11:20AM (#50381013)
I don't think the goal posts were moved. The comment you replied to referred to the "use"--not the existence--of sum types.
Re:Sshole by knome on Thursday November 05, 2026 @05:19PM (#50385300)
because POSIX dictates the shape of crypt from a function written in 1973 and unions/structs were only introduced to the C language in 1976.

the fault for sshhole isn't just "C bad", it's a series of poor choices in workarounds falling together to create a vulnerability.

being condescending about decisions made 50 years ago, before any of the better languages you're mad the developer didn't use even existed, is silly.

if you want to lay blame, lay it at the feet of more recent devs that started returning values instead of NULL because people were using crypt incorrectly. likely sha512 returned a fake value instead of a real one because prior hashes couldn't fail. the interface should have been updated then, but instead they worked around it.

Re:Sshole by K0nserv on Thursday November 05, 2026 @09:49AM (#50379774)
I don't think I've ever seen a C function that uses unions for this purpose though?

I can think of two common patterns that I've seen frequently:

* Return NULL for errors (sometimes with a global errorno() function).

* Return an integer error with any return values as out parameters.

So if it is possible to improve, why doesn't anyone do it?

Re:Sshole by Woodi on Friday November 06, 2026 @01:26AM (#50388877)
Because usually NULL is good enough and you need to have domain use case to make library for it.

> * Return an integer error with any return values as out parameters.

You must first standarize all that (usually negative) integers, make docs, check for it, do not make mistakes in your if's or case's...

And errno is just that - integers with a meaning. errno is even thread local now and quite safe to use but people on internet scream loudly when someone try to use that not app global varible :) I guess their llms statistically have less new texts feeded in ;)

At some point it is just better to return pointer to struct with all info you can imagine.

And how you think other languages implemented their Error type returns ? Just a struct or union with tag. Because current CPU's operate on simple type registers there is no other sane way to do it.

Re:Sshole by creata on Thursday November 05, 2026 @11:08AM (#50380895)
It is extremely unidiomatic to use tagged unions in C for error handling. It's definitely possible, but awkward, and the compiler doesn't check that you checked the tag.
Re:Sshole by zarzavat on Friday November 06, 2026 @07:07AM (#50391403)
That's a universe apart from real sum types with exhaustiveness checking and pattern matching.
Re:Sshole by dolmen on Thursday November 05, 2026 @10:06AM (#50380017)
Nope.

Because in fact all the changes were about changing the function signatures. The function signature is enforced by the linker. The semantic of strings that go through isn't.

Returning 2 values is possible in C (via pointers input parameters). The main issue here is that libc is a strong API contract based on C function signatures, that nobody can change, only augment with new symbols. Such strong API compatibility constraints happen in any programming language, and workaround to fit new behaviours in old APIs happens everywhere.

Re:Sshole by K0nserv on Thursday November 05, 2026 @10:40AM (#50380497)
I don't think nh2 is saying that this is how the "issue" should've been solved here, but that it's a fatal flaw of C itself (yes I know, unions, but no one uses them).

But also, its not like there were many alternative languages when the POSIX standards were designed so it's fair that it works like this.

Re:Sshole by xorcist on Thursday November 05, 2026 @12:47PM (#50382029)
This argument is weak. C, and specifically libc, has errno. The error code is there. Not in band.

But there has to be something in the payload field when it is not applicable. A null is perfectly fine.

The argument for changing that to "", if I understand the above linked discussion correctly, was that some users of this api threw away errors and a dummy value would be harder to ignore. You could argue that these behaviours are less common in Rust or Go, but that's not 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:*").

Re:Sshole by forresthopkinsa on Thursday November 05, 2026 @03:07PM (#50383789)
> But there has to be something in the payload field when it is not applicable

Not in languages with ergonomic sum types. A `Result` or `Either` will have no payload field on the error type. The compiler won't let you dereference it.

Re:Sshole by aidenn0 on Thursday November 05, 2026 @05:55PM (#50385716)
This is wrong. errno is not to be used to detect errors, only to get the cause of the error once an error has been detected.
Re:Sshole by lemonwaterlime on Friday November 06, 2026 @10:12AM (#50393300)
In Haskell, one would return a Maybe or an Either here.

    Maybe
    ——————
    Just hash
    Nothing

    For Either
    ————————————
    Left err
    Right hash
The program would be designed around not allowing this failure mode to even be possible.
Re:Sshole by aidenn0 on Thursday November 05, 2026 @05:54PM (#50385705)
Even C has a better type system than C: All modern C compilers support an extension that lets you flag a function's return-value as requiring a null check.
Re:Sshole by muvlon on Thursday November 05, 2026 @08:52AM (#50379062)
C's lack of all of

- sum/union types

- multiple return values/tuple types

- exceptions

means that most ways of returning an error from a function are not available in C. Because of this, many C APIs, including `crypt` in this case, resort to in-band error signalling by using a sentinel value (like NULL, a negative integer, "*" etc.).

Most other languages lack one or two of the above, but very few lack all three, and so most have developed a different culture around error reporting (Result in Rust, multiple return values in Go, exceptions in Java, Python etc.).

Re:Sshole by jppittma on Thursday November 05, 2026 @09:29AM (#50379511)
C has sum types and unions? What are you talking about? You don't need multiple return. You can use a struct and it's the same thing.
Re:Sshole by adgjlsfhk1 on Thursday November 05, 2026 @10:51AM (#50380655)
not in a way that forces the user to actually handle them properly. C is a weakly typed language and as such is pretty badly suited to systems programming.
Re:Sshole by uecker on Thursday November 05, 2026 @11:05AM (#50380868)
C is not a weakly typed language, this is just nonsense. There is also [[nodiscard]] which you can use to make sure a return value is handled.
Re:Sshole by muvlon on Thursday November 05, 2026 @11:51AM (#50381398)
What is a weakly typed language to you? To me, implicit type conversions are a big part of the definition, and C is flush with those (void* converts to any pointer, all kinds of number conversions, arrays decay to pointers, functions convert to pointers, lvalue conversion strips const/volatile etc.).

However, I also think "weakly typed" is not a well-defined concept, everyone seems to apply it differently. In particular, it seems to mostly mean "playing it loose with types in a way that I don't like".

Re:Sshole by uecker on Thursday November 05, 2026 @12:01PM (#50381507)
Yes, it is not well-defined. But most conversions, function calls, and accesses are strongly typed in C, so I would not call it weakly typed.

Implicit conversion could be seen as weak typing, but it is limited to specific arithmetic operations in C. I would see implicit conversions as problematic if they allowed operations that make no sense. Decay of arrays and function to pointer conversion also do not seem problematic to me for this reason.

Void pointers are also weakly typed, but they are specifically designed for this. So if you do not want this behavior, one would simply not use them. It is a bit like using "unsafe" in Rust.

Re:Sshole by adgjlsfhk1 on Thursday November 05, 2026 @03:09PM (#50383815)
C is definitely not the most weakly typed (e.g. Javascript/PHP are both weaker), but IMO C is weaker than python in that python will give you a runtime type error if you do something invalid, while in C you cast something and just get UB. Most higher level strongly typed languages are able to make their casts checked because they have generics to remove the need for 99% of them, but C is missing those features and thus has to rely much more heavily on unchecked and unsafe runtime casts.
Re:Sshole by uecker on Thursday November 05, 2026 @04:44PM (#50384876)
Casts are escape hatches just like "unsafe" in Rust. You can write safe generics in C that either do not use casts or wrap all unsafe operations behind type-safe interfaces.
Re:Sshole by muvlon on Thursday November 05, 2026 @10:58AM (#50380766)
C unions are sum types if you just "draw the rest of the fucking owl". C has sum types the way x86 assembly has function calls. You see, there are "call" and "ret" instructions. Just figure out calling convention and the stack yourself, it's easy. And yes I can stick the tag next to the union myself in C and write a bunch of functions that hopefully deal with it correctly. I can even write some macros that look like pattern matching and maybe convince myself that it's a language feature, but it just isn't.

And it's very evident why the difference matters. People gravitate to what's readily available and ergonomic. That's why every C API under the sun (most definitely including libc itself) does janky stuff like returning -1 or "*" on error instead of building their own bespoke Result-style abstraction. A programming language is not just the things that are in some way possible within it, but more importantly the things that are easy in it.

Re:Sshole by uecker on Thursday November 05, 2026 @11:12AM (#50380936)
While I agree that this should come out of the box, the "rest of the fucking owl" is a tiny macro or function, so not exactly something difficult to do complex as "drawing an owl" would imply.
Re:Sshole by creata on Thursday November 05, 2026 @11:15AM (#50380968)
But then you're writing C in a way that almost no one else writes C. And you still don't have Rust's match ergonomics.
Re:Sshole by muvlon on Thursday November 05, 2026 @11:18AM (#50380986)
There are different amounts of effort you can pour in to this depending on your requirements, and we can argue about those, but to me the evidence is clear: It's too much of a hassle for library authors to actually go and do it. Everyone just uses sentinel values instead.
Re:Sshole by dvdkon on Thursday November 05, 2026 @01:15PM (#50382361)
Sure, when I last wrote a sizeable chunk of C, I wrote those macros and with a bit of help from new-ish compiler warnings, I got a reasonable error handling system in C. But whenever I interacted with anyone else's code, they apparently didn't get the memo that this is possible and desirable.

A language should encourage writing robust/secure code, not just make it possible.

Re:Sshole by uecker on Thursday November 05, 2026 @02:20PM (#50383210)
While I see the point, I am not entirely sure I agree. I kind of like that C is not an opinionated language which tells you exactly how to write things. This comes at the cost that many people will do things in a bad way, and this is difficult to fix, but still probably easier than getting them to move to a different language.

In any case, I do not think the issue here is caused by not having a pre-defined result type. Checking for null would have been fine. The issue was that to accommodate old and broken code a compromise was made. But as long such compromises are done even where fixing such old code would be quite easy, it is clear that changing to another language is completely unrealistic. So what are we even talking about? The real issue is that nobody wants to spend money or effort maintaining the old code. No language can fix this.

Re:Sshole by tialaramex on Thursday November 05, 2026 @09:45AM (#50379715)
One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

And really often when your "in-band error signalling" in Rust is more expensive you realise (perhaps to your horror) that your C wouldn't be correct, so you just dodged a bullet. For example Option<OwnedFd> is, like OwnedFd, a 32-bit value, Unix has always reserved -1 to mean invalid, so Rust can treat that as a niche. But the Windows equivalent Option<OwnedHandle> can't do this because, well, cue Raymond Chen: https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40...

Re:Sshole by creata on Thursday November 05, 2026 @11:14AM (#50380950)
> One of the small but important significances of Rust is that it very often gets to have the same machine code as a correct "in-band error signalling" strategy from C but with source code that the maintainers can actually understand.

But aside from NonZero- and NonNull-based layout optimizations, I don't think Rust is doing anything special to obtain that machine code - it's just relying on LLVM to optimize things.

Re:Sshole by tialaramex on Friday November 06, 2026 @05:28AM (#50390592)
Well, all of the niche optimisations, so that includes every normal C-style user-defined enumeration because (unlike C or C++) Rust says those aren't just the integers wearing a funny hat.

But sure, for the most part LLVM will do The Right Thing™ here without much help and Rust is just giving better ergonomics to the same backend.

Re:Sshole by sscaryterry on Thursday November 05, 2026 @09:47AM (#50379751)
The first 2 it has, and the 3rd, you really don't want.
Re:Sshole by muvlon on Thursday November 05, 2026 @11:05AM (#50380869)
The first 2 C has in the same way that I don't need Dropbox if I just have an FTP account and curlftpfs (https://news.ycombinator.com/item?id=9224).
Re:Sshole by sscaryterry on Thursday November 05, 2026 @11:41AM (#50381284)
Your analogy is way off...
Re:Sshole by Gibbon1 on Friday November 06, 2026 @06:18AM (#50390961)
I remember mucking with returning multiple values as anonymous structs. It was tantalizing close but no banana.

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);
Re:Sshole by K0nserv on Thursday November 05, 2026 @08:54AM (#50379085)
For the other part of the issue (the truncation) in SHA512 in Rust you could've had:

    struct Input<'a>(&'a [u8]);
    

    impl<'a> Input<'a> {
      fn new(input: &'a [u8]) -> Result<Self, Error> {
        if input.len() >= 256 {
           return Err(Errro...);
        }
        
        Ok(Self(input))
      }
    }
Now the truncation is a terrible idea in the first place, but at least with this it would have been entirely obvious to the caller that it was there.
Re:Sshole by uecker on Thursday November 05, 2026 @11:08AM (#50380892)
In C you could do just the same: https://uecker.codeberg.page/2026-10-24.html Whether you should is a different question.

In any case, you can not change the API of an existing function. But returning NULL for error has required by POSIX would have been just fine.

Re:Sshole by randomNumber7 on Thursday November 05, 2026 @09:56AM (#50379865)
Do you work in industry or academia?
Re:Sshole by f1shy on Thursday November 05, 2026 @11:06AM (#50380876)
I understand that other languages could help avoiding the semi-predicate problem. That being said, in C there are better ways around semi-predicates, as inelegant as they may be, is possble, is not an inherent problem of the language, but may a bad habit/idiom of it.