Hacker News new | ask | show | jobs
by kzhukov 1938 days ago
Security is not a feature of the language or tool. Neither Rust nor C++ are fully secured even though the former could find more memory safety problems at compile time (but not all of them).

Security is the process. It contains continuous risk assessment, penetration testing, fuzzing and using various other tools throughout the product development to eliminate attack vectors. Only then you could build a secured product. Just rewriting everything in Rust won't make it.

6 comments

Very concisely and correctly put, agreed.

I sometimes work on the infosec side of the house. It's easy to point at vulnerabilities due to endless memory access problems in C code and fixate on that. And it's true, so it feels satisfying.

Just rewrite everything in not-C and this is fixed! And that's true as well. But remeber here the word "this" refers to the memory access bugs. It doesn't refer to vulnerabilities in that sentence.

Plenty of systems without a single line of C/C++ code and we have no shortage of ways to break in anyway. So in one sense, everything changed. But wearing the black hat, nothing really changed since I compromised the system anyway. A new language without all the engineering process parent post describes, won't magically get there.

For an existing system "just rewrite everything" will guarantee way more bugs for years to come simply because the old system has been battle-tested and reinforced for years. In the long haul the rewrite will converge to a better state but that haul is long (and assumes budget will remain in place long enough, which might not happen so you end up with a half-baked rewrite).

For new systems starting from scratch that may in any way ever be run in a security relevant context, sure, starting with not-C is a good idea today.

