/* reads the squashfs superblock and returns the size of the file system in `offset' */ static int get_squashfs_size(fec_handle *f, uint64_t *offset) { check(f); check(offset); size_t sb_size = squashfs_get_sb_size(); check(sb_size <= SSIZE_MAX); uint8_t buffer[sb_size]; if (fec_pread(f, buffer, sizeof(buffer), 0) != (ssize_t)sb_size) { error("failed to read superblock: %s", strerror(errno)); return -1; } squashfs_info sq; if (squashfs_parse_sb_buffer(buffer, &sq) < 0) { error("failed to parse superblock: %s", strerror(errno)); return -1; } *offset = sq.bytes_used_4K_padded; return 0; }
/* reads up to `count' bytes starting from the internal file position using error correction and integrity validation, if available */ ssize_t fec_read(struct fec_handle *f, void *buf, size_t count) { ssize_t rc = fec_pread(f, buf, count, f->pos); if (rc > 0) { check(f->pos < UINT64_MAX - rc); f->pos += rc; } return rc; }
/* reads a verity hash and the corresponding data block using error correction, if available */ static bool ecc_read_hashes(fec_handle *f, uint64_t hash_offset, uint8_t *hash, uint64_t data_offset, uint8_t *data) { check(f); if (hash && fec_pread(f, hash, SHA256_DIGEST_LENGTH, hash_offset) != SHA256_DIGEST_LENGTH) { error("failed to read hash tree: offset %" PRIu64 ": %s", hash_offset, strerror(errno)); return false; } check(data); if (fec_pread(f, data, FEC_BLOCKSIZE, data_offset) != FEC_BLOCKSIZE) { error("failed to read hash tree: data_offset %" PRIu64 ": %s", data_offset, strerror(errno)); return false; } return true; }
/* reads the ext4 superblock and returns the size of the file system in `offset' */ static int get_ext4_size(fec_handle *f, uint64_t *offset) { check(f); check(f->size > 1024 + sizeof(ext4_super_block)); check(offset); ext4_super_block sb; if (fec_pread(f, &sb, sizeof(sb), 1024) != sizeof(sb)) { error("failed to read superblock: %s", strerror(errno)); return -1; } fs_info info; info.len = 0; /* only len is set to 0 to ask the device for real size. */ if (ext4_parse_sb(&sb, &info) != 0) { errno = EINVAL; return -1; } *offset = info.len; return 0; }
/* attempts to read verity metadata from `f->fd' position `offset'; if in r/w mode, rewrites the metadata if it had errors */ int verity_parse_header(fec_handle *f, uint64_t offset) { check(f); check(f->data_size > VERITY_METADATA_SIZE); if (offset > f->data_size - VERITY_METADATA_SIZE) { debug("failed to read verity header: offset %" PRIu64 " is too far", offset); return -1; } verity_info *v = &f->verity; uint64_t errors = f->errors; if (!raw_pread(f, &v->header, sizeof(v->header), offset)) { error("failed to read verity header: %s", strerror(errno)); return -1; } /* use raw data to check for the alternative magic, because it will be error corrected to VERITY_MAGIC otherwise */ if (v->header.magic == VERITY_MAGIC_DISABLE) { /* this value is not used by us, but can be used by a caller to decide whether dm-verity should be enabled */ v->disabled = true; } if (fec_pread(f, &v->ecc_header, sizeof(v->ecc_header), offset) != sizeof(v->ecc_header)) { warn("failed to read verity header: %s", strerror(errno)); return -1; } if (validate_header(f, &v->header, offset)) { /* raw verity header is invalid; this could be due to corruption, or due to missing verity metadata */ if (validate_header(f, &v->ecc_header, offset)) { return -1; /* either way, we cannot recover */ } /* report mismatching fields */ if (!v->disabled && v->header.magic != v->ecc_header.magic) { warn("corrected verity header magic"); v->header.magic = v->ecc_header.magic; } if (v->header.version != v->ecc_header.version) { warn("corrected verity header version"); v->header.version = v->ecc_header.version; } if (v->header.length != v->ecc_header.length) { warn("corrected verity header length"); v->header.length = v->ecc_header.length; } if (memcmp(v->header.signature, v->ecc_header.signature, sizeof(v->header.signature))) { warn("corrected verity header signature"); /* we have no way of knowing which signature is correct, if either of them is */ } } v->metadata_start = offset; if (parse_table(f, offset + sizeof(v->header), v->header.length, false) == -1 && parse_table(f, offset + sizeof(v->header), v->header.length, true) == -1) { return -1; } /* if we corrected something while parsing metadata and we are in r/w mode, rewrite the corrected metadata */ if (f->mode & O_RDWR && f->errors > errors && rewrite_metadata(f, offset) < 0) { warn("failed to rewrite verity metadata: %s", strerror(errno)); } if (v->metadata_start < v->hash_start) { f->data_size = v->metadata_start; } else { f->data_size = v->hash_start; } return 0; }
/* reads, corrects and parses the verity table, validates parameters, and if `f->flags' does not have `FEC_VERITY_DISABLE' set, calls `verify_tree' to load and validate the hash tree */ static int parse_table(fec_handle *f, uint64_t offset, uint32_t size, bool useecc) { check(f); check(size >= VERITY_MIN_TABLE_SIZE); check(size <= VERITY_MAX_TABLE_SIZE); debug("offset = %" PRIu64 ", size = %u", offset, size); verity_info *v = &f->verity; std::unique_ptr<char[]> table(new (std::nothrow) char[size + 1]); if (!table) { errno = ENOMEM; return -1; } if (!useecc) { if (!raw_pread(f, table.get(), size, offset)) { error("failed to read verity table: %s", strerror(errno)); return -1; } } else if (fec_pread(f, table.get(), size, offset) != (ssize_t)size) { error("failed to ecc read verity table: %s", strerror(errno)); return -1; } table[size] = '\0'; debug("verity table: '%s'", table.get()); int i = 0; std::unique_ptr<uint8_t[]> salt; uint8_t root[SHA256_DIGEST_LENGTH]; auto tokens = android::base::Split(table.get(), " "); for (const auto& token : tokens) { switch (i++) { case 0: /* version */ if (token != stringify(VERITY_TABLE_VERSION)) { error("unsupported verity table version: %s", token.c_str()); return -1; } break; case 3: /* data_block_size */ case 4: /* hash_block_size */ /* assume 4 KiB block sizes for everything */ if (token != stringify(FEC_BLOCKSIZE)) { error("unsupported verity block size: %s", token.c_str()); return -1; } break; case 5: /* num_data_blocks */ if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE, &v->data_blocks) == -1) { error("invalid number of verity data blocks: %s", token.c_str()); return -1; } break; case 6: /* hash_start_block */ if (parse_uint64(token.c_str(), f->data_size / FEC_BLOCKSIZE, &v->hash_start) == -1) { error("invalid verity hash start block: %s", token.c_str()); return -1; } v->hash_start *= FEC_BLOCKSIZE; break; case 7: /* algorithm */ if (token != "sha256") { error("unsupported verity hash algorithm: %s", token.c_str()); return -1; } break; case 8: /* digest */ if (parse_hex(root, sizeof(root), token.c_str()) == -1) { error("invalid verity root hash: %s", token.c_str()); return -1; } break; case 9: /* salt */ v->salt_size = token.size(); check(v->salt_size % 2 == 0); v->salt_size /= 2; salt.reset(new (std::nothrow) uint8_t[v->salt_size]); if (!salt) { errno = ENOMEM; return -1; } if (parse_hex(salt.get(), v->salt_size, token.c_str()) == -1) { error("invalid verity salt: %s", token.c_str()); return -1; } break; default: break; } } if (i < VERITY_TABLE_ARGS) { error("not enough arguments in verity table: %d; expected at least " stringify(VERITY_TABLE_ARGS), i); return -1; } check(v->hash_start < f->data_size); if (v->metadata_start < v->hash_start) { check(v->data_blocks == v->metadata_start / FEC_BLOCKSIZE); } else { check(v->data_blocks == v->hash_start / FEC_BLOCKSIZE); } if (v->salt) { delete[] v->salt; v->salt = NULL; } v->salt = salt.release(); if (v->table) { delete[] v->table; v->table = NULL; } v->table = table.release(); if (!(f->flags & FEC_VERITY_DISABLE)) { if (verify_tree(f, root) == -1) { return -1; } check(v->hash); uint8_t zero_block[FEC_BLOCKSIZE]; memset(zero_block, 0, FEC_BLOCKSIZE); if (verity_hash(f, zero_block, v->zero_hash) == -1) { error("failed to hash"); return -1; } } return 0; }