From bcd84bdf6def1b8010d1ca6b77f6597bb8b714ee Mon Sep 17 00:00:00 2001 From: Shubham Raj Date: Wed, 5 Aug 2026 13:43:29 +0530 Subject: [PATCH] config: return an error when gzip content is incomplete DecodeGzipContent ignored the error from buf.ReadFrom, so a write_files entry with a truncated or corrupted gzip payload decoded to partial content and was reported as successful. The same helper backs the -validate path, so such a config also passed validation. Add the missing error check and cover the decode helpers, which had no tests. Signed-off-by: Shubham Raj --- config/decode.go | 5 ++- config/decode_test.go | 88 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 config/decode_test.go diff --git a/config/decode.go b/config/decode.go index f5847aa..c210a60 100644 --- a/config/decode.go +++ b/config/decode.go @@ -26,7 +26,10 @@ func DecodeGzipContent(content string) ([]byte, error) { defer gzr.Close() buf := new(bytes.Buffer) - buf.ReadFrom(gzr) + + if _, err := buf.ReadFrom(gzr); err != nil { + return nil, fmt.Errorf("Unable to decode gzip: %q", err) + } return buf.Bytes(), nil } diff --git a/config/decode_test.go b/config/decode_test.go new file mode 100644 index 0000000..6b96f29 --- /dev/null +++ b/config/decode_test.go @@ -0,0 +1,88 @@ +package config + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "testing" +) + +func gzipString(t *testing.T, content string) string { + t.Helper() + + var buf bytes.Buffer + w := gzip.NewWriter(&buf) + if _, err := w.Write([]byte(content)); err != nil { + t.Fatalf("compressing test content: %v", err) + } + if err := w.Close(); err != nil { + t.Fatalf("closing gzip writer: %v", err) + } + + return buf.String() +} + +func TestDecodeContent(t *testing.T) { + content := "#!/bin/bash\necho hello\n" + gzipped := gzipString(t, content) + + tests := []struct { + encoding string + content string + + decoded string + isValid bool + }{ + {encoding: "", content: content, decoded: content, isValid: true}, + {encoding: "base64", content: base64.StdEncoding.EncodeToString([]byte(content)), decoded: content, isValid: true}, + {encoding: "b64", content: base64.StdEncoding.EncodeToString([]byte(content)), decoded: content, isValid: true}, + {encoding: "gzip", content: gzipped, decoded: content, isValid: true}, + {encoding: "gz", content: gzipped, decoded: content, isValid: true}, + {encoding: "gz+base64", content: base64.StdEncoding.EncodeToString([]byte(gzipped)), decoded: content, isValid: true}, + {encoding: "gzip+b64", content: base64.StdEncoding.EncodeToString([]byte(gzipped)), decoded: content, isValid: true}, + {encoding: "base64", content: "not base64", isValid: false}, + {encoding: "gzip", content: "not gzip", isValid: false}, + {encoding: "unknown", content: content, isValid: false}, + } + + for _, tt := range tests { + decoded, err := DecodeContent(tt.content, tt.encoding) + if tt.isValid != (err == nil) { + t.Errorf("bad validity (%q): want %t, got %q", tt.encoding, tt.isValid, err) + continue + } + if tt.isValid && string(decoded) != tt.decoded { + t.Errorf("bad content (%q): want %q, got %q", tt.encoding, tt.decoded, string(decoded)) + } + } +} + +// A gzip stream that is cut short still has a valid header, so the failure only +// shows up while reading the body. Decoding it must not return partial content. +func TestDecodeGzipContentIncomplete(t *testing.T) { + content := bytes.Repeat([]byte("some config content\n"), 200) + gzipped := gzipString(t, string(content)) + + truncated := gzipped[:len(gzipped)/2] + + corrupted := []byte(gzipped) + corrupted[len(corrupted)-1] ^= 0xff + + tests := []struct { + name string + content string + }{ + {name: "truncated stream", content: truncated}, + {name: "corrupted checksum", content: string(corrupted)}, + } + + for _, tt := range tests { + decoded, err := DecodeGzipContent(tt.content) + if err == nil { + t.Errorf("%s: expected an error, got %d bytes of content", tt.name, len(decoded)) + } + if len(decoded) != 0 { + t.Errorf("%s: expected no content on error, got %d bytes", tt.name, len(decoded)) + } + } +}