4 ms·
I still can't figure out how Zig proposes to prevent undefined behavior without a borrow checker (aka MLKit regions) or GC. AFAICT the answer is "inject as man
by octoberfranklin 5y ago
I still can't figure out how Zig proposes to prevent undefined behavior without a borrow checker (aka MLKit regions) or GC.
AFAICT the answer is "inject as many runtime checks as needed" although the docs seem to go way out of their way to avoid making this explicit.... or deal with the fact that these checks are now runtime failures rather than compile-time failures, and therefore need code to handle them.
It seems like it would be the same as writing Rust code using std::cell instead of references, except that Rust would force you to insert handlers for all the new failure modes this would create (of course you could just panic!(), but at least the compiler would force you to insert those panics...).
- _vvhw 5y agoYou're conflating undefined behavior with spatial/temporal memory safety, the latter of which is what is 100% prevented by Rust's borrow checker, provided you're not interfacing with hardware, in which case I believe that things change more in binary fashion. However, Zig treats memory safety not as an extreme-at-all-costs but as a spectrum (there are reasons for wanting to think like this, at least when writing low-level code if you want to make more use of the hardware), getting 100% on the spatial memory safety front and reaching to 50-75% on the temporal memory safety front through the GPA. That's already an order of magnitude more safety than C, at which point memory exploits have dropped in ranking, and you should be more concerned about things like explicit control flow, error handling and checked arithmetic, not to mention the orthogonality of the language. Furthermore, in the systems world, there are many safety critical systems where dynamic allocation and multi-threaded control planes are simply off the table to begin with because they're dangerous in some domains and not as safe as static allocation and single-threaded control planes, which are less dimensional and easier to reason about. And in those cases, UAFs and multi-threaded races are less of a concern (still a concern, but less). Also, Rust won't protect you from all undefined behavior, and Zig often helps more than you think. For example, you might be surprised to hear that Rust has checked arithmetic off by default in safe builds, whereas Zig has this enabled. I've done a little security work on some large systems and the decision to disable checked arithmetic always blows my mind. Integer overflow and underflow are right up there as threat vectors when writing anything that's touching hostile data. I'm waiting for the day when Rust changes direction on this, and I think there's a chance this will happen because the alternative status quo of not checking arithmetic (at all) is just not tenable, at least not if we care about safety and security holistically, and not only memory safety.
- octoberfranklin 5y ago> You're conflating undefined behavior with spatial/temporal memory safety, You are correct; I should have written "memory safety". > Zig treats memory safety not as an extreme-at-all-costs but as a spectrum Zig needs to be more forthright about this. When I first heard about Zig, I googled "zig vs rust" and found an article on the Ziglang website addressing that very topic: https://ziglang.org/learn/why_zig_rust_d_cpp/ https://ziglang.org/learn/why_zig_rust_d_cpp/ It completely fails to mention memory safety at all. That seems extremely dishonest, since memory safety is basically the "headline feature" of Rust (well, one of two or three at most). I wasted a lot of time digging through the Zig language manual ("so then how do they...") before concluding that something didn't add up. It definitely left a bad taste in my mouth. > Rust won't protect you from all undefined behavior ... Rust has checked arithmetic off by default in safe builds That didn't surprise me at all, nor will it surprise anybody who knows Java. Modular arithmetic is perfectly well-defined. It's only C/C++ that picked the crazysauce option of decreeing that signed overflow is totally equivalent to scribbling all over random pieces of memory. It isn't overflow that's a security risk; it's languages that define overflow to be undefined in order to squeeze out a few piddly loop micro-optimizations. This becomes increasingly less beneficial in languages with iterators and no backward-compatible-with-C burden. Details (scroll to "Myth: overflow is undefined"): https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/ https://huonw.github.io/blog/2016/04/myths-and-legends-about...
- _vvhw 5y ago> Modular arithmetic is perfectly well-defined. Yes (and thanks for the link!), I was in fact thinking more of this non-UB case (not signed overflow UB) as an example of where it's clearly defined as wraparound but can be chained into an exploit nevertheless, not technically UB but a vulnerability nevertheless. Not all exploits bother to go as far as a UAF. Unchecked arithmetic can be low hanging fruit. > That didn't surprise me at all It surprises me that Rust doesn't just enable checked arithmetic by default with an opt-out for performance, rather than enabling it by default for performance with an opt-out for safety. Zig's choice here is the safer choice from a security perspective.
- jibalt 5y agoZig doesn't propose to prevent UB (in fact the docs say that it takes aggressive advantage of it for optimization). (Neither does Rust.)
- tialaramex 5y agosafe Rust doesn't have any Undefined Behaviour. Your unsafe Rust is supposed to provide suitable constraint/ guarantees that you, the programmer, conclude it does not have any Undefined Behaviour. The language can't force you to do this, and at some point it becomes a social contract not a programming language feature. I wrote the misfortunate crate to explore Rust's promise here. The crate provides legal but obviously inappropriate implementations of lots of safe Rust traits, and sure enough nothing blows up, there is no undefined behaviour. The defined behaviour can be undesirable for example if you insist on putting a bunch of misfortunate::Maxwells in a HashSet you're going to have a bad time. Rust doesn't promise this is a good idea, it might cause infinite loops, memory leaks, all sorts of defined trouble, but it won't be Undefined Behaviour.
- jibalt 5y agoNot "you, the programmer", any programmer. The Rust standard library can and sometimes does have UB, and that's even more true for other libraries.
- tialaramex 5y ago"This software may have bugs" seems like a very different situation from "Our language just doesn't care whether maybe your program is nonsense and might have Undefined Behaviour".
- jibalt 5y agoNice strawman and goalpost shifting. You said that it's up to "you", the programmer, whether unsafe code misbehaves, but this simply isn't true because "you" don't control all the unsafe code you execute. Again, third party libraries can have UB ... that it's wrapped in "unsafe" keywords doesn't change that, it just means that the compiler offers a tool to help reduce it. What I said is that rust doesn't propose to prevent UB, and that's true. I didn't say that it doesn't care. (And it's not true that Zig doesn't care, either, but it makes a different tradeoff between intrusive limitations and safety.)