Live data from Hacker News

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

kobzol.github.io

251–260 of 270 posts

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

#251
post #250

Earlier quoted context omitted.

Typical invariant for me would look like: Given a, b, c input parameters to my func, it must hold that that a->m->t == b->t. c->mutex must be held, and c->cond is the condition variable that goes with c->mutex and will release any waiters on the buffer contained in a. Or: Integer x is representable using 12 bits only, Integer y should be a multiple of N and I have a integer s is used as a bit-shift that should be les…

> it must hold that that a->m->t == b->t. So define a wrapper type that represents that invariant (it's not going to take up space at runtime), where the only constructor enforces it? > Integer x is representable using 12 bits only, Integer y should be a multiple of N and I have a integer s is used as a bit-shift that should be less than 8. Those are all standard things that already exist? > Or: I need to guarantee t…

Please, go ahead and type the example. I think you are trolling.

> Define a command algebra in which the "ok" syscalls are a subtype

Dude, it's clear you're not doing any actual work. You are living in an ivory tower, and you underestimate the complexity and detail and volatility of real world applications by at least 3 orders of magnitude. You don't understand how to modularize and contain complexity.

You _cannot_ complete a project with this attitude.

You are ignorant of the fact that a type system is necessarily a blunt simplification of the real complexity. Therefore, use of types must be pragmatic, and actual logic must be coded in normal code (which should be obvious but it isn't to type theory weirdos). Otherwise, you require dependent typing or whatever, and you will have to write your code twice, once in a usable programming language and once in a very unusable programming language. Much more than only twice actually, given that all the implicit detail should apprently go explicitly formalized at the type level.

Just to make sure I'm not entirely talking out of my arse because I'm so incredibly annoyed by your otherworldly proposition, I asked an AI about the sel4 microkernel. It consists of 10,000 lines of C code (that says a lot about its practical utility, which is very limited), and of 1,3 million lines of manually written proof code (which says a lot about the practicality of proving).

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

#252

Earlier quoted context omitted.

Because no one is expecting it to work if a null is passed. Your total range of behaviours left are crashes, doesn't crash and is silently ok, or doesn't crash and causes something worse (data corruption, you get your product in a CVE, that area). My proposition is that "it's silently ok" isn't likely enough, which is in line with your position on "don't extend the contract to accept null". So what's left is crash, o…

If you follow that line of reasoning, you will end up testing almost every pointer before accessing it. The reason is that you are extending your valid state space massively since you aren't able to specify "this subset of 7 trillion distinct states is invalid, if it was the case we would have failed before". You are requiring yourself to find a valid outcome for an input that doesn't make _any_ sense in the context…

You're not "extending" the valid state space. That null value being passed to that function is already a potential state of your program.

You're actually pruning the valid state space; before, when the null value is passed to the function, there are more operations performed that have uncertain consequences. If you assert-and-fail when you get the null input, you've pruned those states.

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

#253

Earlier quoted context omitted.

If you follow that line of reasoning, you will end up testing almost every pointer before accessing it. The reason is that you are extending your valid state space massively since you aren't able to specify "this subset of 7 trillion distinct states is invalid, if it was the case we would have failed before". You are requiring yourself to find a valid outcome for an input that doesn't make _any_ sense in the context…

You're not "extending" the valid state space. That null value being passed to that function is already a potential state of your program. You're actually pruning the valid state space; before, when the null value is passed to the function, there are more operations performed that have uncertain consequences. If you assert-and-fail when you get the null input, you've pruned those states.

So if I understand correctly now, you _do_ proclaim to put asserts, not write code that somehow copes with the "possiblity" of NULL.

"Because no one is expecting it to work if a null is passed", so you can do whatever. If you write an assert for every pointer passed to every function, that will be a lot of asserts, for pretty much the same outcome in practice. Asserts are just marginally more ergonomic when they trigger, but are a nuisance in the code often. So my position is to use them judiciously, but not overdo it, be instead focused on the actual task.

When the lack of non-null assertions is an actual problem during development, you have much larger structural issues.

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

#254

Earlier quoted context omitted.

You're not "extending" the valid state space. That null value being passed to that function is already a potential state of your program. You're actually pruning the valid state space; before, when the null value is passed to the function, there are more operations performed that have uncertain consequences. If you assert-and-fail when you get the null input, you've pruned those states.

So if I understand correctly now, you _do_ proclaim to put asserts, not write code that somehow copes with the "possiblity" of NULL. "Because no one is expecting it to work if a null is passed", so you can do whatever. If you write an assert for every pointer passed to every function, that will be a lot of asserts, for pretty much the same outcome in practice. Asserts are just marginally more ergonomic when they trig…

Yes. The work to assert each pointer passed into a function isn't "high"; it's purely mechanical, it could almost be refactored automatically. But most of all, the effort required to prove you don't need to is _way_ higher.

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

#255

Earlier quoted context omitted.

So if I understand correctly now, you _do_ proclaim to put asserts, not write code that somehow copes with the "possiblity" of NULL. "Because no one is expecting it to work if a null is passed", so you can do whatever. If you write an assert for every pointer passed to every function, that will be a lot of asserts, for pretty much the same outcome in practice. Asserts are just marginally more ergonomic when they trig…

Yes. The work to assert each pointer passed into a function isn't "high"; it's purely mechanical, it could almost be refactored automatically. But most of all, the effort required to prove you don't need to is _way_ higher.

I thought so, too, at some point, and I put it to practice, actually did that for a long time. (I'm not _that_ much of a talker, I can back up what I'm saying with real experience having erred on the opposite side for a long time).

I thought so too, for many other things, and did so too in practice. Then I was doing thousands of little tasks in order to improve "security" and "correctness" and "proving everything", and never got to actually do anything useful, and my code became bloated to the point it didn't get to do much useful work.

Now I'm more considerate, trying to weigh everything in the larger context, is it REALLY required to get my job done?

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

#256
post #192
post #77

Earlier quoted context omitted.

I think none of this is special to Rust vs C++, except #4, because C++ doesn't have an equivalent

In 2026 it does, vcpkg and conan.

the fact that you mention two options, shows already that C++ doesn't have an equivalent

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

#257
post #159

Earlier quoted context omitted.

There’s a few things happening here. First of all, Rust is default safe. In C++ developers always trade performance for safety, in Rust they just swallow the penalty (which is often still performant enough). C++ code will often not be as memory safe as it could because someone decided to not use particular checks (like compiler-driven integer overflow checks). Secondly, Android C++ code is not particularly high quali…

> In C++ developers always trade performance for safety A "trade" implies care which isn't actually taken. What WG21 does is they assume that safety costs performance and so they throw away the safety hoping that this means they get performance. They don't measure, which is why you get to see first Herb Sutter explaining that C++ doesn't do bounds checks because they're unaffordable (notice he presents no data) and t…

_GLIBCXX_ASSERTIONS amd many similar flags and options exist for those that want it.

It is disappointing that some new features are default-unsafe but it is the C++ philosophy.

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

#258
post #159

Earlier quoted context omitted.

There’s a few things happening here. First of all, Rust is default safe. In C++ developers always trade performance for safety, in Rust they just swallow the penalty (which is often still performant enough). C++ code will often not be as memory safe as it could because someone decided to not use particular checks (like compiler-driven integer overflow checks). Secondly, Android C++ code is not particularly high quali…

> First of all, Rust is default safe. [...] in Rust they just swallow the penalty The borrow checker runs at compile-time, not run-time. Safety doesn't slow down your code except in a few small, specific ways like array bounds checks and UTF8 validity checking - but these checks also happen in unsafe rust too. The overhead is also mitigated by some of rust's other choices increasing performance. (For example, rust us…

Yes, that was what I was talking about. It seems that bounds checking and some other checks are becoming more palatable.

Perhaps C++ will start using them en masse and address the biggest memory-safety gaps :)

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

#259
post #248
post #245

Earlier quoted context omitted.

I did not know this was disputed, as even developer surveys in the Rust community highlighted complexity as a concern. But seems it is the overall summary of features that make it complex: lifetime, borrowing, a polymorphic type system, traits, multiple tools to manage lifetimes, macros, proc macros, async, unsafe, panic, unwrap, etc. But I agree it is much cleaner than C++.

Still not picking on C++ here. I would claim that these concepts pretty much all exist in C++ too, but often implicitly so. You would solve them by "braining them", with good rules of thumb, patterns, tests and deep knowledge and skill. In fact, you _have_ to solve them this way. In Rust the compiler is going to have a problem with your code unless you are explicit about borrowing/ ownership, gcc won't care. Which is…

I'm a really big fan of Rust, but I have to challenge this, GP was clearly talking about the complexity of the language itself, and that one is high.

Extra features and extra static checking add complexity. Extra complexity in the language can make simple programs harder, and they make complex programs easier to code and maintain, like you said.

However, nobody will ever be able to make a Rust equivalent of TinyCC, the language's rules are insanely complex, especially for things like GATs and the other tricky concepts it's trying to tackle.

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

#260
post #207

Earlier quoted context omitted.

A dereference in the final binary is not different from Rust. A dereference in the source code, that makes the whole program invalid, so the compiler doesn't emit a credentials check you wanted it to emit, is. For availability and stability concerns, the C approach is actually better, but for security and reproducibility, it is not.

The check is removed only if you already dereference the pointer before doing the check. But then, you also get the trap before the check. So the compiler eliding the check is not making this worse - as long as the zero page is not mapped. In any case, you can also configure GCC to not do this, and you can also configure it to insert explicit null checks before dereferencing a pointer. So C can offer you security and…

No, it's also removed if you dereference it later on, that's why it is said, that UB has time traveling behaviour. This means, that the compiler can emit a program, that let's you access data without a security check, while the crash only comes later. Also the crash can be removed, because dereferencing it is UB anyway, so the compiler does not need to emit it.
Post reply on HN