github.com/linuxboot/fiano@v1.2.0/pkg/intel/metadata/fit/entry.go (about)

     1  // Copyright 2017-2021 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 fit
     6  
     7  import (
     8  	"encoding/binary"
     9  	"fmt"
    10  	"io"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/linuxboot/fiano/pkg/intel/metadata/fit/check"
    15  	"github.com/linuxboot/fiano/pkg/intel/metadata/fit/consts"
    16  	"github.com/xaionaro-go/bytesextra"
    17  )
    18  
    19  // Entry is the interface common to any FIT entry
    20  type Entry interface {
    21  	// GetEntryBase returns EntryBase (which contains metadata of the Entry).
    22  	GetEntryBase() *EntryBase
    23  }
    24  
    25  // EntryCustomGetDataSegmentSizer is an extension of Entry which overrides the default
    26  // procedure of calculating the data segment size.
    27  type EntryCustomGetDataSegmentSizer interface {
    28  	// CustomGetDataSegmentSize returns the size of the data segment associates with the entry.
    29  	CustomGetDataSegmentSize(firmwareImage io.ReadSeeker) (uint64, error)
    30  }
    31  
    32  // CustomRecalculateHeaderser is an extension of Entry which overrides the default
    33  // procedure of recalculating EntryHeaders.
    34  type EntryCustomRecalculateHeaderser interface {
    35  	// CustomRecalculateHeaders recalculates metadata to be consistent with data.
    36  	// For example, it fixes checksum, data size, entry type and so on.
    37  	CustomRecalculateHeaders() error
    38  }
    39  
    40  // EntriesByType is a helper to sort a slice of `Entry`-ies by their type/class.
    41  type EntriesByType []Entry
    42  
    43  func (entries EntriesByType) Less(i, j int) bool {
    44  	return entries[i].GetEntryBase().Headers.Type() < entries[j].GetEntryBase().Headers.Type()
    45  }
    46  func (entries EntriesByType) Swap(i, j int) { entries[i], entries[j] = entries[j], entries[i] }
    47  func (entries EntriesByType) Len() int      { return len(entries) }
    48  
    49  // mostCommonRecalculateHeadersOfEntry recalculates entry headers using headers data using the most common rules:
    50  // * Set "Version" to 0x0100.
    51  // * Set "IsChecksumValid" to true.
    52  // * Set "Type" to the type of the entry.
    53  // * Set "Checksum" to the calculated checksum value of the headers
    54  // * Set "Size" to a multiple of 16 of the data size (in other words: len(data) >> 4).
    55  //
    56  // This is considered the most common set of rules for the most FIT entry types. But different types may break
    57  // different rules.
    58  func mostCommonRecalculateHeadersOfEntry(entry Entry) {
    59  	entryType, foundEntryType := entryTypeOf(entry)
    60  	if !foundEntryType {
    61  		panic(fmt.Errorf("type %T is not known", entry))
    62  	}
    63  
    64  	entryBase := entry.GetEntryBase()
    65  	hdr := &entryBase.Headers
    66  	hdr.TypeAndIsChecksumValid.SetType(entryType)
    67  	hdr.TypeAndIsChecksumValid.SetIsChecksumValid(true)
    68  	hdr.Checksum = hdr.CalculateChecksum()
    69  	hdr.Version = EntryVersion(0x0100)
    70  	hdr.Size.SetUint32(uint32(len(entryBase.DataSegmentBytes) >> 4))
    71  }
    72  
    73  // EntryRecalculateHeaders recalculates headers of the entry based on its data.
    74  func EntryRecalculateHeaders(entry Entry) error {
    75  	if recalcer, ok := entry.(EntryCustomRecalculateHeaderser); ok {
    76  		return recalcer.CustomRecalculateHeaders()
    77  	}
    78  
    79  	mostCommonRecalculateHeadersOfEntry(entry)
    80  	return nil
    81  }
    82  
    83  // Entries are a slice of multiple parsed FIT entries (headers + data)
    84  type Entries []Entry
    85  
    86  // RecalculateHeaders recalculates metadata to be consistent with data. For example, it fixes checksum, data size,
    87  // entry type and so on.
    88  //
    89  // Supposed to be used before Inject or/and InjectTo. Since it is possible to prepare data in entries, then
    90  // call Rehash (to prepare headers consistent with data).
    91  func (entries Entries) RecalculateHeaders() error {
    92  	if len(entries) == 0 {
    93  		return nil
    94  	}
    95  
    96  	for idx, entry := range entries {
    97  		err := EntryRecalculateHeaders(entry)
    98  		if err != nil {
    99  			return fmt.Errorf("unable to recalculate headers of FIT entry #%d (%#+v): %w", idx, entry, err)
   100  		}
   101  	}
   102  
   103  	beginEntry, ok := entries[0].(*EntryFITHeaderEntry)
   104  	if !ok {
   105  		return fmt.Errorf("the first entry is not a EntryFITHeaderEntry, but %T", entries[0])
   106  	}
   107  
   108  	// See point 4.2.5 of the FIT specification
   109  	beginEntry.GetEntryBase().Headers.Size.SetUint32(uint32(len(entries)))
   110  
   111  	return nil
   112  }
   113  
   114  // Table returns a table of headers of all entries of the slice.
   115  func (entries Entries) Table() Table {
   116  	result := make(Table, 0, len(entries))
   117  	for _, entry := range entries {
   118  		if entry.GetEntryBase() == nil {
   119  			panic(fmt.Sprintf("%T", entry))
   120  		}
   121  		result = append(result, entry.GetEntryBase().Headers)
   122  	}
   123  	return result
   124  }
   125  
   126  // String implements fmt.Stringer
   127  func (entries Entries) String() string {
   128  	var result strings.Builder
   129  	for idx, entry := range entries {
   130  		hdr := entry.GetEntryBase().Headers
   131  		result.WriteString(fmt.Sprintf("Entry #%d\n\tType: %s (0x%X)\n\tVersion: 0x%04X\n\tAddr: 0x%X\n\tSize: 0x%06X\n\tChecksum: 0x%X\n\tChecksum is valid: %v\n",
   132  			idx,
   133  			hdr.Type(), uint(hdr.Type()),
   134  			uint16(hdr.Version),
   135  			hdr.Address.Pointer(),
   136  			hdr.Size.Uint32(),
   137  			hdr.Checksum,
   138  			hdr.IsChecksumValid(),
   139  		))
   140  		if data := entry.GetEntryBase().DataSegmentBytes; len(data) > 0 {
   141  			result.WriteString(fmt.Sprintf("\tData: 0x%X\n", data))
   142  		}
   143  	}
   144  	return result.String()
   145  }
   146  
   147  // Inject writes complete FIT (headers + data + pointer) to a firmware image.
   148  //
   149  // What will happen:
   150  // 1. The FIT headers will be written by offset headersOffset.
   151  // 2. The FIT pointer will be written at consts.FITPointerOffset offset from the end of the image.
   152  // 3. Data referenced by FIT headers will be written at offsets accordingly to Address fields (in the headers).
   153  //
   154  // Consider calling Rehash() before Inject()/InjectTo()
   155  func (entries Entries) Inject(b []byte, headersOffset uint64) error {
   156  	return entries.InjectTo(bytesextra.NewReadWriteSeeker(b), headersOffset)
   157  }
   158  
   159  // InjectTo does the same as Inject, but for io.WriteSeeker.
   160  func (entries Entries) InjectTo(w io.WriteSeeker, headersOffset uint64) error {
   161  
   162  	// Detect image size
   163  
   164  	imageSize, err := w.Seek(0, io.SeekEnd)
   165  	if err != nil {
   166  		return fmt.Errorf("unable to detect the end of the image: %w", err)
   167  	}
   168  	if imageSize < 0 {
   169  		panic(fmt.Errorf("negative image size: %d", imageSize))
   170  	}
   171  
   172  	// Write FIT pointer
   173  
   174  	if _, err := w.Seek(-consts.FITPointerOffset, io.SeekEnd); err != nil {
   175  		return fmt.Errorf("unable to Seek(%d, %d) to write FIT pointer: %w", headersOffset, io.SeekStart, err)
   176  	}
   177  	pointerValue := CalculatePhysAddrFromOffset(headersOffset, uint64(imageSize))
   178  	if err := binary.Write(w, binary.LittleEndian, pointerValue); err != nil {
   179  		return fmt.Errorf("unable to write FIT pointer: %w", err)
   180  	}
   181  
   182  	// Write headers
   183  
   184  	if _, err := w.Seek(int64(headersOffset), io.SeekStart); err != nil {
   185  		return fmt.Errorf("unable to Seek(%d, %d) to write headers: %w", headersOffset, io.SeekStart, err)
   186  	}
   187  
   188  	table := entries.Table()
   189  	if _, err := table.WriteTo(w); err != nil {
   190  		return fmt.Errorf("unable to write %d headers to offset %d: %w", len(table), headersOffset, err)
   191  	}
   192  
   193  	// Write data sections
   194  
   195  	for idx, entry := range entries {
   196  		if err := entry.GetEntryBase().injectDataSectionTo(w); err != nil {
   197  			return fmt.Errorf("unable to inject data section of entry %d: %w", idx, err)
   198  		}
   199  	}
   200  
   201  	return nil
   202  }
   203  
   204  func copyBytesFrom(r io.ReadSeeker, startIdx, endIdx uint64) ([]byte, error) {
   205  	_, err := r.Seek(int64(startIdx), io.SeekStart)
   206  	if err != nil {
   207  		return nil, fmt.Errorf("unable to Seek(%d, io.SeekStart): %w", int64(startIdx), err)
   208  	}
   209  
   210  	if endIdx < startIdx {
   211  		return nil, fmt.Errorf("endIdx < startIdx: %d < %d", endIdx, startIdx)
   212  	}
   213  
   214  	return readBytesFromReader(r, endIdx-startIdx)
   215  }
   216  
   217  func readBytesFromReader(r io.Reader, size uint64) ([]byte, error) {
   218  	result := make([]byte, size)
   219  	written, err := io.CopyN(bytesextra.NewReadWriteSeeker(result), r, int64(size))
   220  	if err != nil {
   221  		return nil, fmt.Errorf("unable to copy %d bytes: %w", int64(size), err)
   222  	}
   223  	if written != int64(size) {
   224  		return nil, fmt.Errorf("invalid amount of bytes copied: %d != %d", written, int64(size))
   225  	}
   226  
   227  	return result, nil
   228  }
   229  
   230  // EntryDataSegmentSize returns the coordinates of the data segment size associates with the entry.
   231  func EntryDataSegmentSize(entry Entry, firmware io.ReadSeeker) (uint64, error) {
   232  	if sizeGetter, ok := entry.(EntryCustomGetDataSegmentSizer); ok {
   233  		return sizeGetter.CustomGetDataSegmentSize(firmware)
   234  	} else {
   235  		return entry.GetEntryBase().Headers.mostCommonGetDataSegmentSize(), nil
   236  	}
   237  }
   238  
   239  // EntryDataSegmentCoordinates returns the coordinates of the data segment coordinates associates with the entry.
   240  func EntryDataSegmentCoordinates(entry Entry, firmware io.ReadSeeker) (uint64, uint64, error) {
   241  	var err error
   242  
   243  	offset, addErr := entry.GetEntryBase().Headers.getDataSegmentOffset(firmware)
   244  	if addErr != nil {
   245  		err = multierror.Append(err, fmt.Errorf("unable to get data segment offset: %w", err))
   246  	}
   247  
   248  	size, addErr := EntryDataSegmentSize(entry, firmware)
   249  	if addErr != nil {
   250  		err = multierror.Append(err, fmt.Errorf("unable to get data segment size: %w", err))
   251  	}
   252  
   253  	return offset, size, err
   254  }
   255  
   256  // If possible then make a slice of existing data; if not then copy.
   257  func sliceOrCopyBytesFrom(r io.ReadSeeker, startIdx, endIdx uint64) ([]byte, error) {
   258  	switch r := r.(type) {
   259  	case *bytesextra.ReadWriteSeeker:
   260  		if err := check.BytesRange(uint(len(r.Storage)), int(startIdx), int(endIdx)); err != nil {
   261  			return nil, err
   262  		}
   263  		return r.Storage[startIdx:endIdx], nil
   264  	default:
   265  		return copyBytesFrom(r, startIdx, endIdx)
   266  	}
   267  }
   268  
   269  func entryInitDataSegmentBytes(entry Entry, firmware io.ReadSeeker) error {
   270  	dataSegmentOffset, dataSegmentSize, err := EntryDataSegmentCoordinates(entry, firmware)
   271  	if err != nil {
   272  		return fmt.Errorf("unable to get data segment coordinates of entry %T: %w", entry, err)
   273  	}
   274  
   275  	if dataSegmentSize == 0 {
   276  		return nil
   277  	}
   278  
   279  	base := entry.GetEntryBase()
   280  
   281  	base.DataSegmentBytes, err = sliceOrCopyBytesFrom(firmware, dataSegmentOffset, dataSegmentOffset+dataSegmentSize)
   282  	if err != nil {
   283  		return fmt.Errorf("unable to copy data segment bytes from the firmware image (offset:%d, size:%d): %w", dataSegmentOffset, dataSegmentSize, err)
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  // NewEntry returns a new entry using headers and firmware image
   290  func NewEntry(hdr *EntryHeaders, firmware io.ReadSeeker) Entry {
   291  	entry := hdr.Type().newEntry()
   292  	if entry == nil {
   293  		entry = &EntryUnknown{}
   294  	}
   295  	base := entry.GetEntryBase()
   296  	base.Headers = *hdr
   297  
   298  	err := entryInitDataSegmentBytes(entry, firmware)
   299  	if err != nil {
   300  		base.HeadersErrors = append(base.HeadersErrors, err)
   301  	}
   302  
   303  	return entry
   304  }