opt/check_breadcrumbs/CheckBreadcrumbs.cpp (827 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "CheckBreadcrumbs.h" #include <algorithm> #include <fstream> #include <iosfwd> #include <sstream> #include <unordered_set> #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> #include "DexAccess.h" #include "DexClass.h" #include "DexUtil.h" #include "IRCode.h" #include "IRInstruction.h" #include "PassManager.h" #include "Resolver.h" #include "Show.h" #include "Trace.h" #include "Walkers.h" /** * Performs 2 kind of verifications: * 1- no references should be to a DexClass that is "internal" * but not in scope (effectively deleted) * 2- if a field or method reference is a def the field or method * must exist on the class it is defined on * Those are 2 relatively common problems we introduce: leave references * to deleted types, methods or fields. */ namespace { constexpr const char* METRIC_BAD_FIELDS = "bad_fields"; constexpr const char* METRIC_BAD_METHODS = "bad_methods"; constexpr const char* METRIC_BAD_TYPE_INSTRUCTIONS = "bad_type_instructions"; constexpr const char* METRIC_BAD_FIELD_INSTRUCTIONS = "bad_field_instructions"; constexpr const char* METRIC_BAD_METHOD_INSTRUCTIONS = "bad_method_instructions"; constexpr const char* METRIC_ILLEGAL_CROSS_STORE_REFS = "illegal_cross_store_refs"; constexpr const char* METRIC_TYPES_WITH_ALLOWED_VIOLATIONS = "allowed_types_with_violations"; bool class_contains(const DexField* field) { const auto* cls = type_class(field->get_class()); if (cls == nullptr) return true; for (const auto& cls_field : cls->get_ifields()) { if (field == cls_field) return true; } for (const auto& cls_field : cls->get_sfields()) { if (field == cls_field) return true; } return false; } bool class_contains(const DexMethod* method) { const auto* cls = type_class(method->get_class()); if (cls == nullptr) return true; for (const auto& cls_meth : cls->get_vmethods()) { if (method == cls_meth) return true; } for (const auto& cls_meth : cls->get_dmethods()) { if (method == cls_meth) return true; } return false; } using Fields = std::vector<const DexField*>; using Methods = std::vector<const DexMethod*>; using Instructions = std::vector<const IRInstruction*>; using MethodInsns = std::map<const DexMethod*, Instructions, dexmethods_comparator>; const DexType* get_type_from_insn(const IRInstruction* insn) { auto op = insn->opcode(); if (opcode::is_an_invoke(op)) { return insn->get_method()->get_class(); } if (opcode::is_an_ifield_op(op) || opcode::is_an_sfield_op(op)) { return insn->get_field()->get_class(); } return insn->get_type(); } std::string get_store_name(const XStoreRefs& xstores, size_t idx) { auto base_name = xstores.get_store(idx)->get_name(); if (idx > 0) { base_name += std::to_string(idx); } return base_name; } std::string get_store_name(const XStoreRefs& xstores, const DexType* t) { std::string base_name = xstores.get_store(t)->get_name(); size_t idx = xstores.get_store_idx(t); if (idx > 0) { base_name += std::to_string(idx); } return base_name; } size_t sum_instructions(const MethodInsns& map) { size_t result = 0; for (const auto& pair : map) { result += pair.second.size(); } return result; } void build_allowed_violations(const Scope& scope, const std::string& allowed_violations_file_path, bool enforce_types_exist, std::unordered_set<const DexType*>* types, std::unordered_set<std::string>* type_prefixes, std::vector<std::string>* unneeded_lines) { if (!boost::filesystem::exists(allowed_violations_file_path)) { return; } std::ifstream input(allowed_violations_file_path); if (!input) { fprintf(stderr, "[error] Can not open path %s\n", allowed_violations_file_path.c_str()); return; } std::unordered_map<std::string, bool> allowed_class_names; std::string line; while (std::getline(input, line)) { if (line.empty() || boost::algorithm::starts_with(line, "#")) { continue; } if (boost::algorithm::ends_with(line, ";")) { allowed_class_names.emplace(line, false); } else { type_prefixes->emplace(line); } } for (const auto& cls : scope) { auto& dname = cls->get_deobfuscated_name_or_empty(); if (allowed_class_names.count(dname) != 0) { types->emplace(cls->get_type()); allowed_class_names[dname] = true; } } if (enforce_types_exist) { for (const auto& pair : allowed_class_names) { if (!pair.second) { unneeded_lines->emplace_back(pair.first); } } } } void print_allowed_violations_per_class( const Scope& scope, const XStoreRefs& xstores, const std::map<const DexType*, Fields, dextypes_comparator>& illegal_fields, const std::map<const DexMethod*, Types, dexmethods_comparator>& illegal_method, const MethodInsns& illegal_type, const MethodInsns& illegal_field_type, const MethodInsns& illegal_field_cls, const MethodInsns& illegal_method_call) { for (const auto& cls : scope) { auto type = cls->get_type(); std::ostringstream fields_detail; auto fields = illegal_fields.find(type); if (fields != illegal_fields.end()) { for (const auto f : fields->second) { fields_detail << " " << f->get_deobfuscated_name_or_empty() << " (" << get_store_name(xstores, f->get_type()) << ")" << std::endl; } } std::ostringstream methods_detail; for (const auto& method : cls->get_all_methods()) { std::ostringstream method_detail; auto protos = illegal_method.find(method); if (protos != illegal_method.end()) { for (const auto proto_type : protos->second) { method_detail << " Proto type " << show_deobfuscated(proto_type) << " (" << get_store_name(xstores, proto_type) << ")" << std::endl; } } auto type_insns = illegal_type.find(method); if (type_insns != illegal_type.end()) { for (const auto insn : type_insns->second) { method_detail << " Instruction type " << show_deobfuscated(insn) << " (" << get_store_name(xstores, insn->get_type()) << ")" << std::endl; } } auto field_type_insns = illegal_field_type.find(method); if (field_type_insns != illegal_field_type.end()) { for (const auto insn : field_type_insns->second) { method_detail << " Field type " << show_deobfuscated(insn) << " (" << get_store_name(xstores, insn->get_field()->get_type()) << ")" << std::endl; } } auto field_cls_insns = illegal_field_cls.find(method); if (field_cls_insns != illegal_field_cls.end()) { for (const auto insn : field_cls_insns->second) { method_detail << " Field class " << show_deobfuscated(insn) << " (" << get_store_name(xstores, insn->get_field()->get_class()) << ")" << std::endl; } } auto method_calls = illegal_method_call.find(method); if (method_calls != illegal_method_call.end()) { for (const auto insn : method_calls->second) { method_detail << " Callee class " << show_deobfuscated(insn) << " (" << get_store_name(xstores, insn->get_method()->get_class()) << ")" << std::endl; } } auto detail_str = method_detail.str(); if (!detail_str.empty()) { methods_detail << " " << show_deobfuscated(method) << std::endl << detail_str; } } auto fields_detail_str = fields_detail.str(); auto methods_detail_str = methods_detail.str(); if (!fields_detail_str.empty() || !methods_detail_str.empty()) { TRACE(BRCR, 3, "Allowed violations in type %s (%s)", show_deobfuscated(type).c_str(), get_store_name(xstores, type).c_str()); if (!fields_detail_str.empty()) { TRACE(BRCR, 3, " Fields:"); TRACE(BRCR, 3, "%s", fields_detail_str.c_str()); } if (!methods_detail_str.empty()) { TRACE(BRCR, 3, " Methods:"); TRACE(BRCR, 3, "%s", methods_detail_str.c_str()); } } } } template <typename T, typename PrinterFn> void gather_unnessary_allows(const std::unordered_set<T>& expected_violations, const std::unordered_set<T>& actual_violations, const PrinterFn& printer, std::vector<std::string>* unneeded_lines) { for (auto& e : expected_violations) { if (!actual_violations.count(e)) { unneeded_lines->emplace_back(printer(e)); } } } } // namespace Breadcrumbs::Breadcrumbs(const Scope& scope, const std::string& allowed_violations_file_path, DexStoresVector& stores, const std::string& shared_module_prefix, bool reject_illegal_refs_root_store, bool only_verify_primary_dex, bool verify_type_hierarchies, bool verify_proto_cross_dex, bool enforce_allowed_violations_file) : m_scope(scope), m_xstores(stores, shared_module_prefix), m_reject_illegal_refs_root_store(reject_illegal_refs_root_store), m_verify_type_hierarchies(verify_type_hierarchies), m_verify_proto_cross_dex(verify_proto_cross_dex), m_enforce_allowed_violations_file(enforce_allowed_violations_file) { m_classes.insert(scope.begin(), scope.end()); m_multiple_root_store_dexes = stores[0].get_dexen().size() > 1; if (only_verify_primary_dex) { for (auto& c : scope) { if (m_xstores.is_in_primary_dex(c->get_type())) { m_scope_to_walk.push_back(c); } } } else { m_scope_to_walk.insert(m_scope_to_walk.end(), scope.begin(), scope.end()); } build_allowed_violations(m_scope, allowed_violations_file_path, enforce_allowed_violations_file, &m_allow_violations, &m_allow_violation_type_prefixes, &m_unneeded_violations_file_lines); } void Breadcrumbs::check_breadcrumbs() { check_fields(); check_methods(); check_opcodes(); } void Breadcrumbs::report_deleted_types(bool report_only, PassManager& mgr) { size_t bad_fields_count = 0; size_t bad_methods_count = 0; size_t bad_type_insns_count = 0; size_t bad_field_insns_count = 0; size_t bad_meths_insns_count = 0; if (!m_bad_fields.empty() || !m_bad_methods.empty() || !m_bad_type_insns.empty() || !m_bad_field_insns.empty() || !m_bad_meth_insns.empty()) { std::ostringstream ss; for (const auto& bad_field : m_bad_fields) { for (const auto& field : bad_field.second) { bad_fields_count++; ss << "Reference to deleted type " << SHOW(bad_field.first) << " in field " << SHOW(field) << std::endl; } } for (const auto& bad_meth : m_bad_methods) { for (const auto& meth : bad_meth.second) { bad_methods_count++; ss << "Reference to deleted type " << SHOW(bad_meth.first) << " in method " << SHOW(meth) << std::endl; } } for (const auto& bad_insns : m_bad_type_insns) { for (const auto& insns : bad_insns.second) { for (const auto& insn : insns.second) { bad_type_insns_count++; ss << "Reference to deleted type " << SHOW(bad_insns.first) << " in instruction " << SHOW(insn) << " in method " << SHOW(insns.first) << std::endl; } } } for (const auto& bad_insns : m_bad_field_insns) { for (const auto& insns : bad_insns.second) { for (const auto& insn : insns.second) { bad_field_insns_count++; ss << "Reference to deleted field " << SHOW(bad_insns.first) << " in instruction " << SHOW(insn) << " in method " << SHOW(insns.first) << std::endl; } } } for (const auto& bad_insns : m_bad_meth_insns) { for (const auto& insns : bad_insns.second) { for (const auto& insn : insns.second) { bad_meths_insns_count++; ss << "Reference to deleted method " << SHOW(bad_insns.first) << " in instruction " << SHOW(insn) << " in method " << SHOW(insns.first) << std::endl; } } } TRACE(BRCR, 1, "Dangling References in Fields: %ld\n" "Dangling References in Methods: %ld\n" "Dangling References in Type Instructions: %ld\n" "Dangling References in Fields Field Instructions: %ld\n" "Dangling References in Method Instructions: %ld\n", bad_fields_count, bad_methods_count, bad_type_insns_count, bad_field_insns_count, bad_meths_insns_count); TRACE(BRCR, 2, "%s", ss.str().c_str()); always_assert_log( report_only, "ERROR - Dangling References (contact redex@on-call):\n%s", ss.str().c_str()); } else { TRACE(BRCR, 1, "No dangling references"); } mgr.incr_metric(METRIC_BAD_FIELDS, bad_fields_count); mgr.incr_metric(METRIC_BAD_METHODS, bad_methods_count); mgr.incr_metric(METRIC_BAD_TYPE_INSTRUCTIONS, bad_type_insns_count); mgr.incr_metric(METRIC_BAD_FIELD_INSTRUCTIONS, bad_field_insns_count); mgr.incr_metric(METRIC_BAD_METHOD_INSTRUCTIONS, bad_meths_insns_count); } std::string Breadcrumbs::get_methods_with_bad_refs() { std::ostringstream ss; for (const auto& class_meth : m_bad_methods) { const auto type = class_meth.first; const auto& methods = class_meth.second; ss << "Bad methods in class " << type->get_name()->c_str() << std::endl; for (const auto method : methods) { ss << "\t" << method->get_name()->c_str() << std::endl; } ss << std::endl; } for (const auto& meth_field : m_bad_fields_refs) { const auto type = meth_field.first->get_class(); const auto method = meth_field.first; const auto& fields = meth_field.second; ss << "Bad field refs in method " << type->get_name()->c_str() << "." << method->get_name()->c_str() << std::endl; for (const auto field : fields) { ss << "\t" << field->get_name()->c_str() << std::endl; } ss << std::endl; } return ss.str(); } bool Breadcrumbs::should_allow_violations(const DexType* type) { if (m_allow_violations.count(type) > 0) { // Keep track simply for emitting metrics. m_types_with_allowed_violations.emplace(type); return true; } auto dname = show_deobfuscated(type); for (const auto& s : m_allow_violation_type_prefixes) { if (boost::algorithm::starts_with(dname, s)) { m_types_with_allowed_violations.emplace(type); m_type_prefixes_with_allowed_violations.emplace(s); return true; } } return false; } size_t Breadcrumbs::process_illegal_elements(const XStoreRefs& xstores, const MethodInsns& method_to_insns, const char* desc, MethodInsns& allowed, std::ostream& ss) { size_t num_illegal_cross_store_refs = 0; for (const auto& pair : method_to_insns) { const auto method = pair.first; const auto& insns = pair.second; if (should_allow_violations(method->get_class())) { allowed.emplace(method, insns); continue; } ss << "Illegal " << desc << " in method " << method->get_deobfuscated_name_or_empty() << " (" << get_store_name(xstores, method->get_class()) << ")" << std::endl; num_illegal_cross_store_refs += insns.size(); for (const auto insn : insns) { ss << "\t" << show_deobfuscated(insn) << " (" << get_store_name(xstores, get_type_from_insn(insn)) << ")" << std::endl; } } return num_illegal_cross_store_refs; } void Breadcrumbs::report_illegal_refs(bool fail_if_illegal_refs, PassManager& mgr) { size_t num_illegal_fields = 0; size_t num_allowed_illegal_fields = 0; std::ostringstream ss; std::map<const DexType*, Fields, dextypes_comparator> allowed_illegal_fields; for (const auto& pair : m_illegal_field) { const auto type = pair.first; const auto& fields = pair.second; if (should_allow_violations(type)) { allowed_illegal_fields.emplace(type, fields); num_allowed_illegal_fields += fields.size(); continue; } num_illegal_fields += fields.size(); ss << "Illegal fields in class " << type_class(type)->get_deobfuscated_name_or_empty() << " (" << get_store_name(m_xstores, type) << ")" << std::endl; for (const auto field : fields) { ss << "\t" << field->get_deobfuscated_name_or_empty() << " (" << get_store_name(m_xstores, field->get_type()) << ")" << std::endl; } } size_t num_illegal_method_defs = 0; std::map<const DexMethod*, Types, dexmethods_comparator> allowed_illegal_method; for (const auto& pair : m_illegal_method) { const auto method = pair.first; const auto& types = pair.second; if (should_allow_violations(method->get_class())) { allowed_illegal_method.emplace(method, types); continue; } num_illegal_method_defs++; ss << "Illegal types in method proto " << show_deobfuscated(method) << " (" << get_store_name(m_xstores, method->get_class()) << ")" << std::endl; for (const auto t : types) { ss << "\t" << show_deobfuscated(t) << " (" << get_store_name(m_xstores, t) << ")" << std::endl; } } MethodInsns allowed_illegal_type; size_t num_illegal_type_refs = process_illegal_elements( m_xstores, m_illegal_type, "type refs", allowed_illegal_type, ss); MethodInsns allowed_illegal_field_type; size_t num_illegal_field_type_refs = process_illegal_elements( m_xstores, m_illegal_field_type, "field type refs", allowed_illegal_field_type, ss); MethodInsns allowed_illegal_field_cls; size_t num_illegal_field_cls = process_illegal_elements( m_xstores, m_illegal_field_cls, "field class refs", allowed_illegal_field_cls, ss); MethodInsns allowed_illegal_method_call; size_t num_illegal_method_calls = process_illegal_elements(m_xstores, m_illegal_method_call, "method call", allowed_illegal_method_call, ss); size_t num_illegal_cross_store_refs = num_illegal_fields + num_illegal_type_refs + num_illegal_field_cls + num_illegal_field_type_refs + num_illegal_method_calls + num_illegal_method_defs; mgr.set_metric(METRIC_ILLEGAL_CROSS_STORE_REFS, num_illegal_cross_store_refs); TRACE(BRCR, 1, "Illegal fields : %ld\n" "Illegal type refs : %ld\n" "Illegal field type refs : %ld\n" "Illegal field cls refs : %ld\n" "Illegal method calls : %ld\n" "Illegal method defs : %ld\n", num_illegal_fields, num_illegal_type_refs, num_illegal_field_type_refs, num_illegal_field_cls, num_illegal_method_calls, num_illegal_method_defs); TRACE(BRCR, 2, "%s", ss.str().c_str()); always_assert_type_log(ss.str().empty() || !fail_if_illegal_refs, RedexError::REJECTED_CODING_PATTERN, "ERROR - illegal cross store references!\n%s", ss.str().c_str()); mgr.set_metric(METRIC_TYPES_WITH_ALLOWED_VIOLATIONS, m_types_with_allowed_violations.size()); TRACE(BRCR, 1, "Allowed Illegal fields : %ld\n" "Allowed Illegal type refs : %ld\n" "Allowed Illegal field type refs : %ld\n" "Allowed Illegal field cls refs : %ld\n" "Allowed Illegal method calls : %ld\n" "Allowed Illegal method defs : %ld\n", num_allowed_illegal_fields, sum_instructions(allowed_illegal_type), sum_instructions(allowed_illegal_field_type), sum_instructions(allowed_illegal_field_cls), sum_instructions(allowed_illegal_method_call), allowed_illegal_method.size()); if (traceEnabled(BRCR, 3)) { print_allowed_violations_per_class( m_scope_to_walk, m_xstores, allowed_illegal_fields, allowed_illegal_method, allowed_illegal_type, allowed_illegal_field_type, allowed_illegal_field_cls, allowed_illegal_method_call); } if (m_enforce_allowed_violations_file) { // Enforce no unnecessary lines in violations file. gather_unnessary_allows( m_allow_violations, m_types_with_allowed_violations, [](const DexType* t) { return show_deobfuscated(t); }, &m_unneeded_violations_file_lines); gather_unnessary_allows( m_allow_violation_type_prefixes, m_type_prefixes_with_allowed_violations, [](std::string s) { return s; }, &m_unneeded_violations_file_lines); always_assert_log( m_unneeded_violations_file_lines.empty(), "Please prune the following lines from allowed " "violations list, they are not needed:\n%s", boost::algorithm::join(m_unneeded_violations_file_lines, "\n").c_str()); } } bool Breadcrumbs::has_illegal_access(const DexMethod* input_method) { bool result = false; if (input_method->get_code() == nullptr) { return false; } for (const auto& mie : InstructionIterable(input_method->get_code())) { auto* insn = mie.insn; if (insn->has_field()) { auto res_field = resolve_field(insn->get_field()); if (res_field != nullptr) { if (!check_field_accessibility(input_method, res_field)) { result = true; } } else if (referenced_field_is_deleted(insn->get_field())) { result = true; } } if (insn->has_method()) { auto res_method = resolve_method(insn->get_method(), opcode_to_search(insn), input_method); if (res_method != nullptr) { if (!check_method_accessibility(input_method, res_method)) { result = true; } } else if (referenced_method_is_deleted(insn->get_method())) { result = true; } } } return result; } bool Breadcrumbs::is_illegal_cross_store(const DexType* caller, const DexType* callee) { // Skip deleted types, as we don't know the store for those. if (m_classes.count(type_class(caller)) == 0 || m_classes.count(type_class(callee)) == 0 || caller == callee) { return false; } std::set<const DexType*, dextypes_comparator> load_types; if (m_verify_type_hierarchies) { auto callee_cls = type_class(callee); std::unordered_set<DexType*> types; callee_cls->gather_load_types(types); load_types.insert(types.begin(), types.end()); } else { load_types.emplace(callee); } size_t caller_store_idx = m_xstores.get_store_idx(caller); for (const auto& callee_to_check : load_types) { size_t callee_store_idx = m_xstores.get_store_idx(callee_to_check); if (m_multiple_root_store_dexes && caller_store_idx == 0 && callee_store_idx == 1 && !m_reject_illegal_refs_root_store) { return false; } if (m_xstores.illegal_ref_between_stores(caller_store_idx, callee_store_idx)) { if (callee_to_check != callee) { TRACE(BRCR, 2, "Illegal reference from %s (%s) to class %s (%s) in type " "hierarchy of %s", show_deobfuscated(caller).c_str(), get_store_name(m_xstores, caller_store_idx).c_str(), show_deobfuscated(callee_to_check).c_str(), get_store_name(m_xstores, callee_store_idx).c_str(), show_deobfuscated(callee).c_str()); } return true; } } return false; } /** * Return the type reference that is neither external nor defined, or return * null if the type reference is defined or external. */ const DexType* Breadcrumbs::check_type(const DexType* type) { auto type_ref = type::get_element_type_if_array(type); const auto& cls = type_class(type_ref); if (cls == nullptr) return nullptr; if (cls->is_external()) return nullptr; if (m_classes.count(cls) > 0) return nullptr; return type_ref; } /** * Return a type reference on the method ref if the definition of the type is * missing, or return null if all the type references are defined or external. */ const DexType* Breadcrumbs::check_method(const DexMethodRef* method) { std::vector<DexType*> type_refs; method->gather_types_shallow(type_refs); for (auto type : type_refs) { auto bad_ref = check_type(type); if (bad_ref) { return bad_ref; } } return nullptr; } const DexType* Breadcrumbs::check_anno(const DexAnnotationSet* anno) { if (!anno) { return nullptr; } std::vector<DexType*> type_refs; anno->gather_types(type_refs); for (auto type : type_refs) { auto bad_ref = check_type(type); if (bad_ref) { return bad_ref; } } return nullptr; } void Breadcrumbs::bad_type(const DexType* type, const DexMethod* method, const IRInstruction* insn) { m_bad_type_insns[type][method].emplace_back(insn); } // Verify that all field definitions reference types that are not deleted. void Breadcrumbs::check_fields() { walk::fields(m_scope_to_walk, [&](DexField* field) { bool check_cross_store_ref = true; std::vector<DexType*> type_refs; field->gather_types(type_refs); for (auto type : type_refs) { auto bad_ref = check_type(type); if (bad_ref) { m_bad_fields[bad_ref].emplace_back(field); check_cross_store_ref = false; } } if (check_cross_store_ref) { const auto cls = field->get_class(); const auto field_type = field->get_type(); if (is_illegal_cross_store(cls, field_type)) { m_illegal_field[cls].emplace_back(field); } return; } }); } // Verify that all method definitions use not deleted types in their signatures // and annotations. void Breadcrumbs::check_methods() { walk::methods(m_scope_to_walk, [&](DexMethod* method) { bool check_cross_store_ref = true; // Check type references on the method signature. const auto* bad_ref = check_method(method); if (bad_ref) { m_bad_methods[bad_ref].emplace_back(method); check_cross_store_ref = false; } // Check type references on the annotations on the method. bad_ref = check_anno(method->get_anno_set()); if (bad_ref) { m_bad_methods[bad_ref].emplace_back(method); check_cross_store_ref = false; } if (check_cross_store_ref) { has_illegal_access(method); if (m_verify_proto_cross_dex) { // Ensure type hierarchies of proto types, which might be meaningful for // verification on some OS versions. const auto cls = method->get_class(); std::vector<DexType*> proto_types; method->get_proto()->gather_types(proto_types); for (const auto& t : proto_types) { if (is_illegal_cross_store(cls, t)) { m_illegal_method[method].emplace_back(t); } } } } }); } /* verify that all method instructions that access fields are valid */ bool Breadcrumbs::check_field_accessibility(const DexMethod* method, const DexField* res_field) { const auto field_class = res_field->get_class(); const auto method_class = method->get_class(); if (field_class != method_class && is_private(res_field)) { m_bad_fields_refs[method].emplace_back(res_field); return false; } return true; } bool Breadcrumbs::referenced_field_is_deleted(DexFieldRef* field_ref) { auto field = field_ref->as_def(); return field && !class_contains(field); } bool Breadcrumbs::referenced_method_is_deleted(DexMethodRef* method_ref) { auto method = method_ref->as_def(); return method && !class_contains(method); } /* verify that all method instructions that access methods are valid */ bool Breadcrumbs::check_method_accessibility( const DexMethod* method, const DexMethod* res_called_method) { const auto called_method_class = res_called_method->get_class(); const auto method_class = method->get_class(); if (called_method_class != method_class && is_private(res_called_method)) { m_bad_methods[method_class].emplace_back(res_called_method); return false; } return true; } // verify that all opcodes are to non deleted references void Breadcrumbs::check_type_opcode(const DexMethod* method, IRInstruction* insn) { const DexType* type = insn->get_type(); type = check_type(type); if (type != nullptr) { bad_type(type, method, insn); } else { const auto cls = method->get_class(); if (is_illegal_cross_store(cls, insn->get_type())) { m_illegal_type[method].emplace_back(insn); } } } void Breadcrumbs::check_field_opcode(const DexMethod* method, IRInstruction* insn) { bool check_cross_store_ref = true; auto field = insn->get_field(); std::vector<DexType*> type_refs; field->gather_types_shallow(type_refs); for (auto type : type_refs) { auto bad_ref = check_type(type); if (bad_ref) { bad_type(bad_ref, method, insn); check_cross_store_ref = false; } } if (check_cross_store_ref) { auto cls = method->get_class(); if (is_illegal_cross_store(cls, field->get_type())) { m_illegal_field_type[method].emplace_back(insn); } if (is_illegal_cross_store(cls, field->get_class())) { m_illegal_field_cls[method].emplace_back(insn); } } auto res_field = resolve_field(field); if (res_field != nullptr) { // a resolved field can only differ in the owner class if (field != res_field) { auto bad_ref = check_type(field->get_class()); if (bad_ref != nullptr) { bad_type(bad_ref, method, insn); return; } } } else { // the class of the field is around but the field may have // been deleted so let's verify the field exists on the class if (referenced_field_is_deleted(field)) { m_bad_field_insns[static_cast<DexField*>(field)][method].emplace_back( insn); return; } } } void Breadcrumbs::check_method_opcode(const DexMethod* method, IRInstruction* insn) { const auto& meth = insn->get_method(); const DexType* type = check_method(meth); if (type != nullptr) { bad_type(type, method, insn); return; } if (is_illegal_cross_store(method->get_class(), meth->get_class())) { m_illegal_method_call[method].emplace_back(insn); } DexMethod* res_meth = resolve_method(meth, opcode_to_search(insn), method); if (res_meth != nullptr) { // a resolved method can only differ in the owner class if (res_meth != meth) { type = check_type(res_meth->get_class()); if (type != nullptr) { bad_type(type, method, insn); return; } } } else { // the class of the method is around but the method may have // been deleted so let's verify the method exists on the class if (referenced_method_is_deleted(meth)) { m_bad_meth_insns[static_cast<DexMethod*>(meth)][method].emplace_back( insn); return; } } } void Breadcrumbs::check_opcodes() { walk::opcodes( m_scope_to_walk, [](DexMethod*) { return true; }, [&](DexMethod* method, IRInstruction* insn) { if (insn->has_type()) { check_type_opcode(method, insn); return; } if (insn->has_field()) { check_field_opcode(method, insn); return; } if (insn->has_method()) { check_method_opcode(method, insn); } }); } void CheckBreadcrumbsPass::run_pass(DexStoresVector& stores, ConfigFiles& /* conf */, PassManager& mgr) { auto scope = build_class_scope(stores); Breadcrumbs bc(scope, allowed_violations_file_path, stores, shared_module_prefix, reject_illegal_refs_root_store, only_verify_primary_dex, verify_type_hierarchies, verify_proto_cross_dex, enforce_allowed_violations_file); bc.check_breadcrumbs(); bc.report_deleted_types(!fail, mgr); bc.report_illegal_refs(fail_if_illegal_refs, mgr); } static CheckBreadcrumbsPass s_pass;