Skip to main content

secure_exec_kernel/
user.rs

1#[derive(Debug, Clone, PartialEq, Eq)]
2pub struct ProcessIdentity {
3    pub uid: u32,
4    pub gid: u32,
5    pub euid: u32,
6    pub egid: u32,
7    pub supplementary_gids: Vec<u32>,
8}
9
10impl Default for ProcessIdentity {
11    fn default() -> Self {
12        Self {
13            uid: 1000,
14            gid: 1000,
15            euid: 1000,
16            egid: 1000,
17            supplementary_gids: vec![1000],
18        }
19    }
20}
21
22#[derive(Debug, Clone, Default, PartialEq, Eq)]
23pub struct UserConfig {
24    pub uid: Option<u32>,
25    pub gid: Option<u32>,
26    pub euid: Option<u32>,
27    pub egid: Option<u32>,
28    pub username: Option<String>,
29    pub homedir: Option<String>,
30    pub shell: Option<String>,
31    pub gecos: Option<String>,
32    pub group_name: Option<String>,
33    /// Supplementary groups are VM configuration, not guest-mutable state.
34    /// The primary gid is always injected and duplicate gids are dropped.
35    pub supplementary_gids: Vec<u32>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct UserManager {
40    pub uid: u32,
41    pub gid: u32,
42    pub euid: u32,
43    pub egid: u32,
44    pub username: String,
45    pub homedir: String,
46    pub shell: String,
47    pub gecos: String,
48    pub group_name: String,
49    pub supplementary_gids: Vec<u32>,
50}
51
52impl Default for UserManager {
53    fn default() -> Self {
54        Self::from_config(UserConfig::default())
55    }
56}
57
58impl UserManager {
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    pub fn from_config(config: UserConfig) -> Self {
64        let uid = config.uid.unwrap_or(1000);
65        let gid = config.gid.unwrap_or(1000);
66        let username = config.username.unwrap_or_else(|| String::from("user"));
67        let supplementary_gids = normalize_supplementary_gids(gid, config.supplementary_gids);
68
69        Self {
70            uid,
71            gid,
72            euid: config.euid.unwrap_or(uid),
73            egid: config.egid.unwrap_or(gid),
74            username: username.clone(),
75            homedir: config.homedir.unwrap_or_else(|| String::from("/home/user")),
76            shell: config.shell.unwrap_or_else(|| String::from("/bin/sh")),
77            gecos: config.gecos.unwrap_or_default(),
78            group_name: config.group_name.unwrap_or(username),
79            supplementary_gids,
80        }
81    }
82
83    pub fn identity(&self) -> ProcessIdentity {
84        ProcessIdentity {
85            uid: self.uid,
86            gid: self.gid,
87            euid: self.euid,
88            egid: self.egid,
89            supplementary_gids: self.supplementary_gids.clone(),
90        }
91    }
92
93    pub fn getgroups(&self) -> Vec<u32> {
94        self.supplementary_gids.clone()
95    }
96
97    pub fn getpwuid(&self, uid: u32) -> Option<String> {
98        if uid == self.uid {
99            return Some(format!(
100                "{}:x:{}:{}:{}:{}:{}",
101                self.username, self.uid, self.gid, self.gecos, self.homedir, self.shell
102            ));
103        }
104
105        None
106    }
107
108    pub fn getgrgid(&self, gid: u32) -> Option<String> {
109        if gid == self.gid {
110            return Some(format!(
111                "{}:x:{}:{}",
112                self.group_name, self.gid, self.username
113            ));
114        }
115
116        if self.supplementary_gids.contains(&gid) {
117            // Supplementary group names are synthetic because only numeric
118            // secondary group ids are configured for the VM.
119            let group_name = format!("group{gid}");
120            return Some(format!("{group_name}:x:{gid}:{}", self.username));
121        }
122
123        None
124    }
125}
126
127fn normalize_supplementary_gids(primary_gid: u32, supplementary_gids: Vec<u32>) -> Vec<u32> {
128    let mut normalized = Vec::with_capacity(supplementary_gids.len() + 1);
129    normalized.push(primary_gid);
130    for gid in supplementary_gids {
131        if !normalized.contains(&gid) {
132            normalized.push(gid);
133        }
134    }
135    normalized
136}