1use std::path::{Path, PathBuf};
2use std::process::Command;
3use std::{env, fs};
4
5use nix::unistd::Pid;
6use oci_spec::runtime::{Linux, LinuxIdMapping, LinuxNamespace, LinuxNamespaceType, Mount, Spec};
7
8use crate::error::MissingSpecError;
9use crate::namespaces::{NamespaceError, Namespaces};
10use crate::syscall::syscall::{Syscall, create_syscall};
11use crate::utils;
12#[derive(Debug, Clone)]
16pub struct UserNamespaceIDMapper {
17 base_path: PathBuf,
18}
19
20impl Default for UserNamespaceIDMapper {
21 fn default() -> Self {
22 Self {
23 base_path: PathBuf::from("/proc"),
27 }
28 }
29}
30
31impl UserNamespaceIDMapper {
32 pub fn new() -> Self {
35 Default::default()
36 }
37
38 pub fn get_uid_path(&self, pid: &Pid) -> PathBuf {
39 self.base_path.join(pid.to_string()).join("uid_map")
40 }
41 pub fn get_gid_path(&self, pid: &Pid) -> PathBuf {
42 self.base_path.join(pid.to_string()).join("gid_map")
43 }
44
45 #[cfg(test)]
46 pub fn ensure_uid_path(&self, pid: &Pid) -> std::result::Result<(), std::io::Error> {
47 std::fs::create_dir_all(self.get_uid_path(pid).parent().unwrap())?;
48
49 Ok(())
50 }
51
52 #[cfg(test)]
53 pub fn ensure_gid_path(&self, pid: &Pid) -> std::result::Result<(), std::io::Error> {
54 std::fs::create_dir_all(self.get_gid_path(pid).parent().unwrap())?;
55
56 Ok(())
57 }
58
59 #[cfg(test)]
60 pub fn new_test(path: PathBuf) -> Self {
62 Self { base_path: path }
63 }
64}
65
66#[derive(Debug, thiserror::Error)]
67pub enum UserNamespaceError {
68 #[error(transparent)]
69 MissingSpec(#[from] crate::error::MissingSpecError),
70 #[error("user namespace definition is invalid")]
71 NoUserNamespace,
72 #[error("invalid spec for new user namespace container")]
73 InvalidSpec(#[from] ValidateSpecError),
74 #[error("failed to read unprivileged userns clone")]
75 ReadUnprivilegedUsernsClone(#[source] std::io::Error),
76 #[error("failed to parse unprivileged userns clone")]
77 ParseUnprivilegedUsernsClone(#[source] std::num::ParseIntError),
78 #[error("unknown userns clone value")]
79 UnknownUnprivilegedUsernsClone(u8),
80 #[error(transparent)]
81 IDMapping(#[from] MappingError),
82 #[error(transparent)]
83 OtherIO(#[from] std::io::Error),
84}
85
86type Result<T> = std::result::Result<T, UserNamespaceError>;
87
88#[derive(Debug, thiserror::Error)]
89pub enum ValidateSpecError {
90 #[error(transparent)]
91 MissingSpec(#[from] crate::error::MissingSpecError),
92 #[error("new user namespace requires valid uid mappings")]
93 NoUIDMappings,
94 #[error("new user namespace requires valid gid mappings")]
95 NoGIDMapping,
96 #[error("no mount in spec")]
97 NoMountSpec,
98 #[error("unprivileged user can't set supplementary groups")]
99 UnprivilegedUser,
100 #[error("supplementary group needs to be mapped in the gid mappings")]
101 GidNotMapped(u32),
102 #[error("failed to parse ID")]
103 ParseID(#[source] std::num::ParseIntError),
104 #[error("mount options require mapping valid uid inside the container with new user namespace")]
105 MountGidMapping(u32),
106 #[error("mount options require mapping valid gid inside the container with new user namespace")]
107 MountUidMapping(u32),
108 #[error(transparent)]
109 Namespaces(#[from] NamespaceError),
110 #[error(transparent)]
111 OtherIO(#[from] std::io::Error),
112}
113
114#[derive(Debug, thiserror::Error)]
115pub enum MappingError {
116 #[error("newuidmap/newgidmap binaries could not be found in path")]
117 BinaryNotFound,
118 #[error("could not find PATH")]
119 NoPathEnv,
120 #[error("failed to execute newuidmap/newgidmap")]
121 Execute(#[source] std::io::Error),
122 #[error("at least one id mapping needs to be defined")]
123 NoIDMapping,
124 #[error("failed to write id mapping")]
125 WriteIDMapping(#[source] std::io::Error),
126}
127
128#[derive(Debug, Clone, Default)]
129pub struct UserNamespaceConfig {
130 pub newuidmap: Option<PathBuf>,
132 pub newgidmap: Option<PathBuf>,
134 pub(crate) uid_mappings: Option<Vec<LinuxIdMapping>>,
136 pub(crate) gid_mappings: Option<Vec<LinuxIdMapping>>,
138 pub user_namespace: Option<LinuxNamespace>,
140 pub privileged: bool,
142 pub id_mapper: UserNamespaceIDMapper,
144}
145
146impl UserNamespaceConfig {
147 pub fn new(spec: &Spec) -> Result<Option<Self>> {
148 let syscall = create_syscall();
149 let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
150 let namespaces = Namespaces::try_from(linux.namespaces().as_ref())
151 .map_err(ValidateSpecError::Namespaces)?;
152 let user_namespace = namespaces
153 .get(LinuxNamespaceType::User)
154 .map_err(ValidateSpecError::Namespaces)?;
155
156 if user_namespace.is_some() && user_namespace.unwrap().path().is_none() {
157 tracing::debug!("container with new user namespace should be created");
158
159 validate_spec_for_new_user_ns(spec, &*syscall).map_err(|err| {
160 tracing::error!("failed to validate spec for new user namespace: {}", err);
161 err
162 })?;
163 let mut user_ns_config = UserNamespaceConfig::try_from(linux)?;
164 if let Some((uid_binary, gid_binary)) = lookup_map_binaries(linux)? {
165 user_ns_config.newuidmap = Some(uid_binary);
166 user_ns_config.newgidmap = Some(gid_binary);
167 }
168
169 Ok(Some(user_ns_config))
170 } else {
171 tracing::debug!("this container does NOT create a new user namespace");
172 Ok(None)
173 }
174 }
175
176 pub fn write_uid_mapping(&self, target_pid: Pid) -> Result<()> {
177 tracing::debug!("write UID mapping for {:?}", target_pid);
178 if let Some(uid_mappings) = self.uid_mappings.as_ref() {
179 write_id_mapping(
180 target_pid,
181 self.id_mapper.get_uid_path(&target_pid).as_path(),
182 uid_mappings,
183 self.newuidmap.as_deref(),
184 )?;
185 }
186 Ok(())
187 }
188
189 pub fn write_gid_mapping(&self, target_pid: Pid) -> Result<()> {
190 tracing::debug!("write GID mapping for {:?}", target_pid);
191 if let Some(gid_mappings) = self.gid_mappings.as_ref() {
192 write_id_mapping(
193 target_pid,
194 self.id_mapper.get_gid_path(&target_pid).as_path(),
195 gid_mappings,
196 self.newgidmap.as_deref(),
197 )?;
198 }
199 Ok(())
200 }
201
202 pub fn with_id_mapper(&mut self, mapper: UserNamespaceIDMapper) {
203 self.id_mapper = mapper
204 }
205}
206
207impl TryFrom<&Linux> for UserNamespaceConfig {
208 type Error = UserNamespaceError;
209
210 fn try_from(linux: &Linux) -> Result<Self> {
211 let namespaces = Namespaces::try_from(linux.namespaces().as_ref())
212 .map_err(ValidateSpecError::Namespaces)?;
213 let user_namespace = namespaces
214 .get(LinuxNamespaceType::User)
215 .map_err(ValidateSpecError::Namespaces)?;
216 let syscall = create_syscall();
217 Ok(Self {
218 newuidmap: None,
219 newgidmap: None,
220 uid_mappings: linux.uid_mappings().to_owned(),
221 gid_mappings: linux.gid_mappings().to_owned(),
222 user_namespace: user_namespace.cloned(),
223 privileged: !utils::rootless_required(&*syscall)?,
224 id_mapper: UserNamespaceIDMapper::new(),
225 })
226 }
227}
228
229pub fn unprivileged_user_ns_enabled() -> Result<bool> {
230 let user_ns_sysctl = Path::new("/proc/sys/kernel/unprivileged_userns_clone");
231 if !user_ns_sysctl.exists() {
232 return Ok(true);
233 }
234
235 let content = fs::read_to_string(user_ns_sysctl)
236 .map_err(UserNamespaceError::ReadUnprivilegedUsernsClone)?;
237
238 match content
239 .trim()
240 .parse::<u8>()
241 .map_err(UserNamespaceError::ParseUnprivilegedUsernsClone)?
242 {
243 0 => Ok(false),
244 1 => Ok(true),
245 v => Err(UserNamespaceError::UnknownUnprivilegedUsernsClone(v)),
246 }
247}
248
249fn validate_spec_for_new_user_ns(
252 spec: &Spec,
253 syscall: &dyn Syscall,
254) -> std::result::Result<(), ValidateSpecError> {
255 tracing::debug!(
256 ?spec,
257 "validating spec for container with new user namespace"
258 );
259 let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;
260
261 let gid_mappings = linux
262 .gid_mappings()
263 .as_ref()
264 .ok_or(ValidateSpecError::NoGIDMapping)?;
265 let uid_mappings = linux
266 .uid_mappings()
267 .as_ref()
268 .ok_or(ValidateSpecError::NoUIDMappings)?;
269
270 if uid_mappings.is_empty() {
271 return Err(ValidateSpecError::NoUIDMappings);
272 }
273 if gid_mappings.is_empty() {
274 return Err(ValidateSpecError::NoGIDMapping);
275 }
276
277 validate_mounts_for_new_user_ns(
278 spec.mounts()
279 .as_ref()
280 .ok_or(ValidateSpecError::NoMountSpec)?,
281 uid_mappings,
282 gid_mappings,
283 )?;
284
285 if let Some(additional_gids) = spec
286 .process()
287 .as_ref()
288 .and_then(|process| process.user().additional_gids().as_ref())
289 {
290 let privileged = !utils::rootless_required(syscall)?;
291
292 match (privileged, additional_gids.is_empty()) {
293 (true, false) => {
294 for gid in additional_gids {
295 if !is_id_mapped(*gid, gid_mappings) {
296 tracing::error!(
297 ?gid,
298 "gid is specified as supplementary group, but is not mapped in the user namespace"
299 );
300 return Err(ValidateSpecError::GidNotMapped(*gid));
301 }
302 }
303 }
304 (false, false) => {
305 tracing::error!(
306 user = ?syscall.get_euid(),
307 "user is unprivileged. Supplementary groups cannot be set in \
308 a rootless container for this user due to CVE-2014-8989",
309 );
310 return Err(ValidateSpecError::UnprivilegedUser);
311 }
312 _ => {}
313 }
314 }
315
316 Ok(())
317}
318
319fn validate_mounts_for_new_user_ns(
320 mounts: &[Mount],
321 uid_mappings: &[LinuxIdMapping],
322 gid_mappings: &[LinuxIdMapping],
323) -> std::result::Result<(), ValidateSpecError> {
324 for mount in mounts {
325 if let Some(options) = mount.options() {
326 for opt in options {
327 if opt.starts_with("uid=")
328 && !is_id_mapped(
329 opt[4..].parse().map_err(ValidateSpecError::ParseID)?,
330 uid_mappings,
331 )
332 {
333 tracing::error!(
334 ?mount,
335 ?opt,
336 "mount specifies option which is not mapped inside the container with new user namespace"
337 );
338 return Err(ValidateSpecError::MountUidMapping(
339 opt[4..].parse().map_err(ValidateSpecError::ParseID)?,
340 ));
341 }
342
343 if opt.starts_with("gid=")
344 && !is_id_mapped(
345 opt[4..].parse().map_err(ValidateSpecError::ParseID)?,
346 gid_mappings,
347 )
348 {
349 tracing::error!(
350 ?mount,
351 ?opt,
352 "mount specifies option which is not mapped inside the container with new user namespace"
353 );
354 return Err(ValidateSpecError::MountGidMapping(
355 opt[4..].parse().map_err(ValidateSpecError::ParseID)?,
356 ));
357 }
358 }
359 }
360 }
361
362 Ok(())
363}
364
365fn is_id_mapped(id: u32, mappings: &[LinuxIdMapping]) -> bool {
366 mappings
367 .iter()
368 .any(|m| id >= m.container_id() && id <= m.container_id() + m.size())
369}
370
371pub fn lookup_map_binaries(
374 spec: &Linux,
375) -> std::result::Result<Option<(PathBuf, PathBuf)>, MappingError> {
376 if let Some(uid_mappings) = spec.uid_mappings() {
377 if uid_mappings.len() == 1 && uid_mappings.len() == 1 {
378 return Ok(None);
379 }
380
381 let uidmap = lookup_map_binary("newuidmap")?;
382 let gidmap = lookup_map_binary("newgidmap")?;
383
384 match (uidmap, gidmap) {
385 (Some(newuidmap), Some(newgidmap)) => Ok(Some((newuidmap, newgidmap))),
386 _ => Err(MappingError::BinaryNotFound),
387 }
388 } else {
389 Ok(None)
390 }
391}
392
393fn lookup_map_binary(binary: &str) -> std::result::Result<Option<PathBuf>, MappingError> {
394 let paths = env::var("PATH").map_err(|_| MappingError::NoPathEnv)?;
395 Ok(paths
396 .split_terminator(':')
397 .map(|p| Path::new(p).join(binary))
398 .find(|p| p.exists()))
399}
400
401fn write_id_mapping(
402 pid: Pid,
403 map_file: &Path,
404 mappings: &[LinuxIdMapping],
405 map_binary: Option<&Path>,
406) -> std::result::Result<(), MappingError> {
407 tracing::debug!("Write ID mapping: {:?}", mappings);
408
409 match mappings.len() {
410 0 => return Err(MappingError::NoIDMapping),
411 1 => {
412 let mapping = mappings
413 .first()
414 .and_then(|m| format!("{} {} {}", m.container_id(), m.host_id(), m.size()).into())
415 .unwrap();
416 std::fs::write(map_file, &mapping).map_err(|err| {
417 tracing::error!(?err, ?map_file, ?mapping, "failed to write uid/gid mapping");
418 MappingError::WriteIDMapping(err)
419 })?;
420 }
421 _ => {
422 let args: Vec<String> = mappings
423 .iter()
424 .flat_map(|m| {
425 [
426 m.container_id().to_string(),
427 m.host_id().to_string(),
428 m.size().to_string(),
429 ]
430 })
431 .collect();
432
433 Command::new(map_binary.unwrap())
437 .arg(pid.to_string())
438 .args(args)
439 .output()
440 .map_err(|err| {
441 tracing::error!(?err, ?map_binary, "failed to execute newuidmap/newgidmap");
442 MappingError::Execute(err)
443 })?;
444 }
445 }
446
447 Ok(())
448}
449
450#[cfg(test)]
451mod tests {
452 use std::fs;
453
454 use anyhow::Result;
455 use nix::unistd::getpid;
456 use oci_spec::runtime::{
457 LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespaceBuilder, SpecBuilder,
458 };
459 use rand::RngExt;
460 use serial_test::serial;
461
462 use super::*;
463
464 fn gen_u32() -> u32 {
465 rand::rng().random()
466 }
467
468 #[test]
469 fn test_validate_ok() -> Result<()> {
470 let syscall = create_syscall();
471 let userns = LinuxNamespaceBuilder::default()
472 .typ(LinuxNamespaceType::User)
473 .build()?;
474 let uid_mappings = vec![
475 LinuxIdMappingBuilder::default()
476 .host_id(gen_u32())
477 .container_id(0_u32)
478 .size(10_u32)
479 .build()?,
480 ];
481 let gid_mappings = vec![
482 LinuxIdMappingBuilder::default()
483 .host_id(gen_u32())
484 .container_id(0_u32)
485 .size(10_u32)
486 .build()?,
487 ];
488 let linux = LinuxBuilder::default()
489 .namespaces(vec![userns])
490 .uid_mappings(uid_mappings)
491 .gid_mappings(gid_mappings)
492 .build()?;
493 let spec = SpecBuilder::default().linux(linux).build()?;
494 assert!(validate_spec_for_new_user_ns(&spec, &*syscall).is_ok());
495 Ok(())
496 }
497
498 #[test]
499 fn test_validate_err() -> Result<()> {
500 let syscall = create_syscall();
501 let userns = LinuxNamespaceBuilder::default()
502 .typ(LinuxNamespaceType::User)
503 .build()?;
504 let uid_mappings = vec![
505 LinuxIdMappingBuilder::default()
506 .host_id(gen_u32())
507 .container_id(0_u32)
508 .size(10_u32)
509 .build()?,
510 ];
511 let gid_mappings = vec![
512 LinuxIdMappingBuilder::default()
513 .host_id(gen_u32())
514 .container_id(0_u32)
515 .size(10_u32)
516 .build()?,
517 ];
518
519 let linux_uid_empty = LinuxBuilder::default()
520 .namespaces(vec![userns.clone()])
521 .uid_mappings(vec![])
522 .gid_mappings(gid_mappings.clone())
523 .build()?;
524 assert!(
525 validate_spec_for_new_user_ns(
526 &SpecBuilder::default()
527 .linux(linux_uid_empty)
528 .build()
529 .unwrap(),
530 &*syscall
531 )
532 .is_err()
533 );
534
535 let linux_gid_empty = LinuxBuilder::default()
536 .namespaces(vec![userns.clone()])
537 .uid_mappings(uid_mappings.clone())
538 .gid_mappings(vec![])
539 .build()?;
540 assert!(
541 validate_spec_for_new_user_ns(
542 &SpecBuilder::default()
543 .linux(linux_gid_empty)
544 .build()
545 .unwrap(),
546 &*syscall
547 )
548 .is_err()
549 );
550
551 let linux_uid_none = LinuxBuilder::default()
552 .namespaces(vec![userns.clone()])
553 .gid_mappings(gid_mappings)
554 .build()?;
555 assert!(
556 validate_spec_for_new_user_ns(
557 &SpecBuilder::default()
558 .linux(linux_uid_none)
559 .build()
560 .unwrap(),
561 &*syscall
562 )
563 .is_err()
564 );
565
566 let linux_gid_none = LinuxBuilder::default()
567 .namespaces(vec![userns])
568 .uid_mappings(uid_mappings)
569 .build()?;
570 assert!(
571 validate_spec_for_new_user_ns(
572 &SpecBuilder::default()
573 .linux(linux_gid_none)
574 .build()
575 .unwrap(),
576 &*syscall
577 )
578 .is_err()
579 );
580
581 Ok(())
582 }
583
584 #[test]
585 #[serial]
586 fn test_write_uid_mapping() -> Result<()> {
587 let userns = LinuxNamespaceBuilder::default()
588 .typ(LinuxNamespaceType::User)
589 .build()?;
590 let host_uid = gen_u32();
591 let host_gid = gen_u32();
592 let container_id = 0_u32;
593 let size = 10_u32;
594 let uid_mappings = vec![
595 LinuxIdMappingBuilder::default()
596 .host_id(host_uid)
597 .container_id(container_id)
598 .size(size)
599 .build()?,
600 ];
601 let gid_mappings = vec![
602 LinuxIdMappingBuilder::default()
603 .host_id(host_gid)
604 .container_id(container_id)
605 .size(size)
606 .build()?,
607 ];
608 let linux = LinuxBuilder::default()
609 .namespaces(vec![userns])
610 .uid_mappings(uid_mappings)
611 .gid_mappings(gid_mappings)
612 .build()?;
613 let spec = SpecBuilder::default().linux(linux).build()?;
614
615 let pid = getpid();
616 let tmp = tempfile::tempdir()?;
617 let id_mapper = UserNamespaceIDMapper {
618 base_path: tmp.path().to_path_buf(),
619 };
620 id_mapper.ensure_uid_path(&pid)?;
621
622 let mut config = UserNamespaceConfig::new(&spec)?.unwrap();
623 config.with_id_mapper(id_mapper.clone());
624 config.write_uid_mapping(pid)?;
625 assert_eq!(
626 format!("{container_id} {host_uid} {size}"),
627 fs::read_to_string(id_mapper.get_uid_path(&pid))?
628 );
629 config.write_gid_mapping(pid)?;
630 Ok(())
631 }
632
633 #[test]
634 #[serial]
635 fn test_write_gid_mapping() -> Result<()> {
636 let userns = LinuxNamespaceBuilder::default()
637 .typ(LinuxNamespaceType::User)
638 .build()?;
639 let host_uid = gen_u32();
640 let host_gid = gen_u32();
641 let container_id = 0_u32;
642 let size = 10_u32;
643 let uid_mappings = vec![
644 LinuxIdMappingBuilder::default()
645 .host_id(host_uid)
646 .container_id(container_id)
647 .size(size)
648 .build()?,
649 ];
650 let gid_mappings = vec![
651 LinuxIdMappingBuilder::default()
652 .host_id(host_gid)
653 .container_id(container_id)
654 .size(size)
655 .build()?,
656 ];
657 let linux = LinuxBuilder::default()
658 .namespaces(vec![userns])
659 .uid_mappings(uid_mappings)
660 .gid_mappings(gid_mappings)
661 .build()?;
662 let spec = SpecBuilder::default().linux(linux).build()?;
663
664 let pid = getpid();
665 let tmp = tempfile::tempdir()?;
666 let id_mapper = UserNamespaceIDMapper {
667 base_path: tmp.path().to_path_buf(),
668 };
669 id_mapper.ensure_gid_path(&pid)?;
670
671 let mut config = UserNamespaceConfig::new(&spec)?.unwrap();
672 config.with_id_mapper(id_mapper.clone());
673 config.write_gid_mapping(pid)?;
674 assert_eq!(
675 format!("{container_id} {host_gid} {size}"),
676 fs::read_to_string(id_mapper.get_gid_path(&pid))?
677 );
678 Ok(())
679 }
680}