1 //
2 // Copyright (C) 2022 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include <action.h>
18 #include <action_manager.h>
19 #include <action_parser.h>
20 #include <android-base/file.h>
21 #include <android-base/logging.h>
22 #include <android-base/parseint.h>
23 #include <android-base/result.h>
24 #include <android-base/strings.h>
25 #include <apex_file.h>
26 #include <builtins.h>
27 #include <getopt.h>
28 #include <parser.h>
29 #include <pwd.h>
30 #include <service_list.h>
31 #include <service_parser.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34
35 #include <iostream>
36
37 using ::apex::proto::ApexManifest;
38
39 // Fake getpwnam for host execution, used by the init::ServiceParser.
getpwnam(const char *)40 passwd* getpwnam(const char*) {
41 static char fake_buf[] = "fake";
42 static passwd fake_passwd = {
43 .pw_name = fake_buf,
44 .pw_dir = fake_buf,
45 .pw_shell = fake_buf,
46 .pw_uid = 123,
47 .pw_gid = 123,
48 };
49 return &fake_passwd;
50 }
51
52 namespace android {
53 namespace apex {
54 namespace {
55
56 static const std::vector<std::string> partitions = {"system", "system_ext",
57 "product", "vendor", "odm"};
58
PrintUsage()59 void PrintUsage() {
60 printf(R"(usage: host_apex_verifier [options]
61
62 Tests APEX file(s) for correctness.
63
64 Options:
65 --deapexer=PATH Use the deapexer binary at this path when extracting APEXes.
66 --debugfs=PATH Use the debugfs binary at this path when extracting APEXes.
67 --sdk_version=INT The active system SDK version used when filtering versioned
68 init.rc files.
69 --out_system=DIR Path to the factory APEX directory for the system partition.
70 --out_system_ext=DIR Path to the factory APEX directory for the system_ext partition.
71 --out_product=DIR Path to the factory APEX directory for the product partition.
72 --out_vendor=DIR Path to the factory APEX directory for the vendor partition.
73 --out_odm=DIR Path to the factory APEX directory for the odm partition.
74 )");
75 }
76
ApexInitRcSupportedActionMap()77 const android::init::BuiltinFunctionMap& ApexInitRcSupportedActionMap() {
78 static const android::init::BuiltinFunctionMap functions = {
79 // Add any init actions supported inside APEXes here.
80 // See system/core/init/builtins.cpp for expected syntax.
81 };
82 return functions;
83 }
84
85 // Validate any init rc files inside the APEX.
CheckInitRc(const std::string & apex_dir,const ApexManifest & manifest,int sdk_version)86 void CheckInitRc(const std::string& apex_dir, const ApexManifest& manifest,
87 int sdk_version) {
88 init::Parser parser;
89 init::ServiceList service_list = init::ServiceList();
90 parser.AddSectionParser("service", std::make_unique<init::ServiceParser>(
91 &service_list, nullptr, std::nullopt));
92 const init::BuiltinFunctionMap& function_map = ApexInitRcSupportedActionMap();
93 init::Action::set_function_map(&function_map);
94 init::ActionManager action_manager = init::ActionManager();
95 parser.AddSectionParser(
96 "on", std::make_unique<init::ActionParser>(&action_manager, nullptr));
97 std::string init_dir_path = apex_dir + "/etc";
98 std::vector<std::string> init_configs;
99 std::unique_ptr<DIR, decltype(&closedir)> init_dir(
100 opendir(init_dir_path.c_str()), closedir);
101 if (init_dir) {
102 dirent* entry;
103 while ((entry = readdir(init_dir.get()))) {
104 if (base::EndsWith(entry->d_name, "rc")) {
105 init_configs.push_back(init_dir_path + "/" + entry->d_name);
106 }
107 }
108 }
109 // TODO(b/225380016): Extend this tool to check all init.rc files
110 // in the APEX, possibly including different requirements depending
111 // on the SDK version.
112 for (const auto& c :
113 parser.FilterVersionedConfigs(init_configs, sdk_version)) {
114 parser.ParseConfigFile(c);
115 }
116
117 for (const auto& service : service_list) {
118 // Ensure the service path points inside this APEX.
119 auto service_path = service->args()[0];
120 if (!base::StartsWith(service_path, "/apex/" + manifest.name())) {
121 LOG(FATAL) << "Service " << service->name()
122 << " has path outside of the APEX: " << service_path;
123 }
124 LOG(INFO) << service->name() << ": " << service_path;
125 }
126
127 // The parser will fail if there are any unsupported actions.
128 if (parser.parse_error_count() > 0) {
129 LOG(FATAL) << "Failed to parse APEX init rc file(s)";
130 }
131 }
132
133 // Extract and validate a single APEX.
ScanApex(const std::string & deapexer,const std::string & debugfs,int sdk_version,const std::string & apex_path)134 void ScanApex(const std::string& deapexer, const std::string& debugfs,
135 int sdk_version, const std::string& apex_path) {
136 LOG(INFO) << "Checking APEX " << apex_path;
137
138 auto apex = OR_FATAL(ApexFile::Open(apex_path));
139 ApexManifest manifest = apex.GetManifest();
140
141 auto extracted_apex = TemporaryDir();
142 std::string extracted_apex_dir = extracted_apex.path;
143 std::string deapexer_command = deapexer + " --debugfs_path " + debugfs +
144 " extract " + apex_path + " " +
145 extracted_apex_dir;
146 auto code = system(deapexer_command.c_str());
147 if (code != 0) {
148 LOG(FATAL) << "Error running deapexer command \"" << deapexer_command
149 << "\": " << code;
150 }
151
152 CheckInitRc(extracted_apex_dir, manifest, sdk_version);
153 }
154
155 // Scan the factory APEX files in the partition apex dir.
156 // Scans APEX files directly, rather than flattened ${PRODUCT_OUT}/apex/
157 // directories. This allows us to check:
158 // - Prebuilt APEXes which do not flatten to that path.
159 // - Multi-installed APEXes, where only the default
160 // APEX may flatten to that path.
161 // - Extracted target_files archives which may not contain
162 // flattened <PARTITON>/apex/ directories.
ScanPartitionApexes(const std::string & deapexer,const std::string & debugfs,int sdk_version,const std::string & partition_dir)163 void ScanPartitionApexes(const std::string& deapexer,
164 const std::string& debugfs, int sdk_version,
165 const std::string& partition_dir) {
166 LOG(INFO) << "Scanning partition factory APEX dir " << partition_dir;
167
168 std::unique_ptr<DIR, decltype(&closedir)> apex_dir(
169 opendir(partition_dir.c_str()), closedir);
170 if (!apex_dir) {
171 LOG(WARNING) << "Unable to open dir " << partition_dir;
172 return;
173 }
174
175 dirent* entry;
176 while ((entry = readdir(apex_dir.get()))) {
177 if (base::EndsWith(entry->d_name, ".apex") ||
178 base::EndsWith(entry->d_name, ".capex")) {
179 ScanApex(deapexer, debugfs, sdk_version,
180 partition_dir + "/" + entry->d_name);
181 }
182 }
183 }
184
185 } // namespace
186
main(int argc,char ** argv)187 int main(int argc, char** argv) {
188 android::base::InitLogging(argv, &android::base::StdioLogger);
189
190 std::string deapexer, debugfs;
191 int sdk_version = INT_MAX;
192 std::map<std::string, std::string> partition_map;
193
194 while (true) {
195 static const struct option long_options[] = {
196 {"help", no_argument, nullptr, 'h'},
197 {"deapexer", required_argument, nullptr, 0},
198 {"debugfs", required_argument, nullptr, 0},
199 {"sdk_version", required_argument, nullptr, 0},
200 {"out_system", required_argument, nullptr, 0},
201 {"out_system_ext", required_argument, nullptr, 0},
202 {"out_product", required_argument, nullptr, 0},
203 {"out_vendor", required_argument, nullptr, 0},
204 {"out_odm", required_argument, nullptr, 0},
205 {nullptr, 0, nullptr, 0},
206 };
207
208 int option_index;
209 int arg = getopt_long(argc, argv, "h", long_options, &option_index);
210
211 if (arg == -1) {
212 break;
213 }
214
215 switch (arg) {
216 case 0:
217 if (long_options[option_index].name == "deapexer") {
218 deapexer = optarg;
219 }
220 if (long_options[option_index].name == "debugfs") {
221 debugfs = optarg;
222 }
223 if (long_options[option_index].name == "sdk_version") {
224 if (!base::ParseInt(optarg, &sdk_version)) {
225 PrintUsage();
226 return EXIT_FAILURE;
227 }
228 }
229 for (const auto& p : partitions) {
230 if (long_options[option_index].name == "out_" + p) {
231 partition_map[p] = optarg;
232 }
233 }
234 break;
235 case 'h':
236 PrintUsage();
237 return EXIT_SUCCESS;
238 default:
239 LOG(ERROR) << "getopt returned invalid result: " << arg;
240 return EXIT_FAILURE;
241 }
242 }
243
244 argc -= optind;
245 argv += optind;
246
247 if (argc != 0 || deapexer.empty() || debugfs.empty()) {
248 PrintUsage();
249 return EXIT_FAILURE;
250 }
251
252 for (const auto& p : partition_map) {
253 ScanPartitionApexes(deapexer, debugfs, sdk_version, p.second);
254 }
255
256 return EXIT_SUCCESS;
257 }
258
259 } // namespace apex
260 } // namespace android
261
main(int argc,char ** argv)262 int main(int argc, char** argv) { return android::apex::main(argc, argv); }
263