Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 520e92cad8 | |||
| aa9d14a90e | |||
| 44bcf2b75a | |||
| 7cd5bfbb83 | |||
| 5987fffde7 | |||
| 706050482b | |||
| daf131d435 | |||
| 0ccc233131 | |||
| c0a93e5cfa | |||
| 0a4a77a56b | |||
| d2c761a56c | |||
| f40b751bca | |||
| ce9ff8aebd | |||
| bb3ae6e63f | |||
| 61135b0cce |
@@ -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
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
"""
|
||||||
|
|||||||
+18
-15
@@ -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
@@ -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 @@
|
|||||||
|
|
||||||
_ _
|
____ _ ____ ___ ____
|
||||||
| | (_)
|
| _ \ ___ __| | __ ) ___ __ _ _ __ / _ \/ ___|
|
||||||
| | ___ _ ___ _ __ _ _ ___
|
| |_) / _ \ / _` | _ \ / _ \/ _` | '__| | | | \___ \
|
||||||
| |/ / || |/ _ \ | '_ \| | | / __|
|
| _ < __/ (_| | |_) | __/ (_| | | | |_| |___) |
|
||||||
| < | || | (_) || |_) | |_| \__ \
|
|_| \_\___|\__,_|____/ \___|\__,_|_| \___/|____/
|
||||||
|_|\_\|_|/ |\___/ | .__/ \__,_|___/
|
|
||||||
|__/ | |
|
v0.2.2 "Liliya" — Built on Redox OS
|
||||||
|_|
|
|
||||||
|
|
||||||
Red Bear OS v0.2.0 "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,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]
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
[unit]
|
|
||||||
description = "Sudo background handler"
|
|
||||||
|
|
||||||
[service]
|
|
||||||
cmd = "sudo"
|
|
||||||
args = ["--daemon"]
|
|
||||||
type = "oneshot_async"
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[unit]
|
||||||
|
description = "Sudo privilege escalation daemon"
|
||||||
|
|
||||||
|
[service]
|
||||||
|
cmd = "sudo"
|
||||||
|
args = ["--daemon"]
|
||||||
|
type = "daemon"
|
||||||
@@ -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,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"
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,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"
|
||||||
|
|||||||
Executable
+1815
File diff suppressed because it is too large
Load Diff
Executable
+2354
File diff suppressed because it is too large
Load Diff
Executable
+18882
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user