Sort of similarly, I'd like to see more use of sandboxing in memory-safe language programs. But I don't see a ton of people using these OS primitives in, e.g., Rust or Go.
I hope this project gets more traction. I would love to see a memory safe battle tested sudo or polkit in my package manager without having to install a potentially workflow breaking replacement.
If you're into Nix, check out https://github.com/mbrock/filnix — not yet integrated & maintained in upstream Nixpkgs, but lets you replace Nix/NixOS packages with Fil-C versions quite easily.
The author has a knack for generating buzz (and making technically interesting inventions) :)
I'm a little concerned that no one (besides the author?) has checked the implementation to see if reducing the attack surface in one area (memory security) might cause problems in other layers.
For example, Filip mentioned that some setuid programs can be compiled with it, but it also makes changes to ld.so. I pointed this out to the author on Twitter, as it could be problematic. Setuid applications need to be written super-defensively because they can be affected by envars, file descriptors (e.g. there could be funny logical bugs if fd=1/2 is closed for a set-uid app, and then it opens something, and starts using printf(), think about it:), rlimits, and signals. The custom modifications to ld.so likely don't account for this yet?
In other words, these are still teething problems with Fil-C, which will be reviewed and fixed over time. I just want to point out that using it for real-world "infrastructures" might be somewhat risky at this point. We need unix nerds to experiment with.
OTOH, it's probably a good idea to test your codebase with it (provided it compiles, of course) - this phase could uncover some interesting problems (assuming there aren't too many false positives).
It's difficult for me to have a positive opinion of the author when he responds with dismissal and derision to concerns others have raised about Fil-C and memory safety under data races.
The fact is that Fil-C allows capability and pointer writes to tear. That is, when thread 1 writes pointer P2 to a memory location previously holding P1, thread 2 can observe, briefly, the pointer P2 combined with the capability for P1 (or vice versa, the capability for P2 coupled to the pointer bits for P1).
Because thread 2 can observe a mismatch between a pointer and its capability, an attacker controlled index into P2 from thread 2 can access memory of an object other than the one to which P2 points.
The mismatch of pointer and capability breaks memory safety: an attacker can break the abstraction of pointers-as-handles and do nefarious things with pointers viewed instead as locations in RAM.
On one hand, this break is minor and doesn't appear when memory access is correctly synchronized. Fil-C is plenty useful even if this corner case is unsafe.
On the other hand, the Fil-C as author's reaction to discourse about this corner case makes me hesitant to use his system at all. He claims Java has the same problem. It does not. He claims it's not a memory safety violation because thread 1 could previously have seen P1 and its capability and therefore accessed any memory P1's capability allowed. That's correct but irrelevant: thread 2 has P2 and it's paired with the wrong capability. Kaboom.
The guy is technically talented, but he presents himself as Prometheus bringing the fire of memory safety to C-kind. He doesn't acknowledge corner cases like the one I've described. Nor does he acknowledge practical realities like the inevitability of some kind of unsafe escape hatch (e.g. for writing a debugger). He says such things are unnecessary because he's wrapped every system call and added code to enforce his memory model's invariants around it. Okay, is it possible to do that in the context of process_vm_writev?
I hope, sincerely, the author is able to shift perspectives and acknowledge the limitations of his genuinely useful technology. The more he presents it as a panacea, the less I want to use it.
There's a hybrid approach of C -> WASM -> C compilation, which ends up controlling every OS interaction and sandboxing memory access like WASM, while technically remaining C code:
Which requirements does a full blown virtual machine not meet? By leaning on that as the sandbox, we get Qubes, but maybe I don't know what I'm talking about.
OS-level sandboxes are way too coarse grained to achieve a good "hollowing out" of the attack surface. The principle of least privilege should extend down to/start at the individual language library level (because this is where the actual trust boundaries are), or even finer grained, at the individual function or code segment level (thereby providing maximum control), and therefore not be limited to larger domains.
Most software today relies on many (imported, third party) libraries, so the security architecture should provide primitives/abstractions to manage rights at that level, which requires programming languages to implement the ability to sandbox (managing the effects of) code. If they did this with lightweight, portable virtual machines like WebAssembly, that could work.
The vast majority of code out there should be limited to pure computation and have no ability to access anything external at all (and otherwise, only what it actually requires) - yet most languages are simply incapable of providing any such guarantees. If the programmer of software cannot get ironclad assurances, they cannot in turn provide them to their users.
I'm not saying that OS-level sandboxing isn't good, just that it doesn't go far enough. And depending on the setup, it may not sufficiently limit the effects of compromised elements, and it provides no "monitoring in the small". It's also not convenient or efficient to have an entire OS instance for every single system component. Compartmented microkernel operating systems like Genode do it better imo.
My trouble with separate categories "memory safety technology" and "sandboxing technology" is that something like WASM execution is both:
* Depending on how WASM is used, one gets safety guarantees. For example, memory is not executable.
* Privileges are reduced as a WASM module interacts with the environment through the WASM runtime and the embedder
Now, when one compiles C to WASM one may well compile things with bugs. A memory access bug in C is still a memory access bug, but its consequences can be limited in WASM execution. Whether fail-stop behavior is guaranteed actually depends on the code the C compiler generates and the runtime (allocation/deallocation, concurrency) it sets up.
So when we enumerate immediately available security options and count WASM as sandboxing, this is not wrong. But WASM being an execution environment, one could do a lot of things, including a way of compiling and executing C that panics when a memory access bug is encountered.
> including a way of compiling and executing C that panics when a memory access bug is encountered.
WASM couldn’t do that because it doesn’t have a sense of the C memory model nor know what is and isn’t safe - that information has long been lost. That kind of protection is precisely what Fil-C is doing.
WASM is memory safe in that you can’t escape the runtime. It’s not memory safe in that you can escape escape the program running within the sandbox, which you can’t do with a memory safe language like Rust or Fil-C.
Say your C program has sensitive information in module A and a memory safety bug in module B. Running that program in wasm won’t prevent the attacker from using the bug in B to get read/write access to the data in A.
In practice what the attacker will really do is use the memory safety bug to achieve weird execution: even without control over the program counter, the fact that a memory safety bug inside the wasm memory gives read write access to all of that memory means the attacker can make the program do whatever they want, subject to the wasm sandbox limits (ie whatever the host allows the wasm guest to do).
Basically wasm amounts to a lightweight and portable replacement for running native code in a sufficiently sandboxed process
Depends on how it is used is already a sign that WebAssembly isn't really as safe as being sold, by many of its advocates, versus other bytecode formats.
Like, C is actually really safe, it only depends on how it is being used.
People only have to enumerate the various ways and tools to write safe code in C.
Another option: It's genuinely easier, for what amounts to namespacing reasons. Like, if I came up with a cool new C compiler, I'd probably name it ${MYNAME}cc just because that's an easy identifier that is very unlikely to have a collision and doesn't require me to spend time thinking of some name that is clever, unique, and accurately conveys what the project is about.
Meh, a lot of people name their projects or product series using puns on their own name, or an abbreviation, not from pride or ego at all but simply for convenience of a unique namespace.
Can someone give a tldr of what makes fil-c different from just compiling with clang’s address sanitizer?
Calling it memory safe is a bit of a stretch when all it does is convert memory errors to runtime panics, or am I missing something? I mean, that’s still good, just less than I’d expect given the recent hype of fil-c being the savior for making C a competitive language again.
Nit:The word “orthogonal” should not mean merely “different”. It should mean “completely unrelated” if we are drawing a proper analogy from linear algebra. Orthogonal vectors have a dot product of zero. No correlation whatsoever. As ML and linear algebra terms spread to more common language of course the terms will change their meaning. Just as “literally” now often means “figuratively” I’m not going to die on this hill. But I will try to resist degradation of terms that have specific technical meaning.
So I would very much disagree with the statement that memory safety and sandboxing are orthogonal. They are certainly different. Linearly independent even. But with a fair amount of overlap.
25 comments
[ 0.85 ms ] story [ 845 ms ] threadI wonder how they fit into the picture.
I'm a little concerned that no one (besides the author?) has checked the implementation to see if reducing the attack surface in one area (memory security) might cause problems in other layers.
For example, Filip mentioned that some setuid programs can be compiled with it, but it also makes changes to ld.so. I pointed this out to the author on Twitter, as it could be problematic. Setuid applications need to be written super-defensively because they can be affected by envars, file descriptors (e.g. there could be funny logical bugs if fd=1/2 is closed for a set-uid app, and then it opens something, and starts using printf(), think about it:), rlimits, and signals. The custom modifications to ld.so likely don't account for this yet?
In other words, these are still teething problems with Fil-C, which will be reviewed and fixed over time. I just want to point out that using it for real-world "infrastructures" might be somewhat risky at this point. We need unix nerds to experiment with.
OTOH, it's probably a good idea to test your codebase with it (provided it compiles, of course) - this phase could uncover some interesting problems (assuming there aren't too many false positives).
The fact is that Fil-C allows capability and pointer writes to tear. That is, when thread 1 writes pointer P2 to a memory location previously holding P1, thread 2 can observe, briefly, the pointer P2 combined with the capability for P1 (or vice versa, the capability for P2 coupled to the pointer bits for P1).
Because thread 2 can observe a mismatch between a pointer and its capability, an attacker controlled index into P2 from thread 2 can access memory of an object other than the one to which P2 points.
The mismatch of pointer and capability breaks memory safety: an attacker can break the abstraction of pointers-as-handles and do nefarious things with pointers viewed instead as locations in RAM.
On one hand, this break is minor and doesn't appear when memory access is correctly synchronized. Fil-C is plenty useful even if this corner case is unsafe.
On the other hand, the Fil-C as author's reaction to discourse about this corner case makes me hesitant to use his system at all. He claims Java has the same problem. It does not. He claims it's not a memory safety violation because thread 1 could previously have seen P1 and its capability and therefore accessed any memory P1's capability allowed. That's correct but irrelevant: thread 2 has P2 and it's paired with the wrong capability. Kaboom.
The guy is technically talented, but he presents himself as Prometheus bringing the fire of memory safety to C-kind. He doesn't acknowledge corner cases like the one I've described. Nor does he acknowledge practical realities like the inevitability of some kind of unsafe escape hatch (e.g. for writing a debugger). He says such things are unnecessary because he's wrapped every system call and added code to enforce his memory model's invariants around it. Okay, is it possible to do that in the context of process_vm_writev?
I hope, sincerely, the author is able to shift perspectives and acknowledge the limitations of his genuinely useful technology. The more he presents it as a panacea, the less I want to use it.
https://rlbox.dev/
Most software today relies on many (imported, third party) libraries, so the security architecture should provide primitives/abstractions to manage rights at that level, which requires programming languages to implement the ability to sandbox (managing the effects of) code. If they did this with lightweight, portable virtual machines like WebAssembly, that could work.
The vast majority of code out there should be limited to pure computation and have no ability to access anything external at all (and otherwise, only what it actually requires) - yet most languages are simply incapable of providing any such guarantees. If the programmer of software cannot get ironclad assurances, they cannot in turn provide them to their users.
I'm not saying that OS-level sandboxing isn't good, just that it doesn't go far enough. And depending on the setup, it may not sufficiently limit the effects of compromised elements, and it provides no "monitoring in the small". It's also not convenient or efficient to have an entire OS instance for every single system component. Compartmented microkernel operating systems like Genode do it better imo.
* Depending on how WASM is used, one gets safety guarantees. For example, memory is not executable.
* Privileges are reduced as a WASM module interacts with the environment through the WASM runtime and the embedder
Now, when one compiles C to WASM one may well compile things with bugs. A memory access bug in C is still a memory access bug, but its consequences can be limited in WASM execution. Whether fail-stop behavior is guaranteed actually depends on the code the C compiler generates and the runtime (allocation/deallocation, concurrency) it sets up.
So when we enumerate immediately available security options and count WASM as sandboxing, this is not wrong. But WASM being an execution environment, one could do a lot of things, including a way of compiling and executing C that panics when a memory access bug is encountered.
WASM couldn’t do that because it doesn’t have a sense of the C memory model nor know what is and isn’t safe - that information has long been lost. That kind of protection is precisely what Fil-C is doing.
WASM is memory safe in that you can’t escape the runtime. It’s not memory safe in that you can escape escape the program running within the sandbox, which you can’t do with a memory safe language like Rust or Fil-C.
Say your C program has sensitive information in module A and a memory safety bug in module B. Running that program in wasm won’t prevent the attacker from using the bug in B to get read/write access to the data in A.
In practice what the attacker will really do is use the memory safety bug to achieve weird execution: even without control over the program counter, the fact that a memory safety bug inside the wasm memory gives read write access to all of that memory means the attacker can make the program do whatever they want, subject to the wasm sandbox limits (ie whatever the host allows the wasm guest to do).
Basically wasm amounts to a lightweight and portable replacement for running native code in a sufficiently sandboxed process
Like, C is actually really safe, it only depends on how it is being used.
People only have to enumerate the various ways and tools to write safe code in C.
Problem solved, or so we get to believe.
You're getting cool tech for free. You can use it or not use it. Complaining about the name is weird.
Calling it memory safe is a bit of a stretch when all it does is convert memory errors to runtime panics, or am I missing something? I mean, that’s still good, just less than I’d expect given the recent hype of fil-c being the savior for making C a competitive language again.
So I would very much disagree with the statement that memory safety and sandboxing are orthogonal. They are certainly different. Linearly independent even. But with a fair amount of overlap.