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