4 ms·
I actually find that hole in Rust really bad and would be happy if Rust's runtime closed this loophole. Not sure why people think "hah! You'd have to disable
by Jweb_Guru 6y ago
I actually find that hole in Rust really bad and would be happy if Rust's runtime closed this loophole. Not sure why people think "hah! You'd have to disable being able to write bytes to random memory addresses, too!" is an argument ad absurdum, the fact that you can do that is terrible and dangerous.
- geofft 6y agoHow would you close that loophole, exactly? Would you prevent running a subprocess that writes to /proc/$pid/mem? Would you prevent running all subprocesses, or somehow only those that edit /proc/$pid/mem? Like, I get that you want the loophole to not exist, but does there exist a possible world in which Rust closed the loophole and you would actually be happy about it? The fact that you can write bytes to random memory addresses if you intentionally set out to do so is not what Rust's safety system is about. Rust's safety system is a tool for the programmer to build working programs. It is not an end goal in itself - hence why unsafe exists and why so much of the standard library and so many third party crates use safe. It's a tool you have to use correctly, sure, but it's only a tool. We don't avoid memory corruption and data races because they are inherently bad - we avoid them because they cause bad things to happen, like getting the wrong results out of a computation or allowing attackers to subvert program control flow. If you want to 100% guarantee that a program does not bad things, you're looking for a sandbox. Compile your code to wasm, or run it in a VM, or something, and then even if you have memory corruption or data races, it can't escape the sandbox and read the rest of your files. Or if you want to 100% guarantee that your program has no internal bugs, write it in Coq or ATS or something. Rust has a specific goal, and it does not involve being either a sandbox or a theorem prover.
- Jweb_Guru 6y agoI mean, ideally Linux would remove the interface completely, as it's not even needed by the original application that proposed it and is a constant lurking potential security flaw. But moving to more realistic measures... AFAIK, it is actually possible to prevent /proc/$pid/mem from being updated from an individual process, and Rust's runtime could in fact ensure this on startup, at least on Linux. I was also pretty sure (but would have to look this up--it's a really crappy interface) that in general other processes cannot invoke this interface under ordinary privileges, which I think is a fairly reasonable compromise in terms of safety. Not really sure what the point of the rest of your post is. I'm quite confident I have never argued that libraries should not use unsafe, or that Rust should prevent all bugs. I do think that Rust's standard library should not expose tools that can lead directly to memory unsafety, in the same process, when only safe code is used, under some "reasonable" model of the environment in which it runs (which might include, for example, a working MMU, and process isolation boundaries). That seems like a pretty reasonable standard for a language calling itself safe, and even if we can't quite reach it, we should strive for it. I could even buy the argument that exposing an interface like /proc/$pid/mem/ is not "reasonable" and thus discount this unsafety (well, I could if it weren't the default on all Linux distributions I'm aware of). To instead use cases like this as an argument for "pragmatically" giving up memory safety in situations where it isn't actually forced upon us by the underlying execution environment, isn't at all convincing to me.
- geofft 6y agoWell, /proc/$pid/mem is only one way of doing it. Here are a number of others: - Use the debugger interface (ptrace on Linux, but most OSes have some form of debugger). Should Rust programs block themselves from being debugged, iTunes-style? - By editing the binary files on disk that are mapped into the process space. Should Rust binaries refuse to run if they are writable by the current user? - By loading a kernel module. Should Rust binaries drop CAP_SYS_MODULE from the bounding set, just in case someone wants to load kernel modules from a subprocess? And so forth. Now, yes, these cases are getting increasingly silly, but I'm having trouble figuring out what the precise distinction is between "It is bad to release a crate that permits you, in safe code, to call a function that might be buggy" and "It is bad to release a crate that permits you, in safe code, to run SSH, because you could SSH to your own hypervisor and modify the current process's memory." The best distinction I can think of is that a programmer who thinks they are writing code to do task X should not unintentionally also be writing code to cause problem Y. This is the fundamental problem with unsafe interfaces - it's not that, say, gets() doesn't work, it's that it's literally impossible to use gets() without also allowing whoever's providing input to overflow a buffer. If you set out to modify your own memory, well, you're intending to do so, so I don't think that the safe/unsafe distinction makes much sense there. I suspect an argument could be made that AutoCXX's bindings should be marked unsafe, but I think that argument needs to be more complicated than "producing any sort of automated safe bindings is equivalent to the halting problem". > Rust's standard library should not expose tools that can lead directly to memory unsafety when only safe code is used, under some "reasonable" model of the environment in which it runs (which might include, for example, a working MMU, and process isolation boundaries) I do agree with this, I think - I just think that "directly" / "reasonable" excludes stuff like /proc/self/mem.
- Jweb_Guru 6y agoThe difference is that all of the other scenarios you mentioned don't work by default under standard execution privileges, and/or require additional setup beyond just executing the program. /proc/$pid/mem just works with any program under Linux. That's a pretty fundamental difference. Like I said, if it weren't the default behavior on Linux, I wouldn't care. But as it is I don't think you can meaningfully say the default Linux environment is "not a reasonable environment" for running secure software while I am happy to say this about, say, running with root privileges, or having a debugger inserted, or loading non-default kernel modules. The idea that unsafe is supposed to only cover "deliberate" invocations is kind of silly, IMO. That is not what unsafe means. A function called "get_unchecked_index_this_requires_a_bounds_check_be_careful" still needs to be marked unsafe. But even if somehow this distinction could be made meaningful, this turns an accidental bug that lets you manipulate the file path into full blown arbitrary code execution.