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