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