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 }