Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions docs/architecture/windows-sandbox-rfc-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ launch policy, then launches the target with layered Windows controls:
the tree on close;
- handle inheritance disabled;
- AppContainer ACEs for only the compiled read/write roots, with a persisted recovery ledger;
- recursive reparse-point rejection before ACL mutation;
- recursive reparse-point rejection before ACL mutation by default; the W1 filesystem worker may
explicitly mark one read-only Glob root for non-following decomposition, where the root itself
stays strict and nested reparse entries receive no grant;
- a closed, sorted environment from the normalized command;
- bounded local named-pipe framing protected to SYSTEM and the current user.

Expand Down Expand Up @@ -252,7 +254,9 @@ designed but explicitly deferred as later gates, tracked by Phase 4 in
Enforced (merged in #2961 unless tagged with a follow-up PR):

- default-deny filesystem with distinct read/write roots compiled from the exact profile (§6.1);
- recursive reparse-point rejection and multi-hard-link rejection before ACL mutation (§5, §6.1);
- recursive reparse-point rejection and multi-hard-link rejection before ACL mutation (§5, §6.1),
with one explicit read-only W1 Glob exception that decomposes the admitted root around nested
reparse entries without granting or traversing them;
- a fresh request-derived AppContainer SID, per-launch ACL grants in a versioned recovery ledger,
and stale-ledger reconciliation at startup (§6.1, §7.1);
- an AppContainer token with no network capabilities (§6.2);
Expand Down Expand Up @@ -374,7 +378,7 @@ sequenceDiagram
M-->>H: native path + one-shot manifest
H->>B: --broker-local manifest
B->>B: delete manifest; bind PID, nonce, and launch digest
B->>B: recover ledger; reject reparse trees; grant SID ACEs
B->>B: recover ledger; reject or partition reparse trees; grant SID ACEs
B->>J: create kill-on-close Job
B->>C: create AppContainer process with atomic Job attribute
C-->>B: bounded exit result
Expand All @@ -386,9 +390,22 @@ sequenceDiagram

The first implementation needs no elevated setup. Windows creates a request-derived Maka
AppContainer profile, and the packaged native binary grants its unique SID only the roots admitted
for the current launch. Before mutation it recursively rejects `FILE_ATTRIBUTE_REPARSE_POINT`, persists a
versioned ledger with `create_new` and `sync_all`, and reconciles every stale ledger before accepting
a new request. A global kernel mutex covers only ledger/ACL mutation; each launch holds a separate
for the current launch. Before mutation it recursively rejects `FILE_ATTRIBUTE_REPARSE_POINT` by
default. A manifest produced specifically for the read-only W1 Glob operation may mark its single
recursive root as non-following: the broker then records an exact grant for directories containing a
nested reparse entry, recursive grants for clean child directories, and no grant for the reparse
entry or its target. The manifest binds both the canonical authority and the original final path
entry; before ACL mutation the broker opens both with `FILE_FLAG_OPEN_REPARSE_POINT`, rejects a root
reparse point, and requires matching volume/file identity. The worker also compares an opened
directory handle with non-following path metadata before and after each `readdir`, pruning a child
whose cached `Dirent` was replaced. A finite Glob pattern binds its maximum traversal depth, so a
root-only pattern receives one exact directory grant without scanning its children; GLOBSTAR keeps
the full decomposition. The marked root itself and every multi-hard-link file included in a
recursive grant still fail closed. Decomposition fails closed above 4,096 physical grants, 100,000
directory/reparse planning entries, or 256 nested directory levels below the root. Ordinary file
count does not consume the directory planning budget. The broker persists a versioned ledger with `create_new` and `sync_all`, and
reconciles every stale ledger before accepting a new request. A global kernel mutex covers only
ledger/ACL mutation; each launch holds a separate
request-specific kernel lease through child settlement, so recovery skips live ledgers while disjoint
launches execute concurrently. Normal settlement removes the SID ACE and then deletes the ledger.

Expand Down Expand Up @@ -487,7 +504,7 @@ For the W1 preview, the packaged verifier maps the supported attack surface to e

| Category | Packaged evidence |
| --- | --- |
| Filesystem aliases | outside denial plus recursive junction and multi-hard-link admission refusal |
| Filesystem aliases | outside denial; raw recursive junction and multi-hard-link refusal; root-junction refusal; cached-directory replacement pruning; a bounded root-only Glob; and a product Glob that succeeds beside a nested junction without following it |
| Network channels | TCP connect denial without network capabilities |
| IPC | host named-pipe denial and an explicit inherited-handle list |
| Descendants | child creation is denied fail-closed, or a created descendant retains the AppContainer token and kill-on-close Job |
Expand Down
24 changes: 18 additions & 6 deletions docs/architecture/windows-sandbox-rfc-v1.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ policy 的 SHA-256,再叠加以下 Windows 控制:
- 通过 `PROC_THREAD_ATTRIBUTE_JOB_LIST` 在创建时原子附加、close 时杀整棵树的 Job Object;
- 禁止 handle inheritance;
- 只给编译后的 read/write root 添加 AppContainer ACE,并使用持久 recovery ledger;
- ACL 修改前递归拒绝 reparse point;
- ACL 修改前默认递归拒绝 reparse point;W1 filesystem worker 可以为一个只读 Glob root
显式启用不跟随分解,但 root 自身仍严格拒绝 reparse,嵌套 reparse entry 不获得 grant;
- 从规范化 command 构造封闭、排序后的环境;
- 只允许 SYSTEM 和当前用户的本地命名管道,以及有长度上限的 frame。

Expand Down Expand Up @@ -169,7 +170,8 @@ Maka 外已失陷的同用户进程。sandboxed code 从第一条指令开始按
**已强制(未标注者由 #2961 合并强制):**

- 默认拒绝文件系统,读/写 grant 分离(§6.1);
- ACL 修改前拒绝 reparse point 与多硬链接对象(§5/§6.1);
- ACL 修改前拒绝 reparse point 与多硬链接对象(§5/§6.1);唯一例外是 W1 Glob 可为一个
只读 root 显式启用分解,绕开嵌套 reparse entry,但不会授权或遍历它们;
- 每次启动使用 request-derived 独立 AppContainer SID + 版本化 ledger + startup reconcile(§6.1/§7.1);
- 不授予网络 capability 的 AppContainer token(§6.2);
- 创建时原子附加、close 时杀整棵树的 kill-on-close Job(§6.3);
Expand Down Expand Up @@ -226,7 +228,7 @@ sequenceDiagram
M-->>H: native path + one-shot manifest
H->>B: --broker-local manifest
B->>B: delete manifest; bind PID, nonce, launch digest
B->>B: recover ledger; reject reparse tree; grant SID ACE
B->>B: recover ledger; reject or partition reparse tree; grant SID ACE
B->>J: create kill-on-close Job
B->>C: create AppContainer process with atomic Job attribute
C-->>B: bounded exit result
Expand All @@ -237,8 +239,18 @@ sequenceDiagram
### 7.1 Setup 与持久状态

首个实现不需要 elevated setup。Windows 为每次 launch 创建 request-derived Maka AppContainer profile,打包
native binary 只给当前 launch 允许的 root 授予其独立 SID。修改前递归拒绝 `FILE_ATTRIBUTE_REPARSE_POINT`,用 `create_new` 和
`sync_all` 持久化版本化 ledger,并在接收新请求前 reconcile 全部遗留 ledger。正常结束先移除 SID ACE,再
native binary 只给当前 launch 允许的 root 授予其独立 SID。修改前默认递归拒绝
`FILE_ATTRIBUTE_REPARSE_POINT`。只有只读 W1 Glob 生成的 manifest 可以把它唯一的递归 root 标记为
不跟随:Broker 对含嵌套 reparse entry 的目录使用 exact grant,对干净子目录保留 recursive grant,
并且不给 reparse entry 或其 target 授权。manifest 同时绑定 canonical 权限路径与 realpath 前的原始
final entry;ACL 修改前,Broker 使用 `FILE_FLAG_OPEN_REPARSE_POINT` 打开两者,拒绝 root reparse,
并要求 volume/file identity 一致。worker 在每次 `readdir` 前后也会将已打开目录 handle 与不跟随的
路径 metadata 比较,被替换的缓存 `Dirent` 会被剪枝。有限 Glob pattern 会绑定最大遍历深度,因此
只匹配 root entry 的 pattern 只获得一个 exact 目录授权且完全不扫描子项;GLOBSTAR 继续使用完整分解。
被标记的 root 自身以及 recursive grant 涵盖的任何多硬链接文件仍然 fail closed。该分解在超过
4,096 个物理授权、100,000 个目录/reparse 规划条目或根目录以下 256 层嵌套目录时 fail closed;
普通文件数量不消耗目录规划额度。
随后用 `create_new` 和 `sync_all` 持久化版本化 ledger,并在接收新请求前 reconcile 全部遗留 ledger。正常结束先移除 SID ACE,再
删除 ledger。全局 kernel mutex 只覆盖 ledger/ACL 修改;每个 launch 在 child settlement 完成前持有独立的
request-specific kernel lease,因此 recovery 会跳过仍在使用的 ledger,同时不同 launch 仍可并发执行。

Expand Down Expand Up @@ -330,7 +342,7 @@ Windows sandbox job 必须运行真实 child-process 正反测试:

| 类别 | 打包证据 |
| --- | --- |
| 文件别名 | outside 拒绝,加递归 junction 与多硬链接准入拒绝 |
| 文件别名 | outside 拒绝、raw 递归 junction 与多硬链接准入拒绝、root junction 拒绝、缓存目录替换剪枝、受限 root-only Glob,以及产品 Glob 在嵌套 junction 旁成功且不跟随它 |
| 网络通道 | 无网络 capability 时拒绝 TCP connect |
| IPC | 拒绝宿主 named pipe,并只继承显式 handle 列表 |
| descendant | child 创建被 fail-closed 拒绝,或已创建 descendant 仍持有 AppContainer token 与 kill-on-close Job |
Expand Down
14 changes: 12 additions & 2 deletions experiments/windows-sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,18 @@ non-zero, fail-closed outcome.
`launcher --appcontainer <request.json>` is the isolated-identity candidate. It
creates a fresh request-derived AppContainer identity, combines its token with
the same atomic Job attribute, and supplies no network capabilities. Before
launch, the broker persists an ACL recovery ledger, rejects reparse points, and
grants that per-launch SID only the requested roots. A short-lived global mutex
launch, the broker persists an ACL recovery ledger, rejects reparse points by
default, and grants that per-launch SID only the requested roots. The W1
filesystem worker can explicitly mark one read-only Glob root for non-following
decomposition: nested reparse entries are omitted while clean child directories
receive narrower recursive grants; the root itself and hard links remain
fail-closed. The manifest binds both the canonical authority and the original
unfollowed root entry; the broker opens both without following and requires the
same directory identity. Finite Glob patterns also bind their maximum traversal
depth, so a root-only pattern receives one exact directory grant without scanning
its children. Planning is bounded to 4,096 physical grants, 100,000 directory or
reparse entries, and 256 nested directory levels below the root; ordinary file
count does not exhaust the planning budget. A short-lived global mutex
serializes ACL mutation, while a request-specific kernel lease distinguishes
live ledgers from abandoned ones without serializing child execution. The smoke
proves allowed read/write access, denial of a user-readable sibling file and
Expand Down
Loading
Loading