Skip to content

Commit 64348d8

Browse files
committed
chmod: on linux use the safe traversal functions
1 parent f86cf35 commit 64348d8

File tree

2 files changed

+85
-3
lines changed

2 files changed

+85
-3
lines changed

src/uucore/src/lib/features/perms.rs

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
//! Common functions to manage permissions
77
8-
// spell-checker:ignore (jargon) TOCTOU fchownat
8+
// spell-checker:ignore (jargon) TOCTOU fchownat fchown
99

1010
use crate::display::Quotable;
1111
use crate::error::{UResult, USimpleError, strip_errno};
@@ -307,14 +307,41 @@ impl ChownExecutor {
307307
}
308308

309309
let ret = if self.matched(meta.uid(), meta.gid()) {
310-
match wrap_chown(
310+
// Use safe syscalls for root directory to prevent TOCTOU attacks
311+
#[cfg(all(target_os = "linux", feature = "safe-traversal"))]
312+
let chown_result = if path.is_dir() {
313+
// For directories, use safe traversal from the start
314+
match DirFd::open(path) {
315+
Ok(dir_fd) => self.safe_chown_dir(&dir_fd, path, &meta),
316+
Err(_e) => {
317+
// Don't show error here - let safe_dive_into handle directory traversal errors
318+
// This prevents duplicate error messages
319+
Ok(String::new())
320+
}
321+
}
322+
} else {
323+
// For non-directories (files, symlinks), use the regular wrap_chown method
324+
wrap_chown(
325+
path,
326+
&meta,
327+
self.dest_uid,
328+
self.dest_gid,
329+
self.dereference,
330+
self.verbosity.clone(),
331+
)
332+
};
333+
334+
#[cfg(not(all(target_os = "linux", feature = "safe-traversal")))]
335+
let chown_result = wrap_chown(
311336
path,
312337
&meta,
313338
self.dest_uid,
314339
self.dest_gid,
315340
self.dereference,
316341
self.verbosity.clone(),
317-
) {
342+
);
343+
344+
match chown_result {
318345
Ok(n) => {
319346
if !n.is_empty() {
320347
show_error!("{n}");
@@ -351,6 +378,60 @@ impl ChownExecutor {
351378
}
352379
}
353380

381+
#[cfg(all(target_os = "linux", feature = "safe-traversal"))]
382+
fn safe_chown_dir(
383+
&self,
384+
dir_fd: &DirFd,
385+
path: &Path,
386+
meta: &Metadata,
387+
) -> Result<String, String> {
388+
let dest_uid = self.dest_uid.unwrap_or_else(|| meta.uid());
389+
let dest_gid = self.dest_gid.unwrap_or_else(|| meta.gid());
390+
391+
// Use fchown (safe) to change the directory's ownership
392+
if let Err(e) = dir_fd.fchown(self.dest_uid, self.dest_gid) {
393+
let mut error_msg = format!(
394+
"changing {} of {}: {}",
395+
if self.verbosity.groups_only {
396+
"group"
397+
} else {
398+
"ownership"
399+
},
400+
path.quote(),
401+
e
402+
);
403+
404+
if self.verbosity.level == VerbosityLevel::Verbose {
405+
error_msg = if self.verbosity.groups_only {
406+
let gid = meta.gid();
407+
format!(
408+
"{error_msg}\nfailed to change group of {} from {} to {}",
409+
path.quote(),
410+
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
411+
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
412+
)
413+
} else {
414+
let uid = meta.uid();
415+
let gid = meta.gid();
416+
format!(
417+
"{error_msg}\nfailed to change ownership of {} from {}:{} to {}:{}",
418+
path.quote(),
419+
entries::uid2usr(uid).unwrap_or_else(|_| uid.to_string()),
420+
entries::gid2grp(gid).unwrap_or_else(|_| gid.to_string()),
421+
entries::uid2usr(dest_uid).unwrap_or_else(|_| dest_uid.to_string()),
422+
entries::gid2grp(dest_gid).unwrap_or_else(|_| dest_gid.to_string())
423+
)
424+
};
425+
}
426+
427+
return Err(error_msg);
428+
}
429+
430+
// Report the change if verbose (similar to wrap_chown)
431+
self.report_ownership_change_success(path, meta.uid(), meta.gid());
432+
Ok(String::new())
433+
}
434+
354435
#[cfg(all(target_os = "linux", feature = "safe-traversal"))]
355436
fn safe_dive_into<P: AsRef<Path>>(&self, root: P) -> i32 {
356437
let root = root.as_ref();

src/uucore/src/lib/features/safe_traversal.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use std::path::Path;
2424

2525
use nix::dir::Dir;
2626
use nix::fcntl::{OFlag, openat};
27+
use nix::libc;
2728
use nix::sys::stat::{FchmodatFlags, FileStat, Mode, fchmodat, fstatat};
2829
use nix::unistd::{Gid, Uid, UnlinkatFlags, fchown, fchownat, unlinkat};
2930

0 commit comments

Comments
 (0)