Apparently this was done intentionally. The rationale given is that we don't want to allow non-safe C to be used in fil-C programs. Fair enough.
But why should we prevent fil-C programs to be used from Rust (or C, for that matter)?
I don't understand much about compilers, but I guess allowing one would also allow the other? i.e. it's not possible to make fil-C ABI compatible with C (so that it can be more easily called), while also not letting you use call C from it?
You're looking too much at the complete memory safety goal; the important part here is the interop non-goal. Designing a foreign function interface between Fil-C and regular C, in a way that upholds both languages' expectations about the environment the code's running in, is a very difficult problem and nobody seems to have much of an idea of how such a thing could work.
I worry that this same problem will doom the extern "Fil-C" idea that this post advocates. Zig is not really an encouraging precedent because Zig's proposed memory-safe mode adds runtime checks to everything just like Fil-C does, whereas the post author wants to avoid those checks for Rust code that's already statically known to be memory-safe. That said, it's possible I'm missing something.
Yes and AFAIK any fil-C program needs to be linked against fil-C compiled libraries, such as libc/musl/etc. I too would like to know what other challenges exist in making this process more automated.
It's not a replacement for Fil-C's role as a precise ASAN/Valgrind, but it works great if you want to call a C library without letting it freely spray caller's memory.
Fil-C is more precise than valgrind or asan. Valgrind and asan will allow a buggy access (like an OOB) to succeed if the resulting address is valid at all - which is useless from a security enforcement perspective since clobbering valid addresses is what the attacker is trying to do.
Fil-C only allows an access to succeed if it’s in bounds of that pointer’s capability. That is a useful level of precision for security, since it prevents the attacker from clobbering the addresses of their choice.
Extern "fil-C" can't be compatible with Rust code or something similar. It requires a metadata block attached to each allocation. So, if a memory block has been allocated in Rust and a pointer to it is passed to a fil-C function, it can't access it correctly. The only way to allow such cross-langauge-and-abi calls is to compile Rust code itself like fil-C, which requires doubled memory consumption, expensive runtime checks and GC overhead.
Even that way will be very difficult to make it work.
The Fil-C side could change the data behind the pointer, which will not reflect on the Rust side due to the copy. Even if you manage to copy back the changes, Fil-C could also persist those pointers in e.g. global memory: at that point you no longer know when it's safe to copy back (or forward) any change.
The only way I can see this work if you cannot pass any Rust-land pointers into Fil-C, but at that point you could also compile the C code to WASM and use the WASM FFI (which has similar restrictions)
WASM is not memory safe in the same sense, in particular, heap corruption vulnerabilities still exist, which could eg. allow bypassing auth, leaking private information etc.
C compiled to WASM is still vulnerable to something like OpenSSL Heartbleed. Fil-C (and CHERI) is not.
Any reason why you don't use clang's `-fbounds-safety`? It offers ABI compatibility and incremental adoption. The author of Fil-C worked on it also :-)
This would also mean that you wouldn't need unsafe{} to call into the Fil-C ffi. The majority of unsafe{} in (non pure-rust) cargo dependencies is calling C ffi. Rust + Fil-C is a great match that fills a particular niche, I'd love to see it happen.
I'm not sure you need to? Rust references don't exist in C, after all.
That being said I wouldn't be surprised if there were other factors that would weigh against considering FFI calls to Fil-C safe by default (e.g., Fil-C's somewhat looser bounds on what is considered a valid pointer access).
> [...] instead of accidentally linking ordinary C into it
Out of curiosity. If you really needed to, could a language speak both the C ABI and the Fil-C ABI? As I understand Fil-C itself can't do this without having Python-like FFI overhead, but maybe a different compiler implementation could?
LLM-written post, but I think the core idea is okay. I would advocate for more opportunities for safety, even if some of the safety is runtime safety rather than "only" Rust's borrow checker.
Hm on first reading I was confused by the extern "fil-c" framing -- that seems to imply a bridge between C and Fil-C, which introduces some nasty language/runtime inter-op issues.
But I like this part
I want a Rust FFI that speaks the Fil-C ABI. ... We could use Rust for compile-time safety and then pay a performance penalty for using C.
> But the result would give us exactly the right incentives.
Say what you say, but there's plenty in that article looking like it came out of an LLM. This could of course be my own paranoia, since there's a lot of slop coming out right now.
Why isn't fil-C ABI compatible with C?
https://fil-c.org/runtime
Apparently this was done intentionally. The rationale given is that we don't want to allow non-safe C to be used in fil-C programs. Fair enough.
But why should we prevent fil-C programs to be used from Rust (or C, for that matter)?
I don't understand much about compilers, but I guess allowing one would also allow the other? i.e. it's not possible to make fil-C ABI compatible with C (so that it can be more easily called), while also not letting you use call C from it?
You're looking too much at the complete memory safety goal; the important part here is the interop non-goal. Designing a foreign function interface between Fil-C and regular C, in a way that upholds both languages' expectations about the environment the code's running in, is a very difficult problem and nobody seems to have much of an idea of how such a thing could work.
I worry that this same problem will doom the extern "Fil-C" idea that this post advocates. Zig is not really an encouraging precedent because Zig's proposed memory-safe mode adds runtime checks to everything just like Fil-C does, whereas the post author wants to avoid those checks for Rust code that's already statically known to be memory-safe. That said, it's possible I'm missing something.
It's also because Fil-C calls need to carry along a bunch of extra information, to support the safety checks
Yes and AFAIK any fil-C program needs to be linked against fil-C compiled libraries, such as libc/musl/etc. I too would like to know what other challenges exist in making this process more automated.
What precisely do you mean by "this process"? The kind of linking that Fil-C supports is pretty different from what the blog post proposes.
Correct
There is a solution that Mozilla uses in prod for legacy C codecs:
https://rlbox.dev/
It's not a replacement for Fil-C's role as a precise ASAN/Valgrind, but it works great if you want to call a C library without letting it freely spray caller's memory.
Yeah rlbox is great.
Fil-C is more precise than valgrind or asan. Valgrind and asan will allow a buggy access (like an OOB) to succeed if the resulting address is valid at all - which is useless from a security enforcement perspective since clobbering valid addresses is what the attacker is trying to do.
Fil-C only allows an access to succeed if it’s in bounds of that pointer’s capability. That is a useful level of precision for security, since it prevents the attacker from clobbering the addresses of their choice.
Extern "fil-C" can't be compatible with Rust code or something similar. It requires a metadata block attached to each allocation. So, if a memory block has been allocated in Rust and a pointer to it is passed to a fil-C function, it can't access it correctly. The only way to allow such cross-langauge-and-abi calls is to compile Rust code itself like fil-C, which requires doubled memory consumption, expensive runtime checks and GC overhead.
Naah.
Solvable by turning the usual C library allocation pattern inside-out:
Only the C side allocates, Rust gets views of that memory.
Also well established - calling WebAssembly components requires exactly the same pattern, as the only way to malloc is to call into the WASM side.
Why would it double memory consumption just for a metadata block? Why not just wrap the memory allocation function so it makes that block?
Another way would be to enforce a copy, adding the metadata, at the boundary with fil-C, right? Of course that makes the FFI way less useful...
Even that way will be very difficult to make it work.
The Fil-C side could change the data behind the pointer, which will not reflect on the Rust side due to the copy. Even if you manage to copy back the changes, Fil-C could also persist those pointers in e.g. global memory: at that point you no longer know when it's safe to copy back (or forward) any change.
The only way I can see this work if you cannot pass any Rust-land pointers into Fil-C, but at that point you could also compile the C code to WASM and use the WASM FFI (which has similar restrictions)
WASM is not a substitute for Fil-C.
WASM is not memory safe in the same sense, in particular, heap corruption vulnerabilities still exist, which could eg. allow bypassing auth, leaking private information etc.
C compiled to WASM is still vulnerable to something like OpenSSL Heartbleed. Fil-C (and CHERI) is not.
Check this out: https://github.com/IntegralPilot/rustc_codegen_jvm
While this cannot compile C/C++, it can serve as a fil-C alternative for unsafe pure rust code.
https://news.ycombinator.com/item?id=49284966
That's an interesting project, thanks for the link.
Any reason why you don't use clang's `-fbounds-safety`? It offers ABI compatibility and incremental adoption. The author of Fil-C worked on it also :-)
https://clang.llvm.org/docs/BoundsSafety.html#overview
That is a great feature
But it’s strictly less safe than either Fil-C or Rust since it only protects bounds
This would also mean that you wouldn't need unsafe{} to call into the Fil-C ffi. The majority of unsafe{} in (non pure-rust) cargo dependencies is calling C ffi. Rust + Fil-C is a great match that fills a particular niche, I'd love to see it happen.
How does it prevent multiple mutable pointers?
Multiple mutable pointers are fine. You need to be careful to ensure you don't turn them into multiple coexisting mutable references, though.
Nice pedantry. How does it prevent multiple coexisting mutable references?
It’s not pedantry. Pointers and references are truly different things.
A normal C compiler actually has to worry about a lot of extreme cases.
Imagine a function takes two pointer arguments. What if they point to the same thing? What if one points to the other and is used for a write?
The nature of the code generated varies greatly if these are possibilities…
I'm not sure you need to? Rust references don't exist in C, after all.
That being said I wouldn't be surprised if there were other factors that would weigh against considering FFI calls to Fil-C safe by default (e.g., Fil-C's somewhat looser bounds on what is considered a valid pointer access).
> [...] instead of accidentally linking ordinary C into it
Out of curiosity. If you really needed to, could a language speak both the C ABI and the Fil-C ABI? As I understand Fil-C itself can't do this without having Python-like FFI overhead, but maybe a different compiler implementation could?
Wouldn't this abandon the entire premise that fil-c binaries will crash rather than corrupt?
LLM-written post, but I think the core idea is okay. I would advocate for more opportunities for safety, even if some of the safety is runtime safety rather than "only" Rust's borrow checker.
This doesn't look like LLM slop to me, or even LLM generated.
I wasn't calling it slop at all, a fair bit of it just does look generated to me.
(revised comment)
Hm on first reading I was confused by the extern "fil-c" framing -- that seems to imply a bridge between C and Fil-C, which introduces some nasty language/runtime inter-op issues.
But I like this part
I want a Rust FFI that speaks the Fil-C ABI. ... We could use Rust for compile-time safety and then pay a performance penalty for using C.
Reading this my slop-sense is tingling.
Bullshit. Domen is super legit and an incredible dev. I use his tools daily and nothing he does can be described as slop
> But the result would give us exactly the right incentives.
Say what you say, but there's plenty in that article looking like it came out of an LLM. This could of course be my own paranoia, since there's a lot of slop coming out right now.
[flagged]
Can you please link to specific false statements and explain why they're false?
https://x.com/filpizlo/status/2079244258062766177
is false because of his admission here:
https://news.ycombinator.com/item?id=49097258
If “Rust isn’t [memory safe] (because unsafe)”, and “Zig isn’t (because uaf)”, then Fil-C isn't (because zunsafe_call/zunsafe_fast_call).
I read the thread, and couldn't disagree more.
> If Rust isn’t memory safe (because unsafe), and Zig isn’t (because uaf), then Fil-C isn't (because zunsafe_call/zunsafe_fast_call).
Rust isn’t memory safe (because unsafe) isn't an argument anyone makes, it's that Rust that uses unsafe isn't memory safe!
It's called `unsafe` for a reason dude, not even the Rust people think it's safe.
> Rust isn’t memory safe (because unsafe) isn't an argument anyone makes
This is an argument that Fil-C makes, by choosing a very specific definition of memory safety. It’s even stated explicitly in that tweet linked.
What precisely is that definition of memory safety? I'm not sure I follow.
Probably the shortest statement of Filip's definition is at https://fil-c.org/invisicaps . There's a few other statements around HN and twitter, like this https://news.ycombinator.com/item?id=43195623 , and the recent GISMO talk
But you can see in a number of his public comments statements that Rust is not memory safe by his definition because it has unsafe as an escape hatch. https://x.com/filpizlo/status/2079244258062766177 and https://news.ycombinator.com/item?id=49053608 as some examples.