xref: /aosp_15_r20/system/apex/tools/host_apex_verifier.cc (revision 33f3758387333dbd2962d7edbd98681940d895da)
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 <getopt.h>
27 #include <parser.h>
28 #include <pwd.h>
29 #include <service_list.h>
30 #include <service_parser.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 
34 #include <iostream>
35 #include <string_view>
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(const std::string & msg="")59 void PrintUsage(const std::string& msg = "") {
60   if (msg != "") {
61     std::cerr << "Error: " << msg << "\n";
62   }
63   printf(R"(usage: host_apex_verifier [options]
64 
65 Tests APEX file(s) for correctness.
66 
67 Options:
68   --deapexer=PATH             Use the deapexer binary at this path when extracting APEXes.
69   --debugfs=PATH              Use the debugfs binary at this path when extracting APEXes.
70   --fsckerofs=PATH            Use the fsck.erofs binary at this path when extracting APEXes.
71   --sdk_version=INT           The active system SDK version used when filtering versioned
72                               init.rc files.
73 for checking all APEXes:
74   --out_system=DIR            Path to the factory APEX directory for the system partition.
75   --out_system_ext=DIR        Path to the factory APEX directory for the system_ext partition.
76   --out_product=DIR           Path to the factory APEX directory for the product partition.
77   --out_vendor=DIR            Path to the factory APEX directory for the vendor partition.
78   --out_odm=DIR               Path to the factory APEX directory for the odm partition.
79 
80 for checking a single APEX:
81   --apex=PATH                 Path to the target APEX.
82   --partition_tag=[system|vendor|...] Partition for the target APEX.
83 )");
84 }
85 
86 // Use this for better error message when unavailable keyword is used.
87 class NotAvailableParser : public init::SectionParser {
88  public:
NotAvailableParser(const std::string & keyword)89   NotAvailableParser(const std::string& keyword) : keyword_(keyword) {}
ParseSection(std::vector<std::string> &&,const std::string &,int)90   base::Result<void> ParseSection(std::vector<std::string>&&,
91                                   const std::string&, int) override {
92     return base::Error() << "'" << keyword_ << "' is not available.";
93   }
94 
95  private:
96   std::string keyword_;
97 };
98 
99 // Validate any init rc files inside the APEX.
CheckInitRc(const std::string & apex_dir,const ApexManifest & manifest,int sdk_version,bool is_vendor)100 void CheckInitRc(const std::string& apex_dir, const ApexManifest& manifest,
101                  int sdk_version, bool is_vendor) {
102   init::Parser parser;
103   if (is_vendor) {
104     init::InitializeHostSubcontext({apex_dir});
105   }
106   init::ServiceList service_list = init::ServiceList();
107   parser.AddSectionParser("service", std::make_unique<init::ServiceParser>(
108                                          &service_list, init::GetSubcontext()));
109   const init::BuiltinFunctionMap& function_map = init::GetBuiltinFunctionMap();
110   init::Action::set_function_map(&function_map);
111   init::ActionManager action_manager = init::ActionManager();
112   if (is_vendor) {
113     parser.AddSectionParser("on", std::make_unique<init::ActionParser>(
114                                       &action_manager, init::GetSubcontext()));
115   } else {
116     // "on" keyword is not available in non-vendor APEXes.
117     parser.AddSectionParser("on", std::make_unique<NotAvailableParser>("on"));
118   }
119 
120   std::string init_dir_path = apex_dir + "/etc";
121   std::vector<std::string> init_configs;
122   std::unique_ptr<DIR, decltype(&closedir)> init_dir(
123       opendir(init_dir_path.c_str()), closedir);
124   if (init_dir) {
125     dirent* entry;
126     while ((entry = readdir(init_dir.get()))) {
127       if (base::EndsWith(entry->d_name, "rc")) {
128         init_configs.push_back(init_dir_path + "/" + entry->d_name);
129       }
130     }
131   }
132   // TODO(b/225380016): Extend this tool to check all init.rc files
133   // in the APEX, possibly including different requirements depending
134   // on the SDK version.
135   for (const auto& c :
136        init::FilterVersionedConfigs(init_configs, sdk_version)) {
137     auto result = parser.ParseConfigFile(c);
138     if (!result.ok()) {
139       LOG(FATAL) << result.error();
140     }
141   }
142 
143   for (const auto& service : service_list) {
144     // Ensure the service path points inside this APEX.
145     auto service_path = service->args()[0];
146     if (!base::StartsWith(service_path, "/apex/" + manifest.name())) {
147       LOG(FATAL) << "Service " << service->name()
148                  << " has path outside of the APEX: " << service_path;
149     }
150     LOG(INFO) << service->name() << ": " << service_path;
151   }
152 
153   // The parser will fail if there are any unsupported actions.
154   if (parser.parse_error_count() > 0) {
155     exit(EXIT_FAILURE);
156   }
157 }
158 
159 // Extract and validate a single APEX.
ScanApex(const std::string & deapexer,int sdk_version,const std::string & apex_path,const std::string & partition_tag)160 void ScanApex(const std::string& deapexer, int sdk_version,
161               const std::string& apex_path, const std::string& partition_tag) {
162   LOG(INFO) << "Checking APEX " << apex_path;
163 
164   auto apex = OR_FATAL(ApexFile::Open(apex_path));
165   ApexManifest manifest = apex.GetManifest();
166 
167   auto extracted_apex = TemporaryDir();
168   std::string extracted_apex_dir = extracted_apex.path;
169   std::string deapexer_command =
170       deapexer + " extract " + apex_path + " " + extracted_apex_dir;
171   auto code = system(deapexer_command.c_str());
172   if (code != 0) {
173     LOG(FATAL) << "Error running deapexer command \"" << deapexer_command
174                << "\": " << code;
175   }
176   bool is_vendor = partition_tag == "vendor" || partition_tag == "odm";
177   CheckInitRc(extracted_apex_dir, manifest, sdk_version, is_vendor);
178 }
179 
180 // Scan the factory APEX files in the partition apex dir.
181 // Scans APEX files directly, rather than flattened ${PRODUCT_OUT}/apex/
182 // directories. This allows us to check:
183 //   - Prebuilt APEXes which do not flatten to that path.
184 //   - Multi-installed APEXes, where only the default
185 //     APEX may flatten to that path.
186 //   - Extracted target_files archives which may not contain
187 //     flattened <PARTITON>/apex/ directories.
ScanPartitionApexes(const std::string & deapexer,int sdk_version,const std::string & partition_dir,const std::string & partition_tag)188 void ScanPartitionApexes(const std::string& deapexer, int sdk_version,
189                          const std::string& partition_dir,
190                          const std::string& partition_tag) {
191   LOG(INFO) << "Scanning " << partition_dir << " for factory APEXes in "
192             << partition_tag;
193 
194   std::unique_ptr<DIR, decltype(&closedir)> apex_dir(
195       opendir(partition_dir.c_str()), closedir);
196   if (!apex_dir) {
197     LOG(WARNING) << "Unable to open dir " << partition_dir;
198     return;
199   }
200 
201   dirent* entry;
202   while ((entry = readdir(apex_dir.get()))) {
203     if (base::EndsWith(entry->d_name, ".apex") ||
204         base::EndsWith(entry->d_name, ".capex")) {
205       ScanApex(deapexer, sdk_version, partition_dir + "/" + entry->d_name,
206                partition_tag);
207     }
208   }
209 }
210 
211 }  // namespace
212 
main(int argc,char ** argv)213 int main(int argc, char** argv) {
214   android::base::SetMinimumLogSeverity(android::base::ERROR);
215   android::base::InitLogging(argv, &android::base::StdioLogger);
216 
217   std::string deapexer, debugfs, fsckerofs;
218   // Use ANDROID_HOST_OUT for convenience
219   const char* host_out = getenv("ANDROID_HOST_OUT");
220   if (host_out) {
221     deapexer = std::string(host_out) + "/bin/deapexer";
222     debugfs = std::string(host_out) + "/bin/debugfs_static";
223     fsckerofs = std::string(host_out) + "/bin/fsck.erofs";
224   }
225   int sdk_version = INT_MAX;
226   std::map<std::string, std::string> partition_map;
227   std::string apex;
228   std::string partition_tag;
229 
230   while (true) {
231     static const struct option long_options[] = {
232         {"help", no_argument, nullptr, 'h'},
233         {"deapexer", required_argument, nullptr, 0},
234         {"debugfs", required_argument, nullptr, 0},
235         {"fsckerofs", required_argument, nullptr, 0},
236         {"sdk_version", required_argument, nullptr, 0},
237         {"out_system", required_argument, nullptr, 0},
238         {"out_system_ext", required_argument, nullptr, 0},
239         {"out_product", required_argument, nullptr, 0},
240         {"out_vendor", required_argument, nullptr, 0},
241         {"out_odm", required_argument, nullptr, 0},
242         {"apex", required_argument, nullptr, 0},
243         {"partition_tag", required_argument, nullptr, 0},
244         {nullptr, 0, nullptr, 0},
245     };
246 
247     int option_index;
248     int arg = getopt_long(argc, argv, "h", long_options, &option_index);
249 
250     if (arg == -1) {
251       break;
252     }
253 
254     switch (arg) {
255       case 0: {
256         std::string_view name(long_options[option_index].name);
257         if (name == "deapexer") {
258           deapexer = optarg;
259         }
260         if (name == "debugfs") {
261           debugfs = optarg;
262         }
263         if (name == "fsckerofs") {
264           fsckerofs = optarg;
265         }
266         if (name == "sdk_version") {
267           if (!base::ParseInt(optarg, &sdk_version)) {
268             PrintUsage();
269             return EXIT_FAILURE;
270           }
271         }
272         if (name == "apex") {
273           apex = optarg;
274         }
275         if (name == "partition_tag") {
276           partition_tag = optarg;
277         }
278         for (const auto& p : partitions) {
279           if (name == "out_" + p) {
280             partition_map[p] = optarg;
281           }
282         }
283         break;
284       }
285       case 'h':
286         PrintUsage();
287         return EXIT_SUCCESS;
288       default:
289         LOG(ERROR) << "getopt returned invalid result: " << arg;
290         return EXIT_FAILURE;
291     }
292   }
293 
294   argc -= optind;
295   argv += optind;
296 
297   if (argc != 0) {
298     PrintUsage();
299     return EXIT_FAILURE;
300   }
301   if (deapexer.empty() || debugfs.empty() || fsckerofs.empty()) {
302     PrintUsage();
303     return EXIT_FAILURE;
304   }
305   deapexer += " --debugfs_path " + debugfs;
306   deapexer += " --fsckerofs_path " + fsckerofs;
307 
308   if (!!apex.empty() + !!partition_map.empty() != 1) {
309     PrintUsage("use either --apex or --out_<partition>.\n");
310     return EXIT_FAILURE;
311   }
312   if (!apex.empty()) {
313     if (std::find(partitions.begin(), partitions.end(), partition_tag) ==
314         partitions.end()) {
315       PrintUsage(
316           "--apex should come with "
317           "--partition_tag=[system|system_ext|product|vendor|odm].\n");
318       return EXIT_FAILURE;
319     }
320   }
321 
322   if (!partition_map.empty()) {
323     for (const auto& [partition, dir] : partition_map) {
324       ScanPartitionApexes(deapexer, sdk_version, dir, partition);
325     }
326   } else {
327     ScanApex(deapexer, sdk_version, apex, partition_tag);
328   }
329   return EXIT_SUCCESS;
330 }
331 
332 }  // namespace apex
333 }  // namespace android
334 
main(int argc,char ** argv)335 int main(int argc, char** argv) { return android::apex::main(argc, argv); }
336