summaryrefslogtreecommitdiff
path: root/9proc/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-21 14:23:27 -0300
committerGabriel Schneider <[email protected]>2026-09-21 15:20:27 -0300
commit0d7e295efee1fca0935cf4a8bee9629c007dd2b6 (patch)
treed371eb028c63da5ab5b506bd25ac6579cf8a93fc /9proc/test
parent3a23f6a29e47ace901bd4d82b9db4055fcc12bb9 (diff)
downloadcloud9-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 '9proc/test')
-rwxr-xr-x9proc/test/adv_linux_probe.py34
1 files changed, 34 insertions, 0 deletions
diff --git a/9proc/test/adv_linux_probe.py b/9proc/test/adv_linux_probe.py
index 186f008..c921ebb 100755
--- a/9proc/test/adv_linux_probe.py
+++ b/9proc/test/adv_linux_probe.py
@@ -289,6 +289,40 @@ def attack_signals(server):
ok("SIGTERM unlinks the unix socket (clean stop path)", not os.path.exists(path))
s.stop()
+ # A server killed outright leaves its socket behind. The next server
+ # of the same name must recognise the corpse and take the name over:
+ # the listener probes the path, and a probe that cannot tell answers
+ # "live", so a caller that hands it the whole 108-byte sun_path array
+ # instead of the path turns every stale socket into AlreadyListening.
+ s = Srv(server)
+ client(s.path, timeout=5)
+ stale = s.path
+ s.proc.send_signal(signal.SIGKILL)
+ s.proc.wait(timeout=5)
+ ok("SIGKILL leaves the socket behind", os.path.exists(stale))
+ taker = subprocess.Popen([server, "--unix", stale], stderr=subprocess.PIPE)
+ try:
+ # The path exists throughout (the corpse, then the new socket), so
+ # the connect itself is the readiness signal.
+ err, c = None, None
+ for _ in range(250):
+ try:
+ c = client(stale, timeout=10)
+ break
+ except OSError as e:
+ err = e
+ time.sleep(0.02)
+ ok("a fresh server takes over a stale socket path",
+ c is not None and rd(c, [b"build", b"zig_version"])[0] == Rread,
+ err or taker.poll())
+ except Exception as e:
+ ok("a fresh server takes over a stale socket path", False, e)
+ finally:
+ taker.kill()
+ taker.wait(timeout=5)
+ if os.path.exists(stale):
+ os.unlink(stale)
+
def attack_probe(ns, server):
print("# probe loop and admission")