github.com/linuxboot/fiano@v1.2.0/pkg/amd/psb/biosentries.go (about) 1 // Copyright 2023 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 psb 6 7 import ( 8 "errors" 9 "fmt" 10 "os" 11 12 amd_manifest "github.com/linuxboot/fiano/pkg/amd/manifest" 13 bytes2 "github.com/linuxboot/fiano/pkg/bytes" 14 15 "github.com/jedib0t/go-pretty/v6/table" 16 ) 17 18 // BIOSEntryType defines the type to hold BIOS Entry Type fields 19 type BIOSEntryType uint8 20 21 /* 22 * Nicely output human-readable names for BIOS Entry Types 23 * 24 * This doesn't have all the entries mapped, there are still 25 * several more pages left. It does have all the types 26 * encountered in the firmware images used to test 27 * however. 28 * 29 */ 30 func (_type BIOSEntryType) String() string { 31 switch _type { 32 case 0x05: 33 return "BIOS_PUBLIC_KEY" 34 case 0x07: 35 return "BIOS_RTM_SIGNATURE" 36 case 0x60: 37 return "AGESA_PSP_CUSTOMIZATION_BLOCK" 38 case 0x61: 39 return "AGESA_PSP_OUTPUT_BLOCK" 40 case 0x62: 41 return "BIOS_BINARY" 42 case 0x63: 43 return "AGESA_PSP_OUTPUT_BLOCK_NV_COPY" 44 case 0x64: 45 return "PMU_FIRMWARE_INSTRUCTION_PORTION" 46 case 0x65: 47 return "PMU_FIRMWARE_DATA_PORTION" 48 case 0x66: 49 return "MICROCODE_PATCH" 50 case 0x67: 51 return "CORE_MACHINE_EXCEPTION_DATA" 52 case 0x68: 53 return "BACKUP_AGESA_PSP_CUSTOMIZATION_BLOCK" 54 case 0x69: 55 return "INTERPRETER_BINARY_VIDEO" 56 case 0x6A: 57 return "MP2_FIRMWARE_CONFIG" 58 case 0x6B: 59 return "MAIN_MEMORY" 60 case 0x6C: 61 return "MPM_CONFIG" 62 case 0x70: 63 return "BIOS_DIRECTORY_TABLE_LEVEL_2" 64 } 65 return "UNKNOWN" 66 67 } 68 69 func getBIOSTable(pspFirmware *amd_manifest.PSPFirmware, biosLevel uint) (*amd_manifest.BIOSDirectoryTable, error) { 70 switch biosLevel { 71 case 1: 72 return pspFirmware.BIOSDirectoryLevel1, nil 73 case 2: 74 return pspFirmware.BIOSDirectoryLevel2, nil 75 } 76 return nil, fmt.Errorf("cannot extract key database, invalid BIOS Directory Level requested: %d", biosLevel) 77 } 78 79 // OutputBIOSEntries outputs the BIOS entries in an ASCII table format 80 func OutputBIOSEntries(amdFw *amd_manifest.AMDFirmware) error { 81 biosDirectoryLevel1Table, err := getBIOSTable(amdFw.PSPFirmware(), 1) 82 if err != nil { 83 return fmt.Errorf("unable to retrieve BIOS Directory Level 1 Entries: %w", err) 84 } 85 86 biosDirectoryLevel2Table, err := getBIOSTable(amdFw.PSPFirmware(), 2) 87 if err != nil { 88 return fmt.Errorf("unable to retrieve BIOS Directory Level 2 Entries: %w", err) 89 } 90 91 biosDirectories := []amd_manifest.BIOSDirectoryTable{*biosDirectoryLevel1Table, *biosDirectoryLevel2Table} 92 93 for idx, directory := range biosDirectories { 94 // BIOS Header 95 h := table.NewWriter() 96 h.SetOutputMirror(os.Stdout) 97 h.SetTitle("BIOS Directory Level %d Header", idx+1) 98 biosCookie := fmt.Sprintf("0x%x", directory.BIOSCookie) 99 biosChecksum := directory.Checksum 100 biosTotalEntries := directory.TotalEntries 101 h.AppendHeader(table.Row{"BIOS Cookie", "Checksum", "Total Entries"}) 102 h.AppendRow([]interface{}{biosCookie, biosChecksum, biosTotalEntries}) 103 h.Render() 104 105 // BIOS Entries 106 t := table.NewWriter() 107 t.SetOutputMirror(os.Stdout) 108 t.SetTitle("BIOS Directory Level %d", idx+1) 109 t.AppendHeader(table.Row{ 110 "Type", 111 "Type Hex", 112 "RegionType", 113 "ResetImage", 114 "CopyImage", 115 "ReadOnly", 116 "Compressed", 117 "Instance", 118 "Subprogram", 119 "RomID", 120 "Size", 121 "Source Address", 122 "Destination Address", 123 }) 124 for _, entry := range directory.Entries { 125 entryType := BIOSEntryType(entry.Type) 126 entryTypeHex := fmt.Sprintf("0x%-3x", entry.Type) 127 entryRegionType := fmt.Sprintf("0x%-8x", entry.RegionType) 128 entryResetImage := fmt.Sprintf("%-10v", entry.ResetImage) 129 entryCopyImage := fmt.Sprintf("%-9v", entry.CopyImage) 130 entryReadOnly := fmt.Sprintf("%-8v", entry.ReadOnly) 131 entryCompressed := fmt.Sprintf("%-10v", entry.Compressed) 132 entryInstance := fmt.Sprintf("0x%-6x", entry.Instance) 133 entrySubprogram := fmt.Sprintf("0x%-8x", entry.Subprogram) 134 entryRomID := fmt.Sprintf("0x%-3x", entry.RomID) 135 entrySize := fmt.Sprintf("%-6d", entry.Size) 136 entrySourceAddress := fmt.Sprintf("0x%-11x", entry.SourceAddress) 137 entryDestinationAddress := fmt.Sprintf("0x%-18x", entry.DestinationAddress) 138 t.AppendRow([]interface{}{ 139 entryType, 140 entryTypeHex, 141 entryRegionType, 142 entryResetImage, 143 entryCopyImage, 144 entryReadOnly, 145 entryCompressed, 146 entryInstance, 147 entrySubprogram, 148 entryRomID, 149 entrySize, 150 entrySourceAddress, 151 entryDestinationAddress, 152 }) 153 } 154 t.Render() 155 } 156 return nil 157 } 158 159 // ValidateRTM validates signature of RTM volume and BIOS directory table concatenated 160 func ValidateRTM(amdFw *amd_manifest.AMDFirmware, biosLevel uint) (*SignatureValidationResult, error) { 161 pspFw := amdFw.PSPFirmware() 162 163 // Get the byte range we'll need on the BIOS depending on the level 164 var biosDirectoryRange bytes2.Range 165 var directory DirectoryType 166 switch biosLevel { 167 case 1: 168 biosDirectoryRange = pspFw.BIOSDirectoryLevel1Range 169 directory = BIOSDirectoryLevel1 170 case 2: 171 biosDirectoryRange = pspFw.BIOSDirectoryLevel2Range 172 directory = BIOSDirectoryLevel2 173 default: 174 return nil, fmt.Errorf("cannot extract raw BIOS entry, invalid BIOS Directory Level requested: %d", biosLevel) 175 } 176 177 // extract RTM Volume and signature 178 rtmVolume, err := ExtractBIOSEntry(amdFw, biosLevel, BIOSRTMVolumeEntry, 0) 179 if err != nil { 180 return nil, fmt.Errorf("could not extract BIOS entry corresponding to RTM volume (%x): %w", BIOSRTMVolumeEntry, err) 181 } 182 183 oemKey, err := GetPSBSignBIOSKey(amdFw, biosLevel) 184 if err != nil { 185 return nil, err 186 } 187 188 rtmVolumeSignature, err := ExtractBIOSEntry(amdFw, biosLevel, BIOSRTMSignatureEntry, 0) 189 if err != nil { 190 return nil, fmt.Errorf("could not extract BIOS entry corresponding to RTM volume signature (%x): %w", BIOSRTMSignatureEntry, err) 191 } 192 193 // signature of RTM volume is calculated over the concatenation of RTM volume itself and 194 // BIOS directory table 195 firmwareBytes := amdFw.Firmware().ImageBytes() 196 197 biosDirectoryStart := biosDirectoryRange.Offset 198 biosDirectoryEnd := biosDirectoryStart + biosDirectoryRange.Length 199 200 if err := checkBoundaries(biosDirectoryStart, biosDirectoryEnd, firmwareBytes); err != nil { 201 return nil, newErrInvalidFormatWithItem(newDirectoryItem(directory), 202 fmt.Errorf("could not extract BIOS Level %d directory, boundary check error: %w", biosLevel, err)) 203 } 204 205 /** 206 * This is needed due to the fact in the Level 2 BIOS Directory Table, 207 * instead of RTM Volume + Level 2 Header for the signed data, it's actually 208 * RTM Volume + Level 1 Header + Level 2 Header 209 */ 210 if biosLevel == 2 { 211 biosDirectoryLevel1Start := pspFw.BIOSDirectoryLevel1Range.Offset 212 biosDirectoryLevel1End := biosDirectoryLevel1Start + pspFw.BIOSDirectoryLevel1Range.Length 213 214 if err := checkBoundaries(biosDirectoryLevel1Start, biosDirectoryLevel1End, firmwareBytes); err != nil { 215 return nil, newErrInvalidFormatWithItem(newDirectoryItem(BIOSDirectoryLevel1), 216 fmt.Errorf("could not extract BIOS Level 1 directory, boundary check error: %w", err)) 217 } 218 219 biosDirectoryTableBytes := firmwareBytes[biosDirectoryLevel1Start:biosDirectoryLevel1End] 220 rtmVolume = append(rtmVolume, biosDirectoryTableBytes...) 221 } 222 223 biosDirectoryTableBytes := firmwareBytes[biosDirectoryStart:biosDirectoryEnd] 224 rtmVolume = append(rtmVolume, biosDirectoryTableBytes...) 225 226 _, err = NewSignedBlob(reverse(rtmVolumeSignature), rtmVolume, oemKey) 227 return &SignatureValidationResult{signedElement: "RTM Volume concatenated with BIOS Directory", signingKey: oemKey, err: err}, nil 228 } 229 230 // GetPSBSignBIOSKey returns and OEM Key that is used to sign BIOS during PSB enabled 231 func GetPSBSignBIOSKey(amdFw *amd_manifest.AMDFirmware, biosLevel uint) (*Key, error) { 232 keySet, err := GetKeys(amdFw, biosLevel) 233 if err != nil { 234 return nil, fmt.Errorf("could not extract key from firmware: %w", err) 235 } 236 237 oemKeySet, err := keySet.KeysetFromType(OEMKey) 238 if err != nil { 239 return nil, addFirmwareItemToError(err, newBIOSDirectoryEntryItem(uint8(biosLevel), OEMSigningKeyEntry, 0)) 240 } 241 oemKeys := oemKeySet.AllKeyIDs() 242 switch len(oemKeys) { 243 case 0: 244 return nil, newErrNotFound(newBIOSDirectoryEntryItem(uint8(biosLevel), OEMSigningKeyEntry, 0)) 245 case 1: 246 // should be only 1 OEM key 247 default: 248 return nil, newErrInvalidFormatWithItem( 249 newBIOSDirectoryEntryItem(uint8(biosLevel), OEMSigningKeyEntry, 0), 250 fmt.Errorf("multiple '%d' OEM keys", len(oemKeys)), 251 ) 252 } 253 254 oemKey := keySet.GetKey(oemKeys[0]) 255 if oemKey.data.KeyUsageFlag != PSBSignBIOS { 256 return nil, newErrInvalidFormatWithItem( 257 newBIOSDirectoryEntryItem(uint8(biosLevel), OEMSigningKeyEntry, 0), 258 fmt.Errorf("incorrect key usage '%d', expected: '%d'", oemKey.data.KeyUsageFlag, PSBSignBIOS), 259 ) 260 } 261 return oemKey, nil 262 } 263 264 // IsPSBEnabled checks if firmware has PSB enabled 265 func IsPSBEnabled(amdFw *amd_manifest.AMDFirmware) (bool, error) { 266 checkPSBEnabled := func(biosLevel uint) (bool, error) { 267 _, err := GetBIOSEntry(amdFw.PSPFirmware(), 2, OEMSigningKeyEntry, 0) 268 if err == nil { 269 return true, nil 270 } 271 if errors.As(err, &ErrNotFound{}) { 272 return false, nil 273 } 274 return false, err 275 } 276 277 if amdFw.PSPFirmware().BIOSDirectoryLevel2 != nil { 278 return checkPSBEnabled(2) 279 } 280 if amdFw.PSPFirmware().BIOSDirectoryLevel1 != nil { 281 return checkPSBEnabled(1) 282 } 283 // Can happen in pre-ODM firmware: no bios directories -> no PSB :) 284 return false, nil 285 }