/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "format/binary/BinaryResourceParser.h" #include #include #include #include "android-base/logging.h" #include "android-base/macros.h" #include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" #include "androidfw/TypeWrappers.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "Source.h" #include "ValueVisitor.h" #include "format/binary/ResChunkPullParser.h" #include "util/Util.h" using namespace android; using ::android::base::StringPrintf; namespace aapt { namespace { static std::u16string strcpy16_dtoh(const char16_t* src, size_t len) { size_t utf16_len = strnlen16(src, len); if (utf16_len == 0) { return {}; } std::u16string dst; dst.resize(utf16_len); for (size_t i = 0; i < utf16_len; i++) { dst[i] = util::DeviceToHost16(src[i]); } return dst; } // Visitor that converts a reference's resource ID to a resource name, given a mapping from // resource ID to resource name. class ReferenceIdToNameVisitor : public DescendingValueVisitor { public: using DescendingValueVisitor::Visit; explicit ReferenceIdToNameVisitor(const std::map* mapping) : mapping_(mapping) { CHECK(mapping_ != nullptr); } void Visit(Reference* reference) override { if (!reference->id || !reference->id.value().is_valid()) { return; } ResourceId id = reference->id.value(); auto cache_iter = mapping_->find(id); if (cache_iter != mapping_->end()) { reference->name = cache_iter->second; } } private: DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor); const std::map* mapping_; }; } // namespace BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, const void* data, size_t len, io::IFileCollection* files) : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) { } bool BinaryResourceParser::Parse() { ResChunkPullParser parser(data_, data_len_); if (!ResChunkPullParser::IsGoodEvent(parser.Next())) { diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error()); return false; } if (parser.chunk()->type != android::RES_TABLE_TYPE) { diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", static_cast(parser.chunk()->type))); return false; } if (!ParseTable(parser.chunk())) { return false; } if (parser.Next() != ResChunkPullParser::Event::kEndDocument) { if (parser.event() == ResChunkPullParser::Event::kBadDocument) { diag_->Warn(DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error()); } else { diag_->Warn(DiagMessage(source_) << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", static_cast(parser.chunk()->type))); } } return true; } // Parses the resource table, which contains all the packages, types, and entries. bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { const ResTable_header* table_header = ConvertTo(chunk); if (!table_header) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk"); return false; } ResChunkPullParser parser(GetChunkData(&table_header->header), GetChunkDataLen(&table_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (value_pool_.getError() == NO_INIT) { status_t err = value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { diag_->Error(DiagMessage(source_) << "corrupt string pool in ResTable: " << value_pool_.getError()); return false; } // Reserve some space for the strings we are going to add. table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount()); } else { diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable"); } break; case android::RES_TABLE_PACKAGE_TYPE: if (!ParsePackage(parser.chunk())) { return false; } break; default: diag_->Warn(DiagMessage(source_) << "unexpected chunk type " << static_cast(util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } return true; } bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { constexpr size_t kMinPackageSize = sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset); const ResTable_package* package_header = ConvertTo(chunk); if (!package_header) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); return false; } uint32_t package_id = util::DeviceToHost32(package_header->id); if (package_id > std::numeric_limits::max()) { diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")"); return false; } // Extract the package name. std::u16string package_name = strcpy16_dtoh((const char16_t*)package_header->name, arraysize(package_header->name)); ResourceTablePackage* package = table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast(package_id)); if (!package) { diag_->Error(DiagMessage(source_) << "incompatible package '" << package_name << "' with ID " << package_id); return false; } // There can be multiple packages in a table, so // clear the type and key pool in case they were set from a previous package. type_pool_.uninit(); key_pool_.uninit(); ResChunkPullParser parser(GetChunkData(&package_header->header), GetChunkDataLen(&package_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (type_pool_.getError() == NO_INIT) { status_t err = type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { diag_->Error(DiagMessage(source_) << "corrupt type string pool in " << "ResTable_package: " << type_pool_.getError()); return false; } } else if (key_pool_.getError() == NO_INIT) { status_t err = key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { diag_->Error(DiagMessage(source_) << "corrupt key string pool in " << "ResTable_package: " << key_pool_.getError()); return false; } } else { diag_->Warn(DiagMessage(source_) << "unexpected string pool"); } break; case android::RES_TABLE_TYPE_SPEC_TYPE: if (!ParseTypeSpec(package, parser.chunk())) { return false; } break; case android::RES_TABLE_TYPE_TYPE: if (!ParseType(package, parser.chunk())) { return false; } break; case android::RES_TABLE_LIBRARY_TYPE: if (!ParseLibrary(parser.chunk())) { return false; } break; case android::RES_TABLE_OVERLAYABLE_TYPE: if (!ParseOverlayable(parser.chunk())) { return false; } break; default: diag_->Warn(DiagMessage(source_) << "unexpected chunk type " << static_cast(util::DeviceToHost16(parser.chunk()->type))); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); return false; } // Now go through the table and change local resource ID references to // symbolic references. ReferenceIdToNameVisitor visitor(&id_index_); VisitAllValuesInTable(table_, &visitor); return true; } bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; } const ResTable_typeSpec* type_spec = ConvertTo(chunk); if (!type_spec) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); return false; } if (type_spec->id == 0) { diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id); return false; } // The data portion of this chunk contains entry_count 32bit entries, // each one representing a set of flags. const size_t entry_count = dtohl(type_spec->entryCount); // There can only be 2^16 entries in a type, because that is the ID // space for entries (EEEE) in the resource ID 0xPPTTEEEE. if (entry_count > std::numeric_limits::max()) { diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has too many entries (" << entry_count << ")"); return false; } const size_t data_size = util::DeviceToHost32(type_spec->header.size) - util::DeviceToHost16(type_spec->header.headerSize); if (entry_count * sizeof(uint32_t) > data_size) { diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries."); return false; } // Record the type_spec_flags for later. We don't know resource names yet, and we need those // to mark resources as overlayable. const uint32_t* type_spec_flags = reinterpret_cast( reinterpret_cast(type_spec) + util::DeviceToHost16(type_spec->header.headerSize)); for (size_t i = 0; i < entry_count; i++) { ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast(i)); entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]); } return true; } bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; } if (key_pool_.getError() != NO_ERROR) { diag_->Error(DiagMessage(source_) << "missing key string pool"); return false; } // Specify a manual size, because ResTable_type contains ResTable_config, which changes // a lot and has its own code to handle variable size. const ResTable_type* type = ConvertTo(chunk); if (!type) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk"); return false; } if (type->id == 0) { diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id); return false; } ConfigDescription config; config.copyFromDtoH(type->config); const std::string type_str = util::GetString(type_pool_, type->id - 1); // Be lenient on the name of the type if the table is lenient on resource validation. auto parsed_type = ResourceType::kUnknown; if (const ResourceType* parsed = ParseResourceType(type_str)) { parsed_type = *parsed; } else if (table_->GetValidateResources()) { diag_->Error(DiagMessage(source_) << "invalid type name '" << type_str << "' for type with ID " << (int) type->id); return false; } TypeVariant tv(type); for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { const ResTable_entry* entry = *it; if (!entry) { continue; } const ResourceName name(package->name, parsed_type, util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); const ResourceId res_id(package->id.value(), type->id, static_cast(it.index())); std::unique_ptr resource_value; if (entry->flags & ResTable_entry::FLAG_COMPLEX) { const ResTable_map_entry* mapEntry = static_cast(entry); // TODO(adamlesinski): Check that the entry count is valid. resource_value = ParseMapEntry(name, config, mapEntry); } else { const Res_value* value = (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); resource_value = ParseValue(name, config, *value); } if (!resource_value) { diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " (" << res_id << ") with configuration '" << config << "'"); return false; } if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value), diag_)) { return false; } if (entry->flags & ResTable_entry::FLAG_PUBLIC) { Visibility visibility; visibility.level = Visibility::Level::kPublic; if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) { return false; } // Erase the ID from the map once processed, so that we don't mark the same symbol more than // once. entry_type_spec_flags_.erase(res_id); } // Add this resource name->id mapping to the index so // that we can resolve all ID references to name references. auto cache_iter = id_index_.find(res_id); if (cache_iter == id_index_.end()) { id_index_.insert({res_id, name}); } } return true; } bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { DynamicRefTable dynamic_ref_table; if (dynamic_ref_table.load(reinterpret_cast(chunk)) != NO_ERROR) { return false; } const KeyedVector& entries = dynamic_ref_table.entries(); const size_t count = entries.size(); for (size_t i = 0; i < count; i++) { table_->included_packages_[entries.valueAt(i)] = util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string())); } return true; } bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_header* header = ConvertTo(chunk); if (!header) { diag_->Error(DiagMessage(source_) << "corrupt ResTable_category_header chunk"); return false; } auto overlayable = std::make_shared(); overlayable->name = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->name, arraysize(header->name))); overlayable->actor = util::Utf16ToUtf8(strcpy16_dtoh((const char16_t*)header->actor, arraysize(header->name))); ResChunkPullParser parser(GetChunkData(chunk), GetChunkDataLen(chunk)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { if (util::DeviceToHost16(parser.chunk()->type) == android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) { const ResTable_overlayable_policy_header* policy_header = ConvertTo(parser.chunk()); OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone; if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { policies |= OverlayableItem::Policy::kPublic; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { policies |= OverlayableItem::Policy::kSystem; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { policies |= OverlayableItem::Policy::kVendor; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { policies |= OverlayableItem::Policy::kProduct; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_SIGNATURE) { policies |= OverlayableItem::Policy::kSignature; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_ODM_PARTITION) { policies |= OverlayableItem::Policy::kOdm; } if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_OEM_PARTITION) { policies |= OverlayableItem::Policy::kOem; } const ResTable_ref* const ref_begin = reinterpret_cast( ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize)); const ResTable_ref* const ref_end = ref_begin + util::DeviceToHost32(policy_header->entry_count); for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { ResourceId res_id(util::DeviceToHost32(ref_iter->ident)); const auto iter = id_index_.find(res_id); // If the overlayable chunk comes before the type chunks, the resource ids and resource name // pairing will not exist at this point. if (iter == id_index_.cend()) { diag_->Error(DiagMessage(source_) << "failed to find resource name for overlayable" << " resource " << res_id); return false; } OverlayableItem overlayable_item(overlayable); overlayable_item.policies = policies; if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { return false; } } } } return true; } std::unique_ptr BinaryResourceParser::ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value) { std::unique_ptr item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_, value, &table_->string_pool); if (files_ != nullptr) { FileReference* file_ref = ValueCast(item.get()); if (file_ref != nullptr) { file_ref->file = files_->FindFile(*file_ref->path); if (file_ref->file == nullptr) { diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config << "' is a file reference to '" << *file_ref->path << "' but no such path exists"); } } } return item; } std::unique_ptr BinaryResourceParser::ParseMapEntry(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { switch (name.type) { case ResourceType::kStyle: return ParseStyle(name, config, map); case ResourceType::kAttrPrivate: // fallthrough case ResourceType::kAttr: return ParseAttr(name, config, map); case ResourceType::kArray: return ParseArray(name, config, map); case ResourceType::kPlurals: return ParsePlural(name, config, map); case ResourceType::kId: // Special case: An ID is not a bag, but some apps have defined the auto-generated // IDs that come from declaring an enum value in an attribute as an empty map... // We can ignore the value here. return util::make_unique(); default: diag_->Error(DiagMessage() << "illegal map type '" << to_string(name.type) << "' (" << (int)name.type << ")"); break; } return {}; } std::unique_ptr