Unfortunately Rust seems to be the only alternative for use cases where C was actually needed (if it could be written in Java or Go or Python or ... then it didn't really need to be in C in the first place). And sadly rust is fairly user-hostile language so my guess is plenty of new projects will start with C for a long time due to lack of friendly alternatives. And tooling.

> And sadly rust is fairly user-hostile language

Could you elaborate on that? I started using Rust at the end of last year, and have found it to be one of the most user-friendly languages I've learned. There is clear documentation that is easy to generate with the standard toolchain, along with a very good language guide.

The compiler error messages are an absolute joy as somebody coming from C++. Pointing out exactly where the borrow checker found errors, or exactly what the type signature should be for a trait, is really useful.

Well, yes, coming from C++, Rust must seem like a breath of fresh air. Coming from almost any other language however, I can understand that it feels a little bit intimidating (and getting more so with every exotic feature added - "const generics" anyone?), no matter how friendly they try to make the documentation and the error messages...
I can see that a bit. I suppose for me, the biggest questions are (1) is there a way to unambiguously express my intent and (2) is there exactly one way to do so? I'm fine with more features so long as they match some specific problem that people have that isn't already solved in another way.

For example, in the case of const generics, I've used the equivalent in C++ to make a class for a geometric vector in N dimensions. The size varies depending on the number of elements, but is known and can be checked at compile time. The existence of const generics is very closely matched to this particular use case.

I don't think "there is exactly one way to do it" is actually a good guiding principle.

Forcing it down to an overly simplistic setting, if I need to add 5 to a number I can increment 5 times, or add 2 and then 3, or just add 5. Which of those options should we remove?

Perhaps we mean, instead, that there should be one best way, but (sliding into metaphor, I hope) what about when we have a language without 5 and we are considering adding it? Is the use case already handled by 2+3? How are we to decide?

This is just false, Rust is much easier to use than Python (just the package management situation is so much better)
Really? Then there is no point in starting new projects in Python. You can write everything in Rust. You’ll get performance as a bonus.
No python has way more packages like the op said. You can't start projects in rust where you need a significant amount of dependencies because rust is not there yet.

Language wise rust is better the python in every way except for compile times.

> And sadly rust is fairly user-hostile language

Rust seems to me to be user-frienndly, though for the cases where it's safety features are critical and at the fore, it front-loads pain that would be dealt with eventually, which can make it feel intimidating. But its forcing you to confront things that are likely to cause subtle bugs if dealt with sloppy (or overlooked), not actually adding unnecessary complexity.

User hostile? I've found it to be the exact opposite of you consider the user is me, the developer. If anything the language and tooling has gone to lengths to be user friendly.
Surely part of the security process should be to identify parts of any system that are routinely causing security problems and replace those parts by better ones (as they become available) in any context where security is important (which seems to be rapidly converging on "almost all contexts that normal people using computers encounter in day-to-day life").

Rewriting C or C++ applications in Rust won't fix all security problems. But it would be forward progress. It's just like wearing seat belts won't stop people from being injured in car crashes. You could say that "safety isn't a product but a process," which is fine, but if your safety testing process finds that wearing seatbelts reduces injuries substantially it seems pretty obvious that using a seatbelt product is the way forward. At least until someone invents a better replacement for seat belts, or supplements them with other features like airbags, automatic braking systems, self-driving, etc...

“Security is not a feature of the language or tool […] Security is the process”

True, but if the process discovers that a lot of lives are lost because of the use of unsafe tools, it changes the tools to add safety features (https://en.wikipedia.org/wiki/Chainsaw_safety_features), or ditches them altogether (https://en.wikipedia.org/wiki/Hazard_substitution#Processes_...)

So, yes “rewrite in rust” isn’t the full answer, but that does not imply “don’t write it in C” isn’t part of the answer.

This is true, and I suspect that while Rust has this aura of compiler enforced memory safety, its quite possible that the piles of c/c++ tools which _can_ be enabled and run against C code bases make it generally just as secure in practice when those tools are actually enabled.

The simple classes of bugs enforced by rust, are also caught fairly quickly with any kind of memory sanitizer (valgrind?), combined with static analysis tools (coverty?), and automated code quality standards (misra). Run a CI/Code coverage monitor while looking for for these kinds of errors, and I would bet the results are actually better than plain rust due to the maturity of some of these tools.

That's plain wrong. If static analysis was as reliable as rust, why would all these C codebases still be full of buffer overflows and memory errors? The borrow checker is, in effect, a static analysis tool... that requires a lot of annotations from the programmer, and blocks compilation otherwise. The equivalent for C would be to annotate all your functions in some formal language and then run, say, frama-C.

About valgrind or sanitizers: they're runtime, so just like tests they can only show the presence of errors, not their absence. Like dynamic type checking.

The borrow checker is a static analysis tool that is 100% sound, unlike the tools for C/C++
And it only catches a trivial minority of actual security problems, which can occur in a lot more ways than use after free/etc.

IMHO, Rust simply isn't good enough at catching all types of bugs to justify rewrites at this point, and its likely when you look at some of the work being done at the processor manufacturing companies that they don't believe it either.

Consider: https://en.wikichip.org/wiki/arm/mte, https://en.wikipedia.org/wiki/Intel_MPX, and https://lwn.net/Articles/718888/

There are quite a number of these in the pipeline, which make some of what rust does redundant.

Uh, not at all. Rust’s compiler probably catches like half of all bugs that currently lead to security issues in memory-unsafe languages. And the things you mentioned are similar band-aids, not fixes.
The borrow checker is 150% sound, it complains about errors but also complains about a lot of things that could have been perfectly fine in reality. There are plenty of times when multiple mutable references would be perfectly safe for instance.

Valgrind might not catch 100% of errors, but at least what it catches are actual errors I care about.

That's an interesting (and correct!) objection — in stats terms, it's the choice between having false positives (your type checker rejects some valid programs) vs. false negatives (Valgrind didn't catch these cases).

If you're writing safety-critical software though, being forced to restructure your code to satisfy the type checker (which, in this case, is kind of a simple proof assistant) seems like a sane tradeoff.

You're talking about completeness: the borrow checker rejects some valid programs (any type system will do that). Soundness is talking about catching incorrect programs; the borrowck doesn't allow a single invalid program through.
Valgrind is not a static analyzer, You should use some C static analyzers to compare the false positive rate. You can use various dynamic instrumentation based tool to detect other classes of bugs that are not caught by the borrow checker in Rust.
Valgrind only catches the errors that your test suite triggers.
>> Security is the process.

Yes, but do the programming languages / tools we choose make the security process easier?

There is no perfect solution, but some programming languages / tools are better than others for preventing unexpected behavior that can lead to insecure programs.

This sounds wise, but I would bet that for the same amount of effort, "just rewriting everything in Rust" will give a better return than the kind of process you're advocating.
We can pretty much also conclude (given data on CVEs, etc.) that -- in practice -- programmers either aren't following that process or it doesn't have enough of an impact to actually get rid of memory errors of the kind Rust protects completely against.

(Caveat: Obviously 'unsafe' is relevant to this discussion, but at least you only have very small areas of code you need to check extra carefully.)