github.com/linuxboot/fiano@v1.2.0/pkg/uefi/file.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 "encoding/json" 11 "fmt" 12 "strings" 13 14 "github.com/linuxboot/fiano/pkg/guid" 15 "github.com/linuxboot/fiano/pkg/log" 16 ) 17 18 // FVFileType represents the different types possible in an EFI file. 19 type FVFileType uint8 20 21 // UEFI FV File types. 22 const ( 23 FVFileTypeAll FVFileType = iota 24 FVFileTypeRaw 25 FVFileTypeFreeForm 26 FVFileTypeSECCore 27 FVFileTypePEICore 28 FVFileTypeDXECore 29 FVFileTypePEIM 30 FVFileTypeDriver 31 FVFileTypeCombinedPEIMDriver 32 FVFileTypeApplication 33 FVFileTypeSMM 34 FVFileTypeVolumeImage 35 FVFileTypeCombinedSMMDXE 36 FVFileTypeSMMCore 37 FVFileTypeSMMStandalone 38 FVFileTypeSMMCoreStandalone 39 FVFileTypeOEMMin FVFileType = 0xC0 40 FVFileTypeOEMMax FVFileType = 0xDF 41 FVFileTypeDebugMin FVFileType = 0xE0 42 FVFileTypeDebugMax FVFileType = 0xEF 43 FVFileTypePad FVFileType = 0xF0 44 FVFileTypeFFSMin FVFileType = 0xF0 45 FVFileTypeFFSMax FVFileType = 0xFF 46 ) 47 48 // SupportedFiles is a list of files types which will be parsed. File types not 49 // on this list are treated as opaque binary blobs. 50 var SupportedFiles = map[FVFileType]bool{ 51 // These are the file types that we'll actually try to parse sections for. 52 FVFileTypeRaw: false, 53 FVFileTypeFreeForm: true, 54 FVFileTypeSECCore: true, 55 FVFileTypePEICore: true, 56 FVFileTypeDXECore: true, 57 // TODO: Commenting out this line prevents PEI modules from being 58 // decompressed. This solves the problem of PEI being too big when recompressed. 59 //FVFileTypePEIM: true, 60 FVFileTypeDriver: true, 61 FVFileTypeCombinedPEIMDriver: true, 62 FVFileTypeApplication: true, 63 FVFileTypeSMM: true, 64 FVFileTypeVolumeImage: true, 65 FVFileTypeCombinedSMMDXE: true, 66 FVFileTypeSMMCore: true, 67 FVFileTypeSMMStandalone: true, 68 FVFileTypeSMMCoreStandalone: true, 69 } 70 71 var fileTypeNames = map[FVFileType]string{ 72 FVFileTypeRaw: "EFI_FV_FILETYPE_RAW", 73 FVFileTypeFreeForm: "EFI_FV_FILETYPE_FREEFORM", 74 FVFileTypeSECCore: "EFI_FV_FILETYPE_SECURITY_CORE", 75 FVFileTypePEICore: "EFI_FV_FILETYPE_PEI_CORE", 76 FVFileTypeDXECore: "EFI_FV_FILETYPE_DXE_CORE", 77 FVFileTypePEIM: "EFI_FV_FILETYPE_PEIM", 78 FVFileTypeDriver: "EFI_FV_FILETYPE_DRIVER", 79 FVFileTypeCombinedPEIMDriver: "EFI_FV_FILETYPE_COMBINED_PEIM_DRIVER", 80 FVFileTypeApplication: "EFI_FV_FILETYPE_APPLICATION", 81 FVFileTypeSMM: "EFI_FV_FILETYPE_MM", 82 FVFileTypeVolumeImage: "EFI_FV_FILETYPE_FIRMWARE_VOLUME_IMAGE", 83 FVFileTypeCombinedSMMDXE: "EFI_FV_FILETYPE_COMBINED_MM_DXE", 84 FVFileTypeSMMCore: "EFI_FV_FILETYPE_MM_CORE", 85 FVFileTypeSMMStandalone: "EFI_FV_FILETYPE_MM_STANDALONE", 86 FVFileTypeSMMCoreStandalone: "EFI_FV_FILETYPE_MM_CORE_STANDALONE", 87 } 88 89 // NamesToFileType maps from common file type strings to the actual type. 90 var NamesToFileType map[string]FVFileType 91 92 func init() { 93 NamesToFileType = make(map[string]FVFileType) 94 for k, v := range fileTypeNames { 95 NamesToFileType[strings.TrimPrefix(v, "EFI_FV_FILETYPE_")] = k 96 } 97 } 98 99 // String creates a string representation for the file type. 100 func (f FVFileType) String() string { 101 switch { 102 case FVFileTypeOEMMin <= f && f <= FVFileTypeOEMMax: 103 return fmt.Sprintf("EFI_FV_FILETYPE_OEM (%#x)", uint8(f)) 104 case FVFileTypeDebugMin <= f && f <= FVFileTypeDebugMax: 105 return fmt.Sprintf("EFI_FV_FILETYPE_DEBUG (%#x)", uint8(f)) 106 // We use the non-inclusive '<' operator here because pad files belong 107 // to the FFS filetype, but are also their own type. 108 case FVFileTypeFFSMin < f && f <= FVFileTypeFFSMax: 109 return fmt.Sprintf("EFI_FV_FILETYPE_FFS (%#x)", uint8(f)) 110 case f == FVFileTypePad: 111 return "EFI_FV_FILETYPE_FFS_PAD" 112 } 113 if t, ok := fileTypeNames[f]; ok { 114 return t 115 } 116 return "UNKNOWN" 117 } 118 119 // Stock GUIDS 120 var ( 121 ZeroGUID = guid.MustParse("00000000-0000-0000-0000-000000000000") 122 FFGUID = guid.MustParse("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") 123 ) 124 125 // FileAlignments specifies the correct alignments based on the field in the file header. 126 var fileAlignments = []uint64{ 127 // These alignments are not computable, we have to look them up. 128 1, 129 16, 130 128, 131 512, 132 1024, 133 4 * 1024, 134 32 * 1024, 135 64 * 1024, 136 128 * 1024, 137 256 * 1024, 138 512 * 1024, 139 1024 * 1024, 140 2 * 1024 * 1024, 141 4 * 1024 * 1024, 142 8 * 1024 * 1024, 143 16 * 1024 * 1024, 144 } 145 146 const ( 147 // FileHeaderMinLength is the minimum length of a firmware file header. 148 FileHeaderMinLength = 0x18 149 // FileHeaderExtMinLength is the minimum length of an extended firmware file header. 150 FileHeaderExtMinLength = 0x20 151 // EmptyBodyChecksum is the value placed in the File IntegrityCheck field if the body checksum bit isn't set. 152 EmptyBodyChecksum uint8 = 0xAA 153 ) 154 155 // IntegrityCheck holds the two 8 bit checksums for the file header and body separately. 156 type IntegrityCheck struct { 157 Header uint8 158 File uint8 159 } 160 161 type fileAttr uint8 162 163 // FileState (needs to be xored with Attributes.ErasePolarity) 164 type FileState uint8 165 166 // File State Bits 167 const ( 168 FileStateHeaderConstruction FileState = 0x01 169 FileStateHeaderValid FileState = 0x02 170 FileStateDataValid FileState = 0x04 171 FileStateMarkeForUpdate FileState = 0x08 172 FileStateDeleted FileState = 0x10 173 FileStateHeaderInvalid FileState = 0x20 174 175 FileStateValid FileState = FileStateHeaderConstruction | FileStateHeaderValid | FileStateDataValid 176 ) 177 178 type ThreeUint8 [3]uint8 179 180 func (t *ThreeUint8) UnmarshalJSON(b []byte) error { 181 if copy(t[:], b) == 0 { 182 return fmt.Errorf("Cannot unmarshal 3 uint8 from %v\n", b) 183 } 184 return nil 185 } 186 187 func (t *ThreeUint8) MarshalJSON() ([]byte, error) { 188 res := Read3Size(*t) 189 return json.Marshal(res) 190 } 191 192 // FileHeader represents an EFI File header. 193 type FileHeader struct { 194 GUID guid.GUID // This is the GUID of the file. 195 Checksum IntegrityCheck `json:"-"` 196 Type FVFileType 197 Attributes fileAttr 198 Size ThreeUint8 199 State FileState 200 } 201 202 // IsLarge checks if the large file attribute is set. 203 func (a fileAttr) IsLarge() bool { 204 return a&0x01 != 0 205 } 206 207 // GetAlignment returns the byte alignment specified by the file header. 208 func (a fileAttr) GetAlignment() uint64 { 209 alignVal := (a & 0x38) >> 3 210 alignVal |= (a & 0x02) << 2 211 return fileAlignments[alignVal] 212 } 213 214 // Sets the large file attribute. 215 func (a *fileAttr) setLarge(large bool) { 216 if large { 217 *a |= 0x01 218 } else { 219 *a &= 0xFE 220 } 221 } 222 223 // HasChecksum checks if we need to checksum the file body. 224 func (a fileAttr) HasChecksum() bool { 225 return a&0x40 != 0 226 } 227 228 // SetState sets file state respecting erase polarity 229 func (fh *FileHeader) SetState(s FileState) { 230 fh.State = s ^ FileState(Attributes.ErasePolarity) 231 } 232 233 // HeaderLen returns the length of the file header depending on the file size. 234 func (f *File) HeaderLen() uint64 { 235 if f.Header.Attributes.IsLarge() { 236 return FileHeaderExtMinLength 237 } 238 return FileHeaderMinLength 239 } 240 241 // ChecksumHeader returns a checksum of the header. 242 func (f *File) ChecksumHeader() uint8 { 243 fh := f.Header 244 headerSize := FileHeaderMinLength 245 if fh.Attributes.IsLarge() { 246 headerSize = FileHeaderExtMinLength 247 } 248 // Sum over header without State and IntegrityCheck.File. 249 // To do that we just sum over the whole header and subtract. 250 // UEFI PI Spec 3.2.3 EFI_FFS_FILE_HEADER 251 sum := Checksum8(f.buf[:headerSize]) 252 sum -= fh.Checksum.File 253 sum -= uint8(fh.State) 254 return sum 255 } 256 257 // FileHeaderExtended represents an EFI File header with the 258 // large file attribute set. 259 // We also use this as the generic header for all EFI files, regardless of whether 260 // they are actually large. This makes it easier for us to just return one type 261 // All sizes are also copied into the ExtendedSize field so we only have to check once 262 type FileHeaderExtended struct { 263 FileHeader 264 ExtendedSize uint64 `json:"-"` 265 } 266 267 // File represents an EFI File. 268 type File struct { 269 Header FileHeaderExtended 270 Type string 271 272 // a File can contain either Sections or an NVarStore but not both 273 Sections []*Section `json:",omitempty"` 274 NVarStore *NVarStore `json:",omitempty"` 275 276 //Metadata for extraction and recovery 277 buf []byte 278 ExtractPath string 279 DataOffset uint64 280 } 281 282 // Buf returns the buffer. 283 // Used mostly for things interacting with the Firmware interface. 284 func (f *File) Buf() []byte { 285 return f.buf 286 } 287 288 // SetBuf sets the buffer. 289 // Used mostly for things interacting with the Firmware interface. 290 func (f *File) SetBuf(buf []byte) { 291 f.buf = buf 292 } 293 294 // Apply calls the visitor on the File. 295 func (f *File) Apply(v Visitor) error { 296 return v.Visit(f) 297 } 298 299 // ApplyChildren calls the visitor on each child node of File. 300 func (f *File) ApplyChildren(v Visitor) error { 301 if f.NVarStore != nil { 302 if err := f.NVarStore.Apply(v); err != nil { 303 return err 304 } 305 return nil 306 } 307 for _, s := range f.Sections { 308 if err := s.Apply(v); err != nil { 309 return err 310 } 311 } 312 return nil 313 } 314 315 // SetSize sets the size into the File struct. 316 // If resizeFile is true, if the file is too large the file will be enlarged to make space 317 // for the ExtendedHeader 318 func (f *File) SetSize(size uint64, resizeFile bool) { 319 fh := &f.Header 320 // See if we need the extended size 321 // Check if size > 3 bytes size field 322 fh.ExtendedSize = size 323 fh.Attributes.setLarge(false) 324 if fh.ExtendedSize > 0xFFFFFF { 325 // Can't fit, need extended header 326 if resizeFile { 327 // Increase the file size by the additional space needed 328 // for the extended header. 329 fh.ExtendedSize += FileHeaderExtMinLength - FileHeaderMinLength 330 } 331 fh.Attributes.setLarge(true) 332 } 333 // This will set size to 0xFFFFFF if too big. 334 fh.Size = Write3Size(fh.ExtendedSize) 335 } 336 337 // ChecksumAndAssemble takes in the fileData and assembles the file binary 338 func (f *File) ChecksumAndAssemble(fileData []byte) error { 339 // Checksum the header and body, then write out the header. 340 // To checksum the header we write the temporary header to the file buffer first. 341 fh := &f.Header 342 343 header := new(bytes.Buffer) 344 err := binary.Write(header, binary.LittleEndian, fh) 345 if err != nil { 346 return fmt.Errorf("unable to construct binary header of file %v, got %v", 347 fh.GUID, err) 348 } 349 f.buf = header.Bytes() 350 // We need to get rid of whatever it sums to so that the overall sum is zero 351 // Sorry about the name :( 352 fh.Checksum.Header -= f.ChecksumHeader() 353 354 // Checksum the body 355 fh.Checksum.File = EmptyBodyChecksum 356 if fh.Attributes.HasChecksum() { 357 // if the empty checksum had been set to 0 instead of 0xAA 358 // this could have been a bit nicer. BUT NOOOOOOO. 359 fh.Checksum.File = 0 - Checksum8(fileData) 360 } 361 362 // Write out the updated header to the buffer with the new checksums. 363 // Write the extended header only if the large attribute flag is set. 364 header = new(bytes.Buffer) 365 if fh.Attributes.IsLarge() { 366 err = binary.Write(header, binary.LittleEndian, fh) 367 } else { 368 err = binary.Write(header, binary.LittleEndian, fh.FileHeader) 369 } 370 if err != nil { 371 return err 372 } 373 f.buf = header.Bytes() 374 375 f.buf = append(f.buf, fileData...) 376 return nil 377 } 378 379 // CreatePadFile creates an empty pad file in order to align the next file. 380 func CreatePadFile(size uint64) (*File, error) { 381 if size < FileHeaderMinLength { 382 return nil, fmt.Errorf("size too small! min size required is %#x bytes, requested %#x", 383 FileHeaderMinLength, size) 384 } 385 386 f := File{} 387 fh := &f.Header 388 389 // Create empty guid 390 if Attributes.ErasePolarity == 0xFF { 391 fh.GUID = *FFGUID 392 } else if Attributes.ErasePolarity == 0 { 393 fh.GUID = *ZeroGUID 394 } else { 395 return nil, fmt.Errorf("erase polarity not 0x00 or 0xFF, got %#x", Attributes.ErasePolarity) 396 } 397 398 // TODO: I see examples of this where the attributes are just 0 and not dependent on the 399 // erase polarity. Is that right? Check and handle. 400 fh.Attributes = 0 401 402 // Set the size. If the file is too big, we take up more of the padding for the header. 403 // This also sets the large file attribute if file is big. 404 f.SetSize(size, false) 405 fh.Type = FVFileTypePad 406 f.Type = fh.Type.String() 407 408 // Create empty pad filedata based on size 409 var fileData []byte 410 fileData = make([]byte, size-FileHeaderMinLength) 411 if fh.Attributes.IsLarge() { 412 fileData = make([]byte, size-FileHeaderExtMinLength) 413 } 414 // Fill with empty bytes 415 for i, dataLen := 0, len(fileData); i < dataLen; i++ { 416 fileData[i] = Attributes.ErasePolarity 417 } 418 419 fh.SetState(FileStateValid) 420 421 // Everything has been setup. Checksum and create. 422 if err := f.ChecksumAndAssemble(fileData); err != nil { 423 return nil, err 424 } 425 return &f, nil 426 } 427 428 // NewFile parses a sequence of bytes and returns a File 429 // object, if a valid one is passed, or an error. If no error is returned and the File 430 // pointer is nil, it means we've reached the volume free space at the end of the FV. 431 func NewFile(buf []byte) (*File, error) { 432 f := File{} 433 f.DataOffset = FileHeaderMinLength 434 // Read in standard header. 435 r := bytes.NewReader(buf) 436 if err := binary.Read(r, binary.LittleEndian, &f.Header.FileHeader); err != nil { 437 return nil, err 438 } 439 440 // Map type to string. 441 f.Type = f.Header.Type.String() 442 443 // TODO: Check Attribute flag as well. How important is the attribute flag? we already 444 // have FFFFFF in the size 445 if f.Header.Size == [3]uint8{0xFF, 0xFF, 0xFF} { 446 // Extended Header 447 if err := binary.Read(r, binary.LittleEndian, &f.Header.ExtendedSize); err != nil { 448 return nil, err 449 } 450 if f.Header.ExtendedSize == 0xFFFFFFFFFFFFFFFF { 451 // Start of free space 452 // Note: this is not a pad file. Pad files also have valid headers. 453 return nil, nil 454 } 455 f.DataOffset = FileHeaderExtMinLength 456 } else { 457 // Copy small size into big for easier handling. 458 // Damn the 3 byte sizes. 459 f.Header.ExtendedSize = Read3Size(f.Header.Size) 460 } 461 462 if buflen := len(buf); f.Header.ExtendedSize > uint64(buflen) { 463 return nil, fmt.Errorf("File size too big! File with GUID: %v has length %v, but is only %v bytes big", 464 f.Header.GUID, f.Header.ExtendedSize, buflen) 465 } 466 467 if ReadOnly { 468 f.buf = buf[:f.Header.ExtendedSize] 469 } else { 470 // Copy out the buffer. 471 newBuf := buf[:f.Header.ExtendedSize] 472 f.buf = make([]byte, f.Header.ExtendedSize) 473 copy(f.buf, newBuf) 474 } 475 476 // Special case for NVAR Store stored in raw file 477 if f.Header.Type == FVFileTypeRaw && f.Header.GUID == *NVAR { 478 ns, err := NewNVarStore(f.buf[f.DataOffset:]) 479 if err != nil { 480 log.Errorf("error parsing NVAR store in file %v: %v", f.Header.GUID, err) 481 } 482 // Note that ns is nil if there was an error, so this assign is fine either way. 483 f.NVarStore = ns 484 } 485 486 // Parse sections 487 if !SupportedFiles[f.Header.Type] { 488 return &f, nil 489 } 490 491 for i, offset := 0, f.DataOffset; offset < f.Header.ExtendedSize; i++ { 492 s, err := NewSection(f.buf[offset:], i) 493 if err != nil { 494 return nil, fmt.Errorf("error parsing sections of file %v: %v", f.Header.GUID, err) 495 } 496 if s.Header.ExtendedSize == 0 { 497 return nil, fmt.Errorf("invalid length of section of file %v", f.Header.GUID) 498 } 499 offset += uint64(s.Header.ExtendedSize) 500 // Align to 4 bytes for now. The PI Spec doesn't say what alignment it should be 501 // but UEFITool aligns to 4 bytes, and this seems to work on everything I have. 502 offset = Align4(offset) 503 f.Sections = append(f.Sections, s) 504 } 505 return &f, nil 506 }