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

@ -35,6 +35,8 @@
["architecture/terminals.md", "Virtual terminals"],
["architecture/devices.md", "Device model"],
["architecture/usb.md", "USB"],
["architecture/udev.md", "udev"],
["architecture/users.md", "Users and permissions"],
["architecture/graphics.md", "Graphics"],
["architecture/audio.md", "Audio"],
["architecture/networking.md", "Networking"],

View file

@ -94,6 +94,7 @@ Read in this order if you are new:
| [fat.md](fat.md) | FAT12/16/32 with long names: USB sticks, SD cards, the ESP. |
| [devices.md](devices.md) | Buses, classes, binding, and the vtables consumers talk to. |
| [usb.md](usb.md) | xHCI, the USB core, and the three class drivers. |
| [users.md](users.md) | Accounts, login, and the permissions the kernel enforces. |
| [udev.md](udev.md) | eudev on SchOS: the device table, the `/sys` tree, uevents, datagram sockets. |
| [graphics.md](graphics.md) | Framebuffer, `/dev/fb0`, text rendering, the console, PSOGM and GMMOS. |
| [desktop.md](desktop.md) | Xorg on the framebuffer, Window Maker, and what they stand on. |

View file

@ -75,4 +75,5 @@ through ACPI, and reach X as a mouse: pointer, click and tap.
- **Touchpads are mice**: no two-finger scroll, no gestures, no palm rejection.
- **No resolution change from inside X.** The mode is the console's; `schmode` changes the
`vgpud` head only, and the loader's menu is where a real panel's mode is chosen.
- **Not a security boundary.** X runs as root on a system that enforces no file permissions.
- **Runs as an ordinary user.** `startx` as `schos` works by group permission alone
([users.md](users.md)); X itself is still a single seat with no logind or seatd.

View file

@ -435,9 +435,8 @@ allocation over 8 MB mapped straight into a neighbouring live mapping.
- **No page cache.** `mmap` of a file *reads the range in* at map time, and
writable `MAP_SHARED` of a file is refused outright rather than faked. Demand
paging covers anonymous memory only.
- **Permissions are tracked but not enforced.** uid/gid are maintained honestly
so `getuid()`/`setuid()` stop lying, and file access does not consult them;
`setpriority` is the one place the uids are checked.
- **Root is `euid == 0` and nothing finer.** No capabilities; file access, `kill`, `exec`
and the privileged calls are enforced as [users.md](users.md) says.
- **`mprotect` is accepted and not enforced** (`sys_mprotect` returns 0). RELRO is not read-only,
a `PROT_NONE` guard does not fault, and nothing has `NX` (`EFER.NXE` is never set), so every
mapping is executable.

View file

@ -102,9 +102,8 @@ tracked. Two rules, with the two errnos POSIX gives them:
alone. Without it nice is not a courtesy but a way for any process to
outrank the rest of the system by asking.
Both are inert today, because everything runs as root. They are written now
because the alternative is growing a priority knob and a permission model in
separate commits, and the window between the two is exactly the bug.
Both bite now that there are users ([users.md](users.md)); root is exempt from
both.
## Deliberately not a red-black tree

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)).