-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: check filetype in path_open
#4947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -890,45 +890,67 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { | |
| } | ||
| let dir_entry = table.get_dir(dirfd)?; | ||
|
|
||
| let oflags = OFlags::from(&oflags); | ||
|
|
||
| // OFlags that are not supported for directories | ||
| let non_dir_oflags = oflags.contains(OFlags::CREATE) | ||
| || oflags.contains(OFlags::EXCLUSIVE) | ||
| || oflags.contains(OFlags::TRUNCATE); | ||
|
|
||
| if oflags.contains(OFlags::DIRECTORY) && non_dir_oflags { | ||
| return Err(Error::invalid_argument().context("incompatible oflag combination")); | ||
| } | ||
|
|
||
| let mut required_caps = DirCaps::OPEN; | ||
| if oflags.contains(OFlags::CREATE) { | ||
| required_caps = required_caps | DirCaps::CREATE_FILE; | ||
| } | ||
| let dir = dir_entry.get_cap(required_caps)?; | ||
|
|
||
| let symlink_follow = dirflags.contains(types::Lookupflags::SYMLINK_FOLLOW); | ||
|
|
||
| let oflags = OFlags::from(&oflags); | ||
| let fdflags = FdFlags::from(fdflags); | ||
| let path = path.as_str()?; | ||
| if oflags.contains(OFlags::DIRECTORY) { | ||
| if oflags.contains(OFlags::CREATE) | ||
| || oflags.contains(OFlags::EXCLUSIVE) | ||
| || oflags.contains(OFlags::TRUNCATE) | ||
| { | ||
| return Err(Error::invalid_argument().context("directory oflags")); | ||
| let path = path.deref(); | ||
| match dir.get_path_filestat(path, symlink_follow).await { | ||
| Ok(Filestat { | ||
| filetype: FileType::Directory, | ||
| .. | ||
| }) if non_dir_oflags => { | ||
| Err(Error::invalid_argument().context("oflags not applicable to a directory")) | ||
| } | ||
| let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base)); | ||
| let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting)); | ||
| let dir = dir_entry.get_cap(DirCaps::OPEN)?; | ||
| let child_dir = dir.open_dir(symlink_follow, path.deref()).await?; | ||
| drop(dir); | ||
| let fd = table.push(Box::new(DirEntry::new( | ||
| dir_caps, file_caps, None, child_dir, | ||
| )))?; | ||
| Ok(types::Fd::from(fd)) | ||
| } else { | ||
| let mut required_caps = DirCaps::OPEN; | ||
| if oflags.contains(OFlags::CREATE) { | ||
| required_caps = required_caps | DirCaps::CREATE_FILE; | ||
|
|
||
| Ok(Filestat { | ||
| filetype: FileType::Directory, | ||
| .. | ||
| }) => { | ||
| let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base)); | ||
| let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting)); | ||
| let child_dir = dir.open_dir(symlink_follow, path).await?; | ||
| drop(dir); | ||
| let fd = table.push(Box::new(DirEntry::new( | ||
| dir_caps, file_caps, None, child_dir, | ||
| )))?; | ||
| Ok(types::Fd::from(fd)) | ||
| } | ||
|
|
||
| let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base)); | ||
| let dir = dir_entry.get_cap(required_caps)?; | ||
| let read = file_caps.contains(FileCaps::READ); | ||
| let write = file_caps.contains(FileCaps::WRITE) | ||
| || file_caps.contains(FileCaps::ALLOCATE) | ||
| || file_caps.contains(FileCaps::FILESTAT_SET_SIZE); | ||
| let file = dir | ||
| .open_file(symlink_follow, path.deref(), oflags, read, write, fdflags) | ||
| .await?; | ||
| drop(dir); | ||
| let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?; | ||
| Ok(types::Fd::from(fd)) | ||
| Ok(_) if oflags.contains(OFlags::DIRECTORY) => { | ||
| Err(Error::not_dir().context("directory requested")) | ||
| } | ||
|
|
||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ideally, I would want to fail here if error is not
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it is reasonable to downcast_ref and match for an ErrorKind::Noent, and if that fails downcast to a std::io::Error with the appropriate check there as well. This is a big janky, and I think if I had to redesign wasi-common I probably wouldn't use a fully dynamic error type and instead make |
||
| _ => { | ||
| let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_base)); | ||
| let read = file_caps.contains(FileCaps::READ); | ||
| let write = file_caps.contains(FileCaps::WRITE) | ||
| || file_caps.contains(FileCaps::ALLOCATE) | ||
| || file_caps.contains(FileCaps::FILESTAT_SET_SIZE); | ||
| let fdflags = FdFlags::from(fdflags); | ||
| let file = dir | ||
| .open_file(symlink_follow, path, oflags, read, write, fdflags) | ||
| .await?; | ||
| drop(dir); | ||
| let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?; | ||
| Ok(types::Fd::from(fd)) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am concerned about this
get_path_filestat, because it does a second path lookup. There's a window for a race condition here, where a directory could be replaced by something which is not a directory. I don't yet have a clear view of the consequences of the open succeeding on an unexpected type, so this is something I want to investigate more.I wonder if it would be possible to instead do something like:
DirEntryentry.Notdir, open it with regularopen.Would that work?
I know that this is redundant on POSIX hosts, and possibly we could optimize things even more there, but this code is currently written to support Windows also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now have an implementation of this here: #4967
It uses the same testcase as this PR, so it fixes the same bug, but does so in a way that doesn't require doing a stat followed by a separate open. There's more room for improvement beyond that, but hopefully that's a good step.