334 lines
9.0 KiB
Go
334 lines
9.0 KiB
Go
package cbnt
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
|
|
"github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/fit"
|
|
"github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/bootpolicy"
|
|
"github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/common/pretty"
|
|
"github.com/9elements/converged-security-suite/v2/pkg/intel/metadata/manifest/key"
|
|
"github.com/9elements/converged-security-suite/v2/pkg/tools"
|
|
|
|
"github.com/linuxboot/cbfs/pkg/cbfs"
|
|
)
|
|
|
|
// WriteCBnTStructures takes a firmware image and extracts boot policy manifest, key manifest and acm into seperate files.
|
|
func WriteCBnTStructures(image []byte, bpmFile, kmFile, acmFile *os.File) error {
|
|
bpm, km, acm, err := ParseFITEntries(image)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bpmFile != nil && len(bpm.DataBytes) > 0 {
|
|
if _, err = bpmFile.Write(bpm.DataBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if kmFile != nil && len(km.DataBytes) > 0 {
|
|
if _, err = kmFile.Write(km.DataBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if acmFile != nil && len(acm.DataBytes) > 0 {
|
|
if _, err = acmFile.Write(acm.DataBytes); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PrintCBnTStructures takes a firmware image and prints boot policy manifest, key manifest, ACM, chipset, processor and tpm information if available.
|
|
func PrintCBnTStructures(image []byte) error {
|
|
var acm *tools.ACM
|
|
var chipsets *tools.Chipsets
|
|
var processors *tools.Processors
|
|
var tpms *tools.TPMs
|
|
var err, err2 error
|
|
bpmEntry, kmEntry, acmEntry, err := ParseFITEntries(image)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bpm, err := bpmEntry.ParseData()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse BPM: %w", err)
|
|
}
|
|
|
|
km, err := kmEntry.ParseData()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse KM: %w", err)
|
|
}
|
|
|
|
acm, chipsets, processors, tpms, err, err2 = tools.ParseACM(acmEntry.DataBytes)
|
|
if err != nil || err2 != nil {
|
|
return err
|
|
}
|
|
|
|
if bpm != nil {
|
|
fmt.Println(bpm.PrettyString(0, true))
|
|
}
|
|
if km != nil {
|
|
if km.KeyAndSignature.Signature.DataTotalSize() < 1 {
|
|
fmt.Println(km.PrettyString(0, true, pretty.OptionOmitKeySignature(true)))
|
|
} else {
|
|
fmt.Println(km.PrettyString(0, true, pretty.OptionOmitKeySignature(false)))
|
|
}
|
|
}
|
|
if acm != nil {
|
|
acm.PrettyPrint()
|
|
chipsets.PrettyPrint()
|
|
processors.PrettyPrint()
|
|
tpms.PrettyPrint()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PrintFIT takes a firmware image and prints the Firmware Interface Table
|
|
func PrintFIT(image []byte) error {
|
|
fitTable, err := fit.GetTable(image)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get FIT: %w", err)
|
|
}
|
|
fitEntries := fitTable.GetEntries(image)
|
|
fmt.Println("----Firmware Interface Table----")
|
|
fmt.Println()
|
|
for idx, entry := range fitEntries {
|
|
if entry.GetType() == fit.EntryTypeSkip || entry.GetType() == fit.EntryTypeFITHeaderEntry {
|
|
continue
|
|
}
|
|
fmt.Printf("Entry %d\n", idx)
|
|
fmt.Println(entry.GoString())
|
|
fmt.Println()
|
|
}
|
|
fmt.Println()
|
|
return nil
|
|
}
|
|
|
|
// ParseFITEntries takes a firmware image and extract Boot policy manifest, key manifest and acm information.
|
|
func ParseFITEntries(image []byte) (bpm *fit.EntryBootPolicyManifestRecord, km *fit.EntryKeyManifestRecord, acm *fit.EntrySACM, err error) {
|
|
fitTable, err := fit.GetTable(image)
|
|
if err != nil {
|
|
return nil, nil, nil, fmt.Errorf("unable to get FIT: %w", err)
|
|
}
|
|
fitEntries := fitTable.GetEntries(image)
|
|
for _, entry := range fitEntries {
|
|
switch entry := entry.(type) {
|
|
case *fit.EntryBootPolicyManifestRecord:
|
|
bpm = entry
|
|
case *fit.EntryKeyManifestRecord:
|
|
km = entry
|
|
case *fit.EntrySACM:
|
|
acm = entry
|
|
}
|
|
}
|
|
if bpm == nil || km == nil || acm == nil {
|
|
return nil, nil, nil, fmt.Errorf("image has no BPM (isNil:%v) or/and KM (isNil:%v) or/and ACM (isNil:%v)", bpm == nil, km == nil, acm == nil)
|
|
}
|
|
return bpm, km, acm, nil
|
|
}
|
|
|
|
// CalculateNEMSize calculates No Eviction Memory and returns it as count of 4K pages.
|
|
func CalculateNEMSize(image []byte, bpm *bootpolicy.Manifest, km *key.Manifest, acm *tools.ACM) (bootpolicy.Size4K, error) {
|
|
var totalSize uint32
|
|
if bpm == nil || km == nil || acm == nil {
|
|
return 0, fmt.Errorf("BPM, KM or ACM are nil")
|
|
}
|
|
fitTable, err := fit.GetTable(image)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("unable to get FIT: %w", err)
|
|
}
|
|
fitEntries := fitTable.GetEntries(image)
|
|
if len(fitEntries) == 0 || fitEntries[0].GetType() != fit.EntryTypeFITHeaderEntry {
|
|
return 0, fmt.Errorf("unable to get FIT headers")
|
|
}
|
|
hdr := fitEntries[0]
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
totalSize += uint32(km.KeyManifestSignatureOffset)
|
|
totalSize += keySignatureElementMaxSize
|
|
totalSize += hdr.GetHeaders().DataSize()
|
|
totalSize += uint32(2048)
|
|
totalSize += keySignatureElementMaxSize
|
|
totalSize += uint32((&bootpolicy.BPMH{}).TotalSize())
|
|
for _, se := range bpm.SE {
|
|
totalSize += uint32(se.ElementSize)
|
|
for _, ibb := range se.IBBSegments {
|
|
totalSize += ibb.Size
|
|
}
|
|
}
|
|
if bpm.PCDE != nil {
|
|
totalSize += uint32(bpm.PCDE.ElementSize)
|
|
}
|
|
if bpm.PME != nil {
|
|
totalSize += uint32(bpm.PME.ElementSize)
|
|
}
|
|
totalSize += uint32(12)
|
|
totalSize += keySignatureElementMaxSize
|
|
if bpm.TXTE != nil {
|
|
totalSize += uint32(bpm.TXTE.ElementSize)
|
|
}
|
|
totalSize += acm.Header.Size
|
|
totalSize += defaultStackAndDataSize
|
|
if (totalSize + additionalNEMSize) > defaultLLCSize {
|
|
return 0, fmt.Errorf("NEM size is bigger than LLC %d", totalSize+additionalNEMSize)
|
|
}
|
|
if (totalSize % 4096) != 0 {
|
|
totalSize += 4096 - (totalSize % 4096)
|
|
}
|
|
return bootpolicy.NewSize4K(totalSize), nil
|
|
}
|
|
|
|
// StitchFITEntries takes a firmware filename, an acm, a boot policy manifest and a key manifest as byte slices
|
|
// and writes the information into the Firmware Interface Table of the firmware image.
|
|
func StitchFITEntries(biosFilename string, acm, bpm, km []byte) error {
|
|
image, err := ioutil.ReadFile(biosFilename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fitTable, err := fit.GetTable(image)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to get FIT: %w", err)
|
|
}
|
|
fitEntries := fitTable.GetEntries(image)
|
|
file, err := os.OpenFile(biosFilename, os.O_RDWR, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
for _, entry := range fitEntries {
|
|
switch entry := entry.(type) {
|
|
case *fit.EntryBootPolicyManifestRecord:
|
|
if len(bpm) <= 0 {
|
|
continue
|
|
}
|
|
if len(entry.DataBytes) == 0 {
|
|
return fmt.Errorf("FIT entry size is zero for BPM")
|
|
}
|
|
if len(bpm) > len(entry.DataBytes) {
|
|
return fmt.Errorf("new BPM bigger than older BPM")
|
|
}
|
|
addr, err := tools.CalcImageOffset(image, entry.Headers.Address.Pointer())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = file.Seek(0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
size, err := file.WriteAt(bpm, int64(addr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size != len(bpm) {
|
|
return fmt.Errorf("couldn't write new BPM")
|
|
}
|
|
case *fit.EntryKeyManifestRecord:
|
|
if len(km) <= 0 {
|
|
continue
|
|
}
|
|
if len(entry.DataBytes) == 0 {
|
|
return fmt.Errorf("FIT entry size is zero for KM")
|
|
}
|
|
if len(km) > len(entry.DataBytes) {
|
|
return fmt.Errorf("new KM bigger than older KM")
|
|
}
|
|
addr, err := tools.CalcImageOffset(image, entry.Headers.Address.Pointer())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = file.Seek(0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
size, err := file.WriteAt(km, int64(addr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size != len(km) {
|
|
return fmt.Errorf("couldn't write new KM")
|
|
}
|
|
case *fit.EntrySACM:
|
|
if len(acm) <= 0 {
|
|
continue
|
|
}
|
|
addr, err := tools.CalcImageOffset(image, entry.Headers.Address.Pointer())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = file.Seek(int64(addr), io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acmHeader := make([]byte, 32)
|
|
_, err = file.Read(acmHeader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
acmLen, err := tools.LookupACMSize(acmHeader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if acmLen == 0 {
|
|
return fmt.Errorf("ACM size is wrong")
|
|
}
|
|
if len(acm) != int(acmLen) {
|
|
return fmt.Errorf("new ACM size doesn't equal old ACM size")
|
|
}
|
|
_, err = file.Seek(0, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
size, err := file.WriteAt(acm, int64(addr))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if size != len(acm) {
|
|
return fmt.Errorf("couldn't write new ACM")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FindAdditionalIBBs takes a coreboot image and finds componentName to create
|
|
// additional IBBSegment.
|
|
func FindAdditionalIBBs(imagepath string) ([]bootpolicy.IBBSegment, error) {
|
|
ibbs := make([]bootpolicy.IBBSegment, 0)
|
|
image, err := os.Open(imagepath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer image.Close()
|
|
|
|
stat, err := image.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
img, err := cbfs.NewImage(image)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
flashBase := 0xffffffff - stat.Size() + 1
|
|
cbfsbaseaddr := img.Area.Offset
|
|
for _, seg := range img.Segs {
|
|
switch seg.GetFile().Name {
|
|
case
|
|
"fspt.bin",
|
|
"fallback/verstage",
|
|
"bootblock":
|
|
|
|
ibb := bootpolicy.NewIBBSegment()
|
|
ibb.Base = uint32(flashBase) + cbfsbaseaddr + seg.GetFile().RecordStart + seg.GetFile().SubHeaderOffset
|
|
ibb.Size = seg.GetFile().Size
|
|
ibb.Flags = 0
|
|
ibbs = append(ibbs, *ibb)
|
|
}
|
|
}
|
|
return ibbs, nil
|
|
}
|