5 ms·
Rust's unsafe pointer types need an overhaul
- nynx 5y agoI love the idea of depreciating `ptr as usize`.
- ironmagma 5y agoFYI, it's deprecating. Depreciating is another different word.
- KerrAvon 5y agoNext, do Swift’s. So unbelievably awful, and more error-prone because of it.
- jshier 5y agoSwift 5.6 already got a few changes, with more on the way.
- saagarjha 5y agoThey’ve improved some of the things, but the ergonomics of “I have an UnsafeXPointer and I want an UnsafeYPointer” could be significantly improved. Plus, Swift really needs better documentation as to how aliasing works when external code captures a pointer and reinterprets it. My current understand is that it’s far more lenient than what C allows (basically exposing LLVM’s model directly) but this isn’t really documented anywhere that I could find.
- saagarjha 5y agoHaving watched CHERI go through its evolution for the last couple years, I am extremely glad that there’s now a modern thing that breaks the “pointers are integers” assumptions that people have mostly been OK with making for several decades, thinking that those were just things that “old” computers did and were now irrelevant to support. I’m really hoping to see architectures start re-adding trapping arithmetic too so we can start taking advantage of all the things in C that everyone hates is UB but can be repurposed into catching real bugs in hardware. I suspect this is going to break a lot of code that’s been playing fast-and-loose with these things, but “all the world’s a VAX” is a thing we got over before and now it’s going to be replaced with “Intel”.
- spc476 5y agoWhat's funny about "all the world's a VAX" is that the VAX could trap on integer overflow. I suspect that C compilers on VAX never set that mode though (it's a flag in the status register).
- reincarnate0x14 5y agoIIRC the Alpha did that as well but due to instruction pipelining it was more complicated in practice. I want to say it was used on some SCADA software but that's an ancient memory -- and it wasn't even everywhere in that software as I remember an integer overflow error in an alarm processing program that we had to hot-patch one night with live data, that was exciting. Making myself nostalgic for the Alpha again ... I'll be over in the corner crying into my beer.
- renox 5y agoThe MIPS could do it too some instructions trap on integer overflow 'for green (no Icache cost) but AFAIK there were unused.. Convenience trump's security apparently..
- klysm 5y agoComing from a position knowing essentially nothing about computer architecture or compiler fundamentals CHERI seems like a bandaid in the same way that garbage collection is a bandaid. I may be way off base, but it feels like the things CHERI ensures at “run time” _are_ enforceable at “compile time”. I’m not saying it doesn’t have a place though - we definitely don’t live in a world we’re such things are enforced so we need systems to check things at run time. It looks like a good solution but doesn’t feel like the “right” thing. Am I getting the right idea of what it’s purpose is?
- staticassertion 5y agoI think there's two important things to note here. 1. Bandaids are critical. We can't just fix the billion lines of C code out there by replacing it - we need strategies that wholesale destroy bug classes. CHERI does that. If you concretely removed temporal unsafety from all of an operating system's userland, just by recompiling, you'd have done the world a great service. 2. CHERI is very strong. It's not like some mitigation techniques, which rely on increasingly niche thread models (ASLR makes less and less sense as the world pushes towards "send code, not data" - to be fair, pax/grsec explicitly noted this 20 years ago, so it's hardly the fault of ASLR). CHERI totally wrecks spatial memory unsafety. That's fucking huge. The death of buffer overflows. Combined with other techniques like PAC, the memory unsafety threat becomes seriously less critical, or at least that's my position on it - would love to hear someone point out why I'm overly optimistic on this. In theory you could remove all bounds checking from code and leave it up to the hardware to enforce. It also is just sane. Like, I think containers are "sane" - they finally split the OS into disparate pieces. I think that NX is sane - why should memory be RWX by default? "A pointer for an allocation can dereference memory anywhere in the address space" is not sane, so hardware enforcing sanity is good. Where we can enforce sanity, we should. CHERI does that imo. Also, idk, even in memory safe languages it's not like bounds just go away. You get compile time assurances that either the bounds are gone and that's safe, or they're not gone and that's safe.
- blindseer 5y agoI'm not a systems level programmer but I personally super appreciate this kind of dialog and communication about the flaws in the programming language from prominent members in the community. I've seen it a lot more in Rust (and C++) than in most other languages. I wonder if there is a correlation between how much a language grows and changes to how many blog posts there are from prominent members in the language community outlining flaws? (Tangentially, I would love for a search engine where I could track metadata like this) I personally think that way too often the discourse from the core team / stewards / BDFLs etc is wholly positive about their own community + the technical merits of the programming language they are representing. A healthy dose of "here are some flaws" is very refreshing and humbling, and makes me appreciate the community more.
- TheDong 5y agoIf you want a couple other examples here's what I've got offhand.. Perhaps the original example, Hoare helped popularize null pointers, and then gave the "Null Pointers: The Billion Dollar Mistake" talk https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/ https://www.infoq.com/presentations/Null-References-The-Bill... The creator of nodejs talking about some of its mistakes: https://www.youtube.com/watch?v=M3BM9TB-8yA https://www.youtube.com/watch?v=M3BM9TB-8yA Nada Amin was part of the Scala team, and also wrote a wonderful paper about how Scala's type-system is fundamentally unsound: https://namin.seas.harvard.edu/publications/java-and-scalas-type-systems-are-unsound-existential-crisis-null-pointers https://namin.seas.harvard.edu/publications/java-and-scalas-... bradfitz was a core go team member, and wrote a post about what their net.IP type got wrong https://tailscale.com/blog/netaddr-new-ip-type-for-go/ https://tailscale.com/blog/netaddr-new-ip-type-for-go/ I have no doubt there's many more examples too, but those are the ones I can think of offhand.
- amluto 5y ago> For all major architectures and CHERI there is only one address space as far as I’m concerned, but it’s potentially worth opening the door for properly talking about pointers in segmented architectures here. I regret to inform you that x86_64 (which is probably a “major architecture”) is segmented and has three address spaces. In practice, it has two. In user code, those address spaces are normal memory and TLS and, in kernel code, they are normal memory and percpu memory. And the proposed scheme in this article is, on first read, amazing. It would make the epic hackery in the kernel percpu code safer (in Rust, anyway). The major caveat is that, for even slightly decent codegen, the address space of an x86_64 pointer should be part of its type, not its value. Even do, this scheme still works: ptr.with_addr(usize) -> ptr has a type! If the input pointer is TLS or percpu, so is the output.
- zozbot234 5y agoThere are C embedded extensions for separate address spaces. So the idea is not totally new. But AIUI, the C extensions still require some way of converting a pointer in a specified address space to the usual "generic" pointer type where the address space could only be part of the vale.
- pjmlp 5y agoJust like on the 16 bit days there were near and far pointer extensions on MS-DOS and Windows 16 bit.
- mrlonglong 5y agoAnd th choice of lovely memory models including tiny, small, medium, large and huge depending on the code and data sizes. Good times.
- staticassertion 5y ago> I regret to inform you that x86_64 (which is probably a “major architecture”) is segmented and has three address spaces. Can you say more? I assumed the post was referring to segmentation registers, which are not an x86_64 thing.
- staticassertion 5y agoVery well written and approachable. I think everything noted makes perfect sense, really. Semi-related, CHERI is so cool. I find it extremely promising and I truly hope that it sees widespread adoption. I really think if CHERI, or approaches similar, gain widespread adoption we will see a new era of security, similar to if not more significant than when Chrome hit the scene for desktops.
- deleted 5y ago[deleted]
- xorvoid 5y agoInteresting write-up. Unsafe pointers are definitely due for an overhaul. Suggestion on syntax: just use ‘->’ It has the familiarity from C already. Most rust unsafe programmers are long time C/C++ folks. You can define them to only work on raw pointers (like C) and always produce pointers (unlike C). And using it means no auto-deref magic E.g. ptr->field3->[0]->write(7);
- nickitolas 5y agoI think the familiarity might be a double edged sword, since AIUI the proposed operator has different semantics. "ptr~field~field2" is not "ptr->field->field2", it's more like "&(&ptr->field)->field2". Therefore I feel like the familiarity might just confuse people or give them a false sense of security.
- xorvoid 5y agoMeh. It’s harmless. You get a helpful compiler error the first time and you very quickly learn. I’ve been a C programmer for >20 years and this slight difference wouldn’t bother me. Explaining it is simple: ‘.’ uses a reference and produces a reference ‘->’ uses a pointer and produces a pointer
- heinrich5991 5y ago`.` only produces a reference if you explicitly reference the result. `a.b` is of type `B`, only `&a.b` is `&B` and `&mut a.b` is `&mut B`. This is unlike your proposal of `->` where `a->b` would be `*const B`.
- estebank 5y agoThe problem is not ibvalid.code being rejected with an error, but rather code that is accepted but does something unexpected. Lints can help with that, but that has many limits.
- Gankra 5y ago~ is not -> ptr->field in C is (*ptr).field, at which point you are in whatever C's equivalent of a "place (lvalue) expression" is. This creates a weird discontinuity where you -> for the first step and then use `.` for subsequent steps and then do the standard "just kidding, it was a pointer offset all along" thing of slapping `&` in front of it. My proposed ~ always keeps you indirected so you just use ~ all the way until you actually want to load a value from memory (which in C would implicitly happen whenever you have a nested ->).