foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Grok Bot <grokbot@localhost> | 2026-10-31 20:16:47 -0700 |
|---|---|---|
| commit | c1be2a3c7d37b2947c1b9df59cdba131dc6a2ebe | |
| parent | e2b25e53f8f82f0ca9b48ca7c038245263920298 | |
| tree | browse files at this commit | |
docs: M6 user management (passwd/shadow, login, su, first-boot setup, owners and modes), M7 Pi 3 placeholder; K13 kernel prerequisites (open)
| M | docs/kernel-requests.md | +36 -1 |
| M | docs/plan.md | +149 -0 |
2 files changed, 185 insertions, 1 deletion
@@ -2,7 +2,7 @@ The kernel is Edward's lane; Foundry OS never edits `/workspace/rust-kernel`. These are requests. The full list (K0-K11) with background is in-[proposal.md](proposal.md), section 8. K12 (below) is new since M3.+[proposal.md](proposal.md), section 8. K12 and K13 (below) are new since M3. ## How M1 works without any kernel change @@ -135,6 +135,41 @@ unmount or flush the mounted file systems before QEMU exits. For FAT that means calling fatfs's `unmount()`. Our host check (scripts/check-v2-image.sh) prints a WARN for now instead of failing. +## K13: users and permissions, enforced (OPEN; needed for M6)++**Status: open.** Edward has a read-only audit of this queued; until it+reports and our own checks pass, Foundry OS assumes **none** of it works.+What earlier probes showed (up to 0.18.0; not re-checked on 0.21.0):++- Modes, uid and gid are **stored** (`chmod`, the `chown` family read back+ on ramfs; new files are 0:0), and the id syscalls keep track of+ real/effective/saved ids with the right rules in themselves+ (`setresuid(1000,1000,0)` then `setuid(0)` works; after `setuid(1000)`+ going back is `EPERM`).+- But **permissions are not enforced**: `test -x` is always true and+ `access()` always returns 0 (proposal.md section 8: "permissions/ids not+ enforced"). The K6 list only asked for the setuid family as uid-0+ no-ops, so nothing so far was built or tested for a non-root user.+- FAT accepts `chown` to root:root and gives `EPERM` for 5:5.++What M6 (user management, docs/plan.md) needs. **Now** = M6 can't pass+without it; **nice** = can wait.++| | Item | Needed |+|---|---|---|+| (a) | Real per-process credentials: uid, gid, euid, egid, saved uid/gid, supplementary groups; inherited by `fork`, kept by `execve` (except setuid exec) | **now** |+| (b) | `setuid`/`setgid`/`setgroups`/`setresuid`/`setresgid`/`setreuid`/`setregid` that really change the credentials, with Linux's rules (only root may set arbitrary ids; others may only swap between their real/effective/saved ids; `setgroups` root only) | **now** |+| (c) | Permission checks from the mode bits, owner and groups (root bypasses read/write, and exec needs at least one `x` bit) on: `open` (read/write/`O_CREAT`/`O_TRUNC`), `execve`, `unlink`/`rmdir`/`rename` (write + search on the parent, sticky bit on `/tmp`), `mkdir`, `chdir`, path lookup (search `x` on every directory), `truncate` | **now** |+| (d) | **setuid / setgid bits on `execve`**: the program runs with the file owner's uid (gid) as effective and saved id; ignored on a `nosuid` mount and for scripts | **now** (for `su`, `passwd`) |+| (e) | `chown` only by root (a non-root owner may only change the group to one of their own groups); `chmod` only by the owner or root; a write by a non-root user clears setuid/setgid | **now** |+| (f) | ext2 stores uid, gid and the full mode (incl. setuid/setgid/sticky) **persistently**: after a reboot, and as written by `mke2fs -d`/`debugfs` on the host | **now** |+| (g) | `access`/`faccessat` (and `faccessat2`) answer from the **real** uid/gid, as Linux does | **now** |+| (h) | `umask` applied to the mode of new files and directories (`open`, `mkdir`, `mknod`) | **now** |+| (i) | `/dev/console`, `/dev/hvc0`, ptys: owner and mode that `login` can `chown`/`chmod` to the user (and `getty`/`login` can open the tty as root first) | **now** |+| (j) | FAT32 and exFAT (no owners on disk): show a fixed owner and mode, chosen per mount like Linux's `uid=`, `gid=`, `umask=`/`fmask=`/`dmask=` (even if only as kernel build-time options); refuse or ignore `chown` consistently | nice (B: and C: can stay root-owned and world-writable at first) |+| (k) | `/proc/<pid>/status` `Uid:`/`Gid:` lines (for `ps`, `id` checks in tests) | nice |+| (l) | Signals: a non-root process may only `kill` processes with the same real/effective uid | nice (needed before untrusted users) |+ ## What Foundry OS still takes from the kernel repo | What | How | Goes away when |
@@ -46,6 +46,155 @@ Inside the ext2 root: | **M3** | dash as `/bin/sh`, ext2 root (contract v2), Rust `/sbin/init`, exFAT share | **done** on Rustbelt 0.21.0: `make test` (v2) passes, plus host fsck of all three file systems; `make test-v1` still passes | | M4 | sbase tools | needs testing per tool (docs/syscall-gaps.md) | | M5 | busybox in `/usr/lib/busybox` | `bzip2` for the tarball |+| M6 | User management: passwords, `login`, users and groups, real file owners and modes ([below](#m6-user-management-approved-not-started)) | approved; needs kernel K13 |+| M7 | A bootable Raspberry Pi 3 build ([below](#m7-raspberry-pi-3-bootable-build-placeholder)) | placeholder; Edward's arm64/Raspberry Pi port is in progress on the kernel side |++## M6: user management (approved, not started)++**In plain words.** Today everything on Foundry OS runs as **root**, the+all-powerful user (user id, *uid*, 0): any program can read or delete any+file. On a real multi-user system each person has an **account**: a name,+a number (uid), a **group** (gid), a home folder and a password. The+kernel then checks, on every file access, whether *this* uid may read,+write or run *that* file, using the file's owner and its **mode bits**+(`rwxr-x---`: what the owner, the group and everyone else may do). M6+makes Foundry OS work like that: you log in as `tyler`, your shell runs+as uid 1000 in `/home/tyler`, and you need root's password (`su`) for+system changes.++Words used below:++- **`/etc/passwd`**: one line per account (`tyler:x:1000:1000:Tyler:/home/tyler:/bin/rbsh`:+ name, password placeholder, uid, gid, full name, home, shell). Everyone+ may read it, because programs like `ls -l` turn uids into names with it.+- **`/etc/group`**: the groups and who is in them (`wheel:x:10:tyler`).+- **`/etc/shadow`**: the password **hashes**, readable by root only. A+ hash is a one-way scramble of the password: easy to compute from the+ password, practically impossible to turn back into it. `login` hashes+ what you type and compares. We use **SHA-512 crypt** (`$6$...` in the+ file, done by musl's `crypt()`), with a random *salt* per password so+ two equal passwords look different. **yescrypt** (`$y$`), which is slower+ to brute-force, comes later.+- **setuid bit**: a mode bit (`rws` instead of `rwx`) that makes a program+ run as the file's *owner* rather than as whoever started it. That is how+ `su` and `passwd`, owned by root, can read or change `/etc/shadow` when+ `tyler` runs them. They are therefore the most security-sensitive+ programs on the system and must stay small.+- **getty → login → shell**: `getty` opens a terminal and asks for a name,+ `login` checks the password, switches to the user's uid/gid/groups,+ goes to the home folder and starts the user's shell (rbsh).++**Size: L.** The OS-side programs are small, and busybox brings most of+them. But every piece depends on the kernel really enforcing ids and+permissions (K13, not assumed to work), and on the image build getting+owners and modes exactly right. The security pieces (first-boot setup,+no default password, setuid programs) need care and tests, not just code.++### Scope++1. **Account files**: `/etc/passwd`, `/etc/group` (root, wheel, tyler's+ group, ...), and `/etc/shadow` with **mode 0600 root:root** (or 0640+ root:shadow if a `shadow` group is needed for a non-root helper).+ Hashes are SHA-512 crypt through musl; yescrypt later.+2. **Login chain**: init starts `getty` on each terminal (instead of rbsh+ directly), `getty` runs `login`, `login` runs **rbsh as the user in+ their home dir** with `HOME`, `USER`, `LOGNAME`, `SHELL` and `PATH` set.+ A wrong password gives "Login incorrect" and a short delay.+3. **Becoming root**: **`su`** now (busybox `su`, setuid root). **`sudo`**+ or a small Rust **`doas`** later.+4. **Account tools**: `passwd`, `adduser`/`deluser`, `addgroup`, `chmod`,+ `chown`: **busybox first**, Rust replacements (in `rust/`) later.+5. **First-boot setup (no default password, ever)**: the image ships with+ root's password field **locked** (`!` or `*` in `/etc/shadow`: no+ password can match) and no user account. On the first interactive boot+ init sees `/etc/foundry/setup-needed` and runs a setup program on the+ console *before* any login prompt: it asks for a new root password+ (twice), and a user name + password, creates the user and their home+ folder, then deletes the marker.++ *Why this matters:* a Raspberry Pi is usually on a home network, often+ reachable by other machines (and, through port forwarding or an+ attacker on the same Wi-Fi, by the internet). A built-in password like+ `root`/`root` is the first thing every scanning bot tries: the Mirai+ botnet took over hundreds of thousands of devices with exactly such+ factory passwords. Anything that ever listens on the network (`httpd`+ today, a future ssh) is only as safe as the weakest password. So the+ image must not contain *any* password that works, not even "just for+ testing".+6. **Test credentials for headless `make test`, without shipping them**:+ - The image in `out/foundry.img` stays locked (setup-needed).+ - `make test` makes its own copy (it already boots `out/test-v2.img`)+ and writes test accounts **into that copy only**, from the host,+ with e2fsprogs (`debugfs -w`): a `/etc/shadow` with hashes of+ throwaway passwords generated for this run (`openssl passwd -6`,+ random each time, kept in `out/`), and the setup marker removed.+ - The guest test script gets the passwords on its standard input from+ the host-side feed (the same way `scripts/hvc0-session.txt` already+ feeds the hvc0 console), never from a file in the image.+ - A check in `make test` fails if the release image has any shadow+ entry that is not locked.+7. **Owners and modes set by the image build** (`mke2fs -d` copies modes+ but takes owners from `root_owner`; per-file fixes go through+ `debugfs -w` `set_inode_field` or an ext2 build step that reads a+ small table like `root/permissions.txt`):++ | path | owner | mode |+ |---|---|---|+ | `/etc/shadow` | root:root | 0600 (or root:shadow 0640) |+ | `/etc/passwd`, `/etc/group` | root:root | 0644 |+ | `/bin/*`, `/sbin/*` | root:root | 0755 |+ | `/bin/su`, `/bin/passwd` (busybox links: the busybox binary they point to, or a separate setuid busybox with only these applets) | root:root | **4755** (setuid) |+ | `/root` | root:root | 0700 |+ | `/home/tyler` (test image) | tyler:tyler | 0700 |+ | `/tmp` | root:root | 1777 (sticky: you can only delete your own files) |+ | `/etc/foundry/setup-needed` | root:root | 0600 |++ FAT32 and exFAT (B:, C:) have no owners at all; what they *show* is+ up to the kernel's mount options (K13 (j)).++### Done when (checks `make test` can run)++On the test copy of the image, logged in through `getty → login` on hvc0+with the throwaway credentials:++1. Host side, before boot: `debugfs` shows `/etc/shadow` mode 0600 uid 0,+ `/bin/su` mode 4755 uid 0, `/home/tyler` uid 1000; the **release** image+ has only locked shadow entries and the setup marker present.+2. A wrong password prints `Login incorrect`; the right one gives a+ prompt, and `id` prints `uid=1000(tyler) gid=1000(tyler) groups=...`,+ `pwd` prints `/home/tyler`.+3. As tyler: `cat /etc/shadow` fails with `Permission denied`; writing to+ `/etc/passwd`, `/bin/ls` and `/root` fails; creating a file in+ `/home/tyler` works and `ls -l` shows it as `tyler tyler`, with the+ mode umask 022 gives (`-rw-r--r--`); a file chmodded 0600 by tyler is+ unreadable by a second test user.+4. `su` with root's test password gives uid 0 (`id -u` = 0); `su` with a+ wrong password fails; `exit` returns to tyler.+5. As root: `adduser`/`passwd` create a third user; the new `/etc/shadow`+ line starts with `$6$`; that user can log in on the console; `deluser`+ removes them and their login fails.+6. `chown` by tyler on a file of tyler's fails with `Operation not+ permitted`; by root it works.+7. First boot, simulated: booting the release image copy headless with the+ setup answers fed on the console leads to the setup program, then to a+ working login; booting again does not run setup again.++### Kernel prerequisites++All in [kernel-requests.md](kernel-requests.md), **K13** (open). Nothing+there is assumed to work until Edward's audit says so and our checks+pass: earlier probes showed ids and modes being *stored* but not+*enforced*.++## M7: Raspberry Pi 3 bootable build (placeholder)++Edward's arm64 / Raspberry Pi port of Rustbelt is in progress on the+kernel side. Once it boots, this milestone makes Foundry OS build an SD+card image for a Pi 3: `aarch64` builds of our Rust and C programs (a+second target for `foundry-cc` and Cargo), the Pi's boot partition+(firmware + kernel), and the same ext2 root / data / share layout. M6+comes first on purpose: a Pi on a network must not boot with a default+password. Details once the kernel's boot contract for the Pi is known. ## Long-term track: a self-hosted Rust toolchain (after M5, not started)