Skip to content

Add an MaxAliasesForCollections option #803

@bep

Description

@bep

This has been discussed before in #461, and has been a blocker for us using this library in Hugo. Given the maintenance situation for "the other Go YAML library", I have implemented a workaround in this PR, but I'm still convinced that this is something that needs to be fixed closer to the source.

This program illustrates the problem (aka the Billion laughs attack):

package main

import (
	"log"

	"github.com/goccy/go-yaml"
)

func main() {
	data := []byte(`
a: &a [_, _, _, _, _, _, _, _, _, _, _, _, _, _, _]
b: &b [*a, *a, *a, *a, *a, *a, *a, *a, *a, *a]
c: &c [*b, *b, *b, *b, *b, *b, *b, *b, *b, *b]
d: &d [*c, *c, *c, *c, *c, *c, *c, *c, *c, *c]
e: &e [*d, *d, *d, *d, *d, *d, *d, *d, *d, *d]
f: &f [*e, *e, *e, *e, *e, *e, *e, *e, *e, *e]
g: &g [*f, *f, *f, *f, *f, *f, *f, *f, *f, *f]
h: &h [*g, *g, *g, *g, *g, *g, *g, *g, *g, *g]
i: &i [*h, *h, *h, *h, *h, *h, *h, *h, *h, *h]
`)

	target := make(map[string]any)

	// This works fine, but produces a very large object graph.
	err := yaml.Unmarshal(data, &target)
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	// data, err = yaml.Marshal(&target) // This fails/DoSs the system.
	// This experimental option makes it work.
	data, err = yaml.MarshalWithOptions(&target, yaml.WithSmartAnchor())
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	// But rendering it to another format (like JSON) fails/DoSs the system.
	/*_, err = json.MarshalIndent(&target, "", "  ")
	if err != nil {
		log.Fatalf("error: %v", err)
	}*/
}
  • Receiving untrusted user YAML and render it into another format (e.g. HTML or JSON) is a very common use case. And this is currently very hard to do safely.
  • You can argue that with the experimental yaml.WithSmartAnchor option enabled, go-yaml never DoSs the system, but malicious user input doesn't magically get less malicious if you're able to swallow it, and I think it's a very good security practice to fix any security issue as close to the source as possible.

In SnakeYAML, a popular Java YAML library, they added a maxAliasesForCollections at some point to address this problem (default value being 50, which sounds very low, but I may have misunderstood exactly what that setting do), so I propose to add a similar option here.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions