This also does not mean rust might not have its fair share of bugs; IMHO the probability is a little less.
Edit: I mentioned the log4j RCE as it was new & do not mean to imply that it would not have happened if we had a rust implementation. As the comments have mentioned, it would be the same in any language IMO
Not really. You'd have to fight the language and pull in some crates, to be sure. There's no native object serialization in rust and it's not really a concept in the language or ecosystem - except perhaps in the future with wasm runtimes.
There are numerous details of log4j that, in general, just don't apply to rust code.
edit: Since perhaps people don't believe me...
Rust has no equivalent of JNDI, pickle, etc. There is no way to send rust 'code' except, perhaps, wasm. Certainly there's no well supported way with decades of history of being ingrained into libraries, or patterns that support it.
Uh, yeah, the RCE was very bad. And not a thing with Rust due to not having object serialization.
> you can still slurp any environment variables (or other process information) and send it to any host
Yeah, you can write a logger in rust that does really crazy things like adding environment variables through some sort of parsing of a language construct. That would still be a problem, but a lot less serious. It would probably also be really unidiomatic, but that's a separate story.
The root of the log4j problem is a failure to distinguish between log format strings and the formatted log string, beyond that what happens is that Java allows a lot of flexibility at runtime which simply doesn't exist in Rust. In Java it's fine to, at runtime pull in classes you've never used before and run them, you can't do that in Rust - the types only existed at compile time. Indeed this is a key feature of Rust, because it means wrapper types are free at runtime, invaluable to the embedded firmware community who love type checking and other safety features but can't afford runtime overhead.
Of course you can. Rust would be a severely limited systems language if it couldn't support dynamic loading. E.g. see
- https://michael-f-bryan.github.io/rust-ffi-guide/dynamic_loa...
The Log4j "lookup" behaviour was, first of all, a really dumb design decision even in Java but it's just wrong to pretend that it would be equally easy for it to be so catastrophic in most languages, not just Rust, even in C++ I think you'd probably realise you'd screwed up while developing a way for the log framework to run arbitrary code.
You're much less likely to see a deserialization bug in Rust or Go than in Java.
There is nothing intrinsic to Rust or Go that prevents deserialization bugs; they're just not encouraged by idiom like in Java.
Many Java projects, but not all. Class-loading permissions in a Java program are subject to the security-manager policy for the program; class loading can be moderated or even fully disabled (once the main program is loaded, of course).
The default is to allow anything to be loaded, however.
But it does since you eliminate an entire class of bugs, and a class that's generally the source of most exploits out there (eg. heartbleed comes to mind as a similar level of exploit but was a result of memory errors).
JVM does the same with GC, and so do a lot of other languages, but when it comes to rewriting non-managed code Rust is a great choice over C in my book, I would feel better about introducing a Rust dependency over a C/C++ dependency just based on that.
As for ADA and Nim, if it's some black box docker service then sure, otherwise if I have to include it in my build pipeline that's a hard pass.
As opposed to what language? C? x86 assembly? You may be right, but I was using Ada and Nim as an example, not C. I could have used D as well. Again, the fact that it is written in Rust and not Ada, Nim, or D does not mean that it has less bugs.
In fact, I would even argue that even if we do a C vs. Rust, it still does not follow that just because it is written in Rust and not C, it has less bugs. There are all sorts of other bugs that could outnumber the memory-related bugs you may have in C. Can you really not see this possibility?
Yes, C, the language memcached is written in. The fact that it's written in Rust means that it's more likely to work in the first place because more people are actually using that language in the real world vs. Ada, Nim and D.
>In fact, I would even argue that even if we do a C vs. Rust, it still does not follow that just because it is written in Rust and not C, it has less bugs. There are all sorts of other bugs that could outnumber the memory-related bugs you may have in C. Can you really not see this possibility?
I don't feel like just random googling for the sake of argument - it should be easy to find, there have been many analysis of this kind and pretty much any I've seen concludes the largest source of vulnerabilities have been memory errors (I remember FF, Linux, Microsoft, etc.). So yeah if you want to be pedantic for the sake of argument feel free to keep arguing about the semantics - real world experience motivated initial Rust development and it's gaining adoption in places like cloud provider infrastructure and drivers because people dealing with these issues know what their most common bugs are coming from.
Rust is able to learn from the many mistakes that other languages before it have made. But pretending that this means Rust is inherently purer instead of just "has less baggage, but will soon implement its own", is naive.
Exactly, Rust simply eliminates a certain class of bugs. You can still make some really major mistakes in the absence of these bugs, e.g. "goto fail" could have easily been written in Rust.
Here's an explanation of "goto fail", which includes a code snippet.
https://nakedsecurity.sophos.com/2014/02/24/anatomy-of-a-got...
If C conditionals required explicit braces around the body, the second fail would have been redundant code instead of a security vulnerability.
Additonally, Rust will warn you if you have "unreachable" code, such as all the code after the unconditional "goto fail". Though, I'm sure modern C compilers would also warn about this.
All that said, Rust definitely does have plenty of bug classes left for people to trip over. Integer under/overflows come to mind (though they're just logic errors instead of undefined behavior).
Rust also hasn't boarded the "People can't remember precedence rules" train like some modern languages. It has fewer operators with unnecessary values, reducing some opportunities for mistakes like C's increment footgun, but it does still have precedence and so you can still forget the precedence rules which the compiler is going to apply to your code.
But there are a lot of things Rust does catch, and there are even more things that aren't caught by Rust the language per se but are caught in idiomatic Rust because the language affords better ways to do things. Locking is an example, idiomatic Rust locking means you can't make the lock A but change B mistake seen so often in real bugs, because idiomatic use of Rust's type system would put the variable B inside the lock for B, if you didn't take the lock you don't have the variable to change it. https://doc.rust-lang.org/std/sync/struct.Mutex.html
Not only Rust, this applies to all safe languages, just crashing also opens the door to exploits and denial attacks.
And it probably applies to all general purpose languages but of course it doesn't apply to all the safe languages that aren't general purpose. You can't have bounds misses in WUFFS. When you try to touch array[x] in WUFFS, the compiler constrains the type of x to have a boundary limit according to the size of the array, reasoning that you can't very well have meant for it to be possible to overstep the bounds of the array at runtime. Trivial arithmetic mistakes you might find in unit testing or worse production with other languages are often compile time errors in WUFFS as a result.
And it's still a pretty sad indictment when a compiler is looking at your access, can see it's outside the boundaries and doesn't reject the program because the language (e.g. C) wasn't defined with this obvious expectation.
For me it's in the same category as the (now fixed in an errata) behaviour where as I understand it C++ 20 std::format didn't consider it a compile time error if you wrote
format("check X: {} Y: {} and Z: {}", x, z)
... even though that's obviously an error, and it's obvious you can detect it at compile time, the original specification said nah, this code just blows up at runtime, good luck with that.For example I would consider it acceptable if feeding badly corrupted MP3s to the MP3-capable CD-radio makes it stop working... until I disconnect the power and restart it. Nobody dies, if it's very annoying I should remove the offending MP3 file. Whereas if it causes random mayhem that's a problem because now troubleshooting is impractical and most likely the apparent solution is to replace the entire device.
And remember that such "turn it off and on again" is considered acceptable even in some systems you probably deem safety critical, so long as it needn't happen when actively dangerous. For some years trains in the UK with a beacon-based safety system couldn't handle waking up next to a beacon, their firmware wanted to start by not seeing a beacon, then encounter beacons while travelling - so waking up next to one was confusing and they'd go into an error state. The fix? Mostly "don't park the train there when it's to be switched off", but when it happened inadvertently, they would move the train a few metres with its safety systems disabled, turn the power off, wait a few seconds, turn the power back on and no longer able to see a beacon it was now happy.
Exactly that is what we are missing in the software industry, the niche cases of high integrity computing should be a common practice, just like most people usually don't put up with broken stuff when given the option.
We need more liability not less.
User experience matters, and actually just dropping dead in error cases makes for a better user model than many outcomes. The user has a model of what the machine does, which is always over-simplified and often plain wrong, but it's vital to try to get to a place where the user's model doesn't contradict how it's actually to be used. Undefined Behaviour is the enemy of this outcome. The user definitely does not have a correct model of what Undefined Behaviour will be, as we see many programmers don't either, whereas "If I do this particular dumb thing, it stops working and I need to unplug it and start over" fits in the user model relatively well.
Computers aren't special in this regard, watch "Sully" (Miracle on the Hudson), the cockpit dialog is roughly correct. What does their QRH tell them to do in the event of dual engine failure? Turn the engines off first and then try turning them back on again. Didn't work because their engines were trashed, but if even one engine had restarted, those QRH instructions mean there would be no film, just a scary incident some passengers tell stories about and an aviation safety file somewhere, the plane climbs (slowly) on one engine, they're still an emergency but they can put it back down on a runway at their leisure.
Remember heartbleed?...
No? There's no law of conservation of bug class or something, you can eliminate bug classes without introducing a new one.
> This mentally of tying bugs to a language demonstrates a very poor understanding of the issues themselves,
No it doesn't.
> and if anything a large impacting bug demonstrates wide adoption of an individual piece, most probably choosen due to its stability or ease of use.
I don't think this has held true historically, and log4j is a good example of this (though there are many others).
> Remember heartbleed?...
A weird point to bring up since heartbleed was an out of bounds memory read.