1 //
2 // Copyright (C) 2023 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 #include <fcntl.h>
17 
18 #include <algorithm>
19 #include <map>
20 #include <set>
21 #include <sstream>
22 #include <string>
23 #include <vector>
24 
25 #include <android-base/file.h>
26 #include <android-base/logging.h>
27 #include <android-base/stringprintf.h>
28 #include <android-base/strings.h>
29 #include <fmt/format.h>
30 
31 #include "common/libs/fs/shared_buf.h"
32 #include "common/libs/fs/shared_fd.h"
33 #include "common/libs/utils/contains.h"
34 #include "common/libs/utils/environment.h"
35 #include "common/libs/utils/files.h"
36 #include "common/libs/utils/subprocess.h"
37 #include "host/commands/assemble_cvd/boot_image_utils.h"
38 #include "host/commands/assemble_cvd/kernel_module_parser.h"
39 #include "host/libs/config/config_utils.h"
40 #include "host/libs/config/known_paths.h"
41 
42 namespace cuttlefish {
43 
44 namespace {
45 
RoundDown(size_t a,size_t divisor)46 constexpr size_t RoundDown(size_t a, size_t divisor) {
47   return a / divisor * divisor;
48 }
49 
RoundUp(size_t a,size_t divisor)50 constexpr size_t RoundUp(size_t a, size_t divisor) {
51   return RoundDown(a + divisor, divisor);
52 }
53 
54 template <typename Container>
WriteLinesToFile(const Container & lines,const char * path)55 bool WriteLinesToFile(const Container& lines, const char* path) {
56   android::base::unique_fd fd(
57       open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640));
58   if (!fd.ok()) {
59     PLOG(ERROR) << "Failed to open " << path;
60     return false;
61   }
62   for (const auto& line : lines) {
63     if (!android::base::WriteFully(fd, line.data(), line.size())) {
64       PLOG(ERROR) << "Failed to write to " << path;
65       return false;
66     }
67     const char c = '\n';
68     if (write(fd.get(), &c, 1) != 1) {
69       PLOG(ERROR) << "Failed to write to " << path;
70       return false;
71     }
72   }
73   return true;
74 }
75 
76 // Generate a filesystem_config.txt for all files in |fs_root|
WriteFsConfig(const char * output_path,const std::string & fs_root,const std::string & mount_point)77 bool WriteFsConfig(const char* output_path, const std::string& fs_root,
78                    const std::string& mount_point) {
79   android::base::unique_fd fd(
80       open(output_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
81   if (!fd.ok()) {
82     PLOG(ERROR) << "Failed to open " << output_path;
83     return false;
84   }
85   if (!android::base::WriteStringToFd(
86           " 0 0 755 selabel=u:object_r:rootfs:s0 capabilities=0x0\n", fd)) {
87     PLOG(ERROR) << "Failed to write to " << output_path;
88     return false;
89   }
90   WalkDirectory(fs_root, [&fd, &output_path, &mount_point,
91                           &fs_root](const std::string& file_path) {
92     const auto filename = file_path.substr(
93         fs_root.back() == '/' ? fs_root.size() : fs_root.size() + 1);
94     std::string fs_context = " 0 0 644 capabilities=0x0\n";
95     if (DirectoryExists(file_path)) {
96       fs_context = " 0 0 755 capabilities=0x0\n";
97     }
98     if (!android::base::WriteStringToFd(
99             mount_point + "/" + filename + fs_context, fd)) {
100       PLOG(ERROR) << "Failed to write to " << output_path;
101       return false;
102     }
103     return true;
104   });
105   return true;
106 }
107 
GetRamdiskModules(const std::vector<std::string> & all_modules)108 std::vector<std::string> GetRamdiskModules(
109     const std::vector<std::string>& all_modules) {
110   static constexpr auto kRamdiskModules = {
111       "failover.ko",
112       "nd_virtio.ko",
113       "net_failover.ko",
114       "virtio_blk.ko",
115       "virtio_console.ko",
116       "virtio_dma_buf.ko",
117       "virtio-gpu.ko",
118       "virtio_input.ko",
119       "virtio_net.ko",
120       "virtio_pci.ko",
121       "virtio_pci_legacy_dev.ko",
122       "virtio_pci_modern_dev.ko",
123       "virtio-rng.ko",
124       "vmw_vsock_virtio_transport.ko",
125       "vmw_vsock_virtio_transport_common.ko",
126       "vsock.ko",
127       // TODO(b/176860479) once virt_wifi is deprecated fully,
128       // these following modules can be loaded in second stage init
129       "libarc4.ko",
130       "rfkill.ko",
131       "cfg80211.ko",
132       "mac80211.ko",
133       "mac80211_hwsim.ko",
134   };
135   std::vector<std::string> ramdisk_modules;
136   for (const auto& mod_path : all_modules) {
137     if (Contains(kRamdiskModules, android::base::Basename(mod_path))) {
138       ramdisk_modules.emplace_back(mod_path);
139     }
140   }
141   return ramdisk_modules;
142 }
143 
144 // Filter the dependency map |deps| to only contain nodes in |allow_list|
FilterDependencies(const std::map<std::string,std::vector<std::string>> & deps,const std::set<std::string> & allow_list)145 std::map<std::string, std::vector<std::string>> FilterDependencies(
146     const std::map<std::string, std::vector<std::string>>& deps,
147     const std::set<std::string>& allow_list) {
148   std::map<std::string, std::vector<std::string>> new_deps;
149   for (const auto& [mod_name, children] : deps) {
150     if (!allow_list.count(mod_name)) {
151       continue;
152     }
153     new_deps[mod_name].clear();
154     for (const auto& child : children) {
155       if (!allow_list.count(child)) {
156         continue;
157       }
158       new_deps[mod_name].emplace_back(child);
159     }
160   }
161   return new_deps;
162 }
163 
FilterOutDependencies(const std::map<std::string,std::vector<std::string>> & deps,const std::set<std::string> & block_list)164 std::map<std::string, std::vector<std::string>> FilterOutDependencies(
165     const std::map<std::string, std::vector<std::string>>& deps,
166     const std::set<std::string>& block_list) {
167   std::map<std::string, std::vector<std::string>> new_deps;
168   for (const auto& [mod_name, children] : deps) {
169     if (block_list.count(mod_name)) {
170       continue;
171     }
172     new_deps[mod_name].clear();
173     for (const auto& child : children) {
174       if (block_list.count(child)) {
175         continue;
176       }
177       new_deps[mod_name].emplace_back(child);
178     }
179   }
180   return new_deps;
181 }
182 
183 // Update dependency map by prepending '/system/lib/modules' to modules which
184 // have been relocated to system_dlkm partition
UpdateGKIModulePaths(const std::map<std::string,std::vector<std::string>> & deps,const std::set<std::string> & gki_modules)185 std::map<std::string, std::vector<std::string>> UpdateGKIModulePaths(
186     const std::map<std::string, std::vector<std::string>>& deps,
187     const std::set<std::string>& gki_modules) {
188   std::map<std::string, std::vector<std::string>> new_deps;
189   auto&& GetNewModName = [gki_modules](auto&& mod_name) {
190     if (gki_modules.count(mod_name)) {
191       return "/system/lib/modules/" + mod_name;
192     } else {
193       return mod_name;
194     }
195   };
196   for (const auto& [old_mod_name, children] : deps) {
197     const auto mod_name = GetNewModName(old_mod_name);
198     new_deps[mod_name].clear();
199 
200     for (const auto& child : children) {
201       new_deps[mod_name].emplace_back(GetNewModName(child));
202     }
203   }
204   return new_deps;
205 }
206 
207 // Write dependency map to modules.dep file
WriteDepsToFile(const std::map<std::string,std::vector<std::string>> & deps,const std::string & output_path)208 bool WriteDepsToFile(
209     const std::map<std::string, std::vector<std::string>>& deps,
210     const std::string& output_path) {
211   std::stringstream ss;
212   for (const auto& [key, val] : deps) {
213     ss << key << ":";
214     for (const auto& dep : val) {
215       ss << " " << dep;
216     }
217     ss << "\n";
218   }
219   if (!android::base::WriteStringToFile(ss.str(), output_path)) {
220     PLOG(ERROR) << "Failed to write modules.dep to " << output_path;
221     return false;
222   }
223   return true;
224 }
225 
226 // Parse modules.dep into an in-memory data structure, key is path to a kernel
227 // module, value is all dependency modules
LoadModuleDeps(const std::string & filename)228 std::map<std::string, std::vector<std::string>> LoadModuleDeps(
229     const std::string& filename) {
230   std::map<std::string, std::vector<std::string>> dependency_map;
231   const auto dep_str = android::base::Trim(ReadFile(filename));
232   const auto dep_lines = android::base::Split(dep_str, "\n");
233   for (const auto& line : dep_lines) {
234     const auto mod_name = line.substr(0, line.find(":"));
235     auto deps = android::base::Tokenize(line.substr(mod_name.size() + 1), " ");
236     dependency_map[mod_name] = std::move(deps);
237   }
238 
239   return dependency_map;
240 }
241 
242 // Recursively compute all modules which |start_nodes| depend on
243 template <typename StringContainer>
ComputeTransitiveClosure(const StringContainer & start_nodes,const std::map<std::string,std::vector<std::string>> & dependencies)244 std::set<std::string> ComputeTransitiveClosure(
245     const StringContainer& start_nodes,
246     const std::map<std::string, std::vector<std::string>>& dependencies) {
247   std::deque<std::string> queue(start_nodes.begin(), start_nodes.end());
248   std::set<std::string> visited;
249   while (!queue.empty()) {
250     const auto cur = queue.front();
251     queue.pop_front();
252     if (visited.find(cur) != visited.end()) {
253       continue;
254     }
255     visited.insert(cur);
256     const auto it = dependencies.find(cur);
257     if (it == dependencies.end()) {
258       continue;
259     }
260     for (const auto& dep : it->second) {
261       queue.emplace_back(dep);
262     }
263   }
264   return visited;
265 }
266 
267 // Generate a file_context.bin file which can be used by selinux tools to assign
268 // selinux labels to files
GenerateFileContexts(const std::string & output_path,std::string_view mount_point,std::string_view file_label)269 Result<void> GenerateFileContexts(const std::string& output_path,
270                                   std::string_view mount_point,
271                                   std::string_view file_label) {
272   const std::string file_contexts_txt = output_path + ".txt";
273   SharedFD fd = SharedFD::Open(file_contexts_txt,
274                                O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
275   CF_EXPECTF(fd->IsOpen(), "Can't open '{}': {}", output_path, fd->StrError());
276 
277   std::string line = fmt::format("{}(/.*)?         u:object_r:{}:s0\n",
278                                  mount_point, file_label);
279   CF_EXPECT_EQ(WriteAll(fd, line), line.size(), fd->StrError());
280 
281   int exit_code = Execute({
282       HostBinaryPath("sefcontext_compile"),
283       "-o",
284       output_path,
285       file_contexts_txt,
286   });
287 
288   CF_EXPECT_EQ(exit_code, 0);
289 
290   return {};
291 }
292 
AddVbmetaFooter(const std::string & output_image,const std::string & partition_name)293 Result<void> AddVbmetaFooter(const std::string& output_image,
294                              const std::string& partition_name) {
295   // TODO(b/335742241): update to use Avb
296   std::string avbtool_path = AvbToolBinary();
297   // Add host binary path to PATH, so that avbtool can locate host util binaries
298   // such as 'fec'
299   std::string env_path =
300       StringFromEnv("PATH", "") + ":" + android::base::Dirname(avbtool_path);
301   Command avb_cmd =
302       Command(AvbToolBinary())
303           // Must unset an existing environment variable in order to modify it
304           .UnsetFromEnvironment("PATH")
305           .AddEnvironmentVariable("PATH", env_path)
306           .AddParameter("add_hashtree_footer")
307           // Arbitrary salt to keep output consistent
308           .AddParameter("--salt")
309           .AddParameter("62BBAAA0", "E4BD99E783AC")
310           .AddParameter("--hash_algorithm")
311           .AddParameter("sha256")
312           .AddParameter("--image")
313           .AddParameter(output_image)
314           .AddParameter("--partition_name")
315           .AddParameter(partition_name);
316 
317   CF_EXPECT_EQ(avb_cmd.Start().Wait(), 0,
318                "Failed to add avb footer to image " << output_image);
319 
320   return {};
321 }
322 
323 }  // namespace
324 
325 // Steps for building a vendor_dlkm.img:
326 // 1. Generate filesystem_config.txt , which contains standard linux file
327 // permissions, we use 0755 for directories, and 0644 for all files
328 // 2. Write file_contexts, which contains all selinux labels
329 // 3. Call  sefcontext_compile to compile file_contexts
330 // 4. call mkuserimg_mke2fs to build an image, using filesystem_config and
331 // file_contexts previously generated
332 // 5. call avbtool to add hashtree footer, so that init/bootloader can verify
333 // AVB chain
BuildDlkmImage(const std::string & src_dir,const bool is_erofs,const std::string & partition_name,const std::string & output_image)334 Result<void> BuildDlkmImage(const std::string& src_dir, const bool is_erofs,
335                             const std::string& partition_name,
336                             const std::string& output_image) {
337   CF_EXPECT(!is_erofs,
338             "Building DLKM image in EROFS format is currently not supported!");
339 
340   const std::string mount_point = "/" + partition_name;
341   const std::string fs_config = output_image + ".fs_config";
342   CF_EXPECT(WriteFsConfig(fs_config.c_str(), src_dir, mount_point));
343 
344   const std::string file_contexts_bin = output_image + ".file_contexts";
345   if (partition_name == "system_dlkm") {
346     CF_EXPECT(GenerateFileContexts(file_contexts_bin.c_str(), mount_point,
347                                    "system_dlkm_file"));
348   } else {
349     CF_EXPECT(GenerateFileContexts(file_contexts_bin.c_str(), mount_point,
350                                    "vendor_file"));
351   }
352 
353   // We are using directory size as an estimate of final image size. To avoid
354   // any rounding errors, add 16M of head room.
355   const auto fs_size = RoundUp(GetDiskUsage(src_dir) + 16 * 1024 * 1024, 4096);
356   LOG(INFO) << mount_point << " src dir " << src_dir << " has size "
357             << fs_size / 1024 << " KB";
358 
359   Command mkfs_cmd =
360       Command(HostBinaryPath("mkuserimg_mke2fs"))
361           // Arbitrary UUID/seed, just to keep output consistent between runs
362           .AddParameter("--mke2fs_uuid")
363           .AddParameter("cb09b942-ed4e-46a1-81dd-7d535bf6c4b1")
364           .AddParameter("--mke2fs_hash_seed")
365           .AddParameter("765d8aba-d93f-465a-9fcf-14bb794eb7f4")
366           // Arbitrary date, just to keep output consistent
367           .AddParameter("-T")
368           .AddParameter(900979200000)
369           // selinux permission to keep selinux happy
370           .AddParameter("--fs_config")
371           .AddParameter(fs_config)
372 
373           .AddParameter(src_dir)
374           .AddParameter(output_image)
375           .AddParameter("ext4")
376           .AddParameter(mount_point)
377           .AddParameter(fs_size)
378           .AddParameter(file_contexts_bin);
379 
380   CF_EXPECT_EQ(mkfs_cmd.Start().Wait(), 0,
381                "Failed to build vendor_dlkm ext4 image");
382   CF_EXPECT(AddVbmetaFooter(output_image, partition_name));
383 
384   return {};
385 }
386 
RepackSuperWithPartition(const std::string & superimg_path,const std::string & image_path,const std::string & partition_name)387 Result<void> RepackSuperWithPartition(const std::string& superimg_path,
388                                       const std::string& image_path,
389                                       const std::string& partition_name) {
390   int exit_code = Execute({
391       HostBinaryPath("lpadd"),
392       "--replace",
393       superimg_path,
394       partition_name + "_a",
395       "google_vendor_dynamic_partitions_a",
396       image_path,
397   });
398   CF_EXPECT_EQ(exit_code, 0);
399 
400   return {};
401 }
402 
BuildVbmetaImage(const std::string & image_path,const std::string & vbmeta_path)403 Result<void> BuildVbmetaImage(const std::string& image_path,
404                               const std::string& vbmeta_path) {
405   CF_EXPECT(!image_path.empty());
406   CF_EXPECTF(FileExists(image_path), "'{}' does not exist", image_path);
407 
408   std::unique_ptr<Avb> avbtool = GetDefaultAvb();
409   CF_EXPECT(avbtool->MakeVbMetaImage(vbmeta_path, {}, {image_path},
410                                      {"--padding_size", "4096"}));
411   return {};
412 }
413 
Dedup(std::vector<std::string> && vec)414 std::vector<std::string> Dedup(std::vector<std::string>&& vec) {
415   std::sort(vec.begin(), vec.end());
416   vec.erase(unique(vec.begin(), vec.end()), vec.end());
417   return vec;
418 }
419 
SplitRamdiskModules(const std::string & ramdisk_path,const std::string & ramdisk_stage_dir,const std::string & vendor_dlkm_build_dir,const std::string & system_dlkm_build_dir)420 bool SplitRamdiskModules(const std::string& ramdisk_path,
421                          const std::string& ramdisk_stage_dir,
422                          const std::string& vendor_dlkm_build_dir,
423                          const std::string& system_dlkm_build_dir) {
424   const auto vendor_modules_dir = vendor_dlkm_build_dir + "/lib/modules";
425   const auto system_modules_dir = system_dlkm_build_dir + "/lib/modules";
426   auto ret = EnsureDirectoryExists(vendor_modules_dir);
427   CHECK(ret.ok()) << ret.error().FormatForEnv();
428   ret = EnsureDirectoryExists(system_modules_dir);
429   UnpackRamdisk(ramdisk_path, ramdisk_stage_dir);
430   const auto module_load_file =
431       android::base::Trim(FindFile(ramdisk_stage_dir.c_str(), "modules.load"));
432   if (module_load_file.empty()) {
433     LOG(ERROR) << "Failed to find modules.dep file in input ramdisk "
434                << ramdisk_path;
435     return false;
436   }
437   LOG(INFO) << "modules.load location " << module_load_file;
438   const auto module_list =
439       Dedup(android::base::Tokenize(ReadFile(module_load_file), "\n"));
440   const auto module_base_dir = android::base::Dirname(module_load_file);
441   const auto deps = LoadModuleDeps(module_base_dir + "/modules.dep");
442   const auto ramdisk_modules =
443       ComputeTransitiveClosure(GetRamdiskModules(module_list), deps);
444   std::set<std::string> vendor_dlkm_modules;
445   std::set<std::string> system_dlkm_modules;
446 
447   // Move non-ramdisk modules to vendor_dlkm
448   for (const auto& module_path : module_list) {
449     if (ramdisk_modules.count(module_path)) {
450       continue;
451     }
452 
453     const auto module_location =
454         fmt::format("{}/{}", module_base_dir, module_path);
455     if (!FileExists(module_location)) {
456       continue;
457     }
458     if (IsKernelModuleSigned(module_location)) {
459       const auto system_dlkm_module_location =
460           fmt::format("{}/{}", system_modules_dir, module_path);
461       EnsureDirectoryExists(
462           android::base::Dirname(system_dlkm_module_location));
463       RenameFile(module_location, system_dlkm_module_location);
464       system_dlkm_modules.emplace(module_path);
465     } else {
466       const auto vendor_dlkm_module_location =
467           fmt::format("{}/{}", vendor_modules_dir, module_path);
468       EnsureDirectoryExists(
469           android::base::Dirname(vendor_dlkm_module_location));
470       RenameFile(module_location, vendor_dlkm_module_location);
471       vendor_dlkm_modules.emplace(module_path);
472     }
473   }
474   for (const auto& gki_module : system_dlkm_modules) {
475     for (const auto& dep : deps.at(gki_module)) {
476       if (vendor_dlkm_modules.count(dep)) {
477         LOG(ERROR) << "GKI module " << gki_module
478                    << " depends on vendor_dlkm module " << dep;
479         return false;
480       }
481     }
482   }
483   LOG(INFO) << "There are " << ramdisk_modules.size() << " ramdisk modules, "
484             << vendor_dlkm_modules.size() << " vendor_dlkm modules, "
485             << system_dlkm_modules.size() << " system_dlkm modules.";
486 
487   // transfer blocklist in whole to the vendor dlkm partition. It currently
488   // only contains one module that is loaded during second stage init.
489   // We can split the blocklist at a later date IF it contains modules in
490   // different partitions.
491   const auto initramfs_blocklist_path = module_base_dir + "/modules.blocklist";
492   if (FileExists(initramfs_blocklist_path)) {
493     const auto vendor_dlkm_blocklist_path =
494         fmt::format("{}/{}", vendor_modules_dir, "modules.blocklist");
495     RenameFile(initramfs_blocklist_path, vendor_dlkm_blocklist_path);
496   }
497 
498   // Write updated modules.dep and modules.load files
499   CHECK(WriteDepsToFile(FilterDependencies(deps, ramdisk_modules),
500                         module_base_dir + "/modules.dep"));
501   CHECK(WriteLinesToFile(ramdisk_modules, module_load_file.c_str()));
502 
503   CHECK(WriteDepsToFile(
504       UpdateGKIModulePaths(FilterOutDependencies(deps, ramdisk_modules),
505                            system_dlkm_modules),
506       vendor_modules_dir + "/modules.dep"));
507   CHECK(WriteLinesToFile(vendor_dlkm_modules,
508                          (vendor_modules_dir + "/modules.load").c_str()));
509 
510   CHECK(WriteDepsToFile(FilterDependencies(deps, system_dlkm_modules),
511                         system_modules_dir + "/modules.dep"));
512   CHECK(WriteLinesToFile(system_dlkm_modules,
513                          (system_modules_dir + "/modules.load").c_str()));
514   PackRamdisk(ramdisk_stage_dir, ramdisk_path);
515   return true;
516 }
517 
FileEquals(const std::string & file1,const std::string & file2)518 bool FileEquals(const std::string& file1, const std::string& file2) {
519   if (FileSize(file1) != FileSize(file2)) {
520     return false;
521   }
522   std::array<uint8_t, 1024 * 16> buf1{};
523   std::array<uint8_t, 1024 * 16> buf2{};
524   auto fd1 = SharedFD::Open(file1, O_RDONLY);
525   auto fd2 = SharedFD::Open(file2, O_RDONLY);
526   auto bytes_remain = FileSize(file1);
527   while (bytes_remain > 0) {
528     const auto bytes_to_read = std::min<size_t>(bytes_remain, buf1.size());
529     if (fd1->Read(buf1.data(), bytes_to_read) != bytes_to_read) {
530       LOG(ERROR) << "Failed to read from " << file1;
531       return false;
532     }
533     if (!fd2->Read(buf2.data(), bytes_to_read)) {
534       LOG(ERROR) << "Failed to read from " << file2;
535       return false;
536     }
537     if (memcmp(buf1.data(), buf2.data(), bytes_to_read)) {
538       return false;
539     }
540   }
541   return true;
542 }
543 
MoveIfChanged(const std::string & src,const std::string & dst)544 bool MoveIfChanged(const std::string& src, const std::string& dst) {
545   if (FileEquals(src, dst)) {
546     return false;
547   }
548   const auto ret = RenameFile(src, dst);
549   if (!ret.ok()) {
550     LOG(ERROR) << ret.error().FormatForEnv();
551     return false;
552   }
553   return true;
554 }
555 
556 }  // namespace cuttlefish
557