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

165
sandboxexec/sandbox/oci.go Normal file
View file

@ -0,0 +1,165 @@
// Copyright 2026 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sandbox
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// BundleConfig holds configuration for creating an OCI bundle.
type BundleConfig struct {
ID string
RuntimeDir string
EnableNetworking bool
Mounts []Mount
Env []string
Annotations map[string]string
WorkingDir string
Hostname string
}
// NewBundle creates a temporary OCI bundle on the fly with the given configuration.
func NewBundle(cfg BundleConfig) (string, error) {
// Create a bundle directory for the sandbox.
bundleDir := filepath.Join(cfg.RuntimeDir, cfg.ID)
rootfsDir := filepath.Join(bundleDir, "rootfs")
if err := os.MkdirAll(rootfsDir, 0o755); err != nil {
return "", fmt.Errorf("failed to create bundle directories: %w", err)
}
// Define the OCI Specification programmatically.
namespaces := []specs.LinuxNamespace{
{Type: specs.PIDNamespace},
{Type: specs.MountNamespace},
{Type: specs.UTSNamespace},
{Type: specs.IPCNamespace},
}
if os.Geteuid() != 0 {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.UserNamespace})
}
if cfg.EnableNetworking {
namespaces = append(namespaces, specs.LinuxNamespace{Type: specs.NetworkNamespace})
}
spec := &specs.Spec{
Version: "1.0.0",
Annotations: cfg.Annotations,
Root: &specs.Root{
Path: "rootfs",
Readonly: false,
},
Process: &specs.Process{
Terminal: false,
User: specs.User{UID: 0, GID: 0},
// Keeps the sandbox alive in the background.
Args: []string{"sleep", "infinity"},
Cwd: cfg.WorkingDir,
},
Mounts: []specs.Mount{
// Mandatory Linux API Filesystems
{Destination: "/proc", Type: "proc", Source: "proc"},
{Destination: "/dev", Type: "tmpfs", Source: "tmpfs"},
},
// enable basic namespaces for gVisor.
Linux: &specs.Linux{
Namespaces: namespaces,
},
Hostname: cfg.Hostname,
}
baseEnv := []string{"PATH=/bin:/usr/bin:/usr/local/bin"}
spec.Process.Env = append(baseEnv, cfg.Env...)
if os.Geteuid() != 0 {
spec.Linux.UIDMappings = []specs.LinuxIDMapping{
{ContainerID: 0, HostID: uint32(os.Geteuid()), Size: 1},
}
spec.Linux.GIDMappings = []specs.LinuxIDMapping{
{ContainerID: 0, HostID: uint32(os.Getegid()), Size: 1},
}
}
// Map host binaries & libraries as readonly. The binaries will be
// executed in gVisor sandbox, not on the host.
for _, p := range []string{"/bin", "/usr", "/lib", "/lib64", "/etc/alternatives"} {
if _, err := os.Stat(p); err == nil {
opts := []string{"rbind", "ro", "nosuid", "nodev"}
if p == "/etc/alternatives" {
opts = []string{"rbind", "ro"}
}
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: p,
Type: "bind",
Source: p,
Options: opts,
})
}
}
// Add custom mounts. Custom mounts overriding default host mounts create duplicate OCI
// entries. The later entry overrides the earlier one, as expected by OCI specs.
for _, m := range cfg.Mounts {
switch m.Type {
case MountTypeBind:
opts := []string{"rbind"}
if m.ReadOnly {
opts = append(opts, "ro")
} else {
opts = append(opts, "rw")
}
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: filepath.Clean(m.Destination),
Source: filepath.Clean(m.Source),
Type: "bind",
Options: opts,
})
case MountTypeTmpfs:
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: filepath.Clean(m.Destination),
Source: "tmpfs",
Type: "tmpfs",
})
case MountTypeProc:
spec.Mounts = append(spec.Mounts, specs.Mount{
Destination: filepath.Clean(m.Destination),
Source: "proc",
Type: "proc",
})
}
}
// Write the spec to config.json
configPath := filepath.Join(bundleDir, "config.json")
configFile, err := os.Create(configPath)
if err != nil {
return "", fmt.Errorf("failed to create config.json: %w", err)
}
defer configFile.Close()
encoder := json.NewEncoder(configFile)
encoder.SetIndent("", " ")
if err := encoder.Encode(spec); err != nil {
return "", fmt.Errorf("failed to encode config.json: %w", err)
}
return bundleDir, nil
}