[Bug] TLS certificate: panic("unknown value") instead of an error on an unparseable certificate
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 cause — common/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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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