Skip to content

Commit 2e3e765

Browse files
authored
fix: handle off-by-one error in DIFAT read and invalid chains (#11)
1 parent f7c2b4e commit 2e3e765

4 files changed

Lines changed: 60 additions & 18 deletions

File tree

limits.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ const (
44
// Limit the size of the document
55
MAX_SECTORS = 1024 * 1024
66

7-
MAX_SECTOR_SHIFT = 10
7+
sectorShiftV3 = 0x9
8+
sectorShiftV4 = 0xC
89
)

oleparse.go

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ type OLEHeader struct {
3737
Clid [16]byte
3838

3939
MinorVersion uint16
40-
DllVersion uint16
40+
MajorVersion uint16
4141
ByteOrder uint16
4242
SectorShift uint16
4343
MiniSectorShift uint16
4444
Reserved uint16
4545

4646
Reserved1 uint32
47-
Reserved2 uint32
47+
CsectDir uint32 // Count of directory sectors. Only available in version 4.
4848
CsectFat uint32
4949
SectDirStart uint32
5050
Signature uint32
@@ -89,6 +89,9 @@ func NewDirectory(data []byte, index uint32) (*Directory, error) {
8989
if err != nil {
9090
return nil, err
9191
}
92+
if self.Header.Mse == 0 { // Unallocated
93+
return nil, nil
94+
}
9295

9396
self.Name = strings.TrimRight(
9497
string(utf16.Decode(self.Header.AB[:])), "\x00")
@@ -117,7 +120,7 @@ type VBAModule struct {
117120
}
118121

119122
func (self *OLEFile) ReadSector(sector uint32) []byte {
120-
start := 512 + self.SectorSize*int(sector)
123+
start := self.SectorSize * int(sector+1)
121124

122125
to_read := self.SectorSize
123126
if start > len(self.data) || start < 0 {
@@ -145,18 +148,18 @@ func (self *OLEFile) ReadMiniSector(sector uint32) []byte {
145148
return self.ministream[start : start+to_read]
146149
}
147150

148-
func (self *OLEFile) ReadFat(sector uint32) uint32 {
151+
func (self *OLEFile) ReadFat(sector uint32) (uint32, bool) {
149152
if int(sector) >= len(self.Fat) {
150-
return 0
153+
return 0, false
151154
}
152-
return self.Fat[sector]
155+
return self.Fat[sector], true
153156
}
154157

155-
func (self *OLEFile) ReadMiniFat(sector uint32) uint32 {
158+
func (self *OLEFile) ReadMiniFat(sector uint32) (uint32, bool) {
156159
if int(sector) >= len(self.MiniFat) {
157-
return 0
160+
return 0, false
158161
}
159-
return self.MiniFat[sector]
162+
return self.MiniFat[sector], true
160163
}
161164

162165
func (self *OLEFile) ReadChain(start uint32) []byte {
@@ -170,13 +173,18 @@ func (self *OLEFile) ReadMiniChain(start uint32) []byte {
170173
func (self *OLEFile) _ReadChain(
171174
start uint32,
172175
ReadSector func(uint32) []byte,
173-
ReadFat func(sector uint32) uint32) []byte {
176+
ReadFat func(sector uint32) (uint32, bool),
177+
) []byte {
174178
check := make(map[uint32]bool)
175179
result := []byte{}
176180

177181
for sector := start; sector != ENDOFCHAIN; {
178182
result = append(result, ReadSector(sector)...)
179-
next := ReadFat(sector)
183+
next, ok := ReadFat(sector)
184+
if !ok {
185+
DebugPrintf("invalid sector %x in chain", sector)
186+
return result
187+
}
180188
_, pres := check[next]
181189
if pres {
182190
DebugPrintf("infinite loop detected at %v to %v starting at %v",
@@ -225,6 +233,9 @@ func (self *OLEFile) OpenStreamByName(name string) ([]byte, error) {
225233
return self.GetStream(d.Index), nil
226234
}
227235

236+
// NewOLEFile creates a new OLEFile object from the given data.
237+
//
238+
// The OLE format is described in https://winprotocoldoc.z19.web.core.windows.net/MS-CFB/%5bMS-CFB%5d.pdf
228239
func NewOLEFile(data []byte) (*OLEFile, error) {
229240
if len(data) < 8 ||
230241
string(data[:8]) != OLE_SIGNATURE {
@@ -238,9 +249,21 @@ func NewOLEFile(data []byte) (*OLEFile, error) {
238249
return nil, err
239250
}
240251

241-
if self.Header.SectorShift > MAX_SECTOR_SHIFT {
242-
return nil, fmt.Errorf(
243-
"Sector size too large: %v", self.Header.SectorShift)
252+
var expectedSectorShift uint16
253+
switch self.Header.MajorVersion {
254+
case 3:
255+
expectedSectorShift = sectorShiftV3
256+
case 4:
257+
expectedSectorShift = sectorShiftV4
258+
default:
259+
return nil, fmt.Errorf("unsupported major version: %v", self.Header.MajorVersion)
260+
}
261+
if self.Header.MinorVersion != 0x3E {
262+
return nil, fmt.Errorf("unsupported minor version: %v", self.Header.MinorVersion)
263+
}
264+
265+
if self.Header.SectorShift != expectedSectorShift {
266+
return nil, fmt.Errorf("unexpected sector size: %d", 1<<self.Header.SectorShift)
244267
}
245268

246269
self.SectorSize = 1 << self.Header.SectorShift
@@ -250,11 +273,11 @@ func NewOLEFile(data []byte) (*OLEFile, error) {
250273
}
251274

252275
self.MiniSectorSize = 1 << self.Header.MiniSectorShift
253-
if (len(data)-512)%self.SectorSize != 0 {
276+
if len(data)%self.SectorSize != 0 {
254277
DebugPrintf("Last sector has invalid size\n")
255278
}
256279

257-
self.SectorCount = (len(data) - 512) / self.SectorSize
280+
self.SectorCount = len(data)/self.SectorSize - 1 // Subtract 1 for the header sector
258281
for _, sect := range self.Header.SectFat {
259282
if sect != FREESECT {
260283
self.FatSectors = append(self.FatSectors, sect)
@@ -279,7 +302,7 @@ func NewOLEFile(data []byte) (*OLEFile, error) {
279302
}
280303

281304
next := dif_values[len(dif_values)-1]
282-
for _, value := range dif_values[:len(dif_values)-2] {
305+
for _, value := range dif_values[:len(dif_values)-1] {
283306
if value != FREESECT {
284307
self.FatSectors = append(self.FatSectors, value)
285308
}
@@ -319,6 +342,9 @@ func NewOLEFile(data []byte) (*OLEFile, error) {
319342
if err != nil {
320343
return nil, err
321344
}
345+
if dir_obj == nil { // Unallocated index
346+
continue
347+
}
322348
self.Directory = append(self.Directory, dir_obj)
323349
}
324350

oleparse_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"archive/zip"
55
"encoding/json"
66
"io"
7+
"os"
78
"strings"
89
"testing"
910

@@ -20,6 +21,20 @@ func TestMacros(t *testing.T) {
2021
goldie.Assert(t, "vba_macros", serialized)
2122
}
2223

24+
func TestOlev4(t *testing.T) {
25+
oleData, err := os.ReadFile("test_data/vs.msi")
26+
if err != nil {
27+
t.Fatalf("Failed to open test file: %v", err)
28+
}
29+
oleFile, err := NewOLEFile(oleData)
30+
if err != nil {
31+
t.Fatalf("Failed to parse OLE file: %v", err)
32+
}
33+
if len(oleFile.Directory) != 28 {
34+
t.Fatalf("Expected 28 directory entries, got %d", len(oleFile.Directory))
35+
}
36+
}
37+
2338
func FuzzExtractMacros(f *testing.F) {
2439
r, err := zip.OpenReader("test_data/xlswithmacro.xlsm")
2540
if err != nil {

test_data/vs.msi

1.26 MB
Binary file not shown.

0 commit comments

Comments
 (0)