I don't think this meets the definition of "safe" in "safe" Rust: "safe" doesn't just mean "won't crash due to spatial memory errors," it means that the code is in fact spatially and temporally memory safe.
In other words: this won't detect memory unsafety that doesn't result in an abnormal exit or other detectable fault. If I'm writing an exploit, my entire goal is to perform memory corruption without causing a fault; that's why Rust's safety property is much stronger than crash-freeness.
Even better, this library, with its use of unsafe and fork underneath, introduces a whole new class of undefined behavior to a program by providing a safe interface over an unsafe API without actually enforcing the invariants necessary for safety.
In order for the fork() it calls to be safe, it needs to guarantee a bunch of properties of the program that it simply cannot. If this gets used in a multithreaded program that calls malloc, you've got UB. There's a long list of caveats with fork mentioned in some other comments here.
In my view, this is not serious code and should be regarded as a joke. There's no actual value in this type of isolation.
Firefox employs processes for sandboxing but for small components they are not worth the overhead. For those they employed this curious idea: first compile the potentially unsafe code to wasm (any other VM would work), then compile the wasm code to C (using the wasm2c tool). Then use this new C source normally in your program.
All UB in the original code becomes logical bugs in the wasm, that can output incorrect values but not corrupt memory or do things that UB can do. Firefox does this to encapsulate C code, but it can be done with Rust too
This isn't mentioned anywhere on the page, but fork is generally not a great API for these kinds of things. In a multi-threaded application, any code between the fork and exec syscalls should be async-signal-safe. Since the memory is replicated in full at the time of the call, the current state of mutexes is also replicated and if some thread was holding them at the time, there is a risk of a deadlock. A simple print! or anything that allocates memory can lead to a freeze. There's also an issue of user-space buffers, again printing something may write to a user-space buffer that, if not flushed, will be lost after the callback completes.
If you can afford to sacrifice that much performance just to run some potentially unsafe code, then you can probably afford to not be writing Rust in the first place and instead use a garbage-collected language.
This is presumably needed at the integration point, i.e. you already have some C/C++ code being integrated into Rust. So "write it in a different language" is not helpful advice, since all of the code in question is already written.
(However, the technique here is itself not sound, per the other threads.)
This is cool from a theoretical perspective, but `fork()` can be prohibitively expensive, at least on the hot path. This is a cool tool that should be used with care.
This is likely to violate async-signal-safety [1] in any non-trivial program, unless used with extreme care. Running code in between a fork() and an exec() is fraught with peril; it's not hard to end up in a situation where you deadlock because you forked a multi-threaded process where one of the existing threads held a lock at the time of forking, among other hazards.
I know async-signal-safety is particularly important for, you know, signal handlers. But aside from those, and the multi-threading use case you describe, is there another use case where calling non async-signal-safe code from inside this module would lead to issues (that isn't covered in the new limitations)?
I can add another limitation is issues can transpire if the code you run in `callable()` isn't async-signal-safe, but I'd like to offer a few additional examples of gotchas or surprises to point out there.
Forking and this package can be useful if you know that the unsafe code is really unsafe and have no hope of making it better.
But I wouldn't use this often. I'd be willing to bet that you'd lose all performance benefits of using Rust versus something like Python or Ruby that uses forking extensively for parallelism.
this seems like a good place to ask, I don’t write very much unsafe Rust code… but when I do, it’s because I’m calling the Win32 API.
Tools like valgrind do not work on windows, and I am nowhere near smart enough to know the entire layout of memory that should exist.
When using Windows and calling system system functions, there’s a lot of casting involved; to convert wide characters and DWORDS to rust primitives for example. And given that I don’t have a good debugging situation, I’m terrified that I’m corrupting or leaking memory.
does anyone know any good tools that work on windows to help me out here?
The easy solution is, don't call system functions. Instead:
• Work out what you want to do, conceptually.
• Design a safe abstraction that would allow you to do that. (Consult the Win32 API documentation for concepts to use.)
• Implement that abstraction using the Win32 API.
That last step is way easier than trying to use the Win32 API throughout your program, you'll end up with significantly less unsafe code, and if anything does go wrong, it's much easier to fix.
This also means the function might not do what you want, i.e. if it takes a `&mut T` argument, that argument can't actually be mutated, and anything that relies on interior mutability, even if it's not a mut argument, also won't work.
Rust allows memory-impure things, like interior mutability of arguments, so you can get different (i.e. incorrect) results when using this to run otherwise fine rust code.
For example:
fn some_fn(x: &mut i32) {
*x = 2;
}
fn main() {
let mux x = 1;
mem_isolate::execute_in_isolated_process(|| {
some_fn(&mut x);
}).unwrap();
println!("{x}"); // prints '1' even though without 'mem_isolate' this would be 2
}
It's much easier to reason about a child process sending you possibly corrupt objects over a pipe, compared to a child process possibly corrupting shared memory as you are reading it. I've read enough about processor level memory barriers to understand I don't really understand that at all.
Please please please add a big huge warning to your crate that it should never be used in multi-threaded programs. fork() is not safe when there is more than one thread present, as the child process can easily deadlock (or worse) if the fork() happens at just the wrong time with respect to what other threads are doing.
This is super interesting! I would be very curious to see how we can get into even more safety when running WebAssembly in Wasmer with this crate (similar to V8 isolates).
As a joke, it's funny. Obviously you would not want to actually deploy this. I feel like most comments are too quick to criticize using this in prod (don't!) and missing the point.
It's much more problematic how many comments praise it not as a joke. And, honestly, it doesn't seem like it was intended as a joke. It's a legitimately bad idea, that is treated as a good idea by some scary number of people.
woodruffw|11 months ago
In other words: this won't detect memory unsafety that doesn't result in an abnormal exit or other detectable fault. If I'm writing an exploit, my entire goal is to perform memory corruption without causing a fault; that's why Rust's safety property is much stronger than crash-freeness.
mirashii|11 months ago
In order for the fork() it calls to be safe, it needs to guarantee a bunch of properties of the program that it simply cannot. If this gets used in a multithreaded program that calls malloc, you've got UB. There's a long list of caveats with fork mentioned in some other comments here.
In my view, this is not serious code and should be regarded as a joke. There's no actual value in this type of isolation.
braxxox|10 months ago
Let me know what you think, or if you have any additional suggestions.
NoahKAndrews|11 months ago
nextaccountic|11 months ago
https://hacks.mozilla.org/2020/02/securing-firefox-with-weba...
Firefox employs processes for sandboxing but for small components they are not worth the overhead. For those they employed this curious idea: first compile the potentially unsafe code to wasm (any other VM would work), then compile the wasm code to C (using the wasm2c tool). Then use this new C source normally in your program.
All UB in the original code becomes logical bugs in the wasm, that can output incorrect values but not corrupt memory or do things that UB can do. Firefox does this to encapsulate C code, but it can be done with Rust too
panstromek|10 months ago
unknown|11 months ago
[deleted]
dmitrygr|11 months ago
destroycom|11 months ago
pjmlp|11 months ago
Pseudo sandboxing on the fly is an old idea and with its own issues, as proven by classical UNIX approach to launching daemons.
vlovich123|11 months ago
wavemode|11 months ago
colinrozzi|11 months ago
woodruffw|11 months ago
(However, the technique here is itself not sound, per the other threads.)
djha-skin|11 months ago
resonious|11 months ago
VWWHFSfQ|11 months ago
slashdev|11 months ago
Svetlitski|11 months ago
[1] https://man7.org/linux/man-pages/man7/signal-safety.7.html
braxxox|10 months ago
I'm adding a few more limitations in this PR: https://github.com/brannondorsey/mem-isolate/pull/44
I know async-signal-safety is particularly important for, you know, signal handlers. But aside from those, and the multi-threading use case you describe, is there another use case where calling non async-signal-safe code from inside this module would lead to issues (that isn't covered in the new limitations)?
I can add another limitation is issues can transpire if the code you run in `callable()` isn't async-signal-safe, but I'd like to offer a few additional examples of gotchas or surprises to point out there.
null_investor|11 months ago
But I wouldn't use this often. I'd be willing to bet that you'd lose all performance benefits of using Rust versus something like Python or Ruby that uses forking extensively for parallelism.
braxxox|11 months ago
Yeah, this is really the main use case. Its a relatively simple solution when you can't do any better.
I think that's particularly helpful when you're invoking code you don't control, like calling into a some arbitrary C library.
dijit|11 months ago
Tools like valgrind do not work on windows, and I am nowhere near smart enough to know the entire layout of memory that should exist.
When using Windows and calling system system functions, there’s a lot of casting involved; to convert wide characters and DWORDS to rust primitives for example. And given that I don’t have a good debugging situation, I’m terrified that I’m corrupting or leaking memory.
does anyone know any good tools that work on windows to help me out here?
wizzwizz4|11 months ago
• Work out what you want to do, conceptually.
• Design a safe abstraction that would allow you to do that. (Consult the Win32 API documentation for concepts to use.)
• Implement that abstraction using the Win32 API.
That last step is way easier than trying to use the Win32 API throughout your program, you'll end up with significantly less unsafe code, and if anything does go wrong, it's much easier to fix.
pjmlp|11 months ago
Starts with Visual C++ analysers, SAL annotations, hardned runtime.
Then commercial tooling like PVS Studio, Parasoft for example.
bdhcuidbebe|10 months ago
Check out windows-rs instead.
https://github.com/microsoft/windows-rs
TheDong|10 months ago
Rust allows memory-impure things, like interior mutability of arguments, so you can get different (i.e. incorrect) results when using this to run otherwise fine rust code.
For example:
corank|11 months ago
What if the unsafe code is not supposed to be pure but mutates some memory? For example, does this allow implementing a doubly-linked list?
jesprenj|11 months ago
im3w1l|11 months ago
kelnos|10 months ago
braxxox|10 months ago
Let me know if you think this wording could be improved or I'm missing any details.
syrusakbary|10 months ago
Awesome work!
cryptonector|10 months ago
m00dy|11 months ago
It should be called "fork and see" pattern instead :D
chuckadams|10 months ago
teknopaul|11 months ago
Clever trick tho if you are in a bind.
loeg|11 months ago
krick|10 months ago