diff options
Diffstat (limited to 'hotline/flattened_file_object_fuzz_test.go')
| -rw-r--r-- | hotline/flattened_file_object_fuzz_test.go | 95 |
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") +} |