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:
@@ -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;
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user