Skip to content

Commit

Permalink
Fuzz (#427)
Browse files Browse the repository at this point in the history
* fuzz

Uses github.com/dvyukov/go-fuzz; see fuzz.go for instructions

Signed-off-by: Mark Pictor <[email protected]>

* bounds checks, avoid infinite loop

caught with go-fuzz

Signed-off-by: Mark Pictor <[email protected]>

* fuzz.go copyright

Signed-off-by: Mark Pictor <[email protected]>

* comment for golint

Signed-off-by: Mark Pictor <[email protected]>

* update vendor (xz)

Signed-off-by: Mark Pictor <[email protected]>

* Gopkg.lock

Signed-off-by: Mark Pictor <[email protected]>

* test using fuzz corpus

compress corpus with xz rather than zip because archive is ~1/10th the size

Signed-off-by: Mark Pictor <[email protected]>

* temp - disable log.Writer()

Signed-off-by: Mark Pictor <[email protected]>

* remove vendor directory, and Go.lock

Signed-off-by: Ronald G. Minnich <[email protected]>

* update packaged usage (io/ioutil -> io)

Signed-off-by: Ronald G. Minnich <[email protected]>

---------

Signed-off-by: Mark Pictor <[email protected]>
Signed-off-by: Ronald G. Minnich <[email protected]>
Co-authored-by: Mark Pictor <[email protected]>
  • Loading branch information
rminnich and mpictor authored Sep 19, 2024
1 parent 4f59c83 commit 2c70d9e
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 24 deletions.
4 changes: 4 additions & 0 deletions pkg/uefi/biosregion.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ func NewBIOSRegion(buf []byte, r *FlashRegion, _ FlashRegionType) (Region, error
if err != nil {
return nil, err
}
if fv.Length == 0 {
//avoid infinite loop
return nil, errors.New("FV len 0; cannot progress")
}
absOffset += fv.Length
buf = buf[uint64(offset)+fv.Length:]
br.Elements = append(br.Elements, MakeTyped(fv))
Expand Down
3 changes: 3 additions & 0 deletions pkg/uefi/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,9 @@ func NewFile(buf []byte) (*File, error) {

// Special case for NVAR Store stored in raw file
if f.Header.Type == FVFileTypeRaw && f.Header.GUID == *NVAR {
if f.DataOffset >= uint64(len(f.buf)) {
return nil, fmt.Errorf("data offset %#x exceeds buffer size %#x", f.DataOffset, len(f.buf))
}
ns, err := NewNVarStore(f.buf[f.DataOffset:])
if err != nil {
log.Errorf("error parsing NVAR store in file %v: %v", f.Header.GUID, err)
Expand Down
3 changes: 3 additions & 0 deletions pkg/uefi/firmwarevolume.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ func NewFirmwareVolume(data []byte, fvOffset uint64, resizable bool) (*FirmwareV
var prevLen uint64
for offset := fv.DataOffset; offset < lh; offset += prevLen {
offset = Align8(offset)
if uint64(len(data)) <= offset {
return nil, fmt.Errorf("offset %#x is beyond end of FV data (%#x)", offset, len(data))
}
file, err := NewFile(data[offset:])
if err != nil {
return nil, fmt.Errorf("unable to construct firmware file at offset %#x into FV: %v", offset, err)
Expand Down
23 changes: 15 additions & 8 deletions pkg/uefi/flash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ package uefi
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"os"
"sort"
)

// FlashSignature is the sequence of bytes that a Flash image is expected to
// start with.
var (
FlashSignature = []byte{0x5a, 0xa5, 0xf0, 0x0f}
ErrTooShort = errors.New("too small to be firmware")
)

const (
Expand All @@ -39,7 +42,10 @@ type FlashDescriptor struct {

// FindSignature searches for an Intel flash signature.
func FindSignature(buf []byte) (int, error) {
if len(buf) >= 16+len(FlashSignature) && bytes.Equal(buf[16:16+len(FlashSignature)], FlashSignature) {
if len(buf) < 20 {
return -1, ErrTooShort
}
if bytes.Equal(buf[16:16+len(FlashSignature)], FlashSignature) {
// 16 + 4 since the descriptor starts after the signature
return 20, nil
}
Expand All @@ -52,8 +58,8 @@ func FindSignature(buf []byte) (int, error) {
if len(buf) < firstBytesCnt {
firstBytesCnt = len(buf)
}
return -1, fmt.Errorf("flash signature not found: first %d bytes are:\n%s",
firstBytesCnt, hex.Dump(buf[:firstBytesCnt]))
return -1, fmt.Errorf("flash signature not found: first %d bytes are:\n%s:%w",
firstBytesCnt, hex.Dump(buf[:firstBytesCnt]), os.ErrNotExist)
}

// Buf returns the buffer.
Expand Down Expand Up @@ -99,7 +105,11 @@ func (fd *FlashDescriptor) ParseFlashDescriptor() error {

// Region
fd.RegionStart = uint(fd.DescriptorMap.RegionBase) * 0x10
region, err := NewFlashRegionSection(fd.buf[fd.RegionStart : fd.RegionStart+uint(FlashRegionSectionSize)])
regionEnd := fd.RegionStart + uint(FlashRegionSectionSize)
if buflen := uint(len(fd.buf)); fd.RegionStart >= buflen || regionEnd >= buflen {
return fmt.Errorf("flash descriptor region out of bounds: range [%#x:%#x], buflen %#x", fd.RegionStart, regionEnd, buflen)
}
region, err := NewFlashRegionSection(fd.buf[fd.RegionStart:regionEnd])
if err != nil {
return err
}
Expand Down Expand Up @@ -230,10 +240,7 @@ func (f *FlashImage) fillRegionGaps() error {
// mode.
func NewFlashImage(buf []byte) (*FlashImage, error) {
if len(buf) < FlashDescriptorLength {
return nil, fmt.Errorf("flash Descriptor Map size too small: expected %v bytes, got %v",
FlashDescriptorLength,
len(buf),
)
return nil, fmt.Errorf("NewFlashImage: need at least %d bytes, only %d provided:%w", FlashDescriptorLength, len(buf), ErrTooShort)
}
f := FlashImage{FlashSize: uint64(len(buf))}

Expand Down
28 changes: 12 additions & 16 deletions pkg/uefi/flash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ package uefi

import (
"encoding/hex"
"fmt"
"errors"
"os"
"testing"
)

Expand Down Expand Up @@ -43,31 +44,26 @@ var (

func TestFindSignature(t *testing.T) {
var tests = []struct {
name string
buf []byte
offset int
msg string
err error
}{
{nil, -1,
fmt.Sprintf("flash signature not found: first 0 bytes are:\n%s", hex.Dump(nil))},
{[]byte{1, 2, 3}, -1,
fmt.Sprintf("flash signature not found: first 3 bytes are:\n%s", hex.Dump([]byte{1, 2, 3}))},
{emptySig, -1,
fmt.Sprintf("flash signature not found: first 20 bytes are:\n%s", hex.Dump(emptySig[:20]))},
{ichSig, 4, ""},
{pchSig, 20, ""},
{misalignedSig, -1,
fmt.Sprintf("flash signature not found: first 20 bytes are:\n%s", hex.Dump(misalignedSig[:20]))},
{"empty buffer", nil, -1, ErrTooShort},
{"short buffer", []byte{1, 2, 3}, -1, ErrTooShort},
{"empty signature", emptySig, -1, os.ErrNotExist},
{"ichSign", ichSig, 4, nil},
{"pchSig", pchSig, 20, nil},
{"misaligned sig", misalignedSig, -1, os.ErrNotExist},
}
for _, test := range tests {
f := FlashImage{buf: test.buf}
offset, err := f.FindSignature()
if offset != test.offset {
t.Errorf("Offset was not correct, expected %v, got %v", test.offset, offset)
}
if err == nil && test.msg != "" {
t.Errorf("Error was not returned, expected %v", test.msg)
} else if err != nil && err.Error() != test.msg {
t.Errorf("Mismatched Error returned, expected \n%v\n got \n%v\n", test.msg, err.Error())
if !errors.Is(err, test.err) {
t.Errorf("%s: got %v, want %v", test.name, err, test.err)
}
}
}
Expand Down
45 changes: 45 additions & 0 deletions pkg/uefi/fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2020 the LinuxBoot Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build gofuzz

package uefi

import (
"log"
)

// go get github.com/dvyukov/go-fuzz/go-fuzz
// go get github.com/dvyukov/go-fuzz/go-fuzz-build
//
// mkdir fuzz
// go-fuzz-build
// go-fuzz -bin uefi-fuzz.zip -workdir fuzz

type nopWriter struct{}

func (n *nopWriter) Write(_ []byte) (int, error) { return 0, nil }

func init() {
//speed up logging
log.SetFlags(0)
log.SetOutput(&nopWriter{})
}

const (
ICK int = iota - 1
MEH
WOW
)

// func Parse(buf []byte) (Firmware, error)
func Fuzz(b []byte) int {
//initialize, since something could have changed the polarity
Attributes = ROMAttributes{ErasePolarity: poisonedPolarity}
_, err := Parse(b)
if err == nil {
return MEH
}
return WOW
}
89 changes: 89 additions & 0 deletions pkg/uefi/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2020 the LinuxBoot Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause
//

package uefi

import (
"archive/tar"
"fmt"
"io"
"log"
"os"
fp "path/filepath"
"testing"

"github.com/ulikunitz/xz"
)

//no-op writer to minimize logging overhead
type nopWriter struct{}

func (n *nopWriter) Write(_ []byte) (int, error) { return 0, nil }

// Tests using input from fuzzing runs. Ignores any errors, just checks that the
// inputs do not cause crashes.
//
// To update the input zip after a fuzzing run:
// cd fuzz/corpus
// zip ../../testdata/fuzz_in.zip *
//
// Similarly, the zip can be extracted to use as input corpus. See fuzz.go for
// go-fuzz instructions.
func TestFuzzInputs(t *testing.T) {
// if testing.CoverMode() != "" {
// NOTE - since this test doesn't validate the outputs, coverage from
// it is very low value, essentially inflating the coverage numbers.
// t.Skip("this test will inflate coverage")
// }

// not available in go < 1.13
// //restore log behavior at end
// logOut := log.Writer()
// defer log.SetOutput(logOut)

//no logging output for this test, to increase speed
log.SetOutput(&nopWriter{})
log.SetFlags(0)

txz, err := os.Open("testdata/fuzz_in.txz")
if err != nil {
t.Fatal(err)
}
defer txz.Close()
x, err := xz.NewReader(txz)
if err != nil {
t.Fatal(err)
}
tr := tar.NewReader(x)
for i := 0; ; i++ {
zf, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Error(err)
continue
}
n := fp.Base(zf.Name)
if len(n) > 10 {
n = n[:10]
} else {
t.Logf("short: %s", n)
}
name := fmt.Sprintf("%03d_%s", i, n)
t.Run(name, func(t *testing.T) {
data, err := io.ReadAll(tr)
if err != nil {
t.Error(err)
}
//reset polarity before each run, some fuzz files change it
Attributes = ROMAttributes{ErasePolarity: poisonedPolarity}
//ignore any errors - just catch crashes
_, _ = Parse(data)
})
}
}
3 changes: 3 additions & 0 deletions pkg/uefi/nvram.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ func (s *NVarStore) getGUIDFromStore(i uint8) guid.GUID {
}
s.GUIDStore = append(s.GUIDStore, a...)
}
if int(i) >= len(s.GUIDStore) {
return *ZeroGUID
}
return s.GUIDStore[i]
}

Expand Down
22 changes: 22 additions & 0 deletions pkg/uefi/section.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,16 @@ func (s *Section) GenSecHeader() error {
return nil
}

//ErrOversizeHdr is the error returned by NewSection when the header is oversize.
type ErrOversizeHdr struct {
hdrsiz uintptr
bufsiz int
}

func (e *ErrOversizeHdr) Error() string {
return fmt.Sprintf("Header size %#x larger than available data %#x", e.hdrsiz, e.bufsiz)
}

// NewSection parses a sequence of bytes and returns a Section
// object, if a valid one is passed, or an error.
func NewSection(buf []byte, fileOrder int) (*Section, error) {
Expand Down Expand Up @@ -436,20 +446,32 @@ func NewSection(buf []byte, fileOrder int) (*Section, error) {
}

case SectionTypeUserInterface:
if len(s.buf) <= int(headerSize) {
return nil, &ErrOversizeHdr{hdrsiz: headerSize, bufsiz: len(s.buf)}
}
s.Name = unicode.UCS2ToUTF8(s.buf[headerSize:])

case SectionTypeVersion:
if len(s.buf) <= int(headerSize+2) {
return nil, &ErrOversizeHdr{hdrsiz: headerSize + 2, bufsiz: len(s.buf)}
}
s.BuildNumber = binary.LittleEndian.Uint16(s.buf[headerSize : headerSize+2])
s.Version = unicode.UCS2ToUTF8(s.buf[headerSize+2:])

case SectionTypeFirmwareVolumeImage:
if len(s.buf) <= int(headerSize) {
return nil, &ErrOversizeHdr{hdrsiz: headerSize, bufsiz: len(s.buf)}
}
fv, err := NewFirmwareVolume(s.buf[headerSize:], 0, true)
if err != nil {
return nil, err
}
s.Encapsulated = []*TypedFirmware{MakeTyped(fv)}

case SectionTypeDXEDepEx, SectionTypePEIDepEx, SectionMMDepEx:
if len(s.buf) <= int(headerSize) {
return nil, &ErrOversizeHdr{hdrsiz: headerSize, bufsiz: len(s.buf)}
}
var err error
if s.DepEx, err = parseDepEx(s.buf[headerSize:]); err != nil {
log.Warnf("%v", err)
Expand Down
Binary file added pkg/uefi/testdata/fuzz_in.txz
Binary file not shown.

0 comments on commit 2c70d9e

Please sign in to comment.