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  }