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 }