15 Commits

Author SHA1 Message Date
vasilito 520e92cad8 chore: checkpoint before 0.2.3 build system migration 2026-05-29 21:53:11 +03:00
vasilito aa9d14a90e docs: update AGENTS.md and PATCH-GOVERNANCE.md
AGENTS.md: updated session progress, coretempd/login fix notes, Intel plan references. PATCH-GOVERNANCE.md: added mega-patch discipline section and P-patch workflow documentation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-29 21:50:28 +03:00
vasilito 44bcf2b75a docs: add Intel driver modernization plan
Comprehensive 6-phase plan (1,055 lines) for updating the Intel GPU driver from current 1,590-line stub to full Gen9+ support ported from Linux 7.1 i915. Covers register abstraction, GMBUS I2C, DMC firmware, power wells, CDCLK, display pipeline, modesetting, and hardware validation.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-29 21:50:07 +03:00
vasilito 7cd5bfbb83 fix: enable redox-rt proc feature in userutils to fix login crash
userutils compiled redox-rt with default-features=false, disabling the proc feature. This caused login's fork to not pass proc fd to child shell, triggering assertion failed: info.has_proc_fd in redox-rt. P8 patch enables features=['proc']. Verified: zero panics on boot, login works for user/root.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-29 21:49:47 +03:00
vasilito 5987fffde7 fix: P12 init_debug import error in base init
The init_debug macro was used without importing it, causing a compile error. P12 patch adds the missing import. Wired into base recipe.toml patches list.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-29 21:49:07 +03:00
vasilito 706050482b fix: rewrite coretempd to use redox_scheme Socket + SchemeSync
Replaced broken UnixListener::bind(':coretemp') with proper redox_scheme::Socket::create() + SchemeSync trait impl. Event loop uses next_request/handle_sync/write_response pattern. Verified: registers scheme:coretemp, detects CPU info, zero panics.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-29 21:48:51 +03:00
vasilito daf131d435 P7 login diagnostics, P11 init noise reduction, config layering fix 2026-05-29 19:13:16 +03:00
vasilito 0ccc233131 P10: fix Arrow Lake device IDs and DMC firmware mapping
The driver incorrectly labeled Lunar Lake IDs (0x6420/64A0/64B0) as
Arrow Lake, and placed the real Arrow Lake IDs (0x7D41/7D51/etc.) in the
Meteor Lake bucket. This meant:
- Arrow Lake notebooks were misidentified as Meteor Lake
- Lunar Lake was completely missing from the device tables
- The 0xB640 ID (ARL-H) was also misfiled

Fix:
- Move real Arrow Lake IDs (0x7D41, 0x7D51, 0x7DD1, 0x7D67, 0xB640)
  to INTEL_GEN12_ARL_IDS
- Move Lunar Lake IDs (0x6420, 0x64A0, 0x64B0) to INTEL_GEN12_LNL_IDS
- Map Arrow Lake DMC firmware to INTEL_MTL_DMC_KEYS (mtl_dmc.bin),
  since Arrow Lake uses the same display IP 14.0 as Meteor Lake
- Remove Arrow Lake IDs from the Meteor Lake bucket

Per Linux 7.1 reference: Arrow Lake display engine is IP 14.0 (Xe_LPD+),
same as Meteor Lake — NOT Xe2. The i915-style register programming is
correct for Arrow Lake.
2026-05-29 17:49:11 +03:00
vasilito c0a93e5cfa Add input driver init services and driver-manager configs
- ps2d: PS/2 keyboard/mouse (init service + driver-manager wildcard match)
- i2c-hidd: I2C HID keyboard/touchpad (init service + driver-manager match)
- intel-thc-hidd: Intel Touch Host Controller HID (init service + PCI match)

Fixes i2c-hidd path: /usr/bin/i2c-hidd (not /usr/lib/drivers/).

These drivers were already built in the base package but were not wired
into the boot process. Modern Intel notebooks typically use either
i8042 EC emulation (PS/2) or I2C HID for keyboard/touchpad.
2026-05-29 15:44:35 +03:00
vasilito 0a4a77a56b P27: add missing proc.rs guard.caps derivation from euid
The capability bitmask patch was missing the critical proc.rs hunk that
derives caps from euid when procmgr writes ProcSchemeAttrs to a child
context. Without this, all child processes had caps=0, causing
EACCES on dup("create-scheme") and crashing boot.

Fix: in ContextHandle::Attr kwrite path, after setting euid/egid/pid/prio,
set guard.caps = CAP_ALL when euid==0, else 0.
2026-05-29 14:25:37 +03:00
vasilito d2c761a56c fix: P27 caps init + P6 type errors and overlap with P5
P27: add caps: 0 to Context::new() default initialization.
P6: fix syscall::Error vs libredox::Error type mismatch (use ?),
    fix usize->u32 casts for Resugid fields, remove P5 overlap
    (issue/motd/consecutive_failures already in P5), add namespace
    isolation to password-verified auth path.

All 39 kernel patches validate. Full image builds.
2026-05-29 12:24:51 +03:00
vasilito f40b751bca fix: regenerate P27 kernel capability patch against clean P0-P26 baseline
P27-capability-bitmask.patch was generated against an incorrectly
patched source tree, causing hunk mismatches in validate-patches.
Regenerated from clean upstream + P0-P26 baseline using git diff -U0 -w.

All 39 patches now validate successfully.
2026-05-29 11:37:22 +03:00
vasilito ce9ff8aebd 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
2026-05-29 10:25:09 +03:00
vasilito bb3ae6e63f feat: Phase 1 - Plan 9 namespace privilege drop + branding
- login.rs: drop privileges via setresugid after authentication
- login.rs: add namespace isolation to password auth path (was missing)
- login.rs: add drm, input schemes to DEFAULT_SCHEMES
- sudo service: rename 00_sudo -> 12_sudo, type daemon (no boot block)
- Branded login screen with figlet RedBear OS v0.2.2 'Liliya'
- Root user kept but not advertised on login screen
- P6-login-privilege-drop.patch generated and wired

