Skip to content

fix(group): prevent nil pointer panic in Selector on reconnect#3918

Open
kontsevoye wants to merge 1 commit intoSagerNet:stablefrom
kontsevoye:fix-selector-nil-panic
Open

fix(group): prevent nil pointer panic in Selector on reconnect#3918
kontsevoye wants to merge 1 commit intoSagerNet:stablefrom
kontsevoye:fix-selector-nil-panic

Conversation

@kontsevoye
Copy link
Copy Markdown

Summary

Adds nil checks for selected.Load() in Selector's connection methods to prevent nil pointer panic on reconnect.

When a Tailscale endpoint uses a Selector as its detour, restarting sing-box causes a panic at selector.go:144 because the Tailscale endpoint dials through the Selector before Start() populates the selected field.

Fixes #3917.

Changes

Added nil guards in 5 methods that call s.selected.Load() without checking:

  • DialContext — return error
  • ListenPacket — return error
  • NewConnectionExCloseOnHandshakeFailure
  • NewPacketConnectionExCloseOnHandshakeFailure
  • NewDirectRouteConnection — return error

Note: Network() and Now() already had nil checks — this makes the pattern consistent across all methods.

Reproduction

# First run — works fine
sing-box run -c config-with-selector-and-tailscale.json
# Ctrl+C to stop

# Second run — panic
sing-box run -c config-with-selector-and-tailscale.json
# panic: runtime error: invalid memory address or nil pointer dereference
# goroutine N [running]:
# github.com/sagernet/sing-box/protocol/group.(*Selector).DialContext(...)
#     selector.go:144 +0x64

Minimal config to reproduce (replace auth_key and server with valid values):

{
    "dns": {
        "servers": [
            {
                "type": "https",
                "tag": "proxy-dns",
                "detour": "proxy",
                "server": "1.1.1.1"
            },
            {
                "type": "tailscale",
                "tag": "ts-dns",
                "endpoint": "tailscale"
            }
        ],
        "rules": [
            {
                "domain_suffix": [
                    "ts.net"
                ],
                "server": "ts-dns"
            }
        ],
        "strategy": "ipv4_only",
        "final": "proxy-dns"
    },
    "inbounds": [
        {
            "type": "tun",
            "tag": "tun-in",
            "address": [
                "172.19.0.1/30"
            ],
            "auto_route": true,
            "route_address": [
                "100.64.0.0/10"
            ],
            "route_exclude_address": [
                "1.2.3.4/32"
            ]
        }
    ],
    "endpoints": [
        {
            "type": "tailscale",
            "tag": "tailscale",
            "detour": "proxy",
            "state_directory": "tailscale",
            "auth_key": "tskey-auth-VALID_KEY",
            "hostname": "test",
            "accept_routes": true,
            "udp_timeout": "5m0s"
        }
    ],
    "outbounds": [
        {
            "type": "selector",
            "tag": "proxy",
            "outbounds": [
                "vless-out"
            ],
            "default": "vless-out"
        },
        {
            "type": "vless",
            "tag": "vless-out",
            "server": "1.2.3.4",
            "server_port": 8443,
            "uuid": "valid-uuid",
            "flow": "xtls-rprx-vision",
            "tls": {
                "enabled": true,
                "server_name": "example.com",
                "utls": {
                    "enabled": true,
                    "fingerprint": "chrome"
                },
                "reality": {
                    "enabled": true,
                    "public_key": "valid",
                    "short_id": "valid"
                }
            }
        },
        {
            "type": "direct",
            "tag": "direct"
        }
    ],
    "route": {
        "default_domain_resolver": "proxy-dns",
        "rules": [
            {
                "action": "sniff"
            },
            {
                "protocol": "dns",
                "action": "hijack-dns"
            },
            {
                "ip_cidr": [
                    "1.2.3.4/32"
                ],
                "outbound": "direct"
            },
            {
                "ip_cidr": [
                    "100.64.0.0/10"
                ],
                "outbound": "tailscale"
            }
        ],
        "final": "proxy"
    }
}

Testing

  • Built and tested on macOS (darwin/arm64) with Tailscale endpoint + Selector config
  • Original binary: panics on second sing-box run after Ctrl+C
  • Patched binary: reconnects successfully

@nekohasekai nekohasekai force-pushed the stable branch 5 times, most recently from 86c0e9f to d091826 Compare March 26, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant