• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 
17 #define LOG_TAG "VtsSecurityAvbTest"
18 
19 #include <unistd.h>
20 
21 #include <array>
22 #include <list>
23 #include <map>
24 #include <set>
25 #include <vector>
26 
27 #include <android-base/file.h>
28 #include <android-base/logging.h>
29 #include <android-base/stringprintf.h>
30 #include <android-base/unique_fd.h>
31 #include <fs_avb/fs_avb_util.h>
32 #include <fs_mgr/roots.h>
33 #include <fstab/fstab.h>
34 #include <gtest/gtest.h>
35 #include <libavb/libavb.h>
36 #include <libdm/dm.h>
37 #include <log/log.h>
38 #include <openssl/sha.h>
39 
HexDigitToByte(char c)40 static uint8_t HexDigitToByte(char c) {
41   if (c >= '0' && c <= '9') {
42     return c - '0';
43   }
44   if (c >= 'a' && c <= 'f') {
45     return c - 'a' + 10;
46   }
47   if (c >= 'A' && c <= 'Z') {
48     return c - 'A' + 10;
49   }
50   return 0xff;
51 }
52 
HexToBytes(const std::string & hex,std::vector<uint8_t> * bytes)53 static bool HexToBytes(const std::string &hex, std::vector<uint8_t> *bytes) {
54   if (hex.size() % 2 != 0) {
55     return false;
56   }
57   bytes->resize(hex.size() / 2);
58   for (unsigned i = 0; i < bytes->size(); i++) {
59     uint8_t hi = HexDigitToByte(hex[i * 2]);
60     uint8_t lo = HexDigitToByte(hex[i * 2 + 1]);
61     if (lo > 0xf || hi > 0xf) {
62       return false;
63     }
64     bytes->at(i) = (hi << 4) | lo;
65   }
66   return true;
67 }
68 
69 // The abstract class of SHA algorithms.
70 class ShaHasher {
71  protected:
72   const uint32_t digest_size_;
73 
ShaHasher(uint32_t digest_size)74   ShaHasher(uint32_t digest_size) : digest_size_(digest_size) {}
75 
76  public:
~ShaHasher()77   virtual ~ShaHasher() {}
78 
GetDigestSize() const79   uint32_t GetDigestSize() const { return digest_size_; }
80 
81   virtual bool CalculateDigest(const void *buffer, size_t size,
82                                const void *salt, uint32_t block_length,
83                                uint8_t *digest) const = 0;
84 };
85 
86 template <typename CTX_TYPE>
87 class ShaHasherImpl : public ShaHasher {
88  private:
89   typedef int (*InitFunc)(CTX_TYPE *);
90   typedef int (*UpdateFunc)(CTX_TYPE *sha, const void *data, size_t len);
91   typedef int (*FinalFunc)(uint8_t *md, CTX_TYPE *sha);
92 
93   const InitFunc init_func_;
94   const UpdateFunc update_func_;
95   const FinalFunc final_func_;
96 
97  public:
ShaHasherImpl(InitFunc init_func,UpdateFunc update_func,FinalFunc final_func,uint32_t digest_size)98   ShaHasherImpl(InitFunc init_func, UpdateFunc update_func,
99                 FinalFunc final_func, uint32_t digest_size)
100       : ShaHasher(digest_size),
101         init_func_(init_func),
102         update_func_(update_func),
103         final_func_(final_func) {}
104 
~ShaHasherImpl()105   ~ShaHasherImpl() {}
106 
CalculateDigest(const void * buffer,size_t size,const void * salt,uint32_t salt_length,uint8_t * digest) const107   bool CalculateDigest(const void *buffer, size_t size, const void *salt,
108                        uint32_t salt_length, uint8_t *digest) const {
109     CTX_TYPE ctx;
110     if (init_func_(&ctx) != 1) {
111       return false;
112     }
113     if (update_func_(&ctx, salt, salt_length) != 1) {
114       return false;
115     }
116     if (update_func_(&ctx, buffer, size) != 1) {
117       return false;
118     }
119     if (final_func_(digest, &ctx) != 1) {
120       return false;
121     }
122     return true;
123   }
124 };
125 
126 // Creates a hasher with the parameters corresponding to the algorithm name.
CreateShaHasher(const std::string & algorithm)127 static std::unique_ptr<ShaHasher> CreateShaHasher(
128     const std::string &algorithm) {
129   if (algorithm == "sha1") {
130     return std::make_unique<ShaHasherImpl<SHA_CTX>>(
131         SHA1_Init, SHA1_Update, SHA1_Final, SHA_DIGEST_LENGTH);
132   }
133   if (algorithm == "sha256") {
134     return std::make_unique<ShaHasherImpl<SHA256_CTX>>(
135         SHA256_Init, SHA256_Update, SHA256_Final, SHA256_DIGEST_LENGTH);
136   }
137   if (algorithm == "sha512") {
138     return std::make_unique<ShaHasherImpl<SHA512_CTX>>(
139         SHA512_Init, SHA512_Update, SHA512_Final, SHA512_DIGEST_LENGTH);
140   }
141   return nullptr;
142 }
143 
144 // Calculates the digest of a block filled with 0.
CalculateZeroDigest(const ShaHasher & hasher,size_t size,const void * salt,int32_t block_length,uint8_t * digest)145 static bool CalculateZeroDigest(const ShaHasher &hasher, size_t size,
146                                 const void *salt, int32_t block_length,
147                                 uint8_t *digest) {
148   const std::vector<uint8_t> buffer(size, 0);
149   return hasher.CalculateDigest(buffer.data(), size, salt, block_length,
150                                 digest);
151 }
152 
153 // Logical structure of a hashtree:
154 //
155 // Level 2:                        [    root     ]
156 //                                /               \
157 // Level 1:              [entry_0]                 [entry_1]
158 //                      /   ...   \                   ...   \
159 // Level 0:   [entry_0_0]   ...   [entry_0_127]       ...   [entry_1_127]
160 //             /  ...  \           /   ...   \               /   ...   \
161 // Data:    blk_0 ... blk_127  blk_16256 ... blk_16383  blk_32640 ... blk_32767
162 //
163 // The digest of a data block or a hash block in level N is stored in level
164 // N + 1.
165 // The function VerifyHashtree allocates a HashtreeLevel for each level. It
166 // calculates the digests of the blocks in lower level and fills them in
167 // calculating_hash_block. When calculating_hash_block is full, it is compared
168 // with the hash block at comparing_tree_offset in the image. After comparison,
169 // calculating_hash_block is cleared and reused for the next hash block.
170 //
171 //                   comparing_tree_offset
172 //                   |
173 //                   v
174 // [<--------------------    level_size    -------------------->]
175 // [entry_0_0]  ...  [entry_0_127           ]  ...  [entry_1_127]
176 //
177 //                   [calculating_hash_block]
178 //                         ^
179 //                         |
180 //                         calculating_offset
181 struct HashtreeLevel {
182   // Offset of an expected hash block to compare, relative to the beginning of
183   // the hashtree in the image file.
184   uint64_t comparing_tree_offset;
185   // Size of this level, in bytes.
186   const uint64_t level_size;
187   // Offset of a digest in calculating_hash_block.
188   size_t calculating_offset;
189   // The hash block containing the digests calculated from the lower level.
190   std::vector<uint8_t> calculating_hash_block;
191 
HashtreeLevelHashtreeLevel192   HashtreeLevel(uint64_t lv_offset, uint64_t lv_size, size_t hash_block_size)
193       : comparing_tree_offset(lv_offset),
194         level_size(lv_size),
195         calculating_offset(0),
196         calculating_hash_block(hash_block_size) {}
197 };
198 
199 // Calculates and verifies the image's hashtree.
200 //
201 // Arguments:
202 //   image_fd: The raw image file.
203 //   image_size, data_block_size, hash_block_size, tree_offset, tree_size: The
204 //       fields in AvbHashtreeDescriptor.
205 //   salt: The binary value of the salt in FsAvbHashtreeDescriptor.
206 //   hasher: The ShaHasher object.
207 //   root_digest: The binary value of the root_digest in
208 //       FsAvbHashtreeDescriptor.
209 //
210 // Returns:
211 //   An empty string if the function succeeds.
212 //   Otherwise it returns the error message.
VerifyHashtree(int image_fd,uint64_t image_size,const std::vector<uint8_t> & salt,uint32_t data_block_size,uint32_t hash_block_size,uint64_t tree_offset,uint64_t tree_size,const ShaHasher & hasher,const std::vector<uint8_t> & root_digest)213 static std::string VerifyHashtree(int image_fd, uint64_t image_size,
214                                   const std::vector<uint8_t> &salt,
215                                   uint32_t data_block_size,
216                                   uint32_t hash_block_size,
217                                   uint64_t tree_offset, uint64_t tree_size,
218                                   const ShaHasher &hasher,
219                                   const std::vector<uint8_t> &root_digest) {
220   uint32_t digest_size = hasher.GetDigestSize();
221   uint32_t padded_digest_size = 1;
222   while (padded_digest_size < digest_size) {
223     padded_digest_size *= 2;
224   }
225 
226   if (image_size % data_block_size != 0) {
227     return "Image size is not a multiple of data block size";
228   }
229 
230   uint64_t data_block_count = image_size / data_block_size;
231   uint32_t digests_per_block = hash_block_size / padded_digest_size;
232 
233   // Initialize HashtreeLevel in bottom-up order.
234   std::list<HashtreeLevel> levels;
235   {
236     uint64_t hash_block_count = 0;
237     uint32_t level_block_count = data_block_count;
238     // Calculate the hashtree until the root hash is reached.
239     while (level_block_count > 1) {
240       uint32_t next_level_block_count =
241           (level_block_count + digests_per_block - 1) / digests_per_block;
242       hash_block_count += next_level_block_count;
243       // comparing_tree_offset will be initialized later.
244       levels.emplace_back(0 /* comparing_tree_offset */,
245                           next_level_block_count * hash_block_size,
246                           hash_block_size);
247       level_block_count = next_level_block_count;
248     }
249     if (hash_block_count * hash_block_size != tree_size) {
250       return "Block count and tree size mismatch";
251     }
252     // Append the root digest. Its level_size is unused.
253     levels.emplace_back(0 /* comparing_tree_offset */, 0 /* level_size */,
254                         digest_size);
255 
256     // Initialize comparing_tree_offset of each level
257     for (auto level = std::prev(levels.end()); level != levels.begin();
258          level--) {
259       std::prev(level)->comparing_tree_offset =
260           level->comparing_tree_offset + level->level_size;
261     }
262   }
263 
264   std::vector<uint8_t> padded_zero_digest(padded_digest_size, 0);
265   if (!CalculateZeroDigest(hasher, data_block_size, salt.data(), salt.size(),
266                            padded_zero_digest.data())) {
267     return "CalculateZeroDigest fails";
268   }
269 
270   std::vector<uint8_t> data_block(data_block_size);
271   std::vector<uint8_t> tree_block(hash_block_size);
272   for (uint64_t image_offset = 0; image_offset < image_size;
273        image_offset += data_block_size) {
274     ssize_t read_size = TEMP_FAILURE_RETRY(
275         pread64(image_fd, data_block.data(), data_block.size(), image_offset));
276     if (read_size != data_block.size()) {
277       return android::base::StringPrintf(
278           "Fail to read data block at offset %llu",
279           (unsigned long long)image_offset);
280     }
281 
282     bool is_last_data = (image_offset + data_block.size() == image_size);
283     // The block to be digested
284     std::vector<uint8_t> *current_block = &data_block;
285     for (auto level = levels.begin(); true; level++) {
286       uint8_t *current_digest =
287           level->calculating_hash_block.data() + level->calculating_offset;
288       if (!hasher.CalculateDigest(current_block->data(), current_block->size(),
289                                   salt.data(), salt.size(), current_digest)) {
290         return "CalculateDigest fails";
291       }
292       // Stop at root digest
293       if (std::next(level) == levels.end()) {
294         break;
295       }
296 
297       // Pad the digest
298       memset(current_digest + digest_size, 0, padded_digest_size - digest_size);
299       level->calculating_offset += padded_digest_size;
300       // Pad the last hash block of this level
301       if (is_last_data) {
302         memset(
303             level->calculating_hash_block.data() + level->calculating_offset, 0,
304             level->calculating_hash_block.size() - level->calculating_offset);
305       } else if (level->calculating_offset <
306                  level->calculating_hash_block.size()) {
307         // Stop at this level if the hash block is not full, continue to read
308         // more data_blocks from the outside loop for digest calculation
309         break;
310       }
311       // Verify the full hash block
312       // current_block may point to tree_block. Since the following pread64
313       // changes tree_block, do not read current_block in the rest of this
314       // code block.
315       current_block = nullptr;
316       read_size = TEMP_FAILURE_RETRY(
317           pread64(image_fd, tree_block.data(), tree_block.size(),
318                   tree_offset + level->comparing_tree_offset));
319       if (read_size != tree_block.size()) {
320         return android::base::StringPrintf(
321             "Fail to read tree block at offset %llu",
322             (unsigned long long)tree_offset + level->comparing_tree_offset);
323       }
324 
325       for (uint32_t offset = 0; offset < tree_block.size();
326            offset += padded_digest_size) {
327         // If the digest in the hashtree is equal to the digest of zero block,
328         // it indicates the corresponding data block is in DONT_CARE chunk in
329         // sparse image. The block should not be verified.
330         if (level == levels.begin() &&
331             memcmp(tree_block.data() + offset, padded_zero_digest.data(),
332                    padded_digest_size) == 0) {
333           continue;
334         }
335         if (memcmp(tree_block.data() + offset,
336                    level->calculating_hash_block.data() + offset,
337                    padded_digest_size) != 0) {
338           return android::base::StringPrintf(
339               "Hash blocks mismatch, block offset = %llu, digest offset = %u",
340               (unsigned long long)tree_offset + level->comparing_tree_offset,
341               offset);
342         }
343       }
344 
345       level->calculating_offset = 0;
346       level->comparing_tree_offset += hash_block_size;
347       if (level->comparing_tree_offset > tree_size) {
348         return "Tree offset is out of bound";
349       }
350       // Prepare for next/upper level, to calculate the digest of this
351       // hash_block for comparison
352       current_block = &tree_block;
353     }
354   }
355 
356   if (levels.back().calculating_hash_block != root_digest) {
357     return "Root digests mismatch";
358   }
359   return "";
360 }
361 
362 // Reads GSI public key from the path specified in VTS configuration.
363 //
364 // Returns:
365 //   The GSI public key read from the path.
366 //   An empty string if any file operation fails.
ReadGsiPublicKey()367 static std::string ReadGsiPublicKey() {
368   std::string key_blob;
369   if (android::base::ReadFileToString("/data/local/tmp/gsi.avbpubkey",
370                                       &key_blob)) {
371     return key_blob;
372   }
373   return "";
374 }
375 
376 // Converts descriptor.hash_algorithm to std::string.
GetHashAlgorithm(const AvbHashtreeDescriptor & descriptor)377 static std::string GetHashAlgorithm(const AvbHashtreeDescriptor &descriptor) {
378   return std::string(reinterpret_cast<const char *>(descriptor.hash_algorithm));
379 }
380 
381 // Gets the system partition's AvbHashtreeDescriptor and device file path.
382 //
383 // Arguments:
384 //  expected_key_blob: The key to verify the system's vbmeta.
385 //  out_verify_result: The result of vbmeta verification.
386 //  out_system_path: The system's device file path.
387 //
388 // Returns:
389 //   The pointer to the system's AvbHashtreeDescriptor.
390 //   nullptr if any operation fails.
391 static std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor>
GetSystemHashtreeDescriptor(const std::string & expected_key_blob,android::fs_mgr::VBMetaVerifyResult * out_verify_result,std::string * out_system_path)392 GetSystemHashtreeDescriptor(
393     const std::string &expected_key_blob,
394     android::fs_mgr::VBMetaVerifyResult *out_verify_result,
395     std::string *out_system_path) {
396   android::fs_mgr::Fstab default_fstab;
397   bool ok = ReadDefaultFstab(&default_fstab);
398   if (!ok) {
399     ALOGE("ReadDefaultFstab fails");
400     return nullptr;
401   }
402   android::fs_mgr::FstabEntry *system_fstab_entry =
403       GetEntryForPath(&default_fstab, "/system");
404   if (system_fstab_entry == nullptr) {
405     ALOGE("GetEntryForPath fails");
406     return nullptr;
407   }
408 
409   ok = fs_mgr_update_logical_partition(system_fstab_entry);
410   if (!ok) {
411     ALOGE("fs_mgr_update_logical_partition fails");
412     return nullptr;
413   }
414 
415   CHECK(out_system_path != nullptr);
416   *out_system_path = system_fstab_entry->blk_device;
417 
418   std::string out_public_key_data;
419   std::string out_avb_partition_name;
420   std::unique_ptr<android::fs_mgr::VBMetaData> vbmeta =
421       android::fs_mgr::LoadAndVerifyVbmeta(
422           *system_fstab_entry, expected_key_blob, &out_public_key_data,
423           &out_avb_partition_name, out_verify_result);
424   if (vbmeta == nullptr) {
425     ALOGE("LoadAndVerifyVbmeta fails");
426     return nullptr;
427   }
428 
429   std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor> descriptor =
430       android::fs_mgr::GetHashtreeDescriptor("system", std::move(*vbmeta));
431   if (descriptor == nullptr) {
432     ALOGE("GetHashtreeDescriptor fails");
433     return nullptr;
434   }
435 
436   return descriptor;
437 }
438 
439 // Loads contents and metadata of logical system partition, calculates
440 // the hashtree, and compares with the metadata.
TEST(AvbTest,SystemHashtree)441 TEST(AvbTest, SystemHashtree) {
442   std::string expected_key_blob = ReadGsiPublicKey();
443   EXPECT_NE(expected_key_blob, "") << "Fail to read expected GSI key.";
444 
445   android::fs_mgr::VBMetaVerifyResult verify_result;
446   std::string system_path;
447   std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor> descriptor =
448       GetSystemHashtreeDescriptor(expected_key_blob, &verify_result,
449                                   &system_path);
450   ASSERT_TRUE(descriptor);
451 
452   ALOGI("System partition is %s", system_path.c_str());
453 
454   // TODO: Skip assertion when running with non-compliance configuration.
455   EXPECT_EQ(verify_result, android::fs_mgr::VBMetaVerifyResult::kSuccess);
456   EXPECT_NE(verify_result,
457             android::fs_mgr::VBMetaVerifyResult::kErrorVerification)
458       << "The system image is not an officially signed GSI.";
459 
460   const std::string &salt_str = descriptor->salt;
461   const std::string &expected_digest_str = descriptor->root_digest;
462 
463   android::base::unique_fd fd(open(system_path.c_str(), O_RDONLY));
464   ASSERT_GE(fd, 0) << "Fail to open system partition. Try 'adb root'.";
465 
466   const std::string hash_algorithm(GetHashAlgorithm(*descriptor));
467   ALOGI("hash_algorithm = %s", hash_algorithm.c_str());
468 
469   std::unique_ptr<ShaHasher> hasher = CreateShaHasher(hash_algorithm);
470   ASSERT_TRUE(hasher);
471 
472   std::vector<uint8_t> salt, expected_digest;
473   bool ok = HexToBytes(salt_str, &salt);
474   ASSERT_TRUE(ok) << "Invalid salt in descriptor: " << salt_str;
475   ok = HexToBytes(expected_digest_str, &expected_digest);
476   ASSERT_TRUE(ok) << "Invalid digest in descriptor: " << expected_digest_str;
477   ASSERT_EQ(expected_digest.size(), hasher->GetDigestSize());
478 
479   ALOGI("image_size = %llu", (unsigned long long)descriptor->image_size);
480   ALOGI("data_block_size = %u", descriptor->data_block_size);
481   ALOGI("hash_block_size = %u", descriptor->hash_block_size);
482   ALOGI("tree_offset = %llu", (unsigned long long)descriptor->tree_offset);
483   ALOGI("tree_size = %llu", (unsigned long long)descriptor->tree_size);
484 
485   std::string error_message = VerifyHashtree(
486       fd, descriptor->image_size, salt, descriptor->data_block_size,
487       descriptor->hash_block_size, descriptor->tree_offset,
488       descriptor->tree_size, *hasher, expected_digest);
489   ASSERT_EQ(error_message, "");
490 }
491 
492 // Finds the next word consisting of non-whitespace characters in a string.
493 //
494 // Arguments:
495 //   str: The string to be searched for the next word.
496 //   pos: The starting position to search for the next word.
497 //        This function sets it to the past-the-end position of the word.
498 //
499 // Returns:
500 //   The starting position of the word.
501 //   If there is no next word, this function does not change pos and returns
502 //   std::string::npos.
NextWord(const std::string & str,size_t * pos)503 static size_t NextWord(const std::string &str, size_t *pos) {
504   const char *whitespaces = " \t\r\n";
505   size_t start = str.find_first_not_of(whitespaces, *pos);
506   if (start == std::string::npos) {
507     return start;
508   }
509   *pos = str.find_first_of(whitespaces, start);
510   if (*pos == std::string::npos) {
511     *pos = str.size();
512   }
513   return start;
514 }
515 
516 // Compares device mapper table with system hashtree descriptor.
TEST(AvbTest,SystemDescriptor)517 TEST(AvbTest, SystemDescriptor) {
518   // Get system hashtree descriptor.
519   std::string expected_key_blob = ReadGsiPublicKey();
520   EXPECT_NE(expected_key_blob, "") << "Fail to read expected GSI key.";
521 
522   android::fs_mgr::VBMetaVerifyResult verify_result;
523   std::string system_path;
524   std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor> descriptor =
525       GetSystemHashtreeDescriptor(expected_key_blob, &verify_result,
526                                   &system_path);
527   ASSERT_TRUE(descriptor);
528 
529   // TODO: Assert when running with compliance configuration.
530   // The SystemHashtree function asserts verify_result.
531   if (verify_result != android::fs_mgr::VBMetaVerifyResult::kSuccess) {
532     ALOGW("The system image is not an officially signed GSI.");
533   }
534 
535   // Get device mapper table.
536   android::dm::DeviceMapper &device_mapper =
537       android::dm::DeviceMapper::Instance();
538   std::vector<android::dm::DeviceMapper::TargetInfo> table;
539   bool ok = device_mapper.GetTableInfo("system-verity", &table);
540   ASSERT_TRUE(ok) << "GetTableInfo fails";
541   ASSERT_EQ(table.size(), 1);
542   const android::dm::DeviceMapper::TargetInfo &target = table[0];
543   // Sample output:
544   // Device mapper table for system-verity:
545   // 0-1783288: verity, 1 253:0 253:0 4096 4096 222911 222911 sha1
546   // 6b2b46715a2d27c53cc7f91fe63ce798ff1f3df7
547   // 65bc99ca8e97379d4f7adc66664941acc0a8e682 10 restart_on_corruption
548   // ignore_zero_blocks use_fec_from_device 253:0 fec_blocks 224668 fec_start
549   // 224668 fec_roots 2
550   ALOGI("Device mapper table for system-verity:\n%llu-%llu: %s, %s",
551         target.spec.sector_start, target.spec.sector_start + target.spec.length,
552         target.spec.target_type, target.data.c_str());
553   EXPECT_EQ(strcmp(target.spec.target_type, "verity"), 0);
554 
555   // Compare the target's positional parameters with the descriptor. Reference:
556   // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#mapping-table-for-verity-target
557   std::array<std::string, 10> descriptor_values = {
558       std::to_string(descriptor->dm_verity_version),
559       "",  // skip data_dev
560       "",  // skip hash_dev
561       std::to_string(descriptor->data_block_size),
562       std::to_string(descriptor->hash_block_size),
563       std::to_string(descriptor->image_size /
564                      descriptor->data_block_size),  // #blocks
565       std::to_string(descriptor->image_size /
566                      descriptor->data_block_size),  // hash_start
567       GetHashAlgorithm(*descriptor),
568       descriptor->root_digest,
569       descriptor->salt,
570   };
571 
572   size_t next_pos = 0;
573   for (const std::string &descriptor_value : descriptor_values) {
574     size_t begin_pos = NextWord(target.data, &next_pos);
575     ASSERT_NE(begin_pos, std::string::npos);
576     if (!descriptor_value.empty()) {
577       EXPECT_EQ(target.data.compare(begin_pos, next_pos - begin_pos,
578                                     descriptor_value),
579                 0);
580     }
581   }
582 
583   // Compare the target's optional parameters with the descriptor.
584   unsigned long opt_param_count;
585   {
586     size_t begin_pos = NextWord(target.data, &next_pos);
587     ASSERT_NE(begin_pos, std::string::npos);
588     opt_param_count =
589         std::stoul(target.data.substr(begin_pos, next_pos - begin_pos));
590   }
591   // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#optional-parameters
592   std::set<std::string> opt_params = {
593       "check_at_most_once",
594       "ignore_corruption",
595       "ignore_zero_blocks",
596       "restart_on_corruption",
597   };
598   // https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#optional-fec-forward-error-correction-parameters
599   std::map<std::string, std::string> opt_fec_params = {
600       {"fec_blocks", ""},
601       {"fec_roots", ""},
602       {"fec_start", ""},
603       {"use_fec_from_device", ""},
604   };
605 
606   for (unsigned long i = 0; i < opt_param_count; i++) {
607     size_t begin_pos = NextWord(target.data, &next_pos);
608     ASSERT_NE(begin_pos, std::string::npos);
609     const std::string param_name(target.data, begin_pos, next_pos - begin_pos);
610     if (opt_fec_params.count(param_name)) {
611       i++;
612       ASSERT_LT(i, opt_param_count);
613       begin_pos = NextWord(target.data, &next_pos);
614       ASSERT_NE(begin_pos, std::string::npos);
615       opt_fec_params[param_name] =
616           target.data.substr(begin_pos, next_pos - begin_pos);
617     } else {
618       ASSERT_NE(opt_params.count(param_name), 0)
619           << "Unknown dm-verity target parameter: " << param_name;
620     }
621   }
622 
623   EXPECT_EQ(opt_fec_params["fec_roots"],
624             std::to_string(descriptor->fec_num_roots));
625   EXPECT_EQ(
626       opt_fec_params["fec_blocks"],
627       std::to_string(descriptor->fec_offset / descriptor->data_block_size));
628   EXPECT_EQ(
629       opt_fec_params["fec_start"],
630       std::to_string(descriptor->fec_offset / descriptor->data_block_size));
631   // skip use_fec_from_device
632 
633   ASSERT_EQ(NextWord(target.data, &next_pos), std::string::npos);
634 }
635