Skip to content

Commit d6d7511

Browse files
committed
♻️ Unify Windows security operations to handle-based API
Remove path field from SecurityDescriptor and eliminate all path-based Win32 APIs (GetNamedSecurityInfoW/SetNamedSecurityInfoW). All security operations now go through handle-based APIs, making follow_links control purely a handle-opening concern. - Fix UB in build_acl_buffer by setting Vec length after InitializeAcl - Remove SecurityDescriptor::path field and try_from(path) constructor - Extract apply_security_info helper to eliminate duplication - Consolidate ACL constructors from 3 to 1 (try_from_handle) - Unify get_facl/set_facl with follow_links parameter across platforms - Wire set_facl nofollow into extraction path for symlink ACL restoration - Remove inline #[cfg] from core.rs by using unified ACL API - Add Debug derive to FileHandle - Add broken symlink permission preservation test
1 parent 120d23a commit d6d7511

7 files changed

Lines changed: 158 additions & 181 deletions

File tree

cli/src/command/core.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ pub(crate) fn apply_metadata(
979979

980980
let handle = open_read_metadata(path, !meta.file_type().is_symlink())?;
981981
let info = file_information(handle.raw())?;
982-
let sd = SecurityDescriptor::try_from_handle(handle.raw(), path)?;
982+
let sd = SecurityDescriptor::try_from_handle(handle)?;
983983
let mode = mode_from_file_information(path, &info, meta.file_type().is_symlink());
984984
let user = sd.owner_sid()?;
985985
let group = sd.group_sid()?;
@@ -1011,14 +1011,8 @@ pub(crate) fn apply_metadata(
10111011
if let AclStrategy::Always = keep_options.acl_strategy {
10121012
use crate::chunk;
10131013
use pna::RawChunk;
1014-
#[cfg(windows)]
1015-
let acl_result = if meta.file_type().is_symlink() {
1016-
utils::os::windows::acl::get_facl_nofollow(path)
1017-
} else {
1018-
utils::acl::get_facl(path)
1019-
};
1020-
#[cfg(not(windows))]
1021-
let acl_result = utils::acl::get_facl(path);
1014+
let follow_links = !meta.file_type().is_symlink();
1015+
let acl_result = utils::acl::get_facl(path, follow_links);
10221016
match acl_result {
10231017
Ok(acl) => {
10241018
entry

cli/src/command/extract.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1408,7 +1408,8 @@ where
14081408
}
14091409
#[cfg(feature = "acl")]
14101410
if !skip_xattr_acl {
1411-
restore_acls(path, item.acl()?, keep_options.acl_strategy)?;
1411+
let follow_links = item.header().data_kind() != DataKind::SymbolicLink;
1412+
restore_acls(path, item.acl()?, keep_options.acl_strategy, follow_links)?;
14121413
}
14131414
#[cfg(not(feature = "acl"))]
14141415
if let AclStrategy::Always = keep_options.acl_strategy {
@@ -1474,7 +1475,12 @@ where
14741475
/// On supported platforms, if the target filesystem does not support ACLs (e.g., FAT32),
14751476
/// a warning is logged for that path and the operation continues.
14761477
#[cfg(feature = "acl")]
1477-
fn restore_acls(path: &Path, acls: Acls, acl_strategy: AclStrategy) -> io::Result<()> {
1478+
fn restore_acls(
1479+
path: &Path,
1480+
acls: Acls,
1481+
acl_strategy: AclStrategy,
1482+
follow_links: bool,
1483+
) -> io::Result<()> {
14781484
#[cfg(any(
14791485
target_os = "linux",
14801486
target_os = "freebsd",
@@ -1495,6 +1501,7 @@ fn restore_acls(path: &Path, acls: Acls, acl_strategy: AclStrategy) -> io::Resul
14951501
platform,
14961502
entries: acl,
14971503
}),
1504+
follow_links,
14981505
) {
14991506
Ok(()) => {}
15001507
Err(e) if e.kind() == io::ErrorKind::Unsupported => {

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

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ use crate::chunk::{Ace, AcePlatform, Acl, Flag, Identifier, OwnerType, Permissio
22
use std::io;
33
use std::path::Path;
44

5-
pub fn set_facl<P: AsRef<Path>>(path: P, acl: Acl) -> io::Result<()> {
5+
fn acl_option(follow_links: bool) -> Option<exacl::AclOption> {
6+
(!follow_links).then_some(exacl::AclOption::SYMLINK_ACL)
7+
}
8+
9+
pub fn set_facl<P: AsRef<Path>>(path: P, acl: Acl, follow_links: bool) -> io::Result<()> {
610
let path = path.as_ref();
11+
#[cfg(target_os = "linux")]
12+
if !follow_links && path.symlink_metadata()?.file_type().is_symlink() {
13+
return Ok(());
14+
}
715
let mut acl_entries: Vec<exacl::AclEntry> =
816
acl.entries.into_iter().map(Into::into).collect::<Vec<_>>();
917
#[cfg(target_os = "macos")]
1018
{
1119
use std::os::unix::fs::MetadataExt;
12-
let meta = std::fs::metadata(path)?;
20+
let meta = if follow_links {
21+
std::fs::metadata(path)?
22+
} else {
23+
std::fs::symlink_metadata(path)?
24+
};
1325

1426
acl_entries = acl_entries
1527
.into_iter()
@@ -40,7 +52,7 @@ pub fn set_facl<P: AsRef<Path>>(path: P, acl: Acl) -> io::Result<()> {
4052
}
4153
}
4254
if !exist_user || !exist_group || !exist_other {
43-
let facl = exacl::getfacl(path, None)?;
55+
let facl = exacl::getfacl(path, acl_option(follow_links))?;
4456
if !exist_user {
4557
acl_entries.push(
4658
facl.iter()
@@ -82,11 +94,19 @@ pub fn set_facl<P: AsRef<Path>>(path: P, acl: Acl) -> io::Result<()> {
8294
}
8395
}
8496
}
85-
exacl::setfacl(&[path], &acl_entries, None)
97+
exacl::setfacl(&[path], &acl_entries, acl_option(follow_links))
8698
}
8799

88-
pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<Acl> {
89-
let ace_list = exacl::getfacl(path.as_ref(), None)?;
100+
pub fn get_facl<P: AsRef<Path>>(path: P, follow_links: bool) -> io::Result<Acl> {
101+
let path = path.as_ref();
102+
#[cfg(target_os = "linux")]
103+
if !follow_links && path.symlink_metadata()?.file_type().is_symlink() {
104+
return Ok(Acl {
105+
platform: AcePlatform::CURRENT,
106+
entries: vec![],
107+
});
108+
}
109+
let ace_list = exacl::getfacl(path, acl_option(follow_links))?;
90110
Ok(Acl {
91111
platform: AcePlatform::CURRENT,
92112
entries: ace_list.into_iter().map(Into::into).collect(),

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

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use crate::{
22
chunk::{self, AcePlatform, Identifier, OwnerType},
33
utils::os::windows::{
4-
fs::{open_read_metadata, open_write_dacl},
4+
fs::{FileHandle, 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;
1110
use windows::Win32::Security::{
1211
ACCESS_ALLOWED_ACE, ACCESS_DENIED_ACE, ACE_FLAGS, ACE_HEADER, ACL as Win32ACL, ACL_REVISION_DS,
1312
AddAccessAllowedAceEx, AddAccessDeniedAceEx, CONTAINER_INHERIT_ACE, GetAce, INHERIT_ONLY_ACE,
@@ -21,8 +20,13 @@ use windows::Win32::Storage::FileSystem::{
2120
};
2221
use windows::Win32::System::SystemServices::{ACCESS_ALLOWED_ACE_TYPE, ACCESS_DENIED_ACE_TYPE};
2322

24-
pub fn set_facl<P: AsRef<Path>>(path: P, ace_list: chunk::Acl) -> io::Result<()> {
25-
let acl = ACL::try_from(path.as_ref())?;
23+
pub fn set_facl<P: AsRef<Path>>(
24+
path: P,
25+
ace_list: chunk::Acl,
26+
follow_links: bool,
27+
) -> io::Result<()> {
28+
let handle = open_write_dacl(path.as_ref(), follow_links)?;
29+
let acl = ACL::try_from_handle(handle)?;
2630
let group_sid = acl.security_descriptor.group_sid()?;
2731
let owner_sid = acl.security_descriptor.owner_sid()?;
2832
let acl_entries = ace_list
@@ -33,31 +37,9 @@ pub fn set_facl<P: AsRef<Path>>(path: P, ace_list: chunk::Acl) -> io::Result<()>
3337
acl.set_d_acl(&acl_entries)
3438
}
3539

36-
pub fn get_facl<P: AsRef<Path>>(path: P) -> io::Result<chunk::Acl> {
37-
let acl = ACL::try_from(path.as_ref())?;
38-
let ace_list = acl.get_d_acl()?;
39-
Ok(chunk::Acl {
40-
platform: AcePlatform::Windows,
41-
entries: ace_list.into_iter().map(Into::into).collect(),
42-
})
43-
}
44-
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-
59-
pub fn get_facl_nofollow<P: AsRef<Path>>(path: P) -> io::Result<chunk::Acl> {
60-
let acl = ACL::try_from_nofollow(path.as_ref())?;
40+
pub fn get_facl<P: AsRef<Path>>(path: P, follow_links: bool) -> io::Result<chunk::Acl> {
41+
let handle = open_read_metadata(path.as_ref(), follow_links)?;
42+
let acl = ACL::try_from_handle(handle)?;
6143
let ace_list = acl.get_d_acl()?;
6244
Ok(chunk::Acl {
6345
platform: AcePlatform::Windows,
@@ -74,22 +56,9 @@ pub struct ACL {
7456
}
7557

7658
impl ACL {
77-
pub fn try_from(path: &Path) -> io::Result<Self> {
59+
pub fn try_from_handle(handle: FileHandle) -> io::Result<Self> {
7860
Ok(Self {
79-
security_descriptor: SecurityDescriptor::try_from(path)?,
80-
})
81-
}
82-
83-
pub fn try_from_nofollow(path: &Path) -> io::Result<Self> {
84-
let handle = open_read_metadata(path, false)?;
85-
Ok(Self {
86-
security_descriptor: SecurityDescriptor::try_from_handle(handle.raw(), path)?,
87-
})
88-
}
89-
90-
pub fn try_from_handle(handle: HANDLE, path: &Path) -> io::Result<Self> {
91-
Ok(Self {
92-
security_descriptor: SecurityDescriptor::try_from_handle(handle, path)?,
61+
security_descriptor: SecurityDescriptor::try_from_handle(handle)?,
9362
})
9463
}
9564

@@ -154,11 +123,6 @@ impl ACL {
154123
self.security_descriptor
155124
.apply(None, None, Some(buffer.as_ptr() as _))
156125
}
157-
158-
pub fn set_d_acl_by_handle(&self, handle: HANDLE, acl_entries: &[ACLEntry]) -> io::Result<()> {
159-
let buffer = build_acl_buffer(acl_entries)?;
160-
SecurityDescriptor::apply_by_handle(handle, None, None, Some(buffer.as_ptr() as _))
161-
}
162126
}
163127

164128
fn build_acl_buffer(acl_entries: &[ACLEntry]) -> io::Result<Vec<u8>> {
@@ -167,6 +131,7 @@ fn build_acl_buffer(acl_entries: &[ACLEntry]) -> io::Result<Vec<u8>> {
167131
let mut buffer = Vec::<u8>::with_capacity(acl_size);
168132
let ptr = buffer.as_mut_ptr();
169133
unsafe { InitializeAcl(ptr as _, acl_size as u32, ACL_REVISION_DS) }?;
134+
unsafe { buffer.set_len(acl_size) };
170135
for ace in acl_entries {
171136
match ace.ace_type {
172137
AceType::AccessAllow => unsafe {
@@ -382,9 +347,10 @@ mod tests {
382347
| chunk::Permission::EXECUTE,
383348
}],
384349
}),
350+
true,
385351
)
386352
.unwrap();
387-
let acl = get_facl(file.path).unwrap();
353+
let acl = get_facl(file.path, true).unwrap();
388354
assert_eq!(acl.entries.len(), 1);
389355

390356
assert_eq!(
@@ -418,7 +384,7 @@ mod tests {
418384
fn get_acl() {
419385
let file = AutoRemoveFile::new("default.txt");
420386
file.write("default").unwrap();
421-
let acl = get_facl(file.path).unwrap();
387+
let acl = get_facl(file.path, true).unwrap();
422388
assert_ne!(acl.entries.len(), 0);
423389
}
424390
}

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pub(crate) mod owner;
22

3-
use super::security::{SecurityDescriptor, Sid};
3+
use super::security::{Sid, apply_security_info};
44
use crate::utils::str::encode_wide;
55
use std::io;
66
use std::mem::size_of;
@@ -23,6 +23,7 @@ const MODE_READ_BITS: u16 = 0o444;
2323
const MODE_WRITE_BITS: u16 = 0o222;
2424
const MODE_EXEC_BITS: u16 = 0o111;
2525

26+
#[derive(Debug)]
2627
pub(crate) struct FileHandle(HANDLE);
2728

2829
impl FileHandle {
@@ -132,7 +133,7 @@ pub(crate) fn lchown<U: Into<Sid>, G: Into<Sid>>(
132133
let owner_sid = owner.map(Into::into);
133134
let group_sid = group.map(Into::into);
134135
let handle = open_path(path, (READ_CONTROL | WRITE_OWNER).0, false)?;
135-
SecurityDescriptor::apply_by_handle(
136+
apply_security_info(
136137
handle.raw(),
137138
owner_sid.as_ref().map(Sid::as_psid),
138139
group_sid.as_ref().map(Sid::as_psid),
@@ -185,12 +186,14 @@ pub(crate) fn chmod(path: &Path, mode: u16) -> io::Result<()> {
185186
#[cfg(test)]
186187
mod tests {
187188
use super::*;
189+
use crate::utils::os::windows::security::SecurityDescriptor;
188190

189191
#[test]
190192
fn file_chown() {
191193
let path = "chown.txt";
192194
std::fs::write(path, "chown").unwrap();
193-
let sd = SecurityDescriptor::try_from(path.as_ref()).unwrap();
195+
let handle = open_read_metadata(path.as_ref(), true).unwrap();
196+
let sd = SecurityDescriptor::try_from_handle(handle).unwrap();
194197
lchown::<_, Sid>(path.as_ref(), Some(sd.owner_sid().unwrap()), None).unwrap();
195198
lchown::<Sid, _>(path.as_ref(), None, Some(sd.group_sid().unwrap())).unwrap();
196199
lchown(

0 commit comments

Comments
 (0)