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  }