diff --git a/src/shadow-core/src/sysroot.rs b/src/shadow-core/src/sysroot.rs index 08a24c4..9e8134a 100644 --- a/src/shadow-core/src/sysroot.rs +++ b/src/shadow-core/src/sysroot.rs @@ -31,17 +31,28 @@ impl SysRoot { /// Resolve a path relative to the prefix. /// /// Strips leading `/` from `relative` before joining with the prefix. - #[must_use] - pub fn resolve(&self, relative: &str) -> PathBuf { + /// Returns `None` if the path contains `..` components (path traversal). + pub fn try_resolve(&self, relative: &str) -> Option { let stripped = relative.strip_prefix('/').unwrap_or(relative); let joined = self.prefix.join(stripped); // Reject path traversal: ".." components could escape the prefix. for component in joined.components() { if matches!(component, Component::ParentDir) { - return self.prefix.clone(); + return None; } } - joined + Some(joined) + } + + /// Resolve a path relative to the prefix. + /// + /// Strips leading `/` from `relative` before joining with the prefix. + /// Only for hardcoded paths — use [`try_resolve`] for user-controlled input. + #[must_use] + pub fn resolve(&self, relative: &str) -> PathBuf { + // All callers pass hardcoded paths like "/etc/passwd" — never ".." + self.try_resolve(relative) + .unwrap_or_else(|| unreachable!("resolve() called with path traversal: {relative:?}")) } /// Path to `/etc/passwd`. @@ -138,13 +149,19 @@ mod tests { } #[test] - fn test_resolve_rejects_path_traversal() { + fn test_try_resolve_rejects_path_traversal() { + let root = SysRoot::new(Some(Path::new("/mnt/chroot"))); + // Attempting to escape the prefix via ".." returns None. + assert_eq!(root.try_resolve("/../etc/shadow"), None); + assert_eq!(root.try_resolve("/home/../../etc/shadow"), None); + } + + #[test] + fn test_try_resolve_accepts_valid_paths() { let root = SysRoot::new(Some(Path::new("/mnt/chroot"))); - // Attempting to escape the prefix via ".." should return the prefix itself. - assert_eq!(root.resolve("/../etc/shadow"), PathBuf::from("/mnt/chroot")); assert_eq!( - root.resolve("/home/../../etc/shadow"), - PathBuf::from("/mnt/chroot") + root.try_resolve("/etc/shadow"), + Some(PathBuf::from("/mnt/chroot/etc/shadow")) ); } } diff --git a/src/uu/grpck/src/grpck.rs b/src/uu/grpck/src/grpck.rs index 0997827..650e755 100644 --- a/src/uu/grpck/src/grpck.rs +++ b/src/uu/grpck/src/grpck.rs @@ -127,7 +127,7 @@ impl GrpckOptions { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _clean_env = shadow_core::hardening::harden_process(); + let _ = shadow_core::hardening::harden_process(); let matches = uu_app().try_get_matches_from(args)?; let opts = GrpckOptions::from_matches(&matches); diff --git a/src/uu/pwck/src/pwck.rs b/src/uu/pwck/src/pwck.rs index 67530bc..5cc8942 100644 --- a/src/uu/pwck/src/pwck.rs +++ b/src/uu/pwck/src/pwck.rs @@ -141,7 +141,7 @@ impl PwckOptions { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _clean_env = shadow_core::hardening::harden_process(); + let _ = shadow_core::hardening::harden_process(); let matches = uu_app().try_get_matches_from(args)?; let opts = PwckOptions::from_matches(&matches); diff --git a/src/uu/useradd/src/useradd.rs b/src/uu/useradd/src/useradd.rs index d1a388a..c3ef975 100644 --- a/src/uu/useradd/src/useradd.rs +++ b/src/uu/useradd/src/useradd.rs @@ -885,12 +885,13 @@ fn append_subid_entry(path: &Path, name: &str, count: u64) -> UResult<()> { } // Find next available range by starting after the highest existing end. - // This prevents overlapping ranges that could allow container escape. + // Clamp to at least 100_000 even if existing entries are below that threshold. let start = entries .iter() .map(|e| e.start.saturating_add(e.count)) .max() - .unwrap_or(100_000); + .unwrap_or(100_000) + .max(100_000); entries.push(SubIdEntry { name: name.to_string(), diff --git a/src/uu/userdel/src/userdel.rs b/src/uu/userdel/src/userdel.rs index 6ce28a9..239f050 100644 --- a/src/uu/userdel/src/userdel.rs +++ b/src/uu/userdel/src/userdel.rs @@ -72,7 +72,7 @@ impl UError for UserdelError { #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _clean_env = shadow_core::hardening::harden_process(); + let _ = shadow_core::hardening::harden_process(); let matches = match uu_app().try_get_matches_from(args) { Ok(m) => m, diff --git a/src/uu/usermod/src/usermod.rs b/src/uu/usermod/src/usermod.rs index fbf52d2..a4be874 100644 --- a/src/uu/usermod/src/usermod.rs +++ b/src/uu/usermod/src/usermod.rs @@ -76,7 +76,7 @@ impl UError for UsermodError { #[uucore::main] #[allow(clippy::too_many_lines)] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _clean_env = shadow_core::hardening::harden_process(); + let _ = shadow_core::hardening::harden_process(); let matches = match uu_app().try_get_matches_from(args) { Ok(m) => m,