feat: Phase 2 - kernel capability bitmask (uid==0 -> has_cap())

Replace all 9 kernel uid==0 privilege checks with a capability bitmask
model. Adds caps:u64 field to Context and CallerCtx, with CAP_ALL for
root processes. Zero behavioral change - uid==0 still gets all caps.

New module: src/scheme/caps.rs with 10 capability constants.
9 check sites converted: acpi, irq, memory, debug, serio, sys (msr+write),
scheme registration, and fchown.

Patch: local/patches/kernel/P27-capability-bitmask.patch
This commit is contained in:
2026-05-29 10:25:09 +03:00
parent bb3ae6e63f
commit ce9ff8aebd
14 changed files with 164 additions and 10 deletions
@@ -0,0 +1,123 @@
diff --git a/src/context/context.rs b/src/context/context.rs
index 6d723f49..322cb30d 100644
--- a/src/context/context.rs
+++ b/src/context/context.rs
@@ -149,0 +150 @@ pub struct Context {
+ pub caps: u64,
@@ -207,0 +209 @@ impl Context {
+ caps: crate::scheme::caps::CAP_ALL,
@@ -479,0 +482,3 @@ impl Context {
+ pub fn has_cap(&self, cap: u64) -> bool {
+ self.caps & cap != 0
+ }
@@ -485,0 +491 @@ impl Context {
+ caps: self.caps,
diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs
index 5d734691..1326ff57 100644
--- a/src/scheme/acpi.rs
+++ b/src/scheme/acpi.rs
@@ -15,0 +16 @@ use crate::{
+ scheme::caps,
@@ -142 +143 @@ impl KernelScheme for AcpiScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(caps::CAP_ACPI) {
diff --git a/src/scheme/caps.rs b/src/scheme/caps.rs
new file mode 100644
index 00000000..481f2369
--- /dev/null
+++ b/src/scheme/caps.rs
@@ -0,0 +1,11 @@
+pub const CAP_SCHEME_REGISTER: u64 = 1 << 0;
+pub const CAP_PHYS_MEM: u64 = 1 << 1;
+pub const CAP_IRQ: u64 = 1 << 2;
+pub const CAP_ACPI: u64 = 1 << 3;
+pub const CAP_SYS_DEBUG: u64 = 1 << 4;
+pub const CAP_SYS_WRITE: u64 = 1 << 5;
+pub const CAP_SYS_MSR: u64 = 1 << 6;
+pub const CAP_SERIO: u64 = 1 << 7;
+pub const CAP_CHOWN: u64 = 1 << 8;
+pub const CAP_PROC_ATTR: u64 = 1 << 9;
+pub const CAP_ALL: u64 = !0u64;
diff --git a/src/scheme/debug.rs b/src/scheme/debug.rs
index 4a23b3cf..2ab347d7 100644
--- a/src/scheme/debug.rs
+++ b/src/scheme/debug.rs
@@ -76 +76 @@ impl KernelScheme for DebugScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(caps::CAP_SYS_DEBUG) {
diff --git a/src/scheme/irq.rs b/src/scheme/irq.rs
index 42229609..b1b893ac 100644
--- a/src/scheme/irq.rs
+++ b/src/scheme/irq.rs
@@ -20,0 +21 @@ use super::{CallerCtx, HandleMap, OpenResult, SchemeExt, StrOrBytes};
+use super::caps;
@@ -259 +260 @@ impl crate::scheme::KernelScheme for IrqScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(caps::CAP_IRQ) {
diff --git a/src/scheme/memory.rs b/src/scheme/memory.rs
index c2f9f474..29754518 100644
--- a/src/scheme/memory.rs
+++ b/src/scheme/memory.rs
@@ -11,0 +12 @@ use crate::{
+ scheme::caps,
@@ -235 +236 @@ impl KernelScheme for MemoryScheme {
- if ctx.uid != 0
+ if !ctx.has_cap(caps::CAP_PHYS_MEM)
diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs
index 765e547f..68bb8247 100644
--- a/src/scheme/mod.rs
+++ b/src/scheme/mod.rs
@@ -53,0 +54 @@ use self::{
+pub mod caps;
@@ -356 +357 @@ impl KernelScheme for SchemeList {
- if caller.uid != 0 {
+ if !caller.has_cap(caps::CAP_SCHEME_REGISTER) {
@@ -813,0 +815 @@ pub struct CallerCtx {
+ pub caps: u64,
@@ -815,0 +818,3 @@ impl CallerCtx {
+ pub fn has_cap(&self, cap: u64) -> bool {
+ self.caps & cap != 0
+ }
@@ -822,0 +828 @@ impl CallerCtx {
+ caps: self.caps,
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
index a9de02ea..ee033988 100644
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -1275,0 +1276 @@ impl ContextHandle {
+ guard.caps = if info.euid == 0 { crate::scheme::caps::CAP_ALL } else { 0 };
diff --git a/src/scheme/serio.rs b/src/scheme/serio.rs
index 26505021..b4ceab42 100644
--- a/src/scheme/serio.rs
+++ b/src/scheme/serio.rs
@@ -82 +82 @@ impl KernelScheme for SerioScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(caps::CAP_SERIO) {
diff --git a/src/scheme/sys/mod.rs b/src/scheme/sys/mod.rs
index 9eb35644..beed2ad5 100644
--- a/src/scheme/sys/mod.rs
+++ b/src/scheme/sys/mod.rs
@@ -26,0 +27 @@ use super::{CallerCtx, HandleMap, KernelScheme, OpenResult, StrOrBytes};
+use super::caps;
@@ -144 +145 @@ impl KernelScheme for SysScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(caps::CAP_SYS_MSR) {
@@ -170 +171 @@ impl KernelScheme for SysScheme {
- if matches!(entry.1, Wr(_)) && ctx.uid != 0 {
+ if matches!(entry.1, Wr(_)) && !ctx.has_cap(caps::CAP_SYS_WRITE) {
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index dfbf66b1..48fda536 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -29 +29 @@ use crate::{
- scheme::SchemeId,
+ scheme::{caps, SchemeId},
@@ -1593 +1593 @@ impl KernelScheme for UserScheme {
- if cx.euid != 0 && (uid != cx.euid || gid != cx.egid) {
+ if !cx.has_cap(caps::CAP_CHOWN) && (uid != cx.euid || gid != cx.egid) {
diff --git a/src/startup/mod.rs b/src/startup/mod.rs
index 86aabc22..4af65a37 100644
--- a/src/startup/mod.rs
+++ b/src/startup/mod.rs
@@ -190,0 +191 @@ pub(crate) fn kmain(bootstrap: Bootstrap) -> ! {
+ context.caps = crate::scheme::caps::CAP_ALL;
+2
View File
@@ -49,6 +49,8 @@ patches = [
"../../../local/patches/kernel/P25-cpuidle-deep-cstates.patch",
# P26: DebugDisplay proper scrolling — ptr::copy rows up instead of wrapping to top
"../../../local/patches/kernel/P26-debug-display-scroll.patch",
# P27: Capability bitmask — replace uid==0 checks with has_cap() capability model
"../../../local/patches/kernel/P27-capability-bitmask.patch",
]
[build]
@@ -147,6 +147,7 @@ pub struct Context {
// TODO: Temporary replacement for existing kernel logic, replace with capabilities!
pub euid: u32,
pub egid: u32,
pub caps: u64,
pub pid: usize,
/// Supplementary group IDs for access control decisions.
pub groups: Vec<u32>,
@@ -205,6 +206,7 @@ impl Context {
euid: 0,
egid: 0,
caps: crate::scheme::caps::CAP_ALL,
pid: 0,
groups: Vec::new(),
@@ -477,12 +479,16 @@ impl Context {
(for_thread, for_proc, sig)
}
pub fn has_cap(&self, cap: u64) -> bool {
self.caps & cap != 0
}
pub fn caller_ctx(&self) -> CallerCtx {
CallerCtx {
uid: self.euid,
gid: self.egid,
pid: self.pid,
groups: self.groups.clone(),
caps: self.caps,
}
}
}
@@ -13,6 +13,7 @@ use crate::{
arch::sleep,
context::file::InternalFlags,
event,
scheme::caps,
sync::{CleanLockToken, RwLock, WaitCondition, L1},
};
@@ -139,7 +140,7 @@ impl KernelScheme for AcpiScheme {
.or(Err(Error::new(EINVAL)))?
.trim_start_matches('/');
if ctx.uid != 0 {
if !ctx.has_cap(caps::CAP_ACPI) {
return Err(Error::new(EACCES));
}
if flags & O_CREAT == O_CREAT {
@@ -0,0 +1,11 @@
pub const CAP_SCHEME_REGISTER: u64 = 1 << 0;
pub const CAP_PHYS_MEM: u64 = 1 << 1;
pub const CAP_IRQ: u64 = 1 << 2;
pub const CAP_ACPI: u64 = 1 << 3;
pub const CAP_SYS_DEBUG: u64 = 1 << 4;
pub const CAP_SYS_WRITE: u64 = 1 << 5;
pub const CAP_SYS_MSR: u64 = 1 << 6;
pub const CAP_SERIO: u64 = 1 << 7;
pub const CAP_CHOWN: u64 = 1 << 8;
pub const CAP_PROC_ATTR: u64 = 1 << 9;
pub const CAP_ALL: u64 = !0u64;
@@ -73,7 +73,7 @@ impl KernelScheme for DebugScheme {
}
let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 {
if !ctx.has_cap(caps::CAP_SYS_DEBUG) {
return Err(Error::new(EPERM));
}
+2 -1
View File
@@ -18,6 +18,7 @@ use syscall::{
use crate::context::file::InternalFlags;
use super::{CallerCtx, HandleMap, OpenResult, SchemeExt, StrOrBytes};
use super::caps;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
use crate::arch::device::{ioapic, local_apic::ApicId};
@@ -256,7 +257,7 @@ impl crate::scheme::KernelScheme for IrqScheme {
}
let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 {
if !ctx.has_cap(caps::CAP_IRQ) {
return Err(Error::new(EACCES));
}
@@ -9,6 +9,7 @@ use crate::{
memory::{handle_notify_files, AddrSpace, AddrSpaceWrapper, Grant, PageSpan},
},
memory::{free_frames, used_frames, Frame, VirtualAddress, PAGE_SIZE},
scheme::caps,
sync::CleanLockToken,
syscall::{
data::{Map, StatVfs},
@@ -232,7 +233,7 @@ impl KernelScheme for MemoryScheme {
.ok_or(Error::new(ENOENT))?;
// TODO: Support arches with other default memory types?
if ctx.uid != 0
if !ctx.has_cap(caps::CAP_PHYS_MEM)
&& (!flags.is_empty()
|| !matches!(
(handle_ty, mem_ty),
+7 -1
View File
@@ -51,6 +51,7 @@ use self::{
};
/// When compiled with the "acpi" feature - `acpi:` - allows drivers to read a limited set of ACPI tables.
pub mod caps;
pub mod acpi;
pub mod dtb;
@@ -353,7 +354,7 @@ impl KernelScheme for SchemeList {
return Err(Error::new(EINVAL));
}
if caller.uid != 0 {
if !caller.has_cap(caps::CAP_SCHEME_REGISTER) {
return Err(Error::new(EACCES));
};
@@ -811,8 +812,12 @@ pub struct CallerCtx {
pub uid: u32,
pub gid: u32,
pub groups: alloc::vec::Vec<u32>,
pub caps: u64,
}
impl CallerCtx {
pub fn has_cap(&self, cap: u64) -> bool {
self.caps & cap != 0
}
pub fn filter_uid_gid(self, euid: u32, egid: u32) -> Self {
if self.uid == 0 && self.gid == 0 {
Self {
@@ -820,6 +825,7 @@ impl CallerCtx {
uid: euid,
gid: egid,
groups: self.groups,
caps: self.caps,
}
} else {
self
@@ -1273,6 +1273,7 @@ impl ContextHandle {
guard.pid = info.pid as usize;
guard.euid = info.euid;
guard.egid = info.egid;
guard.caps = if info.euid == 0 { crate::scheme::caps::CAP_ALL } else { 0 };
guard.prio = (info.prio as usize).min(39);
Ok(size_of::<ProcSchemeAttrs>())
}
@@ -79,7 +79,7 @@ impl KernelScheme for SerioScheme {
}
let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 {
if !ctx.has_cap(caps::CAP_SERIO) {
return Err(Error::new(EPERM));
}
@@ -24,6 +24,7 @@ use crate::{
};
use super::{CallerCtx, HandleMap, KernelScheme, OpenResult, StrOrBytes};
use super::caps;
mod block;
mod context;
@@ -141,7 +142,7 @@ impl KernelScheme for SysScheme {
} else if path.starts_with("msr/") {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if ctx.uid != 0 {
if !ctx.has_cap(caps::CAP_SYS_MSR) {
return Err(Error::new(EPERM));
}
let rest = &path[4..];
@@ -167,7 +168,7 @@ impl KernelScheme for SysScheme {
.find(|(entry_path, _)| *entry_path == path)
.ok_or(Error::new(ENOENT))?;
if matches!(entry.1, Wr(_)) && ctx.uid != 0 {
if matches!(entry.1, Wr(_)) && !ctx.has_cap(caps::CAP_SYS_WRITE) {
return Err(Error::new(EPERM));
}
@@ -26,7 +26,7 @@ use crate::{
},
event,
memory::{Frame, Page, VirtualAddress, PAGE_SIZE},
scheme::SchemeId,
scheme::{caps, SchemeId},
sync::{CleanLockToken, LockToken, Mutex, RwLock, WaitQueue, L1},
syscall::{
data::{Map, StdFsCallMeta},
@@ -1590,7 +1590,7 @@ impl KernelScheme for UserScheme {
{
let ctx = context::current();
let cx = &ctx.read(token.token());
if cx.euid != 0 && (uid != cx.euid || gid != cx.egid) {
if !cx.has_cap(caps::CAP_CHOWN) && (uid != cx.euid || gid != cx.egid) {
return Err(Error::new(EPERM));
}
}
@@ -188,6 +188,7 @@ pub(crate) fn kmain(bootstrap: Bootstrap) -> ! {
// TODO: Remove these from kernel
context.euid = 0;
context.egid = 0;
context.caps = crate::scheme::caps::CAP_ALL;
}
Err(_err) => halt_boot("FATAL: failed to spawn first userspace process userspace_init\n"),
}