github.com/linuxboot/fiano@v1.2.0/pkg/amd/psb/entries.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 "fmt" 9 "io" 10 "sort" 11 "strings" 12 13 amd_manifest "github.com/linuxboot/fiano/pkg/amd/manifest" 14 bytes2 "github.com/linuxboot/fiano/pkg/bytes" 15 ) 16 17 // DirectoryType denotes specific firmware table in PSP firmware 18 type DirectoryType uint8 19 20 const ( 21 // PSPDirectoryLevel1 represents PSP directory table level 1 22 PSPDirectoryLevel1 DirectoryType = iota 23 24 // PSPDirectoryLevel2 represents PSP directory table level 2 25 PSPDirectoryLevel2 26 27 // BIOSDirectoryLevel1 represents BIOS directory table level 1 28 BIOSDirectoryLevel1 29 30 // BIOSDirectoryLevel2 represents BIOS directory table level 2 31 BIOSDirectoryLevel2 32 ) 33 34 var allDirectoryTypes = []DirectoryType{ 35 PSPDirectoryLevel1, 36 PSPDirectoryLevel2, 37 BIOSDirectoryLevel1, 38 BIOSDirectoryLevel2, 39 } 40 41 // AllDirectoryTypes returns all directory types 42 func AllDirectoryTypes() []DirectoryType { 43 result := make([]DirectoryType, len(allDirectoryTypes)) 44 copy(result, allDirectoryTypes) 45 return result 46 } 47 48 // ShortName returns a short name of directory type 49 func (t DirectoryType) ShortName() string { 50 switch t { 51 case PSPDirectoryLevel1: 52 return "PSPDirectoryLevel1" 53 case PSPDirectoryLevel2: 54 return "PSPDirectoryLevel2" 55 case BIOSDirectoryLevel1: 56 return "BIOSDirectoryLevel1" 57 case BIOSDirectoryLevel2: 58 return "BIOSDirectoryLevel2" 59 } 60 return fmt.Sprintf("Unknown firmware directory type: '%d'", t) 61 } 62 63 func (t DirectoryType) String() string { 64 switch t { 65 case PSPDirectoryLevel1: 66 return "PSP directory level 1" 67 case PSPDirectoryLevel2: 68 return "PSP directory level 2" 69 case BIOSDirectoryLevel1: 70 return "BIOS directory level 1" 71 case BIOSDirectoryLevel2: 72 return "BIOS directory level 2" 73 } 74 return fmt.Sprintf("Unknown PSP firmware directory type: '%d'", t) 75 } 76 77 // Level returns the directory level 78 func (t DirectoryType) Level() uint { 79 switch t { 80 case PSPDirectoryLevel1: 81 return 1 82 case PSPDirectoryLevel2: 83 return 2 84 case BIOSDirectoryLevel1: 85 return 1 86 case BIOSDirectoryLevel2: 87 return 2 88 } 89 panic(fmt.Sprintf("Not supported directory type: %d", t)) 90 } 91 92 // DirectoryTypeFromString converts a string into DirectoryType 93 func DirectoryTypeFromString(in string) (DirectoryType, error) { 94 for _, dt := range allDirectoryTypes { 95 if strings.EqualFold(dt.ShortName(), in) { 96 return dt, nil 97 } 98 } 99 return 0, fmt.Errorf("unknown directory type: %s", in) 100 } 101 102 // GetPSPDirectoryOfLevel returns the PSP directory of a certain level 103 func GetPSPDirectoryOfLevel(level uint) (DirectoryType, error) { 104 switch level { 105 case 1: 106 return PSPDirectoryLevel1, nil 107 case 2: 108 return PSPDirectoryLevel2, nil 109 } 110 return 0, fmt.Errorf("invalid PSP directory level: %d", level) 111 } 112 113 // GetBIOSDirectoryOfLevel returns the BIOS directory of a certain level 114 func GetBIOSDirectoryOfLevel(level uint) (DirectoryType, error) { 115 switch level { 116 case 1: 117 return BIOSDirectoryLevel1, nil 118 case 2: 119 return BIOSDirectoryLevel2, nil 120 } 121 return 0, fmt.Errorf("invalid BIOS directory level: %d", level) 122 } 123 124 // GetBIOSEntries returns all entries of a certain type from BIOS directory sorted by instance 125 func GetBIOSEntries( 126 pspFirmware *amd_manifest.PSPFirmware, 127 biosLevel uint, 128 entryID amd_manifest.BIOSDirectoryTableEntryType, 129 ) ([]amd_manifest.BIOSDirectoryTableEntry, error) { 130 biosTable, err := getBIOSTable(pspFirmware, biosLevel) 131 if err != nil { 132 return nil, err 133 } 134 135 if biosTable == nil { 136 directory, err := GetBIOSDirectoryOfLevel(biosLevel) 137 if err != nil { 138 return nil, fmt.Errorf("unknown bios directory of level %d", biosLevel) 139 } 140 return nil, newErrNotFound(newDirectoryItem(directory)) 141 } 142 143 var biosTableEntries []amd_manifest.BIOSDirectoryTableEntry 144 for _, entry := range biosTable.Entries { 145 if entry.Type == entryID { 146 biosTableEntries = append(biosTableEntries, entry) 147 } 148 } 149 150 sort.Slice(biosTableEntries, func(i, j int) bool { 151 return biosTableEntries[i].Instance < biosTableEntries[j].Instance 152 }) 153 return biosTableEntries, nil 154 } 155 156 // GetBIOSEntry returns a singe entry of a certain type from BIOS directory, returns error if multiple entries are found 157 func GetBIOSEntry( 158 pspFirmware *amd_manifest.PSPFirmware, 159 biosLevel uint, 160 entryID amd_manifest.BIOSDirectoryTableEntryType, 161 instance uint8, 162 ) (*amd_manifest.BIOSDirectoryTableEntry, error) { 163 entries, err := GetBIOSEntries(pspFirmware, biosLevel, entryID) 164 if err != nil { 165 return nil, err 166 } 167 168 var result *amd_manifest.BIOSDirectoryTableEntry 169 for idx := range entries { 170 if entries[idx].Instance == uint8(instance) { 171 if result != nil { 172 directory, err := GetBIOSDirectoryOfLevel(biosLevel) 173 if err != nil { 174 return nil, fmt.Errorf("unknown bios directory of level %d", biosLevel) 175 } 176 return nil, newErrInvalidFormatWithItem( 177 newDirectoryItem(directory), 178 fmt.Errorf("multiple entriers %x of instance %d are found in BIOS directory level %d", entryID, instance, biosLevel), 179 ) 180 } 181 result = &entries[idx] 182 } 183 } 184 185 if result == nil { 186 return nil, newErrNotFound(newBIOSDirectoryEntryItem(uint8(biosLevel), entryID, instance)) 187 } 188 return result, nil 189 } 190 191 // GetPSPEntries returns all entries of a certain type from PSP directory 192 func GetPSPEntries( 193 pspFirmware *amd_manifest.PSPFirmware, 194 pspLevel uint, 195 entryID amd_manifest.PSPDirectoryTableEntryType, 196 ) ([]amd_manifest.PSPDirectoryTableEntry, error) { 197 pspTable, err := getPSPTable(pspFirmware, pspLevel) 198 if err != nil { 199 return nil, err 200 } 201 if pspTable == nil { 202 directory, err := GetPSPDirectoryOfLevel(pspLevel) 203 if err != nil { 204 return nil, fmt.Errorf("unknown psp directory of level %d", pspLevel) 205 } 206 return nil, newErrNotFound(newDirectoryItem(directory)) 207 } 208 var entries []amd_manifest.PSPDirectoryTableEntry 209 for _, entry := range pspTable.Entries { 210 if entry.Type == entryID { 211 entries = append(entries, entry) 212 } 213 } 214 return entries, nil 215 } 216 217 // GetPSPEntry returns a singe entry of a certain type from PSP directory, returns error if multiple entries are found 218 func GetPSPEntry( 219 pspFirmware *amd_manifest.PSPFirmware, 220 pspLevel uint, 221 entryID amd_manifest.PSPDirectoryTableEntryType, 222 ) (*amd_manifest.PSPDirectoryTableEntry, error) { 223 entries, err := GetPSPEntries(pspFirmware, pspLevel, entryID) 224 if err != nil { 225 return nil, err 226 } 227 if len(entries) == 0 { 228 return nil, newErrNotFound(newPSPDirectoryEntryItem(uint8(pspLevel), entryID)) 229 } 230 if len(entries) > 1 { 231 directory, err := GetPSPDirectoryOfLevel(pspLevel) 232 if err != nil { 233 return nil, fmt.Errorf("unknown psp directory of level %d", pspLevel) 234 } 235 return nil, newErrInvalidFormatWithItem( 236 newDirectoryItem(directory), 237 fmt.Errorf("multiple entriers %x are found in PSP directory level %d", entryID, pspLevel), 238 ) 239 } 240 return &entries[0], err 241 } 242 243 // GetEntries returns a list of specific type PSP entries 244 func GetEntries(pspFirmware *amd_manifest.PSPFirmware, directory DirectoryType, entryID uint32) ([]bytes2.Range, error) { 245 var entries []bytes2.Range 246 switch directory { 247 case PSPDirectoryLevel1, PSPDirectoryLevel2: 248 pspEntries, err := GetPSPEntries(pspFirmware, directory.Level(), amd_manifest.PSPDirectoryTableEntryType(entryID)) 249 if err != nil { 250 return nil, err 251 } 252 253 for _, entry := range pspEntries { 254 entries = append(entries, bytes2.Range{Offset: entry.LocationOrValue, Length: uint64(entry.Size)}) 255 } 256 case BIOSDirectoryLevel1, BIOSDirectoryLevel2: 257 biosEntries, err := GetBIOSEntries(pspFirmware, directory.Level(), amd_manifest.BIOSDirectoryTableEntryType(entryID)) 258 if err != nil { 259 return nil, err 260 } 261 262 for _, entry := range biosEntries { 263 entries = append(entries, bytes2.Range{Offset: entry.SourceAddress, Length: uint64(entry.Size)}) 264 } 265 default: 266 return nil, fmt.Errorf("unsopprted directory type: %s", directory) 267 } 268 return entries, nil 269 } 270 271 // GetRangeBytes converts firmware range to continues bytes sequence 272 // TODO: should be moved to fiano's bytes2 273 func GetRangeBytes(image []byte, start, length uint64) ([]byte, error) { 274 end := start + length 275 if err := checkBoundaries(start, end, image); err != nil { 276 return nil, newErrInvalidFormat(fmt.Errorf("boundary check fail: %w", err)) 277 } 278 return image[start:end], nil 279 } 280 281 // ExtractPSPEntry extracts a single generic raw entry from PSP Directory. 282 // Returns an error if multiple entries are found as PSP directory is supposed to have no more than a single entry for each type 283 func ExtractPSPEntry(amdFw *amd_manifest.AMDFirmware, pspLevel uint, entryID amd_manifest.PSPDirectoryTableEntryType) ([]byte, error) { 284 entry, err := GetPSPEntry(amdFw.PSPFirmware(), pspLevel, entryID) 285 if err != nil { 286 return nil, err 287 } 288 data, err := GetRangeBytes(amdFw.Firmware().ImageBytes(), entry.LocationOrValue, uint64(entry.Size)) 289 if err != nil { 290 if errInvalidFormat, ok := err.(ErrInvalidFormat); ok { 291 return nil, newErrInvalidFormatWithItem(newPSPDirectoryEntryItem(uint8(pspLevel), entryID), errInvalidFormat.Unwrap()) 292 } 293 return nil, err 294 } 295 return data, nil 296 } 297 298 // ExtractBIOSEntry extracts a single generic raw entry from BIOS Directory. 299 func ExtractBIOSEntry(amdFw *amd_manifest.AMDFirmware, biosLevel uint, entryID amd_manifest.BIOSDirectoryTableEntryType, instance uint8) ([]byte, error) { 300 entry, err := GetBIOSEntry(amdFw.PSPFirmware(), biosLevel, entryID, instance) 301 if err != nil { 302 return nil, err 303 } 304 data, err := GetRangeBytes(amdFw.Firmware().ImageBytes(), entry.SourceAddress, uint64(entry.Size)) 305 if err != nil { 306 if errInvalidFormat, ok := err.(ErrInvalidFormat); ok { 307 return nil, newErrInvalidFormatWithItem(newBIOSDirectoryEntryItem(uint8(biosLevel), entryID, instance), errInvalidFormat.Unwrap()) 308 } 309 return nil, err 310 } 311 return data, nil 312 } 313 314 // DumpPSPEntry dumps an entry from PSP Directory 315 func DumpPSPEntry(amdFw *amd_manifest.AMDFirmware, pspLevel uint, entryID amd_manifest.PSPDirectoryTableEntryType, w io.Writer) (int, error) { 316 data, err := ExtractPSPEntry(amdFw, pspLevel, entryID) 317 if err != nil { 318 return 0, err 319 } 320 return w.Write(data) 321 } 322 323 // DumpBIOSEntry dumps an entry from BIOS directory 324 func DumpBIOSEntry(amdFw *amd_manifest.AMDFirmware, biosLevel uint, entryID amd_manifest.BIOSDirectoryTableEntryType, instance uint8, w io.Writer) (int, error) { 325 data, err := ExtractBIOSEntry(amdFw, biosLevel, entryID, instance) 326 if err != nil { 327 return 0, err 328 } 329 return w.Write(data) 330 } 331 332 // PatchPSPEntry takes an AmdFirmware object and modifies one entry in PSP directory. 333 // The modified entry is read from `r` reader object, while the modified firmware is written into `w` writer object. 334 func PatchPSPEntry(amdFw *amd_manifest.AMDFirmware, pspLevel uint, entryID amd_manifest.PSPDirectoryTableEntryType, r io.Reader, w io.Writer) (int, error) { 335 entry, err := GetPSPEntry(amdFw.PSPFirmware(), pspLevel, entryID) 336 if err != nil { 337 return 0, err 338 } 339 340 start := entry.LocationOrValue 341 end := start + uint64(entry.Size) 342 return patchEntry(amdFw, start, end, r, w) 343 } 344 345 // PatchBIOSEntry takes an AmdFirmware object and modifies one entry in BIOS directory. 346 // The modified entry is read from `r` reader object, while the modified firmware is written into `w` writer object. 347 func PatchBIOSEntry(amdFw *amd_manifest.AMDFirmware, biosLevel uint, entryID amd_manifest.BIOSDirectoryTableEntryType, instance uint8, r io.Reader, w io.Writer) (int, error) { 348 entry, err := GetBIOSEntry(amdFw.PSPFirmware(), biosLevel, entryID, instance) 349 if err != nil { 350 return 0, err 351 } 352 353 start := entry.SourceAddress 354 end := start + uint64(entry.Size) 355 return patchEntry(amdFw, start, end, r, w) 356 } 357 358 func patchEntry(amdFw *amd_manifest.AMDFirmware, start, end uint64, r io.Reader, w io.Writer) (int, error) { 359 modifiedEntry, err := io.ReadAll(r) 360 if err != nil { 361 return 0, fmt.Errorf("could not read modified entry: %w", err) 362 } 363 364 firmwareBytes := amdFw.Firmware().ImageBytes() 365 366 if err := checkBoundaries(start, end, firmwareBytes); err != nil { 367 return 0, newErrInvalidFormat(fmt.Errorf("cannot extract key database from firmware image, boundary check fail: %w", err)) 368 } 369 370 size := end - start 371 if uint64(end-start) != uint64(len(modifiedEntry)) { 372 return 0, newErrInvalidFormat(fmt.Errorf("cannot write the entry to the firmware image, entry size check fail, expected %d, modified entry is %d", uint64(size), uint64(len(modifiedEntry)))) 373 } 374 375 firmwareBytesFirstSection := firmwareBytes[0:start] 376 firmwareBytesSecondSection := firmwareBytes[end:] 377 378 // Write the firmware to the writer object. firmwareBytes is not modified in place because it would segfault. 379 // The reason is the following: 380 // * We read the firmware with uefi.ParseUEFIFirmwareFile in https://github.com/9elements/converged-security-suite/blob/master/pkg/uefi/uefi.go#L43 381 // * That by default maps as read only: 382 // https://github.com/9elements/converged-security-suite/blob/81375eac5ccc858045c91323eac8e60233dc9882/pkg/ostools/file_to_bytes.go#L25 383 // * Later, the behavior can be modified with ReadOnly flag in 384 // https://github.com/linuxboot/fiano/blob/master/pkg/uefi/uefi.go#L24, which is in turn consumed from NewBIOSRegion. 385 // * If ReadOnly is not set, the whole slice is copied into memory from the mapped region: 386 // https://github.com/linuxboot/fiano/blob/43cb7391010ac6cb416ab6f641a3a5465b5f524e/pkg/uefi/biosregion.go#L88 387 // 388 // Converged security suite sets read-only to true: https://github.com/9elements/converged-security-suite/blob/master/pkg/uefi/uefi.go#L30 389 // Therefore, firmwareBytes is read-only memmapped region. In order to make it read-write, we would need to enable the copy approach 390 // and set ReadOnly to false (fianoUEFI.ReadOnly = false) 391 // We take a more explicit approach and write the memory area before the corrupted region, the corrupted region itself, 392 // and the memory area after the corrupted region. 393 n, err := w.Write(firmwareBytesFirstSection) 394 if err != nil { 395 return n, fmt.Errorf("could not write entry to system file : %w", err) 396 } 397 m, err := w.Write(modifiedEntry) 398 if err != nil { 399 return n, fmt.Errorf("could not write entry to system file : %w", err) 400 } 401 j, err := w.Write(firmwareBytesSecondSection) 402 if err != nil { 403 return n, fmt.Errorf("could not write entry to system file : %w", err) 404 } 405 406 n = n + m + j 407 return n, nil 408 }