starry-kernel 0.10.0

A Linux-compatible OS kernel built on ArceOS unikernel
Documentation
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
//! 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;

#[cfg(feature = "rga")]
use linux_raw_sys::general::CAP_SYS_RAWIO;
use linux_raw_sys::general::{
    CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, CAP_IPC_LOCK, CAP_KILL, CAP_LAST_CAP, CAP_NET_RAW,
    CAP_PERFMON, CAP_SETGID, CAP_SETPCAP, CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_BOOT, CAP_SYS_MODULE,
    CAP_SYS_NICE, CAP_SYS_PTRACE, CAP_SYS_RESOURCE, CAP_SYS_TIME,
};

const CAP_MASK: u64 = (1u64 << (CAP_LAST_CAP + 1)) - 1;
const SECBIT_NO_SETUID_FIXUP: u32 = 1 << 2;
const SECBIT_KEEP_CAPS: u32 = 1 << 4;

/// 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,
    /// Linux securebits flags controlled through `prctl(2)`.
    /// Preserve permitted capabilities when all root UIDs become nonzero.
    pub securebits: u32,
    keep_capabilities: bool,
}

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,
            securebits: 0,
            keep_capabilities: false,
        }
    }

    /// 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.
    #[cfg(all(test, not(axtest)))]
    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,
            securebits: 0,
            keep_capabilities: false,
        }
    }

    /// Builds the subjective credential used by access checks with real IDs.
    /// The live credential is never changed while resolving a path.
    pub fn for_real_id_access(&self) -> Self {
        let mut access = self.clone();
        access.fsuid = self.uid;
        access.fsgid = self.gid;
        if self.securebits & SECBIT_NO_SETUID_FIXUP == 0 {
            access.cap_effective = if self.uid == 0 { self.cap_permitted } else { 0 };
        }
        access
    }

    /// 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) {
        if old.securebits & SECBIT_NO_SETUID_FIXUP != 0 {
            self.sanitize_capabilities();
            return;
        }

        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 {
            if old.securebits & SECBIT_KEEP_CAPS == 0 && !old.keep_capabilities {
                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 set the system clock
    /// (equivalent to `CAP_SYS_TIME`).
    pub fn has_cap_sys_time(&self) -> bool {
        self.has_cap(CAP_SYS_TIME)
    }

    /// Check whether this credential may bypass `RLIMIT_MEMLOCK`.
    pub fn has_cap_ipc_lock(&self) -> bool {
        self.has_cap(CAP_IPC_LOCK)
    }

    /// 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 bypass perf monitoring restrictions.
    ///
    /// Linux keeps `CAP_SYS_ADMIN` as a compatibility fallback for
    /// `CAP_PERFMON`.
    pub fn has_cap_perfmon(&self) -> bool {
        self.has_cap(CAP_PERFMON) || self.has_cap_sys_admin()
    }

    /// Check whether this credential may send signals across UID boundaries.
    pub fn has_cap_kill(&self) -> bool {
        self.has_cap(CAP_KILL)
    }

    /// 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.
    #[cfg(feature = "rga")]
    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.
    pub fn has_cap_sys_ptrace(&self) -> bool {
        self.has_cap(CAP_SYS_PTRACE)
    }

    /// 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)
    }

    /// Return the per-credential `PR_SET_KEEPCAPS` state.
    pub fn keep_capabilities(&self) -> bool {
        self.keep_capabilities
    }

    /// Update the per-credential `PR_SET_KEEPCAPS` state.
    pub fn set_keep_capabilities(&mut self, enabled: bool) {
        self.keep_capabilities = enabled;
    }
}

impl Default for Cred {
    fn default() -> Self {
        Self::root()
    }
}

#[cfg(all(test, not(axtest)))]
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 mut keepcaps_root = root.clone();
    keepcaps_root.set_keep_capabilities(true);
    let mut kept = keepcaps_root.clone();
    kept.uid = 1000;
    kept.euid = 1000;
    kept.suid = 1000;
    kept.cap_ambient = Cred::cap_mask();
    kept.apply_id_change_capability_rules(&keepcaps_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.
    #[cfg(feature = "rga")]
    let root_rawio = root.has_cap_sys_rawio();
    #[cfg(not(feature = "rga"))]
    let root_rawio = true;
    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_time()
        && root.has_cap_ipc_lock()
        && root.has_cap_sys_admin()
        && root.has_cap_sys_boot()
        && root_rawio
        && root.has_cap_sys_module()
        && root.has_cap_chown()
        && root.has_cap_dac_override()
        && root.has_cap_fowner()
        && root.has_cap_setpcap();

    // Root starts with every known effective capability, including
    // CAP_SYS_PTRACE.
    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);
    #[cfg(feature = "rga")]
    let net_raw_lacks_rawio = !net_raw_only.has_cap_sys_rawio();
    #[cfg(not(feature = "rga"))]
    let net_raw_lacks_rawio = true;
    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_lacks_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_sys_time()
        && !net_raw_only.has_cap_ipc_lock()
        && !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
        && kept.cap_permitted == Cred::cap_mask()
        && kept.cap_effective == 0
        && kept.cap_ambient == 0
        && kept.keep_capabilities()
        && 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)
}

#[cfg(all(test, not(axtest)))]
mod tests {
    use super::{Cred, SECBIT_NO_SETUID_FIXUP};

    #[test]
    fn real_id_access_applies_identity_and_capability_fixups() {
        let mut setuid_root = Cred::root();
        setuid_root.uid = 1000;
        setuid_root.gid = 100;
        let access = setuid_root.for_real_id_access();
        assert_eq!((access.fsuid, access.fsgid), (1000, 100));
        assert_eq!(access.cap_effective, 0);

        let mut dropped_effective = Cred::root();
        dropped_effective.euid = 1000;
        dropped_effective.fsuid = 1000;
        dropped_effective.egid = 100;
        dropped_effective.fsgid = 100;
        dropped_effective.cap_effective = 0;
        let access = dropped_effective.for_real_id_access();
        assert_eq!((access.fsuid, access.fsgid), (0, 0));
        assert_eq!(access.cap_effective, access.cap_permitted);

        setuid_root.securebits = SECBIT_NO_SETUID_FIXUP;
        let access = setuid_root.for_real_id_access();
        assert_eq!((access.fsuid, access.fsgid), (1000, 100));
        assert_eq!(access.cap_effective, setuid_root.cap_effective);
    }

    #[test]
    fn credential_capability_rules_hold() {
        assert!(super::credential_capability_rules_hold_for_test());
    }
}