mirror of
https://github.com/anchore/syft.git
synced 2026-05-20 12:15:27 +02:00
fix(json): use value alias in Document.UnmarshalJSON to prevent infinite recursion with encoding/json/v2 (#4748)
The pattern 'type Alias *Document' does not strip methods under encoding/json/v2 (GOEXPERIMENT=jsonv2), causing UnmarshalJSON to call itself infinitely until the goroutine stack overflows (1GB limit). Change to 'type Alias Document' with (*Alias)(d) cast — the standard Go pattern that works correctly with both encoding/json v1 and v2. Adds a regression test that uses debug.SetMaxStack to shrink the goroutine stack limit to 8MB, making the overflow happen in milliseconds rather than minutes if the recursion is reintroduced. Ref: https://github.com/golang/go/issues/75361 Signed-off-by: Benjamin Grandfond <benjamin.grandfond@docker.com>
This commit is contained in:
parent
d0ee9098cf
commit
cc3b8eb48f
@ -17,8 +17,8 @@ type Document struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) UnmarshalJSON(data []byte) error {
|
func (d *Document) UnmarshalJSON(data []byte) error {
|
||||||
type Alias *Document
|
type Alias Document
|
||||||
aux := Alias(d)
|
aux := (*Alias)(d)
|
||||||
|
|
||||||
if err := json.Unmarshal(data, aux); err != nil {
|
if err := json.Unmarshal(data, aux); err != nil {
|
||||||
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
|
return fmt.Errorf("could not unmarshal syft JSON document: %w", err)
|
||||||
|
|||||||
@ -2,12 +2,48 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"runtime/debug"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TestDocumentUnmarshalJSON_NoInfiniteRecursion guards against the regression
|
||||||
|
// where encoding/json/v2 (GOEXPERIMENT=jsonv2) would call UnmarshalJSON
|
||||||
|
// recursively on the alias type, causing a goroutine stack overflow.
|
||||||
|
// See: https://github.com/golang/go/issues/75361
|
||||||
|
func TestDocumentUnmarshalJSON_NoInfiniteRecursion(t *testing.T) {
|
||||||
|
data := `{
|
||||||
|
"artifacts": [
|
||||||
|
{"id": "1", "name": "pkg-a", "version": "1.0", "type": "npm", "foundBy": "cataloger", "locations": [], "licenses": [], "language": "javascript", "cpes": [], "purl": "pkg:npm/pkg-a@1.0"},
|
||||||
|
{"id": "2", "name": "pkg-b", "version": "2.0", "type": "gem", "foundBy": "cataloger", "locations": [], "licenses": [], "language": "ruby", "cpes": [], "purl": "pkg:gem/pkg-b@2.0"}
|
||||||
|
],
|
||||||
|
"schema": {"version": "16.0.0", "url": "https://example.com"},
|
||||||
|
"descriptor": {"name": "syft", "version": "1.0.0"}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Shrink the max goroutine stack to 8MB so that infinite recursion
|
||||||
|
// (golang/go#75361 — encoding/json/v2 re-dispatching to UnmarshalJSON
|
||||||
|
// via type Alias *Document) overflows quickly rather than after minutes.
|
||||||
|
old := debug.SetMaxStack(8 * 1024 * 1024)
|
||||||
|
defer debug.SetMaxStack(old)
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
var doc Document
|
||||||
|
done <- json.Unmarshal([]byte(data), &doc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
require.NoError(t, err)
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
t.Fatal("json.Unmarshal did not complete — likely infinite recursion in UnmarshalJSON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
|
func TestDocumentUnmarshalJSON_SchemaDetection(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user