nucleus/isolation/
usermap.rs1use crate::error::{NucleusError, Result};
2use std::fs;
3use tracing::{debug, info};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct IdMapping {
10 pub container_id: u32,
12 pub host_id: u32,
14 pub count: u32,
16}
17
18impl IdMapping {
19 pub fn new(container_id: u32, host_id: u32, count: u32) -> Self {
21 Self {
22 container_id,
23 host_id,
24 count,
25 }
26 }
27
28 pub fn validate(&self, allow_host_root: bool) -> crate::error::Result<()> {
33 if self.count == 0 {
34 return Err(NucleusError::ConfigError(
35 "ID mapping count must be non-zero".to_string(),
36 ));
37 }
38
39 if self.count > 65_536 {
41 return Err(NucleusError::ConfigError(format!(
42 "ID mapping count {} exceeds maximum 65536",
43 self.count
44 )));
45 }
46
47 if self.container_id.checked_add(self.count).is_none() {
49 return Err(NucleusError::ConfigError(format!(
50 "ID mapping overflow: container_id {} + count {} exceeds u32",
51 self.container_id, self.count
52 )));
53 }
54
55 if self.host_id.checked_add(self.count).is_none() {
57 return Err(NucleusError::ConfigError(format!(
58 "ID mapping overflow: host_id {} + count {} exceeds u32",
59 self.host_id, self.count
60 )));
61 }
62
63 if !allow_host_root && self.host_id == 0 && self.count > 0 {
65 return Err(NucleusError::ConfigError(
66 "ID mapping includes host UID/GID 0; use root-remapped mode if intentional"
67 .to_string(),
68 ));
69 }
70
71 Ok(())
72 }
73
74 pub fn rootless() -> Self {
76 let uid = nix::unistd::getuid().as_raw();
77 Self::new(0, uid, 1)
78 }
79
80 fn format(&self) -> String {
82 format!("{} {} {}\n", self.container_id, self.host_id, self.count)
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct UserNamespaceConfig {
89 pub uid_mappings: Vec<IdMapping>,
91 pub gid_mappings: Vec<IdMapping>,
93}
94
95impl UserNamespaceConfig {
96 pub fn rootless() -> Self {
100 let uid = nix::unistd::getuid().as_raw();
101 let gid = nix::unistd::getgid().as_raw();
102
103 Self {
104 uid_mappings: vec![IdMapping::new(0, uid, 1)],
105 gid_mappings: vec![IdMapping::new(0, gid, 1)],
106 }
107 }
108
109 pub fn root_remapped() -> Self {
114 Self {
115 uid_mappings: vec![IdMapping::new(0, 100_000, 65_536)],
116 gid_mappings: vec![IdMapping::new(0, 100_000, 65_536)],
117 }
118 }
119
120 pub fn custom(
122 uid_mappings: Vec<IdMapping>,
123 gid_mappings: Vec<IdMapping>,
124 ) -> crate::error::Result<Self> {
125 let allow_host_root = nix::unistd::Uid::effective().is_root();
126 for mapping in &uid_mappings {
127 mapping.validate(allow_host_root)?;
128 }
129 for mapping in &gid_mappings {
130 mapping.validate(allow_host_root)?;
131 }
132 Ok(Self {
133 uid_mappings,
134 gid_mappings,
135 })
136 }
137}
138
139pub struct UserNamespaceMapper {
143 config: UserNamespaceConfig,
144}
145
146impl UserNamespaceMapper {
147 pub fn new(config: UserNamespaceConfig) -> Self {
148 Self { config }
149 }
150
151 pub fn setup_mappings(&self) -> Result<()> {
156 info!("Setting up user namespace mappings");
157
158 self.write_setgroups_deny()?;
160
161 self.write_uid_map()?;
163
164 self.write_gid_map()?;
166
167 info!("Successfully configured user namespace mappings");
168 Ok(())
169 }
170
171 fn write_setgroups_deny(&self) -> Result<()> {
175 let path = "/proc/self/setgroups";
176 debug!("Writing 'deny' to {}", path);
177
178 fs::write(path, "deny\n").map_err(|e| {
179 NucleusError::NamespaceError(format!("Failed to write to {}: {}", path, e))
180 })?;
181
182 Ok(())
183 }
184
185 fn write_uid_map(&self) -> Result<()> {
187 let path = "/proc/self/uid_map";
188 let mut content = String::new();
189
190 for mapping in &self.config.uid_mappings {
191 content.push_str(&mapping.format());
192 }
193
194 debug!("Writing UID mappings to {}: {}", path, content.trim());
195
196 fs::write(path, &content).map_err(|e| {
197 NucleusError::NamespaceError(format!("Failed to write UID mappings: {}", e))
198 })?;
199
200 Ok(())
201 }
202
203 fn write_gid_map(&self) -> Result<()> {
205 let path = "/proc/self/gid_map";
206 let mut content = String::new();
207
208 for mapping in &self.config.gid_mappings {
209 content.push_str(&mapping.format());
210 }
211
212 debug!("Writing GID mappings to {}: {}", path, content.trim());
213
214 fs::write(path, &content).map_err(|e| {
215 NucleusError::NamespaceError(format!("Failed to write GID mappings: {}", e))
216 })?;
217
218 Ok(())
219 }
220
221 pub fn config(&self) -> &UserNamespaceConfig {
223 &self.config
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_id_mapping_format() {
233 let mapping = IdMapping::new(0, 1000, 1);
234 assert_eq!(mapping.format(), "0 1000 1\n");
235
236 let mapping = IdMapping::new(1000, 2000, 100);
237 assert_eq!(mapping.format(), "1000 2000 100\n");
238 }
239
240 #[test]
241 fn test_id_mapping_rootless() {
242 let mapping = IdMapping::rootless();
243 assert_eq!(mapping.container_id, 0);
244 assert_eq!(mapping.count, 1);
245 }
247
248 #[test]
249 fn test_user_namespace_config_rootless() {
250 let config = UserNamespaceConfig::rootless();
251 assert_eq!(config.uid_mappings.len(), 1);
252 assert_eq!(config.gid_mappings.len(), 1);
253 assert_eq!(config.uid_mappings[0].container_id, 0);
254 assert_eq!(config.gid_mappings[0].container_id, 0);
255 }
256
257 #[test]
258 fn test_user_namespace_config_custom() {
259 let uid_mappings = vec![IdMapping::new(0, 1000, 1), IdMapping::new(1000, 2000, 100)];
260 let gid_mappings = vec![IdMapping::new(0, 1000, 1)];
261
262 let config =
263 UserNamespaceConfig::custom(uid_mappings.clone(), gid_mappings.clone()).unwrap();
264 assert_eq!(config.uid_mappings, uid_mappings);
265 assert_eq!(config.gid_mappings, gid_mappings);
266 }
267
268 }