diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-21 14:23:27 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-21 15:20:27 -0300 |
| commit | 0d7e295efee1fca0935cf4a8bee9629c007dd2b6 (patch) | |
| tree | d371eb028c63da5ab5b506bd25ac6579cf8a93fc /9ns/docs/DESIGN.md | |
| parent | 3a23f6a29e47ace901bd4d82b9db4055fcc12bb9 (diff) | |
| download | cloud9-0d7e295efee1fca0935cf4a8bee9629c007dd2b6.tar.gz cloud9-0d7e295efee1fca0935cf4a8bee9629c007dd2b6.zip | |
9ns --mntgen: registry subdirectories are mount points too
A registry entry that is a directory is now served the way the root is:
a synthetic directory listing the real one, dialing the sockets inside
it on walk and recursing into further directories, to max_synth_depth
(8) levels across max_synth_dirs (64) synthetic nodes. That is the
plan9port mntgen shape and the layout zmx now posts under, so a live
session reads at /mnt/9p/zmx/<name>. Before this a directory in the
registry was dialed like a socket and answered EIO for good.
post gains the two entry points the traversal needs: postedDir (the
registry scan, against any directory) and dialPath (a dial by composed
path, no name validation).
Hardening, each from an attack that broke the code:
- BATCH_FORGET carries entries for many owners and puts 0 in the header
nodeid, so routing it by the header dropped all of them: 32 of 64
synthetic slots leaked in one close burst and the subdirectories that
held them answered EIO forever. distributeForgets unpacks the body and
hands each entry to its owner.
- probe() and connectBlocking() copied a caller's path into the kernel
address with no bound: a path past sun_path overran the 110-byte stack
sockaddr (a panic in Debug, silent corruption in ReleaseFast). Both
refuse it now, probe as `.live` so a claim never deletes what it could
not inspect.
- That bound then caught 9proc's own listener, which handed probe() the
whole 108-byte sun_path array instead of the path inside it. The probe
reads `.live` for anything it cannot ask about, so every stale socket
became AlreadyListening and no server could ever take a dead
predecessor's name back. It passes the path now.
Suites: 87/87 root (+7 post/serve attack regressions), 48/48 9ns,
51+88 9ns integration (+4 traversal and slot-recycling checks), 213/0
9ns adversarial, 60/60 9proc plus its adversarial suites with a new
stale-socket takeover check, freestanding green.
Diffstat (limited to '9ns/docs/DESIGN.md')
| -rw-r--r-- | 9ns/docs/DESIGN.md | 25 |
1 files changed, 25 insertions, 0 deletions
diff --git a/9ns/docs/DESIGN.md b/9ns/docs/DESIGN.md index 1d66387..6b1b80c 100644 --- a/9ns/docs/DESIGN.md +++ b/9ns/docs/DESIGN.md @@ -114,6 +114,31 @@ posted name reaches that server's whole 9P tree, and nothing is connected until something walks. XDG_RUNTIME_DIR unset is fatal before anything is forked (the registry is not guessable; no `/tmp` fallback). +A registry entry that is a **directory** is served the same way the root +is: a synthetic directory (its node id under the reserved index +`synth_index`, the slot in the low bits) listing the real directory's +entries, dialing the sockets found inside on walk and recursing into +further directories — up to `max_synth_depth` (8) levels, bounded by +`max_synth_dirs` (64) synthetic nodes per 9ns process. This is how +multi-service providers organize themselves (zmx posts its sessions +under `zmx/<name>`), the plan9port `mntgen` shape: one tree, many +mounts, each entry a mount point. Non-socket, non-directory entries +inside a directory answer EIO on walk, exactly like a plain file in the +registry itself; a directory's slots are freed when the kernel forgets +the dentry. + +A synthetic node's slot comes back through FORGET, and the kernel sends +most of them as `BATCH_FORGET`, whose header `nodeid` is 0 and whose +body carries one `(nodeid, nlookup)` per forgotten node — for any mix of +owners. The dispatcher therefore cannot route a batch by its header the +way it routes every other request: `distributeForgets` unpacks the body +and hands each entry to its owner (synthetic root, synthetic +subdirectory or per-mount bridge). Routing the batch whole instead loses +every entry in it, so the 64 slots leak and a subdirectory served once +answers EIO forever. The forgets themselves arrive on the kernel's +schedule, not at `close`, so a slot may take a moment to return; a +listing that needs one meanwhile answers EIO rather than waiting. + ``` program 9ns parent in new userns │ |
