Обновление снапшота с v0.0.0-20250811.0 на пин, которого требует
sing-box после мержа 235 коммитов (upstream d620bbbf2 "Update gvisor to
20260727.0"). Прежний снапшот был взят 2026-08-04 ровно с той версии,
на которой тогда стоял апстрим; разрыв возник 2026-08-05 вместе с его
бампом.
За год апстрим-gvisor изменил ~14 000 строк в 292 файлах. Значимое для
нас — сетевой стек: tcp/connect.go (PMTU-discovery + исправление
начального RTT/RTO: раньше задержка ACK внутри стека завышала стартовый
таймаут на несколько RTT), tcp/snd.go, tcp/rcv.go, stack/conntrack.go,
stack/packet_buffer.go. Всего 30 файлов в TCP и 37 в stack.
Баг SPEC 048 апстрим НЕ исправил — проверено по коду новой версии:
handleConnecting по-прежнему проверяет состояние endpoint'а, но не ep.h,
а performHandshake так же зануляет h и отпускает мьютекс до Close().
Поэтому guard перенесён (12 строк) вместе со своим тестом (45 строк).
Red/green проверен на новой базе: без guard'а тест падает с той же
nil-паникой, что в полевом крашдампе; с ним зелёный.
listenContext.performHandshake, in its failure branch, zeroes ep.h and
releases ep.mu before calling ep.Close(); the endpoint state only changes
later, inside closeLocked. In that window the endpoint is still
SynSent/SynRecv, so connecting() is true.
A segment arriving there wakes the dispatcher, which routes to
handleConnecting. Its gate checks the state but not h, so
ep.h.processSegments() runs on a nil handshake and panics with a nil
receiver at connect.go:534 — killing the whole process, not just the
connection. The two conditions used to coincide; they stopped coinciding
once zeroing h moved ahead of Close().
Fix: bail out early when ep.h is nil, releasing the mutex the same way the
existing state gate does. One guard covers all five ep.h dereferences in
handleConnecting (processSegments, listenEP in the error branch, and both
inside deliverAccepted, which is only reached from here).
Trigger in the field: TCP that never reaches established (silent server,
RST, timeout) while SYN retransmits keep arriving. Reproduced from a
device crash bundle (sing-box 1.14.0-lx.19-rc.3, android/arm64) where both
sides of the race hold the same endpoint address; the accompanying test
recreates that window and panics without this guard.
SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом
без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в
каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для
неё не нужна.
Module path github.com/sagernet/gvisor сохранён намеренно: на него
опирается replace-директива суперпроекта.
Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show
и переносилась на новый пин копированием.
SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH