Files
MobileGL/docs/Disaggregated/notes/p7/ph-f.md
T

323 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# P7 wave 2-F:Ph 小件(分支 `p7/ph-f`)
> 计划见 [`PLAN-PH-P34B-P7.md`](PLAN-PH-P34B-P7.md) §1.1(PH-7、PH-8、D11、门行)与 §3 wave 2-F;
> 裁定见 [`INTEGRATOR-DECISIONS-P7.md`](INTEGRATOR-DECISIONS-P7.md) ID-P7-3(令牌足够、不做 TLS、
> `Welcome.dataNonce`);规范见 [`CONTRACT-P7.md`](../../../../MobileGL/MG_Remote/CONTRACT-P7.md) §0
> 规则 I / J、§9 簇 F、§10 第 3 条。基线 `ec46a550`。
>
> 主机口径:WSL Arch + lavapipe,`build-split` 与集成者同配置(integration tests ON)。flatc 取
> `3rdparty/flatbuffers` 子模块构建的钉版本(`scripts/gen_protocol.py`)。
>
> 基线门读数(`~/w7/logs/p7-gate-f-base.log`):G1 `.text` `0xa52203`、符号 0/0;普查 79 处 abort、
> 0 未标记;棘轮 173;车道 unit 2429 / split 303 / spawn 219 / tcp 222 / magma-tcp 74。
## 0. 每片一条提交,每片一次 red-once
下表的 SHA 都是**包树**(分支 `p7/ph-f`)上的;集成者 cherry-pick 到 `pipe` 后以那边的为准。
| 片 | 项 | 提交 | 状态 |
|---|---|---|---|
| 1 | PH-7 (1)(2)(3):常量时间令牌合一、≥16 字节(TCP 监听处)、无令牌只 loopback、smoke 入 CI | `2975a337` | 落地 |
| 2 | PH-7 (4):`Welcome.dataNonce` 把数据连接绑定到已认证的控制连接 | `596b34fd` | 落地 |
| 3 | PH-8:Hello 要 64 GiB → server 忽略请求、按自己的尺寸建段且不分配 | `a746417a` | 落地 |
| 4 | D11 五处上限 + PH-2 | — | **未做**,见 §4 |
| 5 | PH-6 drop-with-latch | — | **未做**(顺序在 4 之后) |
| 6 | PH-1 (3)(4) | — | **未做**(顺序在 4 之后) |
| F-a | 审查修复 1+4:绑定后关闭 hand-off / 单会话监听者,hand-off 发送不阻塞 | `3370833f` | 落地,见 §6.1 |
| F-b | 审查修复 2:`sha256(protocol.fbs)` 钉住 `MOBILEGL_PROTOCOL_CONTROL_REVISION` | `75f24db0` | 落地,见 §6.2 |
| F-c | 审查 nit 6/8/9/10(代码) | `2b2a0ae0` | 落地,见 §6.3 |
| F-d | 审查 nit 3/5/7/11 + 两条 §12 债(文档) | 本提交 | 落地,见 §6.4 |
red-once 原始行汇总在 `~/w7/logs/p7-ph-f-redonce.txt`。
---
## 1. 片 1:令牌策略合一(PH-7 (1)(2)(3),ID-P7-3)
### 1.1 机制
`MOBILEGL_IPC_TOKEN` 原来有三种读法:`SocketTransport.cpp` 的 `ListenTcp` 只问「非空」,所以一个字节
的令牌也能打开 `tcp://0.0.0.0`;`ServerSession::Accept` 用 `std::strcmp`;`ServerMain::RunSession` 用
`std::string::operator==`,且在 server 没配令牌时反而要求对端送空令牌。两处比较都在第一个不同字节处返回。
- 新文件 `MG_Remote/Transport/AuthToken.h`:`ConfiguredAuthToken()`(空值 = 未配置)、
`kMinimumAuthTokenBytes = 16`(**只在 TCP 监听处**问,见下条;unix 端点既不要求令牌也不问长度)、
`ConstantTimeTokenMatch`(F 修复轮起循环固定 64 字节、两端越界读零,更长的令牌比到最后一字节;长度差并入
累加器,嵌入的 NUL 参与比较)。
- `Handshake.h` `AuthenticatePeerToken`:**唯一**的策略函数,`ServerSession::Accept` 与 supervisor 子进程
(`ServerMain::RunSession`)都调它。不配置令牌时不因对端送了令牌而拒绝——策略由监听侧的 loopback 限制负责。
- `ListenTcp`:配置了但不足 16 字节的令牌**让 TCP 监听失败**(具名 `Refuse{Authentication} ... minimum is 16`,
进程退出 72),而不是悄悄退化成 loopback server。令牌值从不进日志。unix 监听者不检查这条:那条路上令牌
不是访问控制(§6.4 的本地对端债)。
- `Refuse{Authentication}` 是 `protocol.fbs` `RefuseCode` 的枚举值,`fatal_census.py` 规则 4 的词表门
直接认它;普查前后都是 3 个 refusal 词,无需新增 `.def` 行(Refuse 词不是 Fatal 家族词)。
- CI:`TcpLane.SupervisorProtocolControls` 从 wave 0 起挂在 `integration-tcp` 上却因 CI 没有 flatc 而恒为
SKIP。`test.yml` 的 integration-split 作业现在从子模块构建钉版 flatc(`gen_protocol.py --check`,
不改已提交头文件),导出 `MOBILEGL_FLATC_EXECUTABLE`,并用 `junit_tally.py --require-entry-passed
TcpLane.SupervisorProtocolControls` 断言它**跑了且过了**——SKIP 在这里等于红(`tcp_lane_tools_test.py`
覆盖 passed / skipped / notrun / failed / absent 五态)。
- smoke 新增两条监听控制:无令牌 + 通配地址 → 具名拒绝;13 字节令牌 + loopback → 具名拒绝、行里不含令牌。
### 1.2 red-once(两进程,`TcpLane.SupervisorProtocolControls`)
把 `SocketTransport.cpp` 还原到 `ec46a550`、只重链 `libMobileGL.so`:
```
RED AssertionError: (None, '... MG_Remote server: pid=42256 listening on tcp://127.0.0.1:46447')
GREEN short_token_listen exit 72: MG_Remote: Refuse{Authentication} MOBILEGL_IPC_TOKEN is 13 bytes and the
minimum is 16 (PH-7 (3)); refusing to listen on tcp://127.0.0.1:46545 rather than authenticating with
a guessable secret
```
即:改前 13 字节令牌起了一个活的监听者;改后进程以名字拒绝。单测 `SocketTransportTest.TcpRefusesToListen
WithATokenShorterThanTheMinimum` 与 `TheTokenComparisonAnswersLengthAndPrefixTheSameWay` 是廉价的那一半。
---
## 2. 片 2:`Welcome.dataNonce`(PH-7 (4),ID-P7-3)
### 2.1 现场故障
设备窗口 #1:经 Windows `adb forward` 时一个会话的两条连接**乱序**到达;`AcceptPair` 规定「先到的是控制、
后到的是数据」,于是数据连接被当成控制读 Hello,每个子进程等 10 s 后以 67 退出(日志里伴随
`only 1 of 2 connections arrived within 250 ms`)。同一条规则也意味着任何能连到端口的人都能把一条连接
插进窗口、成为别人会话的数据面。
### 2.2 机制
- `protocol.fbs`:`Welcome` 末尾追加 `dataNonce: [ubyte]`;新表 `DataBind { nonce: [ubyte]; }` 追加到
`CtrlMsg` 末尾。头文件由钉版 flatc 重新生成。
- **wire 指纹随之改变。** 原指纹的输入全是数据面的结构尺寸与编解码摘要,控制 schema 改形状不会动它。新增
`MOBILEGL_PROTOCOL_CONTROL_REVISION`(`mg_protocol_base.h`,本次 = 1)作为 `AbiFingerprintInputs.
ControlSchemaRevision` 混入;`SessionHandshakeTest.TheAbiFingerprintChangesWhenAnyOfItsInputsDoes` 钉住
该输入并加扰动行。实测 `wireFingerprint` 由 `0x7d4470f241d23539` 变为 `0x03c4c293a54b3e08`。
**设备侧后果——两个方向不对称**(审查 nit 3):
- **新 server + 旧 client**:旧 client 仍开两条连接、送旧指纹,新 supervisor 读到 Hello 后以
`Refuse{WireFingerprint}` 具名拒绝——这是有帧的那个方向。
- **旧 server + 新 client**(= 手机上的 server **没有**随本提交重装的情形):新 client 只开**一条**连接;
旧 supervisor 的 `AcceptPair` 等第二条连接 2000 ms,记 `only 1 of 2 connections arrived within ... ms`,
把两条都关掉、继续监听——**没有子进程、没有读 Hello、没有任何 `Refuse` 帧**。client 侧的症状是
`MG_Remote client: no Welcome ... (rc=6)`(`MOBILEGL_ERR_TRANSPORT_CLOSED`,对端 EOF),不是一条
具名拒绝。看到这条就先查设备上的 APK 是否是本次构建(`device-window-1-runbook.md` §2 第 3 步)。
- 指纹随 `MOBILEGL_PROTOCOL_CONTROL_REVISION` 变,而 revision 是手改的整数:F 修复轮把
`sha256(protocol.fbs)` 钉在它旁边(§6.2),改了 schema 不 bump 在单元车道与 flatc-check 里都是红。
- 流程(仅 `tcp://`;unix 端点的第二条连接是 SCM_RIGHTS 套接字,保持 `AcceptPair`——那是本地对端债,
§6.4):
1. client `SocketTransport::ConnectControl` 只连控制连接,发 Hello;
2. server 子进程在令牌通过后用内核 CSPRNG(`getrandom`,退回 `/dev/urandom`,绝不退回 PRNG)铸 128 位
nonce,`StreamLink::AttachOwnedDeferred` 先建好私有内存(Welcome 要宣告其尺寸)但**不带 fd、不起
reader 线程**,发 Welcome(带 nonce);
3. client 收到 Welcome 后 `ConnectDataConnection` 开数据连接,第一帧是 `DataBind{nonce}`,随后才挂
StreamLink;
4. supervisor 一次只 `AcceptOne`:无活会话时新连接是控制连接、fork 子进程(带一对 hand-off
socketpair);有活会话时读新连接的**恰好一帧**(`ReceiveOneFrame` 不多读一个字节——DataBind 后面
紧跟 StreamLink 自己的帧),是 DataBind 就把 fd 连同所呈 nonce(sideband)经 `FdPassing::SendFd`
交给子进程,否则照旧 `Refuse{Busy}`;
5. 子进程里 `ServerSession::BindDataConnection` 是**唯一**比较点(`ConstantTimeNonceMatch`):匹配者
`StreamLink::BindDataFd` 起 reader;不匹配者在**那条连接上**回 `Refuse{Authentication} data
connection nonce mismatch` 并关闭,等待继续——陈旧或抢插的连接结束不了它没能加入的会话。截止时间
(5 s)到则在控制连接上回 `Refuse{Authentication} no data connection presented the session nonce`;
控制对端先走则立即结束等待(子进程按 0 退出,不计入 `sessionsFaulted`)。
6. 无活会话时到达的 DataBind 由子进程具名拒绝:`Refuse{Authentication} data connection names no live
session`,干净退出 0。
- 250 ms / 2000 ms 的「两连接到达窗口」在 TCP 上不复存在;取而代之的是 nonce 匹配。非 `--serve` 的单会话
TCP 模式保留监听者,由会话自己 accept 并读 DataBind(`ListenerSource`),比较仍在同一处;F 修复轮起,
数据连接绑定后监听者随即关闭(§6.1)。
- 仍然**没有**做的:ID-P7-3 说「认证在 fork 之前完成」——本片的令牌检查与 nonce 铸造仍在 fork 出的会话子
进程里(每会话一进程,不影响绑定的正确性);把 Hello 读取与认证移进 supervisor 属于 PH-7 (5)「有界的预
认证工作 + 失败退避」,未做。
### 2.3 red-once(两进程)
探针只跑「数据连接先到、控制连接后到」这一条(新 smoke 的 (a)),server 分别是片 1 的头与本片:
```
RED (片 1 server) stray: RuntimeError: control channel closed before a complete frame
control: ConnectionResetError: [Errno 104] Connection reset by peer
supervisor: session pid=1705895 reaped exit=67 sessionsFaulted=1 ← 现场故障原样
GREEN stray: Refuse{Authentication} 'data connection names no live session'(子进程 exit=0)
control: Welcome,nonce 16 字节
```
smoke 常驻控制(`TcpLane.SupervisorProtocolControls`):`data_connection_first`(上面这条)与
`stale_nonce`(会话活着时先呈一个取反的 nonce → 那条连接收到 `Refuse{Authentication} data connection
nonce mismatch`,日志 `MGPipe: Refuse{Authentication} data connection nonce mismatch`,随后正确的 nonce
绑定、子进程打出 `ready`)。单测 `SocketTransportTest.TheDataBindIsReadExactlyAndTheStreamBehindItIsLeft
OnTheSocket` 钉住「恰好一帧」与 16 字节宽度。
`integration-tcp`(真 GL client 走新流程)222/222、`integration-magma-tcp` 74/74 保持全绿。
---
## 3. 片 3:PH-8(server 钳制尺寸)
### 3.1 机制
没有改代码:`ServerSession::Accept` 只看 Hello `linkTerms` 的 `dataPlane` / `wireForm`,段按 server 自己的
`SetSegmentSizes` 建,Welcome 宣告的也是这组。准确地说这不是「钳制」而是**忽略、按 server 尺寸**:四个计数
根本没被读去定尺寸(审查 nit 8);F 修复轮起,请求超过所授条款时 `Accept` 记一条 `MGLOG_W`(§6.3),生产
client 的 Hello 不填这四个数,所以正常会话不会打这条。本片把它钉住:
- 单测 `SessionHandshakeTest.AHelloAskingFor64GiBIsClampedToTheServersTermsAndAllocatesNothingOfIt`:
Hello 四个窗口各要 64 GiB,Welcome 必须回 server 的 4 KiB 条款,四个 SegmentRef 各 ≤ 64 KiB。
- smoke `hello_asks_64_gib`:两进程下同样的请求,Welcome 的条款 < 1 GiB,且回答它的会话子进程(Welcome
`serverPid`)在数据连接绑定后(即全部段已存在)`VmPeak` < 64 GiB。实测:maxReply 2097136、cmd 8 MiB、
stage 32 MiB、event 256 KiB,子进程 VmPeak 237264896。
### 3.2 red-once(变异:`Accept` 改为按 Hello 的条款定尺寸,已还原)
```
RED unit SessionHandshakeTest.cpp:294 Accept rc 2 != 0(64 GiB 私有段建不出来)
RED smoke hello_asks_64_gib: RuntimeError: control channel closed before a complete frame(子进程死于该请求)
GREEN 如 3.1
```
---
## 4. 片 4–6 未做,以及为什么停在这里
计划的停止规则:「某片超过一天就停在上一片」。片 4(D11 五处 + PH-2)的代码改动本身不大,但每一处都要求
**一条在两进程臂上、字节来自对端的负控**,而树上没有任何车道能把一条**越过 client 自身守卫**的畸形记录
送进一个 spawn / TCP server:
- `create_render_state` 的 `Cso.Slot`、`read_pixels` 的 `W×H`(client 在发射前按回复槽尺寸自检)、程序归档
里的 `ArchiveVector` 计数、`StagedTextureStore` 的 run 与 level extent、`SlotTables` 的 handle slot/gen——
全都由 client 的编码器生成且被它自己的检查挡住;要到达 server 必须有一个能按需伪造记录的对端(fuzz 臂 2
「超界计数」一行正是这件事,计划里记在 P7 之后)。
- 另外两处落在簇 F 之外、且会碰 pull 构建:`ProgramArtifactsCodec.cpp` 编进 pull 构建(G1 `.text` 要求新界限
只能在 `MOBILEGL_BUILD_DISAGGREGATED` 内),`SlotTables.h` 在簇 D 的 `MG_Backend/DirectGLES/` 下,且
`MGPipeFatalFamily` 目前只有 `UnmigratedVerb` / `RoleViolation`,PH-2 的具名拒绝需要先加一个
`ProtocolCorruption` 族并在 hook 映射里接上。
- 已核实的一点备查:`MG_Pipe/PipeApply.cpp` **不在** pull 构建里(`build-linux/compile_commands.json`
无该文件),D11 第一行在那里改不影响 G1。
所以片 4 连同其后的片 5(PH-6)、片 6(PH-1 (3)(4))按规则未开工。建议下一包先落「对端伪造记录」的两进程
驱动(它同时是 fuzz 臂 2 的骨架),再逐处上界限,每处一条经它红一次的负控。
---
## 5. 门(片 1–3 的头 `a746417a`,包树 SHA;F 修复轮的头见 §7)
| 门 | 基线 `ec46a550` | 头 |
|---|---|---|
| G1 pull `.text` / 符号增删 | `0xa52203` / 0/0 | `0xa52203` / 0/0(`build-linux` 无需重编:簇 F 文件不在 pull 构建) |
| `fatal_census.py` | 79 abort、44 家族词、3 refusal 词、0 未标记 | 同左 |
| `link_ratchet.py --assert-monotone` | 173 | 173(不变) |
| `spawn_lane_parity.py` | 0 错 | 0 错 |
| unit | 2429 | 2433(+4:片 1 两条、片 2 一条、片 3 一条) |
| integration-split | 303 | 303 |
| integration-spawn | 219 | 219 |
| integration-tcp | 222 | 222 |
| integration-magma-tcp | 74 | 74 |
---
## 6. F 修复轮:片 1–3 的 fable 审查(裁定 land with fixes)
审查的 11 条里 should-fix 五条(1–5)、nit 六条(6–11);下面按提交分。每条都有一次 red-once,原始行在
`~/w7/logs/p7-ph-f-redonce.txt` 的 `F fix round` 段。
### 6.1 修复 1 + 4(`3370833f`):绑定之后,数据连接的来源随即关闭
**缺陷 1(回归)。** 子进程只在 `BindDataConnection` 里读 hand-off socketpair,数据连接绑定后再也不读;而
supervisor 的 `RouteWhileBusy` 对此后**每一条** DataBind 仍然 `SendFd` 进去。后果:每个 fd 在子进程的接收
队列里躺着、对端**没有任何回复**(不是具名拒绝;改前基线对第二条连接还会回 `Refuse{Busy}`),那条 TCP 连接一直
挂到会话结束;队列到 `net.unix.max_dgram_qlen`(手机内核默认 10;本机 512,但发送端 sndbuf 在 ~288 个
datagram 处先满)后 supervisor **阻塞在 `sendmsg`** 直到会话结束——不再 accept、不再回 Busy、不再 reap。
**缺陷 4。** 非 `--serve` 的单会话 TCP 模式为 `ListenerSource` 留着监听者,数据连接绑定后没人关它:内核继续
完成三次握手进 backlog,第二个 client / 重试要等满 `kSpawnConnectTimeoutMs`(20 s)而不是像改前那样立即
`ECONNREFUSED`。
**修法(同一个钩子)。** `RunSession` 多收一个 `sourceFd`——`--serve` 下是子进程那端 `pair[1]`,单会话下是
监听者——`session.Accept()` 一返回就 `close` 并清掉 `DataConnectionSource`。于是 (1) supervisor 再转发时
`sendmsg` 得到 `ECONNREFUSED`/`ENOTCONN`(`FdPassing::SendFd` 把「对端已走」当答案而不是错误日志),已有的
`Refuse{Authentication} data connection could not be handed to the live session` 按名字打出;`SendFd` 加
`dontWait`(`MSG_DONTWAIT`),队列因别的原因满也拒绝而不是卡住;(4) 单会话的第二个连接立即 `ECONNREFUSED`。
**red-once**(server 还原到 `da313010`,只重建 `.so`):
```
RED smoke data_bind_after_bound: TimeoutError: timed out(第一条多余 DataBind 的 receive 就超时——没人回)
RED probe f1probe.py(qlen+2 条 DataBind 只发不收): extras opened=288 of 514 后 connect/send 超时;
answered by name=0 silent=288;此后新开控制连接: CONNECT FAILED TimeoutError(supervisor 卡在 sendmsg);
supervisor alive=True
RED smoke single_session_listener: 绑定后的第二个 connect 成功(监听者还开着)
GREEN smoke data_bind_after_bound: refused=12,每条 Refuse{Authentication} 'data connection could not be
handed to the live session';随后新开控制连接 → Refuse{Busy}
GREEN probe: 514/514 具名拒绝、silent=0、新控制连接 → Busy、supervisor alive
GREEN smoke single_session_listener: second_connect=ConnectionRefusedError(111)
```
smoke 常驻控制两条:`data_bind_after_bound`(会话 `ready` 后 12 条多余 DataBind 各得具名拒绝、再开控制连接仍
Busy)与 `single_session_listener`(无 `--serve` 的 supervisor,`ready` 后 connect 必须 `ECONNREFUSED`)。
`held_session` / `pair` 共用 `connect_control`(启动期 `ECONNREFUSED` 重试 5 s)。
### 6.2 修复 2(`75f24db0`):`sha256(protocol.fbs)` 钉住 revision
revision 是手改整数,改了 `protocol.fbs` 不 bump 就回到「新旧对端指纹相同、字段静默缺失」。
`scripts/ci/protocol_revision_pin.py` 持有表 `{revision → sha256(protocol.fbs)}`
(`protocol_revision_pins.json`,行尾折成 LF 再哈希),当前 revision 没有行、或行与文件摘要不符都红;`--write`
在 bump 后记新行,且拒绝把已有 revision 改钉到另一个摘要(那正是「改 schema 没 bump」的样子)。注册为
`ProtocolSchema.RevisionPinsDigest`(label unit,`--self-test` 先证明两种扰动都红才说绿),并作为 CI
`flatc-check` 作业的一步——正是每次 schema 改动都要重新生成头文件的那个作业。
```
RED protocol.fbs 末尾加一行注释、revision 仍为 1:
::error::protocol.fbs has sha256 a7b5c7fb888d2926... but MOBILEGL_PROTOCOL_CONTROL_REVISION is still 1,
which is pinned to 7fc859f7fa756818... rc=1
GREEN 还原: protocol revision pin: revision 1 holds (sha256 7fc859f7fa756818..., 1 revision(s) pinned,
self-test red on both perturbations) rc=0
```
### 6.3 nit 6 / 8 / 9 / 10(`2b2a0ae0`)
- **nit 6** `AuthToken.h`:改前循环长度 = 本端令牌长度,响应时间正比于**秘密**的长度而头注释写得像什么都不泄。
现在循环固定 `kAuthTokenCompareBytes = 64`(更长的令牌延到其长度,只泄露「长于 64」),两端越界读零、长度差
仍并入累加器、不延到对端长度(不让对端选我们的工作量)。`SocketTransportTest.TheTokenComparisonAnswersLength
AndPrefixTheSameWay` 加 70 字节令牌最后一字节判定的用例。
- **nit 8** `ServerSession::Accept`:请求超过所授条款 → 一条 `MGLOG_W`:`Hello asked for windows the server
does not grant (cmd=68719476736 ...); the ask is ignored and the session is server-sized (cmd=8388608
stage=33554432 event=262144 maxReply=2097136) - PH-8`。单测 §3.1 那条现在要求这一行(改前红:行不存在)。
- **nit 9** `ServerSession::Accept`:`MintNonce` 失败先在控制连接上回 `Refuse{LinkTerms} stream data plane
unavailable: the server could not mint a data nonce`,再返回原错误(仍是 67、仍计入 `sessionsFaulted`——
CSPRNG 给不出字节是 server 的故障不是对端的条款)。没有驱动:不注入故障造不出 `getrandom` + `/dev/urandom`
同时失败,记录于此。
- **nit 10** `RouteWhileBusy`:有活会话时首帧 magic 错或超 1 MiB(`ReceiveOneFrame` 回 `PROTOCOL_MISMATCH`)
改回 `Refuse{MalformedHello} first frame is not a control frame`(`Accept` 对不可验证 Hello 用的同一个词),
关闭前先把对端已发的字节读掉免得 RST 冲掉拒绝帧;格式正确但不是 DataBind 的帧仍是 Busy。smoke 常驻控制
`garbage_first_frame_while_busy`(magic `XXXX` → code 8;改前红:code 7)。普查仍 3 个 refusal 词(新拒绝
走枚举值,源码里没有新字面量)。
### 6.4 记录债(本轮补记;CONTRACT-P7 §12 同步)
- **unix 端点仍按到达顺序配对**(`AcceptPair` 的 250 ms / 2000 ms 窗口)。`SocketTransport.h` 把它写成
「同用户 rendezvous、别人够不着」——但抽象命名空间套接字**没有 mode bits**,「只有本 app 能连」这条性质靠的是
Android 的 SELinux 域隔离,不是传输层自己。本地对端债,不在 Ph 的远端威胁模型里,未做。
- **两处 wave 2-F 之前就存在的预认证暴露**(审查核实、归 PH-7 (5)):(a) supervisor 对每条 TCP 连接**先 fork
再读 Hello**,未认证对端每连接可让 server 付出一次 fork + 最长 10 s 的 Hello 等待;(b) `ValidatePeerHandshake`
在 `AuthenticatePeerToken` **之前**跑,`Refuse{ProtocolVersion|WireFingerprint|BuildFingerprint}` 把本端
wire 指纹与 build stamp 送给了未认证的对端。**(P7 F2 包 f2-auth 已关 (a)(b)**:supervisor 在 fork 前自己读首帧、
先验令牌,待认证工作有期限、有上限、按地址公平让位,失败按地址退避;unix 端点的「先 fork 再读 Hello」仍在,
归本地对端债。旋钮与留下的债见 CONTRACT-P7 §12 末条、`MG_Remote/Server/PreAuthGate.h`。**)**
- 令牌最小长度只在 TCP 监听处检查(§1.1)。
---
## 7. 门(F 修复轮的头 `2b2a0ae0`,包树 SHA;F-d 只改文档,二进制同此头)
读数 `~/w7/logs/p7-ph-f-fgate.log`、车道日志 `~/w7/logs/p7-ph-f-ffinal-*.log`。
| 门 | 片 1–3 头 `a746417a` | F 修复轮头 |
|---|---|---|
| G1 pull `.text` / 符号增删 | `0xa52203` / 0/0 | `0xa52203` / 0/0(`build-linux` 无工作;`compile_commands.json` 里 `MG_Remote` 条目 0——簇 F 文件不在 pull 构建,`mg_protocol_base.h` 只改注释) |
| `fatal_census.py` | 79 abort、44 家族词、3 refusal 词、0 未标记 | 同左(新拒绝走枚举值,无新字面量) |
| `link_ratchet.py --assert-monotone` | 173 | 173 |
| `spawn_lane_parity.py` | 0 错 | rc 0(split 193 / spawn 190 / tcp 190) |
| `protocol_revision_pin.py --self-test` | — | revision 1 holds,两种扰动均红 |
| G14 名集合 | — | 对 `~/w7/p7-before` 基线 removed=1(`CapsMirrorTest.APlaceholderMirrorConsumesNothing`,在本包基线 `da313010` 上已不存在,是更早的 P7 波次退的)、added=2819;本轮只增 `ProtocolSchema.RevisionPinsDigest` |
| unit | 2433 | 2434(+1:`ProtocolSchema.RevisionPinsDigest`;`SessionHandshakeTest` / `SocketTransportTest` 的新断言在既有用例内) |
| integration-tcp | 222 | 222(`TcpLane.SupervisorProtocolControls` Passed,evidence 17 项含 `data_bind_after_bound` / `garbage_first_frame_while_busy` / `single_session_listener`) |
| integration-spawn | 219 | 219 |
| integration-magma-tcp | 74 | 74 |