rust-lang / libs-team

The home of the library team
Apache License 2.0
116 stars 18 forks source link

Add openat/unlinkat/etc. abstractions to ReadDir/DirEntry/OpenOptions #259

Closed the8472 closed 7 months ago

the8472 commented 1 year ago

Proposal

Problem statement

Current std::fs APIs lead to code that's vulnerable to TOCTOU issues because performing any operation relative to a directory currently has to be done by composing the directory's path and the relative path. This happens because the directory structure can change under the user's nose between enumerating the directory entries and then trying to open/create/delete/... a file in that directory.

cap_std::fs::Dir by @sunfishcode already solves this.

Motivating examples or use cases

CVE-2022-21658, CVE-2018-15664, CVE-2021-20316 and similar symlink-TOCTOUs in many applications.

Solution sketch

This is more or less adopting fs::{Dir, DirEntry} APIs or a subset thereof into std. It may be possible to add the Dir methods directly to ReadDir instead.

impl Dir/ReadDir {
     pub fn open<P: AsRef<Path>>(&self, path: P) -> Result<File>
     /// This could be put on OpenOptions instead
     pub fn open_with<P: AsRef<Path>>(&self, path: P, options: &OpenOptions) -> Result<File>
     pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
     pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to_dir: &Self, to: Q) -> Result<()>
     pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()>
     pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()>
     pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q)

     /// ... more convenience methods
}

impl DirEntry {
    pub fn open(&self) -> Result<File>
    /// This could be put on OpenOptions instead
    pub fn open_with(&self, options: &OpenOptions) -> Result<File>
    pub fn remove_file(&self) -> Result<()>
    pub fn remove_dir(&self) -> Result<()>
}

The implementation work can be done piecemeal:

Open Questions

How do we deal with windows? NtCreateFile is considered internal. We're already using it to make remove_dir_all robust against races but in principle we could revert to simpler implementation. Adding public APIs that require it would be a foward-compatibility hazard.

How do we handle platforms that lack some of the necessary APIs? Emulate them via path manipulation (which reintroduces the TOCTOU) or return Unsupported errors?

Alternatives

Status Quo

Crates handling this already exist, we can tell users to use them in security-sensitive contexts.

IO-safety APIs for ReadDir

This would simplify using std::ReadDir as a directory handle and then passing it to crates like cap_std or openat

That would adding From<OwnedFd>, Into<OwnedFd>, AsFd (and windows equivalents) to ReadDir.

Issues:

Links and related work

ChrisDenton commented 1 year ago

How do we deal with windows? NtCreateFile is considered internal. We're already using it to make remove_dir_all robust against races but in principle we could revert to simpler implementation. Adding public APIs that require it would be a foward-compatibility hazard.

I've been reassured that there is not such a hazard to using NtCreateFile. The Calling Internal APIs is mostly outdated (e.g. ntdll.lib is shipped with the normal Windows SDK, so there is "an associated import library"). The hazard is only that with CreateFileW you may get new features without recompiling, whereas with NtCreateFile they may have to be implemented manually (e.g. by setting a new flag, etc).

BurntSushi commented 1 year ago

SGTM.

I'm also interested in this from the perspective of avoiding lots of tiny little allocations when crawling a large directory tree. (It's as hand wavy as it sounds.)

ChrisDenton commented 1 year ago

This has been accepted. Should this issue be closed now? Further discussion can happen on the tracking issue.

thomcc commented 1 year ago

I think without https://github.com/rust-lang/rust/pull/104385 most of these won't be implementable on older macOS, FYI. (Mostly providing this as a link for further motivation of that change)

sunfishcode commented 1 year ago

Where is the tracking issue?

thomcc commented 1 year ago

The tracking issue for what?

sunfishcode commented 1 year ago

@ChrisDenton mentioned that further discussion should happen on the tracking issue.

thomcc commented 1 year ago

Oh, yeah. Good question, I missed that too.

ChrisDenton commented 1 year ago

I mean a tracking issue should be created. Not that there is one already. The process is ACP -> Tracking issues -> (implementation, refinement, etc).

the8472 commented 7 months ago

tracking issue: https://github.com/rust-lang/rust/issues/120426