github.com/linuxboot/fiano@v1.2.0/pkg/visitors/repack.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  	"crypto/sha1"
    10  	"encoding/binary"
    11  	"errors"
    12  	"fmt"
    13  	"unsafe"
    14  
    15  	"github.com/linuxboot/fiano/pkg/compression"
    16  	"github.com/linuxboot/fiano/pkg/uefi"
    17  )
    18  
    19  // Repack repacks a per file compressed FV into a singularly compressed nested FV
    20  type Repack struct {
    21  	// Input
    22  	Predicate func(f uefi.Firmware) bool
    23  
    24  	// Matched File
    25  	FileMatch *uefi.File
    26  }
    27  
    28  // removeFileCompression goes through a newly created, nested firmware volume
    29  // and removes the first level compressed section.
    30  func removeFileCompression(nfv *uefi.FirmwareVolume) error {
    31  	for _, f := range nfv.Files {
    32  		newSectionList := []*uefi.Section{}
    33  		for _, s := range f.Sections {
    34  			if s.Header.Type != uefi.SectionTypeGUIDDefined {
    35  				// This doesn't have a compressed section
    36  				newSectionList = append(newSectionList, s)
    37  				continue
    38  			}
    39  			gdh := s.TypeSpecific.Header.(*uefi.SectionGUIDDefined)
    40  			if guid := gdh.GUID; guid != compression.LZMAGUID && guid != compression.LZMAX86GUID {
    41  				// This doesn't have a compressed section
    42  				newSectionList = append(newSectionList, s)
    43  				continue
    44  			}
    45  			// This is a compressed section we understand, remove it and add children directly to file.
    46  			for _, es := range s.Encapsulated {
    47  				child, ok := es.Value.(*uefi.Section)
    48  				if !ok {
    49  					// This should never happen, die
    50  					return fmt.Errorf("file %v has non sections inside a compressed section, discarding", f.Header.GUID)
    51  				}
    52  				newSectionList = append(newSectionList, child)
    53  			}
    54  		}
    55  		f.Sections = newSectionList
    56  	}
    57  	return nil
    58  }
    59  
    60  func createFirmwareVolume(pfv *uefi.FirmwareVolume) (*uefi.FirmwareVolume, error) {
    61  	nfv := &uefi.FirmwareVolume{} // new Firmware Volume
    62  
    63  	// Create new firmware volume that encloses all the old files.
    64  	// Set up volume header first.
    65  	nfv.FileSystemGUID = *uefi.FFS2
    66  	nfv.Signature = binary.LittleEndian.Uint32([]byte("_FVH"))
    67  	// Copy over attributes from parent. We may want to change this in the future.
    68  	// We don't need the alignment fields since we're nested,
    69  	// so we really only need the lower 16 bits.
    70  	nfv.Attributes = pfv.Attributes & 0x0000FFFF
    71  	nfv.Revision = pfv.Revision
    72  	// Copy and use parent's block size. We assume there's only one nonzero block entry
    73  	nfv.Blocks = make([]uefi.Block, 2)
    74  	if bLen := len(pfv.Blocks); bLen < 1 {
    75  		// According to the spec, should always be two blocks, one null to terminate the block list, but
    76  		// some bioses are not compliant.
    77  		return nil, fmt.Errorf("parent firmware volume block list is too short: need at least 1, got %v",
    78  			bLen)
    79  	}
    80  	if pfv.Blocks[0].Size == 0 {
    81  		return nil, errors.New("first parent firmware volume block has 0 block size, malformed parent block")
    82  	}
    83  	// We use the parent's block size
    84  	nfv.Blocks[0] = uefi.Block{Size: pfv.Blocks[0].Size}
    85  	nfv.Blocks[1] = uefi.Block{}
    86  
    87  	// Calculate the HeaderLen field
    88  	nfv.HeaderLen = uint16(uefi.FirmwareVolumeFixedHeaderSize + int(unsafe.Sizeof(uefi.Block{}))*len(nfv.Blocks))
    89  
    90  	// Create firmware volume metadata
    91  	nfv.DataOffset = uint64(nfv.HeaderLen) // Since we don't have the extended header, HeaderLen is DataOffset.
    92  	nfv.Length = nfv.DataOffset
    93  	nfv.Resizable = true // This is a nested firmware volume, so we can resize it as needed.
    94  
    95  	// Generate binary header.
    96  	header := new(bytes.Buffer)
    97  	err := binary.Write(header, binary.LittleEndian, nfv.FirmwareVolumeFixedHeader)
    98  	if err != nil {
    99  		return nil, fmt.Errorf("unable to construct binary header of nested firmware volume: got %v", err)
   100  	}
   101  	for _, b := range nfv.Blocks {
   102  		err = binary.Write(header, binary.LittleEndian, b)
   103  		if err != nil {
   104  			return nil, fmt.Errorf("unable to construct binary header of nested firmware volume: got %v", err)
   105  		}
   106  
   107  	}
   108  	nfv.SetBuf(header.Bytes())
   109  	// Copy out parent's files
   110  	nfv.Files = append([]*uefi.File{}, pfv.Files...)
   111  
   112  	return nfv, nil
   113  }
   114  
   115  func createVolumeImageFile(cs *uefi.Section) (*uefi.File, error) {
   116  	f := &uefi.File{}
   117  
   118  	f.Header.Type = uefi.FVFileTypeVolumeImage
   119  	f.Header.SetState(uefi.FileStateValid)
   120  
   121  	f.Sections = []*uefi.Section{cs}
   122  
   123  	// Call assemble to populate cs's buffer. then sha1 it for the guid.
   124  	a := &Assemble{}
   125  	if err := a.Run(cs); err != nil {
   126  		return nil, err
   127  	}
   128  	sum := sha1.Sum(cs.Buf())
   129  	copy(f.Header.GUID[:], sum[:]) // GUIDs are smaller, so only 16 bytes are copied
   130  
   131  	return f, nil
   132  }
   133  
   134  func repackFV(fv *uefi.FirmwareVolume) error {
   135  	// fv should be the pointer to the enclosing firmware volume that needs to be repacked.
   136  
   137  	// Create new Firmware Volume.
   138  	// This copies out the parent list of files and assigns it to the new fv
   139  	nfv, err := createFirmwareVolume(fv)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	// Remove per file compression
   145  	if err = removeFileCompression(nfv); err != nil {
   146  		return err
   147  	}
   148  
   149  	// Create new Volume Image section
   150  	vs, err := uefi.CreateSection(uefi.SectionTypeFirmwareVolumeImage, []byte{}, []uefi.Firmware{nfv}, nil)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// Create new compressed section
   156  	cs, err := uefi.CreateSection(uefi.SectionTypeGUIDDefined, []byte{}, []uefi.Firmware{vs}, &compression.LZMAGUID)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	// Create new FV image file
   162  	file, err := createVolumeImageFile(cs)
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	// Set new file as the only firmware file in the original fv.
   168  	fv.Files = append([]*uefi.File{}, file)
   169  	return nil
   170  }
   171  
   172  // Run wraps Visit and performs some setup and teardown tasks.
   173  func (v *Repack) Run(f uefi.Firmware) error {
   174  	// Check that fv being repacked isn't already nested.
   175  	// First run "find" to generate a position to insert into.
   176  	find := Find{
   177  		Predicate: v.Predicate,
   178  	}
   179  	if err := find.Run(f); err != nil {
   180  		return err
   181  	}
   182  
   183  	if numMatch := len(find.Matches); numMatch > 1 {
   184  		return fmt.Errorf("more than one match, only one match allowed! got %v", find.Matches)
   185  	} else if numMatch == 0 {
   186  		return errors.New("no matches found")
   187  	}
   188  
   189  	// Find should only match a file or a firmware volume. If it's an FV, we can
   190  	// edit the FV directly.
   191  	if fvMatch, ok := find.Matches[0].(*uefi.FirmwareVolume); ok {
   192  		// Call repack function.
   193  		return repackFV(fvMatch)
   194  	}
   195  	var ok bool
   196  	if v.FileMatch, ok = find.Matches[0].(*uefi.File); !ok {
   197  		return fmt.Errorf("match was not a file or a firmware volume: got %T, unable to insert", find.Matches[0])
   198  	}
   199  	// Match is a file, apply visitor.
   200  	if err := f.Apply(v); err != nil {
   201  		return err
   202  	}
   203  
   204  	// Assemble the tree just to make sure things are right.
   205  	a := &Assemble{}
   206  	return a.Run(f)
   207  }
   208  
   209  // Visit applies the Repack visitor to any Firmware type.
   210  func (v *Repack) Visit(f uefi.Firmware) error {
   211  	switch f := f.(type) {
   212  	case *uefi.FirmwareVolume:
   213  		for i := 0; i < len(f.Files); i++ {
   214  			if f.Files[i] == v.FileMatch {
   215  				// call repack function.
   216  				return repackFV(f)
   217  			}
   218  		}
   219  	}
   220  
   221  	return f.ApplyChildren(v)
   222  }
   223  
   224  func init() {
   225  	RegisterCLI("repack",
   226  		"repack a per file compressed fv to a nested compressed fv", 1,
   227  		func(args []string) (uefi.Visitor, error) {
   228  			pred, err := FindFileFVPredicate(args[0])
   229  			if err != nil {
   230  				return nil, err
   231  			}
   232  
   233  			// Repack File.
   234  			return &Repack{
   235  				Predicate: pred,
   236  			}, nil
   237  		})
   238  }