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 }