1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Permission is hereby granted, free of charge, to any person 5 * obtaining a copy of this software and associated documentation 6 * files (the "Software"), to deal in the Software without 7 * restriction, including without limitation the rights to use, copy, 8 * modify, merge, publish, distribute, sublicense, and/or sell copies 9 * of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice shall be 13 * included in all copies or substantial portions of the Software. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 */ 24 25 #include "avb_slot_verify.h" 26 #include "avb_chain_partition_descriptor.h" 27 #include "avb_footer.h" 28 #include "avb_hash_descriptor.h" 29 #include "avb_kernel_cmdline_descriptor.h" 30 #include "avb_sha.h" 31 #include "avb_util.h" 32 #include "avb_vbmeta_image.h" 33 #include "avb_version.h" 34 35 /* Maximum allow length (in bytes) of a partition name, including 36 * ab_suffix. 37 */ 38 #define PART_NAME_MAX_SIZE 32 39 40 /* Maximum number of partitions that can be loaded with avb_slot_verify(). */ 41 #define MAX_NUMBER_OF_LOADED_PARTITIONS 32 42 43 /* Maximum number of vbmeta images that can be loaded with avb_slot_verify(). */ 44 #define MAX_NUMBER_OF_VBMETA_IMAGES 32 45 46 /* Maximum size of a vbmeta image - 64 KiB. */ 47 #define VBMETA_MAX_SIZE (64 * 1024) 48 49 /* Helper function to see if we should continue with verification in 50 * allow_verification_error=true mode if something goes wrong. See the 51 * comments for the avb_slot_verify() function for more information. 52 */ 53 static inline bool result_should_continue(AvbSlotVerifyResult result) { 54 switch (result) { 55 case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: 56 case AVB_SLOT_VERIFY_RESULT_ERROR_IO: 57 case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA: 58 case AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION: 59 return false; 60 61 case AVB_SLOT_VERIFY_RESULT_OK: 62 case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION: 63 case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX: 64 case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED: 65 return true; 66 } 67 68 return false; 69 } 70 71 static AvbSlotVerifyResult load_and_verify_hash_partition( 72 AvbOps* ops, 73 const char* const* requested_partitions, 74 const char* ab_suffix, 75 bool allow_verification_error, 76 const AvbDescriptor* descriptor, 77 AvbSlotVerifyData* slot_data) { 78 AvbHashDescriptor hash_desc; 79 const uint8_t* desc_partition_name = NULL; 80 const uint8_t* desc_salt; 81 const uint8_t* desc_digest; 82 char part_name[PART_NAME_MAX_SIZE]; 83 AvbSlotVerifyResult ret; 84 AvbIOResult io_ret; 85 uint8_t* image_buf = NULL; 86 size_t part_num_read; 87 uint8_t* digest; 88 size_t digest_len; 89 const char* found; 90 91 if (!avb_hash_descriptor_validate_and_byteswap( 92 (const AvbHashDescriptor*)descriptor, &hash_desc)) { 93 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 94 goto out; 95 } 96 97 desc_partition_name = 98 ((const uint8_t*)descriptor) + sizeof(AvbHashDescriptor); 99 desc_salt = desc_partition_name + hash_desc.partition_name_len; 100 desc_digest = desc_salt + hash_desc.salt_len; 101 102 if (!avb_validate_utf8(desc_partition_name, hash_desc.partition_name_len)) { 103 avb_error("Partition name is not valid UTF-8.\n"); 104 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 105 goto out; 106 } 107 108 if (!avb_str_concat(part_name, 109 sizeof part_name, 110 (const char*)desc_partition_name, 111 hash_desc.partition_name_len, 112 ab_suffix, 113 avb_strlen(ab_suffix))) { 114 avb_error("Partition name and suffix does not fit.\n"); 115 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 116 goto out; 117 } 118 119 image_buf = avb_malloc(hash_desc.image_size); 120 if (image_buf == NULL) { 121 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 122 goto out; 123 } 124 125 io_ret = ops->read_from_partition(ops, 126 part_name, 127 0 /* offset */, 128 hash_desc.image_size, 129 image_buf, 130 &part_num_read); 131 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 132 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 133 goto out; 134 } else if (io_ret != AVB_IO_RESULT_OK) { 135 avb_errorv(part_name, ": Error loading data from partition.\n", NULL); 136 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 137 goto out; 138 } 139 if (part_num_read != hash_desc.image_size) { 140 avb_errorv(part_name, ": Read fewer than requested bytes.\n", NULL); 141 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 142 goto out; 143 } 144 145 if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha256") == 0) { 146 AvbSHA256Ctx sha256_ctx; 147 avb_sha256_init(&sha256_ctx); 148 avb_sha256_update(&sha256_ctx, desc_salt, hash_desc.salt_len); 149 avb_sha256_update(&sha256_ctx, image_buf, hash_desc.image_size); 150 digest = avb_sha256_final(&sha256_ctx); 151 digest_len = AVB_SHA256_DIGEST_SIZE; 152 } else if (avb_strcmp((const char*)hash_desc.hash_algorithm, "sha512") == 0) { 153 AvbSHA512Ctx sha512_ctx; 154 avb_sha512_init(&sha512_ctx); 155 avb_sha512_update(&sha512_ctx, desc_salt, hash_desc.salt_len); 156 avb_sha512_update(&sha512_ctx, image_buf, hash_desc.image_size); 157 digest = avb_sha512_final(&sha512_ctx); 158 digest_len = AVB_SHA512_DIGEST_SIZE; 159 } else { 160 avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL); 161 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 162 goto out; 163 } 164 165 if (digest_len != hash_desc.digest_len) { 166 avb_errorv( 167 part_name, ": Digest in descriptor not of expected size.\n", NULL); 168 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 169 goto out; 170 } 171 172 if (avb_safe_memcmp(digest, desc_digest, digest_len) != 0) { 173 avb_errorv(part_name, 174 ": Hash of data does not match digest in descriptor.\n", 175 NULL); 176 ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; 177 goto out; 178 } 179 180 ret = AVB_SLOT_VERIFY_RESULT_OK; 181 182 out: 183 184 if (ret == AVB_SLOT_VERIFY_RESULT_OK || result_should_continue(ret)) { 185 /* If this is the requested partition, copy to slot_data. */ 186 found = avb_strv_find_str(requested_partitions, 187 (const char*)desc_partition_name, 188 hash_desc.partition_name_len); 189 if (found != NULL) { 190 AvbPartitionData* loaded_partition; 191 if (slot_data->num_loaded_partitions == MAX_NUMBER_OF_LOADED_PARTITIONS) { 192 avb_errorv(part_name, ": Too many loaded partitions.\n", NULL); 193 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 194 goto fail; 195 } 196 loaded_partition = 197 &slot_data->loaded_partitions[slot_data->num_loaded_partitions++]; 198 loaded_partition->partition_name = avb_strdup(found); 199 loaded_partition->data_size = hash_desc.image_size; 200 loaded_partition->data = image_buf; 201 image_buf = NULL; 202 } 203 } 204 205 fail: 206 if (image_buf != NULL) { 207 avb_free(image_buf); 208 } 209 return ret; 210 } 211 212 static AvbSlotVerifyResult load_and_verify_vbmeta( 213 AvbOps* ops, 214 const char* const* requested_partitions, 215 const char* ab_suffix, 216 bool allow_verification_error, 217 AvbVBMetaImageFlags toplevel_vbmeta_flags, 218 int rollback_index_location, 219 const char* partition_name, 220 size_t partition_name_len, 221 const uint8_t* expected_public_key, 222 size_t expected_public_key_length, 223 AvbSlotVerifyData* slot_data, 224 AvbAlgorithmType* out_algorithm_type) { 225 char full_partition_name[PART_NAME_MAX_SIZE]; 226 AvbSlotVerifyResult ret; 227 AvbIOResult io_ret; 228 size_t vbmeta_offset; 229 size_t vbmeta_size; 230 uint8_t* vbmeta_buf = NULL; 231 size_t vbmeta_num_read; 232 AvbVBMetaVerifyResult vbmeta_ret; 233 const uint8_t* pk_data; 234 size_t pk_len; 235 AvbVBMetaImageHeader vbmeta_header; 236 uint64_t stored_rollback_index; 237 const AvbDescriptor** descriptors = NULL; 238 size_t num_descriptors; 239 size_t n; 240 bool is_main_vbmeta; 241 bool is_vbmeta_partition; 242 AvbVBMetaData* vbmeta_image_data = NULL; 243 244 ret = AVB_SLOT_VERIFY_RESULT_OK; 245 246 avb_assert(slot_data != NULL); 247 248 /* Since we allow top-level vbmeta in 'boot', use 249 * rollback_index_location to determine whether we're the main 250 * vbmeta struct. 251 */ 252 is_main_vbmeta = (rollback_index_location == 0); 253 is_vbmeta_partition = (avb_strcmp(partition_name, "vbmeta") == 0); 254 255 if (!avb_validate_utf8((const uint8_t*)partition_name, partition_name_len)) { 256 avb_error("Partition name is not valid UTF-8.\n"); 257 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 258 goto out; 259 } 260 261 /* Construct full partition name. */ 262 if (!avb_str_concat(full_partition_name, 263 sizeof full_partition_name, 264 partition_name, 265 partition_name_len, 266 ab_suffix, 267 avb_strlen(ab_suffix))) { 268 avb_error("Partition name and suffix does not fit.\n"); 269 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 270 goto out; 271 } 272 273 avb_debugv("Loading vbmeta struct from partition '", 274 full_partition_name, 275 "'.\n", 276 NULL); 277 278 /* If we're loading from the main vbmeta partition, the vbmeta 279 * struct is in the beginning. Otherwise we have to locate it via a 280 * footer. 281 */ 282 if (is_vbmeta_partition) { 283 vbmeta_offset = 0; 284 vbmeta_size = VBMETA_MAX_SIZE; 285 } else { 286 uint8_t footer_buf[AVB_FOOTER_SIZE]; 287 size_t footer_num_read; 288 AvbFooter footer; 289 290 io_ret = ops->read_from_partition(ops, 291 full_partition_name, 292 -AVB_FOOTER_SIZE, 293 AVB_FOOTER_SIZE, 294 footer_buf, 295 &footer_num_read); 296 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 297 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 298 goto out; 299 } else if (io_ret != AVB_IO_RESULT_OK) { 300 avb_errorv(full_partition_name, ": Error loading footer.\n", NULL); 301 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 302 goto out; 303 } 304 avb_assert(footer_num_read == AVB_FOOTER_SIZE); 305 306 if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf, 307 &footer)) { 308 avb_errorv(full_partition_name, ": Error validating footer.\n", NULL); 309 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 310 goto out; 311 } 312 313 /* Basic footer sanity check since the data is untrusted. */ 314 if (footer.vbmeta_size > VBMETA_MAX_SIZE) { 315 avb_errorv( 316 full_partition_name, ": Invalid vbmeta size in footer.\n", NULL); 317 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 318 goto out; 319 } 320 321 vbmeta_offset = footer.vbmeta_offset; 322 vbmeta_size = footer.vbmeta_size; 323 } 324 325 vbmeta_buf = avb_malloc(vbmeta_size); 326 if (vbmeta_buf == NULL) { 327 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 328 goto out; 329 } 330 331 io_ret = ops->read_from_partition(ops, 332 full_partition_name, 333 vbmeta_offset, 334 vbmeta_size, 335 vbmeta_buf, 336 &vbmeta_num_read); 337 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 338 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 339 goto out; 340 } else if (io_ret != AVB_IO_RESULT_OK) { 341 /* If we're looking for 'vbmeta' but there is no such partition, 342 * go try to get it from the boot partition instead. 343 */ 344 if (is_main_vbmeta && io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION && 345 is_vbmeta_partition) { 346 avb_debugv(full_partition_name, 347 ": No such partition. Trying 'boot' instead.\n", 348 NULL); 349 ret = load_and_verify_vbmeta(ops, 350 requested_partitions, 351 ab_suffix, 352 allow_verification_error, 353 0 /* toplevel_vbmeta_flags */, 354 0 /* rollback_index_location */, 355 "boot", 356 avb_strlen("boot"), 357 NULL /* expected_public_key */, 358 0 /* expected_public_key_length */, 359 slot_data, 360 out_algorithm_type); 361 goto out; 362 } else { 363 avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL); 364 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 365 goto out; 366 } 367 } 368 avb_assert(vbmeta_num_read <= vbmeta_size); 369 370 /* Check if the image is properly signed and get the public key used 371 * to sign the image. 372 */ 373 vbmeta_ret = 374 avb_vbmeta_image_verify(vbmeta_buf, vbmeta_num_read, &pk_data, &pk_len); 375 switch (vbmeta_ret) { 376 case AVB_VBMETA_VERIFY_RESULT_OK: 377 avb_assert(pk_data != NULL && pk_len > 0); 378 break; 379 380 case AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED: 381 case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH: 382 case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH: 383 ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; 384 avb_errorv(full_partition_name, 385 ": Error verifying vbmeta image: ", 386 avb_vbmeta_verify_result_to_string(vbmeta_ret), 387 "\n", 388 NULL); 389 if (!allow_verification_error) { 390 goto out; 391 } 392 break; 393 394 case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER: 395 /* No way to continue this case. */ 396 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 397 avb_errorv(full_partition_name, 398 ": Error verifying vbmeta image: invalid vbmeta header\n", 399 NULL); 400 goto out; 401 402 case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION: 403 /* No way to continue this case. */ 404 ret = AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION; 405 avb_errorv(full_partition_name, 406 ": Error verifying vbmeta image: unsupported AVB version\n", 407 NULL); 408 goto out; 409 } 410 411 /* Byteswap the header. */ 412 avb_vbmeta_image_header_to_host_byte_order((AvbVBMetaImageHeader*)vbmeta_buf, 413 &vbmeta_header); 414 415 /* If we're the toplevel, assign flags so they'll be passed down. */ 416 if (is_main_vbmeta) { 417 toplevel_vbmeta_flags = (AvbVBMetaImageFlags)vbmeta_header.flags; 418 } else { 419 if (vbmeta_header.flags != 0) { 420 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 421 avb_errorv(full_partition_name, 422 ": chained vbmeta image has non-zero flags\n", 423 NULL); 424 goto out; 425 } 426 } 427 428 /* Check if key used to make signature matches what is expected. */ 429 if (pk_data != NULL) { 430 if (expected_public_key != NULL) { 431 avb_assert(!is_main_vbmeta); 432 if (expected_public_key_length != pk_len || 433 avb_safe_memcmp(expected_public_key, pk_data, pk_len) != 0) { 434 avb_errorv(full_partition_name, 435 ": Public key used to sign data does not match key in chain " 436 "partition descriptor.\n", 437 NULL); 438 ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED; 439 if (!allow_verification_error) { 440 goto out; 441 } 442 } 443 } else { 444 bool key_is_trusted = false; 445 const uint8_t* pk_metadata = NULL; 446 size_t pk_metadata_len = 0; 447 448 if (vbmeta_header.public_key_metadata_size > 0) { 449 pk_metadata = vbmeta_buf + sizeof(AvbVBMetaImageHeader) + 450 vbmeta_header.authentication_data_block_size + 451 vbmeta_header.public_key_metadata_offset; 452 pk_metadata_len = vbmeta_header.public_key_metadata_size; 453 } 454 455 avb_assert(is_main_vbmeta); 456 io_ret = ops->validate_vbmeta_public_key( 457 ops, pk_data, pk_len, pk_metadata, pk_metadata_len, &key_is_trusted); 458 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 459 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 460 goto out; 461 } else if (io_ret != AVB_IO_RESULT_OK) { 462 avb_errorv(full_partition_name, 463 ": Error while checking public key used to sign data.\n", 464 NULL); 465 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 466 goto out; 467 } 468 if (!key_is_trusted) { 469 avb_errorv(full_partition_name, 470 ": Public key used to sign data rejected.\n", 471 NULL); 472 ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED; 473 if (!allow_verification_error) { 474 goto out; 475 } 476 } 477 } 478 } 479 480 /* Check rollback index. */ 481 io_ret = ops->read_rollback_index( 482 ops, rollback_index_location, &stored_rollback_index); 483 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 484 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 485 goto out; 486 } else if (io_ret != AVB_IO_RESULT_OK) { 487 avb_errorv(full_partition_name, 488 ": Error getting rollback index for location.\n", 489 NULL); 490 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 491 goto out; 492 } 493 if (vbmeta_header.rollback_index < stored_rollback_index) { 494 avb_errorv( 495 full_partition_name, 496 ": Image rollback index is less than the stored rollback index.\n", 497 NULL); 498 ret = AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX; 499 if (!allow_verification_error) { 500 goto out; 501 } 502 } 503 504 /* Copy vbmeta to vbmeta_images before recursing. */ 505 if (is_main_vbmeta) { 506 avb_assert(slot_data->num_vbmeta_images == 0); 507 } else { 508 avb_assert(slot_data->num_vbmeta_images > 0); 509 } 510 if (slot_data->num_vbmeta_images == MAX_NUMBER_OF_VBMETA_IMAGES) { 511 avb_errorv(full_partition_name, ": Too many vbmeta images.\n", NULL); 512 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 513 goto out; 514 } 515 vbmeta_image_data = &slot_data->vbmeta_images[slot_data->num_vbmeta_images++]; 516 vbmeta_image_data->partition_name = avb_strdup(partition_name); 517 vbmeta_image_data->vbmeta_data = vbmeta_buf; 518 /* Note that |vbmeta_buf| is actually |vbmeta_num_read| bytes long 519 * and this includes data past the end of the image. Pass the 520 * actual size of the vbmeta image. Also, no need to use 521 * avb_safe_add() since the header has already been verified. 522 */ 523 vbmeta_image_data->vbmeta_size = 524 sizeof(AvbVBMetaImageHeader) + 525 vbmeta_header.authentication_data_block_size + 526 vbmeta_header.auxiliary_data_block_size; 527 vbmeta_image_data->verify_result = vbmeta_ret; 528 529 /* Now go through all descriptors and take the appropriate action: 530 * 531 * - hash descriptor: Load data from partition, calculate hash, and 532 * checks that it matches what's in the hash descriptor. 533 * 534 * - hashtree descriptor: Do nothing since verification happens 535 * on-the-fly from within the OS. 536 * 537 * - chained partition descriptor: Load the footer, load the vbmeta 538 * image, verify vbmeta image (includes rollback checks, hash 539 * checks, bail on chained partitions). 540 */ 541 descriptors = 542 avb_descriptor_get_all(vbmeta_buf, vbmeta_num_read, &num_descriptors); 543 for (n = 0; n < num_descriptors; n++) { 544 AvbDescriptor desc; 545 546 if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) { 547 avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL); 548 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 549 goto out; 550 } 551 552 switch (desc.tag) { 553 case AVB_DESCRIPTOR_TAG_HASH: { 554 AvbSlotVerifyResult sub_ret; 555 sub_ret = load_and_verify_hash_partition(ops, 556 requested_partitions, 557 ab_suffix, 558 allow_verification_error, 559 descriptors[n], 560 slot_data); 561 if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { 562 ret = sub_ret; 563 if (!allow_verification_error || !result_should_continue(ret)) { 564 goto out; 565 } 566 } 567 } break; 568 569 case AVB_DESCRIPTOR_TAG_CHAIN_PARTITION: { 570 AvbSlotVerifyResult sub_ret; 571 AvbChainPartitionDescriptor chain_desc; 572 const uint8_t* chain_partition_name; 573 const uint8_t* chain_public_key; 574 575 /* Only allow CHAIN_PARTITION descriptors in the main vbmeta image. */ 576 if (!is_main_vbmeta) { 577 avb_errorv(full_partition_name, 578 ": Encountered chain descriptor not in main image.\n", 579 NULL); 580 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 581 goto out; 582 } 583 584 if (!avb_chain_partition_descriptor_validate_and_byteswap( 585 (AvbChainPartitionDescriptor*)descriptors[n], &chain_desc)) { 586 avb_errorv(full_partition_name, 587 ": Chain partition descriptor is invalid.\n", 588 NULL); 589 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 590 goto out; 591 } 592 593 if (chain_desc.rollback_index_location == 0) { 594 avb_errorv(full_partition_name, 595 ": Chain partition has invalid " 596 "rollback_index_location field.\n", 597 NULL); 598 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 599 goto out; 600 } 601 602 chain_partition_name = ((const uint8_t*)descriptors[n]) + 603 sizeof(AvbChainPartitionDescriptor); 604 chain_public_key = chain_partition_name + chain_desc.partition_name_len; 605 606 sub_ret = load_and_verify_vbmeta(ops, 607 requested_partitions, 608 ab_suffix, 609 allow_verification_error, 610 toplevel_vbmeta_flags, 611 chain_desc.rollback_index_location, 612 (const char*)chain_partition_name, 613 chain_desc.partition_name_len, 614 chain_public_key, 615 chain_desc.public_key_len, 616 slot_data, 617 NULL /* out_algorithm_type */); 618 if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { 619 ret = sub_ret; 620 if (!result_should_continue(ret)) { 621 goto out; 622 } 623 } 624 } break; 625 626 case AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE: { 627 const uint8_t* kernel_cmdline; 628 AvbKernelCmdlineDescriptor kernel_cmdline_desc; 629 bool apply_cmdline; 630 631 if (!avb_kernel_cmdline_descriptor_validate_and_byteswap( 632 (AvbKernelCmdlineDescriptor*)descriptors[n], 633 &kernel_cmdline_desc)) { 634 avb_errorv(full_partition_name, 635 ": Kernel cmdline descriptor is invalid.\n", 636 NULL); 637 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 638 goto out; 639 } 640 641 kernel_cmdline = ((const uint8_t*)descriptors[n]) + 642 sizeof(AvbKernelCmdlineDescriptor); 643 644 if (!avb_validate_utf8(kernel_cmdline, 645 kernel_cmdline_desc.kernel_cmdline_length)) { 646 avb_errorv(full_partition_name, 647 ": Kernel cmdline is not valid UTF-8.\n", 648 NULL); 649 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 650 goto out; 651 } 652 653 /* Compare the flags for top-level VBMeta struct with flags in 654 * the command-line descriptor so command-line snippets only 655 * intended for a certain mode (dm-verity enabled/disabled) 656 * are skipped if applicable. 657 */ 658 apply_cmdline = true; 659 if (toplevel_vbmeta_flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) { 660 if (kernel_cmdline_desc.flags & 661 AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) { 662 apply_cmdline = false; 663 } 664 } else { 665 if (kernel_cmdline_desc.flags & 666 AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) { 667 apply_cmdline = false; 668 } 669 } 670 671 if (apply_cmdline) { 672 if (slot_data->cmdline == NULL) { 673 slot_data->cmdline = 674 avb_calloc(kernel_cmdline_desc.kernel_cmdline_length + 1); 675 if (slot_data->cmdline == NULL) { 676 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 677 goto out; 678 } 679 avb_memcpy(slot_data->cmdline, 680 kernel_cmdline, 681 kernel_cmdline_desc.kernel_cmdline_length); 682 } else { 683 /* new cmdline is: <existing_cmdline> + ' ' + <newcmdline> + '\0' */ 684 size_t orig_size = avb_strlen(slot_data->cmdline); 685 size_t new_size = 686 orig_size + 1 + kernel_cmdline_desc.kernel_cmdline_length + 1; 687 char* new_cmdline = avb_calloc(new_size); 688 if (new_cmdline == NULL) { 689 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 690 goto out; 691 } 692 avb_memcpy(new_cmdline, slot_data->cmdline, orig_size); 693 new_cmdline[orig_size] = ' '; 694 avb_memcpy(new_cmdline + orig_size + 1, 695 kernel_cmdline, 696 kernel_cmdline_desc.kernel_cmdline_length); 697 avb_free(slot_data->cmdline); 698 slot_data->cmdline = new_cmdline; 699 } 700 } 701 } break; 702 703 /* Explicit fall-through */ 704 case AVB_DESCRIPTOR_TAG_PROPERTY: 705 case AVB_DESCRIPTOR_TAG_HASHTREE: 706 /* Do nothing. */ 707 break; 708 } 709 } 710 711 if (rollback_index_location >= AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS) { 712 avb_errorv( 713 full_partition_name, ": Invalid rollback_index_location.\n", NULL); 714 ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; 715 goto out; 716 } 717 718 slot_data->rollback_indexes[rollback_index_location] = 719 vbmeta_header.rollback_index; 720 721 if (out_algorithm_type != NULL) { 722 *out_algorithm_type = (AvbAlgorithmType)vbmeta_header.algorithm_type; 723 } 724 725 out: 726 /* If |vbmeta_image_data| isn't NULL it means that it adopted 727 * |vbmeta_buf| so in that case don't free it here. 728 */ 729 if (vbmeta_image_data == NULL) { 730 if (vbmeta_buf != NULL) { 731 avb_free(vbmeta_buf); 732 } 733 } 734 if (descriptors != NULL) { 735 avb_free(descriptors); 736 } 737 return ret; 738 } 739 740 #define NUM_GUIDS 3 741 742 /* Substitutes all variables (e.g. $(ANDROID_SYSTEM_PARTUUID)) with 743 * values. Returns NULL on OOM, otherwise the cmdline with values 744 * replaced. 745 */ 746 static char* sub_cmdline(AvbOps* ops, 747 const char* cmdline, 748 const char* ab_suffix, 749 bool using_boot_for_vbmeta) { 750 const char* part_name_str[NUM_GUIDS] = {"system", "boot", "vbmeta"}; 751 const char* replace_str[NUM_GUIDS] = {"$(ANDROID_SYSTEM_PARTUUID)", 752 "$(ANDROID_BOOT_PARTUUID)", 753 "$(ANDROID_VBMETA_PARTUUID)"}; 754 char* ret = NULL; 755 AvbIOResult io_ret; 756 757 /* Special-case for when the top-level vbmeta struct is in the boot 758 * partition. 759 */ 760 if (using_boot_for_vbmeta) { 761 part_name_str[2] = "boot"; 762 } 763 764 /* Replace unique partition GUIDs */ 765 for (size_t n = 0; n < NUM_GUIDS; n++) { 766 char part_name[PART_NAME_MAX_SIZE]; 767 char guid_buf[37]; 768 769 if (!avb_str_concat(part_name, 770 sizeof part_name, 771 part_name_str[n], 772 avb_strlen(part_name_str[n]), 773 ab_suffix, 774 avb_strlen(ab_suffix))) { 775 avb_error("Partition name and suffix does not fit.\n"); 776 goto fail; 777 } 778 779 io_ret = ops->get_unique_guid_for_partition( 780 ops, part_name, guid_buf, sizeof guid_buf); 781 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 782 return NULL; 783 } else if (io_ret != AVB_IO_RESULT_OK) { 784 avb_error("Error getting unique GUID for partition.\n"); 785 goto fail; 786 } 787 788 if (ret == NULL) { 789 ret = avb_replace(cmdline, replace_str[n], guid_buf); 790 } else { 791 char* new_ret = avb_replace(ret, replace_str[n], guid_buf); 792 avb_free(ret); 793 ret = new_ret; 794 } 795 if (ret == NULL) { 796 goto fail; 797 } 798 } 799 800 return ret; 801 802 fail: 803 if (ret != NULL) { 804 avb_free(ret); 805 } 806 return NULL; 807 } 808 809 static int cmdline_append_option(AvbSlotVerifyData* slot_data, 810 const char* key, 811 const char* value) { 812 size_t offset, key_len, value_len; 813 char* new_cmdline; 814 815 key_len = avb_strlen(key); 816 value_len = avb_strlen(value); 817 818 offset = 0; 819 if (slot_data->cmdline != NULL) { 820 offset = avb_strlen(slot_data->cmdline); 821 if (offset > 0) { 822 offset += 1; 823 } 824 } 825 826 new_cmdline = avb_calloc(offset + key_len + value_len + 2); 827 if (new_cmdline == NULL) { 828 return 0; 829 } 830 if (offset > 0) { 831 avb_memcpy(new_cmdline, slot_data->cmdline, offset - 1); 832 new_cmdline[offset - 1] = ' '; 833 } 834 avb_memcpy(new_cmdline + offset, key, key_len); 835 new_cmdline[offset + key_len] = '='; 836 avb_memcpy(new_cmdline + offset + key_len + 1, value, value_len); 837 if (slot_data->cmdline != NULL) { 838 avb_free(slot_data->cmdline); 839 } 840 slot_data->cmdline = new_cmdline; 841 842 return 1; 843 } 844 845 #define AVB_MAX_DIGITS_UINT64 32 846 847 /* Writes |value| to |digits| in base 10 followed by a NUL byte. 848 * Returns number of characters written excluding the NUL byte. 849 */ 850 static size_t uint64_to_base10(uint64_t value, 851 char digits[AVB_MAX_DIGITS_UINT64]) { 852 char rev_digits[AVB_MAX_DIGITS_UINT64]; 853 size_t n, num_digits; 854 855 for (num_digits = 0; num_digits < AVB_MAX_DIGITS_UINT64 - 1;) { 856 rev_digits[num_digits++] = (value % 10) + '0'; 857 value /= 10; 858 if (value == 0) { 859 break; 860 } 861 } 862 863 for (n = 0; n < num_digits; n++) { 864 digits[n] = rev_digits[num_digits - 1 - n]; 865 } 866 digits[n] = '\0'; 867 return n; 868 } 869 870 static int cmdline_append_version(AvbSlotVerifyData* slot_data, 871 const char* key, 872 uint64_t major_version, 873 uint64_t minor_version) { 874 char major_digits[AVB_MAX_DIGITS_UINT64]; 875 char minor_digits[AVB_MAX_DIGITS_UINT64]; 876 char combined[AVB_MAX_DIGITS_UINT64 * 2 + 1]; 877 size_t num_major_digits, num_minor_digits; 878 879 num_major_digits = uint64_to_base10(major_version, major_digits); 880 num_minor_digits = uint64_to_base10(minor_version, minor_digits); 881 avb_memcpy(combined, major_digits, num_major_digits); 882 combined[num_major_digits] = '.'; 883 avb_memcpy(combined + num_major_digits + 1, minor_digits, num_minor_digits); 884 combined[num_major_digits + 1 + num_minor_digits] = '\0'; 885 886 return cmdline_append_option(slot_data, key, combined); 887 } 888 889 static int cmdline_append_uint64_base10(AvbSlotVerifyData* slot_data, 890 const char* key, 891 uint64_t value) { 892 char digits[AVB_MAX_DIGITS_UINT64]; 893 uint64_to_base10(value, digits); 894 return cmdline_append_option(slot_data, key, digits); 895 } 896 897 static int cmdline_append_hex(AvbSlotVerifyData* slot_data, 898 const char* key, 899 const uint8_t* data, 900 size_t data_len) { 901 char hex_digits[17] = "0123456789abcdef"; 902 char* hex_data; 903 int ret; 904 size_t n; 905 906 hex_data = avb_malloc(data_len * 2 + 1); 907 if (hex_data == NULL) { 908 return 0; 909 } 910 911 for (n = 0; n < data_len; n++) { 912 hex_data[n * 2] = hex_digits[data[n] >> 4]; 913 hex_data[n * 2 + 1] = hex_digits[data[n] & 0x0f]; 914 } 915 hex_data[n * 2] = '\0'; 916 917 ret = cmdline_append_option(slot_data, key, hex_data); 918 avb_free(hex_data); 919 return ret; 920 } 921 922 AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, 923 const char* const* requested_partitions, 924 const char* ab_suffix, 925 bool allow_verification_error, 926 AvbSlotVerifyData** out_data) { 927 AvbSlotVerifyResult ret; 928 AvbSlotVerifyData* slot_data = NULL; 929 AvbAlgorithmType algorithm_type = AVB_ALGORITHM_TYPE_NONE; 930 AvbIOResult io_ret; 931 bool using_boot_for_vbmeta = false; 932 933 if (out_data != NULL) { 934 *out_data = NULL; 935 } 936 937 slot_data = avb_calloc(sizeof(AvbSlotVerifyData)); 938 if (slot_data == NULL) { 939 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 940 goto fail; 941 } 942 slot_data->vbmeta_images = 943 avb_calloc(sizeof(AvbVBMetaData) * MAX_NUMBER_OF_VBMETA_IMAGES); 944 if (slot_data->vbmeta_images == NULL) { 945 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 946 goto fail; 947 } 948 slot_data->loaded_partitions = 949 avb_calloc(sizeof(AvbPartitionData) * MAX_NUMBER_OF_LOADED_PARTITIONS); 950 if (slot_data->loaded_partitions == NULL) { 951 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 952 goto fail; 953 } 954 955 ret = load_and_verify_vbmeta(ops, 956 requested_partitions, 957 ab_suffix, 958 allow_verification_error, 959 0 /* toplevel_vbmeta_flags */, 960 0 /* rollback_index_location */, 961 "vbmeta", 962 avb_strlen("vbmeta"), 963 NULL /* expected_public_key */, 964 0 /* expected_public_key_length */, 965 slot_data, 966 &algorithm_type); 967 if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) { 968 goto fail; 969 } 970 971 if (avb_strcmp(slot_data->vbmeta_images[0].partition_name, "vbmeta") != 0) { 972 avb_assert(avb_strcmp(slot_data->vbmeta_images[0].partition_name, "boot") == 973 0); 974 using_boot_for_vbmeta = true; 975 } 976 977 /* If things check out, mangle the kernel command-line as needed. */ 978 if (result_should_continue(ret)) { 979 /* Fill in |ab_suffix| field. */ 980 slot_data->ab_suffix = avb_strdup(ab_suffix); 981 if (slot_data->ab_suffix == NULL) { 982 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 983 goto fail; 984 } 985 986 /* Add androidboot.vbmeta.device option. */ 987 if (!cmdline_append_option(slot_data, 988 "androidboot.vbmeta.device", 989 "PARTUUID=$(ANDROID_VBMETA_PARTUUID)")) { 990 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 991 goto fail; 992 } 993 994 /* Add androidboot.vbmeta.avb_version option. */ 995 if (!cmdline_append_version(slot_data, 996 "androidboot.vbmeta.avb_version", 997 AVB_VERSION_MAJOR, 998 AVB_VERSION_MINOR)) { 999 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1000 goto fail; 1001 } 1002 1003 /* Substitute $(ANDROID_SYSTEM_PARTUUID) and friends. */ 1004 if (slot_data->cmdline != NULL) { 1005 char* new_cmdline; 1006 new_cmdline = sub_cmdline( 1007 ops, slot_data->cmdline, ab_suffix, using_boot_for_vbmeta); 1008 if (new_cmdline == NULL) { 1009 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1010 goto fail; 1011 } 1012 avb_free(slot_data->cmdline); 1013 slot_data->cmdline = new_cmdline; 1014 } 1015 1016 /* Set androidboot.avb.device_state to "locked" or "unlocked". */ 1017 bool is_device_unlocked; 1018 io_ret = ops->read_is_device_unlocked(ops, &is_device_unlocked); 1019 if (io_ret == AVB_IO_RESULT_ERROR_OOM) { 1020 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1021 goto fail; 1022 } else if (io_ret != AVB_IO_RESULT_OK) { 1023 avb_error("Error getting device state.\n"); 1024 ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; 1025 goto fail; 1026 } 1027 if (!cmdline_append_option(slot_data, 1028 "androidboot.vbmeta.device_state", 1029 is_device_unlocked ? "unlocked" : "locked")) { 1030 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1031 goto fail; 1032 } 1033 1034 /* Set androidboot.vbmeta.{hash_alg, size, digest} - use same hash 1035 * function as is used to sign vbmeta. 1036 */ 1037 switch (algorithm_type) { 1038 /* Explicit fallthrough. */ 1039 case AVB_ALGORITHM_TYPE_NONE: 1040 case AVB_ALGORITHM_TYPE_SHA256_RSA2048: 1041 case AVB_ALGORITHM_TYPE_SHA256_RSA4096: 1042 case AVB_ALGORITHM_TYPE_SHA256_RSA8192: { 1043 AvbSHA256Ctx ctx; 1044 size_t n, total_size = 0; 1045 avb_sha256_init(&ctx); 1046 for (n = 0; n < slot_data->num_vbmeta_images; n++) { 1047 avb_sha256_update(&ctx, 1048 slot_data->vbmeta_images[n].vbmeta_data, 1049 slot_data->vbmeta_images[n].vbmeta_size); 1050 total_size += slot_data->vbmeta_images[n].vbmeta_size; 1051 } 1052 if (!cmdline_append_option( 1053 slot_data, "androidboot.vbmeta.hash_alg", "sha256") || 1054 !cmdline_append_uint64_base10( 1055 slot_data, "androidboot.vbmeta.size", total_size) || 1056 !cmdline_append_hex(slot_data, 1057 "androidboot.vbmeta.digest", 1058 avb_sha256_final(&ctx), 1059 AVB_SHA256_DIGEST_SIZE)) { 1060 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1061 goto fail; 1062 } 1063 } break; 1064 /* Explicit fallthrough. */ 1065 case AVB_ALGORITHM_TYPE_SHA512_RSA2048: 1066 case AVB_ALGORITHM_TYPE_SHA512_RSA4096: 1067 case AVB_ALGORITHM_TYPE_SHA512_RSA8192: { 1068 AvbSHA512Ctx ctx; 1069 size_t n, total_size = 0; 1070 avb_sha512_init(&ctx); 1071 for (n = 0; n < slot_data->num_vbmeta_images; n++) { 1072 avb_sha512_update(&ctx, 1073 slot_data->vbmeta_images[n].vbmeta_data, 1074 slot_data->vbmeta_images[n].vbmeta_size); 1075 total_size += slot_data->vbmeta_images[n].vbmeta_size; 1076 } 1077 if (!cmdline_append_option( 1078 slot_data, "androidboot.vbmeta.hash_alg", "sha512") || 1079 !cmdline_append_uint64_base10( 1080 slot_data, "androidboot.vbmeta.size", total_size) || 1081 !cmdline_append_hex(slot_data, 1082 "androidboot.vbmeta.digest", 1083 avb_sha512_final(&ctx), 1084 AVB_SHA512_DIGEST_SIZE)) { 1085 ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; 1086 goto fail; 1087 } 1088 } break; 1089 case _AVB_ALGORITHM_NUM_TYPES: 1090 avb_assert_not_reached(); 1091 break; 1092 } 1093 1094 if (out_data != NULL) { 1095 *out_data = slot_data; 1096 } else { 1097 avb_slot_verify_data_free(slot_data); 1098 } 1099 } 1100 1101 if (!allow_verification_error) { 1102 avb_assert(ret == AVB_SLOT_VERIFY_RESULT_OK); 1103 } 1104 1105 return ret; 1106 1107 fail: 1108 if (slot_data != NULL) { 1109 avb_slot_verify_data_free(slot_data); 1110 } 1111 return ret; 1112 } 1113 1114 void avb_slot_verify_data_free(AvbSlotVerifyData* data) { 1115 if (data->ab_suffix != NULL) { 1116 avb_free(data->ab_suffix); 1117 } 1118 if (data->cmdline != NULL) { 1119 avb_free(data->cmdline); 1120 } 1121 if (data->vbmeta_images != NULL) { 1122 size_t n; 1123 for (n = 0; n < data->num_vbmeta_images; n++) { 1124 AvbVBMetaData* vbmeta_image = &data->vbmeta_images[n]; 1125 if (vbmeta_image->partition_name != NULL) { 1126 avb_free(vbmeta_image->partition_name); 1127 } 1128 if (vbmeta_image->vbmeta_data != NULL) { 1129 avb_free(vbmeta_image->vbmeta_data); 1130 } 1131 } 1132 avb_free(data->vbmeta_images); 1133 } 1134 if (data->loaded_partitions != NULL) { 1135 size_t n; 1136 for (n = 0; n < data->num_loaded_partitions; n++) { 1137 AvbPartitionData* loaded_partition = &data->loaded_partitions[n]; 1138 if (loaded_partition->partition_name != NULL) { 1139 avb_free(loaded_partition->partition_name); 1140 } 1141 if (loaded_partition->data != NULL) { 1142 avb_free(loaded_partition->data); 1143 } 1144 } 1145 avb_free(data->loaded_partitions); 1146 } 1147 avb_free(data); 1148 } 1149 1150 const char* avb_slot_verify_result_to_string(AvbSlotVerifyResult result) { 1151 const char* ret = NULL; 1152 1153 switch (result) { 1154 case AVB_SLOT_VERIFY_RESULT_OK: 1155 ret = "OK"; 1156 break; 1157 case AVB_SLOT_VERIFY_RESULT_ERROR_OOM: 1158 ret = "ERROR_OOM"; 1159 break; 1160 case AVB_SLOT_VERIFY_RESULT_ERROR_IO: 1161 ret = "ERROR_IO"; 1162 break; 1163 case AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION: 1164 ret = "ERROR_VERIFICATION"; 1165 break; 1166 case AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX: 1167 ret = "ERROR_ROLLBACK_INDEX"; 1168 break; 1169 case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED: 1170 ret = "ERROR_PUBLIC_KEY_REJECTED"; 1171 break; 1172 case AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA: 1173 ret = "ERROR_INVALID_METADATA"; 1174 break; 1175 case AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION: 1176 ret = "ERROR_UNSUPPORTED_VERSION"; 1177 break; 1178 /* Do not add a 'default:' case here because of -Wswitch. */ 1179 } 1180 1181 if (ret == NULL) { 1182 avb_error("Unknown AvbSlotVerifyResult value.\n"); 1183 ret = "(unknown)"; 1184 } 1185 1186 return ret; 1187 } 1188