github.com/linuxboot/fiano@v1.2.0/pkg/uefi/firmwarevolume.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  	"errors"
    11  	"fmt"
    12  
    13  	"github.com/linuxboot/fiano/pkg/guid"
    14  	"github.com/linuxboot/fiano/pkg/log"
    15  )
    16  
    17  // FirmwareVolume constants
    18  const (
    19  	FirmwareVolumeFixedHeaderSize  = 56
    20  	FirmwareVolumeMinSize          = FirmwareVolumeFixedHeaderSize + 8 // +8 for the null block that terminates the block list
    21  	FirmwareVolumeExtHeaderMinSize = 20
    22  )
    23  
    24  // Valid FV GUIDs
    25  var (
    26  	FFS1      = guid.MustParse("7a9354d9-0468-444a-81ce-0bf617d890df")
    27  	FFS2      = guid.MustParse("8c8ce578-8a3d-4f1c-9935-896185c32dd3")
    28  	FFS3      = guid.MustParse("5473c07a-3dcb-4dca-bd6f-1e9689e7349a")
    29  	EVSA      = guid.MustParse("fff12b8d-7696-4c8b-a985-2747075b4f50")
    30  	NVAR      = guid.MustParse("cef5b9a3-476d-497f-9fdc-e98143e0422c")
    31  	EVSA2     = guid.MustParse("00504624-8a59-4eeb-bd0f-6b36e96128e0")
    32  	AppleBoot = guid.MustParse("04adeead-61ff-4d31-b6ba-64f8bf901f5a")
    33  	PFH1      = guid.MustParse("16b45da2-7d70-4aea-a58d-760e9ecb841d")
    34  	PFH2      = guid.MustParse("e360bdba-c3ce-46be-8f37-b231e5cb9f35")
    35  )
    36  
    37  // FVGUIDs holds common FV type names
    38  var FVGUIDs = map[guid.GUID]string{
    39  	*FFS1:      "FFS1",
    40  	*FFS2:      "FFS2",
    41  	*FFS3:      "FFS3",
    42  	*EVSA:      "NVRAM_EVSA",
    43  	*NVAR:      "NVRAM_NVAR",
    44  	*EVSA2:     "NVRAM_EVSA2",
    45  	*AppleBoot: "APPLE_BOOT",
    46  	*PFH1:      "PFH1",
    47  	*PFH2:      "PFH2",
    48  }
    49  
    50  // These are the FVs we actually try to parse beyond the header
    51  // We don't parse anything except FFS2 and FFS3
    52  var supportedFVs = map[guid.GUID]bool{
    53  	*FFS2: true,
    54  	*FFS3: true,
    55  }
    56  
    57  // Block describes number and size of the firmware volume blocks
    58  type Block struct {
    59  	Count uint32
    60  	Size  uint32
    61  }
    62  
    63  // FirmwareVolumeFixedHeader contains the fixed fields of a firmware volume
    64  // header
    65  type FirmwareVolumeFixedHeader struct {
    66  	_               [16]uint8
    67  	FileSystemGUID  guid.GUID
    68  	Length          uint64
    69  	Signature       uint32
    70  	Attributes      uint32 // UEFI PI spec volume 3.2.1 EFI_FIRMWARE_VOLUME_HEADER
    71  	HeaderLen       uint16
    72  	Checksum        uint16
    73  	ExtHeaderOffset uint16
    74  	Reserved        uint8 `json:"-"`
    75  	Revision        uint8
    76  	// _               [3]uint8
    77  }
    78  
    79  // FirmwareVolumeExtHeader contains the fields of an extended firmware volume
    80  // header
    81  type FirmwareVolumeExtHeader struct {
    82  	FVName        guid.GUID
    83  	ExtHeaderSize uint32
    84  }
    85  
    86  // FirmwareVolume represents a firmware volume. It combines the fixed header and
    87  // a variable list of blocks
    88  type FirmwareVolume struct {
    89  	FirmwareVolumeFixedHeader
    90  	// there must be at least one that is zeroed and indicates the end of the
    91  	// block list
    92  	// We don't really have to care about blocks because we just read everything in.
    93  	Blocks []Block
    94  	FirmwareVolumeExtHeader
    95  	Files []*File `json:",omitempty"`
    96  
    97  	// Variables not in the binary for us to keep track of stuff/print
    98  	DataOffset  uint64
    99  	FVType      string `json:"-"`
   100  	buf         []byte
   101  	FVOffset    uint64 // Byte offset from start of BIOS region.
   102  	ExtractPath string
   103  	Resizable   bool   // Determines if this FV is resizable.
   104  	FreeSpace   uint64 `json:"-"`
   105  }
   106  
   107  // Buf returns the buffer.
   108  // Used mostly for things interacting with the Firmware interface.
   109  func (fv *FirmwareVolume) Buf() []byte {
   110  	return fv.buf
   111  }
   112  
   113  // SetBuf sets the buffer.
   114  // Used mostly for things interacting with the Firmware interface.
   115  func (fv *FirmwareVolume) SetBuf(buf []byte) {
   116  	fv.buf = buf
   117  }
   118  
   119  // Apply calls the visitor on the FirmwareVolume.
   120  func (fv *FirmwareVolume) Apply(v Visitor) error {
   121  	return v.Visit(fv)
   122  }
   123  
   124  // ApplyChildren calls the visitor on each child node of FirmwareVolume.
   125  func (fv *FirmwareVolume) ApplyChildren(v Visitor) error {
   126  	for _, f := range fv.Files {
   127  		if err := f.Apply(v); err != nil {
   128  			return err
   129  		}
   130  	}
   131  	return nil
   132  }
   133  
   134  // GetErasePolarity gets the erase polarity
   135  func (fv *FirmwareVolume) GetErasePolarity() uint8 {
   136  	if fv.Attributes&0x800 != 0 {
   137  		return 0xFF
   138  	}
   139  	return 0
   140  }
   141  
   142  // String creates a string representation for the firmware volume.
   143  func (fv FirmwareVolume) String() string {
   144  	if fv.ExtHeaderOffset != 0 {
   145  		return fv.FVName.String()
   146  	}
   147  	return fv.FileSystemGUID.String()
   148  }
   149  
   150  // InsertFile appends the file to the end of the buffer according to alignment requirements.
   151  func (fv *FirmwareVolume) InsertFile(alignedOffset uint64, fBuf []byte) error {
   152  	// fv.Length should contain the minimum fv size.
   153  	// If Resizable is not set, this is the exact FV size.
   154  	bufLen := uint64(len(fv.buf))
   155  	if bufLen > alignedOffset {
   156  		return fmt.Errorf("aligned offset is in the middle of the FV, offset was %#x, fv buffer was %#x",
   157  			alignedOffset, bufLen)
   158  	}
   159  
   160  	// add padding for alignment
   161  	for i, num := uint64(0), alignedOffset-bufLen; i < num; i++ {
   162  		fv.buf = append(fv.buf, Attributes.ErasePolarity)
   163  	}
   164  
   165  	// Check size
   166  	fLen := uint64(len(fBuf))
   167  	if fLen == 0 {
   168  		return errors.New("trying to insert empty file")
   169  	}
   170  	// Overwrite old data in the firmware volume.
   171  	fv.buf = append(fv.buf, fBuf...)
   172  	return nil
   173  }
   174  
   175  // FindFirmwareVolumeOffset searches for a firmware volume signature, "_FVH"
   176  // using 8-byte alignment. If found, returns the offset from the start of the
   177  // bios region, otherwise returns -1.
   178  func FindFirmwareVolumeOffset(data []byte) int64 {
   179  	if len(data) < 32 {
   180  		return -1
   181  	}
   182  	var (
   183  		offset int64
   184  		fvSig  = []byte("_FVH")
   185  	)
   186  	for offset = 32; offset+4 < int64(len(data)); offset += 8 {
   187  		if bytes.Equal(data[offset:offset+4], fvSig) {
   188  			return offset - 40 // the actual volume starts 40 bytes before the signature
   189  		}
   190  	}
   191  	return -1
   192  }
   193  
   194  // NewFirmwareVolume parses a sequence of bytes and returns a FirmwareVolume
   195  // object, if a valid one is passed, or an error
   196  func NewFirmwareVolume(data []byte, fvOffset uint64, resizable bool) (*FirmwareVolume, error) {
   197  	fv := FirmwareVolume{Resizable: resizable}
   198  
   199  	if len(data) < FirmwareVolumeMinSize {
   200  		return nil, fmt.Errorf("Firmware Volume size too small: expected %v bytes, got %v",
   201  			FirmwareVolumeMinSize,
   202  			len(data),
   203  		)
   204  	}
   205  	reader := bytes.NewReader(data)
   206  	if err := binary.Read(reader, binary.LittleEndian, &fv.FirmwareVolumeFixedHeader); err != nil {
   207  		return nil, err
   208  	}
   209  	// read the block map
   210  	blocks := make([]Block, 0)
   211  	for {
   212  		var block Block
   213  		if err := binary.Read(reader, binary.LittleEndian, &block); err != nil {
   214  			return nil, err
   215  		}
   216  		if block.Count == 0 && block.Size == 0 {
   217  			// found the terminating block
   218  			break
   219  		}
   220  		blocks = append(blocks, block)
   221  	}
   222  	fv.Blocks = blocks
   223  
   224  	// Set the erase polarity
   225  	if err := SetErasePolarity(fv.GetErasePolarity()); err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	// Boundary checks (to return an error instead of panicking)
   230  	if fv.Length > uint64(len(data)) {
   231  		return nil, fmt.Errorf("invalid FV length (is greater than the data length): %d > %d",
   232  			fv.Length, len(data))
   233  	}
   234  
   235  	// Parse the extended header and figure out the start of data
   236  	fv.DataOffset = uint64(fv.HeaderLen)
   237  	if fv.ExtHeaderOffset != 0 &&
   238  		fv.Length >= FirmwareVolumeExtHeaderMinSize &&
   239  		uint64(fv.ExtHeaderOffset) < fv.Length-FirmwareVolumeExtHeaderMinSize {
   240  
   241  		// jump to ext header offset.
   242  		r := bytes.NewReader(data[fv.ExtHeaderOffset:])
   243  		if err := binary.Read(r, binary.LittleEndian, &fv.FirmwareVolumeExtHeader); err != nil {
   244  			return nil, fmt.Errorf("unable to parse FV extended header, got: %v", err)
   245  		}
   246  		// TODO: will the ext header ever end before the regular header? I don't believe so. Add a check?
   247  		fv.DataOffset = uint64(fv.ExtHeaderOffset) + uint64(fv.ExtHeaderSize)
   248  	}
   249  	// Make sure DataOffset is 8 byte aligned at least.
   250  	// TODO: handle alignment field in header.
   251  	fv.DataOffset = Align8(fv.DataOffset)
   252  
   253  	fv.FVType = FVGUIDs[fv.FileSystemGUID]
   254  	fv.FVOffset = fvOffset
   255  
   256  	if ReadOnly {
   257  		fv.buf = data[:fv.Length]
   258  	} else {
   259  		// copy out the buffer.
   260  		newBuf := data[:fv.Length]
   261  		fv.buf = make([]byte, fv.Length)
   262  		copy(fv.buf, newBuf)
   263  	}
   264  
   265  	// Parse the files.
   266  	// TODO: handle fv data alignment.
   267  	// Start from the end of the fv header.
   268  	// Test if the fv type is supported.
   269  	if _, ok := supportedFVs[fv.FileSystemGUID]; !ok {
   270  		log.Warnf("unsupported fv type %v,%v not parsing it", fv.FileSystemGUID.String(), fv.FVType)
   271  		return &fv, nil
   272  	}
   273  	lh := fv.Length - FileHeaderMinLength
   274  	var prevLen uint64
   275  	for offset := fv.DataOffset; offset < lh; offset += prevLen {
   276  		offset = Align8(offset)
   277  		file, err := NewFile(data[offset:])
   278  		if err != nil {
   279  			return nil, fmt.Errorf("unable to construct firmware file at offset %#x into FV: %v", offset, err)
   280  		}
   281  		if file == nil {
   282  			// We've reached free space. Terminate
   283  			fv.FreeSpace = fv.Length - offset
   284  			break
   285  		}
   286  		fv.Files = append(fv.Files, file)
   287  		prevLen = file.Header.ExtendedSize
   288  		if prevLen == 0 {
   289  			return nil, fmt.Errorf("invalid length of file at offset %#x", offset)
   290  		}
   291  	}
   292  	return &fv, nil
   293  }