Live data from Hacker News

How memory safety CVEs differ between Rust and C/C++

kobzol.github.io

121–130 of 270 posts

Re: How memory safety CVEs differ between Rust and C/C++

#121

Earlier quoted context omitted.

If I were doing a code review, I would probably accept the code either with or without the assertion. The context of curl_getenv() makes it clear that null is not acceptable. If the author of curl_getenv() had evidence that callers are frequently breaking the contract by passing null, then perhaps the assertion would help shed some light on violators. Otherwise, I would expect everyone to play by the rules, making th…

That is exactly why you have a precondition or assertion. If everyone expects specific behavior - ie it’s in the contract - you require that contract.

The problem with asserts is that they are pretty dramatic and you crash the entire program.

We generally did this in the avahi libraries, be fairly liberal with asserts that "shouldn't happen", it is a source of complaints though because basically you can be using a third party library that uses avahi and have your program crash due to a bug in that library, or in avahi. It's extra fun when using some historical libc systems such as "NSS" and you load a plugin to do hostname resolution, which nss-mdns does.. now you can have any program on the entire system crash if you are assert happy.

On the one hand I agree that if the result is going to be memory un-safety then perhaps you should assert, but more ideally you'd just fail gracefully and throw or return an error. That can sometimes be tricky though, if there is no good way to return an error or return a NULL value or similar. Depending on the API.

Of course, this is the entire reason behind the error return traditions of Golang and Rust, e.g: https://doc.rust-lang.org/book/ch09-00-error-handling.html

Which basically says what I said above :)

