1*993229b6Sjkunz /*
2*993229b6Sjkunz * File: EncoreBootImageReader.cpp
3*993229b6Sjkunz *
4*993229b6Sjkunz * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
5*993229b6Sjkunz * See included license file for license details.
6*993229b6Sjkunz */
7*993229b6Sjkunz
8*993229b6Sjkunz #include "EncoreBootImageReader.h"
9*993229b6Sjkunz #include "SHA1.h"
10*993229b6Sjkunz #include "rijndael.h"
11*993229b6Sjkunz #include "RijndaelCBCMAC.h"
12*993229b6Sjkunz #include <assert.h>
13*993229b6Sjkunz #include "EndianUtilities.h"
14*993229b6Sjkunz #include "Logging.h"
15*993229b6Sjkunz
16*993229b6Sjkunz using namespace elftosb;
17*993229b6Sjkunz
18*993229b6Sjkunz //! \post Stream head points to just after the image header.
19*993229b6Sjkunz //! \exception read_error Thrown if the image header is invalid.
readImageHeader()20*993229b6Sjkunz void EncoreBootImageReader::readImageHeader()
21*993229b6Sjkunz {
22*993229b6Sjkunz // seek to beginning of the stream/file and read the plaintext header
23*993229b6Sjkunz m_stream.seekg(0, std::ios_base::beg);
24*993229b6Sjkunz if (m_stream.read((char *)&m_header, sizeof(m_header)).bad())
25*993229b6Sjkunz {
26*993229b6Sjkunz throw read_error("failed to read image header");
27*993229b6Sjkunz }
28*993229b6Sjkunz
29*993229b6Sjkunz m_header.m_flags = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_flags);
30*993229b6Sjkunz m_header.m_imageBlocks = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_imageBlocks);
31*993229b6Sjkunz m_header.m_firstBootTagBlock = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_firstBootTagBlock);
32*993229b6Sjkunz m_header.m_firstBootableSectionID = ENDIAN_LITTLE_TO_HOST_U32(m_header.m_firstBootableSectionID);
33*993229b6Sjkunz m_header.m_keyCount = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_keyCount);
34*993229b6Sjkunz m_header.m_keyDictionaryBlock = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_keyDictionaryBlock);
35*993229b6Sjkunz m_header.m_headerBlocks = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_headerBlocks);
36*993229b6Sjkunz m_header.m_sectionCount = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_sectionCount);
37*993229b6Sjkunz m_header.m_sectionHeaderSize = ENDIAN_LITTLE_TO_HOST_U16(m_header.m_sectionHeaderSize);
38*993229b6Sjkunz m_header.m_timestamp = ENDIAN_LITTLE_TO_HOST_U64(m_header.m_timestamp);
39*993229b6Sjkunz
40*993229b6Sjkunz // m_header.m_componentVersion.m_major = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_major);
41*993229b6Sjkunz // m_header.m_componentVersion.m_minor = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_minor);
42*993229b6Sjkunz // m_header.m_componentVersion.m_revision = ENDIAN_BIG_TO_HOST_U16(m_header.m_componentVersion.m_revision);
43*993229b6Sjkunz
44*993229b6Sjkunz // m_header.m_productVersion.m_major = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_major);
45*993229b6Sjkunz // m_header.m_productVersion.m_minor = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_minor);
46*993229b6Sjkunz // m_header.m_productVersion.m_revision = ENDIAN_BIG_TO_HOST_U16(m_header.m_productVersion.m_revision);
47*993229b6Sjkunz
48*993229b6Sjkunz // check header signature 1
49*993229b6Sjkunz if (m_header.m_signature[0] != 'S' || m_header.m_signature[1] != 'T' || m_header.m_signature[2] != 'M' || m_header.m_signature[3] != 'P')
50*993229b6Sjkunz {
51*993229b6Sjkunz throw read_error("invalid signature 1");
52*993229b6Sjkunz }
53*993229b6Sjkunz
54*993229b6Sjkunz // check header signature 2 for version 1.1 and greater
55*993229b6Sjkunz if ((m_header.m_majorVersion > 1 || (m_header.m_majorVersion == 1 && m_header.m_minorVersion >= 1)) && (m_header.m_signature2[0] != 's' || m_header.m_signature2[1] != 'g' || m_header.m_signature2[2] != 't' || m_header.m_signature2[3] != 'l'))
56*993229b6Sjkunz {
57*993229b6Sjkunz // throw read_error("invalid signature 2");
58*993229b6Sjkunz Log::log(Logger::WARNING, "warning: invalid signature 2\n");
59*993229b6Sjkunz }
60*993229b6Sjkunz }
61*993229b6Sjkunz
62*993229b6Sjkunz //! \pre The image header must have already been read with a call to readImageHeader().
63*993229b6Sjkunz //!
computeHeaderDigest(sha1_digest_t & digest)64*993229b6Sjkunz void EncoreBootImageReader::computeHeaderDigest(sha1_digest_t & digest)
65*993229b6Sjkunz {
66*993229b6Sjkunz CSHA1 hash;
67*993229b6Sjkunz hash.Reset();
68*993229b6Sjkunz hash.Update((uint8_t *)&m_header.m_signature, sizeof(m_header) - sizeof(sha1_digest_t));
69*993229b6Sjkunz hash.Final();
70*993229b6Sjkunz hash.GetHash(digest);
71*993229b6Sjkunz }
72*993229b6Sjkunz
73*993229b6Sjkunz //! \pre The image header must have already been read.
74*993229b6Sjkunz //! \pre The DEK must have been found already.
75*993229b6Sjkunz //! \post The stream head is at the end of the digest.
readImageDigest()76*993229b6Sjkunz void EncoreBootImageReader::readImageDigest()
77*993229b6Sjkunz {
78*993229b6Sjkunz unsigned digestPosition = sizeOfCipherBlocks(m_header.m_imageBlocks - 2);
79*993229b6Sjkunz m_stream.seekg(digestPosition, std::ios_base::beg);
80*993229b6Sjkunz
81*993229b6Sjkunz // read the two cipher blocks containing the digest, including padding
82*993229b6Sjkunz cipher_block_t digestBlocks[2];
83*993229b6Sjkunz if (m_stream.read((char *)&digestBlocks, sizeof(digestBlocks)).bad())
84*993229b6Sjkunz {
85*993229b6Sjkunz throw read_error("failed to read image digest");
86*993229b6Sjkunz }
87*993229b6Sjkunz
88*993229b6Sjkunz // decrypt the digest
89*993229b6Sjkunz if (isEncrypted())
90*993229b6Sjkunz {
91*993229b6Sjkunz Rijndael cipher;
92*993229b6Sjkunz cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv);
93*993229b6Sjkunz cipher.blockDecrypt((uint8_t *)&digestBlocks, sizeof(digestBlocks) * 8, (uint8_t *)&digestBlocks);
94*993229b6Sjkunz }
95*993229b6Sjkunz
96*993229b6Sjkunz // copy the digest out of the padded blocks
97*993229b6Sjkunz memcpy(m_digest, &digestBlocks, sizeof(m_digest));
98*993229b6Sjkunz }
99*993229b6Sjkunz
100*993229b6Sjkunz //! \pre The image header must have already been read with a call to readImageHeader().
101*993229b6Sjkunz //! \post The stream head is at the end of the image minus the last two cipher blocks.
102*993229b6Sjkunz //! \param digest Where to store the resulting digest.
103*993229b6Sjkunz //! \exception read_error Thrown if the image header is invalid.
computeImageDigest(sha1_digest_t & digest)104*993229b6Sjkunz void EncoreBootImageReader::computeImageDigest(sha1_digest_t & digest)
105*993229b6Sjkunz {
106*993229b6Sjkunz m_stream.seekg(0, std::ios_base::beg);
107*993229b6Sjkunz
108*993229b6Sjkunz CSHA1 hash;
109*993229b6Sjkunz hash.Reset();
110*993229b6Sjkunz
111*993229b6Sjkunz unsigned blockCount = m_header.m_imageBlocks - 2; // exclude digest at end of image
112*993229b6Sjkunz while (blockCount--)
113*993229b6Sjkunz {
114*993229b6Sjkunz cipher_block_t block;
115*993229b6Sjkunz if (m_stream.read((char *)&block, sizeof(block)).bad())
116*993229b6Sjkunz {
117*993229b6Sjkunz throw read_error("failed to read block while computing image digest");
118*993229b6Sjkunz }
119*993229b6Sjkunz hash.Update(block, sizeof(block));
120*993229b6Sjkunz }
121*993229b6Sjkunz
122*993229b6Sjkunz hash.Final();
123*993229b6Sjkunz hash.GetHash(digest);
124*993229b6Sjkunz }
125*993229b6Sjkunz
126*993229b6Sjkunz //! \pre Image header must have been read before this method is called.
127*993229b6Sjkunz //!
readSectionTable()128*993229b6Sjkunz void EncoreBootImageReader::readSectionTable()
129*993229b6Sjkunz {
130*993229b6Sjkunz // seek to the table
131*993229b6Sjkunz m_stream.seekg(sizeOfCipherBlocks(m_header.m_headerBlocks), std::ios_base::beg);
132*993229b6Sjkunz
133*993229b6Sjkunz unsigned sectionCount = m_header.m_sectionCount;
134*993229b6Sjkunz while (sectionCount--)
135*993229b6Sjkunz {
136*993229b6Sjkunz EncoreBootImage::section_header_t header;
137*993229b6Sjkunz if (m_stream.read((char *)&header, sizeof(header)).bad())
138*993229b6Sjkunz {
139*993229b6Sjkunz throw read_error("failed to read section header");
140*993229b6Sjkunz }
141*993229b6Sjkunz
142*993229b6Sjkunz // swizzle section header
143*993229b6Sjkunz header.m_tag = ENDIAN_LITTLE_TO_HOST_U32(header.m_tag);
144*993229b6Sjkunz header.m_offset = ENDIAN_LITTLE_TO_HOST_U32(header.m_offset);
145*993229b6Sjkunz header.m_length = ENDIAN_LITTLE_TO_HOST_U32(header.m_length);
146*993229b6Sjkunz header.m_flags = ENDIAN_LITTLE_TO_HOST_U32(header.m_flags);
147*993229b6Sjkunz
148*993229b6Sjkunz m_sections.push_back(header);
149*993229b6Sjkunz }
150*993229b6Sjkunz }
151*993229b6Sjkunz
152*993229b6Sjkunz //! Requires that an OTP key has been provided as the sole argument. Passing the
153*993229b6Sjkunz //! key into this method lets the caller search the key dictionary for any number
154*993229b6Sjkunz //! of keys and determine which are valid. If \a kek is found in the dictionary,
155*993229b6Sjkunz //! the decrypted DEK is saved and true is returned. A result of false means
156*993229b6Sjkunz //! that \a kek was not found.
157*993229b6Sjkunz //!
158*993229b6Sjkunz //! \pre The image header and section table must have been read already.
159*993229b6Sjkunz //! \post The stream head points somewhere inside the key dictionary, or just after it.
160*993229b6Sjkunz //! \post If the search was successful, the #m_dek member will contain the decrypted
161*993229b6Sjkunz //! session key. Otherwise #m_dek is not modified.
162*993229b6Sjkunz //! \param kek Search for this KEK in the dictionary.
163*993229b6Sjkunz //! \retval true The DEK was found and decrypted. True is also returned when the
164*993229b6Sjkunz //! image is not encrypted at all.
165*993229b6Sjkunz //! \retval false No matching key entry was found. The image cannot be decrypted.
readKeyDictionary(const AESKey<128> & kek)166*993229b6Sjkunz bool EncoreBootImageReader::readKeyDictionary(const AESKey<128> & kek)
167*993229b6Sjkunz {
168*993229b6Sjkunz // do nothing if the image is not encrypted
169*993229b6Sjkunz if (!isEncrypted())
170*993229b6Sjkunz {
171*993229b6Sjkunz return true;
172*993229b6Sjkunz }
173*993229b6Sjkunz
174*993229b6Sjkunz // first compute a CBC-MAC over the image header with our KEK
175*993229b6Sjkunz RijndaelCBCMAC mac(kek);
176*993229b6Sjkunz mac.update((const uint8_t *)&m_header, sizeof(m_header));
177*993229b6Sjkunz
178*993229b6Sjkunz // run the CBC-MAC over each entry in the section table too
179*993229b6Sjkunz section_array_t::iterator it = m_sections.begin();
180*993229b6Sjkunz for (; it != m_sections.end(); ++it)
181*993229b6Sjkunz {
182*993229b6Sjkunz mac.update((const uint8_t *)&(*it), sizeof(EncoreBootImage::section_header_t));
183*993229b6Sjkunz }
184*993229b6Sjkunz
185*993229b6Sjkunz // get the CBC-MAC result
186*993229b6Sjkunz mac.finalize();
187*993229b6Sjkunz const RijndaelCBCMAC::block_t & macResult = mac.getMAC();
188*993229b6Sjkunz
189*993229b6Sjkunz // seek to the key dictionary
190*993229b6Sjkunz m_stream.seekg(sizeOfCipherBlocks(m_header.m_keyDictionaryBlock), std::ios_base::beg);
191*993229b6Sjkunz
192*993229b6Sjkunz // decipher each key entry
193*993229b6Sjkunz unsigned entries = m_header.m_keyCount;
194*993229b6Sjkunz while (entries--)
195*993229b6Sjkunz {
196*993229b6Sjkunz // read the entry
197*993229b6Sjkunz EncoreBootImage::dek_dictionary_entry_t entry;
198*993229b6Sjkunz if (m_stream.read((char *)&entry, sizeof(entry)).bad())
199*993229b6Sjkunz {
200*993229b6Sjkunz throw read_error("failed to read key dictionary entry");
201*993229b6Sjkunz }
202*993229b6Sjkunz
203*993229b6Sjkunz // compare the CBC-MAC we computed with the one in this entry
204*993229b6Sjkunz if (memcmp(macResult, entry.m_mac, sizeof(cipher_block_t)) == 0)
205*993229b6Sjkunz {
206*993229b6Sjkunz // it's a match! now decrypt this entry's key in place
207*993229b6Sjkunz Rijndael cipher;
208*993229b6Sjkunz cipher.init(Rijndael::CBC, Rijndael::Decrypt, kek, Rijndael::Key16Bytes, m_header.m_iv);
209*993229b6Sjkunz cipher.blockDecrypt(entry.m_dek, sizeof(entry.m_dek) * 8, entry.m_dek);
210*993229b6Sjkunz
211*993229b6Sjkunz m_dek = entry.m_dek;
212*993229b6Sjkunz memset(entry.m_dek, 0, sizeof(entry.m_dek)); // wipe the key value from memory
213*993229b6Sjkunz return true;
214*993229b6Sjkunz }
215*993229b6Sjkunz }
216*993229b6Sjkunz
217*993229b6Sjkunz // if we exit the loop normally then no matching MAC was found
218*993229b6Sjkunz return false;
219*993229b6Sjkunz }
220*993229b6Sjkunz
221*993229b6Sjkunz //! Before the boot tag is added to the #m_bootTags member, some basic checks are performed.
222*993229b6Sjkunz //! The command tag field is checked to make sure it matches #ROM_TAG_CMD. And
223*993229b6Sjkunz //! the checksum field is verified to be sure it's correct.
224*993229b6Sjkunz //!
225*993229b6Sjkunz //! After the call to this method returns, the array of boot tags is accessible
226*993229b6Sjkunz //! with the getBootTags() method. The array is sorted in the order in which
227*993229b6Sjkunz //! the boot tags appeared in the image.
228*993229b6Sjkunz //!
229*993229b6Sjkunz //! \pre Image header must have been read.
230*993229b6Sjkunz //! \pre Key dictionary must have been read and a valid DEK found.
231*993229b6Sjkunz //! \post The stream head is left pointing just after the last boot tag.
232*993229b6Sjkunz //! \exception read_error A failure to read the boot tag, or a failure on one
233*993229b6Sjkunz //! of the consistency checks will cause this exception to be thrown.
readBootTags()234*993229b6Sjkunz void EncoreBootImageReader::readBootTags()
235*993229b6Sjkunz {
236*993229b6Sjkunz assert(m_header.m_firstBootTagBlock != 0);
237*993229b6Sjkunz
238*993229b6Sjkunz unsigned bootTagOffset = m_header.m_firstBootTagBlock;
239*993229b6Sjkunz
240*993229b6Sjkunz while (1)
241*993229b6Sjkunz {
242*993229b6Sjkunz // seek to this boot tag and read it into a temporary buffer
243*993229b6Sjkunz EncoreBootImage::boot_command_t header;
244*993229b6Sjkunz m_stream.seekg(sizeOfCipherBlocks(bootTagOffset), std::ios_base::beg);
245*993229b6Sjkunz if (m_stream.read((char *)&header, sizeof(header)).bad())
246*993229b6Sjkunz {
247*993229b6Sjkunz throw read_error("failed to read boot tag");
248*993229b6Sjkunz }
249*993229b6Sjkunz
250*993229b6Sjkunz // swizzle to command header
251*993229b6Sjkunz header.m_flags = ENDIAN_LITTLE_TO_HOST_U16(header.m_flags);
252*993229b6Sjkunz header.m_address = ENDIAN_LITTLE_TO_HOST_U32(header.m_address);
253*993229b6Sjkunz header.m_count = ENDIAN_LITTLE_TO_HOST_U32(header.m_count);
254*993229b6Sjkunz header.m_data = ENDIAN_LITTLE_TO_HOST_U32(header.m_data);
255*993229b6Sjkunz
256*993229b6Sjkunz // decrypt in place
257*993229b6Sjkunz if (isEncrypted())
258*993229b6Sjkunz {
259*993229b6Sjkunz Rijndael cipher;
260*993229b6Sjkunz cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv);
261*993229b6Sjkunz cipher.blockDecrypt((uint8_t *)&header, sizeof(header) * 8, (uint8_t *)&header);
262*993229b6Sjkunz }
263*993229b6Sjkunz
264*993229b6Sjkunz // perform some basic checks
265*993229b6Sjkunz if (header.m_tag != EncoreBootImage::ROM_TAG_CMD)
266*993229b6Sjkunz {
267*993229b6Sjkunz throw read_error("boot tag is wrong command type");
268*993229b6Sjkunz }
269*993229b6Sjkunz
270*993229b6Sjkunz uint8_t checksum = calculateCommandChecksum(header);
271*993229b6Sjkunz if (checksum != header.m_checksum)
272*993229b6Sjkunz {
273*993229b6Sjkunz throw read_error("boot tag checksum is invalid");
274*993229b6Sjkunz }
275*993229b6Sjkunz
276*993229b6Sjkunz // save this boot tag
277*993229b6Sjkunz m_bootTags.push_back(header);
278*993229b6Sjkunz
279*993229b6Sjkunz // and finally, update offset and break out of loop
280*993229b6Sjkunz bootTagOffset += header.m_count + 1; // include this boot tag in offset
281*993229b6Sjkunz if (header.m_flags & EncoreBootImage::ROM_LAST_TAG || bootTagOffset >= m_header.m_imageBlocks - 2)
282*993229b6Sjkunz {
283*993229b6Sjkunz break;
284*993229b6Sjkunz }
285*993229b6Sjkunz }
286*993229b6Sjkunz }
287*993229b6Sjkunz
calculateCommandChecksum(EncoreBootImage::boot_command_t & header)288*993229b6Sjkunz uint8_t EncoreBootImageReader::calculateCommandChecksum(EncoreBootImage::boot_command_t & header)
289*993229b6Sjkunz {
290*993229b6Sjkunz uint8_t * bytes = reinterpret_cast<uint8_t *>(&header);
291*993229b6Sjkunz uint8_t checksum = 0x5a;
292*993229b6Sjkunz int i;
293*993229b6Sjkunz
294*993229b6Sjkunz // start at one to skip checksum field
295*993229b6Sjkunz for (i = 1; i < sizeof(header); ++i)
296*993229b6Sjkunz {
297*993229b6Sjkunz checksum += bytes[i];
298*993229b6Sjkunz }
299*993229b6Sjkunz
300*993229b6Sjkunz return checksum;
301*993229b6Sjkunz }
302*993229b6Sjkunz
303*993229b6Sjkunz //! \param index The index of the section to read.
304*993229b6Sjkunz //!
305*993229b6Sjkunz //! \pre Both the image header and section table must have been read already before
306*993229b6Sjkunz //! calling this method.
307*993229b6Sjkunz //! \exception read_error This exception is raised if the stream reports an error while
308*993229b6Sjkunz //! trying to read from the section.
readSection(unsigned index)309*993229b6Sjkunz EncoreBootImage::Section * EncoreBootImageReader::readSection(unsigned index)
310*993229b6Sjkunz {
311*993229b6Sjkunz // look up section header
312*993229b6Sjkunz assert(index < m_sections.size());
313*993229b6Sjkunz EncoreBootImage::section_header_t & header = m_sections[index];
314*993229b6Sjkunz
315*993229b6Sjkunz // seek to the section
316*993229b6Sjkunz m_stream.seekg(sizeOfCipherBlocks(header.m_offset), std::ios_base::beg);
317*993229b6Sjkunz
318*993229b6Sjkunz uint8_t * contents = NULL;
319*993229b6Sjkunz try
320*993229b6Sjkunz {
321*993229b6Sjkunz // allocate memory for the section contents and read the whole thing
322*993229b6Sjkunz unsigned contentLength = sizeOfCipherBlocks(header.m_length);
323*993229b6Sjkunz contents = new uint8_t[contentLength];
324*993229b6Sjkunz if (m_stream.read((char *)contents, contentLength).bad())
325*993229b6Sjkunz {
326*993229b6Sjkunz throw read_error("failed to read section");
327*993229b6Sjkunz }
328*993229b6Sjkunz
329*993229b6Sjkunz // decrypt the entire section at once, if the image is encrypted and
330*993229b6Sjkunz // the cleartext flag is not set
331*993229b6Sjkunz if (isEncrypted() && (header.m_flags & EncoreBootImage::ROM_SECTION_CLEARTEXT) == 0)
332*993229b6Sjkunz {
333*993229b6Sjkunz Rijndael cipher;
334*993229b6Sjkunz cipher.init(Rijndael::CBC, Rijndael::Decrypt, m_dek, Rijndael::Key16Bytes, m_header.m_iv);
335*993229b6Sjkunz cipher.blockDecrypt(contents, contentLength * 8, contents);
336*993229b6Sjkunz }
337*993229b6Sjkunz
338*993229b6Sjkunz // create section object
339*993229b6Sjkunz EncoreBootImage::Section * resultSection = NULL;
340*993229b6Sjkunz if (header.m_flags & EncoreBootImage::ROM_SECTION_BOOTABLE)
341*993229b6Sjkunz {
342*993229b6Sjkunz // a boot command section.
343*993229b6Sjkunz EncoreBootImage::BootSection * bootSection = new EncoreBootImage::BootSection(header.m_tag);
344*993229b6Sjkunz
345*993229b6Sjkunz bootSection->fillFromData((cipher_block_t *)contents, header.m_length);
346*993229b6Sjkunz
347*993229b6Sjkunz resultSection = bootSection;
348*993229b6Sjkunz }
349*993229b6Sjkunz else
350*993229b6Sjkunz {
351*993229b6Sjkunz // this is a raw data section
352*993229b6Sjkunz EncoreBootImage::DataSection * dataSection = new EncoreBootImage::DataSection(header.m_tag);
353*993229b6Sjkunz dataSection->setDataNoCopy(contents, contentLength);
354*993229b6Sjkunz contents = NULL;
355*993229b6Sjkunz resultSection = dataSection;
356*993229b6Sjkunz }
357*993229b6Sjkunz
358*993229b6Sjkunz return resultSection;
359*993229b6Sjkunz }
360*993229b6Sjkunz catch (...)
361*993229b6Sjkunz {
362*993229b6Sjkunz if (contents)
363*993229b6Sjkunz {
364*993229b6Sjkunz delete [] contents;
365*993229b6Sjkunz }
366*993229b6Sjkunz throw;
367*993229b6Sjkunz }
368*993229b6Sjkunz }
369*993229b6Sjkunz
370*993229b6Sjkunz
371