fix(buzz-private-file): refuse volumes that cannot store ACLs

CreateFileW ignores the security descriptor on file systems without
ACL support, such as FAT and exFAT, so create_new would hand back an
unprotected file and the token cache would be written into it. Check
FILE_PERSISTENT_ACLS on the handle in create_new and open_owned and
fail if it is missing.

Signed-off-by: Pinky <5f5ab050ec58ae208332edd544ebf705221e24c1b86d82a6ca07038a7a8f6ac9@buzz.block.builderlab.xyz>
This commit is contained in:
Pinky
2026-09-30 15:17:53 -06:00
parent 87bb4f16c4
commit 97b49bdfc0
2 changed files with 46 additions and 7 deletions

View File

@@ -17,6 +17,7 @@ windows-sys = { version = "0.61", features = [
"Win32_Security",
"Win32_Security_Authorization",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
"Win32_System_Threading",
] }

View File

@@ -29,9 +29,11 @@ use windows_sys::Win32::Security::{
SECURITY_ATTRIBUTES, TOKEN_OWNER, TOKEN_QUERY,
};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_OPEN_REPARSE_POINT,
FILE_GENERIC_READ, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, WRITE_DAC,
CreateFileW, GetVolumeInformationByHandleW, CREATE_NEW, FILE_ATTRIBUTE_NORMAL,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_GENERIC_READ, FILE_SHARE_DELETE, FILE_SHARE_READ,
FILE_SHARE_WRITE, WRITE_DAC,
};
use windows_sys::Win32::System::SystemServices::FILE_PERSISTENT_ACLS;
use windows_sys::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};
/// Full access for the file's owner and nobody else; `P` blocks inherited ACEs.
@@ -42,7 +44,9 @@ const OWNER_ONLY: &str = "D:P(A;;FA;;;OW)";
/// Fails if `path` exists, even as a dangling link. The DACL is set atomically
/// at creation, so no other user can open the file even briefly. Read, write
/// and delete sharing match std, so another process can still rename a new
/// file over this path.
/// file over this path. On a volume that cannot store ACLs, such as FAT or
/// exFAT, it returns an error after creating the empty file, which the caller
/// removes.
#[allow(unsafe_code)]
pub fn create_new(path: &Path) -> io::Result<File> {
let descriptor = owner_only()?;
@@ -69,15 +73,17 @@ pub fn create_new(path: &Path) -> io::Result<File> {
return Err(io::Error::last_os_error());
}
// SAFETY: `handle` is a valid file handle that nothing else owns.
Ok(unsafe { File::from_raw_handle(handle) })
let file = unsafe { File::from_raw_handle(handle) };
require_acls(&file)?;
Ok(file)
}
/// Open an existing owner-only file for reading.
///
/// Does not follow a final symlink or junction, and refuses anything but a
/// regular file owned by this process's default owner, which is the owner
/// [`create_new`] assigns. It then re-applies the owner-only DACL on the open
/// handle, so a file with inherited permissions is tightened before use.
/// regular file on a volume with ACLs, owned by this process's default owner,
/// which is the owner [`create_new`] assigns. It then re-applies the
/// owner-only DACL on the open handle, so a loose file is tightened before use.
pub fn open_owned(path: &Path) -> io::Result<File> {
let file = OpenOptions::new()
.access_mode(FILE_GENERIC_READ | WRITE_DAC)
@@ -89,6 +95,7 @@ pub fn open_owned(path: &Path) -> io::Result<File> {
"not a regular file",
));
}
require_acls(&file)?;
if !owned_by_current_process(&file)? {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
@@ -210,6 +217,37 @@ fn owned_by_current_process(file: &File) -> io::Result<bool> {
Ok(unsafe { EqualSid(owner, (*buffer.as_ptr().cast::<TOKEN_OWNER>()).Owner) } != 0)
}
/// Refuse a file on a volume that cannot store ACLs, such as FAT or exFAT,
/// where Windows silently drops the DACL.
#[allow(unsafe_code)]
fn require_acls(file: &File) -> io::Result<()> {
let mut flags = 0;
// SAFETY: the handle is open, and null buffers of size zero skip the
// outputs this does not need.
let ok = unsafe {
GetVolumeInformationByHandleW(
file.as_raw_handle(),
null_mut(),
0,
null_mut(),
null_mut(),
&mut flags,
null_mut(),
0,
)
};
if ok == 0 {
return Err(io::Error::last_os_error());
}
if flags & FILE_PERSISTENT_ACLS == 0 {
return Err(io::Error::new(
io::ErrorKind::Unsupported,
"volume cannot store file permissions",
));
}
Ok(())
}
fn win32(status: WIN32_ERROR) -> io::Result<()> {
if status == ERROR_SUCCESS {
Ok(())