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 <android-base/file.h>
17 #include <android-base/logging.h>
18 #include <android-base/strings.h>
19
20 #include <fcntl.h>
21
22 #include <fcntl.h>
23 #include <map>
24 #include <queue>
25 #include <set>
26 #include <sstream>
27 #include <string>
28 #include <vector>
29
30 #include "common/libs/utils/files.h"
31 #include "common/libs/utils/subprocess.h"
32 #include "host/commands/assemble_cvd/boot_image_utils.h"
33 #include "host/commands/assemble_cvd/ramdisk_modules.h"
34 #include "host/libs/config/cuttlefish_config.h"
35
36 namespace cuttlefish {
37
38 namespace {
39
RoundDown(size_t a,size_t divisor)40 constexpr size_t RoundDown(size_t a, size_t divisor) {
41 return a / divisor * divisor;
42 }
43
RoundUp(size_t a,size_t divisor)44 constexpr size_t RoundUp(size_t a, size_t divisor) {
45 return RoundDown(a + divisor, divisor);
46 }
47
48 template <typename Container>
WriteLinesToFile(const Container & lines,const char * path)49 bool WriteLinesToFile(const Container& lines, const char* path) {
50 android::base::unique_fd fd(
51 open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640));
52 if (!fd.ok()) {
53 PLOG(ERROR) << "Failed to open " << path;
54 return false;
55 }
56 for (const auto& line : lines) {
57 if (!android::base::WriteFully(fd, line.data(), line.size())) {
58 PLOG(ERROR) << "Failed to write to " << path;
59 return false;
60 }
61 const char c = '\n';
62 if (write(fd.get(), &c, 1) != 1) {
63 PLOG(ERROR) << "Failed to write to " << path;
64 return false;
65 }
66 }
67 return true;
68 }
69
70
71 // 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)72 bool WriteFsConfig(const char* output_path, const std::string& fs_root,
73 const std::string& mount_point) {
74 android::base::unique_fd fd(
75 open(output_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644));
76 if (!fd.ok()) {
77 PLOG(ERROR) << "Failed to open " << output_path;
78 return false;
79 }
80 if (!android::base::WriteStringToFd(
81 " 0 0 755 selabel=u:object_r:rootfs:s0 capabilities=0x0\n", fd)) {
82 PLOG(ERROR) << "Failed to write to " << output_path;
83 return false;
84 }
85 WalkDirectory(fs_root, [&fd, &output_path, &mount_point,
86 &fs_root](const std::string& file_path) {
87 const auto filename = file_path.substr(
88 fs_root.back() == '/' ? fs_root.size() : fs_root.size() + 1);
89 std::string fs_context = " 0 0 644 capabilities=0x0\n";
90 if (DirectoryExists(file_path)) {
91 fs_context = " 0 0 755 capabilities=0x0\n";
92 }
93 if (!android::base::WriteStringToFd(
94 mount_point + "/" + filename + fs_context, fd)) {
95 PLOG(ERROR) << "Failed to write to " << output_path;
96 return false;
97 }
98 return true;
99 });
100 return true;
101 }
102
GetRamdiskModules(const std::vector<std::string> & all_modules)103 std::vector<std::string> GetRamdiskModules(
104 const std::vector<std::string>& all_modules) {
105 static const auto ramdisk_modules_allow_list =
106 std::set<std::string>(RAMDISK_MODULES.begin(), RAMDISK_MODULES.end());
107 std::vector<std::string> ramdisk_modules;
108 for (const auto& mod_path : all_modules) {
109 if (mod_path.empty()) {
110 continue;
111 }
112 const auto mod_name = cpp_basename(mod_path);
113 if (ramdisk_modules_allow_list.count(mod_name) != 0) {
114 ramdisk_modules.emplace_back(mod_path);
115 }
116 }
117 return ramdisk_modules;
118 }
119
120 // 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)121 std::map<std::string, std::vector<std::string>> FilterDependencies(
122 const std::map<std::string, std::vector<std::string>>& deps,
123 const std::set<std::string>& allow_list) {
124 std::map<std::string, std::vector<std::string>> new_deps;
125 for (const auto& mod_name : allow_list) {
126 new_deps[mod_name].clear();
127 }
128 for (const auto& [mod_name, children] : deps) {
129 if (!allow_list.count(mod_name)) {
130 continue;
131 }
132 for (const auto& child : children) {
133 if (!allow_list.count(child)) {
134 continue;
135 }
136 new_deps[mod_name].emplace_back(child);
137 }
138 }
139 return new_deps;
140 }
141
142 // Write dependency map to modules.dep file
WriteDepsToFile(const std::map<std::string,std::vector<std::string>> & deps,const std::string & output_path)143 bool WriteDepsToFile(
144 const std::map<std::string, std::vector<std::string>>& deps,
145 const std::string& output_path) {
146 std::stringstream ss;
147 for (const auto& [key, val] : deps) {
148 ss << key << ":";
149 for (const auto& dep : val) {
150 ss << " " << dep;
151 }
152 ss << "\n";
153 }
154 if (!android::base::WriteStringToFile(ss.str(), output_path)) {
155 PLOG(ERROR) << "Failed to write modules.dep to " << output_path;
156 return false;
157 }
158 return true;
159 }
160
161 // Parse modules.dep into an in-memory data structure, key is path to a kernel
162 // module, value is all dependency modules
LoadModuleDeps(const std::string & filename)163 std::map<std::string, std::vector<std::string>> LoadModuleDeps(
164 const std::string& filename) {
165 std::map<std::string, std::vector<std::string>> dependency_map;
166 const auto dep_str = android::base::Trim(ReadFile(filename));
167 const auto dep_lines = android::base::Split(dep_str, "\n");
168 for (const auto& line : dep_lines) {
169 const auto mod_name = line.substr(0, line.find(":"));
170 const auto deps =
171 android::base::Tokenize(line.substr(mod_name.size() + 1), " ");
172 if (!deps.empty()) {
173 dependency_map[mod_name] = deps;
174 }
175 }
176
177 return dependency_map;
178 }
179
180 // Recursively compute all modules which |start_nodes| depend on
ComputeTransitiveClosure(const std::vector<std::string> & start_nodes,const std::map<std::string,std::vector<std::string>> & dependencies)181 std::set<std::string> ComputeTransitiveClosure(
182 const std::vector<std::string>& start_nodes,
183 const std::map<std::string, std::vector<std::string>>& dependencies) {
184 std::deque<std::string> queue(start_nodes.begin(), start_nodes.end());
185 std::set<std::string> visited;
186 while (!queue.empty()) {
187 const auto cur = queue.front();
188 queue.pop_front();
189 if (visited.find(cur) != visited.end()) {
190 continue;
191 }
192 visited.insert(cur);
193 const auto it = dependencies.find(cur);
194 if (it == dependencies.end()) {
195 continue;
196 }
197 for (const auto& dep : it->second) {
198 queue.emplace_back(dep);
199 }
200 }
201 return visited;
202 }
203
GenerateFileContexts(const char * output_path,const std::string & mount_point)204 bool GenerateFileContexts(const char* output_path,
205 const std::string& mount_point) {
206 const auto file_contexts_txt = std::string(output_path) + ".txt";
207 android::base::unique_fd fd(open(file_contexts_txt.c_str(),
208 O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
209 0644));
210 if (!fd.ok()) {
211 PLOG(ERROR) << "Failed to open " << output_path;
212 return false;
213 }
214 if (!android::base::WriteStringToFd(mount_point +
215 "(/.*)? "
216 " u:object_r:vendor_file:s0\n",
217 fd)) {
218 return false;
219 }
220 if (!android::base::WriteStringToFd(
221 mount_point + "/etc(/.*)? "
222 " u:object_r:vendor_configs_file:s0\n",
223 fd)) {
224 return false;
225 }
226 Command cmd(HostBinaryPath("sefcontext_compile"));
227 cmd.AddParameter("-o");
228 cmd.AddParameter(output_path);
229 cmd.AddParameter(file_contexts_txt);
230 const auto exit_code = cmd.Start().Wait();
231 return exit_code == 0;
232 }
233
AddVbmetaFooter(const std::string & output_image,const std::string & partition_name)234 bool AddVbmetaFooter(const std::string& output_image,
235 const std::string& partition_name) {
236 auto avbtool_path = HostBinaryPath("avbtool");
237 Command avb_cmd(avbtool_path);
238 // Add host binary path to PATH, so that avbtool can locate host util
239 // binaries such as 'fec'
240 auto PATH =
241 StringFromEnv("PATH", "") + ":" + cpp_dirname(avb_cmd.Executable());
242 // Must unset an existing environment variable in order to modify it
243 avb_cmd.UnsetFromEnvironment("PATH");
244 avb_cmd.AddEnvironmentVariable("PATH", PATH);
245
246 avb_cmd.AddParameter("add_hashtree_footer");
247 // Arbitrary salt to keep output consistent
248 avb_cmd.AddParameter("--salt");
249 avb_cmd.AddParameter("62BBAAA0", "E4BD99E783AC");
250 avb_cmd.AddParameter("--image");
251 avb_cmd.AddParameter(output_image);
252 avb_cmd.AddParameter("--partition_name");
253 avb_cmd.AddParameter(partition_name);
254
255 auto exit_code = avb_cmd.Start().Wait();
256 if (exit_code != 0) {
257 LOG(ERROR) << "Failed to add avb footer to image " << output_image;
258 return false;
259 }
260
261 return true;
262 }
263
264 } // namespace
265
266 // Steps for building a vendor_dlkm.img:
267 // 1. Generate filesystem_config.txt , which contains standard linux file
268 // permissions, we use 0755 for directories, and 0644 for all files
269 // 2. Write file_contexts, which contains all selinux labels
270 // 3. Call sefcontext_compile to compile file_contexts
271 // 4. call mkuserimg_mke2fs to build an image, using filesystem_config and
272 // file_contexts previously generated
273 // 5. call avbtool to add hashtree footer, so that init/bootloader can verify
274 // AVB chain
BuildVendorDLKM(const std::string & src_dir,const bool is_erofs,const std::string & output_image)275 bool BuildVendorDLKM(const std::string& src_dir, const bool is_erofs,
276 const std::string& output_image) {
277 if (is_erofs) {
278 LOG(ERROR)
279 << "Building vendor_dlkm in EROFS format is currently not supported!";
280 return false;
281 }
282 const auto fs_config = output_image + ".fs_config";
283 if (!WriteFsConfig(fs_config.c_str(), src_dir, "/vendor_dlkm")) {
284 return false;
285 }
286 const auto file_contexts_bin = output_image + ".file_contexts";
287 if (!GenerateFileContexts(file_contexts_bin.c_str(), "/vendor_dlkm")) {
288 return false;
289 }
290
291 // We are using directory size as an estimate of final image size. To avoid
292 // any rounding errors, add 16M of head room.
293 const auto fs_size = RoundUp(GetDiskUsage(src_dir) + 16 * 1024 * 1024, 4096);
294 LOG(INFO) << "vendor_dlkm src dir " << src_dir << " has size "
295 << fs_size / 1024 << " KB";
296 const auto mkfs = HostBinaryPath("mkuserimg_mke2fs");
297 Command mkfs_cmd(mkfs);
298 // Arbitrary UUID/seed, just to keep output consistent between runs
299 mkfs_cmd.AddParameter("--mke2fs_uuid");
300 mkfs_cmd.AddParameter("cb09b942-ed4e-46a1-81dd-7d535bf6c4b1");
301 mkfs_cmd.AddParameter("--mke2fs_hash_seed");
302 mkfs_cmd.AddParameter("765d8aba-d93f-465a-9fcf-14bb794eb7f4");
303 // Arbitrary date, just to keep output consistent
304 mkfs_cmd.AddParameter("-T");
305 mkfs_cmd.AddParameter("900979200000");
306
307 // selinux permission to keep selinux happy
308 mkfs_cmd.AddParameter("--fs_config");
309 mkfs_cmd.AddParameter(fs_config);
310
311 mkfs_cmd.AddParameter(src_dir);
312 mkfs_cmd.AddParameter(output_image);
313 mkfs_cmd.AddParameter("ext4");
314 mkfs_cmd.AddParameter("/vendor_dlkm");
315 mkfs_cmd.AddParameter(std::to_string(fs_size));
316 mkfs_cmd.AddParameter(file_contexts_bin);
317
318 int exit_code = mkfs_cmd.Start().Wait();
319 if (exit_code != 0) {
320 LOG(ERROR) << "Failed to build vendor_dlkm ext4 image";
321 return false;
322 }
323 return AddVbmetaFooter(output_image, "vendor_dlkm");
324 }
325
RepackSuperWithVendorDLKM(const std::string & superimg_path,const std::string & vendor_dlkm_path)326 bool RepackSuperWithVendorDLKM(const std::string& superimg_path,
327 const std::string& vendor_dlkm_path) {
328 Command lpadd(HostBinaryPath("lpadd"));
329 lpadd.AddParameter("--replace");
330 lpadd.AddParameter(superimg_path);
331 lpadd.AddParameter("vendor_dlkm_a");
332 lpadd.AddParameter("google_vendor_dynamic_partitions_a");
333 lpadd.AddParameter(vendor_dlkm_path);
334 const auto exit_code = lpadd.Start().Wait();
335 return exit_code == 0;
336 }
337
RebuildVbmetaVendor(const std::string & vendor_dlkm_img,const std::string & vbmeta_path)338 bool RebuildVbmetaVendor(const std::string& vendor_dlkm_img,
339 const std::string& vbmeta_path) {
340 auto avbtool_path = HostBinaryPath("avbtool");
341 Command vbmeta_cmd(avbtool_path);
342 vbmeta_cmd.AddParameter("make_vbmeta_image");
343 vbmeta_cmd.AddParameter("--output");
344 vbmeta_cmd.AddParameter(vbmeta_path);
345 vbmeta_cmd.AddParameter("--algorithm");
346 vbmeta_cmd.AddParameter("SHA256_RSA4096");
347 vbmeta_cmd.AddParameter("--key");
348 vbmeta_cmd.AddParameter(DefaultHostArtifactsPath("etc/cvd_avb_testkey.pem"));
349
350 vbmeta_cmd.AddParameter("--include_descriptors_from_image");
351 vbmeta_cmd.AddParameter(vendor_dlkm_img);
352 vbmeta_cmd.AddParameter("--padding_size");
353 vbmeta_cmd.AddParameter("4096");
354
355 bool success = vbmeta_cmd.Start().Wait();
356 if (success != 0) {
357 LOG(ERROR) << "Unable to create vbmeta. Exited with status " << success;
358 return false;
359 }
360
361 const auto vbmeta_size = FileSize(vbmeta_path);
362 if (vbmeta_size > VBMETA_MAX_SIZE) {
363 LOG(ERROR) << "Generated vbmeta - " << vbmeta_path
364 << " is larger than the expected " << VBMETA_MAX_SIZE
365 << ". Stopping.";
366 return false;
367 }
368 if (vbmeta_size != VBMETA_MAX_SIZE) {
369 auto fd = SharedFD::Open(vbmeta_path, O_RDWR | O_CLOEXEC);
370 if (!fd->IsOpen() || fd->Truncate(VBMETA_MAX_SIZE) != 0) {
371 LOG(ERROR) << "`truncate --size=" << VBMETA_MAX_SIZE << " " << vbmeta_path
372 << "` failed: " << fd->StrError();
373 return false;
374 }
375 }
376 return true;
377 }
378
SplitRamdiskModules(const std::string & ramdisk_path,const std::string & ramdisk_stage_dir,const std::string & vendor_dlkm_build_dir)379 bool SplitRamdiskModules(const std::string& ramdisk_path,
380 const std::string& ramdisk_stage_dir,
381 const std::string& vendor_dlkm_build_dir) {
382 const auto target_modules_dir = vendor_dlkm_build_dir + "/lib/modules";
383 const auto ret = EnsureDirectoryExists(target_modules_dir);
384 CHECK(ret.ok()) << ret.error().Message();
385 UnpackRamdisk(ramdisk_path, ramdisk_stage_dir);
386 const auto module_load_file =
387 android::base::Trim(FindFile(ramdisk_stage_dir.c_str(), "modules.load"));
388 if (module_load_file.empty()) {
389 LOG(ERROR) << "Failed to find modules.dep file in input ramdisk "
390 << ramdisk_path;
391 return false;
392 }
393 LOG(INFO) << "modules.load location " << module_load_file;
394 const auto module_list =
395 android::base::Tokenize(ReadFile(module_load_file), "\n");
396 const auto module_base_dir = cpp_dirname(module_load_file);
397 const auto deps = LoadModuleDeps(module_base_dir + "/modules.dep");
398 const auto ramdisk_modules =
399 ComputeTransitiveClosure(GetRamdiskModules(module_list), deps);
400 std::set<std::string> vendor_dlkm_modules;
401
402 // Move non-ramdisk modules to vendor_dlkm
403 for (const auto& module_path : module_list) {
404 if (!ramdisk_modules.count(module_path)) {
405 const auto vendor_dlkm_module_location =
406 target_modules_dir + "/" + module_path;
407 EnsureDirectoryExists(cpp_dirname(vendor_dlkm_module_location));
408 RenameFile(module_base_dir + "/" + module_path,
409 vendor_dlkm_module_location);
410 vendor_dlkm_modules.emplace(module_path);
411 }
412 }
413 LOG(INFO) << "There are " << ramdisk_modules.size() << " ramdisk modules and "
414 << vendor_dlkm_modules.size() << " vendor_dlkm modules";
415
416 // Write updated modules.dep and modules.load files
417 CHECK(WriteDepsToFile(FilterDependencies(deps, ramdisk_modules),
418 module_base_dir + "/modules.dep"));
419 CHECK(WriteDepsToFile(FilterDependencies(deps, vendor_dlkm_modules),
420 target_modules_dir + "/modules.dep"));
421 CHECK(WriteLinesToFile(ramdisk_modules, module_load_file.c_str()));
422 CHECK(WriteLinesToFile(vendor_dlkm_modules,
423 (target_modules_dir + "/modules.load").c_str()));
424 PackRamdisk(ramdisk_stage_dir, ramdisk_path);
425 return true;
426 }
427
428 } // namespace cuttlefish
429