[HN Gopher] Show HN: I built a Rust crate for running unsafe cod...
       ___________________________________________________________________
        
       Show HN: I built a Rust crate for running unsafe code safely
        
       Author : braxxox
       Score  : 90 points
       Date   : 2025-04-06 13:28 UTC (9 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | djha-skin wrote:
       | 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.
        
         | resonious wrote:
         | The author seems aware of this given their "Run your code 1ms
         | slower" remark in the use cases section.
        
         | VWWHFSfQ wrote:
         | Which pretty much makes this whole thing pointless since a lot
         | of unsafe code exists purely for performance reasons.
        
           | deciduously wrote:
           | Far from all of it, seems like a big leap to write it off as
           | pointless because of one subset.
        
           | tombert wrote:
           | That's what I was thinking; isn't 1 millisecond kind of an
           | eternity in terms of computer performance?
           | 
           | I'm sure there's still value in this project, but I'm not
           | sure I'm versed enough in Rust to know what that is.
        
       | null_investor wrote:
       | 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.
        
         | braxxox wrote:
         | > have no hope of doing better
         | 
         | 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 wrote:
       | 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?
        
         | pjmlp wrote:
         | There are plenty of tools, but they are C and C++ specific.
         | 
         | Starts with Visual C++ analysers, SAL annotations, hardned
         | runtime.
         | 
         | Then commercial tooling like PVS Studio, Parasoft for example.
        
         | wizzwizz4 wrote:
         | 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.
        
           | dijit wrote:
           | that's what I'm doing already, the issue is that unsafe code
           | exists at all.
           | 
           | In order to call the win32 API one must create structs and
           | pass pointers to them into the function call.
           | 
           | sending data is actually quite easy. But reading back data is
           | quite difficult, in some cases you may have a list of
           | something.
           | 
           | Regardless, Rust is not helping me anymore in those bits, and
           | since all of the tools that find memory issues target
           | primarily C++, and rust mangles certain things for C+ +
           | toolchains - I find myself a little bit stuck, I'm not a
           | genius and I'll take all the help I can get.
        
             | wizzwizz4 wrote:
             | The Win32 API's object model is (mostly) compatible with
             | Rust's. Handles play well with OBRM. Does the winsafe crate
             | provide the interfaces you need? https://docs.rs/winsafe/
        
       | teknopaul wrote:
       | Hammer, nut.
       | 
       | Clever trick tho if you are in a bind.
        
       | pjmlp wrote:
       | Rather design the application from the start to use multiple
       | processes, OS IPC and actual OS sandboxing APIs.
       | 
       | Pseudo sandboxing on the fly is an old idea and with its own
       | issues, as proven by classical UNIX approach to launching
       | daemons.
        
         | vlovich123 wrote:
         | What are the sandboxing APIs you'd recommend on Linux, Mac, &
         | Windows? I haven't been able to find any comprehensive
         | references online.
        
           | woodruffw wrote:
           | macOS provides native sandboxing; you can use capabilities at
           | the app level[1] or the sandbox-exec CLI to wrap an existing
           | tool.
           | 
           | For Windows, you probably want WSB[2] or AppContainer
           | isolation[3].
           | 
           | For Linux, the low-level primitives for sandboxing are
           | seccomp and namespaces. You can use tools like Firejail and
           | bubblewrap to wrap individual tool invocations, similar to
           | sandbox-exec on macOS.
           | 
           | [1]: https://developer.apple.com/documentation/xcode/configur
           | ing-...
           | 
           | [2]: https://learn.microsoft.com/en-
           | us/windows/security/applicati...
           | 
           | [3]: https://learn.microsoft.com/en-
           | us/windows/win32/secauthz/app...
        
             | amarshall wrote:
             | Linux also has Landlock now.
             | 
             | macOS sandboxing is notoriously under-documented, has sharp
             | edges, and is nowhere near as expressive as Linux
             | sandboxing.
        
               | anonzzzies wrote:
               | To save a search; https://docs.kernel.org/userspace-
               | api/landlock.html
        
               | woodruffw wrote:
               | Thanks! Landlock is the one I couldn't remember.
               | 
               | Agreed about macOS's sandboxing being under-documented.
        
           | MaulingMonkey wrote:
           | My starting point would be Chromium's documentation, as -
           | presumably - chrome is one of the most widely used and battle
           | tested, user-facing, third party sandboxes running on end
           | user machines.
           | 
           | Windows: https://chromium.googlesource.com/chromium/src/+/mai
           | n/docs/d...
           | 
           | Linux: https://chromium.googlesource.com/chromium/src/+/main/
           | sandbo...
           | 
           | OS X: https://chromium.googlesource.com/chromium/src/+/main/s
           | andbo...
           | 
           | With the caveat that I wouldn't necessairly assume this is
           | the cutting edge at this point, and there might be other
           | resources to invest in for server-side sandboxing involving
           | containers or hypervisors, and that I've only actually
           | engaged with the Windows APIs based on that reading.
           | 
           | I wrote `firehazard` ( https://docs.rs/firehazard/ , https://
           | github.com/MaulingMonkey/firehazard/tree/master/exam... ) to
           | experiment with wrapping the Windows APIs, document edge
           | cases, etc. - although if the long list of warnings in the
           | readme doesn't scare you away, it'll hopefully at least
           | confirm I hesitate to recommend my own code ;)
        
       | woodruffw wrote:
       | 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.
        
         | NoahKAndrews wrote:
         | It's not just that it won't crash, it means that an exploit in
         | the unsafe code won't allow corrupting memory used by the rest
         | of the program
        
           | woodruffw wrote:
           | This is pretty immaterial from an exploit development
           | perspective:
           | 
           | 1. The forked process has a copy of the program state. If I'm
           | trying to steal in-process secrets, I can do it from the
           | forked process.
           | 
           | 2. The forked process is just as privileged as the original
           | process. If I'm trying to obtain code execution, I don't care
           | which process I'm in.
           | 
           | This is why Chrome at al. have full-fledged sandboxes that
           | communicate over restricted IPC; they don't fork the same
           | process and call it a day.
        
         | mirashii wrote:
         | 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.
        
           | woodruffw wrote:
           | Yep. I wanted to start from the high-level point of "safe
           | doesn't mean doesn't crash," but you're right that the
           | technique itself is unsound.
        
             | pclmulqdq wrote:
             | In rust terminology, "safe" actually implies more frequent
             | crashes on untrusted inputs.
        
           | wizzwizz4 wrote:
           | Well, you can close all file descriptors (except the pipe
           | used for sending the return value back to the parent), re-
           | mmap all files with MAP_PRIVATE, and then use
           | SECCOMP_SET_MODE_STRICT to isolate the child process. But at
           | that point, what are you even doing? Probably nothing useful.
           | 
           | If there were a Quick Fix for safety, we'd _probably_ have
           | discovered it by now.
        
             | jmillikin wrote:
             | > use SECCOMP_SET_MODE_STRICT to isolate the child process.
             | But at that       > point, what are you even doing?
             | Probably nothing useful.
             | 
             | The classic example of a fully-seccomp'd subprocess is
             | decoding / decompression. If you want to execute ffmpeg on
             | untrusted user input then seccomp is a sandbox that allows
             | full-power SIMD, and the code has no reason to perform
             | syscalls other than read/write to its input/output stream.
             | 
             | On the client side there's font shaping, PDF rendering,
             | image decoding -- historically rich hunting grounds for
             | browser CVEs.
        
               | Animats wrote:
               | _The classic example of a fully-seccomp 'd subprocess is
               | decoding / decompression._
               | 
               | Yes. I've run JPEG 2000 decoders in a subprocess for that
               | reason.
        
               | WesolyKubeczek wrote:
               | Well, it seems that lately this kind of task wants to
               | write/mmap to a GPU, and poke at font files and interpret
               | them.
        
       | m00dy wrote:
       | >>We call this trick the "fork and free" pattern. It's pretty
       | nifty.
       | 
       | It should be called "fork and see" pattern instead :D
        
       | slashdev wrote:
       | I'd love to know what horrible library / code the author was
       | using where sandboxing it like this seemed like the best
       | alternative.
        
       | destroycom wrote:
       | 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.
        
       | corank wrote:
       | > It forces functions to be memory pure (pure with respect to
       | memory), even if they aren't.
       | 
       | 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?
        
       | wavemode wrote:
       | 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.
        
         | woodruffw wrote:
         | 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.)
        
         | colinrozzi wrote:
         | I think it is basically a garbage collector, just one that
         | operates on a per-function level instead of at the general
         | level of the program
        
       | jesprenj wrote:
       | Why use a pipe to communicate instead of shared memory?
        
         | im3w1l wrote:
         | 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.
        
       | nextaccountic wrote:
       | There _is_ a way to sandbox native code without forking to a new
       | process, and it looks like this
       | 
       | 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
        
         | dmitrygr wrote:
         | You can skip all this nonsense with
         | -fsanitize=undefined
        
           | Georgelemental wrote:
           | Not foolproof, doesn't catch everything.
        
           | cyberax wrote:
           | It won't do anything for data races, for example.
        
         | panstromek wrote:
         | That's actually a pretty clever idea, I never realized you can
         | that. Thanks for sharing.
        
       | loeg wrote:
       | 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.
        
         | krick wrote:
         | 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.
        
       | Svetlitski wrote:
       | 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.
       | 
       | [1] https://man7.org/linux/man-pages/man7/signal-safety.7.html
        
       | syrusakbary wrote:
       | 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).
       | 
       | Awesome work!
        
       | kelnos wrote:
       | 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.
        
       ___________________________________________________________________
       (page generated 2025-04-06 23:00 UTC)