[L-01][high] 握手前阶段没有超时与并发上限:TLS 握手、CONNECT 之前、WebSocket 升级后、HTTP 空闲长连接都能无限占用资源 #20

Closed
opened 2026-09-30 13:56:54 +08:00 by nixevol · 1 comment
Owner

编号:L-01 严重级:high 工作线:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) 来源:审查 P-04
依赖:无 被依赖:无

结论与统一方案

采用审查 P-04 的方案(总审查人读 listener 时也发现同一问题)。首字节 peek 有 10 秒超时,但读完就清掉了 deadline,之后各阶段都没有时限,而 MaximumClients=2000 只统计认证之后的会话:

  1. classify 的 TLS 分支先 SetDeadline(now+10s) 再 Handshake(),成功后清零。
  2. handleConn 调 OnMQTT 之前设 SetReadDeadline(now+10s);internal/broker/ws.go 在 AttachWS 之前对 NetConn 设同样的读超时。mochi 在 OnConnect 之后的 refreshDeadline 会覆盖它;argon2 排队期间不读包,不受影响。
  3. listener 预读 CONNECT 固定头,剩余长度超过 64 KiB 直接关闭,消除 mochi 按剩余长度预分配(最大 768 KiB)的放大。
  4. http.Server 增加 IdleTimeout: 120s、MaxHeaderBytes: 64<<10。如果再加 ReadTimeout / WriteTimeout,必须在 WS Accept 之前用 http.NewResponseController(w) 清掉,否则会带到被劫持的 WebSocket 连接上。
  5. listener 加握手前连接信号量(例如 1024),从 accept 到分流完成期间占用,满了直接关闭新连接。

改动文件

internal/listener/server.go、conn.go;internal/broker/ws.go(仅 AttachWS 之前一行)。

与其他问题的交互 / 冲突说明

  • ws.go 其余部分不动,与 broker 线无冲突;L-07 会把 ws.go 的客户端 IP 解析改为调用 httpx,监听线内顺序合入。
  • 与 L-02、L-03、L-04 同文件(server.go),监听线内顺序合入。
  • SDK 的连接超时是 30 秒,10 秒读完 CONNECT 足够。

验收与测试

  • 只发 0x16,约 10 秒内被关闭。
  • 接真实 broker 只发 0x10,被关闭。
  • WS 升级后不发 CONNECT,被关闭。
  • HTTP 空闲超过 IdleTimeout(测试里调小),被关闭。
  • 发 10 FF FF 2F,立即被关闭。

问题明细(各区审查原文,证据含文件与行号)

以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。解决方案以本 issue 上方的"结论与统一方案"为准;原文里的方案与之不一致时,按上方执行。

[P-04] 握手前阶段没有超时,也没有并发上限:TLS 握手、MQTT CONNECT、WS 升级之后、HTTP 空闲长连接都能无限占用资源

  • 严重级:high
  • 分类:安全 / 并发
  • 现象与影响:
    • 首字节 peek 有 10 秒超时,但读完就把 deadline 清掉了(conn.go:32-34)。之后各阶段都没有时限:
      • TLS:Handshake() 没有 deadline,客户端发一个 0x16 就能把连接挂住(server.go:298-302)。
      • 裸 TCP/TLS 的 MQTT:交给 mochi 后,读 CONNECT 之前没有任何 deadline(mochi 只在 OnConnect 之后才调 refreshDeadline,见 P-3 引用)。而且 ReadPacket 是先按剩余长度分配缓冲再读,客户端只发 5 个字节 10 FF FF 2F,服务器就预分配约 768 KiB 并一直等下去(mochi clients.go:465-474)。
      • WebSocket:升级之后直接进 AttachWS,同样没有 deadline(ws.go:29-39)。
      • HTTP:http.Server 只设了 ReadHeaderTimeout(server.go:121-125、155-159),IdleTimeout 和 ReadTimeout 都是 0,keep-alive 空闲连接永不回收,请求体也能无限慢速上传。
    • MaximumClients=2000 只统计认证之后的会话,握手前的连接没有任何上限;30 秒握手计时器也是 CONNACK 之后才开始(session.go:109-113)。
  • 文档依据:DEVELOPMENT 4.2(10 秒内读不到首字节就关闭)、6.1「连接后 30 秒内没完成握手:断开」;PRD 第 8 节「内部连接上限按 2000 留余量」。
  • 为何不是故意设计:没有记录;握手计时只覆盖了 CONNACK 之后的阶段。
  • 解决方案:
    1. listener.classify 的 TLS 分支:先 c.SetDeadline(now+10s) 再 Handshake(),成功后清零。
    2. handleConn 调 OnMQTT 之前 out.SetReadDeadline(now+10s);ws.go 在 AttachWS 之前 nc.SetReadDeadline(now+10s)(coder 的 NetConn 支持)。mochi 在 OnConnect 之后调 refreshDeadline 会覆盖掉这个 deadline;argon2 排队期间不读包,不受影响。
    3. listener 预读 CONNECT 的固定头,剩余长度超过 64 KiB 就直接关闭,消除 768 KiB 预分配的放大。WS 连接也可以包一层带缓冲的连接来预读。
    4. http.Server 增加 IdleTimeout: 120s 和 MaxHeaderBytes: 64<<10。如果还要加 ReadTimeout 或 WriteTimeout,必须在 WSHandler 里 Accept 之前用 http.NewResponseController(w).SetReadDeadline(time.Time{}) 和 SetWriteDeadline(time.Time{}) 清掉,否则会带到被劫持的 WS 连接上。
    5. listener 加一个握手前连接的信号量(例如 1024),从 accept 到分流完成期间占用,满了就直接关闭新连接。
  • 改动文件:internal/listener/server.go、conn.go;internal/broker/ws.go
  • 与其他模块的交互/冲突风险:SDK 的连接超时是 30 秒,10 秒读完 CONNECT 足够;和 P-6 一起修之后,停机不会再被卡住的握手 goroutine 拖住。
  • 需补测试:
    • 只发 0x16,约 10 秒内被关闭;
    • 接真实 broker 只发 0x10,被关闭;
    • WS 升级后不发 CONNECT,被关闭;
    • HTTP 空闲超过 IdleTimeout(测试里调小),被关闭;
    • 发 10 FF FF 2F,立即被关闭。
  • 置信度:代码阅读确定;768 KiB 预分配对实际内存占用的放大程度见待核实第 2 条

