1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
//! Process credentials and Linux capability state.
//!
//! This module keeps the per-thread credential snapshot used by StarryOS
//! permission checks. It tracks UID/GID families, supplementary groups, and
//! the five Linux capability sets exposed through `capget(2)`, `capset(2)`,
//! `/proc/<pid>/status`, and selected `prctl(2)` operations.
use alloc::sync::Arc;
use linux_raw_sys::general::{
CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_LAST_CAP, CAP_NET_RAW, CAP_SETGID, CAP_SETPCAP,
CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_BOOT, CAP_SYS_MODULE, CAP_SYS_NICE, CAP_SYS_RAWIO,
CAP_SYS_RESOURCE,
};
const CAP_MASK: u64 = (1u64 << (CAP_LAST_CAP + 1)) - 1;
/// Return the bit mask for a single Linux capability number.
fn cap_bit(cap: u32) -> u64 {
if cap <= CAP_LAST_CAP { 1u64 << cap } else { 0 }
}
/// Process credentials used for identity and permission checks.
///
/// The capability fields mirror Linux's inheritable, permitted, effective,
/// bounding, and ambient sets. StarryOS stores the currently known capability
/// range in a `u64`, which is sufficient for `CAP_LAST_CAP`.
#[derive(Clone, Debug)]
pub struct Cred {
/// Real user ID.
pub uid: u32,
/// Real group ID.
pub gid: u32,
/// Effective user ID.
pub euid: u32,
/// Effective group ID.
pub egid: u32,
/// Saved set-user-ID.
pub suid: u32,
/// Saved set-group-ID.
pub sgid: u32,
/// Filesystem user ID.
pub fsuid: u32,
/// Filesystem group ID.
pub fsgid: u32,
/// Supplementary group list.
pub groups: Arc<[u32]>,
/// Inheritable Linux capabilities.
pub cap_inheritable: u64,
/// Permitted Linux capabilities.
pub cap_permitted: u64,
/// Effective Linux capabilities.
pub cap_effective: u64,
/// Capability bounding set.
pub cap_bounding: u64,
/// Ambient Linux capabilities.
pub cap_ambient: u64,
}
impl Cred {
/// Return the mask of all Linux capabilities known to this kernel.
pub const fn cap_mask() -> u64 {
CAP_MASK
}
/// Create root credentials with all permitted/effective capabilities.
pub fn root() -> Self {
Self {
uid: 0,
gid: 0,
euid: 0,
egid: 0,
suid: 0,
sgid: 0,
fsuid: 0,
fsgid: 0,
groups: Arc::from([].as_slice()),
cap_inheritable: 0,
cap_permitted: CAP_MASK,
cap_effective: CAP_MASK,
cap_bounding: CAP_MASK,
cap_ambient: 0,
}
}
/// Create credentials for an unprivileged identity.
///
/// The bounding set remains full so future privileged transitions can
/// still be represented, but the effective/permitted/ambient sets start
/// empty.
pub fn unprivileged(uid: u32, gid: u32) -> Self {
Self {
uid,
gid,
euid: uid,
egid: gid,
suid: uid,
sgid: gid,
fsuid: uid,
fsgid: gid,
groups: Arc::from([].as_slice()),
cap_inheritable: 0,
cap_permitted: 0,
cap_effective: 0,
cap_bounding: CAP_MASK,
cap_ambient: 0,
}
}
/// Check whether a capability is present in the effective set.
pub fn has_cap(&self, cap: u32) -> bool {
self.cap_effective & cap_bit(cap) != 0
}
/// Recompute capability state after UID/GID credential changes.
///
/// This models the usual Linux setxid transitions: leaving all-root UID
/// state drops permitted/effective/ambient caps, losing euid 0 clears the
/// effective set, and regaining euid 0 restores effective from permitted.
pub fn apply_id_change_capability_rules(&mut self, old: &Self) {
let old_all_root = old.uid == 0 && old.euid == 0 && old.suid == 0;
let new_all_nonroot = self.uid != 0 && self.euid != 0 && self.suid != 0;
if old_all_root && new_all_nonroot {
self.cap_permitted = 0;
self.cap_effective = 0;
self.cap_ambient = 0;
} else if old.euid == 0 && self.euid != 0 {
self.cap_effective = 0;
self.cap_ambient = 0;
} else if old.euid != 0 && self.euid == 0 {
self.cap_effective = self.cap_permitted;
}
self.cap_permitted &= CAP_MASK;
self.cap_effective &= self.cap_permitted;
self.cap_inheritable &= CAP_MASK;
self.cap_bounding &= CAP_MASK;
self.cap_ambient &= self.cap_permitted & self.cap_inheritable;
}
/// Limit all capability sets to the kernel-known range and internal
/// invariants.
pub fn sanitize_capabilities(&mut self) {
self.cap_inheritable &= CAP_MASK;
self.cap_permitted &= CAP_MASK;
self.cap_effective &= self.cap_permitted;
self.cap_bounding &= CAP_MASK;
self.cap_ambient &= self.cap_permitted & self.cap_inheritable;
}
/// Check whether this credential has the privilege to change user IDs
/// (equivalent to `CAP_SETUID`).
pub fn has_cap_setuid(&self) -> bool {
self.has_cap(CAP_SETUID)
}
/// Check whether this credential has the privilege to change group IDs
/// (equivalent to `CAP_SETGID`).
pub fn has_cap_setgid(&self) -> bool {
self.has_cap(CAP_SETGID)
}
/// Check whether this credential may create raw network sockets
/// (equivalent to `CAP_NET_RAW`).
pub fn has_cap_net_raw(&self) -> bool {
self.has_cap(CAP_NET_RAW)
}
/// Check whether this credential may raise scheduling priority
/// (equivalent to `CAP_SYS_NICE`).
pub fn has_cap_sys_nice(&self) -> bool {
self.has_cap(CAP_SYS_NICE)
}
/// Check whether this credential may change process resource limits
/// (equivalent to `CAP_SYS_RESOURCE`).
pub fn has_cap_sys_resource(&self) -> bool {
self.has_cap(CAP_SYS_RESOURCE)
}
/// Check whether this credential may bypass file read/write/execute
/// permission checks (equivalent to `CAP_DAC_OVERRIDE`).
pub fn has_cap_dac_override(&self) -> bool {
self.has_cap(CAP_DAC_OVERRIDE)
}
/// Check whether this credential may perform broad system administration
/// operations (equivalent to `CAP_SYS_ADMIN`).
pub fn has_cap_sys_admin(&self) -> bool {
self.has_cap(CAP_SYS_ADMIN)
}
/// Check whether this credential may reboot the system
/// (equivalent to `CAP_SYS_BOOT`).
pub fn has_cap_sys_boot(&self) -> bool {
self.has_cap(CAP_SYS_BOOT)
}
/// Check whether this credential may perform raw I/O — direct access to
/// physical memory / device addresses (equivalent to `CAP_SYS_RAWIO`, the
/// capability Linux requires for `/dev/mem`-class access). Gates handing a
/// raw physical address to a DMA engine, which can otherwise reach arbitrary
/// system memory.
pub fn has_cap_sys_rawio(&self) -> bool {
self.has_cap(CAP_SYS_RAWIO)
}
/// Check whether this credential may load or unload kernel modules
/// (equivalent to `CAP_SYS_MODULE`).
pub fn has_cap_sys_module(&self) -> bool {
self.has_cap(CAP_SYS_MODULE)
}
/// Check whether this credential may inspect another process
/// (equivalent to `CAP_SYS_PTRACE` — approximated as euid == 0).
pub fn has_cap_sys_ptrace(&self) -> bool {
self.euid == 0
}
/// Check whether this credential has the privilege to change file
/// ownership (equivalent to `CAP_CHOWN`).
pub fn has_cap_chown(&self) -> bool {
self.has_cap(CAP_CHOWN)
}
/// Check whether this credential has the privilege to bypass file
/// ownership checks (equivalent to `CAP_FOWNER`).
pub fn has_cap_fowner(&self) -> bool {
self.has_cap(CAP_FOWNER)
}
/// Check whether the caller may adjust capability sets.
pub fn has_cap_setpcap(&self) -> bool {
self.has_cap(CAP_SETPCAP)
}
/// Return true if `gid` is the process's fsgid or is in its
/// supplementary group list. Uses fsgid (not egid) because this
/// method is used for filesystem permission checks.
pub fn in_group(&self, gid: u32) -> bool {
self.fsgid == gid || self.groups.contains(&gid)
}
}
impl Default for Cred {
fn default() -> Self {
Self::root()
}
}
#[cfg(axtest)]
pub(crate) fn credential_capability_rules_hold_for_test() -> bool {
let root = Cred::root();
let mut unprivileged = Cred::unprivileged(1000, 100);
let old_root = root.clone();
let mut dropped = root.clone();
dropped.uid = 1000;
dropped.euid = 1000;
dropped.suid = 1000;
dropped.cap_inheritable = Cred::cap_mask();
dropped.cap_ambient = Cred::cap_mask();
dropped.apply_id_change_capability_rules(&old_root);
let old_user = Cred::unprivileged(1000, 100);
let mut regained_effective = old_user.clone();
regained_effective.euid = 0;
regained_effective.cap_permitted = cap_bit(CAP_SETUID) | cap_bit(CAP_SETPCAP);
regained_effective.apply_id_change_capability_rules(&old_user);
unprivileged.fsgid = 200;
unprivileged.groups = Arc::from([10, 20].as_slice());
unprivileged.cap_permitted = cap_bit(CAP_SETUID);
unprivileged.cap_effective = cap_bit(CAP_SETUID) | cap_bit(CAP_SETPCAP);
unprivileged.cap_inheritable = !0;
unprivileged.cap_ambient = !0;
unprivileged.sanitize_capabilities();
// Exercise every has_cap_* helper at least once on a root credential so
// the bit checks are covered. All of these must be true for root.
let root_capability_helpers = root.has_cap_setuid()
&& root.has_cap_setgid()
&& root.has_cap_net_raw()
&& root.has_cap_sys_nice()
&& root.has_cap_sys_resource()
&& root.has_cap_sys_admin()
&& root.has_cap_sys_boot()
&& root.has_cap_sys_rawio()
&& root.has_cap_sys_module()
&& root.has_cap_chown()
&& root.has_cap_dac_override()
&& root.has_cap_fowner()
&& root.has_cap_setpcap();
// euid == 0 grants CAP_SYS_PTRACE under the StarryOS approximation.
let root_ptrace = root.has_cap_sys_ptrace();
// Build a credential with only CAP_NET_RAW effective to confirm the
// remaining capability helpers report false for non-root.
let mut net_raw_only = Cred::unprivileged(1000, 100);
net_raw_only.cap_effective = cap_bit(CAP_NET_RAW);
let selective_capability_helpers = net_raw_only.has_cap_net_raw()
&& !net_raw_only.has_cap_setuid()
&& !net_raw_only.has_cap_setgid()
&& !net_raw_only.has_cap_sys_admin()
&& !net_raw_only.has_cap_sys_boot()
&& !net_raw_only.has_cap_sys_rawio()
&& !net_raw_only.has_cap_sys_module()
&& !net_raw_only.has_cap_sys_nice()
&& !net_raw_only.has_cap_sys_resource()
&& !net_raw_only.has_cap_chown()
&& !net_raw_only.has_cap_dac_override()
&& !net_raw_only.has_cap_fowner()
&& !net_raw_only.has_cap_setpcap()
&& !net_raw_only.has_cap_sys_ptrace();
// The original root/unprivileged rules must still hold.
root_capability_helpers
&& root_ptrace
&& !Cred::unprivileged(1000, 100).has_cap_setuid()
&& selective_capability_helpers
&& dropped.cap_permitted == 0
&& dropped.cap_effective == 0
&& dropped.cap_ambient == 0
&& regained_effective.cap_effective == regained_effective.cap_permitted
&& unprivileged.cap_effective == cap_bit(CAP_SETUID)
&& unprivileged.cap_ambient == unprivileged.cap_permitted & unprivileged.cap_inheritable
&& unprivileged.in_group(200)
&& unprivileged.in_group(10)
&& !unprivileged.in_group(30)
}