diff options
Diffstat (limited to 'tools/hiddenapi/hiddenapi.cc')
| -rw-r--r-- | tools/hiddenapi/hiddenapi.cc | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/tools/hiddenapi/hiddenapi.cc b/tools/hiddenapi/hiddenapi.cc new file mode 100644 index 0000000000..a755fdb40b --- /dev/null +++ b/tools/hiddenapi/hiddenapi.cc @@ -0,0 +1,408 @@ +/* + * Copyright (C) 2017 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 <fstream> +#include <iostream> +#include <unordered_set> + +#include "android-base/stringprintf.h" +#include "android-base/strings.h" + +#include "base/unix_file/fd_file.h" +#include "dex/art_dex_file_loader.h" +#include "dex/dex_file-inl.h" +#include "hidden_api_access_flags.h" +#include "mem_map.h" +#include "os.h" + +namespace art { + +static int original_argc; +static char** original_argv; + +static std::string CommandLine() { + std::vector<std::string> command; + for (int i = 0; i < original_argc; ++i) { + command.push_back(original_argv[i]); + } + return android::base::Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + android::base::StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +NO_RETURN static void Usage(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError("Usage: hiddenapi [options]..."); + UsageError(""); + UsageError(" --dex=<filename>: specify dex file whose members' access flags are to be set."); + UsageError(" At least one --dex parameter must be specified."); + UsageError(""); + UsageError(" --light-greylist=<filename>:"); + UsageError(" --dark-greylist=<filename>:"); + UsageError(" --blacklist=<filename>: text files with signatures of methods/fields to be marked"); + UsageError(" greylisted/blacklisted respectively. At least one list must be provided."); + UsageError(""); + UsageError(" --print-hidden-api: dump a list of marked methods/fields to the standard output."); + UsageError(" There is no indication which API category they belong to."); + UsageError(""); + + exit(EXIT_FAILURE); +} + +class DexClass { + public: + DexClass(const DexFile& dex_file, uint32_t idx) + : dex_file_(dex_file), class_def_(dex_file.GetClassDef(idx)) {} + + const DexFile& GetDexFile() const { return dex_file_; } + + const dex::TypeIndex GetClassIndex() const { return class_def_.class_idx_; } + + const uint8_t* GetData() const { return dex_file_.GetClassData(class_def_); } + + const char* GetDescriptor() const { return dex_file_.GetClassDescriptor(class_def_); } + + private: + const DexFile& dex_file_; + const DexFile::ClassDef& class_def_; +}; + +class DexMember { + public: + DexMember(const DexClass& klass, const ClassDataItemIterator& it) + : klass_(klass), it_(it) { + DCHECK_EQ(it_.IsAtMethod() ? GetMethodId().class_idx_ : GetFieldId().class_idx_, + klass_.GetClassIndex()); + } + + // Sets hidden bits in access flags and writes them back into the DEX in memory. + // Note that this will not update the cached data of ClassDataItemIterator + // until it iterates over this item again and therefore will fail a CHECK if + // it is called multiple times on the same DexMember. + void SetHidden(HiddenApiAccessFlags::ApiList value) { + const uint32_t old_flags = it_.GetRawMemberAccessFlags(); + const uint32_t new_flags = HiddenApiAccessFlags::EncodeForDex(old_flags, value); + CHECK_EQ(UnsignedLeb128Size(new_flags), UnsignedLeb128Size(old_flags)); + + // Locate the LEB128-encoded access flags in class data. + // `ptr` initially points to the next ClassData item. We iterate backwards + // until we hit the terminating byte of the previous Leb128 value. + const uint8_t* ptr = it_.DataPointer(); + if (it_.IsAtMethod()) { + ptr = ReverseSearchUnsignedLeb128(ptr, it_.GetMethodCodeItemOffset()); + } + ptr = ReverseSearchUnsignedLeb128(ptr, old_flags); + + // Overwrite the access flags. + UpdateUnsignedLeb128(const_cast<uint8_t*>(ptr), new_flags); + } + + // Returns true if this member's API entry is in `list`. + bool IsOnApiList(const std::unordered_set<std::string>& list) const { + return list.find(GetApiEntry()) != list.end(); + } + + // Constructs a string with a unique signature of this class member. + std::string GetApiEntry() const { + std::stringstream ss; + ss << klass_.GetDescriptor() << "->"; + if (it_.IsAtMethod()) { + const DexFile::MethodId& mid = GetMethodId(); + ss << klass_.GetDexFile().GetMethodName(mid) + << klass_.GetDexFile().GetMethodSignature(mid).ToString(); + } else { + const DexFile::FieldId& fid = GetFieldId(); + ss << klass_.GetDexFile().GetFieldName(fid) << ":" + << klass_.GetDexFile().GetFieldTypeDescriptor(fid); + } + return ss.str(); + } + + private: + inline const DexFile::MethodId& GetMethodId() const { + DCHECK(it_.IsAtMethod()); + return klass_.GetDexFile().GetMethodId(it_.GetMemberIndex()); + } + + inline const DexFile::FieldId& GetFieldId() const { + DCHECK(!it_.IsAtMethod()); + return klass_.GetDexFile().GetFieldId(it_.GetMemberIndex()); + } + + static inline bool IsLeb128Terminator(const uint8_t* ptr) { + return *ptr <= 0x7f; + } + + // Returns the first byte of a Leb128 value assuming that: + // (1) `end_ptr` points to the first byte after the Leb128 value, and + // (2) there is another Leb128 value before this one. + // The function will fail after reading 5 bytes (the longest supported Leb128 + // encoding) to protect against situations when (2) is not satisfied. + // When a Leb128 value is discovered, it is decoded and CHECKed against `value`. + static const uint8_t* ReverseSearchUnsignedLeb128(const uint8_t* end_ptr, uint32_t expected) { + const uint8_t* ptr = end_ptr; + + // Move one byte back, check that this is the terminating byte. + ptr--; + CHECK(IsLeb128Terminator(ptr)); + + // Keep moving back while the previous byte is not a terminating byte. + // Fail after reading five bytes in case there isn't another Leb128 value + // before this one. + while (!IsLeb128Terminator(ptr - 1)) { + ptr--; + CHECK_LE((size_t) (end_ptr - ptr), 5u); + } + + // Check that the decoded value matches the `expected` value. + const uint8_t* tmp_ptr = ptr; + CHECK_EQ(DecodeUnsignedLeb128(&tmp_ptr), expected); + + return ptr; + } + + const DexClass& klass_; + const ClassDataItemIterator& it_; +}; + +class HiddenApi FINAL { + public: + HiddenApi() : print_hidden_api_(false) {} + + void ParseArgs(int argc, char** argv) { + original_argc = argc; + original_argv = argv; + + android::base::InitLogging(argv); + + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + for (int i = 0; i < argc; ++i) { + const StringPiece option(argv[i]); + const bool log_options = false; + if (log_options) { + LOG(INFO) << "hiddenapi: option[" << i << "]=" << argv[i]; + } + if (option == "--print-hidden-api") { + print_hidden_api_ = true; + } else if (option.starts_with("--dex=")) { + dex_paths_.push_back(option.substr(strlen("--dex=")).ToString()); + } else if (option.starts_with("--light-greylist=")) { + light_greylist_path_ = option.substr(strlen("--light-greylist=")).ToString(); + } else if (option.starts_with("--dark-greylist=")) { + dark_greylist_path_ = option.substr(strlen("--dark-greylist=")).ToString(); + } else if (option.starts_with("--blacklist=")) { + blacklist_path_ = option.substr(strlen("--blacklist=")).ToString(); + } else { + Usage("Unknown argument '%s'", option.data()); + } + } + } + + bool ProcessDexFiles() { + if (dex_paths_.empty()) { + Usage("No DEX files specified"); + } + + if (light_greylist_path_.empty() && dark_greylist_path_.empty() && blacklist_path_.empty()) { + Usage("No API file specified"); + } + + if (!light_greylist_path_.empty() && !OpenApiFile(light_greylist_path_, &light_greylist_)) { + return false; + } + + if (!dark_greylist_path_.empty() && !OpenApiFile(dark_greylist_path_, &dark_greylist_)) { + return false; + } + + if (!blacklist_path_.empty() && !OpenApiFile(blacklist_path_, &blacklist_)) { + return false; + } + + MemMap::Init(); + if (!OpenDexFiles()) { + return false; + } + + DCHECK(!dex_files_.empty()); + for (auto& dex_file : dex_files_) { + CategorizeAllClasses(*dex_file.get()); + } + + UpdateDexChecksums(); + return true; + } + + private: + bool OpenApiFile(const std::string& path, std::unordered_set<std::string>* list) { + DCHECK(list->empty()); + DCHECK(!path.empty()); + + std::ifstream api_file(path, std::ifstream::in); + if (api_file.fail()) { + LOG(ERROR) << "Unable to open file '" << path << "' " << strerror(errno); + return false; + } + + for (std::string line; std::getline(api_file, line);) { + list->insert(line); + } + + api_file.close(); + return true; + } + + bool OpenDexFiles() { + ArtDexFileLoader dex_loader; + DCHECK(dex_files_.empty()); + + for (const std::string& filename : dex_paths_) { + std::string error_msg; + + File fd(filename.c_str(), O_RDWR, /* check_usage */ false); + if (fd.Fd() == -1) { + LOG(ERROR) << "Unable to open file '" << filename << "': " << strerror(errno); + return false; + } + + // Memory-map the dex file with MAP_SHARED flag so that changes in memory + // propagate to the underlying file. We run dex file verification as if + // the dex file was not in boot claass path to check basic assumptions, + // such as that at most one of public/private/protected flag is set. + // We do those checks here and skip them when loading the processed file + // into boot class path. + std::unique_ptr<const DexFile> dex_file(dex_loader.OpenDex(fd.Release(), + /* location */ filename, + /* verify */ true, + /* verify_checksum */ true, + /* mmap_shared */ true, + &error_msg)); + if (dex_file.get() == nullptr) { + LOG(ERROR) << "Open failed for '" << filename << "' " << error_msg; + return false; + } + + if (!dex_file->IsStandardDexFile()) { + LOG(ERROR) << "Expected a standard dex file '" << filename << "'"; + return false; + } + + // Change the protection of the memory mapping to read-write. + if (!dex_file->EnableWrite()) { + LOG(ERROR) << "Failed to enable write permission for '" << filename << "'"; + return false; + } + + dex_files_.push_back(std::move(dex_file)); + } + return true; + } + + void CategorizeAllClasses(const DexFile& dex_file) { + for (uint32_t class_idx = 0; class_idx < dex_file.NumClassDefs(); ++class_idx) { + DexClass klass(dex_file, class_idx); + const uint8_t* klass_data = klass.GetData(); + if (klass_data == nullptr) { + continue; + } + + for (ClassDataItemIterator it(klass.GetDexFile(), klass_data); it.HasNext(); it.Next()) { + DexMember member(klass, it); + + // Catagorize member and overwrite its access flags. + // Note that if a member appears on multiple API lists, it will be categorized + // as the strictest. + bool is_hidden = true; + if (member.IsOnApiList(blacklist_)) { + member.SetHidden(HiddenApiAccessFlags::kBlacklist); + } else if (member.IsOnApiList(dark_greylist_)) { + member.SetHidden(HiddenApiAccessFlags::kDarkGreylist); + } else if (member.IsOnApiList(light_greylist_)) { + member.SetHidden(HiddenApiAccessFlags::kLightGreylist); + } else { + member.SetHidden(HiddenApiAccessFlags::kWhitelist); + is_hidden = false; + } + + if (print_hidden_api_ && is_hidden) { + std::cout << member.GetApiEntry() << std::endl; + } + } + } + } + + void UpdateDexChecksums() { + for (auto& dex_file : dex_files_) { + // Obtain a writeable pointer to the dex header. + DexFile::Header* header = const_cast<DexFile::Header*>(&dex_file->GetHeader()); + // Recalculate checksum and overwrite the value in the header. + header->checksum_ = dex_file->CalculateChecksum(); + } + } + + // Print signatures of APIs which have been grey-/blacklisted. + bool print_hidden_api_; + + // Paths to DEX files which should be processed. + std::vector<std::string> dex_paths_; + + // Paths to text files which contain the lists of API members. + std::string light_greylist_path_; + std::string dark_greylist_path_; + std::string blacklist_path_; + + // Opened DEX files. Note that these are opened as `const` but eventually will be written into. + std::vector<std::unique_ptr<const DexFile>> dex_files_; + + // Signatures of DEX members loaded from `light_greylist_path_`, `dark_greylist_path_`, + // `blacklist_path_`. + std::unordered_set<std::string> light_greylist_; + std::unordered_set<std::string> dark_greylist_; + std::unordered_set<std::string> blacklist_; +}; + +} // namespace art + +int main(int argc, char** argv) { + art::HiddenApi hiddenapi; + + // Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError. + hiddenapi.ParseArgs(argc, argv); + return hiddenapi.ProcessDexFiles() ? EXIT_SUCCESS : EXIT_FAILURE; +} |