复审基线:main 4059a15(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。

**编号**:L-01 **严重级**:high **工作线**:监听与 HTTP(internal/listener、internal/httpx、serve 的 HTTP 装配) **来源**:审查 P-04 **依赖**:无 **被依赖**:无 ### 结论与统一方案 采用审查 P-04 的方案(总审查人读 listener 时也发现同一问题)。首字节 peek 有 10 秒超时,但读完就清掉了 deadline,之后各阶段都没有时限,而 `MaximumClients=2000` 只统计认证之后的会话: 1. `classify` 的 TLS 分支先 `SetDeadline(now+10s)` 再 `Handshake()`,成功后清零。 2. `handleConn` 调 `OnMQTT` 之前设 `SetReadDeadline(now+10s)`;`internal/broker/ws.go` 在 `AttachWS` 之前对 NetConn 设同样的读超时。mochi 在 `OnConnect` 之后的 `refreshDeadline` 会覆盖它;argon2 排队期间不读包,不受影响。 3. listener 预读 CONNECT 固定头,剩余长度超过 64 KiB 直接关闭,消除 mochi 按剩余长度预分配(最大 768 KiB)的放大。 4. `http.Server` 增加 `IdleTimeout: 120s`、`MaxHeaderBytes: 64<<10`。如果再加 `ReadTimeout` / `WriteTimeout`,必须在 WS `Accept` 之前用 `http.NewResponseController(w)` 清掉,否则会带到被劫持的 WebSocket 连接上。 5. listener 加握手前连接信号量(例如 1024),从 accept 到分流完成期间占用,满了直接关闭新连接。 ### 改动文件 `internal/listener/server.go`、`conn.go`;`internal/broker/ws.go`(仅 `AttachWS` 之前一行)。 ### 与其他问题的交互 / 冲突说明 - ws.go 其余部分不动,与 broker 线无冲突;L-07 会把 ws.go 的客户端 IP 解析改为调用 httpx,监听线内顺序合入。 - 与 L-02、L-03、L-04 同文件(server.go),监听线内顺序合入。 - SDK 的连接超时是 30 秒,10 秒读完 CONNECT 足够。 ### 验收与测试 - 只发 0x16,约 10 秒内被关闭。 - 接真实 broker 只发 0x10,被关闭。 - WS 升级后不发 CONNECT,被关闭。 - HTTP 空闲超过 IdleTimeout(测试里调小),被关闭。 - 发 `10 FF FF 2F`,立即被关闭。 --- ### 问题明细(各区审查原文,证据含文件与行号) > 以下是本次复审各区审查报告的原文段落。A、M、I、P、S 开头的是原始发现编号(A 管理后台与网页、M 消息核心、I 身份认证群在线、P 传输平台部署、S SDK)。**解决方案以本 issue 上方的"结论与统一方案"为准**;原文里的方案与之不一致时,按上方执行。 #### [P-04] 握手前阶段没有超时,也没有并发上限:TLS 握手、MQTT CONNECT、WS 升级之后、HTTP 空闲长连接都能无限占用资源 - **严重级**:high - **分类**:安全 / 并发 - **现象与影响**: - 首字节 peek 有 10 秒超时,但读完就把 deadline 清掉了(`conn.go:32-34`)。之后各阶段都没有时限: - **TLS**:`Handshake()` 没有 deadline,客户端发一个 0x16 就能把连接挂住(`server.go:298-302`)。 - **裸 TCP/TLS 的 MQTT**:交给 mochi 后,读 CONNECT 之前没有任何 deadline(mochi 只在 OnConnect 之后才调 `refreshDeadline`,见 P-3 引用)。而且 `ReadPacket` 是先按剩余长度分配缓冲再读,客户端只发 5 个字节 `10 FF FF 2F`,服务器就预分配约 768 KiB 并一直等下去(mochi `clients.go:465-474`)。 - **WebSocket**:升级之后直接进 `AttachWS`,同样没有 deadline(`ws.go:29-39`)。 - **HTTP**:`http.Server` 只设了 `ReadHeaderTimeout`(`server.go:121-125`、`155-159`),`IdleTimeout` 和 `ReadTimeout` 都是 0,keep-alive 空闲连接永不回收,请求体也能无限慢速上传。 - `MaximumClients=2000` 只统计认证之后的会话,握手前的连接没有任何上限;30 秒握手计时器也是 CONNACK 之后才开始(`session.go:109-113`)。 - **文档依据**:DEVELOPMENT 4.2(10 秒内读不到首字节就关闭)、6.1「连接后 30 秒内没完成握手:断开」;PRD 第 8 节「内部连接上限按 2000 留余量」。 - **为何不是故意设计**:没有记录;握手计时只覆盖了 CONNACK 之后的阶段。 - **解决方案**: 1. `listener.classify` 的 TLS 分支:先 `c.SetDeadline(now+10s)` 再 `Handshake()`,成功后清零。 2. `handleConn` 调 `OnMQTT` 之前 `out.SetReadDeadline(now+10s)`;`ws.go` 在 `AttachWS` 之前 `nc.SetReadDeadline(now+10s)`(coder 的 NetConn 支持)。mochi 在 OnConnect 之后调 `refreshDeadline` 会覆盖掉这个 deadline;argon2 排队期间不读包,不受影响。 3. listener 预读 CONNECT 的固定头,剩余长度超过 64 KiB 就直接关闭,消除 768 KiB 预分配的放大。WS 连接也可以包一层带缓冲的连接来预读。 4. `http.Server` 增加 `IdleTimeout: 120s` 和 `MaxHeaderBytes: 64<<10`。如果还要加 `ReadTimeout` 或 `WriteTimeout`,必须在 `WSHandler` 里 `Accept` 之前用 `http.NewResponseController(w).SetReadDeadline(time.Time{})` 和 `SetWriteDeadline(time.Time{})` 清掉,否则会带到被劫持的 WS 连接上。 5. listener 加一个握手前连接的信号量(例如 1024),从 accept 到分流完成期间占用,满了就直接关闭新连接。 - **改动文件**:`internal/listener/server.go`、`conn.go`;`internal/broker/ws.go` - **与其他模块的交互/冲突风险**:SDK 的连接超时是 30 秒,10 秒读完 CONNECT 足够;和 P-6 一起修之后,停机不会再被卡住的握手 goroutine 拖住。 - **需补测试**: - 只发 0x16,约 10 秒内被关闭; - 接真实 broker 只发 0x10,被关闭; - WS 升级后不发 CONNECT,被关闭; - HTTP 空闲超过 IdleTimeout(测试里调小),被关闭; - 发 `10 FF FF 2F`,立即被关闭。 - **置信度**:代码阅读确定;768 KiB 预分配对实际内存占用的放大程度见待核实第 2 条 --- <sub>复审基线:main `4059a15`(2026-09-30)。编号说明、各工作线的合并顺序、共享文件归属见总览 #7。</sub>
nixevol added the P1-highlane/listenerreview-2026-09-30 labels 2026-09-30 13:56:54 +08:00
Author
Owner

已合入 origin/main 0c9b459。落地提交 c0b2903 fix: 修复监听 accept 退避与握手前超时上限 (#20)。

已合入 origin/main `0c9b459`。落地提交 `c0b2903` fix: 修复监听 accept 退避与握手前超时上限 (#20)。
Sign in to join this conversation.