github.com/linuxboot/fiano@v1.2.0/pkg/uefi/file.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 uefi
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"encoding/json"
    11  	"fmt"
    12  	"strings"
    13  
    14  	"github.com/linuxboot/fiano/pkg/guid"
    15  	"github.com/linuxboot/fiano/pkg/log"
    16  )
    17  
    18  // FVFileType represents the different types possible in an EFI file.
    19  type FVFileType uint8
    20  
    21  // UEFI FV File types.
    22  const (
    23  	FVFileTypeAll FVFileType = iota
    24  	FVFileTypeRaw
    25  	FVFileTypeFreeForm
    26  	FVFileTypeSECCore
    27  	FVFileTypePEICore
    28  	FVFileTypeDXECore
    29  	FVFileTypePEIM
    30  	FVFileTypeDriver
    31  	FVFileTypeCombinedPEIMDriver
    32  	FVFileTypeApplication
    33  	FVFileTypeSMM
    34  	FVFileTypeVolumeImage
    35  	FVFileTypeCombinedSMMDXE
    36  	FVFileTypeSMMCore
    37  	FVFileTypeSMMStandalone
    38  	FVFileTypeSMMCoreStandalone
    39  	FVFileTypeOEMMin   FVFileType = 0xC0
    40  	FVFileTypeOEMMax   FVFileType = 0xDF
    41  	FVFileTypeDebugMin FVFileType = 0xE0
    42  	FVFileTypeDebugMax FVFileType = 0xEF
    43  	FVFileTypePad      FVFileType = 0xF0
    44  	FVFileTypeFFSMin   FVFileType = 0xF0
    45  	FVFileTypeFFSMax   FVFileType = 0xFF
    46  )
    47  
    48  // SupportedFiles is a list of files types which will be parsed. File types not
    49  // on this list are treated as opaque binary blobs.
    50  var SupportedFiles = map[FVFileType]bool{
    51  	// These are the file types that we'll actually try to parse sections for.
    52  	FVFileTypeRaw:      false,
    53  	FVFileTypeFreeForm: true,
    54  	FVFileTypeSECCore:  true,
    55  	FVFileTypePEICore:  true,
    56  	FVFileTypeDXECore:  true,
    57  	// TODO: Commenting out this line prevents PEI modules from being
    58  	// decompressed. This solves the problem of PEI being too big when recompressed.
    59  	//FVFileTypePEIM:               true,
    60  	FVFileTypeDriver:             true,
    61  	FVFileTypeCombinedPEIMDriver: true,
    62  	FVFileTypeApplication:        true,
    63  	FVFileTypeSMM:                true,
    64  	FVFileTypeVolumeImage:        true,
    65  	FVFileTypeCombinedSMMDXE:     true,
    66  	FVFileTypeSMMCore:            true,
    67  	FVFileTypeSMMStandalone:      true,
    68  	FVFileTypeSMMCoreStandalone:  true,
    69  }
    70  
    71  var fileTypeNames = map[FVFileType]string{
    72  	FVFileTypeRaw:                "EFI_FV_FILETYPE_RAW",
    73  	FVFileTypeFreeForm:           "EFI_FV_FILETYPE_FREEFORM",
    74  	FVFileTypeSECCore:            "EFI_FV_FILETYPE_SECURITY_CORE",
    75  	FVFileTypePEICore:            "EFI_FV_FILETYPE_PEI_CORE",
    76  	FVFileTypeDXECore:            "EFI_FV_FILETYPE_DXE_CORE",
    77  	FVFileTypePEIM:               "EFI_FV_FILETYPE_PEIM",
    78  	FVFileTypeDriver:             "EFI_FV_FILETYPE_DRIVER",
    79  	FVFileTypeCombinedPEIMDriver: "EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER",
    80  	FVFileTypeApplication:        "EFI_FV_FILETYPE_APPLICATION",
    81  	FVFileTypeSMM:                "EFI_FV_FILETYPE_MM",
    82  	FVFileTypeVolumeImage:        "EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE",
    83  	FVFileTypeCombinedSMMDXE:     "EFI_FV_FILETYPE_COMBINED_MM_DXE",
    84  	FVFileTypeSMMCore:            "EFI_FV_FILETYPE_MM_CORE",
    85  	FVFileTypeSMMStandalone:      "EFI_FV_FILETYPE_MM_STANDALONE",
    86  	FVFileTypeSMMCoreStandalone:  "EFI_FV_FILETYPE_MM_CORE_STANDALONE",
    87  }
    88  
    89  // NamesToFileType maps from common file type strings to the actual type.
    90  var NamesToFileType map[string]FVFileType
    91  
    92  func init() {
    93  	NamesToFileType = make(map[string]FVFileType)
    94  	for k, v := range fileTypeNames {
    95  		NamesToFileType[strings.TrimPrefix(v, "EFI_FV_FILETYPE_")] = k
    96  	}
    97  }
    98  
    99  // String creates a string representation for the file type.
   100  func (f FVFileType) String() string {
   101  	switch {
   102  	case FVFileTypeOEMMin <= f && f <= FVFileTypeOEMMax:
   103  		return fmt.Sprintf("EFI_FV_FILETYPE_OEM (%#x)", uint8(f))
   104  	case FVFileTypeDebugMin <= f && f <= FVFileTypeDebugMax:
   105  		return fmt.Sprintf("EFI_FV_FILETYPE_DEBUG (%#x)", uint8(f))
   106  	// We use the non-inclusive '<' operator here because pad files belong
   107  	// to the FFS filetype, but are also their own type.
   108  	case FVFileTypeFFSMin < f && f <= FVFileTypeFFSMax:
   109  		return fmt.Sprintf("EFI_FV_FILETYPE_FFS (%#x)", uint8(f))
   110  	case f == FVFileTypePad:
   111  		return "EFI_FV_FILETYPE_FFS_PAD"
   112  	}
   113  	if t, ok := fileTypeNames[f]; ok {
   114  		return t
   115  	}
   116  	return "UNKNOWN"
   117  }
   118  
   119  // Stock GUIDS
   120  var (
   121  	ZeroGUID = guid.MustParse("00000000-0000-0000-0000-000000000000")
   122  	FFGUID   = guid.MustParse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF")
   123  )
   124  
   125  // FileAlignments specifies the correct alignments based on the field in the file header.
   126  var fileAlignments = []uint64{
   127  	// These alignments are not computable, we have to look them up.
   128  	1,
   129  	16,
   130  	128,
   131  	512,
   132  	1024,
   133  	4 * 1024,
   134  	32 * 1024,
   135  	64 * 1024,
   136  	128 * 1024,
   137  	256 * 1024,
   138  	512 * 1024,
   139  	1024 * 1024,
   140  	2 * 1024 * 1024,
   141  	4 * 1024 * 1024,
   142  	8 * 1024 * 1024,
   143  	16 * 1024 * 1024,
   144  }
   145  
   146  const (
   147  	// FileHeaderMinLength is the minimum length of a firmware file header.
   148  	FileHeaderMinLength = 0x18
   149  	// FileHeaderExtMinLength is the minimum length of an extended firmware file header.
   150  	FileHeaderExtMinLength = 0x20
   151  	// EmptyBodyChecksum is the value placed in the File IntegrityCheck field if the body checksum bit isn't set.
   152  	EmptyBodyChecksum uint8 = 0xAA
   153  )
   154  
   155  // IntegrityCheck holds the two 8 bit checksums for the file header and body separately.
   156  type IntegrityCheck struct {
   157  	Header uint8
   158  	File   uint8
   159  }
   160  
   161  type fileAttr uint8
   162  
   163  // FileState (needs to be xored with Attributes.ErasePolarity)
   164  type FileState uint8
   165  
   166  // File State Bits
   167  const (
   168  	FileStateHeaderConstruction FileState = 0x01
   169  	FileStateHeaderValid        FileState = 0x02
   170  	FileStateDataValid          FileState = 0x04
   171  	FileStateMarkeForUpdate     FileState = 0x08
   172  	FileStateDeleted            FileState = 0x10
   173  	FileStateHeaderInvalid      FileState = 0x20
   174  
   175  	FileStateValid FileState = FileStateHeaderConstruction | FileStateHeaderValid | FileStateDataValid
   176  )
   177  
   178  type ThreeUint8 [3]uint8
   179  
   180  func (t *ThreeUint8) UnmarshalJSON(b []byte) error {
   181  	if copy(t[:], b) == 0 {
   182  		return fmt.Errorf("Cannot unmarshal 3 uint8 from %v\n", b)
   183  	}
   184  	return nil
   185  }
   186  
   187  func (t *ThreeUint8) MarshalJSON() ([]byte, error) {
   188  	res := Read3Size(*t)
   189  	return json.Marshal(res)
   190  }
   191  
   192  // FileHeader represents an EFI File header.
   193  type FileHeader struct {
   194  	GUID       guid.GUID      // This is the GUID of the file.
   195  	Checksum   IntegrityCheck `json:"-"`
   196  	Type       FVFileType
   197  	Attributes fileAttr
   198  	Size       ThreeUint8
   199  	State      FileState
   200  }
   201  
   202  // IsLarge checks if the large file attribute is set.
   203  func (a fileAttr) IsLarge() bool {
   204  	return a&0x01 != 0
   205  }
   206  
   207  // GetAlignment returns the byte alignment specified by the file header.
   208  func (a fileAttr) GetAlignment() uint64 {
   209  	alignVal := (a & 0x38) >> 3
   210  	alignVal |= (a & 0x02) << 2
   211  	return fileAlignments[alignVal]
   212  }
   213  
   214  // Sets the large file attribute.
   215  func (a *fileAttr) setLarge(large bool) {
   216  	if large {
   217  		*a |= 0x01
   218  	} else {
   219  		*a &= 0xFE
   220  	}
   221  }
   222  
   223  // HasChecksum checks if we need to checksum the file body.
   224  func (a fileAttr) HasChecksum() bool {
   225  	return a&0x40 != 0
   226  }
   227  
   228  // SetState sets file state respecting erase polarity
   229  func (fh *FileHeader) SetState(s FileState) {
   230  	fh.State = s ^ FileState(Attributes.ErasePolarity)
   231  }
   232  
   233  // HeaderLen returns the length of the file header depending on the file size.
   234  func (f *File) HeaderLen() uint64 {
   235  	if f.Header.Attributes.IsLarge() {
   236  		return FileHeaderExtMinLength
   237  	}
   238  	return FileHeaderMinLength
   239  }
   240  
   241  // ChecksumHeader returns a checksum of the header.
   242  func (f *File) ChecksumHeader() uint8 {
   243  	fh := f.Header
   244  	headerSize := FileHeaderMinLength
   245  	if fh.Attributes.IsLarge() {
   246  		headerSize = FileHeaderExtMinLength
   247  	}
   248  	// Sum over header without State and IntegrityCheck.File.
   249  	// To do that we just sum over the whole header and subtract.
   250  	// UEFI PI Spec 3.2.3 EFI_FFS_FILE_HEADER
   251  	sum := Checksum8(f.buf[:headerSize])
   252  	sum -= fh.Checksum.File
   253  	sum -= uint8(fh.State)
   254  	return sum
   255  }
   256  
   257  // FileHeaderExtended represents an EFI File header with the
   258  // large file attribute set.
   259  // We also use this as the generic header for all EFI files, regardless of whether
   260  // they are actually large. This makes it easier for us to just return one type
   261  // All sizes are also copied into the ExtendedSize field so we only have to check once
   262  type FileHeaderExtended struct {
   263  	FileHeader
   264  	ExtendedSize uint64 `json:"-"`
   265  }
   266  
   267  // File represents an EFI File.
   268  type File struct {
   269  	Header FileHeaderExtended
   270  	Type   string
   271  
   272  	// a File can contain either Sections or an NVarStore but not both
   273  	Sections  []*Section `json:",omitempty"`
   274  	NVarStore *NVarStore `json:",omitempty"`
   275  
   276  	//Metadata for extraction and recovery
   277  	buf         []byte
   278  	ExtractPath string
   279  	DataOffset  uint64
   280  }
   281  
   282  // Buf returns the buffer.
   283  // Used mostly for things interacting with the Firmware interface.
   284  func (f *File) Buf() []byte {
   285  	return f.buf
   286  }
   287  
   288  // SetBuf sets the buffer.
   289  // Used mostly for things interacting with the Firmware interface.
   290  func (f *File) SetBuf(buf []byte) {
   291  	f.buf = buf
   292  }
   293  
   294  // Apply calls the visitor on the File.
   295  func (f *File) Apply(v Visitor) error {
   296  	return v.Visit(f)
   297  }
   298  
   299  // ApplyChildren calls the visitor on each child node of File.
   300  func (f *File) ApplyChildren(v Visitor) error {
   301  	if f.NVarStore != nil {
   302  		if err := f.NVarStore.Apply(v); err != nil {
   303  			return err
   304  		}
   305  		return nil
   306  	}
   307  	for _, s := range f.Sections {
   308  		if err := s.Apply(v); err != nil {
   309  			return err
   310  		}
   311  	}
   312  	return nil
   313  }
   314  
   315  // SetSize sets the size into the File struct.
   316  // If resizeFile is true, if the file is too large the file will be enlarged to make space
   317  // for the ExtendedHeader
   318  func (f *File) SetSize(size uint64, resizeFile bool) {
   319  	fh := &f.Header
   320  	// See if we need the extended size
   321  	// Check if size > 3 bytes size field
   322  	fh.ExtendedSize = size
   323  	fh.Attributes.setLarge(false)
   324  	if fh.ExtendedSize > 0xFFFFFF {
   325  		// Can't fit, need extended header
   326  		if resizeFile {
   327  			// Increase the file size by the additional space needed
   328  			// for the extended header.
   329  			fh.ExtendedSize += FileHeaderExtMinLength - FileHeaderMinLength
   330  		}
   331  		fh.Attributes.setLarge(true)
   332  	}
   333  	// This will set size to 0xFFFFFF if too big.
   334  	fh.Size = Write3Size(fh.ExtendedSize)
   335  }
   336  
   337  // ChecksumAndAssemble takes in the fileData and assembles the file binary
   338  func (f *File) ChecksumAndAssemble(fileData []byte) error {
   339  	// Checksum the header and body, then write out the header.
   340  	// To checksum the header we write the temporary header to the file buffer first.
   341  	fh := &f.Header
   342  
   343  	header := new(bytes.Buffer)
   344  	err := binary.Write(header, binary.LittleEndian, fh)
   345  	if err != nil {
   346  		return fmt.Errorf("unable to construct binary header of file %v, got %v",
   347  			fh.GUID, err)
   348  	}
   349  	f.buf = header.Bytes()
   350  	// We need to get rid of whatever it sums to so that the overall sum is zero
   351  	// Sorry about the name :(
   352  	fh.Checksum.Header -= f.ChecksumHeader()
   353  
   354  	// Checksum the body
   355  	fh.Checksum.File = EmptyBodyChecksum
   356  	if fh.Attributes.HasChecksum() {
   357  		// if the empty checksum had been set to 0 instead of 0xAA
   358  		// this could have been a bit nicer. BUT NOOOOOOO.
   359  		fh.Checksum.File = 0 - Checksum8(fileData)
   360  	}
   361  
   362  	// Write out the updated header to the buffer with the new checksums.
   363  	// Write the extended header only if the large attribute flag is set.
   364  	header = new(bytes.Buffer)
   365  	if fh.Attributes.IsLarge() {
   366  		err = binary.Write(header, binary.LittleEndian, fh)
   367  	} else {
   368  		err = binary.Write(header, binary.LittleEndian, fh.FileHeader)
   369  	}
   370  	if err != nil {
   371  		return err
   372  	}
   373  	f.buf = header.Bytes()
   374  
   375  	f.buf = append(f.buf, fileData...)
   376  	return nil
   377  }
   378  
   379  // CreatePadFile creates an empty pad file in order to align the next file.
   380  func CreatePadFile(size uint64) (*File, error) {
   381  	if size < FileHeaderMinLength {
   382  		return nil, fmt.Errorf("size too small! min size required is %#x bytes, requested %#x",
   383  			FileHeaderMinLength, size)
   384  	}
   385  
   386  	f := File{}
   387  	fh := &f.Header
   388  
   389  	// Create empty guid
   390  	if Attributes.ErasePolarity == 0xFF {
   391  		fh.GUID = *FFGUID
   392  	} else if Attributes.ErasePolarity == 0 {
   393  		fh.GUID = *ZeroGUID
   394  	} else {
   395  		return nil, fmt.Errorf("erase polarity not 0x00 or 0xFF, got %#x", Attributes.ErasePolarity)
   396  	}
   397  
   398  	// TODO: I see examples of this where the attributes are just 0 and not dependent on the
   399  	// erase polarity. Is that right? Check and handle.
   400  	fh.Attributes = 0
   401  
   402  	// Set the size. If the file is too big, we take up more of the padding for the header.
   403  	// This also sets the large file attribute if file is big.
   404  	f.SetSize(size, false)
   405  	fh.Type = FVFileTypePad
   406  	f.Type = fh.Type.String()
   407  
   408  	// Create empty pad filedata based on size
   409  	var fileData []byte
   410  	fileData = make([]byte, size-FileHeaderMinLength)
   411  	if fh.Attributes.IsLarge() {
   412  		fileData = make([]byte, size-FileHeaderExtMinLength)
   413  	}
   414  	// Fill with empty bytes
   415  	for i, dataLen := 0, len(fileData); i < dataLen; i++ {
   416  		fileData[i] = Attributes.ErasePolarity
   417  	}
   418  
   419  	fh.SetState(FileStateValid)
   420  
   421  	// Everything has been setup. Checksum and create.
   422  	if err := f.ChecksumAndAssemble(fileData); err != nil {
   423  		return nil, err
   424  	}
   425  	return &f, nil
   426  }
   427  
   428  // NewFile parses a sequence of bytes and returns a File
   429  // object, if a valid one is passed, or an error. If no error is returned and the File
   430  // pointer is nil, it means we've reached the volume free space at the end of the FV.
   431  func NewFile(buf []byte) (*File, error) {
   432  	f := File{}
   433  	f.DataOffset = FileHeaderMinLength
   434  	// Read in standard header.
   435  	r := bytes.NewReader(buf)
   436  	if err := binary.Read(r, binary.LittleEndian, &f.Header.FileHeader); err != nil {
   437  		return nil, err
   438  	}
   439  
   440  	// Map type to string.
   441  	f.Type = f.Header.Type.String()
   442  
   443  	// TODO: Check Attribute flag as well. How important is the attribute flag? we already
   444  	// have FFFFFF in the size
   445  	if f.Header.Size == [3]uint8{0xFF, 0xFF, 0xFF} {
   446  		// Extended Header
   447  		if err := binary.Read(r, binary.LittleEndian, &f.Header.ExtendedSize); err != nil {
   448  			return nil, err
   449  		}
   450  		if f.Header.ExtendedSize == 0xFFFFFFFFFFFFFFFF {
   451  			// Start of free space
   452  			// Note: this is not a pad file. Pad files also have valid headers.
   453  			return nil, nil
   454  		}
   455  		f.DataOffset = FileHeaderExtMinLength
   456  	} else {
   457  		// Copy small size into big for easier handling.
   458  		// Damn the 3 byte sizes.
   459  		f.Header.ExtendedSize = Read3Size(f.Header.Size)
   460  	}
   461  
   462  	if buflen := len(buf); f.Header.ExtendedSize > uint64(buflen) {
   463  		return nil, fmt.Errorf("File size too big! File with GUID: %v has length %v, but is only %v bytes big",
   464  			f.Header.GUID, f.Header.ExtendedSize, buflen)
   465  	}
   466  
   467  	if ReadOnly {
   468  		f.buf = buf[:f.Header.ExtendedSize]
   469  	} else {
   470  		// Copy out the buffer.
   471  		newBuf := buf[:f.Header.ExtendedSize]
   472  		f.buf = make([]byte, f.Header.ExtendedSize)
   473  		copy(f.buf, newBuf)
   474  	}
   475  
   476  	// Special case for NVAR Store stored in raw file
   477  	if f.Header.Type == FVFileTypeRaw && f.Header.GUID == *NVAR {
   478  		ns, err := NewNVarStore(f.buf[f.DataOffset:])
   479  		if err != nil {
   480  			log.Errorf("error parsing NVAR store in file %v: %v", f.Header.GUID, err)
   481  		}
   482  		// Note that ns is nil if there was an error, so this assign is fine either way.
   483  		f.NVarStore = ns
   484  	}
   485  
   486  	// Parse sections
   487  	if !SupportedFiles[f.Header.Type] {
   488  		return &f, nil
   489  	}
   490  
   491  	for i, offset := 0, f.DataOffset; offset < f.Header.ExtendedSize; i++ {
   492  		s, err := NewSection(f.buf[offset:], i)
   493  		if err != nil {
   494  			return nil, fmt.Errorf("error parsing sections of file %v: %v", f.Header.GUID, err)
   495  		}
   496  		if s.Header.ExtendedSize == 0 {
   497  			return nil, fmt.Errorf("invalid length of section of file %v", f.Header.GUID)
   498  		}
   499  		offset += uint64(s.Header.ExtendedSize)
   500  		// Align to 4 bytes for now. The PI Spec doesn't say what alignment it should be
   501  		// but UEFITool aligns to 4 bytes, and this seems to work on everything I have.
   502  		offset = Align4(offset)
   503  		f.Sections = append(f.Sections, s)
   504  	}
   505  	return &f, nil
   506  }