checker_unittest.py 57 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  1. #!/usr/bin/python2
  2. #
  3. # Copyright (C) 2013 The Android Open Source Project
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. #
  17. """Unit testing checker.py."""
  18. from __future__ import print_function
  19. import array
  20. import collections
  21. import cStringIO
  22. import hashlib
  23. import itertools
  24. import os
  25. import unittest
  26. # pylint cannot find mox.
  27. # pylint: disable=F0401
  28. import mox
  29. from update_payload import checker
  30. from update_payload import common
  31. from update_payload import test_utils
  32. from update_payload import update_metadata_pb2
  33. from update_payload.error import PayloadError
  34. from update_payload.payload import Payload # Avoid name conflicts later.
  35. def _OpTypeByName(op_name):
  36. """Returns the type of an operation from itsname."""
  37. op_name_to_type = {
  38. 'REPLACE': common.OpType.REPLACE,
  39. 'REPLACE_BZ': common.OpType.REPLACE_BZ,
  40. 'MOVE': common.OpType.MOVE,
  41. 'BSDIFF': common.OpType.BSDIFF,
  42. 'SOURCE_COPY': common.OpType.SOURCE_COPY,
  43. 'SOURCE_BSDIFF': common.OpType.SOURCE_BSDIFF,
  44. 'ZERO': common.OpType.ZERO,
  45. 'DISCARD': common.OpType.DISCARD,
  46. 'REPLACE_XZ': common.OpType.REPLACE_XZ,
  47. 'PUFFDIFF': common.OpType.PUFFDIFF,
  48. 'BROTLI_BSDIFF': common.OpType.BROTLI_BSDIFF,
  49. }
  50. return op_name_to_type[op_name]
  51. def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None,
  52. checker_init_dargs=None):
  53. """Returns a payload checker from a given payload generator."""
  54. if payload_gen_dargs is None:
  55. payload_gen_dargs = {}
  56. if checker_init_dargs is None:
  57. checker_init_dargs = {}
  58. payload_file = cStringIO.StringIO()
  59. payload_gen_write_to_file_func(payload_file, **payload_gen_dargs)
  60. payload_file.seek(0)
  61. payload = Payload(payload_file)
  62. payload.Init()
  63. return checker.PayloadChecker(payload, **checker_init_dargs)
  64. def _GetPayloadCheckerWithData(payload_gen):
  65. """Returns a payload checker from a given payload generator."""
  66. payload_file = cStringIO.StringIO()
  67. payload_gen.WriteToFile(payload_file)
  68. payload_file.seek(0)
  69. payload = Payload(payload_file)
  70. payload.Init()
  71. return checker.PayloadChecker(payload)
  72. # This class doesn't need an __init__().
  73. # pylint: disable=W0232
  74. # Unit testing is all about running protected methods.
  75. # pylint: disable=W0212
  76. # Don't bark about missing members of classes you cannot import.
  77. # pylint: disable=E1101
  78. class PayloadCheckerTest(mox.MoxTestBase):
  79. """Tests the PayloadChecker class.
  80. In addition to ordinary testFoo() methods, which are automatically invoked by
  81. the unittest framework, in this class we make use of DoBarTest() calls that
  82. implement parametric tests of certain features. In order to invoke each test,
  83. which embodies a unique combination of parameter values, as a complete unit
  84. test, we perform explicit enumeration of the parameter space and create
  85. individual invocation contexts for each, which are then bound as
  86. testBar__param1=val1__param2=val2(). The enumeration of parameter spaces for
  87. all such tests is done in AddAllParametricTests().
  88. """
  89. def MockPayload(self):
  90. """Create a mock payload object, complete with a mock manifest."""
  91. payload = self.mox.CreateMock(Payload)
  92. payload.is_init = True
  93. payload.manifest = self.mox.CreateMock(
  94. update_metadata_pb2.DeltaArchiveManifest)
  95. return payload
  96. @staticmethod
  97. def NewExtent(start_block, num_blocks):
  98. """Returns an Extent message.
  99. Each of the provided fields is set iff it is >= 0; otherwise, it's left at
  100. its default state.
  101. Args:
  102. start_block: The starting block of the extent.
  103. num_blocks: The number of blocks in the extent.
  104. Returns:
  105. An Extent message.
  106. """
  107. ex = update_metadata_pb2.Extent()
  108. if start_block >= 0:
  109. ex.start_block = start_block
  110. if num_blocks >= 0:
  111. ex.num_blocks = num_blocks
  112. return ex
  113. @staticmethod
  114. def NewExtentList(*args):
  115. """Returns an list of extents.
  116. Args:
  117. *args: (start_block, num_blocks) pairs defining the extents.
  118. Returns:
  119. A list of Extent objects.
  120. """
  121. ex_list = []
  122. for start_block, num_blocks in args:
  123. ex_list.append(PayloadCheckerTest.NewExtent(start_block, num_blocks))
  124. return ex_list
  125. @staticmethod
  126. def AddToMessage(repeated_field, field_vals):
  127. for field_val in field_vals:
  128. new_field = repeated_field.add()
  129. new_field.CopyFrom(field_val)
  130. def SetupAddElemTest(self, is_present, is_submsg, convert=str,
  131. linebreak=False, indent=0):
  132. """Setup for testing of _CheckElem() and its derivatives.
  133. Args:
  134. is_present: Whether or not the element is found in the message.
  135. is_submsg: Whether the element is a sub-message itself.
  136. convert: A representation conversion function.
  137. linebreak: Whether or not a linebreak is to be used in the report.
  138. indent: Indentation used for the report.
  139. Returns:
  140. msg: A mock message object.
  141. report: A mock report object.
  142. subreport: A mock sub-report object.
  143. name: An element name to check.
  144. val: Expected element value.
  145. """
  146. name = 'foo'
  147. val = 'fake submsg' if is_submsg else 'fake field'
  148. subreport = 'fake subreport'
  149. # Create a mock message.
  150. msg = self.mox.CreateMock(update_metadata_pb2._message.Message)
  151. msg.HasField(name).AndReturn(is_present)
  152. setattr(msg, name, val)
  153. # Create a mock report.
  154. report = self.mox.CreateMock(checker._PayloadReport)
  155. if is_present:
  156. if is_submsg:
  157. report.AddSubReport(name).AndReturn(subreport)
  158. else:
  159. report.AddField(name, convert(val), linebreak=linebreak, indent=indent)
  160. self.mox.ReplayAll()
  161. return (msg, report, subreport, name, val)
  162. def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert,
  163. linebreak, indent):
  164. """Parametric testing of _CheckElem().
  165. Args:
  166. is_present: Whether or not the element is found in the message.
  167. is_mandatory: Whether or not it's a mandatory element.
  168. is_submsg: Whether the element is a sub-message itself.
  169. convert: A representation conversion function.
  170. linebreak: Whether or not a linebreak is to be used in the report.
  171. indent: Indentation used for the report.
  172. """
  173. msg, report, subreport, name, val = self.SetupAddElemTest(
  174. is_present, is_submsg, convert, linebreak, indent)
  175. args = (msg, name, report, is_mandatory, is_submsg)
  176. kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
  177. if is_mandatory and not is_present:
  178. self.assertRaises(PayloadError,
  179. checker.PayloadChecker._CheckElem, *args, **kwargs)
  180. else:
  181. ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args,
  182. **kwargs)
  183. self.assertEquals(val if is_present else None, ret_val)
  184. self.assertEquals(subreport if is_present and is_submsg else None,
  185. ret_subreport)
  186. def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak,
  187. indent):
  188. """Parametric testing of _Check{Mandatory,Optional}Field().
  189. Args:
  190. is_mandatory: Whether we're testing a mandatory call.
  191. is_present: Whether or not the element is found in the message.
  192. convert: A representation conversion function.
  193. linebreak: Whether or not a linebreak is to be used in the report.
  194. indent: Indentation used for the report.
  195. """
  196. msg, report, _, name, val = self.SetupAddElemTest(
  197. is_present, False, convert, linebreak, indent)
  198. # Prepare for invocation of the tested method.
  199. args = [msg, name, report]
  200. kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent}
  201. if is_mandatory:
  202. args.append('bar')
  203. tested_func = checker.PayloadChecker._CheckMandatoryField
  204. else:
  205. tested_func = checker.PayloadChecker._CheckOptionalField
  206. # Test the method call.
  207. if is_mandatory and not is_present:
  208. self.assertRaises(PayloadError, tested_func, *args, **kwargs)
  209. else:
  210. ret_val = tested_func(*args, **kwargs)
  211. self.assertEquals(val if is_present else None, ret_val)
  212. def DoAddSubMsgTest(self, is_mandatory, is_present):
  213. """Parametrized testing of _Check{Mandatory,Optional}SubMsg().
  214. Args:
  215. is_mandatory: Whether we're testing a mandatory call.
  216. is_present: Whether or not the element is found in the message.
  217. """
  218. msg, report, subreport, name, val = self.SetupAddElemTest(is_present, True)
  219. # Prepare for invocation of the tested method.
  220. args = [msg, name, report]
  221. if is_mandatory:
  222. args.append('bar')
  223. tested_func = checker.PayloadChecker._CheckMandatorySubMsg
  224. else:
  225. tested_func = checker.PayloadChecker._CheckOptionalSubMsg
  226. # Test the method call.
  227. if is_mandatory and not is_present:
  228. self.assertRaises(PayloadError, tested_func, *args)
  229. else:
  230. ret_val, ret_subreport = tested_func(*args)
  231. self.assertEquals(val if is_present else None, ret_val)
  232. self.assertEquals(subreport if is_present else None, ret_subreport)
  233. def testCheckPresentIff(self):
  234. """Tests _CheckPresentIff()."""
  235. self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
  236. None, None, 'foo', 'bar', 'baz'))
  237. self.assertIsNone(checker.PayloadChecker._CheckPresentIff(
  238. 'a', 'b', 'foo', 'bar', 'baz'))
  239. self.assertRaises(PayloadError, checker.PayloadChecker._CheckPresentIff,
  240. 'a', None, 'foo', 'bar', 'baz')
  241. self.assertRaises(PayloadError, checker.PayloadChecker._CheckPresentIff,
  242. None, 'b', 'foo', 'bar', 'baz')
  243. def DoCheckSha256SignatureTest(self, expect_pass, expect_subprocess_call,
  244. sig_data, sig_asn1_header,
  245. returned_signed_hash, expected_signed_hash):
  246. """Parametric testing of _CheckSha256SignatureTest().
  247. Args:
  248. expect_pass: Whether or not it should pass.
  249. expect_subprocess_call: Whether to expect the openssl call to happen.
  250. sig_data: The signature raw data.
  251. sig_asn1_header: The ASN1 header.
  252. returned_signed_hash: The signed hash data retuned by openssl.
  253. expected_signed_hash: The signed hash data to compare against.
  254. """
  255. try:
  256. # Stub out the subprocess invocation.
  257. self.mox.StubOutWithMock(checker.PayloadChecker, '_Run')
  258. if expect_subprocess_call:
  259. checker.PayloadChecker._Run(
  260. mox.IsA(list), send_data=sig_data).AndReturn(
  261. (sig_asn1_header + returned_signed_hash, None))
  262. self.mox.ReplayAll()
  263. if expect_pass:
  264. self.assertIsNone(checker.PayloadChecker._CheckSha256Signature(
  265. sig_data, 'foo', expected_signed_hash, 'bar'))
  266. else:
  267. self.assertRaises(PayloadError,
  268. checker.PayloadChecker._CheckSha256Signature,
  269. sig_data, 'foo', expected_signed_hash, 'bar')
  270. finally:
  271. self.mox.UnsetStubs()
  272. def testCheckSha256Signature_Pass(self):
  273. """Tests _CheckSha256Signature(); pass case."""
  274. sig_data = 'fake-signature'.ljust(256)
  275. signed_hash = hashlib.sha256('fake-data').digest()
  276. self.DoCheckSha256SignatureTest(True, True, sig_data,
  277. common.SIG_ASN1_HEADER, signed_hash,
  278. signed_hash)
  279. def testCheckSha256Signature_FailBadSignature(self):
  280. """Tests _CheckSha256Signature(); fails due to malformed signature."""
  281. sig_data = 'fake-signature' # Malformed (not 256 bytes in length).
  282. signed_hash = hashlib.sha256('fake-data').digest()
  283. self.DoCheckSha256SignatureTest(False, False, sig_data,
  284. common.SIG_ASN1_HEADER, signed_hash,
  285. signed_hash)
  286. def testCheckSha256Signature_FailBadOutputLength(self):
  287. """Tests _CheckSha256Signature(); fails due to unexpected output length."""
  288. sig_data = 'fake-signature'.ljust(256)
  289. signed_hash = 'fake-hash' # Malformed (not 32 bytes in length).
  290. self.DoCheckSha256SignatureTest(False, True, sig_data,
  291. common.SIG_ASN1_HEADER, signed_hash,
  292. signed_hash)
  293. def testCheckSha256Signature_FailBadAsnHeader(self):
  294. """Tests _CheckSha256Signature(); fails due to bad ASN1 header."""
  295. sig_data = 'fake-signature'.ljust(256)
  296. signed_hash = hashlib.sha256('fake-data').digest()
  297. bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER))
  298. self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header,
  299. signed_hash, signed_hash)
  300. def testCheckSha256Signature_FailBadHash(self):
  301. """Tests _CheckSha256Signature(); fails due to bad hash returned."""
  302. sig_data = 'fake-signature'.ljust(256)
  303. expected_signed_hash = hashlib.sha256('fake-data').digest()
  304. returned_signed_hash = hashlib.sha256('bad-fake-data').digest()
  305. self.DoCheckSha256SignatureTest(False, True, sig_data,
  306. common.SIG_ASN1_HEADER,
  307. expected_signed_hash, returned_signed_hash)
  308. def testCheckBlocksFitLength_Pass(self):
  309. """Tests _CheckBlocksFitLength(); pass case."""
  310. self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
  311. 64, 4, 16, 'foo'))
  312. self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
  313. 60, 4, 16, 'foo'))
  314. self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
  315. 49, 4, 16, 'foo'))
  316. self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength(
  317. 48, 3, 16, 'foo'))
  318. def testCheckBlocksFitLength_TooManyBlocks(self):
  319. """Tests _CheckBlocksFitLength(); fails due to excess blocks."""
  320. self.assertRaises(PayloadError,
  321. checker.PayloadChecker._CheckBlocksFitLength,
  322. 64, 5, 16, 'foo')
  323. self.assertRaises(PayloadError,
  324. checker.PayloadChecker._CheckBlocksFitLength,
  325. 60, 5, 16, 'foo')
  326. self.assertRaises(PayloadError,
  327. checker.PayloadChecker._CheckBlocksFitLength,
  328. 49, 5, 16, 'foo')
  329. self.assertRaises(PayloadError,
  330. checker.PayloadChecker._CheckBlocksFitLength,
  331. 48, 4, 16, 'foo')
  332. def testCheckBlocksFitLength_TooFewBlocks(self):
  333. """Tests _CheckBlocksFitLength(); fails due to insufficient blocks."""
  334. self.assertRaises(PayloadError,
  335. checker.PayloadChecker._CheckBlocksFitLength,
  336. 64, 3, 16, 'foo')
  337. self.assertRaises(PayloadError,
  338. checker.PayloadChecker._CheckBlocksFitLength,
  339. 60, 3, 16, 'foo')
  340. self.assertRaises(PayloadError,
  341. checker.PayloadChecker._CheckBlocksFitLength,
  342. 49, 3, 16, 'foo')
  343. self.assertRaises(PayloadError,
  344. checker.PayloadChecker._CheckBlocksFitLength,
  345. 48, 2, 16, 'foo')
  346. def DoCheckManifestTest(self, fail_mismatched_block_size, fail_bad_sigs,
  347. fail_mismatched_oki_ori, fail_bad_oki, fail_bad_ori,
  348. fail_bad_nki, fail_bad_nri, fail_old_kernel_fs_size,
  349. fail_old_rootfs_fs_size, fail_new_kernel_fs_size,
  350. fail_new_rootfs_fs_size):
  351. """Parametric testing of _CheckManifest().
  352. Args:
  353. fail_mismatched_block_size: Simulate a missing block_size field.
  354. fail_bad_sigs: Make signatures descriptor inconsistent.
  355. fail_mismatched_oki_ori: Make old rootfs/kernel info partially present.
  356. fail_bad_oki: Tamper with old kernel info.
  357. fail_bad_ori: Tamper with old rootfs info.
  358. fail_bad_nki: Tamper with new kernel info.
  359. fail_bad_nri: Tamper with new rootfs info.
  360. fail_old_kernel_fs_size: Make old kernel fs size too big.
  361. fail_old_rootfs_fs_size: Make old rootfs fs size too big.
  362. fail_new_kernel_fs_size: Make new kernel fs size too big.
  363. fail_new_rootfs_fs_size: Make new rootfs fs size too big.
  364. """
  365. # Generate a test payload. For this test, we only care about the manifest
  366. # and don't need any data blobs, hence we can use a plain paylaod generator
  367. # (which also gives us more control on things that can be screwed up).
  368. payload_gen = test_utils.PayloadGenerator()
  369. # Tamper with block size, if required.
  370. if fail_mismatched_block_size:
  371. payload_gen.SetBlockSize(test_utils.KiB(1))
  372. else:
  373. payload_gen.SetBlockSize(test_utils.KiB(4))
  374. # Add some operations.
  375. payload_gen.AddOperation(False, common.OpType.MOVE,
  376. src_extents=[(0, 16), (16, 497)],
  377. dst_extents=[(16, 496), (0, 16)])
  378. payload_gen.AddOperation(True, common.OpType.MOVE,
  379. src_extents=[(0, 8), (8, 8)],
  380. dst_extents=[(8, 8), (0, 8)])
  381. # Set an invalid signatures block (offset but no size), if required.
  382. if fail_bad_sigs:
  383. payload_gen.SetSignatures(32, None)
  384. # Set partition / filesystem sizes.
  385. rootfs_part_size = test_utils.MiB(8)
  386. kernel_part_size = test_utils.KiB(512)
  387. old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size
  388. old_kernel_fs_size = new_kernel_fs_size = kernel_part_size
  389. if fail_old_kernel_fs_size:
  390. old_kernel_fs_size += 100
  391. if fail_old_rootfs_fs_size:
  392. old_rootfs_fs_size += 100
  393. if fail_new_kernel_fs_size:
  394. new_kernel_fs_size += 100
  395. if fail_new_rootfs_fs_size:
  396. new_rootfs_fs_size += 100
  397. # Add old kernel/rootfs partition info, as required.
  398. if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki:
  399. oki_hash = (None if fail_bad_oki
  400. else hashlib.sha256('fake-oki-content').digest())
  401. payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash)
  402. if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or
  403. fail_bad_ori):
  404. ori_hash = (None if fail_bad_ori
  405. else hashlib.sha256('fake-ori-content').digest())
  406. payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash)
  407. # Add new kernel/rootfs partition info.
  408. payload_gen.SetPartInfo(
  409. True, True, new_kernel_fs_size,
  410. None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest())
  411. payload_gen.SetPartInfo(
  412. False, True, new_rootfs_fs_size,
  413. None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest())
  414. # Set the minor version.
  415. payload_gen.SetMinorVersion(0)
  416. # Create the test object.
  417. payload_checker = _GetPayloadChecker(payload_gen.WriteToFile)
  418. report = checker._PayloadReport()
  419. should_fail = (fail_mismatched_block_size or fail_bad_sigs or
  420. fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or
  421. fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or
  422. fail_old_rootfs_fs_size or fail_new_kernel_fs_size or
  423. fail_new_rootfs_fs_size)
  424. part_sizes = {
  425. common.ROOTFS: rootfs_part_size,
  426. common.KERNEL: kernel_part_size
  427. }
  428. if should_fail:
  429. self.assertRaises(PayloadError, payload_checker._CheckManifest, report,
  430. part_sizes)
  431. else:
  432. self.assertIsNone(payload_checker._CheckManifest(report, part_sizes))
  433. def testCheckLength(self):
  434. """Tests _CheckLength()."""
  435. payload_checker = checker.PayloadChecker(self.MockPayload())
  436. block_size = payload_checker.block_size
  437. # Passes.
  438. self.assertIsNone(payload_checker._CheckLength(
  439. int(3.5 * block_size), 4, 'foo', 'bar'))
  440. # Fails, too few blocks.
  441. self.assertRaises(PayloadError, payload_checker._CheckLength,
  442. int(3.5 * block_size), 3, 'foo', 'bar')
  443. # Fails, too many blocks.
  444. self.assertRaises(PayloadError, payload_checker._CheckLength,
  445. int(3.5 * block_size), 5, 'foo', 'bar')
  446. def testCheckExtents(self):
  447. """Tests _CheckExtents()."""
  448. payload_checker = checker.PayloadChecker(self.MockPayload())
  449. block_size = payload_checker.block_size
  450. # Passes w/ all real extents.
  451. extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
  452. self.assertEquals(
  453. 23,
  454. payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
  455. collections.defaultdict(int), 'foo'))
  456. # Passes w/ pseudo-extents (aka sparse holes).
  457. extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5),
  458. (8, 3))
  459. self.assertEquals(
  460. 12,
  461. payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
  462. collections.defaultdict(int), 'foo',
  463. allow_pseudo=True))
  464. # Passes w/ pseudo-extent due to a signature.
  465. extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2))
  466. self.assertEquals(
  467. 2,
  468. payload_checker._CheckExtents(extents, (1024 + 16) * block_size,
  469. collections.defaultdict(int), 'foo',
  470. allow_signature=True))
  471. # Fails, extent missing a start block.
  472. extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16))
  473. self.assertRaises(
  474. PayloadError, payload_checker._CheckExtents, extents,
  475. (1024 + 16) * block_size, collections.defaultdict(int), 'foo')
  476. # Fails, extent missing block count.
  477. extents = self.NewExtentList((0, -1), (8, 3), (1024, 16))
  478. self.assertRaises(
  479. PayloadError, payload_checker._CheckExtents, extents,
  480. (1024 + 16) * block_size, collections.defaultdict(int), 'foo')
  481. # Fails, extent has zero blocks.
  482. extents = self.NewExtentList((0, 4), (8, 3), (1024, 0))
  483. self.assertRaises(
  484. PayloadError, payload_checker._CheckExtents, extents,
  485. (1024 + 16) * block_size, collections.defaultdict(int), 'foo')
  486. # Fails, extent exceeds partition boundaries.
  487. extents = self.NewExtentList((0, 4), (8, 3), (1024, 16))
  488. self.assertRaises(
  489. PayloadError, payload_checker._CheckExtents, extents,
  490. (1024 + 15) * block_size, collections.defaultdict(int), 'foo')
  491. def testCheckReplaceOperation(self):
  492. """Tests _CheckReplaceOperation() where op.type == REPLACE."""
  493. payload_checker = checker.PayloadChecker(self.MockPayload())
  494. block_size = payload_checker.block_size
  495. data_length = 10000
  496. op = self.mox.CreateMock(
  497. update_metadata_pb2.InstallOperation)
  498. op.type = common.OpType.REPLACE
  499. # Pass.
  500. op.src_extents = []
  501. self.assertIsNone(
  502. payload_checker._CheckReplaceOperation(
  503. op, data_length, (data_length + block_size - 1) / block_size,
  504. 'foo'))
  505. # Fail, src extents founds.
  506. op.src_extents = ['bar']
  507. self.assertRaises(
  508. PayloadError, payload_checker._CheckReplaceOperation,
  509. op, data_length, (data_length + block_size - 1) / block_size, 'foo')
  510. # Fail, missing data.
  511. op.src_extents = []
  512. self.assertRaises(
  513. PayloadError, payload_checker._CheckReplaceOperation,
  514. op, None, (data_length + block_size - 1) / block_size, 'foo')
  515. # Fail, length / block number mismatch.
  516. op.src_extents = ['bar']
  517. self.assertRaises(
  518. PayloadError, payload_checker._CheckReplaceOperation,
  519. op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo')
  520. def testCheckReplaceBzOperation(self):
  521. """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ."""
  522. payload_checker = checker.PayloadChecker(self.MockPayload())
  523. block_size = payload_checker.block_size
  524. data_length = block_size * 3
  525. op = self.mox.CreateMock(
  526. update_metadata_pb2.InstallOperation)
  527. op.type = common.OpType.REPLACE_BZ
  528. # Pass.
  529. op.src_extents = []
  530. self.assertIsNone(
  531. payload_checker._CheckReplaceOperation(
  532. op, data_length, (data_length + block_size - 1) / block_size + 5,
  533. 'foo'))
  534. # Fail, src extents founds.
  535. op.src_extents = ['bar']
  536. self.assertRaises(
  537. PayloadError, payload_checker._CheckReplaceOperation,
  538. op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
  539. # Fail, missing data.
  540. op.src_extents = []
  541. self.assertRaises(
  542. PayloadError, payload_checker._CheckReplaceOperation,
  543. op, None, (data_length + block_size - 1) / block_size, 'foo')
  544. # Fail, too few blocks to justify BZ.
  545. op.src_extents = []
  546. self.assertRaises(
  547. PayloadError, payload_checker._CheckReplaceOperation,
  548. op, data_length, (data_length + block_size - 1) / block_size, 'foo')
  549. def testCheckReplaceXzOperation(self):
  550. """Tests _CheckReplaceOperation() where op.type == REPLACE_XZ."""
  551. payload_checker = checker.PayloadChecker(self.MockPayload())
  552. block_size = payload_checker.block_size
  553. data_length = block_size * 3
  554. op = self.mox.CreateMock(
  555. update_metadata_pb2.InstallOperation)
  556. op.type = common.OpType.REPLACE_XZ
  557. # Pass.
  558. op.src_extents = []
  559. self.assertIsNone(
  560. payload_checker._CheckReplaceOperation(
  561. op, data_length, (data_length + block_size - 1) / block_size + 5,
  562. 'foo'))
  563. # Fail, src extents founds.
  564. op.src_extents = ['bar']
  565. self.assertRaises(
  566. PayloadError, payload_checker._CheckReplaceOperation,
  567. op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo')
  568. # Fail, missing data.
  569. op.src_extents = []
  570. self.assertRaises(
  571. PayloadError, payload_checker._CheckReplaceOperation,
  572. op, None, (data_length + block_size - 1) / block_size, 'foo')
  573. # Fail, too few blocks to justify XZ.
  574. op.src_extents = []
  575. self.assertRaises(
  576. PayloadError, payload_checker._CheckReplaceOperation,
  577. op, data_length, (data_length + block_size - 1) / block_size, 'foo')
  578. def testCheckMoveOperation_Pass(self):
  579. """Tests _CheckMoveOperation(); pass case."""
  580. payload_checker = checker.PayloadChecker(self.MockPayload())
  581. op = update_metadata_pb2.InstallOperation()
  582. op.type = common.OpType.MOVE
  583. self.AddToMessage(op.src_extents,
  584. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  585. self.AddToMessage(op.dst_extents,
  586. self.NewExtentList((16, 128), (512, 6)))
  587. self.assertIsNone(
  588. payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo'))
  589. def testCheckMoveOperation_FailContainsData(self):
  590. """Tests _CheckMoveOperation(); fails, message contains data."""
  591. payload_checker = checker.PayloadChecker(self.MockPayload())
  592. op = update_metadata_pb2.InstallOperation()
  593. op.type = common.OpType.MOVE
  594. self.AddToMessage(op.src_extents,
  595. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  596. self.AddToMessage(op.dst_extents,
  597. self.NewExtentList((16, 128), (512, 6)))
  598. self.assertRaises(
  599. PayloadError, payload_checker._CheckMoveOperation,
  600. op, 1024, 134, 134, 'foo')
  601. def testCheckMoveOperation_FailInsufficientSrcBlocks(self):
  602. """Tests _CheckMoveOperation(); fails, not enough actual src blocks."""
  603. payload_checker = checker.PayloadChecker(self.MockPayload())
  604. op = update_metadata_pb2.InstallOperation()
  605. op.type = common.OpType.MOVE
  606. self.AddToMessage(op.src_extents,
  607. self.NewExtentList((1, 4), (12, 2), (1024, 127)))
  608. self.AddToMessage(op.dst_extents,
  609. self.NewExtentList((16, 128), (512, 6)))
  610. self.assertRaises(
  611. PayloadError, payload_checker._CheckMoveOperation,
  612. op, None, 134, 134, 'foo')
  613. def testCheckMoveOperation_FailInsufficientDstBlocks(self):
  614. """Tests _CheckMoveOperation(); fails, not enough actual dst blocks."""
  615. payload_checker = checker.PayloadChecker(self.MockPayload())
  616. op = update_metadata_pb2.InstallOperation()
  617. op.type = common.OpType.MOVE
  618. self.AddToMessage(op.src_extents,
  619. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  620. self.AddToMessage(op.dst_extents,
  621. self.NewExtentList((16, 128), (512, 5)))
  622. self.assertRaises(
  623. PayloadError, payload_checker._CheckMoveOperation,
  624. op, None, 134, 134, 'foo')
  625. def testCheckMoveOperation_FailExcessSrcBlocks(self):
  626. """Tests _CheckMoveOperation(); fails, too many actual src blocks."""
  627. payload_checker = checker.PayloadChecker(self.MockPayload())
  628. op = update_metadata_pb2.InstallOperation()
  629. op.type = common.OpType.MOVE
  630. self.AddToMessage(op.src_extents,
  631. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  632. self.AddToMessage(op.dst_extents,
  633. self.NewExtentList((16, 128), (512, 5)))
  634. self.assertRaises(
  635. PayloadError, payload_checker._CheckMoveOperation,
  636. op, None, 134, 134, 'foo')
  637. self.AddToMessage(op.src_extents,
  638. self.NewExtentList((1, 4), (12, 2), (1024, 129)))
  639. self.AddToMessage(op.dst_extents,
  640. self.NewExtentList((16, 128), (512, 6)))
  641. self.assertRaises(
  642. PayloadError, payload_checker._CheckMoveOperation,
  643. op, None, 134, 134, 'foo')
  644. def testCheckMoveOperation_FailExcessDstBlocks(self):
  645. """Tests _CheckMoveOperation(); fails, too many actual dst blocks."""
  646. payload_checker = checker.PayloadChecker(self.MockPayload())
  647. op = update_metadata_pb2.InstallOperation()
  648. op.type = common.OpType.MOVE
  649. self.AddToMessage(op.src_extents,
  650. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  651. self.AddToMessage(op.dst_extents,
  652. self.NewExtentList((16, 128), (512, 7)))
  653. self.assertRaises(
  654. PayloadError, payload_checker._CheckMoveOperation,
  655. op, None, 134, 134, 'foo')
  656. def testCheckMoveOperation_FailStagnantBlocks(self):
  657. """Tests _CheckMoveOperation(); fails, there are blocks that do not move."""
  658. payload_checker = checker.PayloadChecker(self.MockPayload())
  659. op = update_metadata_pb2.InstallOperation()
  660. op.type = common.OpType.MOVE
  661. self.AddToMessage(op.src_extents,
  662. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  663. self.AddToMessage(op.dst_extents,
  664. self.NewExtentList((8, 128), (512, 6)))
  665. self.assertRaises(
  666. PayloadError, payload_checker._CheckMoveOperation,
  667. op, None, 134, 134, 'foo')
  668. def testCheckMoveOperation_FailZeroStartBlock(self):
  669. """Tests _CheckMoveOperation(); fails, has extent with start block 0."""
  670. payload_checker = checker.PayloadChecker(self.MockPayload())
  671. op = update_metadata_pb2.InstallOperation()
  672. op.type = common.OpType.MOVE
  673. self.AddToMessage(op.src_extents,
  674. self.NewExtentList((0, 4), (12, 2), (1024, 128)))
  675. self.AddToMessage(op.dst_extents,
  676. self.NewExtentList((8, 128), (512, 6)))
  677. self.assertRaises(
  678. PayloadError, payload_checker._CheckMoveOperation,
  679. op, None, 134, 134, 'foo')
  680. self.AddToMessage(op.src_extents,
  681. self.NewExtentList((1, 4), (12, 2), (1024, 128)))
  682. self.AddToMessage(op.dst_extents,
  683. self.NewExtentList((0, 128), (512, 6)))
  684. self.assertRaises(
  685. PayloadError, payload_checker._CheckMoveOperation,
  686. op, None, 134, 134, 'foo')
  687. def testCheckAnyDiff(self):
  688. """Tests _CheckAnyDiffOperation()."""
  689. payload_checker = checker.PayloadChecker(self.MockPayload())
  690. op = update_metadata_pb2.InstallOperation()
  691. # Pass.
  692. self.assertIsNone(
  693. payload_checker._CheckAnyDiffOperation(op, 10000, 3, 'foo'))
  694. # Fail, missing data blob.
  695. self.assertRaises(
  696. PayloadError, payload_checker._CheckAnyDiffOperation,
  697. op, None, 3, 'foo')
  698. # Fail, too big of a diff blob (unjustified).
  699. self.assertRaises(
  700. PayloadError, payload_checker._CheckAnyDiffOperation,
  701. op, 10000, 2, 'foo')
  702. def testCheckSourceCopyOperation_Pass(self):
  703. """Tests _CheckSourceCopyOperation(); pass case."""
  704. payload_checker = checker.PayloadChecker(self.MockPayload())
  705. self.assertIsNone(
  706. payload_checker._CheckSourceCopyOperation(None, 134, 134, 'foo'))
  707. def testCheckSourceCopyOperation_FailContainsData(self):
  708. """Tests _CheckSourceCopyOperation(); message contains data."""
  709. payload_checker = checker.PayloadChecker(self.MockPayload())
  710. self.assertRaises(PayloadError, payload_checker._CheckSourceCopyOperation,
  711. 134, 0, 0, 'foo')
  712. def testCheckSourceCopyOperation_FailBlockCountsMismatch(self):
  713. """Tests _CheckSourceCopyOperation(); src and dst block totals not equal."""
  714. payload_checker = checker.PayloadChecker(self.MockPayload())
  715. self.assertRaises(PayloadError, payload_checker._CheckSourceCopyOperation,
  716. None, 0, 1, 'foo')
  717. def DoCheckOperationTest(self, op_type_name, is_last, allow_signature,
  718. allow_unhashed, fail_src_extents, fail_dst_extents,
  719. fail_mismatched_data_offset_length,
  720. fail_missing_dst_extents, fail_src_length,
  721. fail_dst_length, fail_data_hash,
  722. fail_prev_data_offset, fail_bad_minor_version):
  723. """Parametric testing of _CheckOperation().
  724. Args:
  725. op_type_name: 'REPLACE', 'REPLACE_BZ', 'REPLACE_XZ', 'MOVE', 'BSDIFF',
  726. 'SOURCE_COPY', 'SOURCE_BSDIFF', BROTLI_BSDIFF or 'PUFFDIFF'.
  727. is_last: Whether we're testing the last operation in a sequence.
  728. allow_signature: Whether we're testing a signature-capable operation.
  729. allow_unhashed: Whether we're allowing to not hash the data.
  730. fail_src_extents: Tamper with src extents.
  731. fail_dst_extents: Tamper with dst extents.
  732. fail_mismatched_data_offset_length: Make data_{offset,length}
  733. inconsistent.
  734. fail_missing_dst_extents: Do not include dst extents.
  735. fail_src_length: Make src length inconsistent.
  736. fail_dst_length: Make dst length inconsistent.
  737. fail_data_hash: Tamper with the data blob hash.
  738. fail_prev_data_offset: Make data space uses incontiguous.
  739. fail_bad_minor_version: Make minor version incompatible with op.
  740. """
  741. op_type = _OpTypeByName(op_type_name)
  742. # Create the test object.
  743. payload = self.MockPayload()
  744. payload_checker = checker.PayloadChecker(payload,
  745. allow_unhashed=allow_unhashed)
  746. block_size = payload_checker.block_size
  747. # Create auxiliary arguments.
  748. old_part_size = test_utils.MiB(4)
  749. new_part_size = test_utils.MiB(8)
  750. old_block_counters = array.array(
  751. 'B', [0] * ((old_part_size + block_size - 1) / block_size))
  752. new_block_counters = array.array(
  753. 'B', [0] * ((new_part_size + block_size - 1) / block_size))
  754. prev_data_offset = 1876
  755. blob_hash_counts = collections.defaultdict(int)
  756. # Create the operation object for the test.
  757. op = update_metadata_pb2.InstallOperation()
  758. op.type = op_type
  759. total_src_blocks = 0
  760. if op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
  761. common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF,
  762. common.OpType.PUFFDIFF, common.OpType.BROTLI_BSDIFF):
  763. if fail_src_extents:
  764. self.AddToMessage(op.src_extents,
  765. self.NewExtentList((1, 0)))
  766. else:
  767. self.AddToMessage(op.src_extents,
  768. self.NewExtentList((1, 16)))
  769. total_src_blocks = 16
  770. # TODO(tbrindus): add major version 2 tests.
  771. payload_checker.major_version = common.CHROMEOS_MAJOR_PAYLOAD_VERSION
  772. if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
  773. payload_checker.minor_version = 0
  774. elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
  775. payload_checker.minor_version = 2 if fail_bad_minor_version else 1
  776. elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF):
  777. payload_checker.minor_version = 1 if fail_bad_minor_version else 2
  778. if op_type == common.OpType.REPLACE_XZ:
  779. payload_checker.minor_version = 2 if fail_bad_minor_version else 3
  780. elif op_type in (common.OpType.ZERO, common.OpType.DISCARD,
  781. common.OpType.BROTLI_BSDIFF):
  782. payload_checker.minor_version = 3 if fail_bad_minor_version else 4
  783. elif op_type == common.OpType.PUFFDIFF:
  784. payload_checker.minor_version = 4 if fail_bad_minor_version else 5
  785. if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY):
  786. if not fail_mismatched_data_offset_length:
  787. op.data_length = 16 * block_size - 8
  788. if fail_prev_data_offset:
  789. op.data_offset = prev_data_offset + 16
  790. else:
  791. op.data_offset = prev_data_offset
  792. fake_data = 'fake-data'.ljust(op.data_length)
  793. if not (allow_unhashed or (is_last and allow_signature and
  794. op_type == common.OpType.REPLACE)):
  795. if not fail_data_hash:
  796. # Create a valid data blob hash.
  797. op.data_sha256_hash = hashlib.sha256(fake_data).digest()
  798. payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
  799. fake_data)
  800. elif fail_data_hash:
  801. # Create an invalid data blob hash.
  802. op.data_sha256_hash = hashlib.sha256(
  803. fake_data.replace(' ', '-')).digest()
  804. payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn(
  805. fake_data)
  806. total_dst_blocks = 0
  807. if not fail_missing_dst_extents:
  808. total_dst_blocks = 16
  809. if fail_dst_extents:
  810. self.AddToMessage(op.dst_extents,
  811. self.NewExtentList((4, 16), (32, 0)))
  812. else:
  813. self.AddToMessage(op.dst_extents,
  814. self.NewExtentList((4, 8), (64, 8)))
  815. if total_src_blocks:
  816. if fail_src_length:
  817. op.src_length = total_src_blocks * block_size + 8
  818. elif (op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
  819. common.OpType.SOURCE_BSDIFF) and
  820. payload_checker.minor_version <= 3):
  821. op.src_length = total_src_blocks * block_size
  822. elif fail_src_length:
  823. # Add an orphaned src_length.
  824. op.src_length = 16
  825. if total_dst_blocks:
  826. if fail_dst_length:
  827. op.dst_length = total_dst_blocks * block_size + 8
  828. elif (op_type in (common.OpType.MOVE, common.OpType.BSDIFF,
  829. common.OpType.SOURCE_BSDIFF) and
  830. payload_checker.minor_version <= 3):
  831. op.dst_length = total_dst_blocks * block_size
  832. self.mox.ReplayAll()
  833. should_fail = (fail_src_extents or fail_dst_extents or
  834. fail_mismatched_data_offset_length or
  835. fail_missing_dst_extents or fail_src_length or
  836. fail_dst_length or fail_data_hash or fail_prev_data_offset or
  837. fail_bad_minor_version)
  838. args = (op, 'foo', is_last, old_block_counters, new_block_counters,
  839. old_part_size, new_part_size, prev_data_offset, allow_signature,
  840. blob_hash_counts)
  841. if should_fail:
  842. self.assertRaises(PayloadError, payload_checker._CheckOperation, *args)
  843. else:
  844. self.assertEqual(op.data_length if op.HasField('data_length') else 0,
  845. payload_checker._CheckOperation(*args))
  846. def testAllocBlockCounters(self):
  847. """Tests _CheckMoveOperation()."""
  848. payload_checker = checker.PayloadChecker(self.MockPayload())
  849. block_size = payload_checker.block_size
  850. # Check allocation for block-aligned partition size, ensure it's integers.
  851. result = payload_checker._AllocBlockCounters(16 * block_size)
  852. self.assertEqual(16, len(result))
  853. self.assertEqual(int, type(result[0]))
  854. # Check allocation of unaligned partition sizes.
  855. result = payload_checker._AllocBlockCounters(16 * block_size - 1)
  856. self.assertEqual(16, len(result))
  857. result = payload_checker._AllocBlockCounters(16 * block_size + 1)
  858. self.assertEqual(17, len(result))
  859. def DoCheckOperationsTest(self, fail_nonexhaustive_full_update):
  860. """Tests _CheckOperations()."""
  861. # Generate a test payload. For this test, we only care about one
  862. # (arbitrary) set of operations, so we'll only be generating kernel and
  863. # test with them.
  864. payload_gen = test_utils.PayloadGenerator()
  865. block_size = test_utils.KiB(4)
  866. payload_gen.SetBlockSize(block_size)
  867. rootfs_part_size = test_utils.MiB(8)
  868. # Fake rootfs operations in a full update, tampered with as required.
  869. rootfs_op_type = common.OpType.REPLACE
  870. rootfs_data_length = rootfs_part_size
  871. if fail_nonexhaustive_full_update:
  872. rootfs_data_length -= block_size
  873. payload_gen.AddOperation(False, rootfs_op_type,
  874. dst_extents=[(0, rootfs_data_length / block_size)],
  875. data_offset=0,
  876. data_length=rootfs_data_length)
  877. # Create the test object.
  878. payload_checker = _GetPayloadChecker(payload_gen.WriteToFile,
  879. checker_init_dargs={
  880. 'allow_unhashed': True})
  881. payload_checker.payload_type = checker._TYPE_FULL
  882. report = checker._PayloadReport()
  883. args = (payload_checker.payload.manifest.install_operations, report, 'foo',
  884. 0, rootfs_part_size, rootfs_part_size, rootfs_part_size, 0, False)
  885. if fail_nonexhaustive_full_update:
  886. self.assertRaises(PayloadError, payload_checker._CheckOperations, *args)
  887. else:
  888. self.assertEqual(rootfs_data_length,
  889. payload_checker._CheckOperations(*args))
  890. def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op,
  891. fail_mismatched_pseudo_op, fail_sig_missing_fields,
  892. fail_unknown_sig_version, fail_incorrect_sig):
  893. """Tests _CheckSignatures()."""
  894. # Generate a test payload. For this test, we only care about the signature
  895. # block and how it relates to the payload hash. Therefore, we're generating
  896. # a random (otherwise useless) payload for this purpose.
  897. payload_gen = test_utils.EnhancedPayloadGenerator()
  898. block_size = test_utils.KiB(4)
  899. payload_gen.SetBlockSize(block_size)
  900. rootfs_part_size = test_utils.MiB(2)
  901. kernel_part_size = test_utils.KiB(16)
  902. payload_gen.SetPartInfo(False, True, rootfs_part_size,
  903. hashlib.sha256('fake-new-rootfs-content').digest())
  904. payload_gen.SetPartInfo(True, True, kernel_part_size,
  905. hashlib.sha256('fake-new-kernel-content').digest())
  906. payload_gen.SetMinorVersion(0)
  907. payload_gen.AddOperationWithData(
  908. False, common.OpType.REPLACE,
  909. dst_extents=[(0, rootfs_part_size / block_size)],
  910. data_blob=os.urandom(rootfs_part_size))
  911. do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op)
  912. do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or
  913. fail_sig_missing_fields or fail_unknown_sig_version
  914. or fail_incorrect_sig)
  915. sigs_data = None
  916. if do_forge_sigs_data:
  917. sigs_gen = test_utils.SignaturesGenerator()
  918. if not fail_empty_sigs_blob:
  919. if fail_sig_missing_fields:
  920. sig_data = None
  921. else:
  922. sig_data = test_utils.SignSha256('fake-payload-content',
  923. test_utils._PRIVKEY_FILE_NAME)
  924. sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data)
  925. sigs_data = sigs_gen.ToBinary()
  926. payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data))
  927. if do_forge_pseudo_op:
  928. assert sigs_data is not None, 'should have forged signatures blob by now'
  929. sigs_len = len(sigs_data)
  930. payload_gen.AddOperation(
  931. False, common.OpType.REPLACE,
  932. data_offset=payload_gen.curr_offset / 2,
  933. data_length=sigs_len / 2,
  934. dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)])
  935. # Generate payload (complete w/ signature) and create the test object.
  936. payload_checker = _GetPayloadChecker(
  937. payload_gen.WriteToFileWithData,
  938. payload_gen_dargs={
  939. 'sigs_data': sigs_data,
  940. 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
  941. 'do_add_pseudo_operation': not do_forge_pseudo_op})
  942. payload_checker.payload_type = checker._TYPE_FULL
  943. report = checker._PayloadReport()
  944. # We have to check the manifest first in order to set signature attributes.
  945. payload_checker._CheckManifest(report, {
  946. common.ROOTFS: rootfs_part_size,
  947. common.KERNEL: kernel_part_size
  948. })
  949. should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
  950. fail_mismatched_pseudo_op or fail_sig_missing_fields or
  951. fail_unknown_sig_version or fail_incorrect_sig)
  952. args = (report, test_utils._PUBKEY_FILE_NAME)
  953. if should_fail:
  954. self.assertRaises(PayloadError, payload_checker._CheckSignatures, *args)
  955. else:
  956. self.assertIsNone(payload_checker._CheckSignatures(*args))
  957. def DoCheckManifestMinorVersionTest(self, minor_version, payload_type):
  958. """Parametric testing for CheckManifestMinorVersion().
  959. Args:
  960. minor_version: The payload minor version to test with.
  961. payload_type: The type of the payload we're testing, delta or full.
  962. """
  963. # Create the test object.
  964. payload = self.MockPayload()
  965. payload.manifest.minor_version = minor_version
  966. payload_checker = checker.PayloadChecker(payload)
  967. payload_checker.payload_type = payload_type
  968. report = checker._PayloadReport()
  969. should_succeed = (
  970. (minor_version == 0 and payload_type == checker._TYPE_FULL) or
  971. (minor_version == 1 and payload_type == checker._TYPE_DELTA) or
  972. (minor_version == 2 and payload_type == checker._TYPE_DELTA) or
  973. (minor_version == 3 and payload_type == checker._TYPE_DELTA) or
  974. (minor_version == 4 and payload_type == checker._TYPE_DELTA) or
  975. (minor_version == 5 and payload_type == checker._TYPE_DELTA))
  976. args = (report,)
  977. if should_succeed:
  978. self.assertIsNone(payload_checker._CheckManifestMinorVersion(*args))
  979. else:
  980. self.assertRaises(PayloadError,
  981. payload_checker._CheckManifestMinorVersion, *args)
  982. def DoRunTest(self, rootfs_part_size_provided, kernel_part_size_provided,
  983. fail_wrong_payload_type, fail_invalid_block_size,
  984. fail_mismatched_metadata_size, fail_mismatched_block_size,
  985. fail_excess_data, fail_rootfs_part_size_exceeded,
  986. fail_kernel_part_size_exceeded):
  987. """Tests Run()."""
  988. # Generate a test payload. For this test, we generate a full update that
  989. # has sample kernel and rootfs operations. Since most testing is done with
  990. # internal PayloadChecker methods that are tested elsewhere, here we only
  991. # tamper with what's actually being manipulated and/or tested in the Run()
  992. # method itself. Note that the checker doesn't verify partition hashes, so
  993. # they're safe to fake.
  994. payload_gen = test_utils.EnhancedPayloadGenerator()
  995. block_size = test_utils.KiB(4)
  996. payload_gen.SetBlockSize(block_size)
  997. kernel_filesystem_size = test_utils.KiB(16)
  998. rootfs_filesystem_size = test_utils.MiB(2)
  999. payload_gen.SetPartInfo(False, True, rootfs_filesystem_size,
  1000. hashlib.sha256('fake-new-rootfs-content').digest())
  1001. payload_gen.SetPartInfo(True, True, kernel_filesystem_size,
  1002. hashlib.sha256('fake-new-kernel-content').digest())
  1003. payload_gen.SetMinorVersion(0)
  1004. rootfs_part_size = 0
  1005. if rootfs_part_size_provided:
  1006. rootfs_part_size = rootfs_filesystem_size + block_size
  1007. rootfs_op_size = rootfs_part_size or rootfs_filesystem_size
  1008. if fail_rootfs_part_size_exceeded:
  1009. rootfs_op_size += block_size
  1010. payload_gen.AddOperationWithData(
  1011. False, common.OpType.REPLACE,
  1012. dst_extents=[(0, rootfs_op_size / block_size)],
  1013. data_blob=os.urandom(rootfs_op_size))
  1014. kernel_part_size = 0
  1015. if kernel_part_size_provided:
  1016. kernel_part_size = kernel_filesystem_size + block_size
  1017. kernel_op_size = kernel_part_size or kernel_filesystem_size
  1018. if fail_kernel_part_size_exceeded:
  1019. kernel_op_size += block_size
  1020. payload_gen.AddOperationWithData(
  1021. True, common.OpType.REPLACE,
  1022. dst_extents=[(0, kernel_op_size / block_size)],
  1023. data_blob=os.urandom(kernel_op_size))
  1024. # Generate payload (complete w/ signature) and create the test object.
  1025. if fail_invalid_block_size:
  1026. use_block_size = block_size + 5 # Not a power of two.
  1027. elif fail_mismatched_block_size:
  1028. use_block_size = block_size * 2 # Different that payload stated.
  1029. else:
  1030. use_block_size = block_size
  1031. # For the unittests 246 is the value that generated for the payload.
  1032. metadata_size = 246
  1033. if fail_mismatched_metadata_size:
  1034. metadata_size += 1
  1035. kwargs = {
  1036. 'payload_gen_dargs': {
  1037. 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
  1038. 'do_add_pseudo_operation': True,
  1039. 'is_pseudo_in_kernel': True,
  1040. 'padding': os.urandom(1024) if fail_excess_data else None},
  1041. 'checker_init_dargs': {
  1042. 'assert_type': 'delta' if fail_wrong_payload_type else 'full',
  1043. 'block_size': use_block_size}}
  1044. if fail_invalid_block_size:
  1045. self.assertRaises(PayloadError, _GetPayloadChecker,
  1046. payload_gen.WriteToFileWithData, **kwargs)
  1047. else:
  1048. payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
  1049. **kwargs)
  1050. kwargs = {
  1051. 'pubkey_file_name': test_utils._PUBKEY_FILE_NAME,
  1052. 'metadata_size': metadata_size,
  1053. 'part_sizes': {
  1054. common.KERNEL: kernel_part_size,
  1055. common.ROOTFS: rootfs_part_size}}
  1056. should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
  1057. fail_mismatched_metadata_size or fail_excess_data or
  1058. fail_rootfs_part_size_exceeded or
  1059. fail_kernel_part_size_exceeded)
  1060. if should_fail:
  1061. self.assertRaises(PayloadError, payload_checker.Run, **kwargs)
  1062. else:
  1063. self.assertIsNone(payload_checker.Run(**kwargs))
  1064. # This implements a generic API, hence the occasional unused args.
  1065. # pylint: disable=W0613
  1066. def ValidateCheckOperationTest(op_type_name, is_last, allow_signature,
  1067. allow_unhashed, fail_src_extents,
  1068. fail_dst_extents,
  1069. fail_mismatched_data_offset_length,
  1070. fail_missing_dst_extents, fail_src_length,
  1071. fail_dst_length, fail_data_hash,
  1072. fail_prev_data_offset, fail_bad_minor_version):
  1073. """Returns True iff the combination of arguments represents a valid test."""
  1074. op_type = _OpTypeByName(op_type_name)
  1075. # REPLACE/REPLACE_BZ/REPLACE_XZ operations don't read data from src
  1076. # partition. They are compatible with all valid minor versions, so we don't
  1077. # need to check that.
  1078. if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ,
  1079. common.OpType.REPLACE_XZ) and (fail_src_extents or
  1080. fail_src_length or
  1081. fail_bad_minor_version)):
  1082. return False
  1083. # MOVE and SOURCE_COPY operations don't carry data.
  1084. if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and (
  1085. fail_mismatched_data_offset_length or fail_data_hash or
  1086. fail_prev_data_offset)):
  1087. return False
  1088. return True
  1089. def TestMethodBody(run_method_name, run_dargs):
  1090. """Returns a function that invokes a named method with named arguments."""
  1091. return lambda self: getattr(self, run_method_name)(**run_dargs)
  1092. def AddParametricTests(tested_method_name, arg_space, validate_func=None):
  1093. """Enumerates and adds specific parametric tests to PayloadCheckerTest.
  1094. This function enumerates a space of test parameters (defined by arg_space),
  1095. then binds a new, unique method name in PayloadCheckerTest to a test function
  1096. that gets handed the said parameters. This is a preferable approach to doing
  1097. the enumeration and invocation during the tests because this way each test is
  1098. treated as a complete run by the unittest framework, and so benefits from the
  1099. usual setUp/tearDown mechanics.
  1100. Args:
  1101. tested_method_name: Name of the tested PayloadChecker method.
  1102. arg_space: A dictionary containing variables (keys) and lists of values
  1103. (values) associated with them.
  1104. validate_func: A function used for validating test argument combinations.
  1105. """
  1106. for value_tuple in itertools.product(*arg_space.itervalues()):
  1107. run_dargs = dict(zip(arg_space.iterkeys(), value_tuple))
  1108. if validate_func and not validate_func(**run_dargs):
  1109. continue
  1110. run_method_name = 'Do%sTest' % tested_method_name
  1111. test_method_name = 'test%s' % tested_method_name
  1112. for arg_key, arg_val in run_dargs.iteritems():
  1113. if arg_val or type(arg_val) is int:
  1114. test_method_name += '__%s=%s' % (arg_key, arg_val)
  1115. setattr(PayloadCheckerTest, test_method_name,
  1116. TestMethodBody(run_method_name, run_dargs))
  1117. def AddAllParametricTests():
  1118. """Enumerates and adds all parametric tests to PayloadCheckerTest."""
  1119. # Add all _CheckElem() test cases.
  1120. AddParametricTests('AddElem',
  1121. {'linebreak': (True, False),
  1122. 'indent': (0, 1, 2),
  1123. 'convert': (str, lambda s: s[::-1]),
  1124. 'is_present': (True, False),
  1125. 'is_mandatory': (True, False),
  1126. 'is_submsg': (True, False)})
  1127. # Add all _Add{Mandatory,Optional}Field tests.
  1128. AddParametricTests('AddField',
  1129. {'is_mandatory': (True, False),
  1130. 'linebreak': (True, False),
  1131. 'indent': (0, 1, 2),
  1132. 'convert': (str, lambda s: s[::-1]),
  1133. 'is_present': (True, False)})
  1134. # Add all _Add{Mandatory,Optional}SubMsg tests.
  1135. AddParametricTests('AddSubMsg',
  1136. {'is_mandatory': (True, False),
  1137. 'is_present': (True, False)})
  1138. # Add all _CheckManifest() test cases.
  1139. AddParametricTests('CheckManifest',
  1140. {'fail_mismatched_block_size': (True, False),
  1141. 'fail_bad_sigs': (True, False),
  1142. 'fail_mismatched_oki_ori': (True, False),
  1143. 'fail_bad_oki': (True, False),
  1144. 'fail_bad_ori': (True, False),
  1145. 'fail_bad_nki': (True, False),
  1146. 'fail_bad_nri': (True, False),
  1147. 'fail_old_kernel_fs_size': (True, False),
  1148. 'fail_old_rootfs_fs_size': (True, False),
  1149. 'fail_new_kernel_fs_size': (True, False),
  1150. 'fail_new_rootfs_fs_size': (True, False)})
  1151. # Add all _CheckOperation() test cases.
  1152. AddParametricTests('CheckOperation',
  1153. {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'REPLACE_XZ',
  1154. 'MOVE', 'BSDIFF', 'SOURCE_COPY',
  1155. 'SOURCE_BSDIFF', 'PUFFDIFF',
  1156. 'BROTLI_BSDIFF'),
  1157. 'is_last': (True, False),
  1158. 'allow_signature': (True, False),
  1159. 'allow_unhashed': (True, False),
  1160. 'fail_src_extents': (True, False),
  1161. 'fail_dst_extents': (True, False),
  1162. 'fail_mismatched_data_offset_length': (True, False),
  1163. 'fail_missing_dst_extents': (True, False),
  1164. 'fail_src_length': (True, False),
  1165. 'fail_dst_length': (True, False),
  1166. 'fail_data_hash': (True, False),
  1167. 'fail_prev_data_offset': (True, False),
  1168. 'fail_bad_minor_version': (True, False)},
  1169. validate_func=ValidateCheckOperationTest)
  1170. # Add all _CheckOperations() test cases.
  1171. AddParametricTests('CheckOperations',
  1172. {'fail_nonexhaustive_full_update': (True, False)})
  1173. # Add all _CheckOperations() test cases.
  1174. AddParametricTests('CheckSignatures',
  1175. {'fail_empty_sigs_blob': (True, False),
  1176. 'fail_missing_pseudo_op': (True, False),
  1177. 'fail_mismatched_pseudo_op': (True, False),
  1178. 'fail_sig_missing_fields': (True, False),
  1179. 'fail_unknown_sig_version': (True, False),
  1180. 'fail_incorrect_sig': (True, False)})
  1181. # Add all _CheckManifestMinorVersion() test cases.
  1182. AddParametricTests('CheckManifestMinorVersion',
  1183. {'minor_version': (None, 0, 1, 2, 3, 4, 5, 555),
  1184. 'payload_type': (checker._TYPE_FULL,
  1185. checker._TYPE_DELTA)})
  1186. # Add all Run() test cases.
  1187. AddParametricTests('Run',
  1188. {'rootfs_part_size_provided': (True, False),
  1189. 'kernel_part_size_provided': (True, False),
  1190. 'fail_wrong_payload_type': (True, False),
  1191. 'fail_invalid_block_size': (True, False),
  1192. 'fail_mismatched_metadata_size': (True, False),
  1193. 'fail_mismatched_block_size': (True, False),
  1194. 'fail_excess_data': (True, False),
  1195. 'fail_rootfs_part_size_exceeded': (True, False),
  1196. 'fail_kernel_part_size_exceeded': (True, False)})
  1197. if __name__ == '__main__':
  1198. AddAllParametricTests()
  1199. unittest.main()