app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Share frozen native Rust mechanical development checkpoint and qualification scope
b2d7cdb
26d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
//! Atomically private Windows staging, adapted from the frozen r7 journal policy.
use std::ffi::c_void;
use std::fs::OpenOptions;
use std::io;
use std::mem::{size_of, zeroed};
use std::os::windows::{ffi::OsStrExt, fs::OpenOptionsExt, io::AsRawHandle};
use std::path::Path;
use std::ptr::null_mut;
use windows_sys::Win32::{
Foundation::*,
Security::{Authorization::*, *},
Storage::FileSystem::*,
System::Threading::*,
};
type Result<T> = io::Result<T>;
fn invalid(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::PermissionDenied, message)
}
fn unavailable(message: &str) -> io::Error {
io::Error::new(io::ErrorKind::Unsupported, message)
}
fn last() -> io::Error {
io::Error::last_os_error()
}
struct Local(*mut c_void);
impl Drop for Local {
fn drop(&mut self) {
unsafe {
LocalFree(self.0);
}
}
}
struct Handle(HANDLE);
impl Drop for Handle {
fn drop(&mut self) {
unsafe {
CloseHandle(self.0);
}
}
}
// TOKEN_USER contains an embedded SID pointer. usize storage gives its alignment
// and remains alive until every operation using that SID has finished.
struct User(Vec<usize>);
impl User {
fn current() -> Result<Self> {
unsafe {
let mut thread_token = null_mut();
if OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, &mut thread_token) != 0 {
let _token = Handle(thread_token);
return Err(unavailable(
"Impersonating threads are unsupported for artifact staging.",
));
}
if GetLastError() != ERROR_NO_TOKEN {
return Err(last());
}
let mut token = null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token) == 0 {
return Err(last());
}
let token = Handle(token);
let mut needed = 0;
GetTokenInformation(token.0, TokenUser, null_mut(), 0, &mut needed);
if GetLastError() != ERROR_INSUFFICIENT_BUFFER
|| needed < size_of::<TOKEN_USER>() as u32
{
return Err(last());
}
let mut data = vec![0usize; (needed as usize).div_ceil(size_of::<usize>())];
if GetTokenInformation(
token.0,
TokenUser,
data.as_mut_ptr().cast(),
needed,
&mut needed,
) == 0
{
return Err(last());
}
let value = Self(data);
if IsValidSid(value.sid()) == 0 {
return Err(invalid("Process token user SID is invalid."));
}
// Rust creates the payload with the process default owner.
// Reject alternate token owners (common in elevated tokens) rather
// than changing the token, taking ownership, or repairing sidecars.
let mut owner_needed = 0;
GetTokenInformation(token.0, TokenOwner, null_mut(), 0, &mut owner_needed);
if GetLastError() != ERROR_INSUFFICIENT_BUFFER
|| owner_needed < size_of::<TOKEN_OWNER>() as u32
{
return Err(last());
}
let mut owner_data = vec![0usize; (owner_needed as usize).div_ceil(size_of::<usize>())];
if GetTokenInformation(
token.0,
TokenOwner,
owner_data.as_mut_ptr().cast(),
owner_needed,
&mut owner_needed,
) == 0
{
return Err(last());
}
let owner = (*(owner_data.as_ptr().cast::<TOKEN_OWNER>())).Owner;
if owner.is_null() || IsValidSid(owner) == 0 || EqualSid(owner, value.sid()) == 0 {
return Err(unavailable("The process default object owner must be its current user; elevated alternate-owner tokens are unsupported."));
}
Ok(value)
}
}
fn sid(&self) -> PSID {
unsafe { (*(self.0.as_ptr().cast::<TOKEN_USER>())).User.Sid }
}
fn text(&self) -> Result<String> {
let mut string = null_mut();
unsafe {
if ConvertSidToStringSidW(self.sid(), &mut string) == 0 {
return Err(last());
}
let owned = Local(string.cast());
let mut len = 0;
while *string.add(len) != 0 {
len += 1;
}
let result = String::from_utf16(std::slice::from_raw_parts(string, len))
.map_err(|_| invalid("Process SID encoding is invalid."));
drop(owned);
result
}
}
}
fn descriptor(sddl: &str) -> Result<Local> {
let sddl: Vec<u16> = sddl.encode_utf16().chain([0]).collect();
let mut descriptor = null_mut();
unsafe {
if ConvertStringSecurityDescriptorToSecurityDescriptorW(
sddl.as_ptr(),
SDDL_REVISION_1,
&mut descriptor,
null_mut(),
) == 0
{
return Err(last());
}
}
Ok(Local(descriptor))
}
fn private_descriptor(directory: bool) -> Result<Local> {
let user = User::current()?.text()?;
descriptor(&format!(
"O:{user}D:P(A;{};FA;;;{user})",
if directory { "OICI" } else { "" }
))
}
fn attributes(descriptor: &Local) -> SECURITY_ATTRIBUTES {
SECURITY_ATTRIBUTES {
nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
lpSecurityDescriptor: descriptor.0,
bInheritHandle: 0,
}
}
pub(super) fn create_private_directory(path: &Path) -> Result<()> {
let descriptor = private_descriptor(true)?;
create_directory_with_descriptor(path, &descriptor)?;
if let Err(e) = verify_private_directory(path) {
// Only the just-created empty directory can be removed; never repair ACLs.
return match std::fs::remove_dir(path) {
Ok(()) => Err(e),
Err(cleanup) => Err(io::Error::other(format!(
"{e}; owned empty staging cleanup failed at {}: {cleanup}",
path.display()
))),
};
}
Ok(())
}
fn create_directory_with_descriptor(path: &Path, descriptor: &Local) -> Result<()> {
let mut name = path.as_os_str().encode_wide().collect::<Vec<_>>();
if name.contains(&0) {
return Err(invalid("NUL in staging path"));
}
name.push(0);
let security = attributes(descriptor);
if unsafe { CreateDirectoryW(name.as_ptr(), &security) } == 0 {
return Err(last());
}
Ok(())
}
pub(super) fn verify_private_directory(path: &Path) -> Result<()> {
let file = OpenOptions::new()
.access_mode(READ_CONTROL | FILE_READ_ATTRIBUTES)
.share_mode(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE)
.custom_flags(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)
.open(path)?;
let mut info: BY_HANDLE_FILE_INFORMATION = unsafe { zeroed() };
if unsafe { GetFileInformationByHandle(file.as_raw_handle(), &mut info) } == 0 {
return Err(last());
}
if info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0
|| info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0
{
return Err(invalid("staging must be a plain directory"));
}
let user = User::current()?;
let mut owner = null_mut();
let mut dacl = null_mut();
let mut raw = null_mut();
unsafe {
let status = GetSecurityInfo(
file.as_raw_handle(),
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&mut owner,
null_mut(),
&mut dacl,
null_mut(),
&mut raw,
);
if status != ERROR_SUCCESS {
return Err(io::Error::from_raw_os_error(status as i32));
}
let descriptor = Local(raw);
let mut control = 0;
let mut revision = 0;
if IsValidSecurityDescriptor(descriptor.0) == 0
|| GetSecurityDescriptorControl(descriptor.0, &mut control, &mut revision) == 0
|| owner.is_null()
|| IsValidSid(owner) == 0
|| EqualSid(owner, user.sid()) == 0
|| control & SE_DACL_PRESENT == 0
|| control & SE_DACL_PROTECTED == 0
|| dacl.is_null()
|| IsValidAcl(dacl) == 0
|| (*dacl).AceCount != 1
{
return Err(invalid(
"staging requires a protected current-user-only DACL",
));
}
let mut pointer = null_mut();
if GetAce(dacl, 0, &mut pointer) == 0 {
return Err(last());
}
let header = &*pointer.cast::<ACE_HEADER>();
if header.AceType != 0 || (header.AceSize as usize) < size_of::<ACCESS_ALLOWED_ACE>() {
return Err(invalid("unsupported staging ACE"));
}
let allow = &*pointer.cast::<ACCESS_ALLOWED_ACE>();
let sid: PSID = (&allow.SidStart as *const u32).cast_mut().cast();
let sid_bytes = header.AceSize as usize - 8;
if sid_bytes < 8
|| 8 + 4 * (*sid.cast::<u8>().add(1) as usize) > sid_bytes
|| IsValidSid(sid) == 0
|| EqualSid(sid, user.sid()) == 0
|| header.AceFlags != (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE) as u8
|| allow.Mask != FILE_ALL_ACCESS
{
return Err(invalid(
"staging ACE must give only the current user inheritable full control",
));
}
}
Ok(())
}
#[cfg(test)]
#[test]
fn protected_staging_does_not_inherit_a_shared_parent_dacl() {
let root = super::tests::Directory::new();
let user = User::current().unwrap().text().unwrap();
let shared = root.0.join("shared");
let descriptor =
descriptor(&format!("O:{user}D:P(A;OICI;FA;;;WD)(A;OICI;FA;;;{user})")).unwrap();
create_directory_with_descriptor(&shared, &descriptor).unwrap();
assert!(verify_private_directory(&shared).is_err());
let mut private = super::Staging::create(&shared).unwrap();
verify_private_directory(&private.directory).unwrap();
std::fs::write(&private.file, b"private").unwrap();
private.cleanup().unwrap();
let output = shared.join("bom.csv");
assert!(
super::publish_csv(&output, b"part,quantity\r\nbolt,4\r\n", false)
.unwrap()
.published
);
assert_eq!(std::fs::read_dir(&shared).unwrap().count(), 1);
}