Skip to content
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

Use NtCreateFile to implement open_unchecked on Windows. #293

Merged
merged 1 commit into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cap-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ winx = "0.35.0"
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.45.0"
features = [
"Win32_Storage_FileSystem",
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_Kernel",
"Win32_System_SystemServices",
"Win32_System_WindowsProgramming",
]
31 changes: 28 additions & 3 deletions cap-primitives/src/fs/manually/canonicalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,40 @@ pub(crate) fn canonicalize_with(
let mut canonical_path = PathBuf::new();
let start = MaybeOwnedFile::borrowed(start);

if let Err(e) = internal_open(
match internal_open(
start,
path,
canonicalize_options().follow(follow),
&mut symlink_count,
Some(&mut canonical_path),
) {
if canonical_path.as_os_str().is_empty() {
return Err(e);
// If the open succeeded, we got our path.
Ok(_) => (),

// If it failed due to an invalid argument or filename, report it.
Err(err) if err.kind() == io::ErrorKind::InvalidInput => {
return Err(err);
}
#[cfg(io_error_more)]
Err(err) if err.kind() == io::ErrorKind::InvalidFilename => {
return Err(err);
}
#[cfg(windows)]
Err(err)
if err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_INVALID_NAME as _)
|| err.raw_os_error()
== Some(windows_sys::Win32::Foundation::ERROR_DIRECTORY as _) =>
{
return Err(err);
}

// For any other error, like permission denied, it's ok as long as
// we got our path.
Err(err) => {
if canonical_path.as_os_str().is_empty() {
return Err(err);
}
}
}

Expand Down
1 change: 1 addition & 0 deletions cap-primitives/src/fs/maybe_owned_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ impl<'borrow> MaybeOwnedFile<'borrow> {

/// Produce an owned `File`. This uses `open` on "." if needed to convert a
/// borrowed `File` to an owned one.
#[cfg_attr(windows, allow(dead_code))]
pub(super) fn into_file(self, options: &OpenOptions) -> io::Result<fs::File> {
match self.inner {
MaybeOwned::Owned(file) => Ok(file),
Expand Down
270 changes: 270 additions & 0 deletions cap-primitives/src/windows/fs/create_file_at_w.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
#![allow(unsafe_code)]

use std::convert::TryInto;
use std::mem;
use std::os::windows::io::HandleOrInvalid;
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::{
RtlNtStatusToDosError, SetLastError, ERROR_ALREADY_EXISTS, ERROR_FILE_EXISTS,
ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_NOT_SUPPORTED, HANDLE, INVALID_HANDLE_VALUE,
STATUS_OBJECT_NAME_COLLISION, STATUS_PENDING, STATUS_SUCCESS, SUCCESS, UNICODE_STRING,
};
use windows_sys::Win32::Security::{
SECURITY_ATTRIBUTES, SECURITY_DYNAMIC_TRACKING, SECURITY_QUALITY_OF_SERVICE,
SECURITY_STATIC_TRACKING,
};
use windows_sys::Win32::Storage::FileSystem::{
NtCreateFile, CREATE_ALWAYS, CREATE_NEW, DELETE, FILE_ACCESS_FLAGS, FILE_ATTRIBUTE_ARCHIVE,
FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE, FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_EA,
FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_INTEGRITY_STREAM,
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, FILE_ATTRIBUTE_NO_SCRUB_DATA,
FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED, FILE_ATTRIBUTE_READONLY,
FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN,
FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM,
FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL, FILE_CREATE,
FILE_CREATION_DISPOSITION, FILE_FLAGS_AND_ATTRIBUTES, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_DELETE_ON_CLOSE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OVERLAPPED, FILE_FLAG_POSIX_SEMANTICS,
FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN, FILE_FLAG_SESSION_AWARE,
FILE_FLAG_WRITE_THROUGH, FILE_OPEN, FILE_OPEN_IF, FILE_OVERWRITE, FILE_OVERWRITE_IF,
FILE_READ_ATTRIBUTES, FILE_SHARE_MODE, OPEN_ALWAYS, OPEN_EXISTING, SECURITY_CONTEXT_TRACKING,
SECURITY_EFFECTIVE_ONLY, SECURITY_SQOS_PRESENT, SYNCHRONIZE, TRUNCATE_EXISTING,
};
use windows_sys::Win32::System::Kernel::{OBJ_CASE_INSENSITIVE, OBJ_INHERIT};
use windows_sys::Win32::System::SystemServices::{GENERIC_ALL, GENERIC_READ, GENERIC_WRITE};
use windows_sys::Win32::System::WindowsProgramming::{
FILE_DELETE_ON_CLOSE, FILE_NON_DIRECTORY_FILE, FILE_NO_INTERMEDIATE_BUFFERING, FILE_OPENED,
FILE_OPEN_FOR_BACKUP_INTENT, FILE_OPEN_NO_RECALL, FILE_OPEN_REMOTE_INSTANCE,
FILE_OPEN_REPARSE_POINT, FILE_OVERWRITTEN, FILE_RANDOM_ACCESS, FILE_SEQUENTIAL_ONLY,
FILE_SYNCHRONOUS_IO_NONALERT, FILE_WRITE_THROUGH, IO_STATUS_BLOCK, OBJECT_ATTRIBUTES,
};

// All currently known `FILE_ATTRIBUTE_*` constants, according to
// windows-sys' documentation.
const FILE_ATTRIBUTE_VALID_FLAGS: FILE_FLAGS_AND_ATTRIBUTES = FILE_ATTRIBUTE_EA
| FILE_ATTRIBUTE_DEVICE
| FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_NORMAL
| FILE_ATTRIBUTE_PINNED
| FILE_ATTRIBUTE_SYSTEM
| FILE_ATTRIBUTE_ARCHIVE
| FILE_ATTRIBUTE_OFFLINE
| FILE_ATTRIBUTE_VIRTUAL
| FILE_ATTRIBUTE_READONLY
| FILE_ATTRIBUTE_UNPINNED
| FILE_ATTRIBUTE_DIRECTORY
| FILE_ATTRIBUTE_ENCRYPTED
| FILE_ATTRIBUTE_TEMPORARY
| FILE_ATTRIBUTE_COMPRESSED
| FILE_ATTRIBUTE_SPARSE_FILE
| FILE_ATTRIBUTE_NO_SCRUB_DATA
| FILE_ATTRIBUTE_REPARSE_POINT
| FILE_ATTRIBUTE_RECALL_ON_OPEN
| FILE_ATTRIBUTE_INTEGRITY_STREAM
| FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
| FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS;

/// Like Windows' `CreateFileW`, but takes a `dir` argument to use as the
/// root directory.
#[allow(non_snake_case)]
pub unsafe fn CreateFileAtW(
dir: HANDLE,
lpfilename: &[u16],
dwdesiredaccess: FILE_ACCESS_FLAGS,
dwsharemode: FILE_SHARE_MODE,
lpsecurityattributes: *const SECURITY_ATTRIBUTES,
dwcreationdisposition: FILE_CREATION_DISPOSITION,
dwflagsandattributes: FILE_FLAGS_AND_ATTRIBUTES,
htemplatefile: HANDLE,
) -> HandleOrInvalid {
// Absolute paths are not yet implemented here.
//
// It seems like `NtCreatePath` needs the apparently NT-internal `\??\`
// prefix prepended to absolute paths. It's possible it needs other
// path transforms as well. `RtlDosPathNameToNtPathName_U` may be a
// function that does these things, though it's not available in
// windows-sys and not documented, though one can find
// [unofficial blog posts], though even they say things like "I`m
// sorry that I cannot give more details on these functions".
//
// [unofficial blog posts]: https://mecanik.dev/en/posts/convert-dos-and-nt-paths-using-rtl-functions/
assert!(dir != 0);

// Extended attributes are not implemented yet.
if htemplatefile != 0 {
SetLastError(ERROR_NOT_SUPPORTED);
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
}

// Convert `dwcreationdisposition` to the `createdisposition` argument
// to `NtCreateFile`. Do this before converting `lpfilename` so that
// we can return early on failure.
let createdisposition = match dwcreationdisposition {
CREATE_NEW => FILE_CREATE,
CREATE_ALWAYS => FILE_OVERWRITE_IF,
OPEN_EXISTING => FILE_OPEN,
OPEN_ALWAYS => FILE_OPEN_IF,
TRUNCATE_EXISTING => FILE_OVERWRITE,
_ => {
SetLastError(ERROR_INVALID_PARAMETER);
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
}
};

// Convert `lpfilename` to a `UNICODE_STRING`.
let byte_length = lpfilename.len() * mem::size_of::<u16>();
let length: u16 = match byte_length.try_into() {
Ok(length) => length,
Err(_) => {
SetLastError(ERROR_INVALID_NAME);
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
}
};
let mut unicode_string = UNICODE_STRING {
Buffer: lpfilename.as_ptr() as *mut u16,
Length: length,
MaximumLength: length,
};

let mut handle = INVALID_HANDLE_VALUE;

// Convert `dwdesiredaccess` and `dwflagsandattributes` to the
// `desiredaccess` argument to `NtCreateFile`.
let mut desiredaccess = dwdesiredaccess | SYNCHRONIZE | FILE_READ_ATTRIBUTES;
if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 {
desiredaccess |= DELETE;
}

// Compute `objectattributes`' `Attributes` field. Case-insensitive is
// the expected behavior on Windows.
let mut attributes = 0;
if dwflagsandattributes & FILE_FLAG_POSIX_SEMANTICS != 0 {
attributes |= OBJ_CASE_INSENSITIVE as u32;
};
if !lpsecurityattributes.is_null() && (*lpsecurityattributes).bInheritHandle != 0 {
attributes |= OBJ_INHERIT as u32;
}

// Compute the `objectattributes` argument to `NtCreateFile`.
let mut objectattributes = mem::zeroed::<OBJECT_ATTRIBUTES>();
objectattributes.Length = mem::size_of::<OBJECT_ATTRIBUTES>() as _;
objectattributes.RootDirectory = dir;
objectattributes.ObjectName = &mut unicode_string;
objectattributes.Attributes = attributes;
if !lpsecurityattributes.is_null() {
objectattributes.SecurityDescriptor = (*lpsecurityattributes).lpSecurityDescriptor;
}

// If needed, set `objectattributes`' `SecurityQualityOfService` field.
let mut qos;
if dwflagsandattributes & SECURITY_SQOS_PRESENT != 0 {
qos = mem::zeroed::<SECURITY_QUALITY_OF_SERVICE>();
qos.Length = mem::size_of::<SECURITY_QUALITY_OF_SERVICE>() as _;
qos.ImpersonationLevel = ((dwflagsandattributes >> 16) & 0x3) as _;
qos.ContextTrackingMode = if dwflagsandattributes & SECURITY_CONTEXT_TRACKING != 0 {
SECURITY_DYNAMIC_TRACKING
} else {
SECURITY_STATIC_TRACKING
};
qos.EffectiveOnly = ((dwflagsandattributes & SECURITY_EFFECTIVE_ONLY) != 0) as _;

objectattributes.SecurityQualityOfService =
(&mut qos as *mut SECURITY_QUALITY_OF_SERVICE).cast();
}

let mut iostatusblock = mem::zeroed::<IO_STATUS_BLOCK>();
iostatusblock.Anonymous.Status = STATUS_PENDING;

// Compute the `fileattributes` argument to `NtCreateFile`. Mask off
// unrecognized flags.
let mut fileattributes = dwflagsandattributes & FILE_ATTRIBUTE_VALID_FLAGS;
if fileattributes == 0 {
fileattributes = FILE_ATTRIBUTE_NORMAL;
}

// Compute the `createoptions` argument to `NtCreateFile`.
let mut createoptions = 0;
if dwflagsandattributes & FILE_FLAG_BACKUP_SEMANTICS == 0 {
createoptions |= FILE_NON_DIRECTORY_FILE;
} else {
if dwdesiredaccess & GENERIC_ALL != 0 {
createoptions |= FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REMOTE_INSTANCE;
} else {
if dwdesiredaccess & GENERIC_READ != 0 {
createoptions |= FILE_OPEN_FOR_BACKUP_INTENT;
}
if dwdesiredaccess & GENERIC_WRITE != 0 {
createoptions |= FILE_OPEN_REMOTE_INSTANCE;
}
}
}
if dwflagsandattributes & FILE_FLAG_DELETE_ON_CLOSE != 0 {
createoptions |= FILE_DELETE_ON_CLOSE;
}
if dwflagsandattributes & FILE_FLAG_NO_BUFFERING != 0 {
createoptions |= FILE_NO_INTERMEDIATE_BUFFERING;
}
if dwflagsandattributes & FILE_FLAG_OPEN_NO_RECALL != 0 {
createoptions |= FILE_OPEN_NO_RECALL;
}
if dwflagsandattributes & FILE_FLAG_OPEN_REPARSE_POINT != 0 {
createoptions |= FILE_OPEN_REPARSE_POINT;
}
if dwflagsandattributes & FILE_FLAG_OVERLAPPED == 0 {
createoptions |= FILE_SYNCHRONOUS_IO_NONALERT;
}
// FILE_FLAG_POSIX_SEMANTICS is handled above.
if dwflagsandattributes & FILE_FLAG_RANDOM_ACCESS != 0 {
createoptions |= FILE_RANDOM_ACCESS;
}
if dwflagsandattributes & FILE_FLAG_SESSION_AWARE != 0 {
// TODO: How should we handle FILE_FLAG_SESSION_AWARE?
SetLastError(ERROR_NOT_SUPPORTED);
return HandleOrInvalid::from_raw_handle(INVALID_HANDLE_VALUE as _);
}
if dwflagsandattributes & FILE_FLAG_SEQUENTIAL_SCAN != 0 {
createoptions |= FILE_SEQUENTIAL_ONLY;
}
if dwflagsandattributes & FILE_FLAG_WRITE_THROUGH != 0 {
createoptions |= FILE_WRITE_THROUGH;
}

// Ok, we have what we need to call `NtCreateFile` now!
let status = NtCreateFile(
&mut handle,
desiredaccess,
&mut objectattributes,
&mut iostatusblock,
null_mut(),
fileattributes,
dwsharemode,
createdisposition,
createoptions,
null_mut(),
0,
);

// Check for errors.
if status != STATUS_SUCCESS {
handle = INVALID_HANDLE_VALUE;
if status == STATUS_OBJECT_NAME_COLLISION {
SetLastError(ERROR_FILE_EXISTS);
} else {
SetLastError(RtlNtStatusToDosError(status));
}
} else if (dwcreationdisposition == CREATE_ALWAYS
&& iostatusblock.Information == FILE_OVERWRITTEN as _)
|| (dwcreationdisposition == OPEN_ALWAYS && iostatusblock.Information == FILE_OPENED as _)
{
// Set `ERROR_ALREADY_EXISTS` according to the table for
// `dwCreationDisposition` in the [`CreateFileW` docs].
//
// [`CreateFileW` docs]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
SetLastError(ERROR_ALREADY_EXISTS);
} else {
// Otherwise indicate that we succeeded.
SetLastError(SUCCESS);
}

HandleOrInvalid::from_raw_handle(handle as _)
}
2 changes: 1 addition & 1 deletion cap-primitives/src/windows/fs/dir_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ pub(crate) fn open_ambient_dir_impl(path: &Path, _: AmbientAuthority) -> io::Res
.read(true)
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE)
.open(&path)?;
.open(path)?;

// Require a directory. It may seem possible to eliminate this `metadata()`
// call by appending a slash to the path before opening it so that the OS
Expand Down
2 changes: 1 addition & 1 deletion cap-primitives/src/windows/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod copy;
mod create_dir_unchecked;
mod create_file_at_w;
mod dir_entry_inner;
mod dir_options_ext;
mod dir_utils;
Expand Down Expand Up @@ -74,7 +75,6 @@ pub(crate) use symlink_unchecked::*;
// <https://docs.microsoft.com/en-us/windows/win32/fileio/reparse-points>
pub(crate) const MAX_SYMLINK_EXPANSIONS: u8 = 63;

#[cfg(any(test, racy_asserts))]
pub(crate) fn file_path(file: &std::fs::File) -> Option<std::path::PathBuf> {
get_path::get_path(file).ok()
}
Expand Down
Loading