1 /*
2 * Copyright (C) 2020 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
17 #include "host/commands/assemble_cvd/boot_image_utils.h"
18
19 #include <string.h>
20 #include <unistd.h>
21
22 #include <fstream>
23 #include <memory>
24 #include <regex>
25 #include <sstream>
26 #include <string>
27
28 #include <android-base/logging.h>
29 #include <android-base/strings.h>
30
31 #include "common/libs/fs/shared_fd.h"
32 #include "common/libs/utils/files.h"
33 #include "common/libs/utils/result.h"
34 #include "common/libs/utils/subprocess.h"
35 #include "host/libs/avb/avb.cpp"
36 #include "host/libs/config/cuttlefish_config.h"
37 #include "host/libs/config/known_paths.h"
38
39 const char TMP_EXTENSION[] = ".tmp";
40 const char CPIO_EXT[] = ".cpio";
41 const char TMP_RD_DIR[] = "stripped_ramdisk_dir";
42 const char STRIPPED_RD[] = "stripped_ramdisk";
43 const char CONCATENATED_VENDOR_RAMDISK[] = "concatenated_vendor_ramdisk";
44 namespace cuttlefish {
45 namespace {
ExtractValue(const std::string & dictionary,const std::string & key)46 std::string ExtractValue(const std::string& dictionary, const std::string& key) {
47 std::size_t index = dictionary.find(key);
48 if (index != std::string::npos) {
49 std::size_t end_index = dictionary.find('\n', index + key.length());
50 if (end_index != std::string::npos) {
51 return dictionary.substr(index + key.length(),
52 end_index - index - key.length());
53 }
54 }
55 return "";
56 }
57
58 // Though it is just as fast to overwrite the existing boot images with the newly generated ones,
59 // the cuttlefish composite disk generator checks the age of each of the components and
60 // regenerates the disk outright IF any one of the components is younger/newer than the current
61 // composite disk. If this file overwrite occurs, that condition is fulfilled. This action then
62 // causes data in the userdata partition from previous boots to be lost (which is not expected by
63 // the user if they've been booting the same kernel/ramdisk combination repeatedly).
64 // Consequently, the file is checked for differences and ONLY overwritten if there is a diff.
DeleteTmpFileIfNotChanged(const std::string & tmp_file,const std::string & current_file)65 bool DeleteTmpFileIfNotChanged(const std::string& tmp_file, const std::string& current_file) {
66 if (!FileExists(current_file) ||
67 ReadFile(current_file) != ReadFile(tmp_file)) {
68 if (!RenameFile(tmp_file, current_file).ok()) {
69 LOG(ERROR) << "Unable to delete " << current_file;
70 return false;
71 }
72 LOG(DEBUG) << "Updated " << current_file;
73 } else {
74 LOG(DEBUG) << "Didn't update " << current_file;
75 RemoveFile(tmp_file);
76 }
77
78 return true;
79 }
80
RepackVendorRamdisk(const std::string & kernel_modules_ramdisk_path,const std::string & original_ramdisk_path,const std::string & new_ramdisk_path,const std::string & build_dir)81 void RepackVendorRamdisk(const std::string& kernel_modules_ramdisk_path,
82 const std::string& original_ramdisk_path,
83 const std::string& new_ramdisk_path,
84 const std::string& build_dir) {
85 int success = 0;
86 const std::string ramdisk_stage_dir = build_dir + "/" + TMP_RD_DIR;
87 UnpackRamdisk(original_ramdisk_path, ramdisk_stage_dir);
88
89 success = Execute({"rm", "-rf", ramdisk_stage_dir + "/lib/modules"});
90 CHECK(success == 0) << "Could not rmdir \"lib/modules\" in TMP_RD_DIR. "
91 << "Exited with status " << success;
92
93 const std::string stripped_ramdisk_path = build_dir + "/" + STRIPPED_RD;
94 success = Execute({"/bin/bash", "-c",
95 HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
96 " > " + stripped_ramdisk_path + CPIO_EXT});
97 CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
98 << success;
99
100 success = Execute({"/bin/bash", "-c",
101 HostBinaryPath("lz4") + " -c -l -12 --favor-decSpeed " +
102 stripped_ramdisk_path + CPIO_EXT + " > " +
103 stripped_ramdisk_path});
104 CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
105
106 // Concatenates the stripped ramdisk and input ramdisk and places the result at new_ramdisk_path
107 std::ofstream final_rd(new_ramdisk_path, std::ios_base::binary | std::ios_base::trunc);
108 std::ifstream ramdisk_a(stripped_ramdisk_path, std::ios_base::binary);
109 std::ifstream ramdisk_b(kernel_modules_ramdisk_path, std::ios_base::binary);
110 final_rd << ramdisk_a.rdbuf() << ramdisk_b.rdbuf();
111 }
112
IsCpioArchive(const std::string & path)113 bool IsCpioArchive(const std::string& path) {
114 static constexpr std::string_view CPIO_MAGIC = "070701";
115 auto fd = SharedFD::Open(path, O_RDONLY);
116 std::array<char, CPIO_MAGIC.size()> buf{};
117 if (fd->Read(buf.data(), buf.size()) != CPIO_MAGIC.size()) {
118 return false;
119 }
120 return memcmp(buf.data(), CPIO_MAGIC.data(), CPIO_MAGIC.size()) == 0;
121 }
122
123 } // namespace
124
PackRamdisk(const std::string & ramdisk_stage_dir,const std::string & output_ramdisk)125 void PackRamdisk(const std::string& ramdisk_stage_dir,
126 const std::string& output_ramdisk) {
127 int success = Execute({"/bin/bash", "-c",
128 HostBinaryPath("mkbootfs") + " " + ramdisk_stage_dir +
129 " > " + output_ramdisk + CPIO_EXT});
130 CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
131 << success;
132
133 success = Execute({"/bin/bash", "-c",
134 HostBinaryPath("lz4") + " -c -l -12 --favor-decSpeed " +
135 output_ramdisk + CPIO_EXT + " > " + output_ramdisk});
136 CHECK(success == 0) << "Unable to run lz4. Exited with status " << success;
137 }
138
UnpackRamdisk(const std::string & original_ramdisk_path,const std::string & ramdisk_stage_dir)139 void UnpackRamdisk(const std::string& original_ramdisk_path,
140 const std::string& ramdisk_stage_dir) {
141 int success = 0;
142 if (IsCpioArchive(original_ramdisk_path)) {
143 CHECK(Copy(original_ramdisk_path, original_ramdisk_path + CPIO_EXT))
144 << "failed to copy " << original_ramdisk_path << " to "
145 << original_ramdisk_path + CPIO_EXT;
146 } else {
147 success =
148 Execute({"/bin/bash", "-c",
149 HostBinaryPath("lz4") + " -c -d -l " + original_ramdisk_path +
150 " > " + original_ramdisk_path + CPIO_EXT});
151 CHECK(success == 0) << "Unable to run lz4 on file " << original_ramdisk_path
152 << " . Exited with status " << success;
153 }
154 const auto ret = EnsureDirectoryExists(ramdisk_stage_dir);
155 CHECK(ret.ok()) << ret.error().FormatForEnv();
156
157 success = Execute(
158 {"/bin/bash", "-c",
159 "(cd " + ramdisk_stage_dir + " && while " + HostBinaryPath("toybox") +
160 " cpio -idu; do :; done) < " + original_ramdisk_path + CPIO_EXT});
161 CHECK(success == 0) << "Unable to run cd or cpio. Exited with status "
162 << success;
163 }
164
GetAvbMetadataFromBootImage(const std::string & boot_image_path,const std::string & unpack_dir)165 bool GetAvbMetadataFromBootImage(const std::string& boot_image_path,
166 const std::string& unpack_dir) {
167 std::unique_ptr<Avb> avbtool = GetDefaultAvb();
168 Result<void> result =
169 avbtool->WriteInfoImage(boot_image_path, unpack_dir + "/boot_params");
170 if (!result.ok()) {
171 LOG(ERROR) << result.error().Trace();
172 return false;
173 }
174 return true;
175 }
176
UnpackBootImage(const std::string & boot_image_path,const std::string & unpack_dir)177 bool UnpackBootImage(const std::string& boot_image_path,
178 const std::string& unpack_dir) {
179 auto unpack_path = HostBinaryPath("unpack_bootimg");
180 Command unpack_cmd(unpack_path);
181 unpack_cmd.AddParameter("--boot_img");
182 unpack_cmd.AddParameter(boot_image_path);
183 unpack_cmd.AddParameter("--out");
184 unpack_cmd.AddParameter(unpack_dir);
185
186 auto output_file = SharedFD::Creat(unpack_dir + "/boot_params", 0666);
187 if (!output_file->IsOpen()) {
188 LOG(ERROR) << "Unable to create intermediate boot params file: "
189 << output_file->StrError();
190 return false;
191 }
192 unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
193
194 int success = unpack_cmd.Start().Wait();
195 if (success != 0) {
196 LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status "
197 << success;
198 return false;
199 }
200 return true;
201 }
202
UnpackVendorBootImageIfNotUnpacked(const std::string & vendor_boot_image_path,const std::string & unpack_dir)203 bool UnpackVendorBootImageIfNotUnpacked(
204 const std::string& vendor_boot_image_path, const std::string& unpack_dir) {
205 // the vendor boot params file is created during the first unpack. If it's
206 // already there, a unpack has occurred and there's no need to repeat the
207 // process.
208 if (FileExists(unpack_dir + "/vendor_boot_params")) {
209 return true;
210 }
211
212 auto unpack_path = HostBinaryPath("unpack_bootimg");
213 Command unpack_cmd(unpack_path);
214 unpack_cmd.AddParameter("--boot_img");
215 unpack_cmd.AddParameter(vendor_boot_image_path);
216 unpack_cmd.AddParameter("--out");
217 unpack_cmd.AddParameter(unpack_dir);
218 auto output_file = SharedFD::Creat(unpack_dir + "/vendor_boot_params", 0666);
219 if (!output_file->IsOpen()) {
220 LOG(ERROR) << "Unable to create intermediate vendor boot params file: "
221 << output_file->StrError();
222 return false;
223 }
224 unpack_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, output_file);
225 int success = unpack_cmd.Start().Wait();
226 if (success != 0) {
227 LOG(ERROR) << "Unable to run unpack_bootimg. Exited with status " << success;
228 return false;
229 }
230
231 // Concatenates all vendor ramdisk into one single ramdisk.
232 Command concat_cmd("/bin/bash");
233 concat_cmd.AddParameter("-c");
234 concat_cmd.AddParameter("cat " + unpack_dir + "/vendor_ramdisk*");
235 auto concat_file =
236 SharedFD::Creat(unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK, 0666);
237 if (!concat_file->IsOpen()) {
238 LOG(ERROR) << "Unable to create concatenated vendor ramdisk file: "
239 << concat_file->StrError();
240 return false;
241 }
242 concat_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, concat_file);
243 success = concat_cmd.Start().Wait();
244 if (success != 0) {
245 LOG(ERROR) << "Unable to run cat. Exited with status " << success;
246 return false;
247 }
248 return true;
249 }
250
RepackBootImage(const Avb & avb,const std::string & new_kernel_path,const std::string & boot_image_path,const std::string & new_boot_image_path,const std::string & build_dir)251 Result<void> RepackBootImage(const Avb& avb,
252 const std::string& new_kernel_path,
253 const std::string& boot_image_path,
254 const std::string& new_boot_image_path,
255 const std::string& build_dir) {
256 CF_EXPECT(UnpackBootImage(boot_image_path, build_dir));
257
258 std::string boot_params = ReadFile(build_dir + "/boot_params");
259 auto kernel_cmdline = ExtractValue(boot_params, "command line args: ");
260 LOG(DEBUG) << "Cmdline from boot image is " << kernel_cmdline;
261
262 auto tmp_boot_image_path = new_boot_image_path + TMP_EXTENSION;
263 auto repack_path = HostBinaryPath("mkbootimg");
264 Command repack_cmd(repack_path);
265 repack_cmd.AddParameter("--kernel");
266 repack_cmd.AddParameter(new_kernel_path);
267 repack_cmd.AddParameter("--ramdisk");
268 repack_cmd.AddParameter(build_dir + "/ramdisk");
269 repack_cmd.AddParameter("--header_version");
270 repack_cmd.AddParameter("4");
271 repack_cmd.AddParameter("--cmdline");
272 repack_cmd.AddParameter(kernel_cmdline);
273 repack_cmd.AddParameter("-o");
274 repack_cmd.AddParameter(tmp_boot_image_path);
275 int result = repack_cmd.Start().Wait();
276 CF_EXPECT(result == 0, "Unable to run mkbootimg. Exited with status " << result);
277
278 CF_EXPECT(avb.AddHashFooter(tmp_boot_image_path, "boot", FileSize(boot_image_path)));
279 CF_EXPECT(DeleteTmpFileIfNotChanged(tmp_boot_image_path, new_boot_image_path));
280
281 return {};
282 }
283
RepackVendorBootImage(const std::string & new_ramdisk,const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)284 bool RepackVendorBootImage(const std::string& new_ramdisk,
285 const std::string& vendor_boot_image_path,
286 const std::string& new_vendor_boot_image_path,
287 const std::string& unpack_dir,
288 bool bootconfig_supported) {
289 if (UnpackVendorBootImageIfNotUnpacked(vendor_boot_image_path, unpack_dir) ==
290 false) {
291 return false;
292 }
293
294 std::string ramdisk_path;
295 if (new_ramdisk.size()) {
296 ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
297 if (!FileExists(ramdisk_path)) {
298 RepackVendorRamdisk(new_ramdisk,
299 unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
300 ramdisk_path, unpack_dir);
301 }
302 } else {
303 ramdisk_path = unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK;
304 }
305
306 std::string bootconfig = ReadFile(unpack_dir + "/bootconfig");
307 LOG(DEBUG) << "Bootconfig parameters from vendor boot image are "
308 << bootconfig;
309 std::string vendor_boot_params = ReadFile(unpack_dir + "/vendor_boot_params");
310 auto kernel_cmdline =
311 ExtractValue(vendor_boot_params, "vendor command line args: ") +
312 (bootconfig_supported
313 ? ""
314 : " " + android::base::StringReplace(bootconfig, "\n", " ", true));
315 if (!bootconfig_supported) {
316 // TODO(b/182417593): Until we pass the module parameters through
317 // modules.options, we pass them through bootconfig using
318 // 'kernel.<key>=<value>' But if we don't support bootconfig, we need to
319 // rename them back to the old cmdline version
320 kernel_cmdline = android::base::StringReplace(
321 kernel_cmdline, " kernel.", " ", true);
322 }
323 LOG(DEBUG) << "Cmdline from vendor boot image is " << kernel_cmdline;
324
325 auto tmp_vendor_boot_image_path = new_vendor_boot_image_path + TMP_EXTENSION;
326 auto repack_path = HostBinaryPath("mkbootimg");
327 Command repack_cmd(repack_path);
328 repack_cmd.AddParameter("--vendor_ramdisk");
329 repack_cmd.AddParameter(ramdisk_path);
330 repack_cmd.AddParameter("--header_version");
331 repack_cmd.AddParameter("4");
332 repack_cmd.AddParameter("--vendor_cmdline");
333 repack_cmd.AddParameter(kernel_cmdline);
334 repack_cmd.AddParameter("--vendor_boot");
335 repack_cmd.AddParameter(tmp_vendor_boot_image_path);
336 repack_cmd.AddParameter("--dtb");
337 repack_cmd.AddParameter(unpack_dir + "/dtb");
338 if (bootconfig_supported) {
339 repack_cmd.AddParameter("--vendor_bootconfig");
340 repack_cmd.AddParameter(unpack_dir + "/bootconfig");
341 }
342
343 int success = repack_cmd.Start().Wait();
344 if (success != 0) {
345 LOG(ERROR) << "Unable to run mkbootimg. Exited with status " << success;
346 return false;
347 }
348
349 auto avbtool = Avb(AvbToolBinary());
350 Result<void> result =
351 avbtool.AddHashFooter(tmp_vendor_boot_image_path, "vendor_boot",
352 FileSize(vendor_boot_image_path));
353 if (!result.ok()) {
354 LOG(ERROR) << result.error().Trace();
355 return false;
356 }
357
358 return DeleteTmpFileIfNotChanged(tmp_vendor_boot_image_path, new_vendor_boot_image_path);
359 }
360
RepackVendorBootImageWithEmptyRamdisk(const std::string & vendor_boot_image_path,const std::string & new_vendor_boot_image_path,const std::string & unpack_dir,bool bootconfig_supported)361 bool RepackVendorBootImageWithEmptyRamdisk(
362 const std::string& vendor_boot_image_path,
363 const std::string& new_vendor_boot_image_path,
364 const std::string& unpack_dir, bool bootconfig_supported) {
365 auto empty_ramdisk_file =
366 SharedFD::Creat(unpack_dir + "/empty_ramdisk", 0666);
367 return RepackVendorBootImage(
368 unpack_dir + "/empty_ramdisk", vendor_boot_image_path,
369 new_vendor_boot_image_path, unpack_dir, bootconfig_supported);
370 }
371
RepackGem5BootImage(const std::string & initrd_path,const std::string & bootconfig_path,const std::string & unpack_dir,const std::string & input_ramdisk_path)372 void RepackGem5BootImage(const std::string& initrd_path,
373 const std::string& bootconfig_path,
374 const std::string& unpack_dir,
375 const std::string& input_ramdisk_path) {
376 // Simulate per-instance what the bootloader would usually do
377 // Since on other devices this runs every time, just do it here every time
378 std::ofstream final_rd(initrd_path,
379 std::ios_base::binary | std::ios_base::trunc);
380
381 std::ifstream boot_ramdisk(unpack_dir + "/ramdisk",
382 std::ios_base::binary);
383 std::string new_ramdisk_path = unpack_dir + "/vendor_ramdisk_repacked";
384 // Test to make sure new ramdisk hasn't already been repacked if input ramdisk is provided
385 if (FileExists(input_ramdisk_path) && !FileExists(new_ramdisk_path)) {
386 RepackVendorRamdisk(input_ramdisk_path,
387 unpack_dir + "/" + CONCATENATED_VENDOR_RAMDISK,
388 new_ramdisk_path, unpack_dir);
389 }
390 std::ifstream vendor_boot_ramdisk(FileExists(new_ramdisk_path) ? new_ramdisk_path : unpack_dir +
391 "/concatenated_vendor_ramdisk",
392 std::ios_base::binary);
393
394 std::ifstream vendor_boot_bootconfig(unpack_dir + "/bootconfig",
395 std::ios_base::binary |
396 std::ios_base::ate);
397
398 auto vb_size = vendor_boot_bootconfig.tellg();
399 vendor_boot_bootconfig.seekg(0);
400
401 std::ifstream persistent_bootconfig(bootconfig_path,
402 std::ios_base::binary |
403 std::ios_base::ate);
404
405 auto pb_size = persistent_bootconfig.tellg();
406 persistent_bootconfig.seekg(0);
407
408 // Build the bootconfig string, trim it, and write the length, checksum
409 // and trailer bytes
410
411 std::string bootconfig =
412 "androidboot.slot_suffix=_a\n"
413 "androidboot.force_normal_boot=1\n"
414 "androidboot.verifiedbootstate=orange\n";
415 auto bootconfig_size = bootconfig.size();
416 bootconfig.resize(bootconfig_size + (uint64_t)(vb_size + pb_size), '\0');
417 vendor_boot_bootconfig.read(&bootconfig[bootconfig_size], vb_size);
418 persistent_bootconfig.read(&bootconfig[bootconfig_size + vb_size], pb_size);
419 // Trim the block size padding from the persistent bootconfig
420 bootconfig.erase(bootconfig.find_last_not_of('\0'));
421
422 // Write out the ramdisks and bootconfig blocks
423 final_rd << boot_ramdisk.rdbuf() << vendor_boot_ramdisk.rdbuf()
424 << bootconfig;
425
426 // Append bootconfig length
427 bootconfig_size = bootconfig.size();
428 final_rd.write(reinterpret_cast<const char *>(&bootconfig_size),
429 sizeof(uint32_t));
430
431 // Append bootconfig checksum
432 uint32_t bootconfig_csum = 0;
433 for (auto i = 0; i < bootconfig_size; i++) {
434 bootconfig_csum += bootconfig[i];
435 }
436 final_rd.write(reinterpret_cast<const char *>(&bootconfig_csum),
437 sizeof(uint32_t));
438
439 // Append bootconfig trailer
440 final_rd << "#BOOTCONFIG\n";
441 final_rd.close();
442 }
443
444 // TODO(290586882) switch this function to rely on avb footers instead of
445 // the os version field in the boot image header.
446 // https://source.android.com/docs/core/architecture/bootloader/boot-image-header
ReadAndroidVersionFromBootImage(const std::string & boot_image_path)447 Result<std::string> ReadAndroidVersionFromBootImage(
448 const std::string& boot_image_path) {
449 // temp dir path length is chosen to be larger than sun_path_length (108)
450 char tmp_dir[200];
451 sprintf(tmp_dir, "%s/XXXXXX", StringFromEnv("TEMP", "/tmp").c_str());
452 char* unpack_dir = mkdtemp(tmp_dir);
453 if (!unpack_dir) {
454 return CF_ERR("boot image unpack dir could not be created");
455 }
456 bool unpack_status = GetAvbMetadataFromBootImage(boot_image_path, unpack_dir);
457 if (!unpack_status) {
458 RecursivelyRemoveDirectory(unpack_dir);
459 return CF_ERR("\"" + boot_image_path + "\" boot image unpack into \"" +
460 unpack_dir + "\" failed");
461 }
462
463 // dirty hack to read out boot params
464 size_t dir_path_len = strlen(tmp_dir);
465 std::string boot_params = ReadFile(strcat(unpack_dir, "/boot_params"));
466 unpack_dir[dir_path_len] = '\0';
467
468 RecursivelyRemoveDirectory(unpack_dir);
469 std::string os_version =
470 ExtractValue(boot_params, "Prop: com.android.build.boot.os_version -> ");
471 // if the OS version is "None", it wasn't set when the boot image was made.
472 if (os_version == "None") {
473 LOG(INFO) << "Could not extract os version from " << boot_image_path
474 << ". Defaulting to 0.0.0.";
475 return "0.0.0";
476 }
477
478 // os_version returned above is surrounded by single quotes. Removing the
479 // single quotes.
480 os_version.erase(remove(os_version.begin(), os_version.end(), '\''),
481 os_version.end());
482
483 std::regex re("[1-9][0-9]*([.][0-9]+)*");
484 CF_EXPECT(std::regex_match(os_version, re), "Version string is not a valid version \"" + os_version + "\"");
485 return os_version;
486 }
487 } // namespace cuttlefish
488