Implements Phase 1 of Plan 9 namespace privilege model:
login creates restricted namespace (mkns/setns) then drops
uid/gid to authenticated user before spawning shell.
2026-05-29 09:54:28 +03:00
vasilito 61135b0cce chore: bump OS version to 0.2.2 2026-05-29 09:06:24 +03:00
40 changed files with 25001 additions and 127 deletions
+110 -3
View File
@@ -1,6 +1,6 @@
# RED BEAR OS BUILD SYSTEM — PROJECT KNOWLEDGE BASE # RED BEAR OS BUILD SYSTEM — PROJECT KNOWLEDGE BASE
**Generated:** 2026-04-12 (P1/P2 complete) **Generated:** 2026-04-12 (P1/P2 complete) · Updated: 2026-05-29 (mega-patch discipline)
**Toolchain:** Rust nightly-2025-10-03 (edition 2024) **Toolchain:** Rust nightly-2025-10-03 (edition 2024)
**Architecture:** Microkernel OS in Rust, ~38k files, ~294k LoC Rust **Architecture:** Microkernel OS in Rust, ~38k files, ~294k LoC Rust
**Target Hardware**: AMD64 bare metal, with AMD and Intel machines treated as equal-priority Red Bear OS targets **Target Hardware**: AMD64 bare metal, with AMD and Intel machines treated as equal-priority Red Bear OS targets
@@ -812,10 +812,116 @@ patches must be respected:
When reordering patches: remove the source tree, re-fetch, and rebuild to verify. When reordering patches: remove the source tree, re-fetch, and rebuild to verify.
### MEGA-PATCH + INDIVIDUAL PATCH DISCIPLINE (CRITICAL)
**This is the single most common source of build failures in Red Bear OS.**
Violating these rules causes patches to silently drift, overlap, or conflict —
wasting hours of debugging time.
#### The Two-Layer Architecture
Each patched component (base, kernel, relibc) uses two patch layers:
1. **The mega-patch** (`redox.patch`) — a single consolidated `git diff` of the
entire source tree against upstream. Applied first. This is the **frozen baseline**
of all Red Bear work up to a known date.
2. **Individual P-patches** (`P10-*.patch`, `P11-*.patch`, etc.) — granular,
single-purpose patches for new work done AFTER the mega-patch was generated.
Applied after the mega-patch, in listed order.
The `recipe.toml` patches list looks like:
```toml
patches = [
"redox.patch", # Layer 1: frozen baseline
"P10-rootfs-uuid-search-no-block.patch", # Layer 2: new work on top
"P11-init-noise-reduction.patch", # Layer 2: new work on top
]
```
#### The Discipline (MANDATORY)
| Rule | Why | What happens if violated |
|------|-----|--------------------------|
| **NEVER regenerate `redox.patch` from a source tree that includes P-patches** | The mega-patch absorbs P-patch changes, making P-patches redundant. They then fail to apply ("Reversed or previously applied") on the next clean build. | P-patches conflict with their own changes inside the mega-patch. Build fails. |
| **New work always goes as P-patches AFTER the mega-patch** | Keeps the mega-patch frozen. Each P-patch is a small, reviewable delta. | Mixing changes into the mega-patch makes it a monolith with no logical structure. |
| **To regenerate `redox.patch`, first fold all P-patches into it, then remove them from `recipe.toml`** | Consolidation pass must be atomic — absorb and remove in one step. | Orphan P-patches in `recipe.toml` reference changes already in the mega-patch. |
| **P-patches MUST apply cleanly on top of the mega-patch-only state** | The build system applies patches sequentially: upstream → mega-patch → P-patches. | Build fails on clean fetch. |
| **Validate with `repo validate-patches` after ANY patch change** | Catches drift before it reaches the build. | Drift silently accumulates until the next clean build explodes. |
#### How to Make a New Change (Correct Workflow)
```bash
# 1. Source tree already has mega-patch + existing P-patches applied (working tree)
cd recipes/core/base/source
# 2. Make your edit
vim init/src/main.rs
# 3. Generate the patch against the current git HEAD (upstream rev)
git diff -U0 -w -- init/src/main.rs > ../../../local/patches/base/P<next>-<desc>.patch
# 4. Create symlink and wire into recipe.toml
cd ../../../recipes/core/base
ln -s ../../../local/patches/base/P<next>-<desc>.patch P<next>-<desc>.patch
# Add "P<next>-<desc>.patch" to patches list in recipe.toml
# 5. Validate and rebuild
cd ../../..
./target/release/repo validate-patches base
CI=1 ./target/release/repo cook base-initfs
```
#### How to Consolidate (Periodic Maintenance)
When P-patches accumulate and the mega-patch should absorb them:
```bash
# 1. Source tree has mega-patch + all P-patches applied
cd recipes/core/base/source
# 2. Generate NEW mega-patch from full diff
git diff -U0 -w > ../../../local/patches/base/redox.patch
# 3. Remove ALL P-patches from recipe.toml patches list
# Keep P-patch FILES in local/patches/base/ for history — just remove from recipe.toml
# 4. Validate
./target/release/repo validate-patches base
# 5. Rebuild
CI=1 ./target/release/repo cook base-initfs
```
#### How NOT to Break Things
| Action | Correct | WRONG |
|--------|---------|-------|
| Add new feature | Create P-patch, add after mega-patch in recipe.toml | Regenerate mega-patch from tree that includes P-patches |
| Fix a bug | Create P-patch | Edit mega-patch directly |
| Consolidate | Regenerate mega-patch, remove ALL P-patches from recipe.toml | Regenerate mega-patch but leave P-patches in recipe.toml |
| Update upstream | Provision new release, rebase mega-patch | Cherry-pick upstream commits into source tree |
#### Root Cause of Past Failures
The pattern that has recurred multiple times:
1. Mega-patch generated at time T
2. P-patches added at time T+1, T+2, etc.
3. Someone regenerates mega-patch from source tree at T+3 (which includes P-patch changes)
4. Mega-patch now contains P-patch changes
5. P-patches still in `recipe.toml` try to re-apply their changes → conflicts
6. Build fails with "Reversed or previously applied" and hunk failures
7. Hours spent debugging why "patches that used to work" now fail
**The fix is always the same**: either (a) remove the absorbed P-patches from `recipe.toml`,
or (b) regenerate the mega-patch from a tree WITHOUT P-patch changes. Option (a) is faster.
### Large Patch Files (redox.patch) ### Large Patch Files (redox.patch)
`local/patches/base/redox.patch` (consolidated mega-patch) is stored as 90 MB `local/patches/base/redox.patch` (consolidated mega-patch) is currently ~544K.
chunks under `local/patches/base/redox-patch-chunks/` and reassembled by: If it grows beyond a manageable size, it can be chunked under
`local/patches/base/redox-patch-chunks/` and reassembled by:
```bash ```bash
local/patches/base/reassemble-redox-patch.sh local/patches/base/reassemble-redox-patch.sh
``` ```
@@ -830,6 +936,7 @@ Critical rules:
- **Source trees are disposable** — `repo clean`/`distclean` destroy them - **Source trees are disposable** — `repo clean`/`distclean` destroy them
- **All source changes must be patches** in `local/patches/` - **All source changes must be patches** in `local/patches/`
- **Commit patch files and recipe.toml changes** before session end - **Commit patch files and recipe.toml changes** before session end
- **NEVER regenerate mega-patch from a tree that includes P-patches** — see MEGA-PATCH DISCIPLINE above
### Build Validation ### Build Validation
+3 -4
View File
@@ -122,10 +122,10 @@ data = """
[[files]] [[files]]
path = "/usr/lib/os-release" path = "/usr/lib/os-release"
data = """ data = """
PRETTY_NAME="Red Bear OS 0.1.0" PRETTY_NAME="Red Bear OS 0.2.2"
NAME="Red Bear OS" NAME="Red Bear OS"
VERSION_ID="0.1.0" VERSION_ID="0.2.2"
VERSION="0.1.0" VERSION="0.2.2"
ID="redbear-os" ID="redbear-os"
ID_LIKE="redox-os" ID_LIKE="redox-os"
@@ -310,7 +310,6 @@ gid = 0
shell = "/usr/bin/zsh" shell = "/usr/bin/zsh"
[users.user] [users.user]
# Password is unset
password = "" password = ""
shell = "/usr/bin/zsh" shell = "/usr/bin/zsh"
+76
View File
@@ -240,6 +240,46 @@ command = ["/usr/lib/drivers/ps2d"]
[[driver.match]] [[driver.match]]
vendor = 0xFFFF vendor = 0xFFFF
device = 0xFFFF device = 0xFFFF
[[driver]]
name = "i2c-hidd"
description = "I2C HID keyboard and touchpad driver"
priority = 85
command = ["/usr/bin/i2c-hidd"]
[[driver.match]]
vendor = 0xFFFF
device = 0xFFFF
[[driver]]
name = "intel-thc-hidd"
description = "Intel Touch Host Controller HID driver"
priority = 85
command = ["/usr/lib/drivers/intel-thc-hidd"]
[[driver.match]]
vendor = 0x8086
device = 0x7eb8
[[driver.match]]
vendor = 0x8086
device = 0x7eb9
[[driver.match]]
vendor = 0x8086
device = 0x7ebd
[[driver.match]]
vendor = 0x8086
device = 0x7ebe
[[driver.match]]
vendor = 0x8086
device = 0xa8b8
[[driver.match]]
vendor = 0x8086
device = 0xa8b9
""" """
[[files]] [[files]]
@@ -506,3 +546,39 @@ requires_weak = ["04_drivers.target"]
cmd = "/usr/bin/redbear-usbaudiod" cmd = "/usr/bin/redbear-usbaudiod"
type = "oneshot_async" type = "oneshot_async"
""" """
[[files]]
path = "/etc/init.d/10_ps2d.service"
data = """
[unit]
description = "PS/2 keyboard and mouse driver"
requires_weak = ["00_driver-manager.service"]
[service]
cmd = "/usr/lib/drivers/ps2d"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/10_i2c-hidd.service"
data = """
[unit]
description = "I2C HID keyboard and touchpad driver"
requires_weak = ["00_driver-manager.service"]
[service]
cmd = "/usr/bin/i2c-hidd"
type = "oneshot_async"
"""
[[files]]
path = "/etc/init.d/10_intel-thc-hidd.service"
data = """
[unit]
description = "Intel Touch Host Controller HID driver"
requires_weak = ["00_driver-manager.service"]
[service]
cmd = "/usr/lib/drivers/intel-thc-hidd"
type = "oneshot_async"
"""
+19 -16
View File
@@ -144,29 +144,32 @@ type = "oneshot_async"
[[files]] [[files]]
path = "/etc/issue" path = "/etc/issue"
postinstall = true
data = """ data = """
########## Red Bear OS ######### ____ _ ____ ___ ____
# Login with the following: # | _ \\ ___ __| | __ ) ___ __ _ _ __ / _ \\/ ___|
# `user` # | |_) / _ \\ / _` | _ \\ / _ \\/ _` | '__| | | | \\___ \\
# `root`:`password` # | _ < __/ (_| | |_) | __/ (_| | | | |_| |___) |
################################ |_| \\_\\___|\\__,_|____/ \\___|\\__,_|_| \\___/|____/
v0.2.2 "Liliya"
Login as `user` (no password)
""" """
[[files]] [[files]]
path = "/etc/motd" path = "/etc/motd"
postinstall = true
data = """ data = """
____ _ ____ ___ ____
_ _ | _ \\ ___ __| | __ ) ___ __ _ _ __ / _ \\/ ___|
| | (_) | |_) / _ \\ / _` | _ \\ / _ \\/ _` | '__| | | | \\___ \\
| | ___ _ ___ _ __ _ _ ___ | _ < __/ (_| | |_) | __/ (_| | | | |_| |___) |
| |/ / || |/ _ \\ | '_ \\| | | / __| |_| \\_\\___|\\__,_|____/ \\___|\\__,_|_| \\___/|____/
| < | || | (_) || |_) | |_| \\__ \\
|_|\\_\\|_|/ |\\___/ | .__/ \\__,_|___/ v0.2.2 "Liliya" · Built on Redox OS
|__/ | |
|_|
Red Bear OS v0.2.0 "Liliya" — Built on Redox OS
Type 'help' for available commands.
""" """
[[files]] [[files]]
File diff suppressed because it is too large Load Diff
+40 -2
View File
@@ -16,6 +16,44 @@ The code was recovered from git history, but this must never happen again.
## Rules ## Rules
### 0. MEGA-PATCH + P-PATCH DISCIPLINE (HIGHEST PRIORITY)
Each patched component (base, kernel, relibc) uses a two-layer patch model:
- **`redox.patch`** (the mega-patch) — frozen baseline of all Red Bear work. Applied first.
- **`P<N>-<desc>.patch`** (individual patches) — new work done AFTER the mega-patch. Applied after.
**The single rule that prevents the most build failures:**
> **NEVER regenerate `redox.patch` from a source tree that includes P-patch changes.**
If the source tree has P-patches applied (visible in `git status --short` or in the recipe.toml
patches list after `redox.patch`), then `git diff` from that tree will absorb the P-patches into
the mega-patch. The P-patches will then fail to apply on the next clean build because their
changes are already in the mega-patch.
**Correct operations:**
| Operation | How |
|-----------|-----|
| Add new change | Create a new P-patch, add it after `redox.patch` in `recipe.toml` |
| Consolidate P-patches into mega-patch | Generate new `redox.patch` from full source tree, then **remove all P-patches from `recipe.toml`** in the same commit |
| Fix a bug in a P-patch | Create a new P-patch that fixes it (or regenerate just that P-patch) |
**Wrong operations (causes build failures):**
| Wrong operation | What breaks |
|-----------------|-------------|
| Regenerate mega-patch from tree with P-patches, leave P-patches in recipe.toml | P-patches try to re-apply changes already in mega-patch → "Reversed or previously applied" |
| Edit mega-patch by hand | Line counts go wrong, hunks fail, impossible to debug |
| Remove P-patches from recipe.toml without folding into mega-patch | Changes are lost entirely |
**Incident log:**
| Date | What happened | Root cause | Fix |
|------|---------------|-----------|-----|
| 2026-05-29 | `validate-patches base` showed mega-patch with 100+ hunk failures, P10/P11/P12 also failing | Mega-patch was regenerated from a source tree that included P-patch changes. Individual P-patches touched same files (`init/src/main.rs`) as mega-patch, causing overlap. | P10/P11/P12 are genuinely new work on top of the mega-patch (not absorbed). The validation failure was from the mega-patch itself being stale for some hunks. Fix: regenerate mega-patch from a clean mega-patch-only tree, then re-apply P-patches. |
### 1. Never remove patches to fix build failures ### 1. Never remove patches to fix build failures
When a patch fails to apply: When a patch fails to apply:
@@ -101,5 +139,5 @@ After ANY change to the patches list or patch files:
| 2026-04-26 | Restored 8 removed patches | Agent deleted them to bypass conflicts; restored all from git HEAD | | 2026-04-26 | Restored 8 removed patches | Agent deleted them to bypass conflicts; restored all from git HEAD |
| 2026-04-26 | Restored 9 BINS entries | Agent deleted i2cd, gpiod, ucsid, etc. to bypass missing sources | | 2026-04-26 | Restored 9 BINS entries | Agent deleted i2cd, gpiod, ucsid, etc. to bypass missing sources |
| 2026-04-26 | Added EXISTING_BINS grep loop | Gracefully handles missing driver source instead of build failure | | 2026-04-26 | Added EXISTING_BINS grep loop | Gracefully handles missing driver source instead of build failure |
| 2026-04-26 | Fixed grep/find variables | `${GREP}` and `${FIND}` are unset in redoxer env; use bare `grep`/`find` | | 2026-05-29 | Added P12-init-fix-init-debug-import.patch | P11 introduced `init_debug` calls but forgot to import `init_debug``use crate::color::{init_error, init_warn, ...}` missing `init_debug`. Build error: `cannot find function init_debug in this scope`. |
| 2026-04-26 | Fixed TOML escaping | `\"` in TOML triple-quotes becomes `"` in bash; use `\\\"` for literal `"` | | 2026-05-29 | Documented mega-patch discipline (Rule 0) | Recurring patch corruption caused by regenerating mega-patch from trees that include P-patches. Created Rule 0 in PATCH-GOVERNANCE.md and updated AGENTS.md. |
@@ -0,0 +1,13 @@
diff --git a/init/src/main.rs b/init/src/main.rs
index e7f6712f..6b9da2b2 100644
--- a/init/src/main.rs
+++ b/init/src/main.rs
@@ -169,0 +170 @@ fn main() {
+ if init_config.log_debug {
@@ -171 +172,2 @@ fn main() {
- init_warn(&format!("rootfs-file: {}", name));
+ init_debug(&format!("rootfs-file: {}", name));
+ }
@@ -180 +182 @@ fn main() {
- init_warn(&format!(
+ init_debug(&format!(
@@ -0,0 +1,7 @@
diff --git a/init/src/main.rs b/init/src/main.rs
index 45cb9f73..f3861e94 100644
--- a/init/src/main.rs
+++ b/init/src/main.rs
@@ -18 +18 @@ mod unit;
-use crate::color::{init_error, init_warn, status_fail, status_ok};
+use crate::color::{init_debug, init_error, init_warn, status_ok};
@@ -0,0 +1,7 @@
diff --git a/src/main.rs b/src/main.rs
index be5f3b7..531b167 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -514 +514 @@ fn main(os: &impl Os) -> (usize, u64, KernelArgs) {
- "Redox OS Bootloader {} on {}",
+ "RedBear OS Bootloader {} on {}",
@@ -0,0 +1,140 @@
diff --git a/src/context/context.rs b/src/context/context.rs
index 6d723f4..836ce25 100644
--- a/src/context/context.rs
+++ b/src/context/context.rs
@@ -153,0 +154,3 @@ pub struct Context {
+ /// Capability bitmask — derived from euid by procmgr: euid==0 → CAP_ALL, else 0.
+ pub caps: u64,
+
@@ -159,0 +163,6 @@ pub struct Context {
+impl Context {
+ pub fn has_cap(&self, cap: u64) -> bool {
+ self.caps & cap == cap
+ }
+}
+
@@ -210,0 +220,2 @@ impl Context {
+ caps: 0,
+
@@ -485,0 +497 @@ impl Context {
+ caps: self.caps,
diff --git a/src/scheme/acpi.rs b/src/scheme/acpi.rs
index 5d73469..7e1558a 100644
--- a/src/scheme/acpi.rs
+++ b/src/scheme/acpi.rs
@@ -142 +142 @@ impl KernelScheme for AcpiScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(crate::scheme::caps::CAP_ACPI) {
diff --git a/src/scheme/caps.rs b/src/scheme/caps.rs
new file mode 100644
index 0000000..6886e88
--- /dev/null
+++ b/src/scheme/caps.rs
@@ -0,0 +1,29 @@
+//! Kernel capability bitmask for fine-grained privilege control.
+//!
+//! Each capability is a single bit in a `u64`. Processes with `euid == 0`
+//! (via procmgr SetResugid) receive `CAP_ALL`. Non-root processes receive `0`
+//! by default. Future work: explicit capability assignment via proc scheme.
+
+/// Register or unregister kernel schemes.
+pub const CAP_SCHEME_REGISTER: u64 = 1 << 0;
+/// Map physical memory (scheme:memory/physical).
+pub const CAP_PHYS_MEM: u64 = 1 << 1;
+/// Allocate IRQ vectors (scheme:irq).
+pub const CAP_IRQ: u64 = 1 << 2;
+/// Access ACPI tables (scheme:acpi).
+pub const CAP_ACPI: u64 = 1 << 3;
+/// Use kernel debugger (scheme:debug).
+pub const CAP_SYS_DEBUG: u64 = 1 << 4;
+/// Write to arbitrary files / sys:action (scheme:sys write).
+pub const CAP_SYS_WRITE: u64 = 1 << 5;
+/// Read/write model-specific registers (scheme:msr).
+pub const CAP_SYS_MSR: u64 = 1 << 6;
+/// Access PS/2 keyboard/mouse (scheme:serio).
+pub const CAP_SERIO: u64 = 1 << 7;
+/// Change file ownership (scheme:user chown).
+pub const CAP_CHOWN: u64 = 1 << 8;
+/// Modify process attributes: setuid/setgid, ptrace, signal to arbitrary procs.
+pub const CAP_PROC_ATTR: u64 = 1 << 9;
+
+/// All capabilities set — assigned to euid == 0 processes.
+pub const CAP_ALL: u64 = !0u64;
diff --git a/src/scheme/debug.rs b/src/scheme/debug.rs
index 4a23b3c..ae9a96a 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(crate::scheme::caps::CAP_SYS_DEBUG) {
diff --git a/src/scheme/irq.rs b/src/scheme/irq.rs
index 4222960..bf99a85 100644
--- a/src/scheme/irq.rs
+++ b/src/scheme/irq.rs
@@ -259 +259 @@ impl crate::scheme::KernelScheme for IrqScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(crate::scheme::caps::CAP_IRQ) {
diff --git a/src/scheme/memory.rs b/src/scheme/memory.rs
index c2f9f47..146c461 100644
--- a/src/scheme/memory.rs
+++ b/src/scheme/memory.rs
@@ -235 +235 @@ impl KernelScheme for MemoryScheme {
- if ctx.uid != 0
+ if !ctx.has_cap(crate::scheme::caps::CAP_PHYS_MEM)
diff --git a/src/scheme/mod.rs b/src/scheme/mod.rs
index 765e547..96826de 100644
--- a/src/scheme/mod.rs
+++ b/src/scheme/mod.rs
@@ -81,0 +82,2 @@ pub mod sys;
+pub mod caps;
+
@@ -356 +358 @@ impl KernelScheme for SchemeList {
- if caller.uid != 0 {
+ if !caller.has_cap(caps::CAP_SCHEME_REGISTER) {
@@ -813,0 +816 @@ pub struct CallerCtx {
+ pub caps: u64,
@@ -815,0 +819,3 @@ impl CallerCtx {
+ pub fn has_cap(&self, cap: u64) -> bool {
+ self.caps & cap == cap
+ }
@@ -822,0 +829 @@ impl CallerCtx {
+ caps: self.caps,
diff --git a/src/scheme/serio.rs b/src/scheme/serio.rs
index 2650502..8e32c98 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(crate::scheme::caps::CAP_SERIO) {
diff --git a/src/scheme/sys/mod.rs b/src/scheme/sys/mod.rs
index 9eb3564..902ab5a 100644
--- a/src/scheme/sys/mod.rs
+++ b/src/scheme/sys/mod.rs
@@ -144 +144 @@ impl KernelScheme for SysScheme {
- if ctx.uid != 0 {
+ if !ctx.has_cap(crate::scheme::caps::CAP_SYS_MSR) {
@@ -170 +170 @@ impl KernelScheme for SysScheme {
- if matches!(entry.1, Wr(_)) && ctx.uid != 0 {
+ if matches!(entry.1, Wr(_)) && !ctx.has_cap(crate::scheme::caps::CAP_SYS_WRITE) {
diff --git a/src/scheme/user.rs b/src/scheme/user.rs
index dfbf66b..e215b8d 100644
--- a/src/scheme/user.rs
+++ b/src/scheme/user.rs
@@ -1593 +1593 @@ impl KernelScheme for UserScheme {
- if cx.euid != 0 && (uid != cx.euid || gid != cx.egid) {
+ if !cx.has_cap(crate::scheme::caps::CAP_CHOWN) && (uid != cx.euid || gid != cx.egid) {
diff --git a/src/startup/mod.rs b/src/startup/mod.rs
index 86aabc2..4af65a3 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;
diff --git a/src/scheme/proc.rs b/src/scheme/proc.rs
--- a/src/scheme/proc.rs
+++ b/src/scheme/proc.rs
@@ -1275,0 +1276,5 @@ impl ContextHandle {
+ guard.caps = if info.euid == 0 {
+ crate::scheme::caps::CAP_ALL
+ } else {
+ 0
+ };
@@ -0,0 +1,53 @@
diff --git a/src/bin/login.rs b/src/bin/login.rs
index 022fb47..6e1fda6 100644
--- a/src/bin/login.rs
+++ b/src/bin/login.rs
@@ -8,0 +9,3 @@ use std::str;
+#[cfg(target_os = "redox")]
+use redox_rt::sys::{posix_setresugid, Resugid};
+
@@ -38 +41 @@ const MOTD_FILE: &'static str = "/etc/motd";
-const DEFAULT_SCHEMES: [&'static str; 26] = [
+const DEFAULT_SCHEMES: [&'static str; 28] = [
@@ -65 +68,2 @@ const DEFAULT_SCHEMES: [&'static str; 26] = [
- // Display schemes
+ // Display schemes (DRM/KMS path for GPU drivers)
+ "drm",
@@ -67,0 +72,2 @@ const DEFAULT_SCHEMES: [&'static str; 26] = [
+ // Input schemes
+ "input",
@@ -92,0 +99,17 @@ pub fn apply_login_schemes(
+#[cfg(target_os = "redox")]
+fn drop_privileges(user: &User<redox_users::auth::Full>) -> Result<()> {
+ Ok(posix_setresugid(&Resugid {
+ ruid: Some(user.uid as u32),
+ euid: Some(user.uid as u32),
+ suid: Some(user.uid as u32),
+ rgid: Some(user.gid as u32),
+ egid: Some(user.gid as u32),
+ sgid: Some(user.gid as u32),
+ })?)
+}
+
+#[cfg(not(target_os = "redox"))]
+fn drop_privileges(_user: &User<redox_users::auth::Full>) -> Result<()> {
+ Ok(())
+}
+
@@ -177,0 +201 @@ pub fn main() {
+ drop_privileges(user).unwrap_or_exit(1);
@@ -200,0 +225,9 @@ pub fn main() {
+ let before_ns_fd =
+ apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
+
+ let _ = syscall::fcntl(
+ before_ns_fd.raw(),
+ syscall::F_SETFD,
+ syscall::O_CLOEXEC,
+ );
+ drop_privileges(user).unwrap_or_exit(1);
@@ -201,0 +235,4 @@ pub fn main() {
+ let _ = syscall::fcntl(before_ns_fd.raw(), syscall::F_SETFD, 0);
+ let _ = libredox::call::close(
+ libredox::call::setns(before_ns_fd.into_raw()).unwrap_or_exit(1),
+ );
@@ -0,0 +1,46 @@
diff --git a/src/bin/login.rs b/src/bin/login.rs
index 6e1fda6..e0878c9 100644
--- a/src/bin/login.rs
+++ b/src/bin/login.rs
@@ -193,2 +193,7 @@ pub fn main() {
- let before_ns_fd =
- apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
+ let before_ns_fd = match apply_login_schemes(user, &DEFAULT_SCHEMES) {
+ Ok(fd) => fd,
+ Err(err) => {
+ eprintln!("login: apply_login_schemes failed: {}", err);
+ std::process::exit(1);
+ }
+ };
@@ -201,2 +206,8 @@ pub fn main() {
- drop_privileges(user).unwrap_or_exit(1);
- spawn_shell(user).unwrap_or_exit(1);
+ if let Err(err) = drop_privileges(user) {
+ eprintln!("login: drop_privileges failed: {}", err);
+ std::process::exit(1);
+ }
+ if let Err(err) = spawn_shell(user) {
+ eprintln!("login: spawn_shell failed: {}", err);
+ std::process::exit(1);
+ }
@@ -225,2 +236,7 @@ pub fn main() {
- let before_ns_fd =
- apply_login_schemes(user, &DEFAULT_SCHEMES).unwrap_or_exit(1);
+ let before_ns_fd = match apply_login_schemes(user, &DEFAULT_SCHEMES) {
+ Ok(fd) => fd,
+ Err(err) => {
+ eprintln!("login: apply_login_schemes failed: {}", err);
+ std::process::exit(1);
+ }
+ };
@@ -233,2 +249,8 @@ pub fn main() {
- drop_privileges(user).unwrap_or_exit(1);
- spawn_shell(user).unwrap_or_exit(1);
+ if let Err(err) = drop_privileges(user) {
+ eprintln!("login: drop_privileges failed: {}", err);
+ std::process::exit(1);
+ }
+ if let Err(err) = spawn_shell(user) {
+ eprintln!("login: spawn_shell failed: {}", err);
+ std::process::exit(1);
+ }
@@ -0,0 +1,7 @@
diff --git a/Cargo.toml b/Cargo.toml
index abfc7ae..a05f4fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -73 +73 @@ ioslice = "0.6"
-redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false }
+redox-rt = { git = "https://gitlab.redox-os.org/redox-os/relibc", default-features = false, features = ["proc"] }
@@ -1,12 +1,10 @@
_ _
| | (_)
| | ___ _ ___ _ __ _ _ ___
| |/ / || |/ _ \ | '_ \| | | / __|
| < | || | (_) || |_) | |_| \__ \
|_|\_\|_|/ |\___/ | .__/ \__,_|___/
|__/ | |
|_|
Red Bear OS v0.2.0 "Liliya" — Built on Redox OS ____ _ ____ ___ ____
| _ \ ___ __| | __ ) ___ __ _ _ __ / _ \/ ___|
| |_) / _ \ / _` | _ \ / _ \/ _` | '__| | | | \___ \
| _ < __/ (_| | |_) | __/ (_| | | | |_| |___) |
|_| \_\___|\__,_|____/ \___|\__,_|_| \___/|____/
v0.2.2 "Liliya" — Built on Redox OS
Type 'help' for available commands. Type 'help' for available commands.
@@ -1,7 +1,7 @@
PRETTY_NAME="Red Bear OS 0.2.0 (Liliya)" PRETTY_NAME="Red Bear OS 0.2.2 (Liliya)"
NAME="Red Bear OS" NAME="Red Bear OS"
VERSION_ID="0.2.0" VERSION_ID="0.2.2"
VERSION="0.2.0 (Liliya)" VERSION="0.2.2 (Liliya)"
VERSION_CODENAME="liliya" VERSION_CODENAME="liliya"
ID="redbear-os" ID="redbear-os"
ID_LIKE="redox-os" ID_LIKE="redox-os"
@@ -0,0 +1,21 @@
diff --git a/src/drivers/mod.rs b/src/drivers/mod.rs
--- a/src/drivers/mod.rs
+++ b/src/drivers/mod.rs
@@ -27 +27 @@ const INTEL_GEN12_MTL_IDS: &[u16] = &[
- 0x7D40, 0x7D41, 0x7D45, 0x7D51, 0x7D55, 0x7D60, 0x7D67, 0x7DD1, 0x7DD5,
+ 0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5,
@@ -29,2 +29,2 @@ const INTEL_GEN12_MTL_IDS: &[u16] = &[
-const INTEL_GEN12_ARL_IDS: &[u16] = &[0x6420, 0x64A0, 0x64B0];
-const INTEL_GEN12_LNL_IDS: &[u16] = &[0xB640];
+const INTEL_GEN12_ARL_IDS: &[u16] = &[0x7D41, 0x7D51, 0x7DD1, 0x7D67, 0xB640];
+const INTEL_GEN12_LNL_IDS: &[u16] = &[0x6420, 0x64A0, 0x64B0];
diff --git a/src/main.rs b/src/main.rs
--- a/src/main.rs
+++ b/src/main.rs
@@ -297 +297,5 @@ fn intel_display_firmware_keys(device_id: u16) -> Option<&'static [&'static str]>
- 0x7D40 | 0x7D41 | 0x7D45 | 0x7D51 | 0x7D55 | 0x7D60 | 0x7D67 | 0x7DD1 | 0x7DD5 => {
+ 0x7D40 | 0x7D45 | 0x7D55 | 0x7D60 | 0x7DD5 => {
+ Some(INTEL_MTL_DMC_KEYS)
+ }
+ // Arrow Lake (ARL) — display IP 14.0, same DMC as Meteor Lake
+ 0x7D41 | 0x7D51 | 0x7DD1 | 0x7D67 | 0xB640 => {
+1 -1
View File
@@ -1,6 +1,6 @@
[source] [source]
path = "source" path = "source"
patches = ["P1-intel-gen-gate.patch", "P2-intel-display-fixes.patch", "P3-intel-gen8-gen9-firmware.patch", "P4-virtio-gpu-driver.patch", "P5-virtio-auto-probe.patch", "P6-pcid-coordinate-handoff.patch", "P7-unreachable-pattern-cleanup.patch", "P8-terminal-scheme-ebadf.patch", "P9-virtio-handoff-mmio-map.patch"] patches = ["P1-intel-gen-gate.patch", "P2-intel-display-fixes.patch", "P3-intel-gen8-gen9-firmware.patch", "P4-virtio-gpu-driver.patch", "P5-virtio-auto-probe.patch", "P6-pcid-coordinate-handoff.patch", "P7-unreachable-pattern-cleanup.patch", "P8-terminal-scheme-ebadf.patch", "P9-virtio-handoff-mmio-map.patch", "P10-arrow-lake-device-ids.patch"]
[build] [build]
template = "cargo" template = "cargo"
@@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
redox-scheme = "0.11" redox-scheme = "0.11"
redox_syscall = "0.7" redox_syscall = "0.7"
libredox = "0.1"
libc = "0.2" libc = "0.2"
[profile.release] [profile.release]
+263 -69
View File
@@ -1,10 +1,17 @@
use std::env;
use std::fs; use std::fs;
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::scheme::SchemeState;
use redox_scheme::{CallerCtx, OpenResult, SignalBehavior, Socket};
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
use syscall::flag::O_ACCMODE;
use syscall::schemev2::NewFdFlags;
const POLL_MS: u64 = 2000; const POLL_MS: u64 = 2000;
const SCHEME_ROOT_ID: usize = 0;
const IA32_THERM_STATUS: u32 = 0x19c; const IA32_THERM_STATUS: u32 = 0x19c;
const IA32_TEMPERATURE_TARGET: u32 = 0x1a2; const IA32_TEMPERATURE_TARGET: u32 = 0x1a2;
@@ -19,7 +26,8 @@ enum Vendor {
fn read_msr(cpu: u32, msr: u32) -> Option<u64> { fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr); let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr);
fs::read_to_string(&path).ok() fs::read_to_string(&path)
.ok()
.and_then(|s| u64::from_str_radix(s.trim(), 16).ok()) .and_then(|s| u64::from_str_radix(s.trim(), 16).ok())
} }
@@ -39,27 +47,34 @@ fn detect_cpus() -> Vec<u32> {
for l in d.lines() { for l in d.lines() {
if let Some(id_str) = l.strip_prefix("CPU ") { if let Some(id_str) = l.strip_prefix("CPU ") {
if let Some((num, _)) = id_str.split_once(':') { if let Some((num, _)) = id_str.split_once(':') {
if let Ok(id) = num.trim().parse() { v.push(id); } if let Ok(id) = num.trim().parse::<u32>() {
v.push(id);
}
} }
} }
} }
} }
if v.is_empty() { v.push(0); } if v.is_empty() {
v.push(0);
}
v v
} }
fn read_temperature_intel(cpu: u32, tjmax: u8) -> Option<i16> { fn read_temperature_intel(cpu: u32, tjmax: u8) -> Option<i16> {
let raw = read_msr(cpu, IA32_THERM_STATUS)?; let raw = read_msr(cpu, IA32_THERM_STATUS)?;
let digital_readout = ((raw >> 16) & 0x7F) as u8; let digital_readout = ((raw >> 16) & 0x7F) as u8;
if digital_readout == 0 { return None; } if digital_readout == 0 {
let temp = tjmax.saturating_sub(digital_readout); return None;
Some(temp as i16) }
Some(tjmax.saturating_sub(digital_readout) as i16)
} }
fn read_tjmax_intel(cpu: u32) -> u8 { fn read_tjmax_intel(cpu: u32) -> u8 {
if let Some(raw) = read_msr(cpu, IA32_TEMPERATURE_TARGET) { if let Some(raw) = read_msr(cpu, IA32_TEMPERATURE_TARGET) {
let tj = ((raw >> 16) & 0xFF) as u8; let tj = ((raw >> 16) & 0xFF) as u8;
if tj > 0 && tj < 150 { return tj; } if tj > 0 && tj < 150 {
return tj;
}
} }
100 100
} }
@@ -67,9 +82,10 @@ fn read_tjmax_intel(cpu: u32) -> u8 {
fn read_temperature_amd(cpu: u32) -> Option<i16> { fn read_temperature_amd(cpu: u32) -> Option<i16> {
let raw = read_msr(cpu, AMD_TCTL)?; let raw = read_msr(cpu, AMD_TCTL)?;
let tctl = ((raw >> 21) & 0x3FF) as u16; let tctl = ((raw >> 21) & 0x3FF) as u16;
if tctl == 0 { return None; } if tctl == 0 {
let temp = (tctl as f32) / 8.0; return None;
Some(temp as i16) }
Some(((tctl as f32) / 8.0) as i16)
} }
#[derive(Clone)] #[derive(Clone)]
@@ -79,78 +95,256 @@ struct CpuInfo {
tjmax: u8, tjmax: u8,
} }
fn main() { #[derive(Clone)]
let scheme_path = ":coretemp"; enum Handle {
let _ = fs::remove_file(scheme_path); Listing,
let listener = UnixListener::bind(scheme_path).expect("bind scheme"); CpuTemp { cpu_id: u32 },
eprintln!("[INFO] coretempd: starting"); }
let cpus = detect_cpus(); struct CoretempScheme {
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len()); cpus: Vec<CpuInfo>,
next_handle: usize,
handles: Vec<Option<Handle>>,
}
let cpu_infos: Vec<CpuInfo> = cpus.iter().map(|&id| { impl CoretempScheme {
let vendor = detect_vendor(id); fn new(cpus: Vec<CpuInfo>) -> Self {
let tjmax = if vendor == Vendor::Intel { Self {
read_tjmax_intel(id) cpus,
next_handle: 1,
handles: vec![None],
}
}
fn alloc_handle(&mut self, h: Handle) -> usize {
let id = self.next_handle;
self.handles.push(Some(h));
self.next_handle += 1;
id
}
fn get_handle(&self, id: usize) -> Result<&Handle> {
self.handles
.get(id)
.and_then(|opt| opt.as_ref())
.ok_or_else(|| Error::new(EBADF))
}
fn format_listing(&self) -> String {
let mut out = String::new();
for info in &self.cpus {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
match temp {
Some(t) => out.push_str(&format!("cpu{}: {}C\n", info.id, t)),
None => out.push_str(&format!("cpu{}: N/A\n", info.id)),
}
}
out
}
}
impl SchemeSync for CoretempScheme {
fn scheme_root(&mut self) -> Result<usize> {
Ok(SCHEME_ROOT_ID)
}
fn openat(
&mut self,
dirfd: usize,
path: &str,
flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if flags & O_ACCMODE != 0 {
return Err(Error::new(EINVAL));
}
let handle = if dirfd == SCHEME_ROOT_ID {
let trimmed = path.trim_start_matches('/');
if trimmed.is_empty() {
Handle::Listing
} else if let Some(cpu_str) = trimmed.strip_prefix("cpu") {
let cpu_id = cpu_str
.trim_end_matches('/')
.parse::<u32>()
.map_err(|_| Error::new(ENOENT))?;
if self.cpus.iter().any(|c| c.id == cpu_id) {
Handle::CpuTemp { cpu_id }
} else {
return Err(Error::new(ENOENT));
}
} else {
return Err(Error::new(ENOENT));
}
} else { } else {
0 return Err(Error::new(ENOENT));
}; };
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
CpuInfo { id, vendor, tjmax }
}).collect();
let infos_clone = cpu_infos.clone(); Ok(OpenResult::ThisScheme {
thread::spawn(move || { number: self.alloc_handle(handle),
loop { flags: NewFdFlags::empty(),
thread::sleep(Duration::from_millis(POLL_MS)); })
for info in &infos_clone { }
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let data = match self.get_handle(id)? {
Handle::Listing => self.format_listing(),
Handle::CpuTemp { cpu_id } => {
let info = self
.cpus
.iter()
.find(|c| c.id == *cpu_id)
.ok_or_else(|| Error::new(ENOENT))?;
let temp = match info.vendor { let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax), Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id), Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None, Vendor::Unknown => None,
}; };
if let Some(t) = temp { match temp {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", info.id), format!("{}\n", t)); Some(t) => format!("{}\n", t),
None => "N/A\n".to_string(),
} }
} }
};
let bytes = data.as_bytes();
let off = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
if off >= bytes.len() {
return Ok(0);
}
let count = (bytes.len() - off).min(buf.len());
buf[..count].copy_from_slice(&bytes[off..off + count]);
Ok(count)
}
fn write(
&mut self,
_id: usize,
_buf: &[u8],
_offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
Err(Error::new(EINVAL))
}
fn on_close(&mut self, id: usize) {
if id < self.handles.len() {
self.handles[id] = None;
}
}
}
#[cfg(target_os = "redox")]
fn init_notify_fd() -> Option<i32> {
env::var("INIT_NOTIFY").ok()?.parse::<i32>().ok()
}
#[cfg(target_os = "redox")]
fn notify_scheme_ready(
notify_fd: i32,
socket: &Socket,
scheme: &mut CoretempScheme,
) -> Result<(), String> {
let cap_id = scheme
.scheme_root()
.map_err(|err| format!("coretempd: scheme_root failed: {err}"))?;
let cap_fd = socket
.create_this_scheme_fd(0, cap_id, 0, 0)
.map_err(|err| format!("coretempd: create_this_scheme_fd failed: {err}"))?;
syscall::call::write(
notify_fd as usize,
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
)
.map_err(|err| format!("coretempd: failed to notify init: {err}"))?;
Ok(())
}
#[cfg(target_os = "redox")]
fn run_daemon() -> Result<(), String> {
let notify_fd = init_notify_fd();
let cpus = detect_cpus();
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
let cpu_infos: Vec<CpuInfo> = cpus
.iter()
.map(|&id| {
let vendor = detect_vendor(id);
let tjmax = if vendor == Vendor::Intel {
read_tjmax_intel(id)
} else {
0
};
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
CpuInfo { id, vendor, tjmax }
})
.collect();
let socket = Socket::create()
.map_err(|err| format!("coretempd: failed to create scheme socket: {err}"))?;
let mut state = redox_scheme::scheme::SchemeState::new();
let mut scheme = CoretempScheme::new(cpu_infos);
if let Some(fd) = notify_fd {
notify_scheme_ready(fd, &socket, &mut scheme)?;
}
eprintln!("[INFO] coretempd: registered scheme:coretemp");
let infos_clone = scheme.cpus.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(POLL_MS));
for info in &infos_clone {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", info.id), format!("{}\n", t));
}
} }
}); });
for stream in listener.incoming() { loop {
if let Ok(mut stream) = stream { let request = socket
let mut buf = [0u8; 64]; .next_request(SignalBehavior::Restart)
if let Ok(n) = stream.read(&mut buf) { .map_err(|err| format!("coretempd: failed to read scheme request: {err}"))?;
let req = String::from_utf8_lossy(&buf[..n]).trim().to_string();
let resp = if req == "/" { let Some(request) = request else {
let mut names = String::new(); return Ok(());
for info in &cpu_infos { };
names.push_str(&format!("cpu{}\n", info.id));
} if let redox_scheme::RequestKind::Call(request) = request.kind() {
names let response = request.handle_sync(&mut scheme, &mut state);
} else if let Some(cpu_str) = req.strip_prefix("/cpu") { socket
if let Ok(cpu) = cpu_str.parse::<u32>() { .write_response(response, SignalBehavior::Restart)
if let Some(info) = cpu_infos.iter().find(|i| i.id == cpu) { .map_err(|err| format!("coretempd: failed to write response: {err}"))?;
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
format!("{}\n", t)
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
}
} else {
"N/A\n".to_string()
};
let _ = stream.write_all(resp.as_bytes());
}
} }
} }
} }
#[cfg(not(target_os = "redox"))]
fn run_daemon() -> Result<(), String> {
eprintln!("[INFO] coretempd: not running on Redox, exiting");
Ok(())
}
fn main() {
if let Err(e) = run_daemon() {
eprintln!("[ERROR] coretempd: {e}");
std::process::exit(1);
}
}
-7
View File
@@ -1,7 +0,0 @@
[unit]
description = "Sudo background handler"
[service]
cmd = "sudo"
args = ["--daemon"]
type = "oneshot_async"
+7
View File
@@ -0,0 +1,7 @@
[unit]
description = "Sudo privilege escalation daemon"
[service]
cmd = "sudo"
args = ["--daemon"]
type = "daemon"
+3 -1
View File
@@ -4,6 +4,8 @@ rev = "463f76b9608a896e6f6c9f63457f57f6409873c7"
patches = [ patches = [
"redox.patch", "redox.patch",
"P10-rootfs-uuid-search-no-block.patch", "P10-rootfs-uuid-search-no-block.patch",
"P11-init-noise-reduction.patch",
"P12-init-fix-init-debug-import.patch",
] ]
[package] [package]
@@ -63,7 +65,7 @@ installs = [
"/usr/lib/init.d/00_ipcd.service", "/usr/lib/init.d/00_ipcd.service",
"/usr/lib/init.d/00_pcid-spawner.service", "/usr/lib/init.d/00_pcid-spawner.service",
"/usr/lib/init.d/00_ptyd.service", "/usr/lib/init.d/00_ptyd.service",
"/usr/lib/init.d/00_sudo.service", "/usr/lib/init.d/12_sudo.service",
"/usr/lib/init.d/00_tmp", "/usr/lib/init.d/00_tmp",
"/usr/lib/init.d/05_boot_essential.target", "/usr/lib/init.d/05_boot_essential.target",
"/usr/lib/init.d/10_dhcpd.service", "/usr/lib/init.d/10_dhcpd.service",
+1 -1
View File
@@ -1,6 +1,6 @@
[source] [source]
git = "https://gitlab.redox-os.org/redox-os/bootloader.git" git = "https://gitlab.redox-os.org/redox-os/bootloader.git"
patches = ["redox.patch", "fix-uefi-alloc-panic.patch", "P0-gpt-partition-offset.patch", "P5-live-preload-cap-128mib.patch", "P6-full-ramdisk-preload.patch"] patches = ["redox.patch", "fix-uefi-alloc-panic.patch", "P0-gpt-partition-offset.patch", "P5-live-preload-cap-128mib.patch", "P6-full-ramdisk-preload.patch", "P7-redbear-branding.patch"]
[build] [build]
template = "custom" template = "custom"
+2
View File
@@ -49,6 +49,8 @@ patches = [
"../../../local/patches/kernel/P25-cpuidle-deep-cstates.patch", "../../../local/patches/kernel/P25-cpuidle-deep-cstates.patch",
# P26: DebugDisplay proper scrolling — ptr::copy rows up instead of wrapping to top # P26: DebugDisplay proper scrolling — ptr::copy rows up instead of wrapping to top
"../../../local/patches/kernel/P26-debug-display-scroll.patch", "../../../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] [build]
@@ -151,12 +151,21 @@ pub struct Context {
/// Supplementary group IDs for access control decisions. /// Supplementary group IDs for access control decisions.
pub groups: Vec<u32>, pub groups: Vec<u32>,
/// Capability bitmask — derived from euid by procmgr: euid==0 → CAP_ALL, else 0.
pub caps: u64,
// See [`PreemptGuard`] // See [`PreemptGuard`]
// //
// When > 0, preemption is disabled. // When > 0, preemption is disabled.
pub(super) preempt_locks: usize, pub(super) preempt_locks: usize,
} }
impl Context {
pub fn has_cap(&self, cap: u64) -> bool {
self.caps & cap == cap
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct SignalState { pub struct SignalState {
/// Offset to jump to when a signal is received. /// Offset to jump to when a signal is received.
@@ -208,6 +217,8 @@ impl Context {
pid: 0, pid: 0,
groups: Vec::new(), groups: Vec::new(),
caps: 0,
#[cfg(feature = "syscall_debug")] #[cfg(feature = "syscall_debug")]
syscall_debug_info: crate::syscall::debug::SyscallDebugInfo::default(), syscall_debug_info: crate::syscall::debug::SyscallDebugInfo::default(),
@@ -483,6 +494,7 @@ impl Context {
gid: self.egid, gid: self.egid,
pid: self.pid, pid: self.pid,
groups: self.groups.clone(), groups: self.groups.clone(),
caps: self.caps,
} }
} }
} }
@@ -139,7 +139,7 @@ impl KernelScheme for AcpiScheme {
.or(Err(Error::new(EINVAL)))? .or(Err(Error::new(EINVAL)))?
.trim_start_matches('/'); .trim_start_matches('/');
if ctx.uid != 0 { if !ctx.has_cap(crate::scheme::caps::CAP_ACPI) {
return Err(Error::new(EACCES)); return Err(Error::new(EACCES));
} }
if flags & O_CREAT == O_CREAT { if flags & O_CREAT == O_CREAT {
@@ -0,0 +1,29 @@
//! Kernel capability bitmask for fine-grained privilege control.
//!
//! Each capability is a single bit in a `u64`. Processes with `euid == 0`
//! (via procmgr SetResugid) receive `CAP_ALL`. Non-root processes receive `0`
//! by default. Future work: explicit capability assignment via proc scheme.
/// Register or unregister kernel schemes.
pub const CAP_SCHEME_REGISTER: u64 = 1 << 0;
/// Map physical memory (scheme:memory/physical).
pub const CAP_PHYS_MEM: u64 = 1 << 1;
/// Allocate IRQ vectors (scheme:irq).
pub const CAP_IRQ: u64 = 1 << 2;
/// Access ACPI tables (scheme:acpi).
pub const CAP_ACPI: u64 = 1 << 3;
/// Use kernel debugger (scheme:debug).
pub const CAP_SYS_DEBUG: u64 = 1 << 4;
/// Write to arbitrary files / sys:action (scheme:sys write).
pub const CAP_SYS_WRITE: u64 = 1 << 5;
/// Read/write model-specific registers (scheme:msr).
pub const CAP_SYS_MSR: u64 = 1 << 6;
/// Access PS/2 keyboard/mouse (scheme:serio).
pub const CAP_SERIO: u64 = 1 << 7;
/// Change file ownership (scheme:user chown).
pub const CAP_CHOWN: u64 = 1 << 8;
/// Modify process attributes: setuid/setgid, ptrace, signal to arbitrary procs.
pub const CAP_PROC_ATTR: u64 = 1 << 9;
/// All capabilities set — assigned to euid == 0 processes.
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)))?; let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 { if !ctx.has_cap(crate::scheme::caps::CAP_SYS_DEBUG) {
return Err(Error::new(EPERM)); return Err(Error::new(EPERM));
} }
+1 -1
View File
@@ -256,7 +256,7 @@ impl crate::scheme::KernelScheme for IrqScheme {
} }
let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?; let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 { if !ctx.has_cap(crate::scheme::caps::CAP_IRQ) {
return Err(Error::new(EACCES)); return Err(Error::new(EACCES));
} }
@@ -232,7 +232,7 @@ impl KernelScheme for MemoryScheme {
.ok_or(Error::new(ENOENT))?; .ok_or(Error::new(ENOENT))?;
// TODO: Support arches with other default memory types? // TODO: Support arches with other default memory types?
if ctx.uid != 0 if !ctx.has_cap(crate::scheme::caps::CAP_PHYS_MEM)
&& (!flags.is_empty() && (!flags.is_empty()
|| !matches!( || !matches!(
(handle_ty, mem_ty), (handle_ty, mem_ty),
+8 -1
View File
@@ -79,6 +79,8 @@ pub mod serio;
/// `sys:` - system information, such as the context list and scheme list /// `sys:` - system information, such as the context list and scheme list
pub mod sys; pub mod sys;
pub mod caps;
/// `time:` - allows reading time, setting timeouts and getting events when they are met /// `time:` - allows reading time, setting timeouts and getting events when they are met
pub mod time; pub mod time;
@@ -353,7 +355,7 @@ impl KernelScheme for SchemeList {
return Err(Error::new(EINVAL)); return Err(Error::new(EINVAL));
} }
if caller.uid != 0 { if !caller.has_cap(caps::CAP_SCHEME_REGISTER) {
return Err(Error::new(EACCES)); return Err(Error::new(EACCES));
}; };
@@ -811,8 +813,12 @@ pub struct CallerCtx {
pub uid: u32, pub uid: u32,
pub gid: u32, pub gid: u32,
pub groups: alloc::vec::Vec<u32>, pub groups: alloc::vec::Vec<u32>,
pub caps: u64,
} }
impl CallerCtx { impl CallerCtx {
pub fn has_cap(&self, cap: u64) -> bool {
self.caps & cap == cap
}
pub fn filter_uid_gid(self, euid: u32, egid: u32) -> Self { pub fn filter_uid_gid(self, euid: u32, egid: u32) -> Self {
if self.uid == 0 && self.gid == 0 { if self.uid == 0 && self.gid == 0 {
Self { Self {
@@ -820,6 +826,7 @@ impl CallerCtx {
uid: euid, uid: euid,
gid: egid, gid: egid,
groups: self.groups, groups: self.groups,
caps: self.caps,
} }
} else { } else {
self self
@@ -1273,6 +1273,11 @@ impl ContextHandle {
guard.pid = info.pid as usize; guard.pid = info.pid as usize;
guard.euid = info.euid; guard.euid = info.euid;
guard.egid = info.egid; 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); guard.prio = (info.prio as usize).min(39);
Ok(size_of::<ProcSchemeAttrs>()) Ok(size_of::<ProcSchemeAttrs>())
} }
@@ -79,7 +79,7 @@ impl KernelScheme for SerioScheme {
} }
let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?; let path = user_buf.as_str().or(Err(Error::new(EINVAL)))?;
if ctx.uid != 0 { if !ctx.has_cap(crate::scheme::caps::CAP_SERIO) {
return Err(Error::new(EPERM)); return Err(Error::new(EPERM));
} }
@@ -141,7 +141,7 @@ impl KernelScheme for SysScheme {
} else if path.starts_with("msr/") { } else if path.starts_with("msr/") {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{ {
if ctx.uid != 0 { if !ctx.has_cap(crate::scheme::caps::CAP_SYS_MSR) {
return Err(Error::new(EPERM)); return Err(Error::new(EPERM));
} }
let rest = &path[4..]; let rest = &path[4..];
@@ -167,7 +167,7 @@ impl KernelScheme for SysScheme {
.find(|(entry_path, _)| *entry_path == path) .find(|(entry_path, _)| *entry_path == path)
.ok_or(Error::new(ENOENT))?; .ok_or(Error::new(ENOENT))?;
if matches!(entry.1, Wr(_)) && ctx.uid != 0 { if matches!(entry.1, Wr(_)) && !ctx.has_cap(crate::scheme::caps::CAP_SYS_WRITE) {
return Err(Error::new(EPERM)); return Err(Error::new(EPERM));
} }
@@ -1590,7 +1590,7 @@ impl KernelScheme for UserScheme {
{ {
let ctx = context::current(); let ctx = context::current();
let cx = &ctx.read(token.token()); let cx = &ctx.read(token.token());
if cx.euid != 0 && (uid != cx.euid || gid != cx.egid) { if !cx.has_cap(crate::scheme::caps::CAP_CHOWN) && (uid != cx.euid || gid != cx.egid) {
return Err(Error::new(EPERM)); return Err(Error::new(EPERM));
} }
} }
@@ -188,6 +188,7 @@ pub(crate) fn kmain(bootstrap: Bootstrap) -> ! {
// TODO: Remove these from kernel // TODO: Remove these from kernel
context.euid = 0; context.euid = 0;
context.egid = 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"), Err(_err) => halt_boot("FATAL: failed to spawn first userspace process userspace_init\n"),
} }
+1 -1
View File
@@ -1,6 +1,6 @@
[source] [source]
git = "https://gitlab.redox-os.org/redox-os/userutils.git" git = "https://gitlab.redox-os.org/redox-os/userutils.git"
patches = ["P5-redbear-branding.patch"] patches = ["P5-redbear-branding.patch", "P6-login-privilege-drop.patch", "P7-login-diagnostics.patch", "P8-login-proc-fd.patch"]
[build] [build]
template = "custom" template = "custom"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18882
View File
File diff suppressed because it is too large Load Diff