In one paragraph
EXP-019 showed that about 125 MiB per session is duplicated browser state, and that sharing the browser process breaks isolation. Mechanism K keeps one browser per session and asks the kernel to merge identical 4 KiB pages underneath (KSM). Measured on one sandbox, 3 trials: host memory per added session 229.73 MiB with merging off; 212.70 MiB with merging on and ASLR on (K1, a 7.4% saving, interval of the difference [14.23, 19.84] MiB) at a merging-thread cost of 0.895 vCPU; 135.36 MiB with merging on and ASLR off in every session process (K2, 41.1%, interval [92.21, 96.87] MiB) at 0.706 vCPU. The pre-registered CPU bar was 0.25 vCPU; both arms fail it. K2's saving exists only because every process is laid out identically, which is exactly what ASLR prevents; the author's wording, which we adopt: "no K arm is shippable". KSM also adds a channel the other arms do not have: two cooperating probes in two sessions told whether a page with known content existed in the other session with 99.9% held-out accuracy at about 200 bits per second. "Drums" here means the L0 prototype (namespaces, overlayfs on the sandbox's own Ubuntu 24.04 root, Xvfb, openbox) plus a kernel feature; no shared-runtime (L1) result exists; incumbents' VMs can use KSM too, so nothing here compares against an incumbent.
Setup
Host. One development sandbox: 4 vCPU, 16 GiB, kernel 6.18, cgroup v1, no KVM, writable /sys/kernel/mm/ksm; hostname and provider not recorded.
The idea. Twenty sessions run the same desktop software, so many pages hold identical bytes. Linux can keep one physical copy for all of them; a session that later writes to a merged page gets its own private copy (copy-on-write). Nothing about processes, files or displays changes. The catch: with ASLR each process's pointers sit at different random addresses, so pages that contain pointers differ between sessions and do not merge.
Arms (identical sessions: real OSWorld guest server, per-session Chromium on its own Xvfb, overlay, namespaces):
| Arm | Setting |
|---|---|
| OFF | no merge flag; ASLR as normal (the existing A0 arm) |
| K1 | every session process marked mergeable (prctl(PR_SET_MEMORY_MERGE) in the session's PID 1, inherited); ASLR on |
| K2 | K1 plus personality(ADDR_NO_RANDOMIZE) for every process exec'd in the session |
Host KSM knobs, set for all arms and recorded in the ledger before writing, restored on close: run=1, pages_to_scan=20000, sleep_millisecs=2, use_zero_pages=1 (prior values 0 / 100 / 20 / 0). Aggressive on purpose so convergence waits are short; the CPU cost is measured, not hidden. Only processes that opt in are scanned, so OFF is not merged.
Pre-registered bars (design values, commit 8062ff0, 2026-10-24 06:07 UTC): M-K1 K2 slope at most 0.80 x OFF slope and the interval of (OFF minus K2) excludes 0 (K1 reported against the same rule); M-K2 merging-thread CPU at most 0.25 vCPU averaged over the trial; M-K3 in K2 the guest contract and real-server checks pass, one public task scores 0.0 then 1.0, sibling hygiene holds, ASLR is really off; M-K4 K2 create p95 at most 1.10 x OFF; M-K5 side channel reported, no bar.
What ran
envd_linux.l1proto.ksm_bench ran the three arms interleaved, 3 trials each, measuring host memory at 1, 4, 8, 12, 16 and 20 sessions after the merge counters settled (less than 1% change over 5 s, at most 90 s), plus 60 session creations per arm; a second run added 2 vCPU / 4 GiB cgroups per session (--limits, 2 trials). envd_linux.l1proto.ksm_semantics ran the K2 audit including the public task e0df059f through the real DesktopEnv. envd_linux.l1proto.ksm_sidechannel placed one probe in each of two sessions, each mapping two private anonymous regions of 8192 pages (one region with content both probes know, one random), waited 40 s for merging, timed the first write to every page, fitted a threshold on even-indexed pages and scored odd-indexed pages. Results committed in 1218b2c (06:45 UTC); the "no K arm is shippable" wording in d9de4ad (07:02 UTC).
Results
Mechanism K: kernel same-page merging across Drums L0 sessions, K1 (ASLR on) and K2 (ASLR off), with the cross-session timing side channel failed2026-10-24 · dev-sandbox
| Metric | Value | Unit | n | Note |
|---|---|---|---|---|
| host memory per added session, OFF | 229.73 (227.57 to 232.33) | MiB | 3 | |
| K1 (merging, ASLR on): host memory per added session | 212.7 (210.59 to 214.24) | MiB | 3 | 0.926 x OFF; OFF minus K1 95% interval [14.23, 19.84] MiB; saving 7.4%; bar M-K1 (at most 0.80 x OFF, design value): FAIL |
| K1 (merging, ASLR on): ksmd CPU fraction | 0.895 | vCPU | 3 | bar M-K2 at most 0.25 vCPU (design value): FAIL |
| K1 (merging, ASLR on): create latency | 2.01 (p95 2.68) | s | 60 | |
| K2 (merging, ASLR off): host memory per added session | 135.36 (134.67 to 136.41) | MiB | 3 | 0.589 x OFF; OFF minus K2 95% interval [92.21, 96.87] MiB; saving 41.1%; bar M-K1: PASS, but only with ASLR off in every session process (security regression, not shippable) |
| K2 (merging, ASLR off): ksmd CPU fraction | 0.706 | vCPU | 3 | bar M-K2 at most 0.25 vCPU (design value): FAIL |
| K2 (merging, ASLR off): create latency | 1.92 (p95 2.28) | s | 60 | bar M-K4 (K2 p95 at most 1.10 x OFF p95): PASS; the author does not claim the faster K2 creation as a benefit |
| OFF: create latency | 2.41 (p95 3.2) | s | 60 | |
| K2 semantics (M-K3): guest-contract and real-server failures, namespaces distinct, ASLR really off, task e0df059f 0.0 then 1.0 | 1 / 1 | checks | ksm_semantics.json PASS true; stubbed_modules [librosa] recorded for this run; Xvfb first mapping identical in both sessions (555555554000) | |
| side channel probe 1: median first-write latency, merged vs unique page | 345 to 3,854 | ns | 8192 | 11.17x; held-out balanced accuracy 0.99951; capacity 0.994 bit/page; about 203.3 bit/s over a 40.0 s round |
| side channel probe 2: median first-write latency, merged vs unique page | 342 to 2,973 | ns | 8192 | 8.69x; held-out balanced accuracy 0.99890; capacity 0.988 bit/page; about 202.1 bit/s |
| re-run with 2 vCPU / 4 GiB session cgroups: OFF / K1 / K2 memory per added session | 136.57 to 229.68 | MiB | 2 | OFF 229.68, K1 214.68, K2 136.57; ksmd 0.898 / 0.709 vCPU; a readiness check, not a separate result |
Host: 4 vCPU, 16 GiB, cgroup v1, no KVM, Ubuntu 24.04, kernel 6.18 (sandbox). one development sandbox; hostname and provider not recorded; not a VM or cloud host. Workload: Drums L0 sessions (namespaces + overlayfs + Xvfb + openbox) each with the real OSWorld guest server and its own Playwright Chromium 141; OFF = no merging; K1 = every session process marked mergeable (prctl PR_SET_MEMORY_MERGE), ASLR on; K2 = K1 plus personality(ADDR_NO_RANDOMIZE) for every process exec'd in the session. Method: Host KSM knobs set for all arms: run=1, pages_to_scan=20000, sleep_millisecs=2, use_zero_pages=1 (prior 0/100/20/0, restored on close). Memory: slope of host used memory against sessions at 1, 4, 8, 12, 16, 20 after the merge counters settle, 3 trials per arm interleaved (2 trials in the re-run with 2 vCPU / 4 GiB session cgroups). M-K2: ksmd CPU time over trial wall time. M-K4: create latency from 60 creations per arm. M-K5: two cooperating probes in two sessions, 8192 shared-content and 8192 unique-content private pages each, first-write latency classified with a threshold fitted on even pages and scored on odd pages. Thresholds fixed in 8062ff0; results in 1218b2c.. Source: branch claude/linux-comparison-prep: docs/data/l1-ksm/ksm_bench.json (memory_summary, create_summary, knobs_set, knobs_prior); branch claude/linux-comparison-prep: docs/data/l1-ksm/ksm_bench.log; branch claude/linux-comparison-prep: docs/data/l1-ksm/ksm_bench_limits.json (memory_summary); branch claude/linux-comparison-prep: docs/data/l1-ksm/ksm_semantics.json; branch claude/linux-comparison-prep: docs/data/l1-ksm/ksm_sidechannel.json (probes); branch claude/linux-comparison-prep: docs/l1-shared-runtime-design.md sections 12 and 13.
- No K arm is shippable (author's verdict, commit d9de4ad): K1 fails memory and CPU; K2 passes memory only with ASLR off for every process in the session, which is a security regression, and fails CPU.
- The timing side channel is an upper bound from two cooperating probes with known page content: it confirms whether a guessable 4 KiB page exists in another session; it does not read memory and is not an attack on real task data.
- The M-K3 isolation audit ran on K2 only and does not cover the timing channel; it was measured separately.
- Scan knobs were set aggressively and not re-tuned; a slower scan's saving was not measured.
- Drums here is the L0 prototype plus a kernel feature; no shared-runtime (L1) result exists; incumbents' VMs can use KSM too, so nothing here compares against an incumbent.
- One host, one kernel, 3 trials; intervals are over trials.
| Measurement | Arm | Measured | Bar | Verdict |
|---|---|---|---|---|
| M-K1 memory per added session | OFF | 229.73 MiB (227.57 / 232.33 / 229.30 per trial) | ||
| K1 | 212.70 MiB (0.926 x OFF); OFF minus K1 [14.23, 19.84] MiB | at most 0.80 x OFF | FAIL (7.4%, real but small) | |
| K2 | 135.36 MiB (0.589 x OFF); OFF minus K2 [92.21, 96.87] MiB | same | PASS, with ASLR off | |
| M-K2 ksmd CPU | K1 / K2 | 0.895 / 0.706 vCPU over trial wall time (per trial ksmd_cpu_s 112.6 s over 125.9 s wall for K1 trial 0; 71.2 s over 100.7 s for K2 trial 0) | at most 0.25 vCPU | FAIL for both |
| M-K3 semantics (K2 only) | K2 | guest_contract_failures: [[],[]], real_server_failures: [[],[]]; namespaces distinct; one Xvfb per session; personality 00040000 in both; Xvfb first mapping 555555554000 in both sessions (aslr_off_same_base_in_both_sessions: true); task e0df059f 0.0 then 1.0 with a real accessibility tree; stubbed_modules: ["librosa"] | all hold | PASS |
| M-K4 create latency | OFF / K1 / K2 | p95 3.205 / 2.683 / 2.279 s (n = 60 each) | K2 p95 at most 1.10 x OFF | PASS; the author does not claim K2's faster creation as a benefit (host state was not isolated as a cause) |
| M-K5 side channel | two probes | see below | reported | a strong channel exists |
Under the incumbent resource shape (readiness check, 2 trials, ksm_bench_limits.json): OFF 229.68, K1 214.68, K2 136.57 MiB per added session; OFF minus K2 [91.34, 94.87]; ksmd 0.898 / 0.709 vCPU. The 2 vCPU / 4 GiB limits changed nothing that matters: a session uses about 230 MiB and the merging thread is not inside the session cgroup.
The timing channel (measured, ksm_sidechannel.json)
| probe 1 | probe 2 | |
|---|---|---|
| pages merged before timing | 8192 | 8192 |
| median first-write latency, merged vs unique page | 3854 ns vs 345 ns (11.17x) | 2973 ns vs 342 ns (8.69x) |
| held-out true-positive / true-negative rate | 1.000 / 0.999 | 0.999 / 0.999 |
| held-out balanced accuracy | 0.99951 | 0.99890 |
| binary-channel capacity per page | 0.994 bit | 0.988 bit |
| rate over a 40.0 s round | about 203.3 bit/s | about 202.1 bit/s |
Reading it: a sibling that can guess a page's exact content (a known file's page, a known library page, a known secret with a guessable surrounding structure) can confirm whether another session holds it, at about 200 yes/no answers per second with about 99.9% accuracy, in this setup. That is an upper bound for a cooperating probe with known content. It does not read memory and does not find content the prober cannot guess; whether it works against real task secrets was not tested. The known mitigation (do not merge sensitive regions, or merge only within one tenant) removes the sharing K sells.
Failures and surprises
- Both arms fail the CPU bar by a wide margin. The scan settings were fixed before measuring and were not re-tuned, so this record does not say what a slower scan would save.
- K2 is a security regression. With ASLR off, a memory-corruption bug in any program in the session (browser, office suite, guest server, the agent's own tool calls) becomes far easier to exploit because every address is the same on every run. There is no K2 configuration that keeps the saving and the randomisation. The author: "It must not be a default, a recommendation or a headline."
- The first M-K5 run reported a 1.1x gap and 56% accuracy and was invalid: Python's
mmap(-1, ...)default is a shared anonymous mapping, which KSM does not merge (ksm_merging_pages_beforewas 0). The probe now uses private mappings; the invalid run is not in the data directory. - Page merging does not preserve isolation by construction. Process, mount, network and display isolation are unchanged and were audited (K2 only); the timing channel is outside that audit's scope and was measured separately. No verdict treats "isolation audit passed" as "no side channel".
Limitations
- One host, one kernel, 3 trials; intervals are over trials, not hosts. M-K3 audited K2 only; K1 shares the code path, which is an inference.
- Incumbents' VMs can use KSM too (each QEMU guest is a mergeable process), so nothing here is a claim against an incumbent.
- Merging pays only after sessions are added and the counters settle; the saving is a steady-state number.
- The task-semantics check covers one public task through a shell getter and string match;
librosais stubbed and unreached (this is the one run on the branch that recorded its stub list). - Drums is L0 plus a kernel feature; no L1 result exists.
Reproduce
python -m envd_linux.l1proto.ksm_bench --out ksm_bench.json --trials 3 --max-n 20 [--limits]
python -m envd_linux.l1proto.ksm_semantics --out ksm_semantics.json --osw-python <venv python> --osw-src <OSWorld checkout>
python -m envd_linux.l1proto.ksm_sidechannel --out ksm_sidechannel.json
Needs root and a writable /sys/kernel/mm/ksm; the host knobs are restored afterwards. Check memory_summary.K2_ksmd_vcpu_fraction_mean against 0.25 and probes[].heldout_balanced_accuracy.
Sources
docs/data/l1-ksm/ksm_bench.json(keysmemory_summary,create_summary,knobs_set,knobs_prior),ksm_bench.logdocs/data/l1-ksm/ksm_bench_limits.json,ksm_bench_limits.logdocs/data/l1-ksm/ksm_semantics.jsondocs/data/l1-ksm/ksm_sidechannel.jsondocs/l1-shared-runtime-design.mdsections 12 (pre-registration,8062ff0, 2026-10-24 06:07:10 UTC) and 13 (results,1218b2c, 06:45:01 UTC; wordingd9de4ad, 07:02:02 UTC)docs/mechanism-k-page-merging.mdenvd-linux/envd_linux/l1proto/ksm_bench.py,ksm_semantics.py,ksm_sidechannel.py- Branch
claude/linux-comparison-prep; result filedata/results/exp-020-ksm.jsonon this site