Skip to content

Each laptop claim in the guide has a check behind it, or is named as waiting for hardware - #485

Merged
DevomB merged 83 commits into
mainfrom
guide-laptop-checks
Oct 15, 2026
Merged

DevomB merged 83 commits into
mainfrom
guide-laptop-checks

Conversation

@DevomB

@DevomB DevomB commented Oct 13, 2026

Copy link
Copy Markdown
Owner

The guide's laptop text, read claim by claim against the suites, as the release item asks of the guide.

What was wrong:

  • The guide said every zone but the net zone plays sound; vault has no socket at the mixer either (the sound suite's mixer-zones round requires exactly that).
  • Claims with no check behind them: sound going to a USB device and back to the built-in one, the microphone moving from one zone to another, the menu's Sound line saying a zone records, the microphone's window closing when the menu takes the microphone back, the menu naming a zone WAITING after a sleep, Enter alone leaving a zone waiting, and kryptik suspend itself (the suite only ran kryptik-lock --suspend).
  • The lid, the microphone and brightness keys, the backlight and USB Video Class cameras have no counterpart in QEMU, and the guide did not say so.

What changed:

  • Sound suite: a USB sound card plugged in through QMP (vm-drive.py plug-sound:/unplug-sound:, run-ovmf's new spare snd2 backend, xHCI port): a zone's tone goes to the USB card with the built-in one closed (usb-sound-card, plays-on-usb); pulled out mid-tone, the mixer closes the card and the tone goes on on the built-in card, the mixer not restarting (usb-pull-moves-sound) and the zone playing to its end (usb-pull-zone-plays-on); the host finds that tone (550 Hz) in the first card's WAV. A grant to work moves the microphone from untrusted and ends untrusted's record (microphone-moves); the menu says untrusted, RECORDING NOW (chrome-says-recording); c closes the microphone's window (microphone-window-taken-back).
  • Suspend suite: the real sleep comes from kryptik suspend; the menu's line for work reads WAITING for its passphrase (pN) (menu-says-waiting); after the real sleep, Enter alone where the chrome asks again leaves work waiting, the daemon asked nothing (enter-leaves-zone-waiting; the host presses Enter at GT ZONE-PASS-SKIP whether or not the window came, so the later rounds run).
  • Guide: vault plays nothing; a USB device takes the sound after a few quiet seconds and gives it back at once when pulled; the known-limitations line keeps the exact hardware sentence and adds what QEMU lacks (lid, microphone and brightness keys, backlight, UVC cameras). Status rows name the new checks.

How it was checked: bash -n on each changed script; tools/tests/vm-drive.py gains a stand-in QMP case for the new steps (CI; this laptop's Python has no AF_UNIX). The suites run in the stack's Distro proof; until then the new rounds are unproven.

DevomB added 30 commits October 13, 2026 05:51
…or, in the zone that holds it

ADR-025 hands a zone a descriptor on the partition the user gave it, and
needs a reader there that parses the filesystem on that descriptor:
mtools, exfat-fuse and ntfs-3g take paths, and reopening a block device
by path is refused to the zone.

tools/stick is that reader, a crate of its own, since kryptikd keeps to
libc alone (ADR-010). It is fatfs 0.3.6, without chrono, behind
kryptik-stick: no arguments says what the zone was given, then list
[DIR], get PATH [DEST], put FILE [DIR]. The descriptor comes from the
zone's broker (`stick`, answered `ok ro|rw WHAT` with SCM_RIGHTS), or
--device FILE for an image. A read-only grant refuses every write
whatever fatfs asks. Nothing on either side is written over. Names from
the stick reach the terminal with control characters as `?`, and reach a
path only as one component. A tree nested past 32 directories is
refused. A put is synced to the device before it is reported. FAT's
dates come from the zone's clock as UTC, held to 1980-2107.

CI tests it and builds it for musl. The Distro workflow builds it beside
kryptikd, held to the same path check, and stage 04 installs it
(KRYPTIK_STICK_BIN, the stick step). The licence texts of fatfs,
bitflags, byteorder and log are in build/licences, and
check-rust-licences.sh reads its lock.
The stick reader parses a FAT filesystem in the zone, but nothing in zone 0
could hand it a stick: the design (removable-storage.md) has kryptikd make a
device-mapper linear mapping of the one partition the user picks and pass it
to the zone through its broker, never mounting it.

stick.rs decides which block devices may be given (a partition of a USB disk
or SD card, or such a disk with no partition table; held by nothing, mounted
nowhere, not on a disk the system's own mappings use), makes the mapping
read-only unless the user chose to write, records the grant under
/run/kryptik-stick, and on eject wipes the mapping's table so every read and
write on a descriptor the zone kept fails, then removes it once the last
closes. A zone file says `[storage] removable = false` to take no stick; the
vault's does, and the net zone never takes one.

The broker answers `stick` with the zone's descriptor and `ok ro|rw WHAT`;
the launch daemon has `sticks`, `stick-grant ZONE PART ro|rw` and
`stick-eject ZONE` for the chrome, and `info` says what a zone holds; root
has `kryptikd stick list|grant|eject`. A zone's stick goes back when the zone
stops.

Checked with unit tests on a stand-in /sys of the kernel's shape (a USB
partition named with its vendor, model, size and GPT name; a stick with no
partition table; an SD card; an NVMe disk refused; a held, mounted or
system-disk partition refused; bad names), the grant record round trip, the
mapping lookup, the zone key, and the broker parse.
…s suite plugs one in

A stick plugged in now opens a window of the chrome naming each partition
by what sysfs says of it; a letter and a zone's number give it to that zone
to read, or with w to write too, and e with the number takes it back. The
menu's d opens the same window, and its zone lines say which zone holds a
stick. kryptik-launch gains --sticks, --stick-grant and --stick-eject for it.

Eject now writes out what the zone left unsynced, then wipes the mapping's
table and drops what was cached of the stick: a wiped table alone left the
pages the zone had read readable through a descriptor it kept. A stick
pulled out without eject loses its grant the next time the sticks are
listed, which the chrome does whenever a block device comes or goes, and a
grant is refused while a program in the zone still holds the mapping of a
stick taken back from it.

zones-test.sh plugs a FAT stick into the running guest through QMP
(vm-drive.py's plug: and unplug: steps, run-ovmf.sh --usb-port) and runs
guest-tests/stick-check.sh between the plugs: zone 0 mounts and probes
nothing; vault and the net zone are refused; untrusted, given it read-only,
lists it and copies a file off whole, cannot write, and has no node for it;
another zone asking is refused; eject frees the partition at once and a
descriptor the zone kept reads only EIO; what the zone puts with a write
grant is on the image the host reads after pulling it out; a program
holding the stick when it is pulled out reads EIO and the grant ends with
no oops; and the install medium plugged in offers its ESP, which the reader
lists, and its root, which the reader refuses. chrome-sticks.sh checks the
window and the watcher against a stand-in launcher, and vm-drive.py's test
the plug steps against a stand-in QMP socket.
If lvm2's rules ship, 13-dm-disk.rules runs blkid on every device-mapper
device as it appears or changes, so a grant would have zone 0 parse the
stick's superblock as root through kryptik-stick-ZONE, which
59-kryptik-removable.rules keeps eudev from doing on the partition itself.
12-kryptik-stick.rules sets device-mapper's disable flags for those
mappings, by their sysfs name and by the name 10-dm.rules imports.

stick-check.sh's new round, after the grant: eudev holds no ID_FS_
property for the mapping or the partition.
A video call in a zone had the microphone and no camera: the kernel built no
video support. ADR-026 grants the camera as the microphone is granted.

The kernel builds Video4Linux and uvcvideo as modules, filtered to cameras
and test drivers, with no camera button input device; modprobe.d/camera.conf
keeps uvcvideo from loading by alias, and stage 05 checks both. vivid is
built as a module for the zones suite to stand in for a camera, without its
framebuffer; the hardening checker's finding on it is accepted, with why.

kryptikd's camera.rs: a zone that may hold the camera (a new [camera] use
key; vault's file says no, and the net zone never does) gets at start a
directory of its own from a tmpfs that allows device nodes, bound at
/run/kryptik/camera without nodev, /dev/video0 linking into it, and a
Landlock rule for it. A grant loads uvcvideo, binds again what an earlier
grant's end let go of, finds the first capture node in sysfs and makes it
there, owned by the zone's root, mode 0600. Its end, by the user, the
zone's stop or the screen lock, removes the node, unbinds the driver from
the device so a kept descriptor fails, and unloads uvcvideo unless
something still holds it, which the chrome then says.

The launch daemon has camera-grant and camera-revoke, kryptik-launch
--camera-grant and --camera-revoke, root kryptikd camera grant|revoke|
status. The chrome's menu has k<N> and k and a Camera line, a window says
which zone holds the camera for as long as it does, and kryptik-lock takes
the camera back as the screen locks.

Checked: camera.rs unit tests on a stand-in sysfs (the first capture node,
not a metadata one, numbered as numbers, its name shown plainly; the device
and bus to let go of; the record round trip), the zone key, and
chrome-camera.sh against a stand-in launcher. zones-test step 2c runs
guest-tests/camera-check.sh with vivid: no load by alias, no camera before a
grant, vault and net refused, the grant's node and a captured frame, the
zone's stop ending it, another zone finding none, k alone failing a kept
descriptor and unloading uvcvideo, a grant to a running zone. gui-check's
lock round: the screen locking takes it back.
A mapping ejected while its zone still held the stick is removed by
device-mapper once the zone's last descriptor closes, from a workqueue, so
the eject kryptikd runs at the zone's stop can find it and then lose it
between two dmsetup calls, and logged that as a failure. Eject now treats a
mapping that is gone as done, says whether a grant ended rather than
whether a mapping was there, and still clears a mapping left without a
grant.

stick-check.sh's rounds that look for the mapping to be gone wait up to ten
seconds for that removal instead of looking once.
The chrome opens its camera window within a second or two of a grant, and
a window that maps takes the keyboard: granted after the round's zone 0
window had it, the camera window could take it before the lock did, which
the round would read as the lock's doing. Granted first, and waited for,
the window is under the round's own.
A directory tree nested one level past MAX_DEPTH stops the copy off the
stick with the limit named, where a stick built to loop would otherwise
recurse until the zone's stack or disk ran out.
Every walk of the stick's directories, path lookup included, now goes
through one loop that stops with "the stick's filesystem is malformed"
past FAT's 65,536 entries in a directory, or past as many entries or bytes
as the volume has room for in one command. fatfs follows a cluster chain
that loops, so a directory could be endless (list grew its list until the
zone's memory ran out), and directories aliasing an ancestor multiplied
under get, past what the depth limit stops, until the zone's disk was full.
put looks for the name within the same bounds before fatfs walks the
directory to add to it.

A name holding a control character is no file name here: FAT allows none,
and the errors that name a path here carried it to the terminal. Errors
show paths as names are shown, and the marks that turn text around show as
'?' too.

The stick's row in stage 04 sits above kryptikd's comment, which is
kryptikd's.
… as kryptikd is

Stage 06's strict audit flags every static Rust binary for both, and lists
kryptikd and the proxy by name; kryptik-stick is built the same way.
…wn aliases

kmod's modprobe skips a blacklisted module's own aliases with or without
-b, so a dry run by the alias names nothing either way and could not show
the alias belongs to uvcvideo; modinfo reads it from the module.
…he camera

A zone holding the microphone or the camera has a window of the chrome's
that says so, but a window the user puts in full screen covers it, and only
dwl's bar above that window stays in sight: a zone could take a grant, wait
for full screen, and record with nothing on screen saying so.

The bar now reads, after the zone's name, "mic ZONE" and "cam ZONE" for
whichever zones hold them, on every fullscreen window, not only the holder's.
dwl learns them from two files through one inotify descriptor on its event
loop, and draws every fullscreen bar again on a change: the camera's grant
record, which kryptikd writes as root, and the microphone's holder, which
the mixer now writes into its own control directory (uid 87, 0640, group
kryptik) at its start and on every grant and revoke. dwl takes a file only if
it is a regular file of its writer's that no one else may write, and a name
only if it is a zone dwl knows; anything else marks nothing. kryptikd's
launch daemon mounts the camera's tmpfs as it starts, so the compositor's
watch is on it and not on the directory beneath.

gui-test, with vivid as the camera: untrusted fullscreen, granted the camera
and recorded as the microphone's holder, its bar reads "untrusted mic
untrusted cam untrusted" pixel for pixel; both taken back, and a record of
the microphone's holder put in place by root rather than the mixer, the bar
names the zone alone. The sound suite checks the mixer's record on the grant
and once the zone's stop has ended it.
…a directory made anew drops

An inotify watch belongs to the directory's inode: the mixer's control
directory made again, or anything mounted over a watched directory, ends
the watch, and every later grant went unmarked until dwl restarted. dwl now
also reads both records once a second, draws the fullscreen bars again only
when a holder changed, and sets both watches again each time, which keeps a
watch that is there as it is.

The gui round makes the mixer's directory anew before the records are
written, so the marks it checks come through a directory dwl did not first
watch, and puts the mixer's own back after.
…Bluetooth and input methods, and the laptop bullet rewritten
…ooth and input methods, and the laptop bullet rewritten
DevomB added 27 commits October 14, 2026 21:13
…, and the desktop suite checks it

# Conflicts:
#	docs/status.md
…ke, and the desktop suite checks it

# Conflicts:
#	docs/status.md
…ore lock-0's window opens, and the desktop suite is named beside the zones suite as loading vivid
…out round lists the sticks only once the kernel has let the stick's partition go
…deo0, as no other is granted the camera; the camera suite is named in the image's program list
…ard, so untrusted's fullscreen has ended for Alt+e to give back
… card pulled out, so a moved microphone and a pulled USB card no longer leave a loop spinning
…hes, a zone's sched and ioprio calls refused, proxy-calls ending at a proxy's exit
@DevomB
DevomB changed the base branch from zone-bar-marks to main October 15, 2026 12:31
@DevomB
DevomB marked this pull request as ready for review October 15, 2026 12:31
@DevomB
DevomB merged commit 53b7a9b into main Oct 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant