> I am even more frustrated with the moral crusaders from languages like Rust, one of whom went as far as to suggest that I should personally be criminally prosecuted if some downstream Hare software has a use-after-free bug.
That is likely just trolling, but is also specially obnoxious considering that Rust does prevent use-after-free bugs as much as Java does (i.e. a lot, but definitely not all).
That comment is definitely out of line and trolling, but the author's attitude towards safety and security is still incredibly bad. Two wrongs don't make a right. I'm dismayed to see more new languages copying the safety and security features of C (i.e. nothing).
Hare has significantly more safety and security features than C. Bounds-checked slices, no uninitialized data, mandatory error handling, nullable pointer types, and others still. What it lacks that Rust users object to is a borrow checker.
You don't need a borrow checker -- there are many ways to avoid use-after-free bugs. They don't in Java, or Haskell, or Python, to name 3 languages I work in sometimes.
However, I really do think for a new "systems language" nowadays, you do want to look at how major security holes occur in practice, and have a good story on how users should avoid them.
These are all GC'd languages that run bytecode on an abstract virtual machine. They avoid use-after-free by just not-freeing, if necessary at the cost of leaking unbounded amounts of memory.
This doesn't invalidate the point that borrowing isn't the only way to solve this problem, but there are definitely classes of these bugs for which Rust's borrow checker is the only known production-ready solution that still has manual, deterministic memory management.
We do take security pretty seriously with Hare. To quote our crypto module's introduction as an example:
> Cryptography is a difficult, high-risk domain of programming. The life and well-being of your users may depend on your ability to implement cryptographic applications with due care. Please carefully read all of the documentation, double-check your work, and seek second opinions and independent review of your code. Our documentation and API design aims to prevent easy mistakes from being made, but it is no substitute for a good background in applied cryptography.
We have many safety features built into the language and the standard library is designed to be difficult to use incorrectly. I will address these concerns directly in a subsequent blog post covering the safety and security features of Hare.
The main problem is that some programmers view anything less than what Rust provides as morally unjustified.
When you say you take security pretty seriously, and mention the Hare crypto module, are you talking about the crypto module which silently falls back to storing secure data on the heap when Linux keyctl is not present on the platform?
Yes. As I reiterated many times in that thread, having your data stored in the heap does not introduce any immediate vulnerabilities, and this behavior is thoroughly documented in the standard library.
It is possible for two people who both take security seriously to come away with different take-aways. Once some CVEs are found in Hare you might have some fuel for your argument, but until then it's just speculation.
> Because rust inherently treats humans as fallible to a large extent. Which all of us are.
No it doesn't. It has a bypassable compile-time verified lifetime system, not an infallible programming guarantee. It leaves it entirely up to the developers to write correct and safe applications and libraries, and merely provides (powerful) tools to help.
(I realize this context was in avoiding common security bugs which are usually less likely in memory-safe languages, but it's important to not overstate the benefits.)
I think Rust kinda has a marketing problem: the myth that "Writing in Safe Rust automatically makes your code memory-safe". It doesn't (well, it does most of the time but it isn't guaranteed), but it rather defers the responsibility to other low-level system programmers writing Unsafe code behind the scenes. And oh boy they have a fuckton of responsibility... Stacked Borrows along with various sanitizers can help when writing unsafe code, but it isn't perfect. I highly recommend anyone trying out Rust for the safety guarantees to take a look at the Rustonomicon (https://doc.rust-lang.org/nomicon/), which debunks a lot of the misconceptions around safe/unsafe Rust.
In an ideal la-la land world, there exists an abstract interpreter that can consume safe Rust code and does not enforce any contracts upon the programmer (and hence will never have any undefined behavior). However, real world hardware definitely has contracts which developers have to obey (manually! because of the constraints of actual semiconductor physics! no compiler hand-holding here!). And on top of that all major OSes (Windows, MacOS, Linux) are written in C (so you need unsafe FFI to interact with the OS).
There seems to be a perception among Rust programmers that C's status as a defacto standard came to be in spite of C's contradictions, rather than as a result of them. Skimming the Rustonomicon just now left me with the impression that at least one Rust person gets it. Though it still seemed as if the author felt the need to choose their words very carefully, lest they "make the memory model people angry".
exactly. if i'm writing safe rust and encounter memory safety issues, their origin is with my dependencies, and my responsibility is limited to having chosen such dependencies.
In practice, this makes vulnerabilities in eg. argument parsers (like the recent "baron samedit" vulnerability in sudo) incredibly unlikely.
> the myth that "Writing in Safe Rust automatically makes your code memory-safe"
Of course you're right that that isn't true as stated, but I think it's interesting to try to situate this point along a continuum of other similar points:
1. C with Valgrind and sanitizers isn't always memory safe, because those tools are limited by test coverage.
2. Python isn't always memory safe, because many libraries including the standard library call into C code.
3. Pure Python that doesn't call into any C code isn't always memory safe, because the interpreter might have bugs.
4. Provably correct Ada with a provably correct compiler isn't always memory safe, because the proof checker, the compile-time hardware, or the runtime hardware might have bugs.
I think we all agree that there are important differences between 1 and 4, beyond the simple fact that the defects get less common as you go down the list. Here are some things that stand out to me:
- In cases #2 and below, the application code isn't "at fault" for any memory unsafety that comes up, and whatever code is at fault can be fixed to restore memory safety without changing the application.
- In case #1, there's no clear boundary in any sense between "safe code" (which we know isn't at fault for memory unsafety) and "unsafe code" (which might be at fault). There may be a distinction between code that's well covered by tests and code that isn't, for example, but it's often not easy to tell which is which. In case #2 and below, the boundary is pretty clear.
- In case #1, the amount of "unsafe code" in an application probably grows linearly with the size of the application, or maybe we just consider the whole application unsafe. But in cases #3 and #4, unsafe code is confined to low-level dependencies that get a lot of "battle testing" compared to how much code is in them. Case #2 is kind of a gray area, and we need to look at what dependencies the application is using.
So where should we situate Rust in that continuum? Is being able to write unsafe Rust code more or less risky than being able to call into C? It's certainly a lot more convenient to write an `unsafe` block than to cross the FFI barrier, and maybe that convenience is dangerous. On the other hand (contrary to some common misconceptions), unsafe Rust still benefits a lot from the borrow checker and other safety features, and it might end up having a lower rate of defects for that reason. Maybe it's too early to tell?
But anyway yes, I totally agree that the Rust community has a hard time getting the messaging right about how safe code and unsafe code work. But even though this discussion is really important to Rust, I'm not sure it's a "Rust problem" per se. I think it's actually quite difficult to talk clearly and correctly and precisely about memory safety in general.
This list is actually quite interesting for a few reasons.
One is that a large fraction of these security issues are not real, potentially exploitable vulnerabilities, but merely the fact that it is possible to abuse an API to subvert Rust's safety guarantees. These are things would, by the standards of other programming languages, not be worth even reporting and be considered user error.
The other is that a surprisingly large fraction of these are not from regular unsafe Rust code, but from misunderstanding the guarantees a C library makes when creating bindings for it. This is to be expected, as fully understanding those as a library is pretty difficult.
A total of one memory safety issue reported for an entire ecosystem this year so far also seems pretty good.
All in all, I think these are both pretty promising signs that the safety guarantees Rust provides working as intended.
Wait, that's all? On the entire Rust ecosystem those are the only ones found?
Everybody already knows Rust has unsafe blocks and C FFI. It is not invulnerable to those problems, Rust just makes it very clear where those problems may appear, and if you are smart, you will place most of your code outside of those regions.
Looks like Rust is much safer on practice than what I expected.
Note that unsafe does not contain anything. The problem propagates to the unsuspecting caller of claimed safe code.
Ending up compromised by a problem in tokio, Pin semantics, actix or all the necessary ffi bindings is no different than, say, a C program being compromised by a vulnerability in OpenSSL or libcurl.
A very significant number of memory issues in C stemmed from issues in such single high-profile dependency, so one should not undermine the threat of a bit of unsafe code in the corner of a library.
Not being perfect does not translate to not being better. Being safe by default and having compiler-enforced safety as a top design choice is great.
Rust is better.
It's very much human nature to trace the line in the sand juuuuuust right behind one's heels though, depicting everyone behind as bad and everyone ahead as zealots.
I did not say rust was not better. I said the statement was false, based on a misunderstanding of both the benefits of Rust and the problems of C many if which Rust is not immune to.
Rust is definitely better, hands down, but insisting on thinking code that interacts with unsafe blocks can be "safe by default" is a dangerously wrong mindset which also makes unsafe blocks proliferate without the necessary caution as the problem seem "contained". Anyone remember the actix unsafe saga?
But even though a program with unsafe blocks (read: all rust programs) are by definition not memory safe - calling a language memory safe on current platforms can to some extend even be considered a misnomer - the assistance provided by rust by default certainly helps make such programs much safer.
Well... if you consider the proportion of "lines of code (or projects) ever written in the history of a language" over "security issues found" then Rust will be probably losing.
I can assure you that Hare takes security more seriously than assuming the programmer is smart enough to do it right. I don't appreciate shallow takes which make unsympathetic judgements on the language which are not based in any understanding of how Hare actually works, and I've heard nothing but such takes for a week.
Hare does not prevent use-after-free, but it does prevent many other kinds of bugs which are common in C. I will go into greater detail in another blog post.
And that category of bugs (memory safety) is only avoidable if the unsafe code itself doesn't have undefined behavior (which responsibility is left to the programmer rather than the compiler). If unsafe code is compromised then it's still game over (hence the recent development of various tools/methods like Stacked Borrows in MIRI that checks potential errors outside the borrow checker, as well as various guidelines for developers to write safer unsafe code)
Safe Rust cannot ever cause undefined behavior, but Unsafe Rust can. The ultimate merit of Rust is that when you suspect any undefined behavior you only need to check the unsafe part, which is a much smaller percentage of your codebase (as opposed to C/C++ where you need to check the entirety of your code)
It is not enough to check your unsafe code for UB, you also need to make sure it does not violate the invariants Rust relies on to prove the safe code safe.
a very narrow sense representing 70% of all security vulnerabilities at microsoft and google (self-reported). i'd say it's a class of vulnerabilities worth eliminating, especially when the "cost" is getting a competent and standard package manager and a general focus on correctness that ultimately increases developer productivity and ergonomics (compared with C++, IME)
Honestly, I don't blame him, after the onslaught he's been defending against in every other thread since Hare's launch. It's really embarrassing to see that side of the Rust community acting this way.
You should be implementing a borrow checker or something like it. It's irresponsible not to do that. I'm serious about this. We know how to totally stop most use-after-free bugs during static analysis now, this is a tool that can be implemented in any language, so people should just do it. If you ask me the status quo moved a long time ago. This has nothing to do with Rust.
Also I was wrong before and you were out of line. Matthew wasn't trolling, he never said you should be held criminally liable. You just made that criminal part up for no reason. Anyone should be held socially liable and shamed if their project has bad security and they refuse to fix it after they knew about it. I think you would even agree with that.
I think “incredibly bad” is overstating things quite a bit. Safety isn’t an all-or-nothing game. If it were, Rust would be useless because it’s not Ada or another formally verifiable language.
That seems to be a reference to https://lwn.net/Articles/893346/ - I'll let people come to their own conclusion about whether Drew's framing matches that, but I will say that I've never written a line of Rust in my life and certainly can't be described as representing the Rust community in any way. My position here is based on spending my days dealing with the consequences of bugs that we know how to get rid of - I just don't see the excuse for building something that isn't at least as good as solutions that already exist, especially when the consequences are potentially so significant.
Hare is presented with the expectation that it would be included in Linux distros. With that framing, criticism on matters that are relevant to distro-included software is indeed relevant.
Where was it claimed or implied that criticism of this project isn’t relevant? My comment points out the flaws in the presumption that independently written software needs an excuse to exist.
If you've never written a line of Rust in your life, then your position could be more informed. I've written a lot of Rust, and see how much one sacrifices to get the memory safety guarantees without losing speed or memory safety. You can't make basic observers, RAII, dependency injection, back-references, or a lot of other useful patterns, without sacrificing speed or safety.
I'm experienced in security and rust and I agree with mjg. I've probably written 100s of thousands of lines of Rust, and I've been in security for well over a decade.
None of what you said is even true, all of those patterns are trivial, except "back-references" which are very slightly non-trivial.
And none of it is relevant. We don't need another memory unsafe language. It's fine if it's a toy, but this obviously isn't. It causes real harm.
They all rely on having a mutable member reference to affect the outside world, which the borrow checker rejects. You can try to make it happen with a generic lifetime parameter for your structs, but you end up making invisible the thing you're pointing at, making it useless.
One can use unsafe to have shared mutability, or sacrifice speed with Rc/RefCell (increments/decrements) or Cell (copying, especially bad when copying Vecs which causes heap allocations).
Basic observers aren't possible. The closest thing we can get is some sort of modified observer-like substance that returns a command, which requires a lot of wiring and incidental complexity. [0] [1]
Dependency injection (the pattern, not the framework) isn't viable for the same reasons as observers: you can't have a mutable reference field without causing a lot of headaches elsewhere.
RAII isn't possible without sacrificing speed or safety. Most usages we see are backed by unsafe (FFI) or RefCell. We can't have multiple objects whose drop() affects the outside world, because we can't have multiple extant &mut references, and we can't just pass them in via parameters (because drop takes none).
Back references aren't possible because of the circular problem (having a mutable reference to your owner means nobody else can read it).
I'm not advocating for Hare, I'm just addressing the "I just don't see the excuse" remark, which can be ignorant of the costs of the borrow checker. The borrow checker is a great step forward, but not always a good tradeoff. An architect needs to be aware of these sacrifices before going all-in on a paradigm.
You are fundamentally misunderstanding the relationship between Rust's unique and shared mutabilities and the usual mutability in, say, C. The Rust equivalent of C mutability is Cell. Conversely, the C equivalent of Rust's unique references is a `restrict` pointer.
Cell is meant to be applied at the "leaves" of a type where individual assignments take place. When used this way, there is no additional copying relative to the straightforward C version. (Bringing up Vec and heap allocations here is also complete nonsense; Cell has nothing to do with Clone.)
Once you wrap your head around that, all your shared mutability examples translate over to Rust trivially. All the "sacrifices" in speed you are imagining are relative to `restrict`/`&mut T`, not the usual baseline of a simple mutable object.
RAII is built into Rust. DI is trivial and I use it all the time, idk what you're trying to say there. Graphs require Rc, backreferences are trivial with Rc::weak.
You can't use RAII in Rust? What on earth could this possibly mean? RAII is an extremely pervasive pattern in Rust and is fundamental to many of the safe APIs in the standard library.
To clarify, I was talking about making RAII, not using RAII. And it surprised me too, when I learned that the borrow checker rejects it.
To see it in action: Have a Database object, and try to have multiple Transaction objects that might commit something to it, in their drop().
It's unfortunately not possible, because they can't all have a &mut Database as struct fields.
We can sacrifice speed (by using Cell's copying or Rc's counting) or safety (by using unsafe). Most RAII we see uses unsafe FFI under the hood, which is why it was so surprising to me.
Rust is actually right, you cannot have multiple mutable references to a Database object without things going down the drain. (This is related to the fact that, like other comments said, &mut is an exclusive reference).
However, achieving something like what you want is still more than possible in Rust. You can do this with the pattern of 'interior mutability', which in its simplest form is just a Mutex. This allows upgrading a shared reference to an exclusive reference, so that you can safely mutate an object while upholding the expectations that a mutable reference is exclusive, and a non-mutable reference does not change from under your feet.
Of course, for a database, you will probably want a more advanced implementation of interior mutability, so that you can commit multiple transactions at the same time. (Or not, it seems to work quite well for SQLite.)
RAII is a general pattern for tying resource management to the lifetime of objects such that resource allocation is tied to value construction and resource deallocation is tied to value destruction. The smart pointers for allocation in the Rust standard library (Box, Rc, and Arc) are examples of the RAII pattern, since memory allocation happens at creation time (Box::new()) and memory deallocation happens when the Box goes out of scope (in drop()). Another example of RAII in the standard library is File: opening a file means creating a value of type File, and dropping that value means closing the file. Yet another example are the smart-pointer guards used for accessing RefCell and Mutex: RefCell::borrow() returns a Ref, and Mutex::lock() returns a MutexGuard; the underlying value can only be accessed while the guard exists, and access is relinquished when the guard is dropped. Given all this, it's absurd to say that Rust doesn't support RAII — RAII is fundamental to the design of many of Rust's safe APIs.
The very specific API design that you've described is not possible in Rust, but it is strange to equate this with the entirety of RAII. In any case, there are many alternative APIs (some with no sacrifice in speed or safety!) that are perfectly possible in Rust.
i think the real mistake was to have "exclusive references" be called "mutable references" in the language. I've taken the habit of saying "mut" as "mutually exclusive" for references. Of course you can't have each Statement keep an exclusive reference to a db object. They're exclusive!
You need shared references for your DB, implying you need interior mutability. This is how Statements are implemented in real-world rust database drivers such as rusqlite (any operation on a db is done through a shared reference). The fact that a very real package is doing it proves that the pattern you're talking about is, in fact, possible.
I felt that statement, along with some of the language used in the blog is at odds with the author's goal to...
> Part of our work in developing Hare is laying the groundwork for a collaborative, productive, healthy community that people want to work in
The goal to have a healthy community is laudable but it comes from the top and hitting out at others doesn't set a good foundation. I'd recommend rising above by responding to critique without emotion.
There was a prominent member of the software community arguing in the LWN thread that authors of compilers which don't statically reject use-after-free (presumably including C and Hare) should be held liable for the consequences of use-after-free errors. And I suspect this person doesn't think the rustc developers should be sued for bugs written in unsafe Rust. I would argue that this person, not Drew DeVault, is hitting out at others.
You're right, I do understand that. My issue is with Drew DeVault's blog linked at the top, which I quoted. My advice is that if someone is hitting out at you then reply with statements, evidence and not emotional hyperbolic language. As the chief of any project, you set the tone of the project and blog entries like this are counter-productive if one wishes to run a healthy community.
Tony Hoare stated that language creators should be responsible for the bugs that users of those languages create. Is Tony Hoare a rust evangelist, or a "moral crusader" ?
Such a policy would create a chilling effect on the creation of practical languages which don't refuse to compile unproven code (every language in wide use).
Assuming the "criminally prosecuted" piece is a reference to that thread [1], based on what other comments have also pointed out, did anyone in there actually say that, or imply it? I read that thread and didn't see anything close to that, there is clearly harsh language in there, such as:
> Look if you can't understand that this is a thing that will happen in the real world and that people will potentially suffer as a result you shouldn't be writing a crypto library.
Which is still far from suggesting someone should be prosecuted.
I am not qualified to make any comments on the legal difference between the terms either, and I should also add that I am an ESL, so take that with a grain of salt.
Reading the original comments about being "liable", it felt like another way of saying "there are consequences to your decisions, and as the author you bear some responsibility of what you put out there", which imo is pretty far from how the author of this blog post described it, hence me calling "a pretty big stretch"
That is likely just trolling, but is also specially obnoxious considering that Rust does prevent use-after-free bugs as much as Java does (i.e. a lot, but definitely not all).