How unsafe is mmap, for a database type lib?

Then probably std::process::Command::spawn must either panic (or return Result::Err) if run as root / admin, or be unsafe too. :face_with_diagonal_mouth:

At least on Linux, yes. Probably, the check should return Err for any path that leads to a procfs mount.

1 Like

Yes, there is an issue to be considered here, too.

However, you do realize that these examples are much more contrived and less realistic than someone accidentally overwriting a perfectly normal database file, right?

IOW, you haven't demonstrated that the requirement of these APIs being unsafe, fallible, or otherwise constrained would be absurd. After all, overwriting a file by accident can happen even in a normal user's terminal if s/he makes a typo and cps/mvs/cats to the wrong place. In contrast, people don't accidentally poke around in /proc/self/mem – they usually have a "good" reason to do so.

Explicitly out of scope of the language.

7 Likes

Yes, and I agreed with you in the concrete case from the very beginning.

(Just to clarify.)

Edit: To be fair, I later said that I wasn't "totally sure" if it's unsound to not do it. And I'm still not 100% sure.

I merely tried to find corner cases for the fun of it. And I'm not sure if std::process::Command::spawn should be unsafe (and I think it would need to be, if I follow some of the brought up considerations in this thread).

I understand that this isn’t going to change, but I still think that it’s the wrong call. /proc and /dev are part of the kernel api surface, and as such are as dangerous as any other FFI mechanism. They can also be activated “accidentally” via user input when the program expects to be doing normal filesystem operations; there should be some protection against this happening, even if it’s not perfect.

1 Like

That is very sad because it's a kind of low-hanging fruit. Blacklisting a finite list of privileged files, along with path normalization and symlink resolution, would be a huge improvement over the status quo.

Also, the documentation is simply factually wrong, as you pointed out here:

which is true, whereas the linked documentation says:

Rust’s safety guarantees only cover what the program itself can do, and not what entities outside the program can do to it. /proc/self/mem is considered to be such an external entity

which is obviously false if std::fs and std::io can access /proc/self from within the same pure Rust process as every other std API.

1 Like

the main reason /proc/self/mem is not unsafe is because it's impossible to check all external effects and checking it incurs nontrivial overhead for nothing. There are no issues that arise from /proc/self/mem. Rust does not protect you from going out of your way to cause issues if you want that badly. /proc/self/mem is not a problem and therefore does not need a solution. I don't know where it would be a "huge" improvement

1 Like

But fs::write() within the same process is not an external effect in the first place.

And blacklisting a finite number of paths is not an attempt to "check all external effects" anyway.

I advise you to actually read the thread.

Oh, it absolutely is, as demonstrated by literally this entire thread.

It wouldn't allow the kind of unsoundness that is safe_transmute(), for one?

It wouldn't allow the kind of unsoundness that is safe_transmute(), for one?

you should take a look at the windows implementation of that function

I would like to question whether

  • safe_transmute is unsound,
  • std is unsound,
  • Linux and Windows are not behaving according to their own specification.

I do think safe_transmute can't be unsound, because it is safe.

Does that mean that std must be unsound or Linux and Windows don't behave according to their own specification (here)?

I don't think so. Because there is a fourth option: None of the above three is true. This is if we define unsoundness as the ability to cause UB from within safe Rust, and if we consider certain API's as being out of scope. This could cover both std::process::Command::spawn, std::fs::File::open, and possibly also somememorymappeddb::open (assuming that exclusive file locking is available; this is more a hypothetical/theoretical example).

Further questions:

  • Is there a clear definition / formal specification of who's the culprit?
  • Irregardless of how things are currently defined, what's making most sense and/or is best to help us write bug-free programs?
1 Like

That's incorrect. You are confusing "safe" with "sound":

  • in Rust parlance, "safe" is simply "not unsafe";
  • whereas "sound" is "can't cause UB from safe code";
  • therefore "unsound" is "safe but causes UB".

For example, the following function is safe and unsound:

fn safe_transmute(x: NotString) -> String {
    unsafe { core::mem::transmute::<NotString, String>(x) }
}

struct NotString {
    foo: usize,
    bar: usize,
    qux: usize,
}

It dumps core when actually used.

Strictly speaking, safe_transmute and std are unsound. Linux exposing /proc/self/mem is absolutely expected, and is something that a safe language must deal with.

2 Likes

That function you give as an example uses unsafe.

What I meant above with "I do think safe_transmute can't be unsound, because it is safe." is that it doesn't use unsafe, so it can't be unsound.

1 Like

But that's not what matters! What matters is that calling the function is not unsafe, so the caller doesn't have to write unsafe, yet the call can cause UB!

This is false for exactly the same reason; the counterexample is the very behavior that my example exhibits. In the playground above, main doesn't use unsafe because it calls an incorrect (i.e., unsound) function that "looks" safe – yet it dumps core.

Likewise, safe_transmute() calls only functions that look safe (writes to a file), yet it can cause UB. Thus, there must be unsoundess.

1 Like

The need for all of this separation boils down a single fundamental property of Safe Rust, the soundness property:

No matter what, Safe Rust can't cause Undefined Behavior.

(Rustonomicon)

help us write bug-free programs?

Are you saying there are real world cases of people introducing bugs by accidentally opening and writing to /proc/self/mem?

All cases I know about are fully on purpose, specifically designed to circumvent Rust's usual safety guarantees. Blocking certain paths might be able to prevent accidents, but it will not prevent code from being specifically designed around the safety guards we put in place. It'd just mean that it'd use something more complicated than /proc/self/mem, see for example the Windows implementation of totally_safe_transmute.

Note that the issue isn't specific to the special /dev and /proc file systems. In certain (less common) cases it's also possible to write to your own executable file or to a library file that is loaded or about to be loaded, causing UB. Overwriting certain kernel/system settings in /etc/ can also result in disasters.

I would like to question whether

  • safe_transmute is unsound,
  • std is unsound,
  • Linux and Windows are not behaving according to their own specification.

There is no formal rule set that leads to a simple answer to this question. Linux and Windows don't have a specification that matches the concept of safety and soundness we have in Rust. The theoretical Rust rules are designed to be consistent and fully sound, but as soon as things outside the formal model like files and processes get involved, we must see Rust as just a useful tool that prevents real world bugs, not as a perfect theoretical sandbox where nothing can possibly go wrong.

The simple answer to your question is that totally_safe_transmute is unsound. It is extremely obvious that it is designed exactly for that; I can't imagine anyone genuinely thinking that that crate is a sound way to perform conversions. But unless we fully model all of the environment (processes, files, networking, ...) outside of a Rust program, to include it in a theoretical model, there is simply no set of formal rules that will be able to answer your question, which means we'll just have to fall back to human judgement.

Personally, I care much more about preventing actual accidents than preventing malice/jokes/art. If there are real world accidental bugs caused by the issue, I'd love to find out what we can do about that. But otherwise I'm not sure if this is worth spending energy on.

10 Likes

Then does the safe_transmute() example simply not exist?

It does exist, but it is not unsound, because it's not the responsible part that causes the UB. (Again, a matter of causality.)

I think this is contradictory to the "soundness property" rule of the Rustonomicon. But maybe I'm confusing something.

I think we have a language barrier here.

I'm not saying that safe_transmute() is the one responsible for the whole thing. But it does cause UB, therefore it can't possibly be sound.

You might say that the root cause is std::io/std::fs being unsound, and that you consider only that unsound, and not the obviously tongue-in-cheek caller, transitively. With that, I agree.