| 1219 | #[cfg(unix)] |
| 1220 | #[allow(clippy::similar_names)] |
| 1221 | pub fn drop_privileges(policy: &SandboxPolicy) -> Result<()> { |
| 1222 | let user_name = match policy.process.run_as_user.as_deref() { |
| 1223 | Some(name) if !name.is_empty() => Some(name), |
| 1224 | _ => None, |
| 1225 | }; |
| 1226 | let group_name = match policy.process.run_as_group.as_deref() { |
| 1227 | Some(name) if !name.is_empty() => Some(name), |
| 1228 | _ => None, |
| 1229 | }; |
| 1230 | |
| 1231 | // If no user/group is configured and we are running as root, fall back to |
| 1232 | // "sandbox:sandbox" instead of silently keeping root. This covers the |
| 1233 | // local/dev-mode path where policies are loaded from disk and never pass |
| 1234 | // through the server-side `ensure_sandbox_process_identity` normalization. |
| 1235 | // For non-root runtimes, the no-op is safe -- we are already unprivileged. |
| 1236 | if user_name.is_none() && group_name.is_none() { |
| 1237 | if nix::unistd::geteuid().is_root() { |
| 1238 | let mut fallback = policy.clone(); |
| 1239 | fallback.process.run_as_user = Some("sandbox".into()); |
| 1240 | fallback.process.run_as_group = Some("sandbox".into()); |
| 1241 | return drop_privileges(&fallback); |
| 1242 | } |
| 1243 | return Ok(()); |
| 1244 | } |
| 1245 | |
| 1246 | // Resolve UID: numeric values are used directly; names resolve via passwd. |
| 1247 | let target_uid = match user_name { |
| 1248 | Some(name) if name.parse::<u32>().is_ok() => Uid::from_raw(name.parse().into_diagnostic()?), |
| 1249 | Some(name) => { |
| 1250 | User::from_name(name) |
| 1251 | .into_diagnostic()? |
| 1252 | .ok_or_else(|| miette::miette!("Sandbox user not found: {name}"))? |
| 1253 | .uid |
| 1254 | } |
| 1255 | None => nix::unistd::geteuid(), |
| 1256 | }; |
| 1257 | |
| 1258 | // Resolve group: if a numeric GID is configured use it directly. |
| 1259 | // Otherwise try name resolution, then fall back to current user's primary group. |
| 1260 | let target_gid = match group_name { |
| 1261 | Some(name) if name.parse::<u32>().is_ok() => Gid::from_raw(name.parse().into_diagnostic()?), |
| 1262 | Some(name) => { |
| 1263 | Group::from_name(name) |
| 1264 | .into_diagnostic()? |
| 1265 | .ok_or_else(|| miette::miette!("Sandbox group not found: {name}"))? |
| 1266 | .gid |
| 1267 | } |
| 1268 | None => match target_uid.as_raw() { |
| 1269 | 0 => nix::unistd::getegid(), |
| 1270 | _ => Group::from_gid( |
| 1271 | User::from_uid(target_uid) |
| 1272 | .into_diagnostic()? |
| 1273 | .ok_or_else(|| miette::miette!("Failed to resolve user from UID {target_uid}"))? |
| 1274 | .gid, |
| 1275 | ) |
| 1276 | .into_diagnostic()? |
| 1277 | .map_or_else(nix::unistd::getegid, |g| g.gid), |
| 1278 | }, |