Phase 48
Security Foundation
m3OS closes the trust-floor gaps that currently block any honest claim of "safe enough for serious headless use." When this phase lands, identity transitions, password storage, entropy, and default remote-exposure behavior all match the project's intended multi-user and network-facing story.
Learning goal
Understand the difference between interesting security mechanisms and a credible threat model.
Category
infrastructure
Components touched
Builds on
Unlocked by this phase
Why this phase matters
m3OS closes the trust-floor gaps that currently block any honest claim of “safe enough for serious headless use.” When this phase lands, identity transitions, password storage, entropy, and default remote-exposure behavior all match the project’s intended multi-user and network-facing story.
How it works in m3OS
Phase 48 closes the trust-floor gaps in the m3OS multi-user and network-facing security model. It replaces advisory, demo-quality defaults with real enforcement: kernel-checked credential transitions, hardware-backed entropy, iterated password hashing, and safe boot defaults.
How it builds on earlier phases
This phase builds on Doom and turns that foundation into Security Foundation.
Implementation outline
- Audit the current trust-floor failures and map each one to a concrete kernel, userspace, or image-default fix.
- Implement kernel-side credential transition enforcement and remove any remaining trust shortcuts.
- Replace the current weak entropy path with a documented stronger CSPRNG/seed pipeline.
- Remove baked-in default credentials and define the replacement provisioning or first-boot flow.
- Make telnet opt-in only and align init/service defaults with the new exposure policy.
- Upgrade password hashing and account-file update semantics.
- Add or extend smoke/regression coverage for login, SSH, password changes, and boot defaults.
- Update documentation, release framing, and version references for the new trust floor.
Task progress snapshot
0 of 115 tracked tasks are complete.
Documentation targets
- Documentation deliverables have not been listed yet.
Deferred follow-on work
- Full privilege separation across all network services
- Advanced key-management, rotation, and audit infrastructure
- Rich multi-factor or hardware-backed authentication flows
- General sandboxing beyond the repaired trust floor
Source materials
Important code
Code spotlight cards
Code spotlight
Audit current setuid/setgid enforcement
credentials unconditionally with no privilege check, meaning any unprivileged. Why it matters: sys_linux_setuid and sys_linux_setgid currently set. Focus on `sys_linux_setuid` and `sys_linux_setgid` in `kernel/src/arch/x86_64/syscall.rs` while you trace the snippet.
/// `setuid(uid)` — set user ID (syscall 105).
///
/// Enforces POSIX-style privilege checks:
/// - If euid == 0 (root): sets both real uid and effective uid.
/// - If euid != 0: only allows setting effective uid back to the real uid.
/// - Otherwise returns -EPERM.
///
/// Login and su work because they run as root (euid 0) when transitioning.
fn sys_linux_setuid(uid_arg: u64) -> u64 {
let new_uid = uid_arg as u32;
let pid = crate::process::current_pid();
let mut table = crate::process::PROCESS_TABLE.lock();
let proc = match table.find_mut(pid) {
Some(p) => p,
None => return NEG_EPERM,
};
let mut cred = kernel_core::cred::Credentials {
uid: proc.uid,
gid: proc.gid,
euid: proc.euid,
egid: proc.egid,
};
if cred.set_uid(new_uid).is_err() {
return NEG_EPERM; Code spotlight
Audit password hashing and default credentials
iteration using the username as salt. The image ships with hardcoded root and. Why it matters: Passwords are currently hashed with a single SHA-256. Focus on `hash_password` and `populate_ext2_files` in `userspace/syscall-lib/src/sha256.rs` while you trace the snippet.
/// Hash a password with salt: SHA-256(salt || password).
pub fn hash_password(password: &[u8], salt: &[u8]) -> [u8; 32] {
// Concatenate salt + password into a buffer.
let total = salt.len() + password.len();
let mut buf = [0u8; 512];
if total > buf.len() {
// Truncate if too long (shouldn't happen in practice).
return sha256(password);
}
buf[..salt.len()].copy_from_slice(salt);
buf[salt.len()..total].copy_from_slice(password);
sha256(&buf[..total])
} Code spotlight
Audit service defaults and telnet exposure
auto-starts on every boot with restart=always. A plaintext remote shell in. Why it matters: Telnet is currently in the default service list and. Focus on `KNOWN_CONFIGS` and `restart=always` in `userspace/init/src/main.rs` while you trace the snippet.
/// Known service config files to try opening (no readdir available).
const KNOWN_CONFIGS: &[&[u8]] = &[
b"/etc/services.d/sshd.conf\0",
b"/etc/services.d/telnetd.conf\0",
b"/etc/services.d/syslogd.conf\0",
b"/etc/services.d/crond.conf\0",
b"/etc/services.d/httpd.conf\0",
b"/etc/services.d/dhcpd.conf\0",
b"/etc/services.d/ntpd.conf\0",
b"/etc/services.d/ftpd.conf\0",
]; Key files
- kernel/Cargo.toml — When the phase lands, bump kernel/Cargo.toml and any release/version references to 0.48.0.
- kernel-core/src/cred.rs — Credential transition logic with POSIX privilege checks
- kernel-core/src/prng.rs — Xorshift64-multiply PRNG, extracted for host testing
- kernel/src/arch/x86_64/syscall.rs — Kernel syscall handlers for setuid/setgid/getrandom
- userspace/syscall-lib/src/sha256.rs — Password hashing with iterated SHA-256
- userspace/login/src/main.rs — First-boot password setup for locked accounts
- userspace/passwd/src/main.rs — Password change with random salts
- userspace/adduser/src/main.rs — Account creation with single-write pattern and fsync
- xtask/src/main.rs — Image build with locked accounts and telnet opt-in
How real OSes differ
- Linux and BSD systems have much deeper credential, entropy, and hardening stacks than m3OS needs initially.
- Production systems also layer service users, secret rotation, audit trails, and more advanced account policy on top of the basics.
- m3OS should copy the discipline of safe defaults and explicit trust boundaries, not the full operational complexity of a mature distribution.
Success criteria
- Unprivileged processes can no longer escalate via unconditional `setuid` or `setgid`.
- `getrandom()` is backed by a documented stronger entropy source than the current time-seeded fallback.
- The default boot path does not expose telnet unless explicitly enabled.
- Images no longer ship with baked-in default credentials.
- Password hashes use a slow salted scheme, or a clearly documented transitional replacement that materially improves the current state.
- Boot/login/SSH smoke coverage exists for the hardened defaults and passes in the normal validation path.
Related phases
Keep exploring the roadmap
Phase 47
CompleteDoom
m3OS can run DOOM as a real full-screen graphical application. The game loads its WAD data from disk, renders through the framebuffer path, accepts keyboard input for gameplay, and returns the system to a usable administration path when it exits.
Phase 49
CompleteArchitectural Declaration
m3OS gains an explicit, enforceable architecture contract for what belongs in ring 0, what is transitional, and what must move to userspace over time. The phase also turns the giant syscall surface into a structure that can be unwound safely in later phases.