diff options
author | ckitagawa <ckitagawa@chromium.org> | 2021-09-03 15:48:28 +0000 |
---|---|---|
committer | Copybara-Service <copybara-worker@google.com> | 2021-09-03 08:59:53 -0700 |
commit | 26518ffbdf042c4c35617be2446dac49590a89c2 (patch) | |
tree | 1fc7aa788fd12474c4df6bd18b47461cd93e6023 /disassembler_dex.cc | |
parent | f137bf4b5542b966abc4c08762c5e60b21913f4d (diff) | |
download | zucchini-26518ffbdf042c4c35617be2446dac49590a89c2.tar.gz |
[Zucchini] DEX Version 38 Support
DEX Version 38 added:
* CallSiteId & CallSite items
* MethodHandle items
* invoke-polymorphic containing meth@BBBB and proto@HHHH references
* invoke-custom containing a call_site@BBBB reference
This CL:
* Adds CallSiteIdToCallSite
* Adds MethodHandleTo{MethodId, FieldId}
* Adds CodeToProtoId16 for invoke-polymorphic
* Adds CodeToCallSiteId16 and WriteCallSiteId16 for invoke-custom
* Updates CodeToMethodId16 for invoke-polymorphic
Fuzzed about 1 million iterations locally and uploaded new samples to
the clusterfuzz bucket. 97% coverage.
Manually tested on hand-written dex files using smali as well as the
dexdump test corpus.
Bug: 1231885
Change-Id: Icd885be2cfd433d0befe689d16c4a1e99573ca6c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3060745
Reviewed-by: Samuel Huang <huangs@chromium.org>
Reviewed-by: Etienne Pierre-Doray <etiennep@chromium.org>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#918119}
NOKEYCHECK=True
GitOrigin-RevId: 9cc600ef0b60ff1ec76683a2bfb98a6bdbb05d1e
Diffstat (limited to 'disassembler_dex.cc')
-rw-r--r-- | disassembler_dex.cc | 244 |
1 files changed, 214 insertions, 30 deletions
diff --git a/disassembler_dex.cc b/disassembler_dex.cc index 5b25c50..74c5e69 100644 --- a/disassembler_dex.cc +++ b/disassembler_dex.cc @@ -69,6 +69,10 @@ size_t GetItemBaseSize(uint16_t type_item_code) { return sizeof(dex::MethodIdItem); case dex::kTypeClassDefItem: return sizeof(dex::ClassDefItem); + case dex::kTypeCallSiteIdItem: + return sizeof(dex::CallSiteIdItem); + case dex::kTypeMethodHandleItem: + return sizeof(dex::MethodHandleItem); // No need to handle dex::kTypeMapList. case dex::kTypeTypeList: return sizeof(uint32_t); // Variable-length. @@ -416,17 +420,23 @@ class ItemReferenceReader : public ReferenceReader { // |item_size| is the size of a fixed-size item. |rel_location| is the // relative location of MVI from the start of the item containing it. + // |rel_item_offset| is the offset to use relative to |item_offset| in cases + // where a value other than |rel_location| is required. For an example of this + // see ReadMethodHandleFieldOrMethodId. ItemReferenceReader(offset_t lo, offset_t hi, const dex::MapItem& map_item, size_t item_size, size_t rel_location, - Mapper&& mapper) + Mapper&& mapper, + bool mapper_wants_item = false) : hi_(hi), item_base_offset_(base::checked_cast<offset_t>(map_item.offset)), num_items_(base::checked_cast<uint32_t>(map_item.size)), item_size_(base::checked_cast<uint32_t>(item_size)), rel_location_(base::checked_cast<uint32_t>(rel_location)), + mapper_input_delta_( + mapper_wants_item ? 0 : base::checked_cast<uint32_t>(rel_location)), mapper_(std::move(mapper)) { static_assert(sizeof(decltype(map_item.offset)) <= sizeof(offset_t), "map_item.offset too large."); @@ -457,7 +467,11 @@ class ItemReferenceReader : public ReferenceReader { // |reference_width| is unneeded. if (location >= hi_) break; - const offset_t target = mapper_.Run(location); + + // |location == item_offset + mapper_input_delta_| in the majority of + // cases. The exception is when |mapper_| wants an item aligned location + // instead e.g. ReadMethodHandleFieldOrMethodId. + const offset_t target = mapper_.Run(item_offset + mapper_input_delta_); // kDexSentinelOffset (0) may appear for the following: // - ProtoIdItem: parameters_off. @@ -467,6 +481,9 @@ class ItemReferenceReader : public ReferenceReader { // - AnnotationSetRefItem: annotations_off. // kDexSentinelIndexAsOffset (0xFFFFFFFF) may appear for the following: // - ClassDefItem: superclass_idx, source_file_idx. + // - MethodHandleItem: |mapper_| uses ReadMethodHandleFieldOrMethodId and + // determines the item at |cur_idx_| is not of the required reference + // type. if (target == kDexSentinelOffset || target == kDexSentinelIndexAsOffset) { ++cur_idx_; continue; @@ -492,6 +509,7 @@ class ItemReferenceReader : public ReferenceReader { const uint32_t num_items_; const uint32_t item_size_; const uint32_t rel_location_; + const uint32_t mapper_input_delta_; const Mapper mapper_; offset_t cur_idx_ = 0; }; @@ -688,6 +706,53 @@ static offset_t ReadTargetIndex(ConstBufferView image, base::checked_cast<offset_t>(unsafe_idx * target_item_size); } +// Reads a field or method index of the MethodHandleItem located at |location| +// in |image| and translates |method_handle_item.field_or_method_id| to the +// offset of a fixed-size item specified by |target_map_item| and +// |target_item_size|. The index is deemed to be of the correct target type if +// |method_handle_item.method_handle_type| falls within the range [|min_type|, +// |max_type|]. If the target type is correct ReadTargetIndex is called. +// Returns the target offset if valid, or kDexSentinelIndexAsOffset if +// |method_handle_item.method_handle_type| is of the wrong type, or +// kInvalidOffset otherwise. +// +// As of DEX version 39 MethodHandleType values for FieldId and MethodId each +// form one consecutive block of values. If this changes, then the interface to +// this function will need to be redesigned. +static offset_t ReadMethodHandleFieldOrMethodId( + ConstBufferView image, + const dex::MapItem& target_map_item, + size_t target_item_size, + dex::MethodHandleType min_type, + dex::MethodHandleType max_type, + offset_t location) { + dex::MethodHandleItem method_handle_item = + image.read<dex::MethodHandleItem>(location); + + // Cannot use base::checked_cast as dex::MethodHandleType is an enum class so + // static_assert on the size instead. + static_assert(sizeof(decltype(dex::MethodHandleItem::method_handle_type)) <= + sizeof(dex::MethodHandleType), + "dex::MethodHandleItem::method_handle_type may not fit into " + "dex::MethodHandleType."); + dex::MethodHandleType method_handle_type = + static_cast<dex::MethodHandleType>(method_handle_item.method_handle_type); + + if (method_handle_type >= dex::MethodHandleType::kMaxMethodHandleType) { + return kInvalidOffset; + } + + // Use DexSentinelIndexAsOffset to skip the item as it isn't of the + // corresponding method handle type. + if (method_handle_type < min_type || method_handle_type > max_type) { + return kDexSentinelIndexAsOffset; + } + + return ReadTargetIndex<decltype(dex::MethodHandleItem::field_or_method_id)>( + image, target_map_item, target_item_size, + location + offsetof(dex::MethodHandleItem, field_or_method_id)); +} + // Reads uint32_t value in |image| at (valid) |location| and checks whether it // is a safe offset of a fixed-size item. Returns the target offset (possibly a // sentinel) if valid, or kInvalidOffset otherwise. This is compatible with @@ -779,9 +844,9 @@ bool ReadDexHeader(ConstBufferView image, ReadDexHeaderResults* opt_results) { dex_version = dex_version * 10 + (header->magic[i] - '0'); } - // Only support DEX versions 35 and 37. - // TODO(huangs): Handle version 38. - if (dex_version != 35 && dex_version != 37) + // Only support DEX versions 35, 37, and 38. + // TODO(ckitagawa): Handle version 39. + if (dex_version != 35 && dex_version != 37 && dex_version != 38) return false; if (header->file_size > image.size() || @@ -864,18 +929,27 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const { {{2, TypeTag(kCodeToTypeId), PoolTag(kTypeId)}, &DisassemblerDex::MakeReadCodeToTypeId16, &DisassemblerDex::MakeWriteTypeId16}, + {{2, TypeTag(kCodeToProtoId), PoolTag(kProtoId)}, + &DisassemblerDex::MakeReadCodeToProtoId16, + &DisassemblerDex::MakeWriteProtoId16}, {{2, TypeTag(kMethodIdToProtoId), PoolTag(kProtoId)}, &DisassemblerDex::MakeReadMethodIdToProtoId16, &DisassemblerDex::MakeWriteProtoId16}, {{2, TypeTag(kCodeToFieldId), PoolTag(kFieldId)}, &DisassemblerDex::MakeReadCodeToFieldId16, &DisassemblerDex::MakeWriteFieldId16}, + {{2, TypeTag(kMethodHandleToFieldId), PoolTag(kFieldId)}, + &DisassemblerDex::MakeReadMethodHandleToFieldId16, + &DisassemblerDex::MakeWriteFieldId16}, {{4, TypeTag(kAnnotationsDirectoryToFieldId), PoolTag(kFieldId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToFieldId32, &DisassemblerDex::MakeWriteFieldId32}, {{2, TypeTag(kCodeToMethodId), PoolTag(kMethodId)}, &DisassemblerDex::MakeReadCodeToMethodId16, &DisassemblerDex::MakeWriteMethodId16}, + {{2, TypeTag(kMethodHandleToMethodId), PoolTag(kMethodId)}, + &DisassemblerDex::MakeReadMethodHandleToMethodId16, + &DisassemblerDex::MakeWriteMethodId16}, {{4, TypeTag(kAnnotationsDirectoryToMethodId), PoolTag(kMethodId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToMethodId32, &DisassemblerDex::MakeWriteMethodId32}, @@ -883,6 +957,9 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const { PoolTag(kMethodId)}, &DisassemblerDex::MakeReadAnnotationsDirectoryToParameterMethodId32, &DisassemblerDex::MakeWriteMethodId32}, + {{2, TypeTag(kCodeToCallSiteId), PoolTag(kCallSiteId)}, + &DisassemblerDex::MakeReadCodeToCallSiteId16, + &DisassemblerDex::MakeWriteCallSiteId16}, {{4, TypeTag(kProtoIdToParametersTypeList), PoolTag(kTypeList)}, &DisassemblerDex::MakeReadProtoIdToParametersTypeList, &DisassemblerDex::MakeWriteAbs32}, @@ -936,6 +1013,9 @@ std::vector<ReferenceGroup> DisassemblerDex::MakeReferenceGroups() const { PoolTag(kAnnotationsDirectory)}, &DisassemblerDex::MakeReadClassDefToAnnotationDirectory, &DisassemblerDex::MakeWriteAbs32}, + {{4, TypeTag(kCallSiteIdToCallSite), PoolTag(kCallSite)}, + &DisassemblerDex::MakeReadCallSiteIdToCallSite32, + &DisassemblerDex::MakeWriteAbs32}, }; } @@ -1125,6 +1205,46 @@ DisassemblerDex::MakeReadClassDefToStaticValuesEncodedArray(offset_t lo, offsetof(dex::ClassDefItem, static_values_off), std::move(mapper)); } +std::unique_ptr<ReferenceReader> +DisassemblerDex::MakeReadCallSiteIdToCallSite32(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadTargetOffset32, image_); + return std::make_unique<ItemReferenceReader>( + lo, hi, call_site_map_item_, sizeof(dex::CallSiteIdItem), + offsetof(dex::CallSiteIdItem, call_site_off), std::move(mapper)); +} + +std::unique_ptr<ReferenceReader> +DisassemblerDex::MakeReadMethodHandleToFieldId16(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_, + field_map_item_, sizeof(dex::FieldIdItem), + dex::MethodHandleType::kStaticPut, + dex::MethodHandleType::kInstanceGet); + // Use |mapper_wants_item == true| for ItemReferenceReader such that + // |location| is aligned with MethodHandleItem when passed to |mapper|. This + // allows ReadMethodHandleFieldOrMethodId to safely determine whether the + // reference in the MethodHandleItem is of the correct type to be emitted. + return std::make_unique<ItemReferenceReader>( + lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem), + offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper), + /*mapper_wants_item=*/true); +} + +std::unique_ptr<ReferenceReader> +DisassemblerDex::MakeReadMethodHandleToMethodId16(offset_t lo, offset_t hi) { + auto mapper = base::BindRepeating(ReadMethodHandleFieldOrMethodId, image_, + method_map_item_, sizeof(dex::MethodIdItem), + dex::MethodHandleType::kInvokeStatic, + dex::MethodHandleType::kInvokeInterface); + // Use |mapper_wants_item == true| for ItemReferenceReader such that + // |location| is aligned with MethodHandleItem when passed to |mapper|. This + // allows ReadMethodHandleFieldOrMethodId to safely determine whether the + // reference in the MethodHandleItem is of the correct type to be emitted. + return std::make_unique<ItemReferenceReader>( + lo, hi, method_handle_map_item_, sizeof(dex::MethodHandleItem), + offsetof(dex::MethodHandleItem, field_or_method_id), std::move(mapper), + /*mapper_wants_item=*/true); +} + std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadTypeListToTypeId16( offset_t lo, offset_t hi) { @@ -1233,6 +1353,10 @@ DisassemblerDex::MakeReadAnnotationsDirectoryToParameterAnnotationSetRef( std::move(mapper)); } +// MakeReadCode* readers use offset relative to the instruction beginning based +// on the instruction format ID. +// See https://source.android.com/devices/tech/dalvik/instruction-formats + std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToStringId16( offset_t lo, offset_t hi) { @@ -1295,6 +1419,47 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToTypeId16( image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); } +std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToProtoId16( + offset_t lo, + offset_t hi) { + auto filter = base::BindRepeating( + [](const InstructionParser::Value& value) -> offset_t { + if (value.instr->format == dex::FormatId::c && + (value.instr->opcode == 0xFA || // invoke-polymorphic + value.instr->opcode == 0xFB)) { // invoke-polymorphic/range + // HHHH from e.g, invoke-polymorphic {vC, vD, vE, vF, vG}, + // meth@BBBB, proto@HHHH + return value.instr_offset + 6; + } + return kInvalidOffset; + }); + auto mapper = base::BindRepeating(ReadTargetIndex<uint16_t>, image_, + proto_map_item_, sizeof(dex::ProtoIdItem)); + return std::make_unique<InstructionReferenceReader>( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + +std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToCallSiteId16( + offset_t lo, + offset_t hi) { + auto filter = base::BindRepeating( + [](const InstructionParser::Value& value) -> offset_t { + if (value.instr->format == dex::FormatId::c && + (value.instr->opcode == 0xFC || // invoke-custom + value.instr->opcode == 0xFD)) { // invoke-custom/range + // BBBB from e.g, invoke-custom {vC, vD, vE, vF, vG}, + // call_site@BBBB + return value.instr_offset + 2; + } + return kInvalidOffset; + }); + auto mapper = + base::BindRepeating(ReadTargetIndex<uint16_t>, image_, + call_site_map_item_, sizeof(dex::CallSiteIdItem)); + return std::make_unique<InstructionReferenceReader>( + image_, lo, hi, code_item_offsets_, std::move(filter), std::move(mapper)); +} + std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToFieldId16( offset_t lo, offset_t hi) { @@ -1321,7 +1486,9 @@ std::unique_ptr<ReferenceReader> DisassemblerDex::MakeReadCodeToMethodId16( [](const InstructionParser::Value& value) -> offset_t { if (value.instr->format == dex::FormatId::c && (value.instr->opcode == 0x6E || // invoke-kind - value.instr->opcode == 0x74)) { // invoke-kind/range + value.instr->opcode == 0x74 || // invoke-kind/range + value.instr->opcode == 0xFA || // invoke-polymorphic + value.instr->opcode == 0xFB)) { // invoke-polymorphic/range // BBBB from e.g., invoke-virtual {vC, vD, vE, vF, vG}, meth@BBBB. return value.instr_offset + 2; } @@ -1491,6 +1658,14 @@ std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteMethodId32( return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer)); } +std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteCallSiteId16( + MutableBufferView image) { + auto writer = + base::BindRepeating(WriteTargetIndex<uint16_t>, call_site_map_item_, + sizeof(dex::CallSiteIdItem)); + return std::make_unique<ReferenceWriterAdaptor>(image, std::move(writer)); +} + std::unique_ptr<ReferenceWriter> DisassemblerDex::MakeWriteRelCode8( MutableBufferView image) { auto writer = base::BindRepeating([](Reference ref, MutableBufferView image) { @@ -1587,15 +1762,7 @@ bool DisassemblerDex::ParseHeader() { return false; // Read and validate map list, ensuring that required item types are present. - // - GetItemBaseSize() should have an entry for each item. - // - dex::kTypeCodeItem is actually not required; it's possible to have a DEX - // file with classes that have no code. However, this is unlikely to appear - // in application, so for simplicity we require DEX files to have code. - std::set<uint16_t> required_item_types = { - dex::kTypeStringIdItem, dex::kTypeTypeIdItem, dex::kTypeProtoIdItem, - dex::kTypeFieldIdItem, dex::kTypeMethodIdItem, dex::kTypeClassDefItem, - dex::kTypeTypeList, dex::kTypeCodeItem, - }; + // GetItemBaseSize() should have an entry for each item. for (offset_t i = 0; i < list_size; ++i) { const dex::MapItem* item = &item_list[i]; // Reject unreasonably large |item->size|. @@ -1605,29 +1772,46 @@ bool DisassemblerDex::ParseHeader() { return false; if (!map_item_map_.insert(std::make_pair(item->type, item)).second) return false; // A given type must appear at most once. - required_item_types.erase(item->type); } - // TODO(huangs): Replace this with guards throughout file. - if (!required_item_types.empty()) - return false; // Make local copies of main map items. - string_map_item_ = *map_item_map_[dex::kTypeStringIdItem]; - type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem]; - proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem]; - field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem]; - method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem]; - class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem]; - type_list_map_item_ = *map_item_map_[dex::kTypeTypeList]; - code_map_item_ = *map_item_map_[dex::kTypeCodeItem]; - - // The following types are optional and may not be present in every DEX file. + if (map_item_map_.count(dex::kTypeStringIdItem)) { + string_map_item_ = *map_item_map_[dex::kTypeStringIdItem]; + } + if (map_item_map_.count(dex::kTypeTypeIdItem)) { + type_map_item_ = *map_item_map_[dex::kTypeTypeIdItem]; + } + if (map_item_map_.count(dex::kTypeProtoIdItem)) { + proto_map_item_ = *map_item_map_[dex::kTypeProtoIdItem]; + } + if (map_item_map_.count(dex::kTypeFieldIdItem)) { + field_map_item_ = *map_item_map_[dex::kTypeFieldIdItem]; + } + if (map_item_map_.count(dex::kTypeMethodIdItem)) { + method_map_item_ = *map_item_map_[dex::kTypeMethodIdItem]; + } + if (map_item_map_.count(dex::kTypeClassDefItem)) { + class_def_map_item_ = *map_item_map_[dex::kTypeClassDefItem]; + } + if (map_item_map_.count(dex::kTypeCallSiteIdItem)) { + call_site_map_item_ = *map_item_map_[dex::kTypeCallSiteIdItem]; + } + if (map_item_map_.count(dex::kTypeMethodHandleItem)) { + method_handle_map_item_ = *map_item_map_[dex::kTypeMethodHandleItem]; + } + if (map_item_map_.count(dex::kTypeTypeList)) { + type_list_map_item_ = *map_item_map_[dex::kTypeTypeList]; + } if (map_item_map_.count(dex::kTypeAnnotationSetRefList)) { annotation_set_ref_list_map_item_ = *map_item_map_[dex::kTypeAnnotationSetRefList]; } - if (map_item_map_.count(dex::kTypeAnnotationSetItem)) + if (map_item_map_.count(dex::kTypeAnnotationSetItem)) { annotation_set_map_item_ = *map_item_map_[dex::kTypeAnnotationSetItem]; + } + if (map_item_map_.count(dex::kTypeCodeItem)) { + code_map_item_ = *map_item_map_[dex::kTypeCodeItem]; + } if (map_item_map_.count(dex::kTypeAnnotationsDirectoryItem)) { annotations_directory_map_item_ = *map_item_map_[dex::kTypeAnnotationsDirectoryItem]; |