From 1b4df95b4fc6a6658cc3a71990db316454b80b13 Mon Sep 17 00:00:00 2001 From: cerdelen Date: Sat, 24 Jan 2026 13:18:57 +0100 Subject: [PATCH 1/5] chroot: fix gid being set by uid with --userspec --- src/uu/chroot/src/chroot.rs | 14 ++++++++++++-- src/uucore/src/lib/features/entries.rs | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 04cb8c0c965..8b187f055eb 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -14,7 +14,7 @@ use std::os::unix::prelude::OsStrExt; use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process; -use uucore::entries::{Locate, Passwd, grp2gid, usr2uid}; +use uucore::entries::{Locate, Passwd, grp2gid, usr2gid, usr2uid}; use uucore::error::{UResult, UUsageError}; use uucore::fs::{MissingHandling, ResolveMode, canonicalize}; use uucore::libc::{self, chroot, setgid, setgroups, setuid}; @@ -279,6 +279,16 @@ fn name_to_uid(name: &str) -> Result { } } +/// Get the GID for the given username, falling back to numeric parsing. +fn usr_name_to_gid(name: &str) -> Result { + match usr2gid(name) { + Ok(gid) => Ok(gid), + Err(_) => name + .parse::() + .map_err(|_| ChrootError::NoSuchGroup), + } +} + /// Get the GID for the given group name, falling back to numeric parsing. /// /// According to the documentation of GNU `chroot`, "POSIX requires that @@ -401,7 +411,7 @@ fn set_context(options: &Options) -> UResult<()> { } Some(UserSpec::UserOnly(user)) => { let uid = name_to_uid(user)?; - let gid = uid as libc::gid_t; + let gid = usr_name_to_gid(user)?; let strategy = Strategy::FromUID(uid, false); set_supplemental_gids_with_strategy(strategy, options.groups.as_ref())?; set_gid(gid).map_err(|e| ChrootError::SetGidFailed(user.to_owned(), e))?; diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index 6a067e132b7..25c741728ed 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -356,6 +356,11 @@ pub fn usr2uid(name: &str) -> IOResult { Passwd::locate(name).map(|p| p.uid) } +#[inline] +pub fn usr2gid(name: &str) -> IOResult { + Passwd::locate(name).map(|p| p.gid) +} + #[inline] pub fn grp2gid(name: &str) -> IOResult { Group::locate(name).map(|p| p.gid) From 5e7eccdd08936572a21400c4cdc00b2d47245d6c Mon Sep 17 00:00:00 2001 From: Cedric Erdelen Date: Mon, 26 Jan 2026 19:03:04 +0100 Subject: [PATCH 2/5] chroot: check using sync user that has uid != gid --- tests/by-util/test_chroot.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index adeaf32bf6c..170b9210458 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -327,3 +327,28 @@ fn test_chroot_extra_arg() { print!("Test skipped; requires root user"); } } + +#[test] +fn test_chroot_userspec_does_not_set_gid_with_uid() { + use uucore::entries::{usr2gid, usr2uid}; + + let ts = TestScenario::new(util_name!()); + if let Ok(uid) = usr2uid("sync") { + if let Ok(gid) = usr2gid("sync") { + if gid == uid { + println!("Test skipped; requires sync user to have uid != gid"); + return; + } + // Ubuntu has a sync user whose gid is 65534 per default + if let Ok(result) = run_ucmd_as_root(&ts, &["--userspec=sync", "/", "id", "-g"]) { + result.success().no_stderr().stdout_is("{gid}"); + } else { + println!("Test skipped; requires root user"); + } + } else { + println!("Test skipped; requires 'sync' user"); + } + } else { + println!("Test skipped; requires 'sync' user"); + } +} From 5eb9a4e1b100c341049166ad2af5535f7c1fd688 Mon Sep 17 00:00:00 2001 From: Cedric Erdelen Date: Wed, 11 Feb 2026 19:17:32 +0100 Subject: [PATCH 3/5] chroot: correct Error response on failure case --- src/uu/chroot/src/chroot.rs | 12 +----------- src/uucore/src/lib/features/entries.rs | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/uu/chroot/src/chroot.rs b/src/uu/chroot/src/chroot.rs index 8b187f055eb..e6c4ee70192 100644 --- a/src/uu/chroot/src/chroot.rs +++ b/src/uu/chroot/src/chroot.rs @@ -279,16 +279,6 @@ fn name_to_uid(name: &str) -> Result { } } -/// Get the GID for the given username, falling back to numeric parsing. -fn usr_name_to_gid(name: &str) -> Result { - match usr2gid(name) { - Ok(gid) => Ok(gid), - Err(_) => name - .parse::() - .map_err(|_| ChrootError::NoSuchGroup), - } -} - /// Get the GID for the given group name, falling back to numeric parsing. /// /// According to the documentation of GNU `chroot`, "POSIX requires that @@ -411,7 +401,7 @@ fn set_context(options: &Options) -> UResult<()> { } Some(UserSpec::UserOnly(user)) => { let uid = name_to_uid(user)?; - let gid = usr_name_to_gid(user)?; + let gid = usr2gid(user).map_err(|_| ChrootError::NoGroupSpecified(uid))?; let strategy = Strategy::FromUID(uid, false); set_supplemental_gids_with_strategy(strategy, options.groups.as_ref())?; set_gid(gid).map_err(|e| ChrootError::SetGidFailed(user.to_owned(), e))?; diff --git a/src/uucore/src/lib/features/entries.rs b/src/uucore/src/lib/features/entries.rs index 25c741728ed..b6c969a04bc 100644 --- a/src/uucore/src/lib/features/entries.rs +++ b/src/uucore/src/lib/features/entries.rs @@ -357,7 +357,7 @@ pub fn usr2uid(name: &str) -> IOResult { } #[inline] -pub fn usr2gid(name: &str) -> IOResult { +pub fn usr2gid(name: &str) -> IOResult { Passwd::locate(name).map(|p| p.gid) } From 104a80fc3a7bbfbf88baa7ef792885f2fdf339d5 Mon Sep 17 00:00:00 2001 From: Cedric Erdelen Date: Wed, 11 Feb 2026 19:17:51 +0100 Subject: [PATCH 4/5] chroot: fix test --- tests/by-util/test_chroot.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index 170b9210458..a4ba0013d72 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -341,7 +341,7 @@ fn test_chroot_userspec_does_not_set_gid_with_uid() { } // Ubuntu has a sync user whose gid is 65534 per default if let Ok(result) = run_ucmd_as_root(&ts, &["--userspec=sync", "/", "id", "-g"]) { - result.success().no_stderr().stdout_is("{gid}"); + result.success().no_stderr().stdout_is(format!("{gid}\n")); } else { println!("Test skipped; requires root user"); } From 0219fbe8c4e8e457db5efc6f4ac314b11c6094ed Mon Sep 17 00:00:00 2001 From: Cedric Erdelen Date: Wed, 11 Feb 2026 19:27:18 +0100 Subject: [PATCH 5/5] chroot: test failure output --- tests/by-util/test_chroot.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/by-util/test_chroot.rs b/tests/by-util/test_chroot.rs index a4ba0013d72..7d7a1c3b227 100644 --- a/tests/by-util/test_chroot.rs +++ b/tests/by-util/test_chroot.rs @@ -352,3 +352,18 @@ fn test_chroot_userspec_does_not_set_gid_with_uid() { println!("Test skipped; requires 'sync' user"); } } + +#[test] +fn test_chroot_userspec_unknown_uid() { + let ts = TestScenario::new(util_name!()); + if let Ok(result) = + run_ucmd_as_root(&ts, &["--userspec=99999", "--groups=root", "/", "id", "-g"]) + { + result + .failure() + .code_is(125) + .stderr_is("chroot: no group specified for unknown uid: 99999\n"); + } else { + println!("Test skipped; requires root user"); + } +}