Skip to content

Commit 87d6551

Browse files
committed
✨ Add set_facl_nofollow for symlink-aware ACL writes on Windows
Use handle-based SetSecurityInfo with FILE_FLAG_OPEN_REPARSE_POINT to set ACLs on symlinks without following them. Extract ACL buffer building into build_acl_buffer to share between set_d_acl and set_d_acl_by_handle.
1 parent 9d812f9 commit 87d6551

2 files changed

Lines changed: 67 additions & 31 deletions

File tree

cli/src/utils/os/windows/acl.rs

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use crate::{
22
chunk::{self, AcePlatform, Identifier, OwnerType},
33
utils::os::windows::{
4-
fs::open_read_metadata,
4+
fs::{open_read_metadata, open_write_dacl},
55
security::{SecurityDescriptor, Sid, SidType},
66
},
77
};
88
use field_offset::offset_of;
99
use std::{io, mem, path::Path, ptr::null_mut};
10+
use windows::Win32::Foundation::HANDLE;
1011
use windows::Win32::Security::{
1112
ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, ACE_FLAGS, ACE_HEADER, ACL as Win32ACL, ACL_REVISION_DS,
1213
AddAccessAllowedAceEx, AddAccessDeniedAceEx, CONTAINER_INHERIT_ACE, GetAce, INHERIT_ONLY_ACE,
@@ -41,6 +42,20 @@ pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<chunk::Acl> {
4142
})
4243
}
4344

45+
pub fn set_facl_nofollow<P: AsRef<Path>>(path: P, ace_list: chunk::Acl) -> io::Result<()> {
46+
let path = path.as_ref();
47+
let handle = open_write_dacl(path, false)?;
48+
let acl = ACL::try_from_handle(handle.raw(), path)?;
49+
let group_sid = acl.security_descriptor.group_sid()?;
50+
let owner_sid = acl.security_descriptor.owner_sid()?;
51+
let acl_entries = ace_list
52+
.entries
53+
.into_iter()
54+
.map(|it| it.into_acl_entry_with(&owner_sid, &group_sid))
55+
.collect::<Vec<_>>();
56+
acl.set_d_acl_by_handle(handle.raw(), &acl_entries)
57+
}
58+
4459
pub fn get_facl_nofollow<P: AsRef<Path>>(path: P) -> io::Result<chunk::Acl> {
4560
let acl = ACL::try_from_nofollow(path.as_ref())?;
4661
let ace_list = acl.get_d_acl()?;
@@ -71,6 +86,12 @@ impl ACL {
7186
})
7287
}
7388

