As I noted elsewhere in this thread ( https://news.ycombinator.com/item?id=14753692 ), I've actually been working on a low-level media decoder in Rust. Compared to my earlier work in C (the xmlrpc-c library, for example), my Rust code has stood up very well to heavy fuzzing. My xmlrpc-c code, on the other hand, was written with ridiculous levels of paranoia, and it has been the subject of multiple CVEs thanks to a third-party XML parser I used.
So in terms of practical engineering reality, Rust seems to make a big difference. And even more importantly, it means I can trust other people's libraries a bit more.
Rust's practical advantages seem to be:
1. Runtime bounds checks! I wouldn't have thought that this was the most important thing, but when fuzzing, it's the last line of defense against quite a few potential exploits.
2. Debug-mode checks for integer overflow. Rust defines integer overflow as an error, and debug builds will actually check for it. This turns out to work really well with fuzzers.
3. The borrow checker. In my experience, this is actually less useful than (1) and (2) above in terms of preventing security bugs. But it does help.
So as somebody who's written parsers for untrusted data in both C and Rust, I definitely found that Rust helped me a lot. Of course, Rust can't help with some forms of stupidity, such as writing a bad custom memory allocator and using it to handle key material. OpenSSL's problems go well beyond the ordinary hazards of C.
C.A.R Hoare talking in 1981 about how their Algol compiler customers reacted to the proposal of turning off bounds checking.
"Many years later we asked our customers whether they wished us to provide an option to switch off these checks in the interests of efficiency on production runs. Unanimously, they urged us not to--they already knew how frequently subscript errors occur on production runs where failure to detect them could be disastrous. I note with fear and horror that even in 1980, language designers and users have not learned this lesson. In any respectable branch of engineering, failure to observe such elementary precautions would have long been against the law."
The mainstream adoption of UNIX and with it, C, made many without the knowledge of how computing outside AT&T walls since the dawn of computing looked it, to cargo cult ideas that were proven wrong elsewhere.
But given that victorious get to re-write history and UNIX got adopted by the enterprise, those OSes written in safer systems programming languages get forgotten.
The recent example was to learn about PL/8 and how IBM used it, in a compiler framework similar to LLVM, to do the research that lead to RISC.
http://rsim.cs.illinois.edu/arch/qual_papers/compilers/ausla...
Imagine that! The RISC computer was designed using an OS written in a system programming language using Algol safety checks, including bounds checking, in the 70's.
EDIT: Added a link to the PL/8 research paper.
The Rust language is more than just memory safety. Memory safety is certainly the most prominent and unique feature of Rust, but the language also includes a lot of features that make writing good, bug-free code easier.
For example:
`Result`, when returned, forces all callees to check for errors. It's all too easy to throw away error codes in C/C++. This is probably one of the most important features in Rust, besides memory safety.
To easily consume an `Enum`, you have to use `match`, which forces you to handle all members of the enum. In C/C++ it's easy to build a switch for an enum, and then later when a new member is added to the enum and you forget to update all your switches.
Support for multiple return values (through tuples) means being able to avoid the nightmare of output pointer arguments to functions.
No NULL pointers.
Unit testing built into the canonical tooling.
Documentation testing built into the canonical tooling.
The `Option` type, which lets you return "nothing". In C/C++ you'd have to return a bool, and again use a nightmarish output pointer argument.
`OsRng` in the rand crate (go ahead, try to write a function that safely reads urandom in C).
No undefined behavior.
The list goes on. The flavor of it, is that Rust makes it easier to write defensive code. Defensive code is code that is hard to use wrong. For example, for a backup solution I built using Rust, I made all the cryptographic types their own unique, opaque types. EncryptionKey, HmacKey, Salt, etc. For someone new to the codebase, these types make it obvious what each function is asking for, and make it really difficult to use the functions or the types the wrong way. And they are super easy to build and use in Rust, see my code: https://github.com/fpgaminer/preserve/blob/master/src/keysto...
C++ would require a whole set of classes, and require instantiating a new class every time I want to add a new cryptographic type. Rust only requires 3 lines per type.
TL;DR: The real magic is that it's easier to do the right thing in Rust.
* option and result are also available in C++, the former is even standardised.
* random numbers support is quite good.
* C++ compilers will warn if not all members of an enum have been handled in a switch. It's also easy to assert/throw in the default case as a safety measure.
* tuples are likewise supported
* I believe something like your opaque types could be done with templates and tag classes in a similar amount of LOC.
But yes, in Rust it's generally easier to do the right thing. This is the big benefit of learning from others' mistakes and not having to maintain backwards compatibility with ancient decisions.
To me, this is super important. But yeah, it's still a good thing to have!
IIRC there are TMP-implemented versions of this that are safer (though a bit ugly to use) though.
Nowadays, you should just call getentropy (http://man7.org/linux/man-pages/man3/getentropy.3.html) instead of trying to read from /dev/urandom, unless you have special requirements.
It's even worse here on HN, where the a certain group seem to thrive on blasting not-Rust, especially if that not-Rust is C or C++. I've seen comments to the effect that anyone writing in C or C++ today is literally acting with reckless indifference to life and safety. It's terrible.
It's easy to find fault in C/C++. It's easy to parrot the Rust marketing. It's not so easy to actually have spent the time to learn and use Rust, let alone articulate the subtle ways that its design, taken as a whole, is better than alternatives.
Building those constructive arguments is _hard_. I remember reading an article about error handling. It had to have been at least 10 pages worth of content, covering the history of error handling throughout all the old and modern languages, and using examples for where each fails or succeeds. It culminates, more or less, in explaining exactly why the error handling of languages like Rust outperforms in the field of systems programming.
(NOTE: The author of that article was actually arguing for the error system used in the language they developed for their OS, IIRC. But Rust was pretty darn close to the ideal the author was going for, and they pointed out as much.)
My point is that it took 10+ pages to make a solid argument for _just_ error handling. Not many people have the experience, wisdom, time, and willingness to write even a fraction of such insightful content in a comment on HN.
That said, well written, balanced comments still generally float to the top in even controversial HN threads.
EDIT: I would also like to point out something specifically in response to "I've seen comments to the effect that anyone writing in C or C++ today is literally acting with reckless indifference to life and safety."
There is a tremendous amount of pent up anger in the programming community against C/C++. The language should have been put our to pasture a _long_ time ago. Rust is the first language which has a real chance to displace it. So it's no wonder that a lot of us are willing to just burn C/C++ to the ground and jump ship.
Not to mention that as programmers we may be quickly approaching a time when security bugs become a governmental issue, and the _last_ thing we want is the government mandating how we write code. So it is in everyone's interest if we self regulate. Again, that would explain why everyone is kinda jumpy about C and security bugs.
Then the UNIX FOSS, written mostly in C took over, and set us almost 20 years back in security and safety.
But I've basically stopped commenting in Rust threads. There's too much "not even wrong" as regards C and C++ to be bothered with.
Warning is produced by nearly every good compiler. They aren't forced because sometimes I don't really want to check for return values.
> In C/C++ it's easy to build a switch for an enum, and then later when a new member is added to the enum and you forget to update all your switches.
why would you update all other enum values in accordance to addition to a new one? I understand you could possibly be skipping one switch case because you just added it in enum definition but didn't update switch case, but why would you update rest? Is this a forced requirement in Rust? This is bad if it is.
> Support for multiple return values (through tuples) means being able to avoid the nightmare of output pointer arguments to functions.
std::tuple?
> No NULL pointers.
but what if you need them? Lets say a type safe, non-zero nullptr? I understand some people want to bring up whole NULL is bad mistake argument but remember some of us work with maintaining large code where such changes are non-trivial.
> Unit testing built into the canonical tooling. Documentation testing built into the canonical tooling.
This is good argument :)
> The `Option` type, which lets you return "nothing". In C/C++ you'd have to return a bool, and again use a nightmarish output pointer argument.
std::optional? Or many other implementations of optional type? I've been using them since ages(before rust was even born) and writing them is no NP problem. Don't credit rust as if it brought the `Option` type.
> `OsRng` in the rand crate (go ahead, try to write a function that safely reads urandom in C).
Is this intended to be sarcasm? try <random> header and see if it fulfills your requirement, otherwise there are tons of libraries. But wait, what? This is another already solved problem unless I'm missing something.
> No undefined behavior.
Lol do you even unsafe bro?
> C++ would require a whole set of classes, and require instantiating a new class every time I want to add a new cryptographic type. Rust only requires 3 lines per type.
Depends on your cpp skills and judging by this post, they're certainly less than expected from a beginner. I hope you learn about certain languages (even the ones you are defending) before writing things on internet, since 90% of what you wrote won't even come near acceptable so I suggest doing your research properly.
Regarding safely reading urandom, you don't seem to be aware that concurrency issues occur when accessing it from multiple processes. This is something that the borrow system can aid with by formalizing access to a resource like urandom and not allowing contention for it to occur at runtime.
Also, nobody appreciates aggro kid language.
Sure one boundary error could have been detected in any language, but there will always be fewer in a language that simply disallows them. Consider how infrequently Gotos are used now there are no longer errors with setting state before a jmp/goto, because modern constrol flow tools function, case, inheritance, generics and others tools have strong semantics with good implementations that work well and reduce complexity.
Unfortunately language adoption is not a pure meritocratic affair. Bad languages can stick around for far too long because of external factors. Consider that Ada never had real use outside the DoD when it was shiny and new. That group isn't exactly known for evangelizing. The Rust community has better marketing strategy and has chosen a highly meritocratic approach to language design.
Yes, it would. In Rust, the server would have crashed when it overran the buffer instead of sending that potentially-secret memory back to the client. Heartbleed is a great example of why rewriting in Rust is good.
Now the reason why some people claim that Rust would not have stopped it, is that a similar leaky buffer system is possible to be written in Rust. This however is just applicable if you just use byte buffers for everything. At that point you become close to emulation. Also if the system malloc is shit, you just use jemalloc which is the standard for Rust. So there is no need to roll your own malloc replacement in Rust
I happen to like rust, though I would not suggest that it is the only solution to the classes of problems it avoids. I would, however, suggest that people very strongly consider using languages that avoid the problems that rust avoids whenever possible. Like, literally any time you're considering writing C or C++, at least strongly consider rust or another much safer option.
Really?
Validation and sanitization are the last, not the first line of defense. It is not about erecting barriers, it is about failure modes. All your software components, from core to border, should fail fast, safe and in a predictable way -- the exact opposite of what happens in C.
In any sane programming language (e.g. Java -- not particularly sane, but boring enough) Heartbleed would have resulted, at worst, in OutOfBoundsException or similar, and the only damage would have been the denial of service. And to do bounds checking, you don't need to have garbage collector -- it is the most basic thing the runtime can do, and performance penalty is minimal. Why it is customary to skip this in C/C++, I don't know.
Lack of bounds checking, not pointer arithmetic or allocation lifecycle, is the major source of security issues with existing software.
No, this is not true. Use after free is more pernicious nowadays.
If a language, for example, don't have NULLs and you need to use Sum Types (like ML or Swift) you have solved FOREVER and FOR ALL THE DEVELOPERS that problem.
But say that just because this is one problem among dozens, then is better FOREVER AND ALL THE DEVELOPERS to stay with the problem?
Instead, why not put as our goals to eliminate all the problems that are possible?
The problem was that many companies had certain problems paying for Ada compilers and rather used the free C compiler delivered with their OS SDK.
Heartbleed implemented their own memory management system (and had a bug in their use of it). This is pretty unusual even in C. It's extremely unusual in Rust, but who's to say that a similar implementation in Rust would come across the same challenges and implement their own memory management too? And write the same bug? Ultimately this kind of system would be unsafe code anyway, so Rust won't help you there. The question is if a Rust programmer would design such a thing (it's very non-Rust-y). The answer is probably "no". However, the same can be said about C programmers and the heartbleed code, so you have a bit of a no-true-scotsman vibe happening here.
So, yes, heartbleed could have happened in Rust too. I do feel it's less likely (Rust more strongly forces you to compartmentalize unsafe abstractions), but all of that is really debatable.
You didn't read the article. He acknowledged that the hyperbolic calls to "IMMEDIATELY REWRITE EVERYTHING IN RUST!!1!!1one" are silly. In bold, the author states:
> What I’m advocating for is much simpler: surgically replace weaker parts but keep most of the project intact.
Which, by the way, is exactly how Mozilla is applying Rust-written rewrites to Firefox (dogfooding their own programming language).
Your argument does apply to Ada, but freeing from the heap in Ada is unsafe, which has made it difficult to use in a safe way for a lot of cases.
Rust really does make an advance in this regard: safety, and no significant runtime requirements.
D and modern C++ might work for disciplined teams, but seem a few steps behind rust safety-wise.
I think you'll want to check your facts again:
https://tonyarcieri.com/would-rust-have-prevented-heartbleed...
Not all security vulnerabilities are due to pointer arithmetic or out of bounds execution or pick-your-rust-is-better-idiom.
Heartbleed is a great example. It wasn't caused by an error with how C handles memory or strings or anything else. It was caused by failing to validate untrusted input. Rust isn't going to help you with that.
Could the affected parts be re-written in a way where the boundary checks would have worked? Yes. As they could have in C, C++, D, Fortran, or any number of other languages.
If memory safety and correctness was so truly valued above everything else, we'd be seeing a lot more Ada than we do today.
And this doesn't even cover the fact that not everything can be re-written in Rust, mainly due to compilation target restrictions.
Exasperated Edit: No, I'm not saying code should never be re-written. I'm not even saying that Rust is a bad tool if the decision to re-write is made. What I am saying is that Rust is not a silver bullet. Rust is not going to solve every problem ever. Rust is one of many choices for writing memory safe, C ABI compatible code.
The reasons provided by this article are insufficient to solely justify a re-write in Rust; the exact same arguments could be used as justification for rewriting everything in Ada. Or Haskell. Perhaps some thought will be put in the next "re-write everything in Rust" article that takes that into account.
Then again, none of the past articles have.