SagerNet / SagerNet/sing-box

[Bug] TLS certificate: panic("unknown value") instead of an error on an unparseable certificate

Open Beginner friendly
#4,347 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
38.1k
Forks
4.6k
Avg merge
19d 15h
Merged PRs (30d)
1

Description

Operating system

macOS

System version

macOS 26.5.2 (darwin/arm64) — the bug is platform-independent

Installation type

Original sing-box Command Line

Version
sing-box version 1.13.12

Environment: go1.25.10 darwin/arm64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,badlinkname,tfogo_checklinkname0
Revision: 1086ab2563320e0da0c23b3a491d8dfa0939dff4
CGO: enabled
Description

A TLS certificate that is not a parseable X.509 certificate makes sing-box panic instead of returning an error. sing-box check is enough to trigger it — no network, no server.

The config below is invalid, and rejecting it is correct. The bug is how it is rejected: a Go panic with a goroutine dump, rather than the "failed to parse certificate" error the code is clearly trying to produce.

Root causecommon/tls/std_client.go (line 191 on testing, 168 in the 1.13.12 build above):

if !certPool.AppendCertsFromPEM(certificate) {
    return nil, E.New("failed to parse certificate:\n\n", certificate)
}

certificate is a []byte. E.New passes its arguments to sing/common/format.ToString, whose type switch handles string, bool, the integer and float types, uintptr, error and Stringer — but not []byte — and whose default branch is panic("unknown value") (common/format/fmt.go:60-61). So the error path for a bad certificate panics before the error can be returned.

Still present on the default testing branch as of today.

This is the same defect class you already fixed once: #4093 ([Bug] VLESS outbound: panic("unknown value") instead of error on invalid packet_encoding) was closed as completed via 7d847c05. Same panic("unknown value"), same cause — a value format.ToString cannot handle reaching E.New on a validation-failure path. #4185 looks like another instance.

Prior report: #3325 hit this exact panic on 1.12.3. It was closed as not planned after the thread turned into a discussion of how to write the certificate field (string vs array of lines). The formatting question was answered, but the panic itself was never addressed — and it still reproduces, on any malformed value, whichever syntax is used.

Suggested fix — either at the call site:

return nil, E.New("failed to parse certificate:\n\n", string(certificate))

or by giving format.ToString a []byte case. The call-site fix is the smaller change; the ToString one would also cover any other caller that reaches the same default branch.

Reproduction

Save as min.json — no remote server needed:

{
  "outbounds": [
    {
      "type": "hysteria2",
      "tag": "h2",
      "server": "example.com",
      "server_port": 8443,
      "password": "x",
      "tls": {
        "enabled": true,
        "server_name": "example.com",
        "certificate": "-----BEGIN CERTIFICATE-----\nAgECAgEC\n-----END CERTIFICATE-----"
      }
    }
  ]
}

Then:

sing-box check -c min.json

Expected: failed to parse certificate: ...
Actual: panic: unknown value plus a goroutine dump (below).

Any certificate whose body is well-formed base64 but not a parseable certificate does it. Three that all panic:

certificate body why it is not a certificate
AgECAgEC DER tag 02 (INTEGER), not 30 (SEQUENCE)
MIIBszCCAVmgAwIBAgIU truncated — header declares 435 content bytes, 15 follow
QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo= base64 of plain ASCII text, never DER

A certificate carrying explicit EC curve parameters (rather than a named curve) also panics — it is well-formed DER that Go's x509 refuses. That one is easy to hit by accident with openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1, which is why I mention it: the input does not have to be corrupt to reach this path.

The certificate_path form reaches the same code and panics the same way.

Logs
panic: unknown value

goroutine 1 [running]:
github.com/sagernet/sing/common/format.ToString({0x140002b0218?, 0x2, 0x40?})
	github.com/sagernet/sing@v0.8.10/common/format/fmt.go:61 +0x116c
github.com/sagernet/sing/common/exceptions.New(...)
	github.com/sagernet/sing@v0.8.10/common/exceptions/error.go:26
github.com/sagernet/sing-box/common/tls.NewSTDClient({_, _}, {_, _}, {_, _}, {0x1, 0x0, {0x140002c09e0, 0xb}, ...})
	github.com/sagernet/sing-box/common/tls/std_client.go:168 +0xcf8
github.com/sagernet/sing-box/common/tls.NewClientWithOptions({{0x103004bf8, 0x140002b7650}, {0x14f4024b0, 0x140002a68a0}, {0x140002c09a0, 0xb}, {0x1, 0x0, {0x140002c09e0, 0xb}, ...}, ...})
	github.com/sagernet/sing-box/common/tls/client.go:69 +0x19c
github.com/sagernet/sing-box/common/tls.NewClient(...)
	github.com/sagernet/sing-box/common/tls/client.go:36
github.com/sagernet/sing-box/protocol/hysteria2.NewOutbound({_, _}, {_, _}, {_, _}, {_, _}, {{{0x0, 0x0}, ...}, ...})
	github.com/sagernet/sing-box/protocol/hysteria2/outbound.go:47 +0x184
github.com/sagernet/sing-box/adapter/outbound.Register[...].func2(...)
	github.com/sagernet/sing-box/adapter/outbound/registry.go:23 +0xe4
github.com/sagernet/sing-box/adapter/outbound.(*Registry).CreateOutbound(...)
	github.com/sagernet/sing-box/adapter/outbound/registry.go:64 +0x204

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in common/tls/std_client.go at the certificate parsing path, then read common/format/fmt.go and the related error construction. Reproduce with the supplied min.json using sing-box check -c min.json; done means malformed certificate and certificate_path inputs return a parse error without panicking.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.