aboutsummaryrefslogtreecommitdiff
path: root/hotline/flattened_file_object_fuzz_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'hotline/flattened_file_object_fuzz_test.go')
-rw-r--r--hotline/flattened_file_object_fuzz_test.go95
1 files changed, 95 insertions, 0 deletions
diff --git a/hotline/flattened_file_object_fuzz_test.go b/hotline/flattened_file_object_fuzz_test.go
new file mode 100644
index 0000000..574fed3
--- /dev/null
+++ b/hotline/flattened_file_object_fuzz_test.go
@@ -0,0 +1,95 @@
+package hotline
+
+import (
+ "bytes"
+ "io"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+// sampleFFOBytes encodes a small flattened file object (FILP header, INFO fork header,
+// information fork, DATA fork header) — the layout that ReadFrom parses off the wire.
+func sampleFFOBytes(t interface{ Fatal(...any) }) []byte {
+ ffo := flattenedFileObject{
+ FlatFileHeader: FlatFileHeader{
+ Format: [4]byte{'F', 'I', 'L', 'P'},
+ Version: [2]byte{0, 1},
+ ForkCount: [2]byte{0, 2},
+ },
+ FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt", [8]byte{}, "TEXT", "TTXT"),
+ FlatFileDataForkHeader: FlatFileForkHeader{
+ ForkType: [4]byte{'D', 'A', 'T', 'A'},
+ DataSize: [4]byte{0, 0, 0, 5},
+ },
+ }
+ b, err := io.ReadAll(&ffo)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return b
+}
+
+// FuzzFlatFileInformationForkUnmarshal feeds raw untrusted bytes to the information fork
+// decoder, which parses the metadata section of client file uploads.
+func FuzzFlatFileInformationForkUnmarshal(f *testing.F) {
+ fork := NewFlatFileInformationFork("testfile.txt", [8]byte{}, "TEXT", "TTXT")
+ forkBytes, err := io.ReadAll(&fork)
+ if err != nil {
+ f.Fatal(err)
+ }
+ f.Add(forkBytes)
+ f.Add(forkBytes[:flatFileInfoForkMinLen]) // fixed-size section only
+ f.Add([]byte{}) // empty
+
+ f.Fuzz(func(t *testing.T, data []byte) {
+ var ffif FlatFileInformationFork
+ if err := ffif.UnmarshalBinary(data); err != nil {
+ return
+ }
+ // A successfully decoded fork must re-encode without panicking.
+ _, err := io.ReadAll(&ffif)
+ require.NoError(t, err)
+ })
+}
+
+// FuzzFlattenedFileObjectReadFrom feeds raw untrusted bytes to the flattened file object
+// parser — the entry point for decoding client file uploads on the transfer port.
+func FuzzFlattenedFileObjectReadFrom(f *testing.F) {
+ sample := sampleFFOBytes(f)
+ f.Add(sample)
+ f.Add(sample[:24]) // FILP header only
+ // Information fork header declaring an absurd size (previously triggered an unbounded allocation).
+ huge := bytes.Clone(sample)
+ copy(huge[36:40], []byte{0xff, 0xff, 0xff, 0xff})
+ f.Add(huge)
+
+ f.Fuzz(func(t *testing.T, data []byte) {
+ var ffo flattenedFileObject
+ _, _ = ffo.ReadFrom(bytes.NewReader(data)) // must not panic or over-allocate
+ })
+}
+
+// TestFlattenedFileObject_ReadFrom_RoundTrip pins that ReadFrom can parse what Read encodes.
+func TestFlattenedFileObject_ReadFrom_RoundTrip(t *testing.T) {
+ sample := sampleFFOBytes(t)
+
+ var ffo flattenedFileObject
+ _, err := ffo.ReadFrom(bytes.NewReader(sample))
+ require.NoError(t, err)
+
+ require.Equal(t, [4]byte{'F', 'I', 'L', 'P'}, ffo.FlatFileHeader.Format)
+ require.Equal(t, []byte("testfile.txt"), ffo.FlatFileInformationFork.Name)
+ require.Equal(t, ForkType{'D', 'A', 'T', 'A'}, ffo.FlatFileDataForkHeader.ForkType)
+ require.Equal(t, int64(5), ffo.dataSize())
+}
+
+// TestFlattenedFileObject_ReadFrom_RejectsOversizedInfoFork pins the untrusted-size guard.
+func TestFlattenedFileObject_ReadFrom_RejectsOversizedInfoFork(t *testing.T) {
+ sample := sampleFFOBytes(t)
+ copy(sample[36:40], []byte{0xff, 0xff, 0xff, 0xff})
+
+ var ffo flattenedFileObject
+ _, err := ffo.ReadFrom(bytes.NewReader(sample))
+ require.ErrorContains(t, err, "exceeds maximum")
+}