Users and permissions: accounts, login, enforced file permissions, setuid, su, passwd, doas

The kernel now checks owner/group/other on open, create, unlink, rename, exec
and path search; honours set-user-id and set-group-id; restricts kill, mount,
reboot and mknod; keeps supplementary groups; and implements chown. devtmpfs
nodes carry owners and modes that chmod/chown keep. Terminals run login, the
image has root and schos, X starts as the ordinary user.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
Nexoniarz 2026-10-26 13:16:07 +01:00
commit 020aa6113c
34 changed files with 1342 additions and 120 deletions

View file

@ -0,0 +1,79 @@
# Users and permissions
SchOS has accounts, a login prompt, and file permissions that the kernel
enforces. Until now every process was root and `uid` was bookkeeping; this is
the part that makes it a multi-user system.
## What you meet
- **`login` on tty1–tty5.** `etc/schos-getty` takes the terminal back from
whoever had it and runs `login`, which asks who is there and starts their
shell; when the shell exits `schinit` starts the prompt again.
- **tty6 is a root shell with no login**, on purpose: the way in when login
itself is what is broken.
- **Two accounts in the image.** `root`, and `schos` (uid 1000, home
`/home/schos`, in `wheel tty video input audio cdrom`). Both have empty
passwords, so `login` lets them in without asking — it is a live image.
`passwd` sets one.
- **`su` and `passwd`** (`cmd/su`, `cmd/passwd`) are setuid root and small
programs of their own, not toybox applets: toybox is one binary under every
name, and a setuid copy of it can be asked for any applet by naming it, as
root. `su` to root takes membership in `wheel`, whatever the password.
- **`doas`** (Alpine's, setuid) with `permit nopass :wheel` in `/etc/doas.conf`.
## What the kernel enforces
| | |
|---|---|
| open, create, unlink, rename, link | the usual owner/group/other bits, `w`+`x` on the directory, and the sticky bit (`/tmp`) |
| path walk | `x` on every directory searched |
| `exec` | `x` on the file; set-user-id and set-group-id make the new image run as its owner or group, and the saved ids follow |
| `chmod`, `chown`, `utimensat` | owner or root; a non-root `chown` drops the set-id bits |
| `access` / `faccessat` | by the *real* ids (`AT_EACCESS` for the effective ones) |
| `kill`, `tkill`, `tgkill` | root, or a target whose real or saved uid is one of yours. The kernel's own signals (`SIGPIPE`, the terminal's) are nobody's to refuse |
| `mount`, `umount`, `chroot`, `reboot`, `shutdown`, `setgroups`, `mknod` of a device | root |
| `setuid` family | root may set anything; anyone else only to an id they already hold |
Root reads and writes everything and executes a file only if some execute bit
is set. A process's supplementary groups (`setgroups`) are consulted along with
its effective group. Ownership is stored by SchFS (it always had the fields);
FAT volumes belong to everyone, since they cannot say otherwise, and `/proc`
and `/sys` are root's.
`inode_permission()` in `kernel/fs/fs.c` is the one check; the rest of this is
call sites.
## Devices
`/dev` is built by the kernel and each node has the owner, group and mode in the
`devnodes[]` table of `kernel/fs/devfs.c`: `null` and `zero` are 0666,
terminals `root:tty 0660`, `fb0` `root:video`, input `root:input`, raw disks
`root:disk`, `pci`, `dbg` and `netdev` root's alone. `chmod` and `chown` on a
node are kept (login claims its terminal this way, and udev sets modes) in an
override table applied whenever the inode is loaded. A new pseudo-terminal's
slave belongs to whoever opened `ptmx`.
udev would set a node it has no rule for to 0600, which made `/dev/null`
root-only, so `etc/udev-schos.rules` (installed as `90-schos.rules`) names the
kernel's devices with the answers the kernel gave.
## The image
Everything in the image is owned by root: the build host's user means nothing
inside it. `mkschfs --owners FILE` takes the exceptions, one `path uid gid
[mode]` per line (`tools/posix/mkaroot.sh` writes it: `/home/schos 1000 1000
0700`, and the modes of `/tmp`, `/var/tmp` and `/root`). The setuid bits are set
on `su`, `passwd` and `doas` in the root tree directly, because `apk` run as an
ordinary user may have dropped them.
## Not done
- **No PAM and no `/etc/securetty`.** `login` is busybox's and checks
`/etc/shadow` itself.
- **No capabilities.** Root is `euid == 0`, all of it.
- **No per-user `/run`, no logind.** X as a non-root user works (`startx` as `schos`, under
QEMU: Window Maker up) by group permission alone — video, input and tty — and
`/etc/X11/Xwrapper.config` lets anyone start it, because the wrapper's "console users" test
looks for a Linux VT on a standard descriptor that xinit's server child does not keep.
- **`/proc` and `/sys` hide nothing from other users.**
- **`mprotect` is still not enforced** ([kernel.md](kernel.md)).