Skip to content

hysteria2 / tuic: UpdateUsers 用数组下标作为 ctx 中的用户标识,热更新后流量错位且可能 panic #54

@oballm

Description

@oballm

问题概述

UpdateUsers 把 数组下标(int)写进了连接的 ctx 作为用户标识。下标在热更新(添加/删除用户)后会指向不同的人,导致:

  1. 流量归因错位:原本绑定到下标 3 的长连接,更新后被记到新的下标 3 用户头上。
  2. 越界 panic:新列表更短时,h.userNameList[userID] 直接越界,goroutine panic。由于 sing-quic/hysteria2.Service 的 accept 循环没有 recover(),整个 sing-box 进程会崩溃。

只影响 cedar2025 fork(fork 自行加的 UpdateUsers 接口),上游 sagernet/sing-box 没有这段代码。同分支的 anytls 用法是对的,可以作为参考。

受影响协议

┌───────────┬─────────────────┬─────────────┐
│ 协议 │ ctx 类型 │ 是否有 bug │
├───────────┼─────────────────┼─────────────┤
│ hysteria2 │ int (数组下标) │ ❌ │
├───────────┼─────────────────┼─────────────┤
│ tuic │ int (数组下标) │ ❌ │
└───────────┴─────────────────┴─────────────┘

根因代码

protocol/hysteria2/user.go:
for index, user := range users {
userList = append(userList, index) // ← 把下标当 ID
userNameList = append(userNameList, user.Name)
userPasswordList = append(userPasswordList, user.Password)
}
h.service.UpdateUsers(userList, userPasswordList)
h.userNameList = userNameList

protocol/hysteria2/inbound.go:158-164:
userID, _ := auth.UserFromContextint // 数组下标
if userName := h.userNameList[userID]; userName != "" { // ← 越界点
metadata.User = userName
}

protocol/tuic/user.go 同模式。

复现路径

  1. inbound 启动时有 5 个用户,user[3]=Alice 建立长连接,ctx 里存 userID=3。
  2. 面板下发新用户列表(删掉前面某个用户),现在 user[3]=Bob。
  3. Alice 的已有连接继续传输数据,但 userNameList[3] 返回 Bob——流量计到 Bob 头上。
  4. 若新列表只剩 3 个用户,userNameList[3] 越界 → panic → 进程退出。

修复方向

参考 protocol/anytls:把 ctx 里存的标识换成稳定的字符串(用户名/UUID),而不是数组下标。

  • 方案 A(推荐):UpdateUsers 与 auth.UserFromContext 都改用 string,下游用 map 查找用户名 → 密码/配置。
  • 方案 B:连接建立时立即把用户名"钉"在连接对象上,后续不再回查数组。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions