From 0d7e295efee1fca0935cf4a8bee9629c007dd2b6 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 21 Sep 2026 14:23:27 -0300 Subject: 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/. 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. --- 9proc/src/linux/probe.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to '9proc/src/linux') diff --git a/9proc/src/linux/probe.zig b/9proc/src/linux/probe.zig index c2f1026..00c256e 100644 --- a/9proc/src/linux/probe.zig +++ b/9proc/src/linux/probe.zig @@ -542,7 +542,12 @@ pub fn Probe(comptime Srv: type) type { const st = unixStat(@ptrCast(&sa.path)) catch return error.Occupied; if (st) |s| { if (s.mode & linux.S.IFMT != linux.S.IFSOCK) return error.Occupied; - if (cloud9.post.probe(@ptrCast(&sa.path)) != .stale) return error.AlreadyListening; + // The probe wants the path, not the whole `sun_path` + // array: a 108-byte slice is past the address budget, and + // `probe` owns that uncertainty as `.live` — which would + // make every stale socket look like a live server. + const probe_path: [:0]const u8 = sa.path[0..path.len :0]; + if (cloud9.post.probe(probe_path) != .stale) return error.AlreadyListening; _ = linux.unlink(@ptrCast(&sa.path)); } try p.check(linux.bind(lfd, @ptrCast(&sa), @sizeOf(linux.sockaddr.un))); -- cgit v1.3