I'm beginning to worry that every time Rust is mentioned as a solution for every memory-unsafe operation we're moving towards an irrational exuberance about how much value that safety really has over time. Maybe let's not jump too enthusiastically onto that bandwagon.
What's with the hyping of Rust as the Holy Grail as the solution to everything not including P=NP and The Halting Problem?
Most security bugs/holes have been related to buffer [over|under]flows. Statistically speaking, it makes sense to use a language that eliminates those bugs by the mere virtue of the program compiling. Do you disagree with that?
It can however make it extremely difficult to exploit and it can make such use cases very esoteric (and easier to implement correctly).
I also said "way, way less" not "not at all". I still think about memory safety in our Rust programs, I just don't allocate time to address it (today) specifically.
For example, in garbage collected languages the programmer does not need to think about memory management all the time, and therefore they can think more about security issues. Rust's typesystem, on the other hand, can really get in the way and make code more opaque and more difficult to understand. And this can be problematic even if Rust solves every security bug in the class of (for instance) buffer overflows.
If you want secure, better use a suitable GC'ed language. If you want fast and reasonably secure, then you could use Rust.
"Don't use Rust because it is GC'd" is a take that I think basically nobody working on memory safety (either as a platform concern or as a general software engineering concern) would agree with.
I don't disagree with the premise of your post, which is that time spent on X takes away from time spent on security. I'll just say that I have not had the experience, as a professional rust engineer for a few years now, that Rust slows me down at all compared to GC'd languages. Not even a little.
In fact, I regret not choosing Rust for more of our product, because the productivity benefits are massive. Our rust code is radically more stable, better instrumented, better tested, easier to work with, etc.
https://dropbox.tech/infrastructure/extending-magic-pocket-i...
There are likely many other examples of, say, Java not having memory safety issues. Java makes very similar guarantees to Rust, so we can extrapolate, using common sense, that the findings roughly translate.
Common sense is a really powerful tool for these sorts of conversations. "Proof" and "evidence" are complex things, and yet the world goes on with assumptions that turn out to hold quite well.
But these tricks have the same root: What if we used all this research academics have been writing about for decades, improvements to the State of the Art, ideas which exist in toy languages nobody uses -- but we actually industrialise them so we can use the resulting language for Firefox and Linux not just get a paper into a prestigious journal or conference?
If ten years from now everybody is writing their low-level code in a memory safe new C++ epoch, or in Zig, that wouldn't astonish me at all. Rust is nice, I like Rust, lots of people like Rust, but there are other people who noticed this was a good idea and are doing it. The idea is much better than Rust is. If you can't do Rust but you can do this idea, you should.
If ten years from now people are writing unsafe C and C++ like it's still somehow OK, that would be crazy.
Imagine it's 1995, you have just seen an Internet streaming radio station demonstrated, using RealAudio.
Is RealAudio the future? In 25 years will everybody be using RealAudio? No, it turns out they will not. But, is this all just stupid hype for nothing? Er no. In 25 years everybody will understand what an "Internet streaming radio station" would be, they just aren't using RealAudio, the actual technology they use might be MPEG audio layer III aka MP3 (which exists in 1995 but is little known) or it might be something else, they do not care.
1. Rust also has other safety features that may be relevant to your interests. It is Data Race Free. If your existing safe-but-slow language offers concurrency (and it might not) it almost certainly just tells you that all bets are off if you have a Data Race, which means complicated concurrent programs exhibit mysterious hard-to-debug issues -- and that puts you off choosing concurrency unless it's a need-to-have for a project. But with Data Race Freedom this doesn't happen. Your concurrent Rust programs just have normal bugs that don't hurt your brain when you think about them, so you feel free to pick "concurrency" as a feature any time it helps.
2. The big surface area of iMessage is partly driven by Parsing Untrusted File Formats. You could decide to rewrite everything in Rust, or, more plausibly, Swift. But this is the exact problem WUFFS is intended to solve.
WUFFS is narrowly targeted at explaining safely how to parse Untrusted File Formats. It makes Rust look positively care free. You say this byte from the format is an 8-bit unsigned integer? OK. And you want to add it to this other byte that's an 8-bit unsigned integer? You need to sit down and patiently explain to WUFFS whether you understand the result should be a 16-bit unsigned integer, or whether you mean for this to wrap around modulo 256, or if you actually are promising that the sum is never greater than 255.
WUFFS isn't in the same "market" as Rust, its "Hello, world." program doesn't even print Hello, World. Because it can't. Why would parsing an Untrusted File Format ever do that? It shouldn't, so WUFFS can't. That's the philosophy iMessage or similar apps need for this problem. NSO up against WUFFS instead of whatever an intern cooked up in C last week to parse the latest "must have" format would be a very different story.
It's 26 years after Java was released. Java has largely been the main competitor to C++. I don't see C++ going away nor do I see C going away. And it's almost always a mistake to lump C and C++ developers together. There is rarely an intersection between the two.
I think you do not understand how short 10 years is. There are tons of people still running computers on Sandy Bridge.
I don’t mean this in a very critical spirit, though.
Communication is really hard - especially in a large setting where not everyone reads you in the same context, and not everyone means well.
On balance, you post was valuable to me!
I'm glad the post was of value to you. The talk is really good and I think more people should read it.
A Java program can't write over the return address on the stack.
On the other hand, you could choose to think about communications in an analogous way to your code, both being subject to attack by bad actors trying to subvert your good intentions.
So, the argument could be made, that removing attack surface from communication is analogous to hardening your code.
I also come from a coding background (albeit a long time ago) and with the help of some well meaning bosses over time eventually came to realize, that my messages could gain more influence, by reducing unnecessary attack surface. - Doesn’t mean I always get it right, even now - but I am aware and generally try hard to do just that.
(I realize that racing threads can cause logic based security issues. I've never seen a traditional memory exploit from on racing goroutines though.)
I have another neat trick to avoid races. Just write single threaded programs. Whenever you think you need another thread, you either don't need it, or you need another program.
Apparently in Golang, you can achieve memory unsafety through data races: https://blog.stalkr.net/2015/04/golang-data-races-to-break-m... (though I'm not sure if a workaround has been added to prevent memory unsafety).
Ten years is about the time since C++ 11. I may be wrong, but I do not regret my estimate.
If you have a race which definitely only touches some simple value like an int and nothing more complicated then Go may be able to promise your problem isn't more widespread - that value is ruined, you can't trust that it makes any sense (now, in the future, or previously), but everything else remains on the up-and-up. However, the moment something complicated is touched by a race, you lose, your program has no defined meaning whatsoever.
I have to admire the practicality of the approach they've been taking.
Basically they have a lot of runtime checks enabled in debug mode, where you do the majority of your testing, that are then disabled in the release binary.
Additionally the approach they've taken to allocators means that you can use special allocators for testing that can perform even more checks, including leak detection.
I think it's a great idea and a really interesting approach but it's definitely not as rigorous as what Rust provides.
If your program loses track of which file handles are open, which database transactions are committed, which network sockets are connected, GC does not help you at all for those resources, when you are low on heap the system automatically looks for some garbage to get rid of, but when you are low on network sockets, the best it could try is hope that cleaning up garbage disconnects some of them for you.
Rust's lifetime tracking doesn't care why we are tracking the lifetime of each object. Maybe it just uses heap memory, but maybe it's a database transaction or a network socket. Either way though, at lifetime expiry it gets dropped, and that's where the resource gets cleaned up.
There are objects where that isn't good enough, but the vast majority of cases, and far more than under a GC, are solved by Rust's Drop trait.
Could you say why Java is not susceptible to ROP?
Put another way: Anything you could do in the malloc/free model that Zig uses right now is something you could do in C++, or C for that matter. Maybe there's some super-hardened malloc design yet to be found that achieves memory safety in practice for C++. But we've been looking for decades and haven't found such a thing--except for one family of techniques broadly known as garbage collection (which, IMO, should be on the table for systems programming; Chromium did it as part of the Oilpan project and it works well there).
There is always a temptation to think "mitigations will eliminate bugs this time around"! But, frankly, at this point I feel that pushing mitigations as a viable alternative to memory safety for new code is dangerous (as opposed to pushing mitigations for existing code, which is very valuable work). We've been developing mitigations for 40 years and they have not eliminated the vulnerabilities. There's little reason to think that if we just try harder we will succeed.
[1]: https://chromium.googlesource.com/chromium/src/+/HEAD/base/a...
But there's the problem: Testing can't and won't cover all inputs that a malicious attacker will try [1]. Now you've tested all inputs you can think of with runtime checks enabled, you release your software without runtime checks, and you can be sure that some hacker will find a way to exploit a memory bug in your code.
[1] Except for very thorough fuzzing. Maybe. If you're lucky. But probably not.
https://www.cvedetails.com/vulnerability-list/vendor_id-1902...
What if the bug is in std?
What if I use a bugged Vec::from_iter?
What if I use the bugged zip implementation from std?
You'll probably blame unsafe functions, but those unsafe functions were in std, written by the people who know Rust better than anyone.
Imagine what you and me could do writing unsafe.
Imagine trusting a 3rd party library...
Not disagreeing, just mentioning.
That's true, but this is one of the cases where obtaining the last 5-10% of clarify might require 90% of the total effort.
Now whether one actually already has plucked all the low-hanging fruit in their own communication and if it's already good -- that's a separate discussion.
Rust Lang strives for safety and safety is no 1 priority. Regarding the unsafe in std please read the source code just to know how much careful they are with the implementation. They only use unsafe for performance and even with unsafe rust, it doesn't provide too much freedom tbh.
The 3rd party thing you are referring etc sounds childish. They are not the rust lang fault tbh. If you don't trust them don't use. It is as simple as that.
So I think telling people rust program that doesn't have unsafe will not have memory safety bugs. Exceptions to this statement do occurs but are rare.
It is a different story in languages meant to run untrusted code of course.
While the first part of the sentence is mostly true (although the intention is to make safe Zig memory safe, and unsafe Rust isn't safe either), the second isn't. The goal isn't to use a safe language, but to use a language that best reduces certain problems. The claim that the best way to reduce memory safety problems is by completely eliminating all of them regardless of type and regardless of cost is neither established nor sensical;. Zig completely eliminates overflows, and, in exchange for the cost of eliminating use-after-free, makes detecting and correcting it, and other problems, easier.
But this shouldn't be called “memory safety”.
A Java program, by construction, cannot write to memory regions not allocated on the stack or pointed to by a field of an object constructed with "new". Runtime checks prevent ordinary sorts of problems and a careful memory model prevents fun with concurrency errors. There are interesting attacks against the Java Security Manager - but this is independent of memory safety.
We are on a thread about "a case against security nihilism".
1. Not all vulnerabilities are memory safety vulnerabilities. The idea that adopting memory safe languages will prevent all vulns is not only a strawman, but empirically incorrect since we've had memory safe languages for many decades.
2. It is the case that a tremendously large number of vulns are caused by memory safety errors and that transitioning away from memory-unsafe languages will be a large win for industry safety. 'unsafe' is a limitation of Rust, but compared to the monstrous gaping maw of eldritch horror that is C and C++, it is small potatoes.
3. You are going to struggle to write real programs without ever using third party code.
Billions and billions of dollars. Large organizations like Microsoft and Google have published numbers on the proportion of vulns in their software that are caused by memory errors. As you can imagine, a lot of effort is spent within these institutions to try to mitigate this risk (world class fuzzing, static analysis, and pentesting) yet vulns continue to persist.
Rust is not the solution. Memory-safe languages are. It is just that there aren't many such languages that can compete with C++ when it comes to speed (Rust and Swift are the big ones) so Rust gets mentioned a lot to preempt the "but I gotta go fast" concerns.
The Mozilla case study is not a real world study. It simply looks at the types of bugs that existed and says "I promise these wouldn't have existed if we had used Rust". Would Rust have introduced new bugs? Would there be an additional cost to using Rust? We don't know and probably never will. What we care about is preventing real world damage. Does Rust prevent real world damage? We have no idea.
What I'm saying is that truth is a matter of debate. We believe lots of things based on evidence much less rigorous than a formal proof in many cases - like most modern legal systems, which rely on various types of evidence, and then a jury that must form a consensus.
So saying "there is no evidence" is sort of missing the point. Safe Rust does not have memory safety issues, barring compiler bugs, therefor common sense as well as experience with other languages (Java, C#, etc), would show that that memory safety issues are likely to be far less common. Maybe that isn't the evidence that you're after, but I find that compelling.
To me, the question of "does rust improve upon memory safety relative to C/C++" is obvious to the point that it really doesn't require justification, but that's just me.
I could try to find more evidence, but I'm not sure what would convince you. There's people fuzzing rust code and finding far fewer relevant vulns - but you could find that that's not compelling, or whatever.
A language and a memory access model are no panacea. 10 years is like the day after tomorrow in many industries.
There are runtime checks around class structure that ensure that a field load cannot actually read some unexpected portion of memory.
There are runtime checks that ensure that you cannot read through a field on a deallocated object, even when using weakreference and therefore triggering a GC even while the program has access to that field.
There are runtime checks around array reads that ensure that you cannot access memory outside of the allocated bounds of the array.
I have no idea why "susceptible to something like ROP" is especially relevant here. ROP is not the same as "writing over the return address" ROP is a technique you use to get around non-executable data sections and happens after you abuse some memory safety error to write over the return address (or otherwise control a jump). It means "constructing an exploit via repeated jumps to already existing code rather than jumping into code written by the attacker".
But just for the record, Java does have security monitoring of the call stack that can ensure that you cannot return to a function that isn't on the call stack so even if you could change the return target the runtime can still detect this.
The brilliant thing about RAAI style resource management is that library authors can define what happens at the end of an object's lifetime and the Rust compiler enforces the use of lifetimes.
I mean to be clear, modern C++ can be effectively as safe as rust is. It requires some discipline and code review, but I can construct a tool-chain and libraries that will tell me about memory violations just as well as rust will. Better even in some ways.
I think people don't realize just how much modern C++ has changed.
Concurrent Pascal or Singularity also fit the bill, with actual operating systems being written in it.
As for tooling, things like valgrind provide an excellent mechanism for ensuring that the program was memory safe, even in it's "unsafe" areas or when calling into external libraries (something that rust can't provide without similar tools anyway).
My broader point is that safety is more than just a compiler saying "ok you did it", though that certainly helps. I would trust well written safety focused C++ over Rust. On the other hand, I would trust randomly written Rust over C++. Rust is good for raising the lower end of the bar, but not really the top of it unless paired with a culture and ecosystem of safety focus around the language.
There isn't even specific language support necessary, it's on the library level.
Since we're in a thread about security this is a crucial difference. I'm sure Amy, Bob, Charlie and Deb were able to use the new version of Puppy Simulator successfully for hours without any sign of unsafety in the sanitizer. Good to go. Unfortunately, the program was unsafe and Evil Emma had no problem finding a way to attack it. Amy, Bob, Charlie and Deb had no reason to try naming a Puppy in Puppy Simulator with 256 NUL characters, so, they didn't, but Emma did and now she's got Administrator rights on your server. Oops.
In contrast safe Rust is actually safe. Not just "It was safe in my tests" but it's just safe.
Even though it might seem like this doesn't buy you anything when of course fundamental stuff must use unsafe somewhere, the safe/unsafe boundary does end up buying you something by clearly delineating responsibility.
For example, sometimes in the Rust community you will see developers saying they had to use unsafe because, alas, the stupid compiler won't optimise the safe version of their code properly. For example it has a stupid bounds check they don't need so they used "unsafe" to avoid that. But surprisingly often, another programmer looks at their "clever" use of unsafe, and actually they did need that bounds check they got rid of, their code is unsafe for some parameters.
For example just like the C++ standard vector, Rust's Vec is a wasteful solution for a dozen integers or whatever, it does a heap allocation, it has all this logic for growing and shrinking - I don't need that for a dozen integers. There are at least two Rust "small vector" replacements. One of them makes liberal use of "unsafe" arguing that it is needed to go a little faster. The other is entirely safe. Guess which one has had numerous safety bugs.... right.
Over in the C++ world, if you do this sort of thing, the developer comes back saying duh, of course my function will cause mayhem if you give it unreasonable parameters, that was your fault - and maybe they update the documentation or maybe they don't bother. But in Rust we've got this nice clean line in the sand, that function is unsafe, if you can't do better label it "unsafe" so that it can't be called from safe code.
This discipline doesn't exist in C++. The spirit is willing but the flesh (well, the language syntax in this case) is too weak. Everything is always potentially unsafe and you are never more than one mistake from disaster.
And this is where the argument breaks down for me. The C++ vector class can be just as safe if people are disciplined. And as you even described, people in rust can write "unsafe" and do whatever they want anyway to introduce bugs.
The language doesn't really seem to matter at the end of the day from what you are telling me (and that's my main argument).
With the right template libraries (including many parts of the modern C++ STL) you can get the same warnings you can from Rust. One just makes you chant "unsafe" to get around it. But a code review should tell off any developer doing something unsafe in either language. C++ with only "safe" templates is just as "actually safe" as rust is (except for with a better recovery solution than panics!).