6 ms·
Yeah, it sounds like this is a clever hack to get clang to compile C code into a safe binary form; with runtime validation around pointers and array lookups and
by josephg 5y ago
Yeah, it sounds like this is a clever hack to get clang to compile C code into a safe binary form; with runtime validation around pointers and array lookups and things.
I'm surprised clang doesn't simply have a mode to do this already. Its weird we need this strange wasm dance. I'm imagining a sort of a release mode version of ASAN + UBSAN where the compiled code has bounds checks for all array and pointer lookups. (And probably some more checks, besides). The rule is that it should be impossible for any binary code compiled this way to overrun arrays, or read or write memory outside of its allocated section. No matter the C code, the binary is memory-safe.
It would have a (modest) cost in runtime performance and binary size, but frankly that would be a trade off well worth making in all sorts of software we use daily.
- wahern 5y ago> I'm surprised clang doesn't simply have a mode to do this already. Its weird we need this strange wasm dance Compiling to WebAssembly doesn't make the code safe; it simply compiles it to a virtual machine with a memory model that isolates any bugs from the rest of the parent process (presuming a bug-free parent process). Basically, all pointers in WebAssembly are indexes to a global array (or global arrays--one for data and another for functions). It's basically the same trick people use in Rust to get around restrictions on reference cycles. And like in Rust, the compiled code can still have the same dangling pointer and buffer overflow problems, but the memory thus referenced is restricted to the unstructured array, rather than to potentially any address in the parent process' address space. The difference between Rust and WASM is that when you compile WASM to native code, at least when JIT'ing the code, you can completely elide the extra degree of memory reference indirection. (I don't think the extra indirection can be elided for AOT compilation of WASM code without a special runtime linking step.) The cost, however, is that you're left with this single global data array, so any bug in the WASM program can break any other part of the WASM program, whereas in Rust you effectively end up with many segments of unstructured memory (basically, one per component that uses the trick) that can't be cross-referenced if the program is buggy.
- jcranmer 5y agoThere's a very good reason such a thing doesn't exist: any C compiler that worked like that would break the vast majority of C programs. Fundamentally, memory safety requires that you keep track of not just where a pointer currently points to, but where a pointer could validly point to. But, as applied in practice, the C ABI requires that pointers be integers of where it currently points to; making pointers also include range information (aka "fat" pointers) is possible, but requires rewriting almost any software you care about--it's the kind of change that's about as fundamental to C programs as assuming 2's complement arithmetic. The way tools like ASAN and Valgrind work is by instead keeping track of where any pointer could point to (via shadow maps, basically a bit per memory location saying if you can read/write that location), and then padding every allocation with sufficient space that an innocuous read overflow (especially one-past-the-end) is likely to hit the padding. Their guarantees are at best probabilistic, not anything you would want to rely on for security.
- _0w8t 5y agoGiven that one can compile C to Wasm it is simply not true that it would break C code. Wasm implements pointers as offset into an array and then checks for out-of-bounds access. LLVM should be able to do that directly.
- jrtc27 5y agoMost C and C++ code is perfectly happy with fat pointers provided you take care in how you implement them, especially around (u)intptr_t. For CHERI we see a very tiny % of LoC that need changing; e.g. http://www.capabilitieslimited.co.uk/pdfs/20210917-capltd-cheri-desktop-report-version1-FINAL.pdf http://www.capabilitieslimited.co.uk/pdfs/20210917-capltd-ch... documents a recent case study in porting a KDE desktop stack (X11 itself, Qt, other standard graphical desktop libraries, Plasma, Dolphin, Okular) and across the 6 million LoC only 0.026% needed changing. So, if you pick your fat pointer implementation poorly, then yes, you run up against both the standard and de-facto C, but if you take care then the vast majority of C code just works, and we're (uniquely?) positioned to be able to prove that by having a FreeBSD-based kernel and userspace, and graphical desktop stack (plus an adaptation of WebKit's JSC JIT) all built with a compiler that maps C pointers to capabilities that enforce fine-grained bounds.
- rstuart4133 5y ago> runtime validation around pointers and array lookups and things. I think you're right but "validation around pointers and array lookups" makes it sound far better than it is. That sounds like the sort of strong validation is "safe" Rust gives you. But all it does is ensure the WASM virtual machine "executing" the WASM code doesn't access RAM outside of the VM's memory space. Everything in that memory space is fair game, so all the foot shooting stuff C allows like corrupting pointers and following them within that memory space is still possible. It achieves what they want for security I guess, but not much more.