263 lines
8.8 KiB
C++
263 lines
8.8 KiB
C++
//
|
|
// 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 <action.h>
|
|
#include <action_manager.h>
|
|
#include <action_parser.h>
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/result.h>
|
|
#include <android-base/strings.h>
|
|
#include <apex_file.h>
|
|
#include <builtins.h>
|
|
#include <getopt.h>
|
|
#include <parser.h>
|
|
#include <pwd.h>
|
|
#include <service_list.h>
|
|
#include <service_parser.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <iostream>
|
|
|
|
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<std::string> partitions = {"system", "system_ext",
|
|
"product", "vendor", "odm"};
|
|
|
|
void PrintUsage() {
|
|
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.
|
|
--sdk_version=INT The active system SDK version used when filtering versioned
|
|
init.rc files.
|
|
--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.
|
|
)");
|
|
}
|
|
|
|
const android::init::BuiltinFunctionMap& ApexInitRcSupportedActionMap() {
|
|
static const android::init::BuiltinFunctionMap functions = {
|
|
// Add any init actions supported inside APEXes here.
|
|
// See system/core/init/builtins.cpp for expected syntax.
|
|
};
|
|
return functions;
|
|
}
|
|
|
|
// Validate any init rc files inside the APEX.
|
|
void CheckInitRc(const std::string& apex_dir, const ApexManifest& manifest,
|
|
int sdk_version) {
|
|
init::Parser parser;
|
|
init::ServiceList service_list = init::ServiceList();
|
|
parser.AddSectionParser("service", std::make_unique<init::ServiceParser>(
|
|
&service_list, nullptr, std::nullopt));
|
|
const init::BuiltinFunctionMap& function_map = ApexInitRcSupportedActionMap();
|
|
init::Action::set_function_map(&function_map);
|
|
init::ActionManager action_manager = init::ActionManager();
|
|
parser.AddSectionParser(
|
|
"on", std::make_unique<init::ActionParser>(&action_manager, nullptr));
|
|
std::string init_dir_path = apex_dir + "/etc";
|
|
std::vector<std::string> init_configs;
|
|
std::unique_ptr<DIR, decltype(&closedir)> 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 :
|
|
parser.FilterVersionedConfigs(init_configs, sdk_version)) {
|
|
parser.ParseConfigFile(c);
|
|
}
|
|
|
|
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) {
|
|
LOG(FATAL) << "Failed to parse APEX init rc file(s)";
|
|
}
|
|
}
|
|
|
|
// Extract and validate a single APEX.
|
|
void ScanApex(const std::string& deapexer, const std::string& debugfs,
|
|
int sdk_version, const std::string& apex_path) {
|
|
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 + " --debugfs_path " + debugfs +
|
|
" 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;
|
|
}
|
|
|
|
CheckInitRc(extracted_apex_dir, manifest, sdk_version);
|
|
}
|
|
|
|
// 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 <PARTITON>/apex/ directories.
|
|
void ScanPartitionApexes(const std::string& deapexer,
|
|
const std::string& debugfs, int sdk_version,
|
|
const std::string& partition_dir) {
|
|
LOG(INFO) << "Scanning partition factory APEX dir " << partition_dir;
|
|
|
|
std::unique_ptr<DIR, decltype(&closedir)> 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, debugfs, sdk_version,
|
|
partition_dir + "/" + entry->d_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::StdioLogger);
|
|
|
|
std::string deapexer, debugfs;
|
|
int sdk_version = INT_MAX;
|
|
std::map<std::string, std::string> partition_map;
|
|
|
|
while (true) {
|
|
static const struct option long_options[] = {
|
|
{"help", no_argument, nullptr, 'h'},
|
|
{"deapexer", required_argument, nullptr, 0},
|
|
{"debugfs", 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},
|
|
{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:
|
|
if (long_options[option_index].name == "deapexer") {
|
|
deapexer = optarg;
|
|
}
|
|
if (long_options[option_index].name == "debugfs") {
|
|
debugfs = optarg;
|
|
}
|
|
if (long_options[option_index].name == "sdk_version") {
|
|
if (!base::ParseInt(optarg, &sdk_version)) {
|
|
PrintUsage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
for (const auto& p : partitions) {
|
|
if (long_options[option_index].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 || deapexer.empty() || debugfs.empty()) {
|
|
PrintUsage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
for (const auto& p : partition_map) {
|
|
ScanPartitionApexes(deapexer, debugfs, sdk_version, p.second);
|
|
}
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
} // namespace apex
|
|
} // namespace android
|
|
|
|
int main(int argc, char** argv) { return android::apex::main(argc, argv); }
|