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 }