snapshot: sagernet/gvisor v0.0.0-20260727.0-sing-box-mod.1 + SPEC 048 guard

Обновление снапшота с 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-паникой, что в полевом крашдампе; с ним зелёный.
This commit is contained in:
Leadaxe 2026-08-05 14:53:31 +03:00
parent ffebe42860
commit 117243aa02
293 changed files with 16413 additions and 2842 deletions

View file

@ -15,6 +15,7 @@
package linux
import (
"structs"
"time"
"github.com/sagernet/gvisor/pkg/marshal/primitive"
@ -93,6 +94,7 @@ const (
// +marshal
// +stateify savable
type FUSEHeaderIn struct {
_ structs.HostLayout
// Len specifies the total length of the data, including this header.
Len uint32
@ -127,6 +129,7 @@ var SizeOfFUSEHeaderIn = uint32((*FUSEHeaderIn)(nil).SizeBytes())
// +marshal
// +stateify savable
type FUSEHeaderOut struct {
_ structs.HostLayout
// Len specifies the total length of the data, including this header.
Len uint32
@ -172,9 +175,10 @@ const (
// Constants relevant to FUSE operations.
const (
FUSE_NAME_MAX = 1024
FUSE_PAGE_SIZE = 4096
FUSE_DIRENT_ALIGN = 8
FUSE_NAME_MAX = 1024
FUSE_PAGE_SIZE = 4096
FUSE_DIRENT_ALIGN = 8
FUSE_FSYNC_FDATASYNC = 1 << 0
)
// FUSEInitIn is the request sent by the kernel to the daemon,
@ -182,6 +186,7 @@ const (
//
// +marshal
type FUSEInitIn struct {
_ structs.HostLayout
// Major version supported by kernel.
Major uint32
@ -201,6 +206,7 @@ type FUSEInitIn struct {
//
// +marshal
type FUSEInitOut struct {
_ structs.HostLayout
// Major version supported by daemon.
Major uint32
@ -249,6 +255,7 @@ type FUSEInitOut struct {
//
// +marshal
type FUSEStatfsOut struct {
_ structs.HostLayout
// Blocks is the maximum number of data blocks the filesystem may store, in
// units of BlockSize.
Blocks uint64
@ -290,6 +297,7 @@ const FUSE_GETATTR_FH = (1 << 0)
//
// +marshal
type FUSEGetAttrIn struct {
_ structs.HostLayout
// GetAttrFlags specifies whether getattr request is sent with a nodeid or
// with a file handle.
GetAttrFlags uint32
@ -305,6 +313,7 @@ type FUSEGetAttrIn struct {
//
// +marshal
type FUSEAttr struct {
_ structs.HostLayout
// Ino is the inode number of this file.
Ino uint64
@ -376,6 +385,7 @@ func (a FUSEAttr) CTimeNsec() int64 {
//
// +marshal
type FUSEAttrOut struct {
_ structs.HostLayout
// AttrValid and AttrValidNsec describe the attribute cache duration
AttrValid uint64
@ -394,6 +404,7 @@ type FUSEAttrOut struct {
//
// +marshal
type FUSEEntryOut struct {
_ structs.HostLayout
// NodeID is the ID for current inode.
NodeID uint64
@ -445,6 +456,7 @@ func (s *CString) SizeBytes() int {
//
// +marshal dynamic
type FUSELookupIn struct {
_ structs.HostLayout
// Name is a file name to be looked up.
Name CString
}
@ -475,6 +487,12 @@ const (
FOPEN_KEEP_CACHE = 1 << 1
// FOPEN_NONSEEKABLE indicates the file cannot be seeked.
FOPEN_NONSEEKABLE = 1 << 2
// FOPEN_CACHE_DIR indicated to allow caching this directory
FOPEN_CACHE_DIR = 1 << 3
// FOPEN_STREAM indicates the file is stream-like (no file position at all)
FOPEN_STREAM = 1 << 4
// FOPEN_NOFLUSH indicates the file does not need to be flushed on close.
FOPEN_NOFLUSH = 1 << 5
)
// FUSEOpenIn is the request sent by the kernel to the daemon,
@ -482,6 +500,7 @@ const (
//
// +marshal
type FUSEOpenIn struct {
_ structs.HostLayout
// Flags of this open request.
Flags uint32
@ -493,6 +512,7 @@ type FUSEOpenIn struct {
//
// +marshal
type FUSEOpenOut struct {
_ structs.HostLayout
// Fh is the file handler for opened files.
Fh uint64
@ -507,6 +527,7 @@ type FUSEOpenOut struct {
//
// +marshal
type FUSECreateOut struct {
_ structs.HostLayout
FUSEEntryOut
FUSEOpenOut
}
@ -521,6 +542,7 @@ const (
//
// +marshal
type FUSEReadIn struct {
_ structs.HostLayout
// Fh is the file handle in userspace.
Fh uint64
@ -553,6 +575,7 @@ type FUSEReadIn struct {
//
// +marshal
type FUSEWriteIn struct {
_ structs.HostLayout
// Fh is the file handle in userspace.
Fh uint64
@ -583,8 +606,9 @@ var SizeOfFUSEWriteIn = uint32((*FUSEWriteIn)(nil).SizeBytes())
//
// +marshal dynamic
type FUSEWritePayloadIn struct {
_ structs.HostLayout
Header FUSEWriteIn
Payload primitive.ByteSlice
Payload primitive.ByteSlice `hostlayout:"ignore"`
}
// SizeBytes implements marshal.Marshallable.SizeBytes.
@ -612,6 +636,7 @@ func (r *FUSEWritePayloadIn) UnmarshalBytes(src []byte) []byte {
//
// +marshal
type FUSEWriteOut struct {
_ structs.HostLayout
// Size is the number of bytes written.
Size uint32
@ -623,6 +648,7 @@ type FUSEWriteOut struct {
//
// +marshal
type FUSEReleaseIn struct {
_ structs.HostLayout
// Fh is the file handler for the file to be released.
Fh uint64
@ -641,6 +667,7 @@ type FUSEReleaseIn struct {
//
// +marshal
type FUSECreateMeta struct {
_ structs.HostLayout
// Flags of the creating file.
Flags uint32
@ -656,6 +683,7 @@ type FUSECreateMeta struct {
//
// +marshal dynamic
type FUSERenameIn struct {
_ structs.HostLayout
Newdir primitive.Uint64
Oldname CString
Newname CString
@ -683,6 +711,7 @@ func (r *FUSERenameIn) SizeBytes() int {
//
// +marshal dynamic
type FUSECreateIn struct {
_ structs.HostLayout
// CreateMeta contains mode, rdev and umash fields for FUSE_MKNODS.
CreateMeta FUSECreateMeta
@ -711,6 +740,7 @@ func (r *FUSECreateIn) SizeBytes() int {
//
// +marshal
type FUSEMknodMeta struct {
_ structs.HostLayout
// Mode of the inode to create.
Mode uint32
@ -728,6 +758,7 @@ type FUSEMknodMeta struct {
//
// +marshal dynamic
type FUSEMknodIn struct {
_ structs.HostLayout
// MknodMeta contains mode, rdev and umash fields for FUSE_MKNODS.
MknodMeta FUSEMknodMeta
// Name is the name of the node to create.
@ -755,6 +786,7 @@ func (r *FUSEMknodIn) SizeBytes() int {
//
// +marshal dynamic
type FUSESymlinkIn struct {
_ structs.HostLayout
// Name of symlink to create.
Name CString
@ -782,6 +814,7 @@ func (r *FUSESymlinkIn) SizeBytes() int {
//
// +marshal dynamic
type FUSELinkIn struct {
_ structs.HostLayout
// OldNodeID is the ID of the inode that is being linked to.
OldNodeID primitive.Uint64
// Name of the new hard link to create.
@ -807,7 +840,9 @@ func (r *FUSELinkIn) SizeBytes() int {
// FUSEEmptyIn is used by operations without request body.
//
// +marshal dynamic
type FUSEEmptyIn struct{}
type FUSEEmptyIn struct {
_ structs.HostLayout
}
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
func (r *FUSEEmptyIn) MarshalBytes(buf []byte) []byte {
@ -829,6 +864,7 @@ func (r *FUSEEmptyIn) SizeBytes() int {
//
// +marshal
type FUSEMkdirMeta struct {
_ structs.HostLayout
// Mode of the directory of create.
Mode uint32
// Umask is the user file creation mask.
@ -840,6 +876,7 @@ type FUSEMkdirMeta struct {
//
// +marshal dynamic
type FUSEMkdirIn struct {
_ structs.HostLayout
// MkdirMeta contains Mode and Umask of the directory to create.
MkdirMeta FUSEMkdirMeta
// Name of the directory to create.
@ -867,6 +904,7 @@ func (r *FUSEMkdirIn) SizeBytes() int {
//
// +marshal dynamic
type FUSERmDirIn struct {
_ structs.HostLayout
// Name is a directory name to be removed.
Name CString
}
@ -886,12 +924,94 @@ func (r *FUSERmDirIn) SizeBytes() int {
return r.Name.SizeBytes()
}
// FUSEGetXattrHdr contains the static fields of FUSEGetXattrIn.
//
// +marshal
type FUSEGetXattrHdr struct {
_ structs.HostLayout
Size uint32
_ uint32
}
// FUSEGetXattrIn contains the arguments for FUSE_GETXATTR.
//
// +marshal dynamic
type FUSEGetXattrIn struct {
_ structs.HostLayout
Hdr FUSEGetXattrHdr
Name CString
}
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
func (r *FUSEGetXattrIn) MarshalBytes(buf []byte) []byte {
buf = r.Hdr.MarshalBytes(buf)
return r.Name.MarshalBytes(buf)
}
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
func (r *FUSEGetXattrIn) UnmarshalBytes(buf []byte) []byte {
panic("Unimplemented, FUSEGetXattrIn is never unmarshalled")
}
// SizeBytes implements marshal.Marshallable.SizeBytes.
func (r *FUSEGetXattrIn) SizeBytes() int {
return r.Hdr.SizeBytes() + r.Name.SizeBytes()
}
// FUSEGetXattrOut is the reply sent by the daemon to the kernel
// for FUSE_GETXATTR and FUSE_LISTXATTR when the input size was 0.
//
// +marshal
type FUSEGetXattrOut struct {
_ structs.HostLayout
Size uint32
_ uint32
}
// FUSESetXattrHdr contains the static fields of FUSESetXattrIn.
//
// +marshal
type FUSESetXattrHdr struct {
_ structs.HostLayout
Size uint32
Flags uint32
}
// FUSESetXattrIn contains the arguments for FUSE_SETXATTR.
//
// +marshal dynamic
type FUSESetXattrIn struct {
_ structs.HostLayout
Hdr FUSESetXattrHdr
Name CString
Value []byte `hostlayout:"ignore"`
}
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
func (r *FUSESetXattrIn) MarshalBytes(buf []byte) []byte {
buf = r.Hdr.MarshalBytes(buf)
buf = r.Name.MarshalBytes(buf)
copy(buf, r.Value)
return buf[len(r.Value):]
}
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
func (r *FUSESetXattrIn) UnmarshalBytes(buf []byte) []byte {
panic("Unimplemented, FUSESetXattrIn is never unmarshalled")
}
// SizeBytes implements marshal.Marshallable.SizeBytes.
func (r *FUSESetXattrIn) SizeBytes() int {
return r.Hdr.SizeBytes() + r.Name.SizeBytes() + len(r.Value)
}
// FUSEDirents is a list of Dirents received from the FUSE daemon server.
// It is used for FUSE_READDIR.
//
// +marshal dynamic
type FUSEDirents struct {
Dirents []*FUSEDirent
_ structs.HostLayout
Dirents []*FUSEDirent `hostlayout:"ignore"`
}
// FUSEDirent is a Dirent received from the FUSE daemon server.
@ -899,10 +1019,11 @@ type FUSEDirents struct {
//
// +marshal dynamic
type FUSEDirent struct {
_ structs.HostLayout
// Meta contains all the static fields of FUSEDirent.
Meta FUSEDirentMeta
// Name is the filename of the dirent.
Name string
Name string `hostlayout:"ignore"`
}
// FUSEDirentMeta contains all the static fields of FUSEDirent.
@ -910,6 +1031,7 @@ type FUSEDirent struct {
//
// +marshal
type FUSEDirentMeta struct {
_ structs.HostLayout
// Inode of the dirent.
Ino uint64
// Offset of the dirent.
@ -937,23 +1059,17 @@ func (r *FUSEDirents) MarshalBytes(buf []byte) []byte {
// UnmarshalBytes deserializes FUSEDirents from the src buffer.
func (r *FUSEDirents) UnmarshalBytes(src []byte) []byte {
for {
if len(src) <= (*FUSEDirentMeta)(nil).SizeBytes() {
for len(src) >= (*FUSEDirentMeta)(nil).SizeBytes() {
var dirent FUSEDirent
rem := dirent.UnmarshalBytes(src)
if len(rem) == len(src) || len(dirent.Name) == 0 {
break
}
// Its unclear how many dirents there are in src. Each dirent is dynamically
// sized and so we can't make assumptions about how many dirents we can allocate.
if r.Dirents == nil {
r.Dirents = make([]*FUSEDirent, 0)
}
// We have to allocate a struct for each dirent - there must be a better way
// to do this. Linux allocates 1 page to store all the dirents and then
// simply reads them from the page.
var dirent FUSEDirent
src = dirent.UnmarshalBytes(src)
r.Dirents = append(r.Dirents, &dirent)
src = rem
}
return src
}
@ -985,12 +1101,18 @@ func (r *FUSEDirent) shiftNextDirent(buf []byte) []byte {
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
func (r *FUSEDirent) UnmarshalBytes(src []byte) []byte {
if len(src) < (*FUSEDirentMeta)(nil).SizeBytes() {
return src
}
srcP := r.Meta.UnmarshalBytes(src)
if r.Meta.NameLen > FUSE_NAME_MAX || r.Meta.NameLen > uint32(len(srcP)) {
// The name is too long and therefore invalid. We don't
// need to unmarshal the name since it'll be thrown away.
return r.shiftNextDirent(src)
// Calculate the 8-byte aligned size of this directory entry record.
recLen := (r.Meta.SizeBytes() + int(r.Meta.NameLen) + (FUSE_DIRENT_ALIGN - 1)) & ^(FUSE_DIRENT_ALIGN - 1)
// If the name is invalid or if the record straddles the end of the source
// buffer (making it incomplete), return the buffer unconsumed. This leaves
// the offset at the start of this entry so it can be re-fetched whole.
if r.Meta.NameLen == 0 || r.Meta.NameLen > FUSE_NAME_MAX || recLen > len(src) {
return src
}
buf := make([]byte, r.Meta.NameLen)
@ -1021,6 +1143,7 @@ const (
//
// +marshal
type FUSESetAttrIn struct {
_ structs.HostLayout
// Valid indicates which attributes are modified by this request.
Valid uint32
@ -1072,6 +1195,7 @@ type FUSESetAttrIn struct {
//
// +marshal dynamic
type FUSEUnlinkIn struct {
_ structs.HostLayout
// Name of the node to unlink.
Name CString
}
@ -1096,6 +1220,7 @@ func (r *FUSEUnlinkIn) SizeBytes() int {
//
// +marshal
type FUSEFsyncIn struct {
_ structs.HostLayout
Fh uint64
FsyncFlags uint32
@ -1109,6 +1234,7 @@ type FUSEFsyncIn struct {
//
// +marshal
type FUSEAccessIn struct {
_ structs.HostLayout
Mask uint32
// padding
_ uint32
@ -1119,6 +1245,7 @@ type FUSEAccessIn struct {
//
// +marshal
type FUSEFallocateIn struct {
_ structs.HostLayout
Fh uint64
Offset uint64
Length uint64
@ -1132,6 +1259,7 @@ type FUSEFallocateIn struct {
//
// +marshal
type FUSEFlushIn struct {
_ structs.HostLayout
Fh uint64
_ uint32 // unused
_ uint32 // padding