| /* |
| * 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); |
| DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(ptr), it_.GetMethodCodeItemOffset()); |
| } |
| ptr = ReverseSearchUnsignedLeb128(ptr); |
| DCHECK_EQ(DecodeUnsignedLeb128WithoutMovingCursor(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()); |
| } |
| |
| 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; |
| } |