m³OS docs GitHub

Phase 27

User Accounts

The OS supports multiple user accounts with login authentication, file ownership, and permission enforcement. A `login` program prompts for username and password before granting shell access. This is the foundation for multi-user operation and secure remote access.

Complete

Learning goal

Understand how Unix user/group identity works at the kernel level (UID/GID).

Category

userspace

Why this phase matters

The OS supports multiple user accounts with login authentication, file ownership, and permission enforcement. A login program prompts for username and password before granting shell access. This is the foundation for multi-user operation and secure remote access.

How it works in m3OS

Phase 27 adds multi-user support to m3OS: per-process UID/GID identity, file ownership and Unix permission enforcement across all filesystem backends, SHA-256 password hashing, and a login-based boot flow. Six new userspace programs (login, su, passwd, adduser, id, whoami) provide authentication and account management. After Phase 27, the system boots to a login: prompt instead of dropping directly into a shell. Two default accounts exist: root (UID 0) and user (UID 1000).

How it builds on earlier phases

This phase builds on Text Editor and turns that foundation into User Accounts.

Implementation outline

  • Add UID/GID fields to the kernel process structure; default to 0 (root) for init.
  • Implement getuid, setuid, and related syscalls.
  • Add permission mode and owner fields to VFS inodes.
  • Implement permission checking in VFS operations.
  • Implement chmod and chown syscalls.
  • Write the login program: read /etc/passwd, verify password hash from /etc/shadow.
  • Modify init to spawn login instead of directly spawning the shell.
  • Write su, passwd, id, whoami, adduser utilities.
  • Create default /etc/passwd, /etc/shadow, /etc/group in the disk image.

Task progress snapshot

86 of 86 tracked tasks are complete.

Documentation targets

  • Documentation deliverables have not been listed yet.

Deferred follow-on work

  • ext2 filesystem (Phase 28)
  • PAM or pluggable authentication
  • Supplementary groups
  • ACLs (Access Control Lists)
  • Mandatory access control (SELinux-style)
  • Home directory creation on first login
  • User quotas
  • Account lockout after failed attempts

Source materials

Important code

Code spotlight cards

Code spotlight

Add UID/GID fields to Process struct

kernel/src/process/mod.rs L343-L366

Add UID/GID fields to Process struct. Why it matters: Every permission check and identity syscall reads these fields from the current process. Focus on `uid` and `gid` in `kernel/src/process/mod.rs` while you trace the snippet.

/// Descriptor for a single userspace process.
pub struct Process {
    /// This process's unique identifier.
    pub pid: Pid,
    /// Parent PID.  0 means no parent (init or an orphan).
    pub ppid: Pid,
    /// Current lifecycle state.
    pub state: ProcessState,
    /// Root physical address of this process's page table.
    ///
    /// `None` means the process has not yet been assigned a dedicated
    /// address space and shares the kernel's mappings (pre-Phase 12).
    pub page_table_root: Option<x86_64::PhysAddr>,
    /// Top of this process's kernel-mode stack (virtual address).
    pub kernel_stack_top: u64,
    /// Userspace entry-point virtual address.
    pub entry_point: u64,
    /// Initial userspace RSP: the virtual address of `argc` on the ABI stack,
    /// as returned by `setup_abi_stack`.  This is **not** the raw top of the
    /// stack allocation — it points into the stack, below the argv/envp data.
    pub user_stack_top: u64,
    /// Exit code written when the process transitions to [`ProcessState::Zombie`].
    pub exit_code: Option<i32>,
    /// Signal that caused the process to stop (set when transitioning to Stopped).

Code spotlight

Update identity syscalls to return real fields

kernel/src/arch/x86_64/syscall.rs L1446-L1452

Update identity syscalls to return real fields. Why it matters: Programs like id and whoami rely on these syscalls to report the current user. Focus on `sys_linux_getuid` and `sys_linux_getgid` in `kernel/src/arch/x86_64/syscall.rs` while you trace the snippet.

/// `getuid()` — return real user ID (syscall 102).
fn sys_linux_getuid() -> u64 {
    current_process_ids().0 as u64
}

/// `getgid()` — return real group ID (syscall 104).
fn sys_linux_getgid() -> u64 {

Code spotlight

Extend ramdisk nodes with metadata

kernel/src/fs/ramdisk.rs See file

Extend ramdisk nodes with metadata. Why it matters: Ramdisk files (initrd binaries) need permission bits so exec permission checks work. Focus on `uid` and `gid` in `kernel/src/fs/ramdisk.rs` while you trace the snippet.

Key files

  • kernel/src/process/mod.rs — The Process struct (kernel/src/process/mod.rs) carries four
  • kernel/src/arch/x86_64/syscall.rs — kernel/src/arch/x86_64/syscall.rs:
  • kernel-core/src/fs/tmpfs.rs — FileData and DirData in kernel-core/src/fs/tmpfs.rs each hold
  • kernel/src/fs/ext2.rs — ext2 driver (kernel/src/fs/ext2.rs) reads and writes these fields
  • userspace/syscall-lib/src/sha256.rs — userspace/syscall-lib/src/sha256.rs (FIPS 180-4, no external
  • xtask/src/main.rs — populate_ext2_files() in xtask/src/main.rs, which uses debugfs
  • userspace/init/src/main.rs — Init (userspace/init/src/main.rs) was modified to:
  • userspace/login/ — Rust no_std binary. Implements the full authentication cycle: prompt,
  • userspace/su/ — Rust no_std binary. su <username> (defaults to root). Any user can
  • userspace/passwd/ — setuid-on-exec support exists.
  • userspace/adduser/ — with SHA-256, and rewrites /etc/shadow with the updated entry.
  • userspace/id/ — group files. Creates the home directory and sets ownership.
  • userspace/whoami/ — /etc/passwd and /etc/group, prints uid=N(name) gid=N(name).
  • userspace/syscall-lib/ — Phase 27 added to userspace/syscall-lib/:

How real OSes differ

  • Real Unix systems use PAM (Pluggable Authentication Modules) for flexible auth backends.
  • NSS (Name Service Switch) enables looking up users from LDAP, NIS, etc.
  • Supplementary groups allow a user to belong to multiple groups.
  • setuid/setgid binaries provide controlled privilege escalation.
  • Fine-grained capabilities (Linux capabilities) replace the binary root vs. non-root model.
  • SELinux/AppArmor provide mandatory access control beyond discretionary Unix permissions.
  • Our implementation uses the classic Unix permission model (user/group/other with rwx bits), which is simple to understand and sufficient for learning.

Success criteria

  • Boot reaches a `login:` prompt instead of an immediate shell.
  • Entering correct credentials for `root` drops into a root shell.
  • Entering wrong credentials is rejected with an error message.
  • `id` shows the correct UID/GID after login.
  • Files created by `user` are owned by UID 1000.
  • `user` cannot read `/etc/shadow` (permission denied).
  • `user` cannot delete files owned by `root` in protected directories.
  • `su root` from `user` prompts for root's password and grants a root shell.
  • `passwd` changes the current user's password, and the new password works on next login.

Related phases

Keep exploring the roadmap