payload.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #
  2. # Copyright (C) 2013 The Android Open Source Project
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. """Tools for reading, verifying and applying Chrome OS update payloads."""
  17. from __future__ import print_function
  18. import hashlib
  19. import struct
  20. from update_payload import applier
  21. from update_payload import checker
  22. from update_payload import common
  23. from update_payload import update_metadata_pb2
  24. from update_payload.error import PayloadError
  25. #
  26. # Helper functions.
  27. #
  28. def _ReadInt(file_obj, size, is_unsigned, hasher=None):
  29. """Reads a binary-encoded integer from a file.
  30. It will do the correct conversion based on the reported size and whether or
  31. not a signed number is expected. Assumes a network (big-endian) byte
  32. ordering.
  33. Args:
  34. file_obj: a file object
  35. size: the integer size in bytes (2, 4 or 8)
  36. is_unsigned: whether it is signed or not
  37. hasher: an optional hasher to pass the value through
  38. Returns:
  39. An "unpacked" (Python) integer value.
  40. Raises:
  41. PayloadError if an read error occurred.
  42. """
  43. return struct.unpack(common.IntPackingFmtStr(size, is_unsigned),
  44. common.Read(file_obj, size, hasher=hasher))[0]
  45. #
  46. # Update payload.
  47. #
  48. class Payload(object):
  49. """Chrome OS update payload processor."""
  50. class _PayloadHeader(object):
  51. """Update payload header struct."""
  52. # Header constants; sizes are in bytes.
  53. _MAGIC = 'CrAU'
  54. _VERSION_SIZE = 8
  55. _MANIFEST_LEN_SIZE = 8
  56. _METADATA_SIGNATURE_LEN_SIZE = 4
  57. def __init__(self):
  58. self.version = None
  59. self.manifest_len = None
  60. self.metadata_signature_len = None
  61. self.size = None
  62. def ReadFromPayload(self, payload_file, hasher=None):
  63. """Reads the payload header from a file.
  64. Reads the payload header from the |payload_file| and updates the |hasher|
  65. if one is passed. The parsed header is stored in the _PayloadHeader
  66. instance attributes.
  67. Args:
  68. payload_file: a file object
  69. hasher: an optional hasher to pass the value through
  70. Returns:
  71. None.
  72. Raises:
  73. PayloadError if a read error occurred or the header is invalid.
  74. """
  75. # Verify magic
  76. magic = common.Read(payload_file, len(self._MAGIC), hasher=hasher)
  77. if magic != self._MAGIC:
  78. raise PayloadError('invalid payload magic: %s' % magic)
  79. self.version = _ReadInt(payload_file, self._VERSION_SIZE, True,
  80. hasher=hasher)
  81. self.manifest_len = _ReadInt(payload_file, self._MANIFEST_LEN_SIZE, True,
  82. hasher=hasher)
  83. self.size = (len(self._MAGIC) + self._VERSION_SIZE +
  84. self._MANIFEST_LEN_SIZE)
  85. self.metadata_signature_len = 0
  86. if self.version == common.BRILLO_MAJOR_PAYLOAD_VERSION:
  87. self.size += self._METADATA_SIGNATURE_LEN_SIZE
  88. self.metadata_signature_len = _ReadInt(
  89. payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True,
  90. hasher=hasher)
  91. def __init__(self, payload_file, payload_file_offset=0):
  92. """Initialize the payload object.
  93. Args:
  94. payload_file: update payload file object open for reading
  95. payload_file_offset: the offset of the actual payload
  96. """
  97. self.payload_file = payload_file
  98. self.payload_file_offset = payload_file_offset
  99. self.manifest_hasher = None
  100. self.is_init = False
  101. self.header = None
  102. self.manifest = None
  103. self.data_offset = None
  104. self.metadata_signature = None
  105. self.metadata_size = None
  106. def _ReadHeader(self):
  107. """Reads and returns the payload header.
  108. Returns:
  109. A payload header object.
  110. Raises:
  111. PayloadError if a read error occurred.
  112. """
  113. header = self._PayloadHeader()
  114. header.ReadFromPayload(self.payload_file, self.manifest_hasher)
  115. return header
  116. def _ReadManifest(self):
  117. """Reads and returns the payload manifest.
  118. Returns:
  119. A string containing the payload manifest in binary form.
  120. Raises:
  121. PayloadError if a read error occurred.
  122. """
  123. if not self.header:
  124. raise PayloadError('payload header not present')
  125. return common.Read(self.payload_file, self.header.manifest_len,
  126. hasher=self.manifest_hasher)
  127. def _ReadMetadataSignature(self):
  128. """Reads and returns the metadata signatures.
  129. Returns:
  130. A string containing the metadata signatures protobuf in binary form or
  131. an empty string if no metadata signature found in the payload.
  132. Raises:
  133. PayloadError if a read error occurred.
  134. """
  135. if not self.header:
  136. raise PayloadError('payload header not present')
  137. return common.Read(
  138. self.payload_file, self.header.metadata_signature_len,
  139. offset=self.payload_file_offset + self.header.size +
  140. self.header.manifest_len)
  141. def ReadDataBlob(self, offset, length):
  142. """Reads and returns a single data blob from the update payload.
  143. Args:
  144. offset: offset to the beginning of the blob from the end of the manifest
  145. length: the blob's length
  146. Returns:
  147. A string containing the raw blob data.
  148. Raises:
  149. PayloadError if a read error occurred.
  150. """
  151. return common.Read(self.payload_file, length,
  152. offset=self.payload_file_offset + self.data_offset +
  153. offset)
  154. def Init(self):
  155. """Initializes the payload object.
  156. This is a prerequisite for any other public API call.
  157. Raises:
  158. PayloadError if object already initialized or fails to initialize
  159. correctly.
  160. """
  161. if self.is_init:
  162. raise PayloadError('payload object already initialized')
  163. self.manifest_hasher = hashlib.sha256()
  164. # Read the file header.
  165. self.payload_file.seek(self.payload_file_offset)
  166. self.header = self._ReadHeader()
  167. # Read the manifest.
  168. manifest_raw = self._ReadManifest()
  169. self.manifest = update_metadata_pb2.DeltaArchiveManifest()
  170. self.manifest.ParseFromString(manifest_raw)
  171. # Read the metadata signature (if any).
  172. metadata_signature_raw = self._ReadMetadataSignature()
  173. if metadata_signature_raw:
  174. self.metadata_signature = update_metadata_pb2.Signatures()
  175. self.metadata_signature.ParseFromString(metadata_signature_raw)
  176. self.metadata_size = self.header.size + self.header.manifest_len
  177. self.data_offset = self.metadata_size + self.header.metadata_signature_len
  178. self.is_init = True
  179. def Describe(self):
  180. """Emits the payload embedded description data to standard output."""
  181. def _DescribeImageInfo(description, image_info):
  182. """Display info about the image."""
  183. def _DisplayIndentedValue(name, value):
  184. print(' {:<14} {}'.format(name+':', value))
  185. print('%s:' % description)
  186. _DisplayIndentedValue('Channel', image_info.channel)
  187. _DisplayIndentedValue('Board', image_info.board)
  188. _DisplayIndentedValue('Version', image_info.version)
  189. _DisplayIndentedValue('Key', image_info.key)
  190. if image_info.build_channel != image_info.channel:
  191. _DisplayIndentedValue('Build channel', image_info.build_channel)
  192. if image_info.build_version != image_info.version:
  193. _DisplayIndentedValue('Build version', image_info.build_version)
  194. if self.manifest.HasField('old_image_info'):
  195. _DescribeImageInfo('Old Image', self.manifest.old_image_info)
  196. if self.manifest.HasField('new_image_info'):
  197. _DescribeImageInfo('New Image', self.manifest.new_image_info)
  198. def _AssertInit(self):
  199. """Raises an exception if the object was not initialized."""
  200. if not self.is_init:
  201. raise PayloadError('payload object not initialized')
  202. def ResetFile(self):
  203. """Resets the offset of the payload file to right past the manifest."""
  204. self.payload_file.seek(self.payload_file_offset + self.data_offset)
  205. def IsDelta(self):
  206. """Returns True iff the payload appears to be a delta."""
  207. self._AssertInit()
  208. return (self.manifest.HasField('old_kernel_info') or
  209. self.manifest.HasField('old_rootfs_info') or
  210. any(partition.HasField('old_partition_info')
  211. for partition in self.manifest.partitions))
  212. def IsFull(self):
  213. """Returns True iff the payload appears to be a full."""
  214. return not self.IsDelta()
  215. def Check(self, pubkey_file_name=None, metadata_sig_file=None,
  216. metadata_size=0, report_out_file=None, assert_type=None,
  217. block_size=0, part_sizes=None, allow_unhashed=False,
  218. disabled_tests=()):
  219. """Checks the payload integrity.
  220. Args:
  221. pubkey_file_name: public key used for signature verification
  222. metadata_sig_file: metadata signature, if verification is desired
  223. metadata_size: metadata size, if verification is desired
  224. report_out_file: file object to dump the report to
  225. assert_type: assert that payload is either 'full' or 'delta'
  226. block_size: expected filesystem / payload block size
  227. part_sizes: map of partition label to (physical) size in bytes
  228. allow_unhashed: allow unhashed operation blobs
  229. disabled_tests: list of tests to disable
  230. Raises:
  231. PayloadError if payload verification failed.
  232. """
  233. self._AssertInit()
  234. # Create a short-lived payload checker object and run it.
  235. helper = checker.PayloadChecker(
  236. self, assert_type=assert_type, block_size=block_size,
  237. allow_unhashed=allow_unhashed, disabled_tests=disabled_tests)
  238. helper.Run(pubkey_file_name=pubkey_file_name,
  239. metadata_sig_file=metadata_sig_file,
  240. metadata_size=metadata_size,
  241. part_sizes=part_sizes,
  242. report_out_file=report_out_file)
  243. def Apply(self, new_parts, old_parts=None, bsdiff_in_place=True,
  244. bspatch_path=None, puffpatch_path=None,
  245. truncate_to_expected_size=True):
  246. """Applies the update payload.
  247. Args:
  248. new_parts: map of partition name to dest partition file
  249. old_parts: map of partition name to partition file (optional)
  250. bsdiff_in_place: whether to perform BSDIFF operations in-place (optional)
  251. bspatch_path: path to the bspatch binary (optional)
  252. puffpatch_path: path to the puffpatch binary (optional)
  253. truncate_to_expected_size: whether to truncate the resulting partitions
  254. to their expected sizes, as specified in the
  255. payload (optional)
  256. Raises:
  257. PayloadError if payload application failed.
  258. """
  259. self._AssertInit()
  260. # Create a short-lived payload applier object and run it.
  261. helper = applier.PayloadApplier(
  262. self, bsdiff_in_place=bsdiff_in_place, bspatch_path=bspatch_path,
  263. puffpatch_path=puffpatch_path,
  264. truncate_to_expected_size=truncate_to_expected_size)
  265. helper.Run(new_parts, old_parts=old_parts)