-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Description
Background
btcjson.StringOrArray declares its own MarshalJSON method with a value receiver that simply calls json.Marshal(h).
Because the value still satisfies json.Marshaler, json.Marshal re-invokes MarshalJSON, triggering infinite recursion and eventually a stack‐overflow panic at runtime.
This manifests anywhere a StringOrArray is marshalled (e.g. GetNetworkInfoResult.Warnings), crashing downstream apps that depend on btcd/btcjson.
Your environment
- btcd / btcjson version:
v0.24.3-0.20250506233109-1eb974aab6ef - Go version:
go1.24.2 - OS:
Darwin 23.4.0 arm64(Apple Silicon macOS 14.4) - Reproducible on Linux/amd64 as well.
Steps to reproduce
package main
import (
"encoding/json"
"github.com/btcsuite/btcd/btcjson"
)
func main() {
val := btcjson.StringOrArray{"boom"}
_, _ = json.Marshal(val) // panic: runtime: stack overflow
}Or run any program that marshals btcjson.GetNetworkInfoResult when the
warnings field is non-nil (see attached stack trace snippet).
Expected behaviour
StringOrArray should serialize to:
"foo"when length == 1["foo","bar"]when length > 1
without panicking.
Actual behaviour
json.Marshal enters an infinite loop inside StringOrArray.MarshalJSON, eventually crashing with:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
...
encoding/json.marshalerEncoder → btcjson.StringOrArray.MarshalJSON → json.Marshal → (repeat)
Proposed fix (PR incoming)
Change the receiver to a pointer (or remove the method entirely) and add unit tests:
func (h *StringOrArray) MarshalJSON() ([]byte, error) {
return json.Marshal([]string(*h))
}Tests confirming safe round-trips for single and multiple values are included.