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