// // Copyright (C) 2022 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using ::apex::proto::ApexManifest; // Fake getpwnam for host execution, used by the init::ServiceParser. passwd* getpwnam(const char*) { static char fake_buf[] = "fake"; static passwd fake_passwd = { .pw_name = fake_buf, .pw_dir = fake_buf, .pw_shell = fake_buf, .pw_uid = 123, .pw_gid = 123, }; return &fake_passwd; } namespace android { namespace apex { namespace { static const std::vector partitions = {"system", "system_ext", "product", "vendor", "odm"}; void PrintUsage(const std::string& msg = "") { if (msg != "") { std::cerr << "Error: " << msg << "\n"; } printf(R"(usage: host_apex_verifier [options] Tests APEX file(s) for correctness. Options: --deapexer=PATH Use the deapexer binary at this path when extracting APEXes. --debugfs=PATH Use the debugfs binary at this path when extracting APEXes. --fsckerofs=PATH Use the fsck.erofs binary at this path when extracting APEXes. --sdk_version=INT The active system SDK version used when filtering versioned init.rc files. for checking all APEXes: --out_system=DIR Path to the factory APEX directory for the system partition. --out_system_ext=DIR Path to the factory APEX directory for the system_ext partition. --out_product=DIR Path to the factory APEX directory for the product partition. --out_vendor=DIR Path to the factory APEX directory for the vendor partition. --out_odm=DIR Path to the factory APEX directory for the odm partition. for checking a single APEX: --apex=PATH Path to the target APEX. --partition_tag=[system|vendor|...] Partition for the target APEX. )"); } // Use this for better error message when unavailable keyword is used. class NotAvailableParser : public init::SectionParser { public: NotAvailableParser(const std::string& keyword) : keyword_(keyword) {} base::Result ParseSection(std::vector&&, const std::string&, int) override { return base::Error() << "'" << keyword_ << "' is not available."; } private: std::string keyword_; }; // Validate any init rc files inside the APEX. void CheckInitRc(const std::string& apex_dir, const ApexManifest& manifest, int sdk_version, bool is_vendor) { init::Parser parser; if (is_vendor) { init::InitializeHostSubcontext({apex_dir}); } init::ServiceList service_list = init::ServiceList(); parser.AddSectionParser("service", std::make_unique( &service_list, init::GetSubcontext())); const init::BuiltinFunctionMap& function_map = init::GetBuiltinFunctionMap(); init::Action::set_function_map(&function_map); init::ActionManager action_manager = init::ActionManager(); if (is_vendor) { parser.AddSectionParser("on", std::make_unique( &action_manager, init::GetSubcontext())); } else { // "on" keyword is not available in non-vendor APEXes. parser.AddSectionParser("on", std::make_unique("on")); } std::string init_dir_path = apex_dir + "/etc"; std::vector init_configs; std::unique_ptr init_dir( opendir(init_dir_path.c_str()), closedir); if (init_dir) { dirent* entry; while ((entry = readdir(init_dir.get()))) { if (base::EndsWith(entry->d_name, "rc")) { init_configs.push_back(init_dir_path + "/" + entry->d_name); } } } // TODO(b/225380016): Extend this tool to check all init.rc files // in the APEX, possibly including different requirements depending // on the SDK version. for (const auto& c : init::FilterVersionedConfigs(init_configs, sdk_version)) { auto result = parser.ParseConfigFile(c); if (!result.ok()) { LOG(FATAL) << result.error(); } } for (const auto& service : service_list) { // Ensure the service path points inside this APEX. auto service_path = service->args()[0]; if (!base::StartsWith(service_path, "/apex/" + manifest.name())) { LOG(FATAL) << "Service " << service->name() << " has path outside of the APEX: " << service_path; } LOG(INFO) << service->name() << ": " << service_path; } // The parser will fail if there are any unsupported actions. if (parser.parse_error_count() > 0) { exit(EXIT_FAILURE); } } // Extract and validate a single APEX. void ScanApex(const std::string& deapexer, int sdk_version, const std::string& apex_path, const std::string& partition_tag) { LOG(INFO) << "Checking APEX " << apex_path; auto apex = OR_FATAL(ApexFile::Open(apex_path)); ApexManifest manifest = apex.GetManifest(); auto extracted_apex = TemporaryDir(); std::string extracted_apex_dir = extracted_apex.path; std::string deapexer_command = deapexer + " extract " + apex_path + " " + extracted_apex_dir; auto code = system(deapexer_command.c_str()); if (code != 0) { LOG(FATAL) << "Error running deapexer command \"" << deapexer_command << "\": " << code; } bool is_vendor = partition_tag == "vendor" || partition_tag == "odm"; CheckInitRc(extracted_apex_dir, manifest, sdk_version, is_vendor); } // Scan the factory APEX files in the partition apex dir. // Scans APEX files directly, rather than flattened ${PRODUCT_OUT}/apex/ // directories. This allows us to check: // - Prebuilt APEXes which do not flatten to that path. // - Multi-installed APEXes, where only the default // APEX may flatten to that path. // - Extracted target_files archives which may not contain // flattened /apex/ directories. void ScanPartitionApexes(const std::string& deapexer, int sdk_version, const std::string& partition_dir, const std::string& partition_tag) { LOG(INFO) << "Scanning " << partition_dir << " for factory APEXes in " << partition_tag; std::unique_ptr apex_dir( opendir(partition_dir.c_str()), closedir); if (!apex_dir) { LOG(WARNING) << "Unable to open dir " << partition_dir; return; } dirent* entry; while ((entry = readdir(apex_dir.get()))) { if (base::EndsWith(entry->d_name, ".apex") || base::EndsWith(entry->d_name, ".capex")) { ScanApex(deapexer, sdk_version, partition_dir + "/" + entry->d_name, partition_tag); } } } } // namespace int main(int argc, char** argv) { android::base::SetMinimumLogSeverity(android::base::ERROR); android::base::InitLogging(argv, &android::base::StdioLogger); std::string deapexer, debugfs, fsckerofs; // Use ANDROID_HOST_OUT for convenience const char* host_out = getenv("ANDROID_HOST_OUT"); if (host_out) { deapexer = std::string(host_out) + "/bin/deapexer"; debugfs = std::string(host_out) + "/bin/debugfs_static"; fsckerofs = std::string(host_out) + "/bin/fsck.erofs"; } int sdk_version = INT_MAX; std::map partition_map; std::string apex; std::string partition_tag; while (true) { static const struct option long_options[] = { {"help", no_argument, nullptr, 'h'}, {"deapexer", required_argument, nullptr, 0}, {"debugfs", required_argument, nullptr, 0}, {"fsckerofs", required_argument, nullptr, 0}, {"sdk_version", required_argument, nullptr, 0}, {"out_system", required_argument, nullptr, 0}, {"out_system_ext", required_argument, nullptr, 0}, {"out_product", required_argument, nullptr, 0}, {"out_vendor", required_argument, nullptr, 0}, {"out_odm", required_argument, nullptr, 0}, {"apex", required_argument, nullptr, 0}, {"partition_tag", required_argument, nullptr, 0}, {nullptr, 0, nullptr, 0}, }; int option_index; int arg = getopt_long(argc, argv, "h", long_options, &option_index); if (arg == -1) { break; } switch (arg) { case 0: { std::string_view name(long_options[option_index].name); if (name == "deapexer") { deapexer = optarg; } if (name == "debugfs") { debugfs = optarg; } if (name == "fsckerofs") { fsckerofs = optarg; } if (name == "sdk_version") { if (!base::ParseInt(optarg, &sdk_version)) { PrintUsage(); return EXIT_FAILURE; } } if (name == "apex") { apex = optarg; } if (name == "partition_tag") { partition_tag = optarg; } for (const auto& p : partitions) { if (name == "out_" + p) { partition_map[p] = optarg; } } break; } case 'h': PrintUsage(); return EXIT_SUCCESS; default: LOG(ERROR) << "getopt returned invalid result: " << arg; return EXIT_FAILURE; } } argc -= optind; argv += optind; if (argc != 0) { PrintUsage(); return EXIT_FAILURE; } if (deapexer.empty() || debugfs.empty() || fsckerofs.empty()) { PrintUsage(); return EXIT_FAILURE; } deapexer += " --debugfs_path " + debugfs; deapexer += " --fsckerofs_path " + fsckerofs; if (!!apex.empty() + !!partition_map.empty() != 1) { PrintUsage("use either --apex or --out_.\n"); return EXIT_FAILURE; } if (!apex.empty()) { if (std::find(partitions.begin(), partitions.end(), partition_tag) == partitions.end()) { PrintUsage( "--apex should come with " "--partition_tag=[system|system_ext|product|vendor|odm].\n"); return EXIT_FAILURE; } } if (!partition_map.empty()) { for (const auto& [partition, dir] : partition_map) { ScanPartitionApexes(deapexer, sdk_version, dir, partition); } } else { ScanApex(deapexer, sdk_version, apex, partition_tag); } return EXIT_SUCCESS; } } // namespace apex } // namespace android int main(int argc, char** argv) { return android::apex::main(argc, argv); }