Hacker News
points by my-huge-pony 3 days ago | 0 comments
How would using a language different than C help here? At it's core this bug is a logic bug, where "*" == crypt(x), because crypt in musl started returning "*" in some cases some years ago. This could be Rust. This could be Go.

Not every C vuln is a memory safety problem.

[–] rhdjebejdbd 3 days ago
Another comment said that returning a * was to avoid returning null which would crash users of the library who didn't check for null. That seems like a C smell to me
[–] ethin 3 days ago
Not really. This is like if the char* were an Option<String> but you had programs which stupidly didn't check if the Option had something and assumed it always did. As others have said, Rust would not prevent this.
[–] K0nserv 3 days ago
`Option<String>` or `Result<String>` forces the check at the callsite. Unlike C, you cannot forget that the function can error. This absolutely helps prevent issues like this.

Now, the decision to return `"*"` instead of letting poorly written code crash on `NULL` is not good either.

[–] pheggs 2 days ago
I don't think it's that people forget it while writing the code, it's that people want their program to work with as little effort as possible and decide to ignore it "for now". And that definitely can happen with Option and Result as well. There's no protection for not checking return values, and it's probably not even possible to protect against (in entirety)
[–] _flux 2 days ago
Well they would use `unwrap` if they were lazy, but at least that would be quite visible in the code and would not result in undefined behavior due to operating on a null value. And there might be a group of developers who first choose to use .unwrap. I hope it is the minority.

Given how easy it is to just forward that error to the caller, I think most Rust developers would just choose to do that.

[–] uecker 2 days ago
While dereferencing a null pointer is technically UB, it practice it leads to a safe trap. So .unwrap is the right comparison. If you always forward the error to the caller, it will also just lead to some random error message at some point, not really different to a crash.
[–] K0nserv 2 days ago
> There's no protection for not checking return values, and it's probably not even possible to protect against (in entirety)

That's exactly what you get with Result and Option, it's impossible to forget.

Compare:

    char* hash = crypt(key, salt);

    // We crash here on the hash derefence, it's no clear from the code
    // that we were supposed to check for NULL or that we forgot. 
    if (!strcmp(hash, expected)) {
    }
To:

    let result: Result<Vec<u8>, Error> = crypt(key, salt);
    // We MUST write this line, which is a very clear signal
    // that we can panic here.
    // This can be grepped for or forbidden.
    let hash: String = result.unwrap();

    // if result == expected { would type error
    if hash == expected {
    }
[–] pheggs 2 days ago
That's exactly what I mean. And I have seen a huge amount of unchecked unwraps in production code in the past years
[–] K0nserv 2 days ago
Sure, but it's far better than the C code and I don't think in Rust a library would start returning `Ok` with sentinel values to "fix" shit calling code. Maintainers would, rightfully, tell people to fix their code.
[–] realharo 3 days ago
>because crypt in musl started returning "*" in some cases some years ago

In Rust, the type of the return value would be a `Result<T, E>`, which means the chain of events that led to this would never have started in the first place.

[–] my-huge-pony 3 days ago
In C the return value would be NULL, except it was changed by musl at some point.

Edit: at the beginning, crypt always returned a value. So hypothetical Rust return type would be a String, and then it would be retained for backward compatibility. Or something else would happen, this is a complex logic bug triggered by a 25 year old chain of events.

[–] realharo 3 days ago
The type of the value in C is `char*`.

That's what led some people to write code where it crashes without checks. Which is still their mistake. But if the type was something that can't be implicitly used like an always-present value, that would have prevented the situation, even with those same people.

[–] uecker 2 days ago
A pointer is a type where a null pointer can't be used as a regular value but leads to a trap when dereferenced.
[–] realharo 1 day ago
By "can't be used as a regular value", I meant that it would be a compile-time error. That you have to explicitly opt into skipping the check, e.g. with `unwrap` in Rust, or with something like `!` in languages where `string` and `string?` are different types (like TypeScript, Kotlin, Swift, C#, etc.)

Trying to change a function return type from e.g. `string` to `string?` would make the backwards-incompatible breaking change immediately visible, and break the builds of all code that relied on not having to check it back when it was `string` - at compile time.

[–] uecker 1 day ago
I see. For safety a run-time check is enough though.

But it turns out one can have a compile-time error forthis in C as well: https://godbolt.org/z/8E9z66cfs

[–] atomicUpdate 2 days ago
You mean could be a `Result<T, E>`. There’s nothing in Rust that forces that.
[–] Gibbon1 2 days ago
I write firmware where the program has to run for years unattended.

You are right, C with manual memory management, no lifetime management, lack of phat pointers and a culture of leet has problems with deranged maddening bugs that other languages don't have.

But this bug first shows how inband signaling is bad news usually. But worse it's a failure to adhere to the spec properly. If you implement a library and you don't adhere to the spec you're going to bone someone.

Perhaps even worse is obviously the spec doesn't come with any standard testing framework. That was also the root of Apples infamous goto bug.

[–] bmacho 3 days ago
> How would using a language different than C help here?

Easier to formulate invariants and check/prove them. Better language correlates with safer software.

[–] slopinthebag 3 days ago
why does this logic bug exist? why are they returning a string, why not a result type, (nil, err), or something else?

oh right, because c is a crusty old language with barely any features.