Live data from Hacker News

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

kobzol.github.io

181–190 of 270 posts

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

#181
post #169

Earlier quoted context omitted.

May. If. If. If. In case. We are talking about an extremely simple straightforward API with an obvious contract. It's good enough for this function to reliably surface almost all wrong uses with a segfault immediately. Wrong use will result in segfaults and otherwise bugs and crashes. The goal is not to work when used wrong but to work when used right. You cannot save the world from scratch in every little function.…

> You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on. Or you can take all of 10 minutes to put sanity-check assertions at the start of all your public-facing API functions, eliminating a source of security bugs, get on with your life, and worry about the performance implications as and when it becomes a problem (hint: it's never going to become a…

You can try and do this if it's a relatively narrow public facing API, but otherwise this is a theoretic ideal. In practice, if you add an assertion for every pointer argument to every little function, you'll go insane, and it is completely pointless, and the code will not be readable anymore.

There are so many other interesting and relevant invariants that are usually in an API contract that are much harder or impossible to check upfront (let alone express formally in a type system), and even violations may be impossible to diagnose when they happen.

People focus on NULL because that's the only way they can apply their silly limited type systems. But NULL checks give very little return for investment. In practice, you'll see templated Option types and whatnot, and when I have to look at or even work with such code I want to kill myself because it's so painful.

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

#182
post #169

Earlier quoted context omitted.

> You cannot save the world from scratch in every little function. You still have a job to get done, and you have to move on. Or you can take all of 10 minutes to put sanity-check assertions at the start of all your public-facing API functions, eliminating a source of security bugs, get on with your life, and worry about the performance implications as and when it becomes a problem (hint: it's never going to become a…

It takes a lot longer to figure out if it'll be a problem than to just add the check. And you don't have to ponder whether it's possible for a null to get there, because now it's fine if it does.

Are you talking about extending the API contract to allow for NULL? That is often the path to madness, especially if it requires complicating the signature (return value etc). Better to just assert/crash.

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

#183
post #136
post #29

Is it only me that would have expected curl_getenv() to have an assert that it's argument isn't NULL? I know this doesn't stop runtime problems in release builds, but i'd have thought this sort of simple precondition check would help users find problems in their library useage. It's not going to stop you passing a non-terminated string, or other such invalid input though, which is I guess more the point, that it's to…

> it's not possible to add a precondition without introducing a runtime overhead Indeed. Adding an assertion to a single function isn't a big deal, but if every function has to check all of it's arguments, that's going to add up. And even if you could have the assertion only in debug builds, that isn't enough unless you have a very exhaustive test suite, because an edge case could trigger undefined behavior in produc…

It is so bad as C culture, that the only way to fix the culture is by having hardware where those C programmers no longer have a say on bounds checking.

Most systems languages, with exception of C, have ways to do bounds checking, even C++ and Objective-C, by using the respective collection classes.

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

#184

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…

Crashing a program is always a much better alternative than behaviours that silently lead to memory corrupt, having much severe outcomes than a crash.

Ah but what high integrity computing, well there neither crashes nor memory corruption are welcomed, hence programming guidelines and certification workflows that would make most C devs cry with the language features they are allowed to use, and how each line of code gets analysed by tools and humans.

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

#185
post #111

Earlier quoted context omitted.

> Rust's standard library is incredibly thin (intentionally so). As a result, you need to use the crate ecosystem. This comes with some downsides. This is no different than C++. C++ standard library made so many compromises in the name of ABI compatibility almost none of the library is actually usable for any use case. So people start to quickly add things like boost, abseil, folly, Qt, asio, imgui, doctest etc. Ther…

I've never seen a C program use hundreds of dependencies. This is typical in Rust (and Node). I know a few high assurance teams that dropped Rust for this very reason.

I haven't seen hundreds of dependencies in C projects either. But I _have_ seen on the scale of 1s to 10s of libraries and algorithms vendored in (sometimes just a header or 5).

It's also an indirect risk, but I've seen C projects reimplement things that would be a dependency in Rust, and introduce subtle (or not subtle) bugs.

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

#186
post #163
post #142

There can be made very good arguments why C is less safe than Rust, but null pointer dereferences which are perfectly safe everywhere except on weird platforms (and usually could be made safe even there by turning on a compiler flag) seems a very misleading argument. And as as the Cloudflare incident showed, a Rust unwrap can have equally bad consequences. (or as Ariane 5 showed, a safe overflow in Ada can have explo…

These Rust discussions typically contain grains of truth wrapped into exaggerations while downplaying or ignoring the two big issues of Rust, which are complexity and out of control dependency trees. For the first issue of complexity, the reply is along the lines of show me the code and then either nitpicking that to pieces or explaining in several paragraphs how the behavior is perfectly reasonable and in fact quite…

I would argue that what you call complexity here is better referred to as explicit or visible complexity, as compared to hidden or implicit commplexity. The complexity as such comes with the domain and its application; but with Rust you can see it and you are forced to deal. With C++ you can get away with pretending that it is in fact not there.

This is honestly not intended to pick on C++ as a Rust fanboy, however. It really isn't.

For instance, to be a little unpolished, I would shrug off the unwrap incident as doing it the C++ way and pretending that the complexity does not need to be solved right then and there because ItShouldNotHappen or IKnowWhatIAmDoing.

I prefer Rust over C++, but I do not hate C++. This isn't even about C++ anyway.

When it comes to out of control dependency trees, I agree with you. But there is a trade off here too and that is that either you implement the thing or you externalize it. I use both approaches myself and am in no way excusing anything or attempting to be Rust's defense lawyer. There definitely seems to be a leftpad-problem brewing in the Rust community.

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

#187

Earlier quoted context omitted.

It takes a lot longer to figure out if it'll be a problem than to just add the check. And you don't have to ponder whether it's possible for a null to get there, because now it's fine if it does.

Are you talking about extending the API contract to allow for NULL? That is often the path to madness, especially if it requires complicating the signature (return value etc). Better to just assert/crash.

No. I'm talking about adding the check to reject NULL. Then you don't have to spend time justifying or figuring out why a NULL can't turn up here.

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

#188

Earlier quoted context omitted.

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 r…

Zig still doesn't have a way to represent that a pointer to a heap allocated region is no longer valid.

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

#189

Earlier quoted context omitted.

Are you talking about extending the API contract to allow for NULL? That is often the path to madness, especially if it requires complicating the signature (return value etc). Better to just assert/crash.

No. I'm talking about adding the check to reject NULL. Then you don't have to spend time justifying or figuring out why a NULL can't turn up here.

So reject as in assert? But how does that go together with what you said, "because now it's fine if it does"?

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

#190
post #149

Earlier quoted context omitted.

Can you do that with a dynamic array? If not, it's pretty severely limited (unless you mean that literally forbidding dynamic memory is usually not done, which I guess it's true outside of some embedded code but not a particularly meaningful statement).

The syntax is rather confusing. This is not an array of length 1, but rather a pointer which points to a memory segment which is at least 1 integer long. In other words, any array of any length (>=1) would be a valid argument to this function. "static" here means "don't do the normal thing where you totally ignore the length of an array argument to a function (which is, like usual, really just a pointer)". "static" w…

I can understand that this a somewhat practical solution to the problem of codebase is all C and all I have is a C compiler, so there is merit to using the static keyword here. But it is also unwieldy and used rarely, so I do not think it is comparable to Rust’s borrow rules in any meaningful sense.
Post reply on HN