But in the case of curl_getenv, returning NULL seems a valid possibility (https://curl.se/libcurl/c/curl_getenv.html) as that is indicated to be done if you don't find the requested environment variable. Arguably the NULL environment variable is not found. so, this feels likely to be acceptable. Though I could see an argument for you now assuming the environment variable you were actually looking for not existing, but you didn't actually ask for one, and now your logic is broken and maybe you introduce a different class of security bug because you change your behaviour based on some environment variable not existing.

As always everything is a trade-off...

Re: How memory safety CVEs differ between Rust and C/C++

#122
post #73

> Because sometimes I see people online who compare the number of CVEs in Rust and C/C++ software, [...] a rule of thumb i follow is that the second someone starts comparing or talking about the number of CVEs, i just ignore whatever they say next. its hard to think of a more useless metric than "number of CVEs", especially now. (edit: the people disagreeing are encouraged to share how you use "number of CVEs" to inf…

Oh no, you're in for a surprise. "Especially now" all these infosec folks "need to get CVEs fixed because compliance/SOC2, etc" and they will be even more up your a*! Something has to change with how compliance works. It is so outdated and crazy.

Compliance != security. It's almost the natural enemy of security.

Re: How memory safety CVEs differ between Rust and C/C++

#123

Earlier quoted context omitted.

Yes, but null pointers are so pervasive in C code that we really can't afford to put assertions everywhere. It's often better to let the app crash on violations.

An assertion is an app crashing on a violation. The problem is when it's not guaranteed to crash, and instead does something very wrong.

An assert is not guaranteed to terminate the process. In C, the most common implementation choice is to completely omit the check if you're not building in debug mode.

Re: How memory safety CVEs differ between Rust and C/C++

#124

Earlier quoted context omitted.

That is exactly why you have a precondition or assertion. If everyone expects specific behavior - ie it’s in the contract - you require that contract.

The problem with asserts is that they are pretty dramatic and you crash the entire program. We generally did this in the avahi libraries, be fairly liberal with asserts that "shouldn't happen", it is a source of complaints though because basically you can be using a third party library that uses avahi and have your program crash due to a bug in that library, or in avahi. It's extra fun when using some historical libc…

Returning to the context of this post, this is one of the things I really like about rust. (And zig, haskell, typescript, swift and others). These languages make invalid states impossible to represent. If my function takes a value of type T (or &T), you can't accidentally receive NULL. So you just don't need to worry about this stuff any more. The compiler simply won't compile the program if type checking fails. At runtime, I only have to consider valid values.

Re: How memory safety CVEs differ between Rust and C/C++

#125

Earlier quoted context omitted.

But it isn't different, that's Tony Hoare's Billion Dollar Mistake.

It is different. Handling `None` in a way that crashes your program is well defined in a Rust function. If you're using `unwrap` or `expect`, the program will crash with a stack trace and an error, instead of running into undefined behavior.

Yes. Its also explicit instead of implicit. In rust (and typescript, swift, haskell and others), you have to opt-in to nullability. By default, functions can't take a null in place of a non-nullable value. And whether a function accepts a T or an Option is part of the signature.

Re: How memory safety CVEs differ between Rust and C/C++

#126
post #70

Earlier quoted context omitted.

No, that's exactly what I'm aware of, and is exactly the wrong behavior I'm talking about. "Sometimes crashes, sometimes two's compliment" are extremely different behaviors, and not meaningfully different from just saying it's UB. It should always panic, with no way to disable it. The wrap around in release mode is simply bad behavior. It can't be relied upon (because it panics in debug), and it's not useful behavior…

It's surprising and potentially buggy behavior, but that's very different from undefined behavior. To such a degree that I think you honestly might not understand what it means, and what the risks are around undefined behavior, especially in the presence of an optimizing compiler. As a starter / refresher perhaps, both of these are perfectly permissible and happen in practice with UB, but never with "wrap or panic" /…

-fsantize=integer and whambo bambo C/C++'s "time traveling UB" is now more consistent, better defined, and safer than Rust's release build behavior.

I don't know if Rust will ever attempt to fix this mistake, but I seriously hope they do.

Re: How memory safety CVEs differ between Rust and C/C++

#127

Earlier quoted context omitted.

Until Rust has equal meta-programming support to C++ it's always going to be "slower". That's why people always say this because it's always true there is nothing Rust can do C++ can't but there is quite a few things you can do in C++ but not in Rust. Realistically the difference doesn't matter much and if you're writing code that must be as fast as possible your writing unsafe Rust that looks a lot more like C/C++ t…

> Until Rust has equal meta-programming support to C++ it's always going to be "slower". I don't think that makes a lot of sense even theoretically because of e.g. aliasing, but it doesn't matter because as I said, C++ chooses to be slower, Titus gives a number of examples where we know how to do X fast, and that's how Rust does X - in theory C++ could X the same way, but none of the three C++ compilers people actual…

No one writing anything that needs performance cares about some standard library ABI issues. Rust already has warts from bad API designs that constrains performance and they are unlikely to ever be fixed even with new editions. Rust will continue to pick up baggage as basically every language has done.

Aliasing has yet to provide any real benefit for Rust and a hell of a lot of issues. Maybe one day it will be a big win but realistically anyplace that aliasing matter c/c++ will just drop __restrict on it.

Re: How memory safety CVEs differ between Rust and C/C++

#128
post #70

Earlier quoted context omitted.

It's surprising and potentially buggy behavior, but that's very different from undefined behavior. To such a degree that I think you honestly might not understand what it means, and what the risks are around undefined behavior, especially in the presence of an optimizing compiler. As a starter / refresher perhaps, both of these are perfectly permissible and happen in practice with UB, but never with "wrap or panic" /…

-fsantize=integer and whambo bambo C/C++'s "time traveling UB" is now more consistent, better defined, and safer than Rust's release build behavior. I don't know if Rust will ever attempt to fix this mistake, but I seriously hope they do.

If you're talking about compiler flags,

  rustc -C overflow-checks=yes
or in your project config,

  [profile.release]
  overflow-checks = true
gives you consistent panics on overflow.

Re: How memory safety CVEs differ between Rust and C/C++

#129

Earlier quoted context omitted.

> If they rewrote it in C++ again, they would have most likely got the same result because they got a chance to fix a design that might not have been most optimal. This speculation has been offered every time. It's not crazy to think this might be true, but it's also not crazy to think that if C++ keeps leaving performance on the table and Rust doesn't that adds up for real projects. When Titus wrote "ABI: Now or Nev…

Until Rust has equal meta-programming support to C++ it's always going to be "slower". That's why people always say this because it's always true there is nothing Rust can do C++ can't but there is quite a few things you can do in C++ but not in Rust. Realistically the difference doesn't matter much and if you're writing code that must be as fast as possible your writing unsafe Rust that looks a lot more like C/C++ t…

> Until Rust has equal meta-programming support to C++ it's always going to be "slower".

What metaprogramming does C++ have that rust is lacking?

If you need more than traits + generics, rust also has proc macros. Proc macros are essentially portable compiler extensions. They take in a stream of symbols from the user's program at compile time and emit rust code that gets passed straight to the compiler. You lose out on syntax highlighting and they make compilation slower. But macros are essentially compile-time code gen. They work great. In rust, you can do things like JSX at compile-time without any special compiler support. (See: leptos.)

> Realistically the difference doesn't matter much and if you're writing code that must be as fast as possible your writing unsafe Rust that looks a lot more like C/C++ then anything Rust.

I agree that the difference is small in practice. Good rust often does look a lot like C - with plain structs everywhere and lots of global-ish scoped functions.

But I don't agree about unsafe. I've spent some time porting well optimised code from C to rust. I generally find I need far less unsafe code than I expected. I ported a ~500 line skip list implementation from C to rust a few years ago. I think my rust code ended up using just 2 unsafe functions. The rest of the code didn't need any unsafe at all.

My skip list was a monster to debug in C because most logic bugs ended up corrupting memory. As a result, a bug in one function caused crashes in far away code. In rust, debugging was much easier. There wasn't any "spooky action at a distance". And that let me reason about the code much more easily. As a result, once I got it working I ended up adding a few more optimisations in rust that I was too overwhelmed to write in C. The rust code is now ~2-3x faster.

If you're interested:

https://github.com/josephg/jumprope-rs#benchmarks

C code is here: https://github.com/josephg/librope

Re: How memory safety CVEs differ between Rust and C/C++

#130

Earlier quoted context omitted.

> Until Rust has equal meta-programming support to C++ it's always going to be "slower". I don't think that makes a lot of sense even theoretically because of e.g. aliasing, but it doesn't matter because as I said, C++ chooses to be slower, Titus gives a number of examples where we know how to do X fast, and that's how Rust does X - in theory C++ could X the same way, but none of the three C++ compilers people actual…

No one writing anything that needs performance cares about some standard library ABI issues. Rust already has warts from bad API designs that constrains performance and they are unlikely to ever be fixed even with new editions. Rust will continue to pick up baggage as basically every language has done. Aliasing has yet to provide any real benefit for Rust and a hell of a lot of issues. Maybe one day it will be a big…

> Rust already has warts from bad API designs that constrains performance and they are unlikely to ever be fixed even with new editions.

Like what?

> Aliasing has yet to provide any real benefit for Rust and a hell of a lot of issues.

Yeah, the performance wins so far are quite small. But rust's noalias-by-default did unearth a whole lot of latent bugs in LLVM. Even if you don't care about rust, its great that rust led LLVM to track down and fix these bugs. They affected C/C++ code too.

> realistically anyplace that aliasing matter c/c++ will just drop __restrict on it.

Is there a way to tell? When I'm writing C, I have no idea if using restrict will help other than staring at the assembly. (Or just trying it). I'm also leery of using restrict in C because its so hard to audit callers. How do you know when restrict is safe?

Post reply on HN