github.com/linuxboot/fiano@v1.2.0/pkg/visitors/createfv.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  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"strconv"
    12  	"unsafe"
    13  
    14  	"github.com/linuxboot/fiano/pkg/guid"
    15  	"github.com/linuxboot/fiano/pkg/uefi"
    16  )
    17  
    18  // CreateFV creates a firmware volume at given offset
    19  type CreateFV struct {
    20  	AbsOffset uint64
    21  	Size      uint64
    22  	Name      guid.GUID
    23  
    24  	found bool
    25  }
    26  
    27  // Run wraps Visit and performs some setup and teardown tasks.
    28  func (v *CreateFV) Run(f uefi.Firmware) error {
    29  	err := f.Apply(v)
    30  	if err != nil {
    31  		return err
    32  	}
    33  	if !v.found {
    34  		return fmt.Errorf("cannot create FV at %#x (+%#x) no BIOS region found", v.AbsOffset, v.Size)
    35  	}
    36  	return nil
    37  }
    38  
    39  // Visit applies the CreateFV visitor to any Firmware type.
    40  func (v *CreateFV) Visit(f uefi.Firmware) error {
    41  	if v.found {
    42  		return nil
    43  	}
    44  	var offset, end uint64
    45  	switch f := f.(type) {
    46  	case *uefi.BIOSRegion:
    47  		if f.FRegion != nil {
    48  			offset = uint64(f.FRegion.BaseOffset())
    49  			end = uint64(f.FRegion.EndOffset())
    50  		} else {
    51  			end = f.Length
    52  		}
    53  		if v.AbsOffset < offset {
    54  			return fmt.Errorf("cannot create FV at %#x, BIOS region starts at %#x", v.AbsOffset, offset)
    55  		}
    56  		if v.AbsOffset+v.Size > end {
    57  			return fmt.Errorf("cannot create FV ending at %#x (%#x + %#x), BIOS region ends at %#x", v.AbsOffset+v.Size, v.AbsOffset, v.Size, end)
    58  		}
    59  
    60  		// Manually visit children, we want the index
    61  		idx := -1
    62  		var bp *uefi.BIOSPadding
    63  	L:
    64  		for i, e := range f.Elements {
    65  			switch f := e.Value.(type) {
    66  			case *uefi.BIOSPadding:
    67  				bp = f
    68  				bpOffset := offset + f.Offset
    69  				bpEnd := bpOffset + uint64(len(f.Buf()))
    70  				// Warning: This won't work if for whatever reason we have two biospads that are contiguous
    71  				// TODO: join contiguous before !
    72  				if v.AbsOffset >= bpOffset && v.AbsOffset+v.Size <= bpEnd {
    73  
    74  					idx = i
    75  					break L
    76  				}
    77  			}
    78  		}
    79  
    80  		if idx == -1 {
    81  			return fmt.Errorf("cannot create FV at %#x (+%#x) no matching BIOS Pad found", v.AbsOffset, v.Size)
    82  		}
    83  		v.found = true
    84  		fv, err := createEmptyFirmwareVolume(v.AbsOffset-offset, v.Size, &v.Name)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		return insertFVinBP(f, v.AbsOffset-offset, bp, idx, fv)
    89  	}
    90  
    91  	return f.ApplyChildren(v)
    92  }
    93  
    94  func insertFVinBP(br *uefi.BIOSRegion, offset uint64, bp *uefi.BIOSPadding, idx int, fv *uefi.FirmwareVolume) error {
    95  	// Copy the Elements before the modified BIOS Pad
    96  	newElements := make([]*uefi.TypedFirmware, idx, len(br.Elements))
    97  	copy(newElements, br.Elements[:idx])
    98  
    99  	bpBuf := bp.Buf()
   100  	// keep part of the BIOS Pad before the new fv if needed
   101  	if bp.Offset < offset {
   102  		hbp, err := uefi.NewBIOSPadding(bpBuf[:offset-bp.Offset], bp.Offset)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		newElements = append(newElements, uefi.MakeTyped(hbp))
   107  	}
   108  
   109  	// Add the FV
   110  	newElements = append(newElements, uefi.MakeTyped(fv))
   111  
   112  	// keep part of the BIOS Pad after the new fv if needed
   113  	tbpStart := offset - bp.Offset + fv.Length
   114  	if tbpStart < uint64(len(bpBuf)) {
   115  		tbp, err := uefi.NewBIOSPadding(bpBuf[tbpStart:], offset+fv.Length)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		newElements = append(newElements, uefi.MakeTyped(tbp))
   120  	}
   121  
   122  	// Keep the remaining Elements in the BIOS Region
   123  	if idx+1 < len(br.Elements) {
   124  		newElements = append(newElements, br.Elements[idx+1:]...)
   125  	}
   126  	br.Elements = newElements
   127  	return nil
   128  }
   129  
   130  func createEmptyFirmwareVolume(fvOffset, size uint64, name *guid.GUID) (*uefi.FirmwareVolume, error) {
   131  	// TODO: can this be refactored with the code in repack.go and assemble.go ?
   132  	fv := &uefi.FirmwareVolume{} // new Firmware Volume
   133  	// Set up volume header first.
   134  	fv.FileSystemGUID = *uefi.FFS2
   135  	fv.Signature = binary.LittleEndian.Uint32([]byte("_FVH"))
   136  	// TODO: retrieve all details from (all) other fv in BIOS Region
   137  	fv.Attributes = 0x0004FEFF
   138  	fv.Revision = 2
   139  	// Create Blocks
   140  	fv.Blocks = make([]uefi.Block, 2)
   141  	fv.Blocks[0] = uefi.Block{Size: 4096, Count: uint32(size / 4096)}
   142  	fv.Blocks[1] = uefi.Block{}
   143  	// Calculate the HeaderLen field
   144  	fv.HeaderLen = uint16(uefi.FirmwareVolumeFixedHeaderSize + int(unsafe.Sizeof(uefi.Block{}))*len(fv.Blocks))
   145  
   146  	fv.DataOffset = uint64(fv.HeaderLen) // unless we add the extended header
   147  	fv.Length = size
   148  
   149  	if name != nil {
   150  		// TODO: compute the extended header offset (especially if more than 2 Blocks, this offset will be wrong)
   151  		fv.ExtHeaderOffset = 0x60
   152  		fv.FVName = *name
   153  		fv.ExtHeaderSize = uefi.FirmwareVolumeExtHeaderMinSize
   154  	}
   155  	// Generate binary header.
   156  	header := new(bytes.Buffer)
   157  	err := binary.Write(header, binary.LittleEndian, fv.FirmwareVolumeFixedHeader)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("unable to construct binary header of new firmware volume: got %v", err)
   160  	}
   161  	for _, b := range fv.Blocks {
   162  		err = binary.Write(header, binary.LittleEndian, b)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("unable to construct binary header of new firmware volume: got %v", err)
   165  		}
   166  
   167  	}
   168  	buf := header.Bytes()
   169  
   170  	// Checksum the header
   171  	sum, err := uefi.Checksum16(buf[:fv.HeaderLen])
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	newSum := 0 - sum
   176  	binary.LittleEndian.PutUint16(buf[50:], newSum)
   177  
   178  	// Store the header buffer in
   179  	fv.SetBuf(buf)
   180  
   181  	if name != nil {
   182  		// Build the ExtHeader
   183  		extHeader := new(bytes.Buffer)
   184  		err = binary.Write(extHeader, binary.LittleEndian, fv.FirmwareVolumeExtHeader)
   185  		if err != nil {
   186  			return nil, fmt.Errorf("unable to construct binary extended header of new firmware volume: got %v", err)
   187  		}
   188  
   189  		// The extended header in encapsulated in a PadFile just after the header.
   190  		// The UEFI PI Specification is not clear on that point, however the implementation in tianocore GenFv tools is clear:
   191  		// At [1] `GenerateFvImage` gives the extended header as an argument to `AddPadFile` implemented at [2].
   192  		// [1]: https://github.com/tianocore/edk2/blob/master/BaseTools/Source/C/GenFv/GenFvInternalLib.c#L2772
   193  		// [2]: https://github.com/tianocore/edk2/blob/master/BaseTools/Source/C/GenFv/GenFvInternalLib.c#L563
   194  		extHeaderFile, err := uefi.CreatePadFile(uint64(uefi.FileHeaderMinLength + fv.ExtHeaderSize))
   195  		if err != nil {
   196  			return nil, fmt.Errorf("building ExtHeader %v", err)
   197  		}
   198  		if err := extHeaderFile.ChecksumAndAssemble(extHeader.Bytes()); err != nil {
   199  			return nil, fmt.Errorf("building ExtHeader %v", err)
   200  		}
   201  
   202  		// Add the extended header in a Padfile just after the header.
   203  		extHeaderFileBuf := extHeaderFile.Buf()
   204  		if err = fv.InsertFile(fv.DataOffset, extHeaderFileBuf); err != nil {
   205  			return nil, fmt.Errorf("adding ExtHeader %v", err)
   206  		}
   207  		fv.DataOffset += uint64(len(extHeaderFileBuf))
   208  	}
   209  	// Add empty space
   210  	extLen := fv.Length - fv.DataOffset
   211  	emptyBuf := make([]byte, extLen)
   212  	uefi.Erase(emptyBuf, uefi.Attributes.ErasePolarity)
   213  
   214  	// Store the buffer in
   215  	fv.SetBuf(append(fv.Buf(), emptyBuf...))
   216  
   217  	// Make sure DataOffset is 8 byte aligned at least.
   218  	fv.DataOffset = uefi.Align8(fv.DataOffset)
   219  
   220  	// Internal fields
   221  	fv.FVOffset = fvOffset
   222  	fv.FreeSpace = fv.Length - fv.DataOffset
   223  
   224  	return fv, nil
   225  }
   226  
   227  func init() {
   228  	RegisterCLI("create-fv", "creates a FV given an offset, size and volume GUID (can only replace a BIOS Padding)", 3, func(args []string) (uefi.Visitor, error) {
   229  		offset, err := strconv.ParseUint(args[0], 0, 64)
   230  		if err != nil {
   231  			return nil, err
   232  		}
   233  		size, err := strconv.ParseUint(args[1], 0, 64)
   234  		if err != nil {
   235  			return nil, err
   236  		}
   237  		name, err := guid.Parse(args[2])
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		return &CreateFV{
   242  			AbsOffset: offset,
   243  			Size:      size,
   244  			Name:      *name,
   245  		}, nil
   246  	})
   247  }