Comment by nh2

2 days ago

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

C has unions. C has sum types. People have been doing tagged unions in C for longer than rust has been a language

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

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

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

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

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

  • That's a universe apart from real sum types with exhaustiveness checking and pattern matching.

This argument is weak. C, and specifically libc, has errno. The error code is there. Not in band.

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

The argument for changing that to "*", if I understand the above linked discussion correctly, was that some users of this api threw away errors and a dummy value would be harder to ignore. Maybe one could argue that these behaviours are less common in Rust or Go, but not because of a technical difference.

The world is not perfect and sometimes you really need to work around bad users. Even in Rust or Go. I am not convinced this was such a case. If you are convinced it was it would still have been better to select a less trivial dummy value (such as "*:ERROR:*").

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

  • In Haskell, one would return a Maybe or an Either here.

        Maybe
        ——————
        Just hash
        Nothing
    
        Either
        ——————————
        Left err
        Right hash
    

    The program would be designed around not allowing this failure mode to even be possible.

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

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.

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

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.