1 /*
2 * File: EncoreBootImage.cpp
3 *
4 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
5 * See included license file for license details.
6 */
7
8 #include "EncoreBootImage.h"
9 #include <stdexcept>
10 #include <algorithm>
11 #include <time.h>
12 #include "crc.h"
13 #include "SHA1.h"
14 #include "Random.h"
15 #include "rijndael.h"
16 #include "RijndaelCBCMAC.h"
17 #include "Logging.h"
18 #include "EndianUtilities.h"
19
20 using namespace elftosb;
21
EncoreBootImage()22 EncoreBootImage::EncoreBootImage()
23 : m_headerFlags(0),
24 m_productVersion(),
25 m_componentVersion(),
26 m_driveTag(0)
27 {
28 }
29
~EncoreBootImage()30 EncoreBootImage::~EncoreBootImage()
31 {
32 // dispose of all sections
33 section_iterator_t it = beginSection();
34 for (; it != endSection(); ++it)
35 {
36 delete *it;
37 }
38 }
39
40 //! \exception std::runtime_error Raised if \a newSection has the same tag as a previously
41 //! added section.
addSection(Section * newSection)42 void EncoreBootImage::addSection(Section * newSection)
43 {
44 // check for another section with this tag
45 section_iterator_t it = beginSection();
46 for (; it != endSection(); ++it)
47 {
48 if ((*it)->getIdentifier() == newSection->getIdentifier())
49 {
50 throw std::runtime_error("new section with non-unique tag");
51 }
52 }
53
54 // no conflicting section tags, so add it
55 m_sections.push_back(newSection);
56
57 // tell the image who owns it now
58 newSection->setImage(this);
59 }
60
findSection(Section * section)61 EncoreBootImage::section_iterator_t EncoreBootImage::findSection(Section * section)
62 {
63 return std::find(beginSection(), endSection(), section);
64 }
65
setProductVersion(const version_t & version)66 void EncoreBootImage::setProductVersion(const version_t & version)
67 {
68 m_productVersion = version;
69 }
70
setComponentVersion(const version_t & version)71 void EncoreBootImage::setComponentVersion(const version_t & version)
72 {
73 m_componentVersion = version;
74 }
75
76 //! \todo Optimize writing section data. Right now it only writes one block at a
77 //! time, which is of course quite slow (in relative terms).
78 //! \todo Refactor this into several different methods for writing each region
79 //! of the image. Use a context structure to keep track of shared data between
80 //! each of the methods.
81 //! \todo Refactor the section and boot tag writing code to only have a single
82 //! copy of the block writing and encryption loop.
writeToStream(std::ostream & stream)83 void EncoreBootImage::writeToStream(std::ostream & stream)
84 {
85 // always generate the session key or DEK even if image is unencrypted
86 m_sessionKey.randomize();
87
88 // prepare to compute CBC-MACs with each KEK
89 unsigned i;
90 smart_array_ptr<RijndaelCBCMAC> macs(0);
91 if (isEncrypted())
92 {
93 macs = new RijndaelCBCMAC[m_keys.size()];
94 for (i=0; i < m_keys.size(); ++i)
95 {
96 RijndaelCBCMAC mac(m_keys[i]);
97 (macs.get())[i] = mac;
98 }
99 }
100
101 // prepare to compute SHA-1 digest over entire image
102 CSHA1 hash;
103 hash.Reset();
104
105 // count of total blocks written to the file
106 unsigned fileBlocksWritten = 0;
107
108 // we need some pieces of the header down below
109 boot_image_header_t imageHeader;
110 prepareImageHeader(imageHeader);
111
112 // write plaintext header
113 {
114 // write header
115 assert(sizeOfPaddingForCipherBlocks(sizeof(boot_image_header_t)) == 0);
116 stream.write(reinterpret_cast<char *>(&imageHeader), sizeof(imageHeader));
117 fileBlocksWritten += numberOfCipherBlocks(sizeof(imageHeader));
118
119 // update CBC-MAC over image header
120 if (isEncrypted())
121 {
122 for (i=0; i < m_keys.size(); ++i)
123 {
124 (macs.get())[i].update(reinterpret_cast<uint8_t *>(&imageHeader), sizeof(imageHeader));
125 }
126 }
127
128 // update SHA-1
129 hash.Update(reinterpret_cast<uint8_t *>(&imageHeader), sizeof(imageHeader));
130 }
131
132 // write plaintext section table
133 {
134 section_iterator_t it = beginSection();
135 for (; it != endSection(); ++it)
136 {
137 Section * section = *it;
138
139 // write header for this section
140 assert(sizeOfPaddingForCipherBlocks(sizeof(section_header_t)) == 0);
141 section_header_t sectionHeader;
142 section->fillSectionHeader(sectionHeader);
143 stream.write(reinterpret_cast<char *>(§ionHeader), sizeof(sectionHeader));
144 fileBlocksWritten += numberOfCipherBlocks(sizeof(sectionHeader));
145
146 // update CBC-MAC over this entry
147 if (isEncrypted())
148 {
149 for (i=0; i < m_keys.size(); ++i)
150 {
151 (macs.get())[i].update(reinterpret_cast<uint8_t *>(§ionHeader), sizeof(sectionHeader));
152 }
153 }
154
155 // update SHA-1
156 hash.Update(reinterpret_cast<uint8_t *>(§ionHeader), sizeof(sectionHeader));
157 }
158 }
159
160 // finished with the CBC-MAC
161 if (isEncrypted())
162 {
163 for (i=0; i < m_keys.size(); ++i)
164 {
165 (macs.get())[i].finalize();
166 }
167 }
168
169 // write key dictionary
170 if (isEncrypted())
171 {
172 key_iterator_t it = beginKeys();
173 for (i=0; it != endKeys(); ++it, ++i)
174 {
175 // write CBC-MAC result for this key, then update SHA-1
176 RijndaelCBCMAC & mac = (macs.get())[i];
177 const RijndaelCBCMAC::block_t & macResult = mac.getMAC();
178 stream.write(reinterpret_cast<const char *>(&macResult), sizeof(RijndaelCBCMAC::block_t));
179 hash.Update(reinterpret_cast<const uint8_t *>(&macResult), sizeof(RijndaelCBCMAC::block_t));
180 fileBlocksWritten++;
181
182 // encrypt DEK with this key, write it out, and update image digest
183 Rijndael cipher;
184 cipher.init(Rijndael::CBC, Rijndael::Encrypt, *it, Rijndael::Key16Bytes, imageHeader.m_iv);
185 AESKey<128>::key_t wrappedSessionKey;
186 cipher.blockEncrypt(m_sessionKey, sizeof(AESKey<128>::key_t) * 8, wrappedSessionKey);
187 stream.write(reinterpret_cast<char *>(&wrappedSessionKey), sizeof(wrappedSessionKey));
188 hash.Update(reinterpret_cast<uint8_t *>(&wrappedSessionKey), sizeof(wrappedSessionKey));
189 fileBlocksWritten++;
190 }
191 }
192
193 // write sections and boot tags
194 {
195 section_iterator_t it = beginSection();
196 for (; it != endSection(); ++it)
197 {
198 section_iterator_t itCopy = it;
199 bool isLastSection = (++itCopy == endSection());
200
201 Section * section = *it;
202 cipher_block_t block;
203 unsigned blockCount = section->getBlockCount();
204 unsigned blocksWritten = 0;
205
206 Rijndael cipher;
207 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv);
208
209 // Compute the number of padding blocks needed to align the section. This first
210 // call to getPadBlockCountForOffset() passes an offset that excludes
211 // the boot tag for this section.
212 unsigned paddingBlocks = getPadBlockCountForSection(section, fileBlocksWritten);
213
214 // Insert nop commands as padding to align the start of the section, if
215 // the section has special alignment requirements.
216 NopCommand nop;
217 while (paddingBlocks--)
218 {
219 blockCount = nop.getBlockCount();
220 blocksWritten = 0;
221 while (blocksWritten < blockCount)
222 {
223 nop.getBlocks(blocksWritten, 1, &block);
224
225 if (isEncrypted())
226 {
227 // re-init after encrypt to update IV
228 cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block);
229 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block);
230 }
231
232 stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t));
233 hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t));
234
235 blocksWritten++;
236 fileBlocksWritten++;
237 }
238 }
239
240 // reinit cipher for boot tag
241 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv);
242
243 // write boot tag
244 TagCommand tag(*section);
245 tag.setLast(isLastSection);
246 if (!isLastSection)
247 {
248 // If this isn't the last section, the tag needs to include any
249 // padding for the next section in its length, otherwise the ROM
250 // won't be able to find the next section's boot tag.
251 unsigned nextSectionOffset = fileBlocksWritten + section->getBlockCount() + 1;
252 tag.setSectionLength(section->getBlockCount() + getPadBlockCountForSection(*itCopy, nextSectionOffset));
253 }
254 blockCount = tag.getBlockCount();
255 blocksWritten = 0;
256 while (blocksWritten < blockCount)
257 {
258 tag.getBlocks(blocksWritten, 1, &block);
259
260 if (isEncrypted())
261 {
262 // re-init after encrypt to update IV
263 cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block);
264 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block);
265 }
266
267 stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t));
268 hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t));
269
270 blocksWritten++;
271 fileBlocksWritten++;
272 }
273
274 // reinit cipher for section data
275 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv);
276
277 // write section data
278 blockCount = section->getBlockCount();
279 blocksWritten = 0;
280 while (blocksWritten < blockCount)
281 {
282 section->getBlocks(blocksWritten, 1, &block);
283
284 // Only encrypt the section contents if the entire boot image is encrypted
285 // and the section doesn't have the "leave unencrypted" flag set. Even if the
286 // section is unencrypted the boot tag will remain encrypted.
287 if (isEncrypted() && !section->getLeaveUnencrypted())
288 {
289 // re-init after encrypt to update IV
290 cipher.blockEncrypt(block, sizeof(cipher_block_t) * 8, block);
291 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, block);
292 }
293
294 stream.write(reinterpret_cast<char *>(&block), sizeof(cipher_block_t));
295 hash.Update(reinterpret_cast<uint8_t *>(&block), sizeof(cipher_block_t));
296
297 blocksWritten++;
298 fileBlocksWritten++;
299 }
300 }
301 }
302
303 // write SHA-1 digest over entire image
304 {
305 // allocate enough room for digest and bytes to pad out to the next cipher block
306 const unsigned padBytes = sizeOfPaddingForCipherBlocks(sizeof(sha1_digest_t));
307 unsigned digestBlocksSize = sizeof(sha1_digest_t) + padBytes;
308 smart_array_ptr<uint8_t> digestBlocks = new uint8_t[digestBlocksSize];
309 hash.Final();
310 hash.GetHash(digestBlocks.get());
311
312 // set the pad bytes to random values
313 RandomNumberGenerator rng;
314 rng.generateBlock(&(digestBlocks.get())[sizeof(sha1_digest_t)], padBytes);
315
316 // encrypt with session key
317 if (isEncrypted())
318 {
319 Rijndael cipher;
320 cipher.init(Rijndael::CBC, Rijndael::Encrypt, m_sessionKey, Rijndael::Key16Bytes, imageHeader.m_iv);
321 cipher.blockEncrypt(digestBlocks.get(), digestBlocksSize * 8, digestBlocks.get());
322 }
323
324 // write to the stream
325 stream.write(reinterpret_cast<char *>(digestBlocks.get()), digestBlocksSize);
326 }
327 }
328
prepareImageHeader(boot_image_header_t & header)329 void EncoreBootImage::prepareImageHeader(boot_image_header_t & header)
330 {
331 // get identifier for the first bootable section
332 Section * firstBootSection = findFirstBootableSection();
333 section_id_t firstBootSectionID = 0;
334 if (firstBootSection)
335 {
336 firstBootSectionID = firstBootSection->getIdentifier();
337 }
338
339 // fill in header fields
340 header.m_signature[0] = 'S';
341 header.m_signature[1] = 'T';
342 header.m_signature[2] = 'M';
343 header.m_signature[3] = 'P';
344 header.m_majorVersion = ROM_BOOT_IMAGE_MAJOR_VERSION;
345 header.m_minorVersion = ROM_BOOT_IMAGE_MINOR_VERSION;
346 header.m_flags = ENDIAN_HOST_TO_LITTLE_U16(m_headerFlags);
347 header.m_imageBlocks = ENDIAN_HOST_TO_LITTLE_U32(getImageSize());
348 header.m_firstBootableSectionID = ENDIAN_HOST_TO_LITTLE_U32(firstBootSectionID);
349 header.m_keyCount = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)m_keys.size());
350 header.m_headerBlocks = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)numberOfCipherBlocks(sizeof(header)));
351 header.m_sectionCount = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)m_sections.size());
352 header.m_sectionHeaderSize = ENDIAN_HOST_TO_LITTLE_U16((uint16_t)numberOfCipherBlocks(sizeof(section_header_t)));
353 header.m_signature2[0] = 's';
354 header.m_signature2[1] = 'g';
355 header.m_signature2[2] = 't';
356 header.m_signature2[3] = 'l';
357 header.m_timestamp = ENDIAN_HOST_TO_LITTLE_U64(getTimestamp());
358 header.m_driveTag = m_driveTag;
359
360 // Prepare version fields by converting them to the correct byte order.
361 header.m_productVersion = m_productVersion;
362 header.m_componentVersion = m_componentVersion;
363 header.m_productVersion.fixByteOrder();
364 header.m_componentVersion.fixByteOrder();
365
366 // the fields are dependant on others
367 header.m_keyDictionaryBlock = ENDIAN_HOST_TO_LITTLE_U16(header.m_headerBlocks + header.m_sectionCount * header.m_sectionHeaderSize);
368 header.m_firstBootTagBlock = ENDIAN_HOST_TO_LITTLE_U32(header.m_keyDictionaryBlock + header.m_keyCount * 2);
369
370 // generate random pad bytes
371 RandomNumberGenerator rng;
372 rng.generateBlock(header.m_padding0, sizeof(header.m_padding0));
373 rng.generateBlock(header.m_padding1, sizeof(header.m_padding1));
374
375 // compute SHA-1 digest over the image header
376 uint8_t * message = reinterpret_cast<uint8_t *>(&header.m_signature);
377 uint32_t length = static_cast<uint32_t>(sizeof(header) - sizeof(header.m_digest)); // include padding
378
379 CSHA1 hash;
380 hash.Reset();
381 hash.Update(message, length);
382 hash.Final();
383 hash.GetHash(header.m_digest);
384 }
385
386 //! Returns the number of microseconds since 00:00 1-1-2000. In actuality, the timestamp
387 //! is only accurate to seconds, and is simply extended out to microseconds.
388 //!
389 //! \todo Use the operating system's low-level functions to get a true microsecond
390 //! timestamp, instead of faking it like we do now.
391 //! \bug The timestamp might be off an hour.
getTimestamp()392 uint64_t EncoreBootImage::getTimestamp()
393 {
394 #if defined(WIN32) || defined(__CYGWIN__) || defined(__sun)
395 struct tm epoch = { 0, 0, 0, 1, 0, 100, 0, 0 }; // 00:00 1-1-2000
396 #else
397 struct tm epoch = { 0, 0, 0, 1, 0, 100, 0, 0, 1, 0, NULL }; // 00:00 1-1-2000
398 #endif
399 time_t epochTime = mktime(&epoch);
400 time_t now = time(NULL);
401 now -= epochTime;
402 uint64_t microNow = uint64_t(now) * 1000000; // convert to microseconds
403 return microNow;
404 }
405
406 //! Scans the section list looking for the first section which has
407 //! the #ROM_SECTION_BOOTABLE flag set on it.
findFirstBootableSection()408 EncoreBootImage::Section * EncoreBootImage::findFirstBootableSection()
409 {
410 section_iterator_t it = beginSection();
411 for (; it != endSection(); ++it)
412 {
413 if ((*it)->getFlags() & ROM_SECTION_BOOTABLE)
414 {
415 return *it;
416 }
417 }
418
419 // no bootable sections were found
420 return NULL;
421 }
422
423 //! The boot tag for \a section is taken into account, thus making the
424 //! result offset point to the first block of the actual section data.
425 //!
426 //! \note The offset will only be valid if all encryption keys and all
427 //! sections have already been added to the image.
getSectionOffset(Section * section)428 uint32_t EncoreBootImage::getSectionOffset(Section * section)
429 {
430 // start with boot image headers
431 uint32_t offset = numberOfCipherBlocks(sizeof(boot_image_header_t)); // header
432 offset += numberOfCipherBlocks(sizeof(section_header_t)) * sectionCount(); // section table
433 offset += 2 * keyCount(); // key dictiontary
434
435 // add up sections before this one
436 section_iterator_t it = beginSection();
437 for (; it != endSection() && *it != section; ++it)
438 {
439 Section * thisSection = *it;
440
441 // insert padding for section alignment
442 offset += getPadBlockCountForSection(thisSection, offset);
443
444 // add one for boot tag associated with this section
445 offset++;
446
447 // now add the section's contents
448 offset += thisSection->getBlockCount();
449 }
450
451 // and add padding for this section
452 offset += getPadBlockCountForSection(section, offset);
453
454 // skip over this section's boot tag
455 offset++;
456
457 return offset;
458 }
459
460 //! Computes the number of blocks of padding required to align \a section while
461 //! taking into account the boot tag that gets inserted before the section contents.
getPadBlockCountForSection(Section * section,unsigned offset)462 unsigned EncoreBootImage::getPadBlockCountForSection(Section * section, unsigned offset)
463 {
464 // Compute the number of padding blocks needed to align the section. This first
465 // call to getPadBlockCountForOffset() passes an offset that excludes
466 // the boot tag for this section.
467 unsigned paddingBlocks = section->getPadBlockCountForOffset(offset);
468
469 // If the pad count comes back as 0 then we need to try again with an offset that
470 // includes the boot tag. This is all because we're aligning the section contents
471 // start and not the section's boot tag.
472 if (paddingBlocks == 0)
473 {
474 paddingBlocks = section->getPadBlockCountForOffset(offset + 1);
475 }
476 // Otherwise if we get a nonzero pad amount then we need to subtract the block
477 // for the section's boot tag from the pad count.
478 else
479 {
480 paddingBlocks--;
481 }
482
483 return paddingBlocks;
484 }
485
getImageSize()486 uint32_t EncoreBootImage::getImageSize()
487 {
488 // determine to total size of the image
489 const uint32_t headerBlocks = numberOfCipherBlocks(sizeof(boot_image_header_t));
490 const uint32_t sectionHeaderSize = numberOfCipherBlocks(sizeof(section_header_t));
491 uint32_t imageBlocks = headerBlocks;
492 imageBlocks += sectionHeaderSize * m_sections.size(); // section table
493 imageBlocks += 2 * m_keys.size(); // key dict
494
495 // add in each section's size
496 section_iterator_t it = beginSection();
497 for (; it != endSection(); ++it)
498 {
499 // add in this section's size, padding to align it, and its boot tag
500 imageBlocks += getPadBlockCountForSection(*it, imageBlocks);
501 imageBlocks += (*it)->getBlockCount();
502 imageBlocks++;
503 }
504
505 // image MAC
506 imageBlocks += 2;
507
508 return imageBlocks;
509 }
510
debugPrint() const511 void EncoreBootImage::debugPrint() const
512 {
513 const_section_iterator_t it = beginSection();
514 for (; it != endSection(); ++it)
515 {
516 const Section * section = *it;
517 section->debugPrint();
518 }
519 }
520
521 //! \param blocks Pointer to the raw data blocks.
522 //! \param count Number of blocks pointed to by \a blocks.
523 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
524 //! by the command. Should be at least 1 for every command. This must not be NULL
525 //! on entry!
526 //!
527 //! \return A new boot command instance.
528 //! \retval NULL The boot command pointed to by \a blocks was not recognized as a known
529 //! command type.
530 //!
531 //! \exception std::runtime_error This exception indicates that a command was recognized
532 //! but contained invalid data. Compare this to a NULL result which indicates that
533 //! no command was recognized at all.
createFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)534 EncoreBootImage::BootCommand * EncoreBootImage::BootCommand::createFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
535 {
536 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
537 BootCommand * command = NULL;
538
539 switch (header->m_tag)
540 {
541 case ROM_NOP_CMD:
542 command = new NopCommand();
543 break;
544 case ROM_TAG_CMD:
545 command = new TagCommand();
546 break;
547 case ROM_LOAD_CMD:
548 command = new LoadCommand();
549 break;
550 case ROM_FILL_CMD:
551 command = new FillCommand();
552 break;
553 case ROM_MODE_CMD:
554 command = new ModeCommand();
555 break;
556 case ROM_JUMP_CMD:
557 command = new JumpCommand();
558 break;
559 case ROM_CALL_CMD:
560 command = new CallCommand();
561 break;
562 }
563
564 if (command)
565 {
566 command->initFromData(blocks, count, consumed);
567 }
568 return command;
569 }
570
571 //! The checksum algorithm is totally straightforward, except that the
572 //! initial checksum byte value is set to 0x5a instead of 0.
calculateChecksum(const boot_command_t & header)573 uint8_t EncoreBootImage::BootCommand::calculateChecksum(const boot_command_t & header)
574 {
575 const uint8_t * bytes = reinterpret_cast<const uint8_t *>(&header);
576 uint8_t checksum = 0x5a;
577 int i;
578
579 // start at one to skip checksum field
580 for (i = 1; i < sizeof(header); ++i)
581 {
582 checksum += bytes[i];
583 }
584
585 return checksum;
586 }
587
588 //! The default implementation returns 0, indicating that no blocks are
589 //! available.
getBlockCount() const590 unsigned EncoreBootImage::BootCommand::getBlockCount() const
591 {
592 return 1 + getDataBlockCount();
593 }
594
595 //! Up to \a maxCount cipher blocks are copied into the buffer pointed to by
596 //! the \a data argument. The index of the first block to copy is
597 //! held in the \a offset argument.
598 //!
599 //! \param offset Starting block number to copy. Zero means the first available block.
600 //! \param maxCount Up to this number of blocks may be copied into \a data. Must be 1 or greater.
601 //! \param data Buffer for outgoing cipher blocks. Must have enough room to hold
602 //! \a maxCount blocks.
603 //!
604 //! \return The number of cipher blocks copied into \a data.
605 //! \retval 0 No more blocks are available and nothing was written to \a data.
606 //!
607 //! \exception std::out_of_range If \a offset is invalid.
getBlocks(unsigned offset,unsigned maxCount,cipher_block_t * data)608 unsigned EncoreBootImage::BootCommand::getBlocks(unsigned offset, unsigned maxCount, cipher_block_t * data)
609 {
610 assert(data);
611 assert(maxCount >= 1);
612
613 // check for valid offset
614 if (offset >= getBlockCount())
615 {
616 throw std::out_of_range("invalid offset");
617 }
618
619 // handle the command header block separately
620 if (offset == 0)
621 {
622 assert(sizeof(boot_command_t) == sizeof(cipher_block_t));
623
624 boot_command_t header;
625 fillCommandHeader(header);
626 memcpy(data, &header, sizeof(header));
627
628 return 1;
629 }
630
631 // handle any data blocks
632 return getDataBlocks(offset - 1, maxCount, data);
633 }
634
635 //! The checksum field of \a testHeader is always computed and checked against itself.
636 //! All other fields are compared to the corresponding value set in \a modelHeader
637 //! if the appropriate flag is set in \a whichFields. For example, the m_address fields
638 //! in \a testHeader and \a modelHeader are compared when the CMD_ADDRESS_FIELD bit
639 //! is set in \a whichFields. An exception is thrown if any comparison fails.
640 //!
641 //! \param modelHeader The baseline header to compare against. Only those fields that
642 //! have corresponding bits set in \a whichFields need to be set.
643 //! \param testHeader The actual command header which is being validated.
644 //! \param whichFields A bitfield used to determine which fields of the boot command
645 //! header are compared. Possible values are:
646 //! - CMD_TAG_FIELD
647 //! - CMD_FLAGS_FIELD
648 //! - CMD_ADDRESS_FIELD
649 //! - CMD_COUNT_FIELD
650 //! - CMD_DATA_FIELD
651 //!
652 //! \exception std::runtime_error Thrown if any requested validation fails.
validateHeader(const boot_command_t * modelHeader,const boot_command_t * testHeader,unsigned whichFields)653 void EncoreBootImage::BootCommand::validateHeader(const boot_command_t * modelHeader, const boot_command_t * testHeader, unsigned whichFields)
654 {
655 // compare all the fields that were requested
656 if ((whichFields & CMD_TAG_FIELD) && (testHeader->m_tag != modelHeader->m_tag))
657 {
658 throw std::runtime_error("invalid tag field");
659 }
660
661 if ((whichFields & CMD_FLAGS_FIELD) && (testHeader->m_flags != modelHeader->m_flags))
662 {
663 throw std::runtime_error("invalid flags field");
664 }
665
666 if ((whichFields & CMD_ADDRESS_FIELD) && (testHeader->m_address != modelHeader->m_address))
667 {
668 throw std::runtime_error("invalid address field");
669 }
670
671 if ((whichFields & CMD_COUNT_FIELD) && (testHeader->m_count != modelHeader->m_count))
672 {
673 throw std::runtime_error("invalid count field");
674 }
675
676 if ((whichFields & CMD_DATA_FIELD) && (testHeader->m_data != modelHeader->m_data))
677 {
678 throw std::runtime_error("invalid data field");
679 }
680
681 // calculate checksum
682 uint8_t testChecksum = calculateChecksum(*testHeader);
683 if (testChecksum != testHeader->m_checksum)
684 {
685 throw std::runtime_error("invalid checksum");
686 }
687 }
688
689 //! Since the NOP command has no data, this method just validates the command header.
690 //! All fields except the checksum are expected to be set to 0.
691 //!
692 //! \param blocks Pointer to the raw data blocks.
693 //! \param count Number of blocks pointed to by \a blocks.
694 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
695 //! by the command. Should be at least 1 for every command. This must not be NULL
696 //! on entry!
697 //!
698 //! \exception std::runtime_error Thrown if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)699 void EncoreBootImage::NopCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
700 {
701 const boot_command_t model = { 0, ROM_NOP_CMD, 0, 0, 0, 0 };
702 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
703 validateHeader(&model, header, CMD_TAG_FIELD | CMD_FLAGS_FIELD | CMD_ADDRESS_FIELD | CMD_COUNT_FIELD | CMD_DATA_FIELD);
704
705 *consumed = 1;
706 }
707
708 //! All fields of the boot command header structure are set to 0, except
709 //! for the checksum. This includes the tag field since the tag value for
710 //! the #ROM_NOP_CMD is zero. And since all fields are zeroes the checksum
711 //! remains the initial checksum value of 0x5a.
fillCommandHeader(boot_command_t & header)712 void EncoreBootImage::NopCommand::fillCommandHeader(boot_command_t & header)
713 {
714 header.m_tag = getTag();
715 header.m_flags = 0;
716 header.m_address = 0;
717 header.m_count = 0;
718 header.m_data = 0;
719 header.m_checksum = calculateChecksum(header); // do this last
720 }
721
debugPrint() const722 void EncoreBootImage::NopCommand::debugPrint() const
723 {
724 Log::log(Logger::INFO2, "\tNOOP\n");
725 }
726
727 //! The identifier, length, and flags fields are taken from \a section.
728 //!
729 //! \todo How does length get set correctly if the length is supposed to include
730 //! this command?
TagCommand(const Section & section)731 EncoreBootImage::TagCommand::TagCommand(const Section & section)
732 {
733 m_sectionIdentifier = section.getIdentifier();
734 m_sectionLength = section.getBlockCount();
735 m_sectionFlags = section.getFlags();
736 }
737
738 //! \param blocks Pointer to the raw data blocks.
739 //! \param count Number of blocks pointed to by \a blocks.
740 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
741 //! by the command. Should be at least 1 for every command. This must not be NULL
742 //! on entry!
743 //!
744 //! \exception std::runtime_error Thrown if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)745 void EncoreBootImage::TagCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
746 {
747 const boot_command_t model = { 0, ROM_TAG_CMD, 0, 0, 0, 0 };
748 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
749 validateHeader(&model, header, CMD_TAG_FIELD);
750
751 // read fields from header
752 m_isLast = (ENDIAN_LITTLE_TO_HOST_U16(header->m_flags) & ROM_LAST_TAG) != 0;
753 m_sectionIdentifier = ENDIAN_LITTLE_TO_HOST_U32(header->m_address);
754 m_sectionLength = ENDIAN_LITTLE_TO_HOST_U32(header->m_count);
755 m_sectionFlags = ENDIAN_LITTLE_TO_HOST_U32(header->m_data);
756
757 *consumed = 1;
758 }
759
760 //! This method currently assumes that the next tag command will come immediately
761 //! after the data for this section.
fillCommandHeader(boot_command_t & header)762 void EncoreBootImage::TagCommand::fillCommandHeader(boot_command_t & header)
763 {
764 header.m_tag = getTag();
765 header.m_flags = ENDIAN_HOST_TO_LITTLE_U16(m_isLast ? ROM_LAST_TAG : 0);
766 header.m_address = ENDIAN_HOST_TO_LITTLE_U32(m_sectionIdentifier);
767 header.m_count = ENDIAN_HOST_TO_LITTLE_U32(m_sectionLength);
768 header.m_data = ENDIAN_HOST_TO_LITTLE_U32(m_sectionFlags);
769 header.m_checksum = calculateChecksum(header); // do this last
770 }
771
debugPrint() const772 void EncoreBootImage::TagCommand::debugPrint() const
773 {
774 Log::log(Logger::INFO2, " BTAG | sec=0x%08x | cnt=0x%08x | flg=0x%08x\n", m_sectionIdentifier, m_sectionLength, m_sectionFlags);
775 }
776
777 //! All fields are set to zero.
778 //!
LoadCommand()779 EncoreBootImage::LoadCommand::LoadCommand()
780 : BootCommand(), m_data(), m_padCount(0), m_length(0), m_address(0), m_loadDCD(false)
781 {
782 fillPadding();
783 }
784
LoadCommand(uint32_t address,const uint8_t * data,uint32_t length)785 EncoreBootImage::LoadCommand::LoadCommand(uint32_t address, const uint8_t * data, uint32_t length)
786 : BootCommand(), m_data(), m_padCount(0), m_length(0), m_address(address), m_loadDCD(false)
787 {
788 fillPadding();
789 setData(data, length);
790 }
791
792 //! \param blocks Pointer to the raw data blocks.
793 //! \param count Number of blocks pointed to by \a blocks.
794 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
795 //! by the command. Should be at least 1 for every command. This must not be NULL
796 //! on entry!
797 //!
798 //! \exception std::runtime_error This exception is thrown if the actual CRC of the load
799 //! data does not match the CRC stored in the command header. Also thrown if the
800 //! \a count parameter is less than the number of data blocks needed for the length
801 //! specified in the command header or if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)802 void EncoreBootImage::LoadCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
803 {
804 // check static fields
805 const boot_command_t model = { 0, ROM_LOAD_CMD, 0, 0, 0, 0 };
806 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
807 validateHeader(&model, header, CMD_TAG_FIELD);
808
809 // read fields from header
810 m_address = ENDIAN_LITTLE_TO_HOST_U32(header->m_address);
811 m_length = ENDIAN_LITTLE_TO_HOST_U32(header->m_count);
812 unsigned crc = ENDIAN_LITTLE_TO_HOST_U32(header->m_data);
813 unsigned dataBlockCount = numberOfCipherBlocks(m_length);
814 m_padCount = sizeOfPaddingForCipherBlocks(dataBlockCount);
815 m_loadDCD = (ENDIAN_LITTLE_TO_HOST_U16(header->m_flags) & ROM_LOAD_DCD) != 0;
816
817 // make sure there are enough blocks
818 if (count - 1 < dataBlockCount)
819 {
820 throw std::runtime_error("not enough cipher blocks for load data");
821 }
822
823 // copy data
824 setData(reinterpret_cast<const uint8_t *>(blocks + 1), m_length);
825
826 // copy padding
827 if (m_padCount)
828 {
829 const uint8_t * firstPadByte = reinterpret_cast<const uint8_t *> (blocks + (1 + dataBlockCount)) - m_padCount;
830 memcpy(m_padding, firstPadByte, m_padCount);
831 }
832
833 // check CRC
834 uint32_t actualCRC = calculateCRC();
835 if (actualCRC != crc)
836 {
837 throw std::runtime_error("load data failed CRC check");
838 }
839
840 *consumed = 1 + dataBlockCount;
841 }
842
843 //! The only thing unique in the load command header is the
844 //! #elftosb::EncoreBootImage::boot_command_t::m_data. It contains a CRC-32 over the
845 //! load data, plus any bytes of padding in the last data cipher block.
fillCommandHeader(boot_command_t & header)846 void EncoreBootImage::LoadCommand::fillCommandHeader(boot_command_t & header)
847 {
848 header.m_tag = getTag();
849 header.m_flags = ENDIAN_HOST_TO_LITTLE_U16(m_loadDCD ? ROM_LOAD_DCD : 0);
850 header.m_address = ENDIAN_HOST_TO_LITTLE_U32(m_address);
851 header.m_count = ENDIAN_HOST_TO_LITTLE_U32(m_length);
852 header.m_data = ENDIAN_HOST_TO_LITTLE_U32(calculateCRC());
853
854 // do this last
855 header.m_checksum = calculateChecksum(header);
856 }
857
858 //! A CRC-32 is calculated over the load data, including any pad bytes
859 //! that are required in the last data cipher block. Including the
860 //! pad bytes in the CRC makes it vastly easier for the ROM to calculate
861 //! the CRC for validation.
calculateCRC() const862 uint32_t EncoreBootImage::LoadCommand::calculateCRC() const
863 {
864 uint32_t result;
865 CRC32 crc;
866 crc.update(m_data, m_length);
867 if (m_padCount)
868 {
869 // include random padding in the CRC
870 crc.update(m_padding, m_padCount);
871 }
872 crc.truncatedFinal(reinterpret_cast<uint8_t*>(&result), sizeof(result));
873
874 return result;
875 }
876
877 //! A local copy of the load data is made. This copy will be disposed of when this object
878 //! is destroyed. This means the caller is free to deallocate \a data after this call
879 //! returns. It also means the caller can pass a pointer into the middle of a buffer for
880 //! \a data and not worry about ownership issues.
setData(const uint8_t * data,uint32_t length)881 void EncoreBootImage::LoadCommand::setData(const uint8_t * data, uint32_t length)
882 {
883 assert(data);
884 assert(length);
885
886 uint8_t * dataCopy = new uint8_t[length];
887 memcpy(dataCopy, data, length);
888
889 m_data = dataCopy;
890 m_length = length;
891
892 m_padCount = sizeOfPaddingForCipherBlocks(m_length);
893 }
894
895 //! \return The number of cipher blocks required to hold the load data,
896 //! rounded up as necessary.
getDataBlockCount() const897 unsigned EncoreBootImage::LoadCommand::getDataBlockCount() const
898 {
899 // round up to the next cipher block
900 return numberOfCipherBlocks(m_length);
901 }
902
903 //! Up to \a maxCount data blocks are copied into the buffer pointed to by
904 //! the \a data argument. This is only a request for \a maxCount blocks.
905 //! A return value of 0 indicates that no more blocks are available. The
906 //! index of the first block to copy is held in the \a offset argument.
907 //! If there are pad bytes needed to fill out the last data block, they
908 //! will be filled with random data in order to add to the "whiteness" of
909 //! the data on both sides of encryption.
910 //!
911 //! \param offset Starting block number to copy. Zero means the first available block.
912 //! \param maxCount Up to this number of blocks may be copied into \a data. Must be 1 or greater.
913 //! \param data Buffer for outgoing data blocks. Must have enough room to hold
914 //! \a maxCount blocks.
915 //!
916 //! \return The number of data blocks copied into \a data.
917 //! \retval 0 No more blocks are available and nothing was written to \a data.
918 //!
919 //! \exception std::out_of_range Thrown when offset is invalid.
920 //!
921 //! \todo fill pad bytes with random bytes
getDataBlocks(unsigned offset,unsigned maxCount,cipher_block_t * data)922 unsigned EncoreBootImage::LoadCommand::getDataBlocks(unsigned offset, unsigned maxCount, cipher_block_t * data)
923 {
924 assert(data);
925 assert(maxCount != 0);
926
927 uint32_t blockCount = getDataBlockCount();
928
929 // check offset
930 if (offset >= blockCount)
931 {
932 throw std::out_of_range("invalid offset");
933 }
934
935 // figure out how many blocks to return
936 unsigned resultBlocks = blockCount - offset;
937 if (resultBlocks > maxCount)
938 {
939 resultBlocks = maxCount;
940
941 // exclude last block if there is padding
942 if (m_padCount && (offset != blockCount - 1) && (offset + resultBlocks == blockCount))
943 {
944 resultBlocks--;
945 }
946 }
947
948 // if there are pad bytes, handle the last block specially
949 if (m_padCount && offset == blockCount - 1)
950 {
951 // copy the remainder of the load data into the first part of the result block
952 unsigned remainderLength = sizeof(cipher_block_t) - m_padCount;
953 memcpy(data, &m_data[sizeof(cipher_block_t) * offset], remainderLength);
954
955 // copy pad bytes we previously generated into the last part of the result block
956 // data is a cipher block pointer, so indexing is done on cipher block
957 // boundaries, thus we need a byte pointer to index properly
958 uint8_t * bytePtr = reinterpret_cast<uint8_t*>(data);
959 memcpy(bytePtr + remainderLength, &m_padding, m_padCount);
960 }
961 else
962 {
963 memcpy(data, &m_data[sizeof(cipher_block_t) * offset], sizeof(cipher_block_t) * resultBlocks);
964 }
965
966 return resultBlocks;
967 }
968
969 //! Fills #m_padding with random bytes that may be used to fill up the last data
970 //! cipher block.
fillPadding()971 void EncoreBootImage::LoadCommand::fillPadding()
972 {
973 RandomNumberGenerator rng;
974 rng.generateBlock(m_padding, sizeof(m_padding));
975 }
976
debugPrint() const977 void EncoreBootImage::LoadCommand::debugPrint() const
978 {
979 Log::log(Logger::INFO2, " LOAD | adr=0x%08x | len=0x%08x | crc=0x%08x | flg=0x%08x\n", m_address, m_length, calculateCRC(), m_loadDCD ? ROM_LOAD_DCD : 0);
980 }
981
982 //! The pattern, address, and count are all initialized to zero, and the pattern
983 //! size is set to a word.
FillCommand()984 EncoreBootImage::FillCommand::FillCommand()
985 : BootCommand(), m_address(0), m_count(0), m_pattern(0)
986 {
987 }
988
989 //! \param blocks Pointer to the raw data blocks.
990 //! \param count Number of blocks pointed to by \a blocks.
991 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
992 //! by the command. Should be at least 1 for every command. This must not be NULL
993 //! on entry!
994 //!
995 //! \exception std::runtime_error Thrown if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)996 void EncoreBootImage::FillCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
997 {
998 // check static fields
999 const boot_command_t model = { 0, ROM_FILL_CMD, 0, 0, 0, 0 };
1000 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
1001 validateHeader(&model, header, CMD_TAG_FIELD | CMD_FLAGS_FIELD);
1002
1003 // read fields from header
1004 m_address = ENDIAN_LITTLE_TO_HOST_U32(header->m_address);
1005 m_count = ENDIAN_LITTLE_TO_HOST_U32(header->m_count);
1006 m_pattern = ENDIAN_LITTLE_TO_HOST_U32(header->m_data);
1007
1008 *consumed = 1;
1009 }
1010
fillCommandHeader(boot_command_t & header)1011 void EncoreBootImage::FillCommand::fillCommandHeader(boot_command_t & header)
1012 {
1013 header.m_tag = getTag();
1014 header.m_flags = 0;
1015 header.m_address = ENDIAN_HOST_TO_LITTLE_U32(m_address);
1016 header.m_count = ENDIAN_HOST_TO_LITTLE_U32(m_count);
1017 header.m_data = ENDIAN_HOST_TO_LITTLE_U32(m_pattern);
1018 header.m_checksum = calculateChecksum(header); // do this last
1019 }
1020
1021 //! Extends the pattern across 32 bits.
1022 //!
setPattern(uint8_t pattern)1023 void EncoreBootImage::FillCommand::setPattern(uint8_t pattern)
1024 {
1025 m_pattern = (pattern << 24) | (pattern << 16) | (pattern << 8) | pattern;
1026 }
1027
1028 //! Extends the pattern across 32 bits.
1029 //!
setPattern(uint16_t pattern)1030 void EncoreBootImage::FillCommand::setPattern(uint16_t pattern)
1031 {
1032 m_pattern = (pattern << 16) | pattern;
1033 }
1034
setPattern(uint32_t pattern)1035 void EncoreBootImage::FillCommand::setPattern(uint32_t pattern)
1036 {
1037 m_pattern = pattern;
1038 }
1039
debugPrint() const1040 void EncoreBootImage::FillCommand::debugPrint() const
1041 {
1042 Log::log(Logger::INFO2, " FILL | adr=0x%08x | len=0x%08x | ptn=0x%08x\n", m_address, m_count, m_pattern);
1043 }
1044
1045 //! \param blocks Pointer to the raw data blocks.
1046 //! \param count Number of blocks pointed to by \a blocks.
1047 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
1048 //! by the command. Should be at least 1 for every command. This must not be NULL
1049 //! on entry!
1050 //!
1051 //! \exception std::runtime_error Thrown if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)1052 void EncoreBootImage::ModeCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
1053 {
1054 // check static fields
1055 const boot_command_t model = { 0, ROM_MODE_CMD, 0, 0, 0, 0 };
1056 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
1057 validateHeader(&model, header, CMD_TAG_FIELD | CMD_FLAGS_FIELD | CMD_ADDRESS_FIELD | CMD_COUNT_FIELD);
1058
1059 // read fields from header
1060 m_mode = ENDIAN_LITTLE_TO_HOST_U32(header->m_data);
1061
1062 *consumed = 1;
1063 }
1064
fillCommandHeader(boot_command_t & header)1065 void EncoreBootImage::ModeCommand::fillCommandHeader(boot_command_t & header)
1066 {
1067 header.m_tag = getTag();
1068 header.m_flags = 0;
1069 header.m_address = 0;
1070 header.m_count = 0;
1071 header.m_data = ENDIAN_HOST_TO_LITTLE_U32(m_mode);
1072 header.m_checksum = calculateChecksum(header); // do this last
1073 }
1074
debugPrint() const1075 void EncoreBootImage::ModeCommand::debugPrint() const
1076 {
1077 Log::log(Logger::INFO2, " MODE | mod=0x%08x\n", m_mode);
1078 }
1079
1080 //! \param blocks Pointer to the raw data blocks.
1081 //! \param count Number of blocks pointed to by \a blocks.
1082 //! \param[out] consumed On exit, this points to the number of cipher blocks that were occupied
1083 //! by the command. Should be at least 1 for every command. This must not be NULL
1084 //! on entry!
1085 //!
1086 //! \exception std::runtime_error Thrown if header fields are invalid.
initFromData(const cipher_block_t * blocks,unsigned count,unsigned * consumed)1087 void EncoreBootImage::JumpCommand::initFromData(const cipher_block_t * blocks, unsigned count, unsigned * consumed)
1088 {
1089 // check static fields
1090 const boot_command_t model = { 0, getTag(), 0, 0, 0, 0 };
1091 const boot_command_t * header = reinterpret_cast<const boot_command_t *>(blocks);
1092 validateHeader(&model, header, CMD_TAG_FIELD | CMD_COUNT_FIELD);
1093
1094 // read fields from header
1095 m_address = ENDIAN_LITTLE_TO_HOST_U32(header->m_address);
1096 m_argument = ENDIAN_LITTLE_TO_HOST_U32(header->m_data);
1097 m_isHAB = (ENDIAN_LITTLE_TO_HOST_U16(header->m_flags) & ROM_HAB_EXEC) != 0;
1098
1099 *consumed = 1;
1100 }
1101
fillCommandHeader(boot_command_t & header)1102 void EncoreBootImage::JumpCommand::fillCommandHeader(boot_command_t & header)
1103 {
1104 header.m_tag = getTag();
1105 header.m_flags = ENDIAN_HOST_TO_LITTLE_U16(m_isHAB ? ROM_HAB_EXEC : 0);
1106 header.m_address = ENDIAN_HOST_TO_LITTLE_U32(m_address);
1107 header.m_count = 0;
1108 header.m_data = ENDIAN_HOST_TO_LITTLE_U32(m_argument);
1109 header.m_checksum = calculateChecksum(header); // do this last
1110 }
1111
debugPrint() const1112 void EncoreBootImage::JumpCommand::debugPrint() const
1113 {
1114 Log::log(Logger::INFO2, " JUMP | adr=0x%08x | arg=0x%08x | flg=0x%08x\n", m_address, m_argument, m_isHAB ? ROM_HAB_EXEC : 0);
1115 }
1116
debugPrint() const1117 void EncoreBootImage::CallCommand::debugPrint() const
1118 {
1119 Log::log(Logger::INFO2, " CALL | adr=0x%08x | arg=0x%08x | flg=0x%08x\n", m_address, m_argument, m_isHAB ? ROM_HAB_EXEC : 0);
1120 }
1121
1122 //! Only if the section has been assigned a boot image owner object will this
1123 //! method be able to fill in the #section_header_t::m_offset field. If no
1124 //! boot image has been set the offset will be set to 0.
fillSectionHeader(section_header_t & header)1125 void EncoreBootImage::Section::fillSectionHeader(section_header_t & header)
1126 {
1127 header.m_tag = getIdentifier();
1128 header.m_offset = 0;
1129 header.m_length = ENDIAN_HOST_TO_LITTLE_U32(getBlockCount());
1130 header.m_flags = ENDIAN_HOST_TO_LITTLE_U32(getFlags());
1131
1132 // if we're attached to an image, we can compute our real offset
1133 if (m_image)
1134 {
1135 header.m_offset = ENDIAN_HOST_TO_LITTLE_U32(m_image->getSectionOffset(this));
1136 }
1137 }
1138
1139 //! The alignment will never be less than 16, since that is the size of the
1140 //! cipher block which is the basic unit of the boot image format. If an
1141 //! alignment less than 16 is set it will be ignored.
1142 //!
1143 //! \param alignment Alignment in bytes for this section. Must be a power of two.
1144 //! Ignored if less than 16.
setAlignment(unsigned alignment)1145 void EncoreBootImage::Section::setAlignment(unsigned alignment)
1146 {
1147 if (alignment > BOOT_IMAGE_MINIMUM_SECTION_ALIGNMENT)
1148 {
1149 m_alignment = alignment;
1150 }
1151 }
1152
1153 //! This method calculates the number of padding blocks that need to be inserted
1154 //! from a given offset for the section to be properly aligned. The value returned
1155 //! is the number of padding blocks that should be inserted starting just after
1156 //! \a offset to align the first cipher block of the section contents. The section's
1157 //! boot tag is \i not taken into account by this method, so the caller must
1158 //! deal with that herself.
1159 //!
1160 //! \param offset Start offset in cipher blocks (not bytes).
1161 //!
1162 //! \return A number of cipher blocks of padding to insert.
getPadBlockCountForOffset(unsigned offset)1163 unsigned EncoreBootImage::Section::getPadBlockCountForOffset(unsigned offset)
1164 {
1165 // convert alignment from byte to block alignment
1166 unsigned blockAlignment = m_alignment >> 4;
1167
1168 unsigned nextAlignmentOffset = (offset + blockAlignment - 1) / blockAlignment * blockAlignment;
1169
1170 return nextAlignmentOffset - offset;
1171 }
1172
~BootSection()1173 EncoreBootImage::BootSection::~BootSection()
1174 {
1175 deleteCommands();
1176 }
1177
deleteCommands()1178 void EncoreBootImage::BootSection::deleteCommands()
1179 {
1180 // dispose of all sections
1181 iterator_t it = begin();
1182 for (; it != end(); ++it)
1183 {
1184 delete *it;
1185 }
1186 }
1187
1188 //! Always returns at least 1 for the required tag command.
1189 //!
getBlockCount() const1190 unsigned EncoreBootImage::BootSection::getBlockCount() const
1191 {
1192 unsigned count = 0;
1193
1194 const_iterator_t it = begin();
1195 for (; it != end(); ++it)
1196 {
1197 count += (*it)->getBlockCount();
1198 }
1199
1200 return count;
1201 }
1202
1203 //! Up to \a maxCount cipher blocks are copied into the buffer pointed to by
1204 //! the \a data argument. A return value of 0 indicates that
1205 //! no more blocks are available. The index of the first block to copy is
1206 //! held in the \a offset argument.
1207 //!
1208 //! \param offset Starting block number to copy. Zero means the first available block.
1209 //! \param maxCount Up to this number of blocks may be copied into \a data.
1210 //! \param data Buffer for outgoing cipher blocks. Must have enough room to hold
1211 //! \a maxCount blocks.
1212 //!
1213 //! \return The number of cipher blocks copied into \a data.
1214 //! \retval 0 No more blocks are available and nothing was written to \a data.
getBlocks(unsigned offset,unsigned maxCount,cipher_block_t * data)1215 unsigned EncoreBootImage::BootSection::getBlocks(unsigned offset, unsigned maxCount, cipher_block_t * data)
1216 {
1217 assert(data);
1218 assert(maxCount >= 1);
1219
1220 unsigned currentOffset = 0;
1221 unsigned readCount = maxCount;
1222
1223 iterator_t it = begin();
1224 for (; it != end(); ++it)
1225 {
1226 BootCommand * command = *it;
1227 unsigned commandBlocks = command->getBlockCount();
1228
1229 // this should never be false!
1230 assert(offset >= currentOffset);
1231
1232 // skip forward until we hit the requested offset
1233 if (offset >= currentOffset + commandBlocks)
1234 {
1235 currentOffset += commandBlocks;
1236 continue;
1237 }
1238
1239 // read from this command
1240 unsigned commandOffset = offset - currentOffset;
1241 unsigned commandRemaining = commandBlocks - commandOffset;
1242 if (readCount > commandRemaining)
1243 {
1244 readCount = commandRemaining;
1245 }
1246 return command->getBlocks(commandOffset, readCount, data);
1247 }
1248
1249 return 0;
1250 }
1251
1252 //! The entire contents of the section must be in memory, pointed to by \a blocks.
1253 //! Any commands that had previously been added to the section are disposed of.
1254 //!
1255 //! \param blocks Pointer to the section contents.
1256 //! \param count Number of blocks pointed to by \a blocks.
1257 //!
1258 //! \exception std::runtime_error Thrown if a boot command cannot be created from
1259 //! the cipher block stream.
fillFromData(const cipher_block_t * blocks,unsigned count)1260 void EncoreBootImage::BootSection::fillFromData(const cipher_block_t * blocks, unsigned count)
1261 {
1262 // start with an empty slate
1263 deleteCommands();
1264
1265 const cipher_block_t * currentBlock = blocks;
1266 unsigned remaining = count;
1267 while (remaining)
1268 {
1269 // try to create a command from the next cipher block. the number of
1270 // blocks the command used up is returned in consumed.
1271 unsigned consumed;
1272 BootCommand * command = BootCommand::createFromData(currentBlock, remaining, &consumed);
1273 if (!command)
1274 {
1275 throw std::runtime_error("invalid boot section data");
1276 }
1277
1278 addCommand(command);
1279
1280 // update loop counters
1281 remaining -= consumed;
1282 currentBlock += consumed;
1283 }
1284 }
1285
debugPrint() const1286 void EncoreBootImage::BootSection::debugPrint() const
1287 {
1288 Log::log(Logger::INFO2, "Boot Section 0x%08x:\n", m_identifier);
1289
1290 const_iterator_t it = begin();
1291 for (; it != end(); ++it)
1292 {
1293 const BootCommand * command = *it;
1294 command->debugPrint();
1295 }
1296 }
1297
1298 //! A copy is made of \a data. Any previously assigned data is disposed of.
1299 //!
setData(const uint8_t * data,unsigned length)1300 void EncoreBootImage::DataSection::setData(const uint8_t * data, unsigned length)
1301 {
1302 m_data = new uint8_t[length];
1303 memcpy(m_data.get(), data, length);
1304 m_length = length;
1305 }
1306
1307 //! The section takes ownership of \a data and will dispose of it using the
1308 //! array delete operator upon its destruction.
setDataNoCopy(const uint8_t * data,unsigned length)1309 void EncoreBootImage::DataSection::setDataNoCopy(const uint8_t * data, unsigned length)
1310 {
1311 m_data = data;
1312 m_length = length;
1313 }
1314
getBlockCount() const1315 unsigned EncoreBootImage::DataSection::getBlockCount() const
1316 {
1317 return numberOfCipherBlocks(m_length);
1318 }
1319
getBlocks(unsigned offset,unsigned maxCount,cipher_block_t * data)1320 unsigned EncoreBootImage::DataSection::getBlocks(unsigned offset, unsigned maxCount, cipher_block_t * data)
1321 {
1322 assert(data);
1323 assert(maxCount != 0);
1324
1325 unsigned blockCount = getBlockCount();
1326 unsigned padCount = sizeOfPaddingForCipherBlocks(m_length);
1327
1328 // check offset
1329 if (offset >= blockCount)
1330 {
1331 throw std::out_of_range("invalid offset");
1332 }
1333
1334 // figure out how many blocks to return
1335 unsigned resultBlocks = blockCount - offset;
1336 if (resultBlocks > maxCount)
1337 {
1338 resultBlocks = maxCount;
1339
1340 // exclude last block if there is padding
1341 if (padCount && (offset != blockCount - 1) && (offset + resultBlocks == blockCount))
1342 {
1343 resultBlocks--;
1344 }
1345 }
1346
1347 // if there are pad bytes, handle the last block specially
1348 if (padCount && offset == blockCount - 1)
1349 {
1350 // copy the remainder of the load data into the first part of the result block
1351 unsigned remainderLength = sizeof(cipher_block_t) - padCount;
1352 memcpy(data, &m_data[sizeOfCipherBlocks(offset)], remainderLength);
1353
1354 // set pad bytes to zeroes.
1355 // data is a cipher block pointer, so indexing is done on cipher block
1356 // boundaries, thus we need a byte pointer to index properly
1357 uint8_t * bytePtr = reinterpret_cast<uint8_t*>(data);
1358 memset(bytePtr + remainderLength, 0, padCount);
1359 }
1360 else
1361 {
1362 memcpy(data, &m_data[sizeOfCipherBlocks(offset)], sizeOfCipherBlocks(resultBlocks));
1363 }
1364
1365 return resultBlocks;
1366 }
1367
debugPrint() const1368 void EncoreBootImage::DataSection::debugPrint() const
1369 {
1370 Log::log(Logger::INFO2, "Data Section 0x%08x: (%d bytes, %d blocks)\n", m_identifier, m_length, getBlockCount());
1371 }
1372
1373