Skip to content

Commit f0f3d99

Browse files
committed
Implement ID map parsing and translation
1 parent c6b8b21 commit f0f3d99

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

shell.nix

+3
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,8 @@ pkgs.mkShell {
88

99
# For llvm-objdump
1010
llvmPackages.bintools
11+
12+
# To aid testing
13+
runc
1114
];
1215
}

src/runc/container.rs

+12-11
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use std::fs::File;
22
use std::io::{BufRead, BufReader, Seek};
33
use std::path::Path;
44

5-
use anyhow::{bail, ensure, Context, Result};
6-
use rustix::fs::{FileType, Gid, Mode, Uid};
5+
use anyhow::{bail, Context, Result};
6+
use rustix::fs::{FileType, Mode};
77
use rustix::process::{Pid, Signal};
88
use tokio::io::unix::AsyncFd;
99
use tokio::io::Interest;
@@ -63,8 +63,10 @@ impl CgroupEventNotifier {
6363
}
6464

6565
pub struct Container {
66-
uid: Uid,
67-
gid: Gid,
66+
// Uid and gid of the primary container user.
67+
// Note that they're inside the user namespace (if any).
68+
uid: u32,
69+
gid: u32,
6870
pid: Pid,
6971
wait: tokio::sync::watch::Receiver<bool>,
7072
cgroup_device_filter: Mutex<Box<dyn DeviceAccessController + Send>>,
@@ -87,11 +89,9 @@ impl Container {
8789
Box::new(DeviceAccessControllerV2::new(&state.cgroup_paths.unified)?)
8890
};
8991

90-
ensure!(config.process.user.uid != u32::MAX && config.process.user.gid != u32::MAX);
91-
9292
Ok(Self {
93-
uid: unsafe { Uid::from_raw(config.process.user.uid) },
94-
gid: unsafe { Gid::from_raw(config.process.user.gid) },
93+
uid: config.process.user.uid,
94+
gid: config.process.user.gid,
9595
pid: Pid::from_raw(state.init_process_pid.try_into()?).context("Invalid PID")?,
9696
wait: recv,
9797
cgroup_device_filter: Mutex::new(cgroup_device_filter),
@@ -113,7 +113,8 @@ impl Container {
113113
}
114114

115115
pub async fn mknod(&self, node: &Path, (major, minor): (u32, u32)) -> Result<()> {
116-
crate::util::namespace::MntNamespace::of_pid(self.pid)?.enter(|| {
116+
let ns = crate::util::namespace::MntNamespace::of_pid(self.pid)?;
117+
ns.enter(|| {
117118
if let Some(parent) = node.parent() {
118119
let _ = std::fs::create_dir_all(parent);
119120
}
@@ -125,8 +126,8 @@ impl Container {
125126
Mode::from(0o644),
126127
rustix::fs::makedev(major, minor),
127128
)?;
128-
if !self.uid.is_root() {
129-
rustix::fs::chown(node, Some(self.uid), Some(self.gid))?;
129+
if self.uid != 0 {
130+
rustix::fs::chown(node, Some(ns.uid(self.uid)?), Some(ns.gid(self.gid)?))?;
130131
}
131132
Ok(())
132133
})?

src/util/namespace.rs

+57-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,70 @@
11
use std::fs::File;
22
use std::os::fd::AsFd;
3-
use std::os::unix::fs::MetadataExt;
3+
use std::path::Path;
44

5-
use anyhow::Result;
5+
use anyhow::{Context, Result};
66
use rustix::fs::{Gid, Uid};
77
use rustix::process::Pid;
88
use rustix::thread::{CapabilitiesSecureBits, LinkNameSpaceType, UnshareFlags};
99

10+
pub struct IdMap {
11+
map: Vec<(u32, u32, u32)>,
12+
}
13+
14+
impl IdMap {
15+
fn read(path: &Path) -> Result<Self> {
16+
Self::parse(&std::fs::read_to_string(path)?)
17+
}
18+
19+
fn parse(content: &str) -> Result<Self> {
20+
let mut map = Vec::new();
21+
for line in content.lines() {
22+
let mut words = line.split_ascii_whitespace();
23+
let inside = words.next().context("unexpected id_map")?.parse()?;
24+
let outside = words.next().context("unexpected id_map")?.parse()?;
25+
let count = words.next().context("unexpected id_map")?.parse()?;
26+
map.push((inside, outside, count));
27+
}
28+
Ok(Self { map })
29+
}
30+
31+
fn translate(&self, id: u32) -> Option<u32> {
32+
for &(inside, outside, count) in self.map.iter() {
33+
if (inside..inside.checked_add(count)?).contains(&id) {
34+
return (id - inside).checked_add(outside);
35+
}
36+
}
37+
None
38+
}
39+
}
40+
1041
pub struct MntNamespace {
11-
fd: File,
42+
mnt_fd: File,
43+
uid_map: IdMap,
44+
gid_map: IdMap,
1245
}
1346

1447
impl MntNamespace {
1548
/// Open the mount namespace of a process.
1649
pub fn of_pid(pid: Pid) -> Result<MntNamespace> {
17-
let path = format!("/proc/{}/ns/mnt", pid.as_raw_nonzero());
18-
let fd = File::open(path)?;
19-
Ok(MntNamespace { fd })
50+
let mnt_fd = File::open(format!("/proc/{}/ns/mnt", pid.as_raw_nonzero()))?;
51+
let uid_map = IdMap::read(format!("/proc/{}/uid_map", pid.as_raw_nonzero()).as_ref())?;
52+
let gid_map = IdMap::read(format!("/proc/{}/gid_map", pid.as_raw_nonzero()).as_ref())?;
53+
Ok(MntNamespace {
54+
mnt_fd,
55+
uid_map,
56+
gid_map,
57+
})
58+
}
59+
60+
/// Translate user ID into a UID in the namespace.
61+
pub fn uid(&self, uid: u32) -> Result<Uid> {
62+
Ok(unsafe { Uid::from_raw(self.uid_map.translate(uid).context("UID overflows")?) })
63+
}
64+
65+
/// Translate group ID into a GID in the namespace.
66+
pub fn gid(&self, gid: u32) -> Result<Gid> {
67+
Ok(unsafe { Gid::from_raw(self.gid_map.translate(gid).context("GID overflows")?) })
2068
}
2169

2270
/// Enter the mount namespace.
@@ -32,7 +80,7 @@ impl MntNamespace {
3280

3381
// Switch this particular thread to the container's mount namespace.
3482
rustix::thread::move_into_link_name_space(
35-
self.fd.as_fd(),
83+
self.mnt_fd.as_fd(),
3684
Some(LinkNameSpaceType::Mount),
3785
)?;
3886

@@ -51,18 +99,15 @@ impl MntNamespace {
5199
//
52100
// https://elixir.bootlin.com/linux/v6.11.1/source/fs/namei.c#L4073
53101
// https://elixir.bootlin.com/linux/v6.11.1/source/include/linux/cred.h#L111
54-
let metadata = std::fs::metadata("/")?;
55-
let uid = metadata.uid();
56-
let gid = metadata.gid();
57102

58103
// By default `setuid` will drop capabilities when transitioning from root
59104
// to non-root user. This bit prevents it so our code still have superpower.
60105
rustix::thread::set_capabilities_secure_bits(
61106
CapabilitiesSecureBits::NO_SETUID_FIXUP,
62107
)?;
63108

64-
rustix::thread::set_thread_uid(unsafe { Uid::from_raw(uid) })?;
65-
rustix::thread::set_thread_gid(unsafe { Gid::from_raw(gid) })?;
109+
rustix::thread::set_thread_uid(self.uid(0)?)?;
110+
rustix::thread::set_thread_gid(self.gid(0)?)?;
66111

67112
Ok(f())
68113
})

0 commit comments

Comments
 (0)