Skip to content

Commit f7c2b4e

Browse files
authored
fix: detect issues by fuzzing (#10)
Create two fuzz tests and fix some panics / loops shown by the fuzzer.
1 parent efc7153 commit f7c2b4e

2 files changed

Lines changed: 123 additions & 11 deletions

File tree

oleparse.go

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (self *OLEFile) ReadSector(sector uint32) []byte {
120120
start := 512 + self.SectorSize*int(sector)
121121

122122
to_read := self.SectorSize
123-
if start > len(self.data) {
123+
if start > len(self.data) || start < 0 {
124124
return nil
125125
}
126126

@@ -134,7 +134,7 @@ func (self *OLEFile) ReadMiniSector(sector uint32) []byte {
134134
start := self.MiniSectorSize * int(sector)
135135

136136
to_read := self.MiniSectorSize
137-
if start > len(self.ministream) {
137+
if start > len(self.ministream) || start < 0 {
138138
return nil
139139
}
140140

@@ -373,6 +373,11 @@ func DecompressStream(compressed_container []byte) []byte {
373373
// compressed_chunk_start := 0
374374
decompressed_chunk_start := 0
375375

376+
if len(compressed_container) == 0 {
377+
DebugPrintf("compressed stream is empty")
378+
return nil
379+
}
380+
376381
sig_byte := compressed_container[compressed_current]
377382
if sig_byte != 0x01 {
378383
DebugPrintf("invalid signature byte %02X", sig_byte)
@@ -382,6 +387,11 @@ func DecompressStream(compressed_container []byte) []byte {
382387
compressed_current += 1
383388

384389
for compressed_current < len(compressed_container) {
390+
if compressed_current+2 > len(compressed_container) {
391+
// At least 2 bytes for the header are needed
392+
DebugPrintf("Compressed stream ended prematurely")
393+
break
394+
}
385395
// 2.4.1.1.5
386396
// compressed_chunk_start = compressed_current
387397
compressed_chunk_header := binary.LittleEndian.Uint16(
@@ -409,6 +419,8 @@ func DecompressStream(compressed_container []byte) []byte {
409419
compressed_end := len(compressed_container)
410420
if compressed_end > compressed_current+int(chunk_size) {
411421
compressed_end = compressed_current + int(chunk_size)
422+
} else {
423+
DebugPrintf("Chunk exceeds compressed stream length")
412424
}
413425

414426
compressed_current += 2
@@ -425,17 +437,22 @@ func DecompressStream(compressed_container []byte) []byte {
425437
flag_byte := compressed_container[compressed_current]
426438
compressed_current += 1
427439
for bit_index := uint16(0); bit_index < 8; bit_index++ {
428-
if compressed_current >= compressed_end {
429-
break
430-
}
431-
432440
if (1<<bit_index)&flag_byte == 0 { // LiteralToken
441+
if compressed_current >= compressed_end {
442+
DebugPrintf("Compressed stream ended prematurely")
443+
break
444+
}
433445
decompressed_container = append(decompressed_container,
434446
compressed_container[compressed_current])
435447
compressed_current += 1
436448
continue
437449
}
438450

451+
if compressed_current > compressed_end-2 {
452+
DebugPrintf("Compressed stream ended prematurely")
453+
break
454+
}
455+
439456
// copy tokens
440457
copy_token := binary.LittleEndian.Uint16(
441458
compressed_container[compressed_current:])
@@ -589,7 +606,7 @@ func ExtractMacros(ofdoc *OLEFile) ([]*VBAModule, error) {
589606
compatversion_size := getUint32(dir_stream, &i)
590607
check_value("PROJECTCOMPATVERSION_Size", 0x4, compatversion_size)
591608
i += 4 // Skip ProjectCompatVersion
592-
} else {
609+
} else if i >= 2 {
593610
i -= 2 // No CompatVersionRecord present - undo read of the ID
594611
}
595612

@@ -657,7 +674,7 @@ func ExtractMacros(ofdoc *OLEFile) ([]*VBAModule, error) {
657674
projecthelpfilepath_id := getUint16(dir_stream, &i)
658675
check_value("PROJECTHELPFILEPATH_Id", 0x0006, uint32(projecthelpfilepath_id))
659676
projecthelpfilepath_sizeof_helpfile1 := int(getUint32(dir_stream, &i))
660-
if projecthelpfilepath_sizeof_helpfile1 > 260 {
677+
if projecthelpfilepath_sizeof_helpfile1 > 260 || projecthelpfilepath_sizeof_helpfile1+i > len(dir_stream) {
661678
return nil, errors.New(fmt.Sprintf(
662679
"PROJECTHELPFILEPATH_SizeOfHelpFile1 value not in range: %v", projecthelpfilepath_sizeof_helpfile1))
663680
}
@@ -668,6 +685,9 @@ func ExtractMacros(ofdoc *OLEFile) ([]*VBAModule, error) {
668685
projecthelpfilepath_sizeof_helpfile2 := int(getUint32(dir_stream, &i))
669686
if projecthelpfilepath_sizeof_helpfile2 != projecthelpfilepath_sizeof_helpfile1 {
670687
return nil, errors.New("PROJECTHELPFILEPATH_SizeOfHelpFile1 does not equal PROJECTHELPFILEPATH_SizeOfHelpFile2")
688+
} else if projecthelpfilepath_sizeof_helpfile2+i > len(dir_stream) {
689+
return nil, errors.New(fmt.Sprintf(
690+
"PROJECTHELPFILEPATH_SizeOfHelpFile2 value not in range: %v", projecthelpfilepath_sizeof_helpfile2))
671691
}
672692
projecthelpfilepath_helpfile2 := dir_stream[i : i+projecthelpfilepath_sizeof_helpfile2]
673693
i += projecthelpfilepath_sizeof_helpfile2
@@ -723,7 +743,7 @@ func ExtractMacros(ofdoc *OLEFile) ([]*VBAModule, error) {
723743
}
724744
// projectconstants_constants_unicode := dir_stream[i : i+projectconstants_sizeof_constants_unicode]
725745
i += projectconstants_sizeof_constants_unicode
726-
} else {
746+
} else if i >= 2 {
727747
i -= 2
728748
}
729749

@@ -869,6 +889,10 @@ loop:
869889
// uni_out = lambda unicode_text: unicode_text.encode("utf-8", "replace")
870890
DebugPrintf("parsing %v modules", projectmodules_count)
871891
for projectmodule_index := 0; projectmodule_index < int(projectmodules_count); projectmodule_index++ {
892+
if i >= len(dir_stream)-2 { // At the very least, there must by a 2-byte ID
893+
return nil, errors.New("dir_stream index out of range")
894+
}
895+
872896
modulestreamname_streamname := ""
873897
modulestreamname_streamname_unicode := []byte{}
874898
moduleoffset_textoffset := uint32(0)
@@ -877,6 +901,9 @@ loop:
877901

878902
check_value("MODULENAME_Id", 0x0019, uint32(modulename_id))
879903
modulename_sizeof_modulename := int(getUint32(dir_stream, &i))
904+
if len(dir_stream) < i+modulename_sizeof_modulename {
905+
return nil, errors.New("MODULENAME_SizeOfModuleName value not in range")
906+
}
880907
modulename_modulename := string(dir_stream[i : i+modulename_sizeof_modulename])
881908
i += modulename_sizeof_modulename
882909

@@ -886,8 +913,10 @@ loop:
886913
// account for optional sections
887914
section_id := getUint16(dir_stream, &i)
888915
if section_id == 0x0047 {
889-
modulename_unicode_sizeof_modulename_unicode := int(binary.LittleEndian.Uint32(dir_stream[i:]))
890-
i += 4
916+
modulename_unicode_sizeof_modulename_unicode := int(getUint32(dir_stream, &i))
917+
if len(dir_stream) < i+modulename_unicode_sizeof_modulename_unicode {
918+
return nil, errors.New("MODULENAMEUNICODE_SizeOfModuleNameUnicode value not in range")
919+
}
891920
modulename_unicode_modulename_unicode = dir_stream[i : i+
892921
modulename_unicode_sizeof_modulename_unicode]
893922
i += modulename_unicode_sizeof_modulename_unicode
@@ -897,12 +926,18 @@ loop:
897926

898927
if section_id == 0x001A {
899928
modulestreamname_sizeof_streamname := int(getUint32(dir_stream, &i))
929+
if len(dir_stream) < i+modulestreamname_sizeof_streamname {
930+
return nil, errors.New("MODULESTREAMNAME_SizeOfStreamName value not in range")
931+
}
900932
modulestreamname_streamname = string(dir_stream[i : i+modulestreamname_sizeof_streamname])
901933
i = i + modulestreamname_sizeof_streamname
902934

903935
modulestreamname_reserved := getUint16(dir_stream, &i)
904936
check_value("MODULESTREAMNAME_Reserved", 0x0032, uint32(modulestreamname_reserved))
905937
modulestreamname_sizeof_streamname_unicode := int(getUint32(dir_stream, &i))
938+
if len(dir_stream) < i+modulestreamname_sizeof_streamname_unicode {
939+
return nil, errors.New("MODULESTREAMNAME_SizeOfStreamNameUnicode value not in range")
940+
}
906941
modulestreamname_streamname_unicode = dir_stream[i : i+
907942
modulestreamname_sizeof_streamname_unicode]
908943
i += modulestreamname_sizeof_streamname_unicode
@@ -1004,6 +1039,11 @@ loop:
10041039

10051040
DebugPrintf("length of code_data = %v", len(code_data))
10061041
DebugPrintf("offset of code_data = %v", moduleoffset_textoffset)
1042+
if int(moduleoffset_textoffset) > len(code_data) {
1043+
DebugPrintf("invalid offset for module %v: %v",
1044+
modulestreamname_streamname, moduleoffset_textoffset)
1045+
continue
1046+
}
10071047
code_data = code_data[moduleoffset_textoffset:]
10081048
if len(code_data) > 0 {
10091049
result = append(result, &VBAModule{

oleparse_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package oleparse
22

33
import (
4+
"archive/zip"
45
"encoding/json"
6+
"io"
7+
"strings"
58
"testing"
69

710
"github.com/sebdah/goldie"
@@ -16,3 +19,72 @@ func TestMacros(t *testing.T) {
1619
serialized, _ := json.MarshalIndent(macros, " ", " ")
1720
goldie.Assert(t, "vba_macros", serialized)
1821
}
22+
23+
func FuzzExtractMacros(f *testing.F) {
24+
r, err := zip.OpenReader("test_data/xlswithmacro.xlsm")
25+
if err != nil {
26+
f.Fatal(err)
27+
}
28+
defer r.Close()
29+
30+
for _, file := range r.File {
31+
if BINFILE_NAME.MatchString(file.Name) {
32+
rc, err := file.Open()
33+
if err != nil {
34+
f.Fatal(err)
35+
}
36+
data, err := io.ReadAll(rc)
37+
if err != nil {
38+
f.Fatal(err)
39+
}
40+
f.Add(data)
41+
}
42+
}
43+
44+
f.Fuzz(func(t *testing.T, data []byte) {
45+
modules, err := ParseBuffer(data)
46+
if err != nil {
47+
t.Skip()
48+
}
49+
var macros strings.Builder
50+
for _, module := range modules {
51+
macros.WriteString(module.Code)
52+
}
53+
t.Log(macros.String())
54+
})
55+
}
56+
57+
func FuzzDecompressStream(f *testing.F) {
58+
r, err := zip.OpenReader("test_data/xlswithmacro.xlsm")
59+
if err != nil {
60+
f.Fatal(err)
61+
}
62+
defer r.Close()
63+
64+
for _, file := range r.File {
65+
if BINFILE_NAME.MatchString(file.Name) {
66+
rc, err := file.Open()
67+
if err != nil {
68+
f.Fatal(err)
69+
}
70+
data, err := io.ReadAll(rc)
71+
if err != nil {
72+
f.Fatal(err)
73+
}
74+
75+
oleFile, err := NewOLEFile(data)
76+
if err != nil {
77+
f.Fatal(err)
78+
}
79+
80+
for _, dir := range oleFile.Directory {
81+
f.Add(dir.data)
82+
}
83+
}
84+
}
85+
86+
f.Fuzz(func(t *testing.T, data []byte) {
87+
decompressed := DecompressStream(data)
88+
t.Log(decompressed)
89+
})
90+
}

0 commit comments

Comments
 (0)