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

     1  // Copyright 2019 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  	"fmt"
     9  
    10  	"github.com/linuxboot/fiano/pkg/log"
    11  	"github.com/linuxboot/fiano/pkg/uefi"
    12  )
    13  
    14  // TightenME tighten ME's belt to give more room for LinuxBoot
    15  // This changes the ME Region Limit to fit the actual content as described in the ME partitions. Also update the BIOS Region Base to start just after the ME Region
    16  type TightenME struct {
    17  	fd  *uefi.FlashDescriptor
    18  	mer *uefi.MERegion
    19  	br  *uefi.BIOSRegion
    20  }
    21  
    22  // Run wraps Visit and performs some setup and teardown tasks.
    23  func (v *TightenME) Run(f uefi.Firmware) error {
    24  	err := f.Apply(v)
    25  	if err != nil {
    26  		return fmt.Errorf("error looking for IFD, ME and BIOS regions: %v", err)
    27  	}
    28  
    29  	return v.process()
    30  }
    31  
    32  // Visit applies the TightenME visitor to any Firmware type.
    33  func (v *TightenME) Visit(f uefi.Firmware) error {
    34  	switch f := f.(type) {
    35  	case *uefi.FlashDescriptor:
    36  		v.fd = f
    37  		return nil
    38  	case *uefi.MERegion:
    39  		v.mer = f
    40  		return nil
    41  	case *uefi.BIOSRegion:
    42  		v.br = f
    43  		return nil
    44  	default:
    45  		return f.ApplyChildren(v)
    46  	}
    47  }
    48  
    49  func (v *TightenME) process() error {
    50  	// Ensuring IFD exists also ensure FRegion are populated in Regions
    51  	if v.fd == nil {
    52  		return fmt.Errorf("no IFD found")
    53  	}
    54  	if v.mer == nil {
    55  		return fmt.Errorf("no ME region found")
    56  	}
    57  	if v.br == nil {
    58  		return fmt.Errorf("no BIOS region found")
    59  	}
    60  	// TODO: We might be able to relax this restriction if there is a region
    61  	// in between that can be shifted, but this needs more tests...
    62  	if v.mer.FRegion.EndOffset() != v.br.FRegion.BaseOffset() {
    63  		return fmt.Errorf("ME and BIOS regions are not contiguous: ME end at %#x BIOS starts at %#x", v.mer.FRegion.EndOffset(), v.br.FRegion.BaseOffset())
    64  
    65  	}
    66  	// Compute the new limit
    67  	updateOffset := uint64(v.mer.FRegion.BaseOffset()) + v.mer.FreeSpaceOffset
    68  	updateBase := (updateOffset + uefi.RegionBlockSize - 1) / uefi.RegionBlockSize
    69  	// align the new absolute offset to uefi.RegionBlockSize
    70  	updateOffset = updateBase * uefi.RegionBlockSize
    71  	bufOffset := updateOffset - uint64(v.mer.FRegion.BaseOffset())
    72  	// check the zone if empty
    73  	buf := v.mer.Buf()
    74  	if !uefi.IsErased(buf[bufOffset:], uefi.Attributes.ErasePolarity) {
    75  		return fmt.Errorf("ME unused space in not erased as expected")
    76  	}
    77  	// Shrink ME Region
    78  	v.mer.FRegion.Limit = uint16(updateBase - 1)
    79  	v.mer.SetBuf(buf[:bufOffset])
    80  	// Expand BIOS Region
    81  	offsetShift := uint64(v.br.FRegion.BaseOffset()) - updateOffset
    82  	v.br.FRegion.Base = uint16(updateBase)
    83  	v.br.Length += offsetShift
    84  	if v.br.Length > 16*1024*1024 {
    85  		log.Warnf("new BIOS Regions length %d (%#x) exceed 16MiB limit", v.br.Length, v.br.Length)
    86  	}
    87  	// update elements offsets
    88  	for i, e := range v.br.Elements {
    89  		switch f := e.Value.(type) {
    90  		case *uefi.FirmwareVolume:
    91  			f.FVOffset += offsetShift
    92  		case *uefi.BIOSPadding:
    93  			f.Offset += offsetShift
    94  		default:
    95  			return fmt.Errorf("unexpected Element at %d: %s", i, e.Type)
    96  		}
    97  	}
    98  	// insert BIOSPad
    99  	bp, err := uefi.NewBIOSPadding(buf[bufOffset:], 0)
   100  	if err != nil {
   101  		return fmt.Errorf("could not create BIOS Padding: %v", err)
   102  	}
   103  	v.br.Elements = append([]*uefi.TypedFirmware{uefi.MakeTyped(bp)}, v.br.Elements...)
   104  	// Assemble will regenerate IFD so regions will be updated in the image
   105  
   106  	return nil
   107  }
   108  
   109  func init() {
   110  	RegisterCLI("tighten_me", "tighten ME's belt to give more room for LinuxBoot", 0, func(args []string) (uefi.Visitor, error) {
   111  		return &TightenME{}, nil
   112  	})
   113  }