github.com/linuxboot/fiano@v1.2.0/pkg/intel/metadata/fit/table.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  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  	"io"
    12  	"strings"
    13  
    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  // Table is the FIT entry headers table (located by the "FIT Pointer"), without
    20  // data this headers reference to.
    21  type Table []EntryHeaders
    22  
    23  // GetEntries returns parsed FIT-entries
    24  func (table Table) GetEntries(firmware []byte) (result Entries) {
    25  	return table.GetEntriesFrom(bytesextra.NewReadWriteSeeker(firmware))
    26  }
    27  
    28  // GetEntriesFrom returns parsed FIT-entries
    29  func (table Table) GetEntriesFrom(firmware io.ReadSeeker) (result Entries) {
    30  	for _, headers := range table {
    31  		result = append(result, headers.GetEntryFrom(firmware))
    32  	}
    33  	return
    34  }
    35  
    36  // String prints the fit table in a tabular form
    37  func (table Table) String() string {
    38  	var s strings.Builder
    39  	// PrintFit prints the Firmware Interface Table in a tabular human readable form.
    40  	fmt.Fprintf(&s, "%-3s | %-32s | %-20s | %-8s | %-6s | %-15s | %-10s\n", "#", "Type", "Address", "Size", "Version", "Checksum valid", "Checksum")
    41  	s.WriteString("---------------------------------------------------------------------------------------------------------------\n")
    42  	for idx, entry := range table {
    43  		fmt.Fprintf(&s, "%-3d | %-25s (0x%02X) | %-20s | %-8d | 0x%04x  | %-15v | %-10d\n",
    44  			idx,
    45  			entry.Type(), uint8(entry.Type()),
    46  			entry.Address.String(),
    47  			entry.Size.Uint32(),
    48  			uint16(entry.Version),
    49  			entry.IsChecksumValid(),
    50  			entry.Checksum)
    51  	}
    52  	return s.String()
    53  }
    54  
    55  // First returns the first entry headers with selected entry type
    56  func (table Table) First(entryType EntryType) *EntryHeaders {
    57  	for idx, headers := range table {
    58  		if headers.Type() == entryType {
    59  			return &table[idx]
    60  		}
    61  	}
    62  	return nil
    63  }
    64  
    65  // Write compiles FIT headers into a binary representation and writes to "b". If len(b)
    66  // is less than required, then io.ErrUnexpectedEOF is returned.
    67  func (table Table) Write(b []byte) (n int, err error) {
    68  	for idx, entryHeaders := range table {
    69  		addN, err := entryHeaders.Write(b)
    70  		if err != nil {
    71  			return n, fmt.Errorf("unable to write headers #%d (%#+v): %w", idx, entryHeaders, err)
    72  		}
    73  		n += addN
    74  	}
    75  
    76  	return n, nil
    77  }
    78  
    79  // WriteTo does the same as Write, but for io.Writer
    80  func (table Table) WriteTo(w io.Writer) (n int64, err error) {
    81  	for idx, entryHeaders := range table {
    82  		addN, err := entryHeaders.WriteTo(w)
    83  		if err != nil {
    84  			return n, fmt.Errorf("unable to write headers #%d (%#+v): %w", idx, entryHeaders, err)
    85  		}
    86  		n += addN
    87  	}
    88  
    89  	return n, nil
    90  }
    91  
    92  // WriteToFirmwareImage finds the position of FIT in a firmware image and writes the table there.
    93  func (table Table) WriteToFirmwareImage(w io.ReadWriteSeeker) (n int64, err error) {
    94  	startIdx, _, err := GetHeadersTableRangeFrom(w)
    95  	if err != nil {
    96  		return 0, fmt.Errorf("unable to find the beginning of the FIT: %w", err)
    97  	}
    98  
    99  	if _, err := w.Seek(int64(startIdx), io.SeekStart); err != nil {
   100  		return 0, fmt.Errorf("unable to Seek(%d, io.SeekStart): %w", int64(startIdx), err)
   101  	}
   102  
   103  	return table.WriteTo(w)
   104  }
   105  
   106  // ParseEntryHeadersFrom parses a single entry headers entry.
   107  func ParseEntryHeadersFrom(r io.Reader) (*EntryHeaders, error) {
   108  	entryHeaders := EntryHeaders{}
   109  	err := binary.Read(r, binary.LittleEndian, &entryHeaders)
   110  	if err != nil {
   111  		return nil, fmt.Errorf("unable to parse FIT entry headers: %w", err)
   112  	}
   113  
   114  	return &entryHeaders, nil
   115  }
   116  
   117  // ParseTable parses a FIT table from `b`.
   118  func ParseTable(b []byte) (Table, error) {
   119  	var result Table
   120  	r := bytes.NewReader(b)
   121  	for r.Len() > 0 {
   122  		entryHeaders, err := ParseEntryHeadersFrom(r)
   123  		if err != nil {
   124  			return nil, fmt.Errorf("unable to parse FIT headers table: %w", err)
   125  		}
   126  		result = append(result, *entryHeaders)
   127  	}
   128  	return result, nil
   129  }
   130  
   131  // GetPointerCoordinates returns the position of the FIT pointer within
   132  // the firmware.
   133  func GetPointerCoordinates(firmwareSize uint64) (startIdx, endIdx int64) {
   134  	startIdx = int64(firmwareSize) - consts.FITPointerOffset
   135  	endIdx = startIdx + consts.FITPointerSize
   136  	return
   137  }
   138  
   139  // GetHeadersTableRangeFrom returns the starting and ending indexes of the FIT
   140  // headers table within the firmware image.
   141  func GetHeadersTableRangeFrom(firmware io.ReadSeeker) (startIdx, endIdx uint64, err error) {
   142  
   143  	/*
   144  		An example:
   145  		<image start>
   146  		...
   147  		01bb0000: 5f46 4954 5f20 2020 1b00 0000 0001 0000  _FIT_   ........ <--+
   148  		01bb0010: 80d5 e3ff 0000 0000 0000 0000 0001 0100  ................    |
   149  		01bb0020: 804d e4ff 0000 0000 0000 0000 0001 0100  .M..............    |
   150  		01bb0030: 80c5 e4ff 0000 0000 0000 0000 0001 0100  ................    |
   151  		01bb0040: 8035 e5ff 0000 0000 0000 0000 0001 0100  .5..............    |
   152  		01bb0050: ffff ffff 0000 0000 0000 0000 0001 7f00  ................    |
   153  		...                                                                    |
   154  		01bb0180: 7000 7100 0105 2a00 0000 0000 0000 0a00  p.q...*.........    |
   155  		01bb0190: 80c2 e5ff 0000 0000 4102 0000 0001 0b00  ........A.......    |
   156  		01bb01a0: 00b2 e5ff 0000 0000 df02 0000 0001 0c00  ................    |
   157  		01bb01b0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  xxxxxxxxxxxxxxxx    |
   158  		...                                                                    |
   159  		01ffffb0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  xxxxxxxxxxxxxxxx    |
   160  		01ffffc0: 0000 bbff 0000 0000 0000 0000 0000 0000  ................ >--+
   161  		01ffffd0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  xxxxxxxxxxxxxxxx
   162  		01ffffe0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  xxxxxxxxxxxxxxxx
   163  		01fffff0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  xxxxxxxxxxxxxxxx
   164  		<image end>
   165  
   166  		So "0000 bbff" (LE: 0xffbb0000) is seems to be the fitPointer
   167  		(according to the specification).
   168  
   169  		Re-check:
   170  		 * fitPointerOffset <- 0x100000000 - 0xffbb0000 == 0x450000
   171  		 * headersStartIdx <- 0x2000000 - 0x450000 == 0x1bb0000
   172  		It's the correct value, yey!
   173  
   174  		The full procedure in more formal terms was:
   175  		 * fitPointerPointer <- 0x2000000 (firmwareLength) - 0x40 == 0x01ffffc0
   176  		 * fitPointer <- *fitPointerPointer == 0xffbb0000
   177  		 * fitPointerOffset <- 0x100000000 (const) - 0xffbb0000 == 0x450000
   178  		 * headersStartIdx <- 0x2000000 - 0x450000 == 0x1bb0000
   179  	*/
   180  
   181  	firmwareSize, err := firmware.Seek(0, io.SeekEnd)
   182  	if err != nil || firmwareSize < 0 {
   183  		return 0, 0, fmt.Errorf("unable to determine firmware size; result: %d; err: %w", firmwareSize, err)
   184  	}
   185  
   186  	fitPointerStartIdx, fitPointerEndIdx := GetPointerCoordinates(uint64(firmwareSize))
   187  
   188  	if err := check.BytesRange(uint(firmwareSize), int(fitPointerStartIdx), int(fitPointerEndIdx)); err != nil {
   189  		return 0, 0, fmt.Errorf("invalid fit pointer bytes range: %w", err)
   190  	}
   191  
   192  	fitPointerBytes, err := sliceOrCopyBytesFrom(firmware, uint64(fitPointerStartIdx), uint64(fitPointerEndIdx))
   193  	if err != nil {
   194  		return 0, 0, fmt.Errorf("unable to get FIT pointer value: %w", err)
   195  	}
   196  	fitPointerValue := binary.LittleEndian.Uint64(fitPointerBytes)
   197  	fitPointerOffset := CalculateTailOffsetFromPhysAddr(fitPointerValue)
   198  	startIdx = uint64(firmwareSize) - fitPointerOffset
   199  
   200  	// OK, now we need to calculate the end of the headers...
   201  	//
   202  	// It's pretty easy. The first entry describes the table itself, and it's
   203  	// size is the size of the table. So let's just use it.
   204  
   205  	firstHeaderEndIdx := startIdx + uint64(entryHeadersSize)
   206  	if err = check.BytesRange(uint(firmwareSize), int(startIdx), int(firstHeaderEndIdx)); err != nil {
   207  		err = fmt.Errorf("invalid the first entry bytes range: %w", err)
   208  		return
   209  	}
   210  
   211  	tableMeta := EntryHeaders{}
   212  
   213  	if _, err = firmware.Seek(int64(startIdx), io.SeekStart); err != nil {
   214  		err = fmt.Errorf("unable to Seek(%d, io.SeekStart) in the firmware: %w", int64(startIdx), err)
   215  		return
   216  	}
   217  	err = binary.Read(firmware, binary.LittleEndian, &tableMeta)
   218  	if err != nil {
   219  		err = fmt.Errorf("unable to parse the first entry: %w", err)
   220  		return
   221  	}
   222  
   223  	// Verify if the first entry contains "_FIT_  " as the address (as it is
   224  	// described by the point 1.2.2 of the specification).
   225  
   226  	var buf bytes.Buffer
   227  	err = binary.Write(&buf, binary.LittleEndian, tableMeta.Address)
   228  	if err != nil {
   229  		err = fmt.Errorf("unable to read the Address value of the FIT header entry: %w", err)
   230  		return
   231  	}
   232  	if !bytes.Equal([]byte(consts.FITHeadersMagic), buf.Bytes()) {
   233  		err = &ErrExpectedFITHeadersMagic{Received: buf.Bytes()}
   234  		return
   235  	}
   236  
   237  	// OK, it's correct. Now we know the size of the table and we can
   238  	// parseHeaders it.
   239  
   240  	endIdx = startIdx + uint64(tableMeta.Size.Uint32()<<4) // See 4.2.5
   241  	if err = check.BytesRange(uint(firmwareSize), int(startIdx), int(endIdx)); err != nil {
   242  		err = fmt.Errorf("invalid entries bytes range: %w", err)
   243  		return
   244  	}
   245  
   246  	return
   247  }
   248  
   249  // GetTable returns the table of FIT entries of the firmware image.
   250  func GetTable(firmware []byte) (Table, error) {
   251  	return GetTableFrom(bytesextra.NewReadWriteSeeker(firmware))
   252  }
   253  
   254  // GetTableFrom returns the table of FIT entries of the firmware image.
   255  func GetTableFrom(firmware io.ReadSeeker) (Table, error) {
   256  	startIdx, endIdx, err := GetHeadersTableRangeFrom(firmware)
   257  	if err != nil {
   258  		return nil, fmt.Errorf("unable to locate the table coordinates (does the image contain FIT?): %w", err)
   259  	}
   260  
   261  	tableBytes, err := sliceOrCopyBytesFrom(firmware, startIdx, endIdx)
   262  	if err != nil {
   263  		return nil, fmt.Errorf("unable to copy bytes from the firmware: %w", err)
   264  	}
   265  
   266  	result, err := ParseTable(tableBytes)
   267  	if err != nil {
   268  		return nil, fmt.Errorf("unable to parse the table: %w", err)
   269  	}
   270  
   271  	return result, nil
   272  }