It is time to standardize principles and practices for software memory safety
cacm.acm.org
cacm.acm.org
- out-of-bounds on array read/write
- stack corruption such as overwriting the return address
It doesn't directly say "you can't use C", but achieving this level of soundness in C is quite hard (see sel4 and its Coq proof).
Also these properties are a very small subset of general correctness. Who cares if you write a "safe" program if it computes the wrong answer?
Err .. that is actually the point of the proof. Can you give an example of something with a Coq-type safety proof that has a memory safety bug in it?
But there's another aspect that I think you missed: use after free.
As you say, achieving this level of soundness with C is hard. Proving it is much harder. (Except, how do you know you've achieved it if you don't prove it?)
Just sharing an anecdote: recently, I had to create Linux images for x86 on ARM machine using QEMU. During this process, I discovered that, for example, creation of initrd fails because of memory page size (some code makes assumption about page size and calculates the memory location to access instead of using system interface to discover that location). There's a similar problem when using "locate" utility. Probably a bunch more programs that have been successfully used millions, well, probably trillions times. This manifests itself in QEMU segfaulting when trying to perform these operations.
But, to answer the question: I think, one way to define memory safety is to ensure that the language doesn't have the ability to do I/O to a memory address not obtained through system interface. Not sure if this is too much to ask. Feels like for application development purposes this should be OK, and for system development this obviously will not work (someone has to create the system interface that supplies valid memory addresses).
Why am I more worried than excited about a new standard?
By the way bounds checking was introduced in Turbo Pascal in 1987. Iirc people ended up disabling it in release builds but it was always on in debug.
But ... it's Pascal, right? Toy language.
Not really.
It's just out of fashion. But there are really high quality current day implementation, like the one from Embarcadero (i think they acquired Borland a while ago?): https://www.embarcadero.com/products/delphi/features/design
Here is my favourite quote, every time we discuss bounds checking.
"A consequence of this principle is that every occurrence of every subscript of every subscripted variable was on every occasion checked at run time against both the upper and the lower declared bounds of the array. 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."
-- C.A.R Hoare's "The 1980 ACM Turing Award Lecture"
Guess what programming language he is referring to by "1980 language designers and users have not learned this lesson".
Having a well-defined memory model is important when running on infra with very different models, such as x86 and aarch64.
Oh, wait, it exists https://docs.oracle.com/javase/8/docs/api/java/util/Optional...
I haven't tested this code, and definitely don't do this.
This code is intended to lets attackers run any shell command by sending JSON with a "debug_command" field - similar to Log4J, it's a "feature" being misused rather than a memory bug that Rust would catch.
```rust
use serde_json::Value;
use std::process::Command;
fn process_log_entry(log: &str) {
// UNSAFE: Allows command injection
if let Ok(json) = serde_json::from_str::<Value>(log) {
if let Some(cmd) = json.get("debug_command") {
Command::new("sh")
.arg("-c")
.arg(cmd.as_str().unwrap_or(""))
.output()
.unwrap();
}
}
}
```To keep it concise, I had take some liberties.
If you have more time than me, please feel free to reproduce Log4J more accurately in Rust.
In a number of applications this means you do not actually want a general purpose language which is why WUFFS makes sense.
But, even when you don't have that constraint it's reasonable to ask: How easy was it to make a thing you didn't intend, by accident ?
That's a sound deduction. The JVM had such a vulnerability because it's full of ambient authority and rights amplification patterns. More such vulnerabilities probably exist, they're just hard to see.
Why remove the refrences in the TR to frama-C, cbmc, etc. from the opinion report? They are easier to adopt than the heavier tooling of coq, etc. I'm always suprised to see those tools ignored or downplayed, when it comes to these discussions. I do agree with the TR's sentiment that we need to improve accessibility and understanding of these tools, but this is not a great showing for that sentiment.
Additionally, both articles miss that compiler modified languages/builds are a path, such as fbounds-safety. They will be part of the solution, and frankly, likely the biggest part at the rate we are going. Eg. current stack defenses for C/C++/Rust, unaddressed in safe language design, are compiler based. The compiler extension path is not particularly different than cheri, which requires a recompile with some modifications, and the goal of both approaches is to allow maintainers to band aid dependencies with minimal effort.
The TR handwaves away the question of the complexity of the development of formal method tools for Rust/Unsafe Rust and C++. Ie. rust really only has two tools at the moment: miri and kani (which is a cbmc wrapper). Heavier tools are in various states of atrophying/development. And C++ support from the c family of formal tools such as frama-C, is mostly experimental. It's not assured, that with the continued language development rate of both languages and the complexity of the analysis, that the tools for these languages will come forth to cover this gap anytime soon.
I do not think the claim in the TR that the current unsafe/safe seperation will result in only requiring formal analysis of the unsafe sections is true, as logical errors, which are normal in safe rust, can cross the boundries to unsafe and cause errors, thus nessecitating whole program analysis to resolve if an unsafe section could result in errors. Perhaps it will decrease the constants, but not the complexity. If rust does further restricts perhaps more of the space could be covered to help create that senario, but the costs might be high in both usability and so on.
There are, of course, other good reasons to choose c and c++ over rust. And of course rust has its own warts. just pointing out that performance and memory safety are not necessarily mutually exclusive
My experience is that Rust guides you towards defaults that tend to not hit those things and for the cases where you really do need that fine grained control unsafe blocks with direct pointer access are available(and I've used them when needed).
It also depends on the team, some teams have a high density of seasoned experts who've made the mistakes and know what to avoid but I think the history on mem vulns show that it's very hard to keep that bar consistently across large codebases or disperse teams.
Grandparent can’t argue that these top tier engineers aren’t RTFM here. Of course they are. Even after the manual reading they still cannot manage to write perfectly safe code. Because it is extremely hard to do
???????
Yes you need to read the docs. That is programming 101. If you have vim set up properly then you can open the man page for the identifier under your cursor in a single keypress. There is ZERO excuse not to read the manual. There is no excuse not to check error messages. etc.
Yet we consistently see people that want everything babyproofed.
On the other hand, there's no excuse for designers & developers (or their product manager, if that's the one in authority) not to work their ass off on the ergonomics/affordance of the tools they release to any public (be it end users or developers, which are the end users of the tool makers, etc.).
It benefits literally everyone: the users, the product reputation & value, the builders reputation, the support team, etc.
And yet...
Do people read the docs? Often, no, they don't. So, are you creating tools for the people we have, or for the people you think we should have? If the latter, you are likely to find that your tool makes less impact than you think it should.
Computer languages are not tools for illiterates. You need to learn what you're doing. And yet, programmers do so less than we think they should. If we don't license programmers (to weed out the under-trained), then we're going to have to deal with languages being used by people who didn't read the docs. We should give at least some thought to having them degrade gracefully in that situation.
You need memory management to be painful like in C so that it forces people to go for better options like linear/static group allocations.
2. Even when they exist, Rust lets you use unsafe code but only where needed. It's still much better than having your entire program be unsafe.
3. In practice Rust versions of programs are as fast, if not faster than C/C++ ones.
However, the two sort algorithms in Rust are safe to use, as well as being faster than their equivalents in C++. In fact even the previous sorts, the ones which were replaced by the current implementations, were both faster and safer than what you're getting in C++
https://youtu.be/rZ7QQWKP8Rk?t=2054
Watch until the next slide - it shows a comparison of a port of a very fast C++ sorting algorithm to Rust. Rust is faster due to algorithmic changes. Ignoring those they are very similar speeds; certainly not an issue.
[0] https://github.com/Voultapher/sort-research-rs/blob/main/wri...
> My take on it is that there are no 'language comparisons' there.
Watch the video a few minutes forwards from when I linked, there are "language comparison" slides. Basically C++ and Rust are on par.
You're correct that if you implemented these algorithms carefully in C++ you can expect very similar results. I don't believe that anybody has done that and certainly there is no sign the three major implementations would attempt to switch to these algorithms for their standard library sorts.
In Rust today the standard library sorts provided are further refinements of the "ipnsort" and "glidesort" algorithms described in the paper you linked. As the papers arguing to use these algorithms point out, the downside is that although they've been tested extensively with available tools we can't actually prove they're even safe, the upside is of course performance.
One of the reason is you get the whole program optimization automatically in Rust while C/C++ you need to use put the function that need to be inline in the header or enable LTO at the link time. Bound checking in Rust that people keep using as an example for performance problem is not actually a problem. For example, if you need to access the same index multiple times Rust will perform bound-checking only on the first access (e.g. https://play.rust-lang.org/?version=stable&mode=release&edit...).
Borrow checker is your friend, not an enemy once you know how work with it.
People seriously underestimate how capable and expressive modern C++ metaprogramming facilities are. Most don’t bother to learn it but it is one of the most powerful features of the language when it comes to both performance and safety. The absence of it is very noticeable when I use other systems languages. I’m not a huge fan of C++ but that is a killer feature.
Not a rhetorical question, I'd love to see a book or notes that carves out precisely that subset so those of us who want to learn can avoid the tons upon tons of outdated or misleading documentation!
This is moving the goalposts. "Safe rust" isn't a distinct language. The unsafe escape hatch is there to make sure that all programs can be implemented safely.
This is at best a misunderstanding of the way rust works. Unsafe is a tool for producing safe abstractions.
I think we disagree on what "giving up safety" means, or perhaps you thought I meant "you are giving up all safety." (And honestly, I'm just trying to clarify what I meant when I read/wrote it. I'm not going for a No True Scotsman, or trying to move the goalposts here.)
Manually convincing yourself (proving) that an implementation is correct is how you write correct code in any language. In this sense you never "give up safety" in any language, but that's clearly not the sense that is being discussed in this thread. In this thread "giving up safety" appears to me to mean giving up automated safety guarantees provided by the language and compiler.
I acknowledge that it is possible to write just the bare minimum in unsafe rust to realise an abstraction, and that these "unsafe rust" fragments may be provably safe thus rendering an entire abstraction safe. This may be best practice, or "the way rust works" as you say. None the less the unsafe fragments are not proved safe by construction/use of safe rust and/or automatically safe by virtue of the type system/borrow checker.
My point was that if you use unsafe rust you have reduced the number of automated safety guarantees. It is on the developer to prove safety of the unsafe rust, and of the abstraction as a whole. Needless to say, human proof is a fallible process. You may convince yourself that you have not given up safety, but I argue that you have merely contained and reduced risk. You have still "given up safety."
The performance tradeoff is not intrinsic. Rust’s weakness is that it struggles to express safety models sometimes used in high performance code that are outside its native safety model. C++ DGAF, for better and worse.
The hardcoded safety model combined with a somewhat broken async situation has led me to the conclusion that Rust is not a realistic C++ replacement for the kinds of code where C++ excels. I am hopeful something else will come along but there isn’t much on the horizon other than Zig, which I like in many regards but may turn out to be a bit too spartan (amazing C replacement though).
Isn't Rusts's async situation "somewhat broken" in the exact same way the C++'s async situation is?
Moreover, if I remember correctly, they all are made possible by a single (long-standing) compiler bug that eventually will be fixed.
Previously discussed: https://news.ycombinator.com/item?id=39440808
I think this mindset is the big difference. We're not perfect, but we're working on it.
These bugs are not really a problem in practice though as long as the developer is not malicious. However, they are a problem for supply chain security or any case where the Rust source is fully untrusted.
[1]: https://github.com/rust-lang/rust/issues/25860
Users should have control and trust in their devices. If they can be remotely compromised, they cannot get that.
Meanwhile the rest of the population gets pushed towards authoritarian dystopia.
Users should have control and trust in their devices.
I think that already went away once they started adding spyware ("telemetry" being the usual euphemism) and forced automatic updates ("remotely compromised", as you put it.)
Meanwhile, everything else passes under the guise of "safety and security".
We're living the inbetween and people are beating the drums that drive us towards either endpoint. Not for no reason either. Turbulent times.
No need for rust, Ada, CHERI, SPARK, etc.
"As a large languge model, I can't answer your patch merge request you absolute fucking moron"