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