fix: fixes a possible panic in NewYAMLToJSONDecoder

This PR fixes a possible panic caused by decoding a JSON document
followed by a YAML document that is shorter than the first json
document.

This can cause a panic because the stream already consumed the JSON
data. When we fallback to YAML reader, the YAML starts with a zero
offset while the stream consumed data is non-zero. This could lead into
consuming negative bytes because `d.yaml.InputOffset() -
d.stream.Consumed()` is negative which will cause a panic.

Signed-off-by: Tiago Silva <tiago.silva@goteleport.com>
This commit is contained in:
Tiago Silva 2025-05-09 18:27:20 +01:00
parent d3c7573e54
commit a257be8299
No known key found for this signature in database
GPG Key ID: 3A8808B722014A00
2 changed files with 24 additions and 6 deletions

View File

@ -247,10 +247,12 @@ func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err
// finding a non-YAML-delimited series of objects), it will not switch to YAML. // finding a non-YAML-delimited series of objects), it will not switch to YAML.
// Once it switches to YAML it will not switch back to JSON. // Once it switches to YAML it will not switch back to JSON.
type YAMLOrJSONDecoder struct { type YAMLOrJSONDecoder struct {
json *json.Decoder json *json.Decoder
yaml *YAMLToJSONDecoder jsonConsumed int64 // of the stream total, how much was JSON?
stream *StreamReader yaml *YAMLToJSONDecoder
count int // how many objects have been decoded yamlConsumed int64 // of the stream total, how much was YAML?
stream *StreamReader
count int // how many objects have been decoded
} }
type JSONSyntaxError struct { type JSONSyntaxError struct {
@ -299,8 +301,10 @@ func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
if d.json != nil { if d.json != nil {
err := d.json.Decode(into) err := d.json.Decode(into)
if err == nil { if err == nil {
d.stream.Consume(int(d.json.InputOffset()) - d.stream.Consumed())
d.count++ d.count++
consumed := d.json.InputOffset() - d.jsonConsumed
d.stream.Consume(int(consumed))
d.jsonConsumed += consumed
return nil return nil
} }
if err == io.EOF { //nolint:errorlint if err == io.EOF { //nolint:errorlint
@ -334,7 +338,9 @@ func (d *YAMLOrJSONDecoder) Decode(into interface{}) error {
if d.yaml != nil { if d.yaml != nil {
err := d.yaml.Decode(into) err := d.yaml.Decode(into)
if err == nil { if err == nil {
d.stream.Consume(d.yaml.InputOffset() - d.stream.Consumed()) consumed := int64(d.yaml.InputOffset()) - d.yamlConsumed
d.stream.Consume(int(consumed))
d.yamlConsumed += consumed
d.count++ d.count++
return nil return nil
} }
@ -375,6 +381,7 @@ func (d *YAMLOrJSONDecoder) consumeWhitespace() error {
if err == io.EOF { //nolint:errorlint if err == io.EOF { //nolint:errorlint
break break
} }
consumed += sz
} }
return io.EOF return io.EOF
} }

View File

@ -343,6 +343,17 @@ func TestYAMLOrJSONDecoder(t *testing.T) {
{"foo": "bar"}, {"foo": "bar"},
{"baz": "biz"}, {"baz": "biz"},
}}, }},
// First document is JSON, second is YAML but with smaller size.
{"{\"foo\": \"bar\"}\n---\na: b", 100, false, false, []generic{
{"foo": "bar"},
{"a": "b"},
}},
// First document is JSON, second is YAML,but with smaller size and
// trailing whitespace.
{"{\"foo\": \"bar\"} \n---\na: b", 100, false, false, []generic{
{"foo": "bar"},
{"a": "b"},
}},
// First document is JSON, second is YAML, longer than the buffer // First document is JSON, second is YAML, longer than the buffer
{"{\"foo\": \"bar\"}\n---\n{baz: biz0123456780123456780123456780123456780123456789}", 20, false, false, []generic{ {"{\"foo\": \"bar\"}\n---\n{baz: biz0123456780123456780123456780123456780123456789}", 20, false, false, []generic{
{"foo": "bar"}, {"foo": "bar"},