89+
pub fn try_from_handle(handle: HANDLE, path: &Path) -> io::Result<Self> {
90+
Ok(Self {
91+
security_descriptor: SecurityDescriptor::try_from_handle(handle, path)?,
92+
})
93+
}
94+
7495
pub fn get_d_acl(&self) -> io::Result<Vec<ACLEntry>> {
7596
let mut result = Vec::new();
7697
let p_acl = self.security_descriptor.p_dacl;
@@ -128,37 +149,47 @@ impl ACL {
128149
}
129150

130151
pub fn set_d_acl(&self, acl_entries: &[ACLEntry]) -> io::Result<()> {
131-
let acl_size = acl_entries.iter().map(|it| it.size as usize).sum::<usize>()
132-
+ mem::size_of::<Win32ACL>();
133-
let mut new_acl_buffer = Vec::<u8>::with_capacity(acl_size);
134-
let new_acl = new_acl_buffer.as_mut_ptr();
135-
unsafe { InitializeAcl(new_acl as _, acl_size as u32, ACL_REVISION_DS) }?;
136-
for ace in acl_entries {
137-
match ace.ace_type {
138-
AceType::AccessAllow => unsafe {
139-
AddAccessAllowedAceEx(
140-
new_acl as _,
141-
ACL_REVISION_DS,
142-
ACE_FLAGS(ace.flags as u32),
143-
ace.mask,
144-
ace.sid.as_psid(),
145-
)
146-
},
147-
AceType::AccessDeny => unsafe {
148-
AddAccessDeniedAceEx(
149-
new_acl as _,
150-
ACL_REVISION_DS,
151-
ACE_FLAGS(ace.flags as u32),
152-
ace.mask,
153-
ace.sid.as_psid(),
154-
)
155-
},
156-
AceType::Unknown(n) => return Err(io::Error::other(format!("{}", n))),
157-
}?;
158-
}
152+
let buffer = build_acl_buffer(acl_entries)?;
159153
self.security_descriptor
160-
.apply(None, None, Some(new_acl as _))
154+
.apply(None, None, Some(buffer.as_ptr() as _))
155+
}
156+
157+
pub fn set_d_acl_by_handle(&self, handle: HANDLE, acl_entries: &[ACLEntry]) -> io::Result<()> {
158+
let buffer = build_acl_buffer(acl_entries)?;
159+
SecurityDescriptor::apply_by_handle(handle, None, None, Some(buffer.as_ptr() as _))
160+
}
161+
}
162+
163+
fn build_acl_buffer(acl_entries: &[ACLEntry]) -> io::Result<Vec<u8>> {
164+
let acl_size =
165+
acl_entries.iter().map(|it| it.size as usize).sum::<usize>() + mem::size_of::<Win32ACL>();
166+
let mut buffer = Vec::<u8>::with_capacity(acl_size);
167+
let ptr = buffer.as_mut_ptr();
168+
unsafe { InitializeAcl(ptr as _, acl_size as u32, ACL_REVISION_DS) }?;
169+
for ace in acl_entries {
170+
match ace.ace_type {
171+
AceType::AccessAllow => unsafe {
172+
AddAccessAllowedAceEx(
173+
ptr as _,
174+
ACL_REVISION_DS,
175+
ACE_FLAGS(ace.flags as u32),
176+
ace.mask,
177+
ace.sid.as_psid(),
178+
)
179+
},
180+
AceType::AccessDeny => unsafe {
181+
AddAccessDeniedAceEx(
182+
ptr as _,
183+
ACL_REVISION_DS,
184+
ACE_FLAGS(ace.flags as u32),
185+
ace.mask,
186+
ace.sid.as_psid(),
187+
)
188+
},
189+
AceType::Unknown(n) => return Err(io::Error::other(format!("{}", n))),
190+
}?;
161191
}
192+
Ok(buffer)
162193
}
163194

164195
#[derive(Clone, Copy, Debug, PartialEq)]

cli/src/utils/os/windows/fs.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use windows::Win32::Storage::FileSystem::{
1212
FILE_READ_ATTRIBUTES, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
1313
FILE_WRITE_ATTRIBUTES, FileBasicInfo, GetFileInformationByHandle, GetFileInformationByHandleEx,
1414
MOVEFILE_COPY_ALLOWED, MOVEFILE_REPLACE_EXISTING, MoveFileExW, OPEN_EXISTING, READ_CONTROL,
15-
SetFileInformationByHandle, WRITE_OWNER,
15+
SetFileInformationByHandle, WRITE_DAC, WRITE_OWNER,
1616
};
1717
use windows::core::PCWSTR;
1818

@@ -80,6 +80,11 @@ pub(crate) fn open_read_metadata(path: &Path, follow_symlink: bool) -> io::Resul
8080
)
8181
}
8282

83+
#[inline]
84+
pub(crate) fn open_write_dacl(path: &Path, follow_symlink: bool) -> io::Result<FileHandle> {
85+
open_path(path, (READ_CONTROL | WRITE_DAC).0, follow_symlink)
86+
}
87+
8388
#[inline]
8489
pub(crate) fn file_information(handle: HANDLE) -> io::Result<BY_HANDLE_FILE_INFORMATION> {
8590
let mut info = BY_HANDLE_FILE_INFORMATION::default();

0 commit comments

Comments
 (0)