liandu2024:Open-Box v0.1.223 修复 FakeIP UDP 缓存竞态导致的崩溃
作者说明补丁统一 ReadPacket 的原子占用逻辑,并报告竞态检测与回归用例结果;v0.1.223 会连同 1.14.1-openbox-tcp8 内核下载,仍建议继续观察。
作者原文@liandu2024你这份栈直接把问题指出来了,已定位并修好,v0.1.223 发出去了,内核版本
1.14.1-openbox-tcp8。是什么
问题在依赖库
github.com/sagernet/singv0.9.4 的CachedPacketConn:同一个 UDP 缓存包有两条"取走"的路,却用了两套不同的同步——
ReadCachedPacket()用原子标志taken.CompareAndSwapReadPacket()只是裸检查if c.buffer != nil并发时
ReadPacket检查完、还没来得及DecRef,缓存就被另一边置成 nil 了,于是refs.Add(-1)打在空指针上——正是你栈里的sync/atomic.(*Int32).Add buf.(*Buffer).DecRef buffer.go:297 bufio.(*CachedPacketConn).ReadPacket cache.go:187
addr=0x30正好是Buffer.refs的偏移。你那次slice bounds out of range [:1232] with capacity 0是同一个竞态的另一面:读到了已经Release回收的内存。你的栈里有
route.(*fakeIPNATPacketConn).ReadPacket,说明你开着 FakeIP,走 FakeIP 的 UDP 流量最容易撞上这条路径(不开 FakeIP 也有机会,只是概率低)。反过来还有一份:
ReadPacket先取走时不会置taken,随后的ReadCachedPacket仍会赢下 CAS,把一个Buffer已经是 nil 的包交给调用方——那是另一个空指针入口。怎么修的
让
ReadPacket也走同一个taken原子标志,谁抢到谁处理。上游 main 分支我今天核对过,至今仍是裸检查,所以这个补丁是 Open-Box 自己打的(也是我们第一个改依赖库而不是 sing-box 本体的补丁)。验证:
- Go 竞态检测器:打补丁前稳定报
DATA RACE(读 cache.go:182 / 写 cache.go:200),补丁后干净;- 三条回归用例进了内核构建,两个架构都跑;
- 反向验证:拿未打补丁的库跑,用例当场失败并打印出那个
Buffer:<nil>的包。你需要做的
面板里点升级到 v0.1.223。这一版会连内核一起下载(约 16 MB),不像前几版只下面板;升级过程中内核重启一次。
升完请留意一两天。如果还崩,麻烦再贴一次
logread -e sing-box | tail -n 80—— 那就是另一条路径,我接着查。感谢你把完整栈贴出来,没有这个定位不到。