github.com/linuxboot/fiano@v1.2.0/pkg/visitors/validate.go (about)

     1  // Copyright 2018 the LinuxBoot Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package visitors
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  
    14  	"github.com/linuxboot/fiano/pkg/uefi"
    15  )
    16  
    17  // Validate performs extra checks on the firmware image.
    18  type Validate struct {
    19  	// An optional Writer for writing errors when validation is complete.
    20  	// When the writer it set, Run will also call os.Exit(1) upon finding
    21  	// an error.
    22  	W io.Writer
    23  
    24  	// List of validation errors.
    25  	Errors []error
    26  }
    27  
    28  // Run wraps Visit and performs some setup and teardown tasks.
    29  func (v *Validate) Run(f uefi.Firmware) error {
    30  	if err := f.Apply(v); err != nil {
    31  		return err
    32  	}
    33  
    34  	if v.W != nil && len(v.Errors) != 0 {
    35  		for _, e := range v.Errors {
    36  			fmt.Println(e)
    37  		}
    38  		os.Exit(1)
    39  	}
    40  	return nil
    41  }
    42  
    43  // Visit applies the Validate visitor to any Firmware type.
    44  func (v *Validate) Visit(f uefi.Firmware) error {
    45  	// TODO: add more verification where needed
    46  	switch f := f.(type) {
    47  	case *uefi.FlashImage:
    48  		_, err := f.FindSignature()
    49  		if err != nil {
    50  			v.Errors = append(v.Errors, err)
    51  		}
    52  
    53  	case *uefi.FlashDescriptor:
    54  		d := f.DescriptorMap
    55  		if d.MasterBase > uefi.FlashDescriptorMapMaxBase {
    56  			v.Errors = append(v.Errors, fmt.Errorf("MasterBase too large: expected %v bytes, got %v",
    57  				uefi.FlashDescriptorMapMaxBase,
    58  				d.MasterBase,
    59  			))
    60  		}
    61  		if d.RegionBase > uefi.FlashDescriptorMapMaxBase {
    62  			v.Errors = append(v.Errors, fmt.Errorf("RegionBase too large: expected %v bytes, got %v",
    63  				uefi.FlashDescriptorMapMaxBase,
    64  				d.RegionBase,
    65  			))
    66  		}
    67  		if d.MasterBase > uefi.FlashDescriptorMapMaxBase {
    68  			v.Errors = append(v.Errors, fmt.Errorf("ComponentBase too large: expected %v bytes, got %v",
    69  				uefi.FlashDescriptorMapMaxBase,
    70  				d.MasterBase,
    71  			))
    72  		}
    73  		if d.MasterBase == d.RegionBase {
    74  			v.Errors = append(v.Errors, fmt.Errorf("MasterBase must be different from RegionBase: both are at 0x%x",
    75  				d.MasterBase,
    76  			))
    77  		}
    78  		if d.MasterBase == d.ComponentBase {
    79  			v.Errors = append(v.Errors, fmt.Errorf("MasterBase must be different from ComponentBase: both are at 0x%x",
    80  				d.MasterBase,
    81  			))
    82  		}
    83  		if d.RegionBase == d.ComponentBase {
    84  			v.Errors = append(v.Errors, fmt.Errorf("RegionBase must be different from ComponentBase: both are at 0x%x",
    85  				d.RegionBase,
    86  			))
    87  		}
    88  
    89  	case *uefi.FirmwareVolume:
    90  		// Check for min length
    91  		fvlen := uint64(len(f.Buf()))
    92  		// We need this check in case HeaderLen doesn't exist, and bail out early
    93  		if fvlen < uefi.FirmwareVolumeMinSize {
    94  			v.Errors = append(v.Errors, fmt.Errorf("length too small!, buffer is only %#x bytes long", fvlen))
    95  			break
    96  		}
    97  		// Check header length
    98  		if f.HeaderLen < uefi.FirmwareVolumeMinSize {
    99  			v.Errors = append(v.Errors, fmt.Errorf("header length too small, got: %#x", f.HeaderLen))
   100  			break
   101  		}
   102  		// Check for full header and bail out if its not fully formed.
   103  		if fvlen < uint64(f.HeaderLen) {
   104  			v.Errors = append(v.Errors, fmt.Errorf("buffer smaller than header!, header is %#x bytes, buffer is %#x bytes",
   105  				f.HeaderLen, fvlen))
   106  			break
   107  		}
   108  		// Do we want to fail in this case? maybe not.
   109  		if uefi.FVGUIDs[f.FileSystemGUID] == "" {
   110  			v.Errors = append(v.Errors, fmt.Errorf("unknown FV type! Guid was %v", f.FileSystemGUID))
   111  		}
   112  		// UEFI PI spec says version should always be 2
   113  		if f.Revision != 2 {
   114  			v.Errors = append(v.Errors, fmt.Errorf("revision should be 2, was %v", f.Revision))
   115  		}
   116  		// Check Signature
   117  		fvSigInt := binary.LittleEndian.Uint32([]byte("_FVH"))
   118  		if f.Signature != fvSigInt {
   119  			v.Errors = append(v.Errors, fmt.Errorf("signature was not _FVH, got: %#08x", f.Signature))
   120  		}
   121  		// Check length
   122  		if f.Length != fvlen {
   123  			v.Errors = append(v.Errors, fmt.Errorf("length mismatch!, header has %#x, buffer is %#x bytes long", f.Length, fvlen))
   124  		}
   125  		// Check checksum
   126  		sum, err := uefi.Checksum16(f.Buf()[:f.HeaderLen]) // TODO: use the Header() function which does not exist yet
   127  		if err != nil {
   128  			v.Errors = append(v.Errors, fmt.Errorf("unable to checksum FV header: %v", err))
   129  		} else if sum != 0 {
   130  			v.Errors = append(v.Errors, fmt.Errorf("header did not sum to 0, got: %#x", sum))
   131  		}
   132  
   133  	case *uefi.File:
   134  		buflen := uint64(len(f.Buf()))
   135  		blankSize := [3]uint8{0xFF, 0xFF, 0xFF}
   136  		if buflen < uefi.FileHeaderMinLength {
   137  			v.Errors = append(v.Errors, fmt.Errorf("file length too small!, buffer is only %#x bytes long", buflen))
   138  			break
   139  		}
   140  
   141  		// Size Checks
   142  		fh := &f.Header
   143  		if fh.Size == blankSize {
   144  			if buflen < uefi.FileHeaderExtMinLength {
   145  				v.Errors = append(v.Errors, fmt.Errorf("file %v length too small!, buffer is only %#x bytes long for extended header",
   146  					fh.GUID, buflen))
   147  				break
   148  			}
   149  			if !fh.Attributes.IsLarge() {
   150  				v.Errors = append(v.Errors, fmt.Errorf("file %v using extended header, but large attribute is not set",
   151  					fh.GUID))
   152  				break
   153  			}
   154  		} else if uefi.Read3Size(f.Header.Size) != fh.ExtendedSize {
   155  			v.Errors = append(v.Errors, fmt.Errorf("file %v size not copied into extendedsize",
   156  				fh.GUID))
   157  			break
   158  		}
   159  		if buflen != fh.ExtendedSize {
   160  			v.Errors = append(v.Errors, fmt.Errorf("file %v size mismatch! Size is %#x, buf length is %#x",
   161  				fh.GUID, fh.ExtendedSize, buflen))
   162  			break
   163  		}
   164  
   165  		// Header Checksums
   166  		if sum := f.ChecksumHeader(); sum != 0 {
   167  			v.Errors = append(v.Errors, fmt.Errorf("file %v header checksum failure! sum was %v",
   168  				fh.GUID, sum))
   169  		}
   170  
   171  		// Body Checksum
   172  		if !fh.Attributes.HasChecksum() && fh.Checksum.File != uefi.EmptyBodyChecksum {
   173  			v.Errors = append(v.Errors, fmt.Errorf("file %v body checksum failure! Attribute was not set, but sum was %v instead of %v",
   174  				fh.GUID, fh.Checksum.File, uefi.EmptyBodyChecksum))
   175  		} else if fh.Attributes.HasChecksum() {
   176  			headerSize := uefi.FileHeaderMinLength
   177  			if fh.Attributes.IsLarge() {
   178  				headerSize = uefi.FileHeaderExtMinLength
   179  			}
   180  			if sum := uefi.Checksum8(f.Buf()[headerSize:]); sum != 0 { // TODO: use the Payload function which does not exist yet
   181  				v.Errors = append(v.Errors, fmt.Errorf("file %v body checksum failure! sum was %v",
   182  					fh.GUID, sum))
   183  			}
   184  		}
   185  
   186  	case *uefi.Section:
   187  		buflen := uint32(len(f.Buf()))
   188  		blankSize := [3]uint8{0xFF, 0xFF, 0xFF}
   189  
   190  		// Size Checks
   191  		sh := &f.Header
   192  		if sh.Size == blankSize {
   193  			if buflen < uefi.SectionExtMinLength {
   194  				v.Errors = append(v.Errors, fmt.Errorf("section length too small!, buffer is only %#x bytes long for extended header",
   195  					buflen))
   196  				break
   197  			}
   198  		} else if uint32(uefi.Read3Size(f.Header.Size)) != sh.ExtendedSize {
   199  			v.Errors = append(v.Errors, errors.New("section size not copied into extendedsize"))
   200  			break
   201  		}
   202  		if buflen != sh.ExtendedSize {
   203  			v.Errors = append(v.Errors, fmt.Errorf("section size mismatch! Size is %#x, buf length is %#x",
   204  				sh.ExtendedSize, buflen))
   205  			break
   206  		}
   207  
   208  	case *uefi.BIOSRegion:
   209  		if f.FlashRegion() != nil && !f.FlashRegion().Valid() {
   210  			v.Errors = append(v.Errors, fmt.Errorf("BIOSRegion is not valid, region was %v", *f.FlashRegion()))
   211  		}
   212  
   213  		if _, err := f.FirstFV(); err != nil {
   214  			v.Errors = append(v.Errors, err)
   215  		}
   216  
   217  		for i, e := range f.Elements {
   218  			if err := e.Value.Apply(v); err != nil {
   219  				return err
   220  			}
   221  			f, ok := e.Value.(*uefi.FirmwareVolume)
   222  			if !ok {
   223  				// Not a firmware volume
   224  				continue
   225  			}
   226  			// We have to do this because they didn't put an encapsulating structure around the FVs.
   227  			// This means it's possible for different firmware volumes to report different erase polarities.
   228  			// Now we have to check to see if we're in some insane state.
   229  			if ep := f.GetErasePolarity(); ep != uefi.Attributes.ErasePolarity {
   230  				v.Errors = append(v.Errors, fmt.Errorf("erase polarity mismatch! fv 0 has %#x and fv %d has %#x",
   231  					uefi.Attributes.ErasePolarity, i, ep))
   232  			}
   233  		}
   234  		return nil // We already traversed the children manually.
   235  
   236  	case *uefi.MERegion:
   237  		if f.FlashRegion() == nil {
   238  			v.Errors = append(v.Errors, errors.New("region position is nil"))
   239  		}
   240  		if !f.FlashRegion().Valid() {
   241  			v.Errors = append(v.Errors, fmt.Errorf("region is not valid, region was %v", *f.FlashRegion()))
   242  		}
   243  
   244  	case *uefi.RawRegion:
   245  		if f.FlashRegion() == nil {
   246  			v.Errors = append(v.Errors, errors.New("region position is nil"))
   247  		}
   248  		if !f.FlashRegion().Valid() {
   249  			v.Errors = append(v.Errors, fmt.Errorf("region is not valid, region was %v", *f.FlashRegion()))
   250  		}
   251  	}
   252  	return f.ApplyChildren(v)
   253  }
   254  
   255  func init() {
   256  	RegisterCLI("validate", "perform extra validation checks", 0, func(args []string) (uefi.Visitor, error) {
   257  		return &Validate{}, nil
   258  	})
   259  }