[veridex] Detect reflection uses.
Add HiddenApiFinder to walk over the code item of
app dex files, and find static linking uses and potential
reflection uses.
bug: 64382372
Test: m
Change-Id: I35f0b276703504f2e27a80007d410625ba7c9af3
diff --git a/tools/veridex/Android.bp b/tools/veridex/Android.bp
index a74bf3d..cbf62d9 100644
--- a/tools/veridex/Android.bp
+++ b/tools/veridex/Android.bp
@@ -17,6 +17,7 @@
host_supported: true,
srcs: [
"hidden_api.cc",
+ "hidden_api_finder.cc",
"resolver.cc",
"veridex.cc",
],
diff --git a/tools/veridex/hidden_api.cc b/tools/veridex/hidden_api.cc
index 33e499b..93f921a 100644
--- a/tools/veridex/hidden_api.cc
+++ b/tools/veridex/hidden_api.cc
@@ -44,17 +44,6 @@
return ss.str();
}
-bool HiddenApi::LogIfIn(const std::string& name,
- const std::set<std::string>& list,
- const std::string& log,
- const std::string& access_kind) {
- if (list.find(name) != list.end()) {
- LOG(WARNING) << std::string(log) << " usage found " << name << " (" << access_kind << ")";
- return true;
- }
- return false;
-}
-
void HiddenApi::FillList(const char* filename, std::set<std::string>& entries) {
if (filename == nullptr) {
return;
diff --git a/tools/veridex/hidden_api.h b/tools/veridex/hidden_api.h
index 282e7cf..5893b8a 100644
--- a/tools/veridex/hidden_api.h
+++ b/tools/veridex/hidden_api.h
@@ -17,6 +17,9 @@
#ifndef ART_TOOLS_VERIDEX_HIDDEN_API_H_
#define ART_TOOLS_VERIDEX_HIDDEN_API_H_
+#include "dex/hidden_api_access_flags.h"
+
+#include <ostream>
#include <set>
#include <string>
@@ -35,10 +38,20 @@
FillList(blacklist, blacklist_);
}
- bool LogIfInList(const std::string& name, const char* access_kind) const {
- return LogIfIn(name, blacklist_, "Blacklist", access_kind) ||
- LogIfIn(name, dark_greylist_, "Dark greylist", access_kind) ||
- LogIfIn(name, light_greylist_, "Light greylist", access_kind);
+ HiddenApiAccessFlags::ApiList GetApiList(const std::string& name) const {
+ if (IsInList(name, blacklist_)) {
+ return HiddenApiAccessFlags::kBlacklist;
+ } else if (IsInList(name, dark_greylist_)) {
+ return HiddenApiAccessFlags::kDarkGreylist;
+ } else if (IsInList(name, light_greylist_)) {
+ return HiddenApiAccessFlags::kLightGreylist;
+ } else {
+ return HiddenApiAccessFlags::kWhitelist;
+ }
+ }
+
+ bool IsInRestrictionList(const std::string& name) const {
+ return GetApiList(name) != HiddenApiAccessFlags::kWhitelist;
}
static std::string GetApiMethodName(const DexFile& dex_file, uint32_t method_index);
@@ -46,10 +59,9 @@
static std::string GetApiFieldName(const DexFile& dex_file, uint32_t field_index);
private:
- static bool LogIfIn(const std::string& name,
- const std::set<std::string>& list,
- const std::string& log,
- const std::string& access_kind);
+ static bool IsInList(const std::string& name, const std::set<std::string>& list) {
+ return list.find(name) != list.end();
+ }
static void FillList(const char* filename, std::set<std::string>& entries);
diff --git a/tools/veridex/hidden_api_finder.cc b/tools/veridex/hidden_api_finder.cc
new file mode 100644
index 0000000..d611f78
--- /dev/null
+++ b/tools/veridex/hidden_api_finder.cc
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2018 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 "hidden_api_finder.h"
+
+#include "dex/code_item_accessors-inl.h"
+#include "dex/dex_instruction-inl.h"
+#include "dex/dex_file.h"
+#include "dex/method_reference.h"
+#include "hidden_api.h"
+#include "resolver.h"
+#include "veridex.h"
+
+#include <iostream>
+
+namespace art {
+
+void HiddenApiFinder::CheckMethod(uint32_t method_id,
+ VeridexResolver* resolver,
+ MethodReference ref) {
+ // Cheap check that the method is resolved. If it is, we know it's not in
+ // a restricted list.
+ if (resolver->GetMethod(method_id) != nullptr) {
+ return;
+ }
+ std::string name = HiddenApi::GetApiMethodName(resolver->GetDexFile(), method_id);
+ if (hidden_api_.IsInRestrictionList(name)) {
+ method_locations_[name].push_back(ref);
+ }
+}
+
+void HiddenApiFinder::CheckField(uint32_t field_id,
+ VeridexResolver* resolver,
+ MethodReference ref) {
+ // Cheap check that the field is resolved. If it is, we know it's not in
+ // a restricted list.
+ if (resolver->GetField(field_id) != nullptr) {
+ return;
+ }
+ std::string name = HiddenApi::GetApiFieldName(resolver->GetDexFile(), field_id);
+ if (hidden_api_.IsInRestrictionList(name)) {
+ field_locations_[name].push_back(ref);
+ }
+}
+
+void HiddenApiFinder::CollectAccesses(VeridexResolver* resolver) {
+ const DexFile& dex_file = resolver->GetDexFile();
+ size_t class_def_count = dex_file.NumClassDefs();
+ for (size_t class_def_index = 0; class_def_index < class_def_count; ++class_def_index) {
+ const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
+ const uint8_t* class_data = dex_file.GetClassData(class_def);
+ if (class_data == nullptr) {
+ // Empty class.
+ continue;
+ }
+ ClassDataItemIterator it(dex_file, class_data);
+ it.SkipAllFields();
+ for (; it.HasNextMethod(); it.Next()) {
+ const DexFile::CodeItem* code_item = it.GetMethodCodeItem();
+ if (code_item == nullptr) {
+ continue;
+ }
+ CodeItemDataAccessor code_item_accessor(dex_file, code_item);
+ for (const DexInstructionPcPair& inst : code_item_accessor) {
+ switch (inst->Opcode()) {
+ case Instruction::CONST_CLASS: {
+ dex::TypeIndex type_index(inst->VRegB_21c());
+ std::string name = dex_file.StringByTypeIdx(type_index);
+ // Only keep classes that are in a restriction list.
+ if (hidden_api_.IsInRestrictionList(name)) {
+ classes_.insert(name);
+ }
+ break;
+ }
+ case Instruction::CONST_STRING: {
+ dex::StringIndex string_index(inst->VRegB_21c());
+ std::string name = std::string(dex_file.StringDataByIdx(string_index));
+ // Cheap filtering on the string literal. We know it cannot be a field/method/class
+ // if it contains a space.
+ if (name.find(' ') == std::string::npos) {
+ // Class names at the Java level are of the form x.y.z, but the list encodes
+ // them of the form Lx/y/z;. Inner classes have '$' for both Java level class
+ // names in strings, and hidden API lists.
+ std::string str = name;
+ std::replace(str.begin(), str.end(), '.', '/');
+ str = "L" + str + ";";
+ // Note: we can query the lists directly, as HiddenApi added classes that own
+ // private methods and fields in them.
+ // We don't add class names to the `strings_` set as we know method/field names
+ // don't have '.' or '/'. All hidden API class names have a '/'.
+ if (hidden_api_.IsInRestrictionList(str)) {
+ classes_.insert(str);
+ } else if (hidden_api_.IsInRestrictionList(name)) {
+ // Could be something passed to JNI.
+ classes_.insert(name);
+ } else {
+ // We only keep track of the location for strings, as these will be the
+ // field/method names the user is interested in.
+ strings_.insert(name);
+ reflection_locations_[name].push_back(
+ MethodReference(&dex_file, it.GetMemberIndex()));
+ }
+ }
+ break;
+ }
+ case Instruction::INVOKE_DIRECT:
+ case Instruction::INVOKE_INTERFACE:
+ case Instruction::INVOKE_STATIC:
+ case Instruction::INVOKE_SUPER:
+ case Instruction::INVOKE_VIRTUAL: {
+ CheckMethod(
+ inst->VRegB_35c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ case Instruction::INVOKE_DIRECT_RANGE:
+ case Instruction::INVOKE_INTERFACE_RANGE:
+ case Instruction::INVOKE_STATIC_RANGE:
+ case Instruction::INVOKE_SUPER_RANGE:
+ case Instruction::INVOKE_VIRTUAL_RANGE: {
+ CheckMethod(
+ inst->VRegB_3rc(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ case Instruction::IGET:
+ case Instruction::IGET_WIDE:
+ case Instruction::IGET_OBJECT:
+ case Instruction::IGET_BOOLEAN:
+ case Instruction::IGET_BYTE:
+ case Instruction::IGET_CHAR:
+ case Instruction::IGET_SHORT: {
+ CheckField(
+ inst->VRegC_22c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ case Instruction::IPUT:
+ case Instruction::IPUT_WIDE:
+ case Instruction::IPUT_OBJECT:
+ case Instruction::IPUT_BOOLEAN:
+ case Instruction::IPUT_BYTE:
+ case Instruction::IPUT_CHAR:
+ case Instruction::IPUT_SHORT: {
+ CheckField(
+ inst->VRegC_22c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ case Instruction::SGET:
+ case Instruction::SGET_WIDE:
+ case Instruction::SGET_OBJECT:
+ case Instruction::SGET_BOOLEAN:
+ case Instruction::SGET_BYTE:
+ case Instruction::SGET_CHAR:
+ case Instruction::SGET_SHORT: {
+ CheckField(
+ inst->VRegB_21c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ case Instruction::SPUT:
+ case Instruction::SPUT_WIDE:
+ case Instruction::SPUT_OBJECT:
+ case Instruction::SPUT_BOOLEAN:
+ case Instruction::SPUT_BYTE:
+ case Instruction::SPUT_CHAR:
+ case Instruction::SPUT_SHORT: {
+ CheckField(
+ inst->VRegB_21c(), resolver, MethodReference(&dex_file, it.GetMemberIndex()));
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+static std::string GetApiMethodName(MethodReference ref) {
+ return HiddenApi::GetApiMethodName(*ref.dex_file, ref.index);
+}
+
+void HiddenApiFinder::Run(const std::vector<std::unique_ptr<VeridexResolver>>& resolvers) {
+ for (const std::unique_ptr<VeridexResolver>& resolver : resolvers) {
+ CollectAccesses(resolver.get());
+ }
+
+ Dump(std::cout);
+}
+
+void HiddenApiFinder::Dump(std::ostream& os) {
+ static const char* kPrefix = " ";
+ uint32_t count = 0;
+ uint32_t linking_count = method_locations_.size() + field_locations_.size();
+ uint32_t api_counts[4] = {0, 0, 0, 0};
+
+ // Dump methods from hidden APIs linked against.
+ for (const std::pair<std::string, std::vector<MethodReference>>& pair : method_locations_) {
+ HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first);
+ api_counts[api_list]++;
+ os << "#" << ++count << ": Linking " << api_list << " " << pair.first << " use(s):";
+ os << std::endl;
+ for (const MethodReference& ref : pair.second) {
+ os << kPrefix << GetApiMethodName(ref) << std::endl;
+ }
+ os << std::endl;
+ }
+
+ // Dump fields from hidden APIs linked against.
+ for (const std::pair<std::string, std::vector<MethodReference>>& pair : field_locations_) {
+ HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(pair.first);
+ api_counts[api_list]++;
+ os << "#" << ++count << ": Linking " << api_list << " " << pair.first << " use(s):";
+ os << std::endl;
+ for (const MethodReference& ref : pair.second) {
+ os << kPrefix << GetApiMethodName(ref) << std::endl;
+ }
+ os << std::endl;
+ }
+
+ // Dump potential reflection uses.
+ for (const std::string& cls : classes_) {
+ for (const std::string& name : strings_) {
+ std::string full_name = cls + "->" + name;
+ HiddenApiAccessFlags::ApiList api_list = hidden_api_.GetApiList(full_name);
+ api_counts[api_list]++;
+ if (api_list != HiddenApiAccessFlags::kWhitelist) {
+ os << "#" << ++count << ": Reflection " << api_list << " " << full_name
+ << " potential use(s):";
+ os << std::endl;
+ for (const MethodReference& ref : reflection_locations_[name]) {
+ os << kPrefix << GetApiMethodName(ref) << std::endl;
+ }
+ os << std::endl;
+ }
+ }
+ }
+
+ os << count << " hidden API(s) used: "
+ << linking_count << " linked against, "
+ << count - linking_count << " potentially through reflection" << std::endl;
+ os << kPrefix << api_counts[HiddenApiAccessFlags::kBlacklist]
+ << " in blacklist" << std::endl;
+ os << kPrefix << api_counts[HiddenApiAccessFlags::kDarkGreylist]
+ << " in dark greylist" << std::endl;
+ os << kPrefix << api_counts[HiddenApiAccessFlags::kLightGreylist]
+ << " in light greylist" << std::endl;
+}
+
+} // namespace art
diff --git a/tools/veridex/hidden_api_finder.h b/tools/veridex/hidden_api_finder.h
new file mode 100644
index 0000000..243079c
--- /dev/null
+++ b/tools/veridex/hidden_api_finder.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef ART_TOOLS_VERIDEX_HIDDEN_API_FINDER_H_
+#define ART_TOOLS_VERIDEX_HIDDEN_API_FINDER_H_
+
+#include "dex/method_reference.h"
+
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+
+namespace art {
+
+class HiddenApi;
+class VeridexResolver;
+
+/**
+ * Reports potential uses of hidden APIs from static linking and reflection.
+ */
+class HiddenApiFinder {
+ public:
+ explicit HiddenApiFinder(const HiddenApi& hidden_api) : hidden_api_(hidden_api) {}
+
+ // Iterate over the dex files associated with the passed resolvers to report
+ // hidden API uses.
+ void Run(const std::vector<std::unique_ptr<VeridexResolver>>& app_resolvers);
+
+ private:
+ void CollectAccesses(VeridexResolver* resolver);
+ void CheckMethod(uint32_t method_idx, VeridexResolver* resolver, MethodReference ref);
+ void CheckField(uint32_t field_idx, VeridexResolver* resolver, MethodReference ref);
+ void Dump(std::ostream& os);
+
+ const HiddenApi& hidden_api_;
+ std::set<std::string> classes_;
+ std::set<std::string> strings_;
+ std::map<std::string, std::vector<MethodReference>> reflection_locations_;
+ std::map<std::string, std::vector<MethodReference>> method_locations_;
+ std::map<std::string, std::vector<MethodReference>> field_locations_;
+};
+
+} // namespace art
+
+#endif // ART_TOOLS_VERIDEX_HIDDEN_API_FINDER_H_
diff --git a/tools/veridex/resolver.cc b/tools/veridex/resolver.cc
index 6ab872e..13dda5c 100644
--- a/tools/veridex/resolver.cc
+++ b/tools/veridex/resolver.cc
@@ -277,10 +277,8 @@
return field_info;
}
-void VeridexResolver::ResolveAll(const HiddenApi& hidden_api) {
+void VeridexResolver::ResolveAll() {
for (uint32_t i = 0; i < dex_file_.NumTypeIds(); ++i) {
- // Note: we don't look at HiddenApi for types, as the lists don't contain
- // classes.
if (GetVeriClass(dex::TypeIndex(i)) == nullptr) {
LOG(WARNING) << "Unresolved " << dex_file_.PrettyType(dex::TypeIndex(i));
}
@@ -288,17 +286,13 @@
for (uint32_t i = 0; i < dex_file_.NumMethodIds(); ++i) {
if (GetMethod(i) == nullptr) {
- if (!hidden_api.LogIfInList(HiddenApi::GetApiMethodName(dex_file_, i), "Linking")) {
- LOG(WARNING) << "Unresolved: " << dex_file_.PrettyMethod(i);
- }
+ LOG(WARNING) << "Unresolved: " << dex_file_.PrettyMethod(i);
}
}
for (uint32_t i = 0; i < dex_file_.NumFieldIds(); ++i) {
if (GetField(i) == nullptr) {
- if (!hidden_api.LogIfInList(HiddenApi::GetApiFieldName(dex_file_, i), "Linking")) {
- LOG(WARNING) << "Unresolved: " << dex_file_.PrettyField(i);
- }
+ LOG(WARNING) << "Unresolved: " << dex_file_.PrettyField(i);
}
}
}
diff --git a/tools/veridex/resolver.h b/tools/veridex/resolver.h
index 82f6aae..06c8aa7 100644
--- a/tools/veridex/resolver.h
+++ b/tools/veridex/resolver.h
@@ -66,9 +66,13 @@
const char* field_name,
const char* field_type);
- // Resolve all type_id/method_id/field_id. Log for unresolved
- // entities, or entities part of a hidden API list.
- void ResolveAll(const HiddenApi& hidden_api);
+ // Resolve all type_id/method_id/field_id.
+ void ResolveAll();
+
+ // The dex file this resolver is associated to.
+ const DexFile& GetDexFile() const {
+ return dex_file_;
+ }
private:
// Return the resolver where `kls` is from.
diff --git a/tools/veridex/veridex.cc b/tools/veridex/veridex.cc
index c5203fe..16e9f0e 100644
--- a/tools/veridex/veridex.cc
+++ b/tools/veridex/veridex.cc
@@ -21,6 +21,7 @@
#include "dex/dex_file.h"
#include "dex/dex_file_loader.h"
#include "hidden_api.h"
+#include "hidden_api_finder.h"
#include "resolver.h"
#include <sstream>
@@ -162,11 +163,10 @@
std::vector<std::unique_ptr<VeridexResolver>> app_resolvers;
Resolve(app_dex_files, resolver_map, type_map, &app_resolvers);
- // Resolve all type_id/method_id/field_id of app dex files.
+ // Find and log uses of hidden APIs.
HiddenApi hidden_api(options.blacklist, options.dark_greylist, options.light_greylist);
- for (const std::unique_ptr<VeridexResolver>& resolver : app_resolvers) {
- resolver->ResolveAll(hidden_api);
- }
+ HiddenApiFinder api_finder(hidden_api);
+ api_finder.Run(app_resolvers);
return 0;
}