Skip to content

[bug]: StringOrArray.MarshalJSON triggers infinite recursion → stack-overflow panic #2369

@cvieira-tng

Description

@cvieira-tng

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions