Add boot image checksums to image header.

This adds an early validity check, so that we do not need to
load an image extension and oat file before we can discard
them as out-of-date based on the checksums from oat header.

Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing --interp-ac
Bug: 119800099
Change-Id: I951e8429f8dde73f3fc41c36dffe5a657a0db62b
diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc
index 1a5701d..33d122b 100644
--- a/dex2oat/linker/image_test.cc
+++ b/dex2oat/linker/image_test.cc
@@ -87,6 +87,8 @@
                              oat_file_end,
                              /*boot_image_begin=*/ 0u,
                              /*boot_image_size=*/ 0u,
+                             /*boot_image_component_count=*/ 0u,
+                             /*boot_image_checksum=*/ 0u,
                              sizeof(void*));
 
     ASSERT_TRUE(image_header.IsValid());
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index ede5ef7..eb87b82 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -2653,6 +2653,22 @@
     }
   }
 
+  // Compute boot image checksums for the primary component, leave as 0 otherwise.
+  uint32_t boot_image_components = 0u;
+  uint32_t boot_image_checksums = 0u;
+  if (oat_index == 0u) {
+    const std::vector<gc::space::ImageSpace*>& image_spaces =
+        Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    boot_image_components = dchecked_integral_cast<uint32_t>(image_spaces.size());
+    DCHECK_EQ(boot_image_components == 0u, compiler_options_.IsBootImage());
+    for (uint32_t i = 0; i != boot_image_components; ) {
+      const ImageHeader& header = image_spaces[i]->GetImageHeader();
+      boot_image_checksums ^= header.GetImageChecksum();
+      DCHECK_LE(header.GetComponentCount(), boot_image_components - i);
+      i += header.GetComponentCount();
+    }
+  }
+
   // Create the image sections.
   auto section_info_pair = image_info.CreateImageSections();
   const size_t image_end = section_info_pair.first;
@@ -2695,6 +2711,8 @@
       PointerToLowMemUInt32(oat_file_end),
       boot_image_begin_,
       boot_image_size_,
+      boot_image_components,
+      boot_image_checksums,
       static_cast<uint32_t>(target_ptr_size_));
 }
 
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index 88f5d4e..b3a14e2 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -126,6 +126,8 @@
         /*oat_file_end=*/ PointerToLowMemUInt32(oat_map.Begin() + oat_size),
         /*boot_image_begin=*/ 0u,
         /*boot_image_size=*/ 0u,
+        /*boot_image_component_count=*/ 0u,
+        /*boot_image_checksum=*/ 0u,
         /*pointer_size=*/ sizeof(void*));
     return new DummyImageSpace(std::move(image_map),
                                std::move(live_bitmap),
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 31dffc8..9ff799c 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -819,6 +819,10 @@
                                   image_filename);
         return nullptr;
       }
+      if (!ValidateBootImageChecksum(image_filename, *image_header, oat_file, error_msg)) {
+        DCHECK(!error_msg->empty());
+        return nullptr;
+      }
     }
 
     if (VLOG_IS_ON(startup)) {
@@ -942,6 +946,72 @@
   }
 
  private:
+  static bool ValidateBootImageChecksum(const char* image_filename,
+                                        const ImageHeader& image_header,
+                                        const OatFile* oat_file,
+                                        /*out*/std::string* error_msg) {
+    // Use the boot image component count to calculate the checksum from
+    // the appropriate number of boot image chunks.
+    const std::vector<ImageSpace*>& image_spaces =
+        Runtime::Current()->GetHeap()->GetBootImageSpaces();
+    uint32_t boot_image_component_count = image_header.GetBootImageComponentCount();
+    size_t image_spaces_size = image_spaces.size();
+    if (boot_image_component_count > image_spaces_size) {
+      *error_msg = StringPrintf("Too many boot image dependencies (%u > %zu) in image %s",
+                                boot_image_component_count,
+                                image_spaces_size,
+                                image_filename);
+      return false;
+    }
+    uint32_t checksum = 0u;
+    size_t chunk_count = 0u;
+    for (size_t component_count = 0u; component_count != boot_image_component_count; ) {
+      const ImageHeader& current_header = image_spaces[component_count]->GetImageHeader();
+      if (current_header.GetComponentCount() > boot_image_component_count - component_count) {
+        *error_msg = StringPrintf("Boot image component count in %s ends in the middle of a chunk, "
+                                      "%u is between %zu and %zu",
+                                  image_filename,
+                                  boot_image_component_count,
+                                  component_count,
+                                  component_count + current_header.GetComponentCount());
+        return false;
+      }
+      component_count += current_header.GetComponentCount();
+      checksum ^= current_header.GetImageChecksum();
+      chunk_count += 1u;
+    }
+    if (image_header.GetBootImageChecksum() != checksum) {
+      *error_msg = StringPrintf("Boot image checksum mismatch (0x%x != 0x%x) in image %s",
+                                image_header.GetBootImageChecksum(),
+                                checksum,
+                                image_filename);
+      return false;
+    }
+    // Oat checksums, if present, have already been validated, so we know that
+    // they match the loaded image spaces. Therefore, we just verify that they
+    // are consistent in the number of boot image chunks they list by looking
+    // for the kImageChecksumPrefix at the start of each component.
+    const char* oat_boot_class_path_checksums =
+        oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
+    if (oat_boot_class_path_checksums != nullptr) {
+      size_t oat_bcp_chunk_count = 0u;
+      while (*oat_boot_class_path_checksums == kImageChecksumPrefix) {
+        oat_bcp_chunk_count += 1u;
+        // Find the start of the next component if any.
+        const char* separator = strchr(oat_boot_class_path_checksums, ':');
+        oat_boot_class_path_checksums = (separator != nullptr) ? separator + 1u : "";
+      }
+      if (oat_bcp_chunk_count != chunk_count) {
+        *error_msg = StringPrintf("Boot image chunk count mismatch (%zu != %zu) in image %s",
+                                  oat_bcp_chunk_count,
+                                  chunk_count,
+                                  image_filename);
+        return false;
+      }
+    }
+    return true;
+  }
+
   static MemMap LoadImageFile(const char* image_filename,
                               const char* image_location,
                               const ImageHeader& image_header,
@@ -1413,6 +1483,8 @@
     size_t component_count;
     size_t reservation_size;
     uint32_t checksum;
+    uint32_t boot_image_component_count;
+    uint32_t boot_image_checksum;
   };
 
   BootImageLayout(const std::string& image_location, ArrayRef<const std::string> boot_class_path)
@@ -1493,6 +1565,10 @@
       /*out*/std::vector<std::pair<std::string, size_t>>* base_locations_and_bcp_indexes,
       /*out*/std::string* error_msg);
 
+  bool ValidateBootImageChecksum(const std::string& actual_filename,
+                                 const ImageHeader& header,
+                                 /*out*/std::string* error_msg);
+
   bool ReadHeader(const std::string& base_location,
                   const std::string& base_filename,
                   size_t bcp_index,
@@ -1671,6 +1747,56 @@
   return true;
 }
 
+bool ImageSpace::BootImageLayout::ValidateBootImageChecksum(const std::string& actual_filename,
+                                                            const ImageHeader& header,
+                                                            /*out*/std::string* error_msg) {
+  uint32_t boot_image_component_count = header.GetBootImageComponentCount();
+  if (chunks_.empty() != (boot_image_component_count == 0u)) {
+    *error_msg = StringPrintf("Unexpected boot image component count in %s: %u, %s",
+                              actual_filename.c_str(),
+                              header.GetImageReservationSize(),
+                              chunks_.empty() ? "should be 0" : "should not be 0");
+    return false;
+  }
+  uint32_t component_count = 0u;
+  uint32_t composite_checksum = 0u;
+  for (const ImageChunk& chunk : chunks_) {
+    if (component_count == boot_image_component_count) {
+      break;  // Hit the component count.
+    }
+    if (chunk.start_index != component_count) {
+      break;  // End of contiguous chunks, fail below; same as reaching end of `chunks_`.
+    }
+    if (chunk.component_count > boot_image_component_count - component_count) {
+      *error_msg = StringPrintf("Boot image component count in %s ends in the middle of a chunk, "
+                                    "%u is between %u and %zu",
+                                actual_filename.c_str(),
+                                boot_image_component_count,
+                                component_count,
+                                component_count + chunk.component_count);
+      return false;
+    }
+    component_count += chunk.component_count;
+    composite_checksum ^= chunk.checksum;
+  }
+  DCHECK_LE(component_count, boot_image_component_count);
+  if (component_count != boot_image_component_count) {
+    *error_msg = StringPrintf("Missing boot image components for checksum in %s: %u > %u",
+                              actual_filename.c_str(),
+                              boot_image_component_count,
+                              component_count);
+    return false;
+  }
+  if (composite_checksum != header.GetBootImageChecksum()) {
+    *error_msg = StringPrintf("Boot image checksum mismatch in %s: 0x%08x != 0x%08x",
+                              actual_filename.c_str(),
+                              header.GetBootImageChecksum(),
+                              composite_checksum);
+    return false;
+  }
+  return true;
+}
+
 bool ImageSpace::BootImageLayout::ReadHeader(const std::string& base_location,
                                              const std::string& base_filename,
                                              size_t bcp_index,
@@ -1703,6 +1829,9 @@
                               allowed_reservation_size);
     return false;
   }
+  if (!ValidateBootImageChecksum(actual_filename, header, error_msg)) {
+    return false;
+  }
 
   if (chunks_.empty()) {
     base_address_ = reinterpret_cast32<uint32_t>(header.GetImageBegin());
@@ -1714,6 +1843,8 @@
   chunk.component_count = header.GetComponentCount();
   chunk.reservation_size = header.GetImageReservationSize();
   chunk.checksum = header.GetImageChecksum();
+  chunk.boot_image_component_count = header.GetBootImageComponentCount();
+  chunk.boot_image_checksum = header.GetBootImageChecksum();
   chunks_.push_back(std::move(chunk));
   next_bcp_index_ = bcp_index + header.GetComponentCount();
   total_component_count_ += header.GetComponentCount();
@@ -2448,7 +2579,7 @@
   bool OpenOatFile(ImageSpace* space,
                    const std::string& dex_filename,
                    bool validate_oat_file,
-                   ArrayRef<const std::unique_ptr<ImageSpace>> available_dependencies,
+                   ArrayRef<const std::unique_ptr<ImageSpace>> dependencies,
                    TimingLogger* logger,
                    /*inout*/MemMap* image_reservation,
                    /*out*/std::string* error_msg) {
@@ -2509,7 +2640,7 @@
                                     space->GetName());
           return false;
         }
-      } else if (available_dependencies.empty()) {
+      } else if (dependencies.empty()) {
         std::string expected_boot_class_path = android::base::Join(ArrayRef<const std::string>(
               boot_class_path_locations_).SubArray(0u, component_count), ':');
         if (expected_boot_class_path != oat_boot_class_path) {
@@ -2525,7 +2656,7 @@
         if (!VerifyBootClassPathChecksums(
                  oat_boot_class_path_checksums,
                  oat_boot_class_path,
-                 available_dependencies,
+                 dependencies,
                  ArrayRef<const std::string>(boot_class_path_locations_),
                  ArrayRef<const std::string>(boot_class_path_),
                  &local_error_msg)) {
@@ -2592,6 +2723,14 @@
 
     bool is_extension = (chunk.start_index != 0u);
     DCHECK_NE(spaces->empty(), is_extension);
+    if (max_image_space_dependencies < chunk.boot_image_component_count) {
+      DCHECK(is_extension);
+      *error_msg = StringPrintf("Missing dependencies for extension component %s, %zu < %u",
+                                boot_class_path_locations_[chunk.start_index].c_str(),
+                                max_image_space_dependencies,
+                                chunk.boot_image_component_count);
+      return false;
+    }
     ArrayRef<const std::string> requested_bcp_locations =
         ArrayRef<const std::string>(boot_class_path_locations_).SubArray(
             chunk.start_index, chunk.component_count);
@@ -2612,24 +2751,34 @@
           !Loader::CheckImageComponentCount(*space, expected_component_count, error_msg)) {
         return false;
       }
-      if (i == 0 && chunk.checksum != space->GetImageHeader().GetImageChecksum()) {
-        *error_msg = StringPrintf("Image checksum modified since previously read from %s, "
-                                      "received %u, expected %u",
+      const ImageHeader& header = space->GetImageHeader();
+      if (i == 0 && (chunk.checksum != header.GetImageChecksum() ||
+                     chunk.boot_image_component_count != header.GetBootImageComponentCount() ||
+                     chunk.boot_image_checksum != header.GetBootImageChecksum())) {
+        *error_msg = StringPrintf("Image header modified since previously read from %s; "
+                                      "checksum: 0x%08x -> 0x%08x,"
+                                      "boot_image_component_count: %u -> %u, "
+                                      "boot_image_checksum: 0x%08x -> 0x%08x",
                                   space->GetImageFilename().c_str(),
-                                  space->GetImageHeader().GetImageChecksum(),
-                                  chunk.checksum);
+                                  chunk.checksum,
+                                  header.GetImageChecksum(),
+                                  chunk.boot_image_component_count,
+                                  header.GetBootImageComponentCount(),
+                                  chunk.boot_image_checksum,
+                                  header.GetBootImageChecksum());
         return false;
       }
     }
-    ArrayRef<const std::unique_ptr<ImageSpace>> available_dependencies =
-        ArrayRef<const std::unique_ptr<ImageSpace>>(*spaces).SubArray(/*pos=*/ 0u,
-                                                                      max_image_space_dependencies);
+    DCHECK_GE(max_image_space_dependencies, chunk.boot_image_component_count);
+    ArrayRef<const std::unique_ptr<ImageSpace>> dependencies =
+        ArrayRef<const std::unique_ptr<ImageSpace>>(*spaces).SubArray(
+            /*pos=*/ 0u, chunk.boot_image_component_count);
     for (std::size_t i = 0u, size = locations.size(); i != size; ++i) {
       ImageSpace* space = (*spaces)[spaces->size() - chunk.component_count + i].get();
       if (!OpenOatFile(space,
                        boot_class_path_[chunk.start_index + i],
                        validate_oat_file,
-                       available_dependencies,
+                       dependencies,
                        logger,
                        image_reservation,
                        error_msg)) {
@@ -3259,10 +3408,17 @@
     DCHECK(!error_msg->empty());
     return false;
   }
+  const size_t num_image_spaces = image_spaces.size();
+  if (num_image_spaces != oat_bcp_size) {
+    *error_msg = StringPrintf("Image header records more dependencies (%zu) than BCP (%zu)",
+                              num_image_spaces,
+                              oat_bcp_size);
+    return false;
+  }
 
   // Verify image checksums.
   size_t image_pos = 0u;
-  while (image_pos != image_spaces.size() && StartsWith(oat_checksums, "i")) {
+  while (image_pos != num_image_spaces && StartsWith(oat_checksums, "i")) {
     // Verify the current image checksum.
     const ImageHeader& current_header = image_spaces[image_pos]->GetImageHeader();
     uint32_t component_count = current_header.GetComponentCount();
diff --git a/runtime/image.cc b/runtime/image.cc
index 08b81c1..06ba946 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -29,7 +29,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '7', '9', '\0' };  // FP16ToHalf intrinsic
+const uint8_t ImageHeader::kImageVersion[] = { '0', '8', '0', '\0' };  // Chained checksums.
 
 ImageHeader::ImageHeader(uint32_t image_reservation_size,
                          uint32_t component_count,
@@ -44,6 +44,8 @@
                          uint32_t oat_file_end,
                          uint32_t boot_image_begin,
                          uint32_t boot_image_size,
+                         uint32_t boot_image_component_count,
+                         uint32_t boot_image_checksum,
                          uint32_t pointer_size)
   : image_reservation_size_(image_reservation_size),
     component_count_(component_count),
@@ -57,6 +59,8 @@
     oat_file_end_(oat_file_end),
     boot_image_begin_(boot_image_begin),
     boot_image_size_(boot_image_size),
+    boot_image_component_count_(boot_image_component_count),
+    boot_image_checksum_(boot_image_checksum),
     image_roots_(image_roots),
     pointer_size_(pointer_size) {
   CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize));
diff --git a/runtime/image.h b/runtime/image.h
index a2d163a..12950a3 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -137,6 +137,8 @@
               uint32_t oat_file_end,
               uint32_t boot_image_begin,
               uint32_t boot_image_size,
+              uint32_t boot_image_component_count,
+              uint32_t boot_image_checksum,
               uint32_t pointer_size);
 
   bool IsValid() const;
@@ -350,6 +352,14 @@
     return boot_image_size_;
   }
 
+  uint32_t GetBootImageComponentCount() const {
+    return boot_image_component_count_;
+  }
+
+  uint32_t GetBootImageChecksum() const {
+    return boot_image_checksum_;
+  }
+
   uint64_t GetDataSize() const {
     return data_size_;
   }
@@ -461,10 +471,15 @@
   // .so files. Used for positioning a following alloc spaces.
   uint32_t oat_file_end_ = 0u;
 
-  // Boot image begin and end (app image headers only).
+  // Boot image begin and end (only applies to boot image extension and app image headers).
   uint32_t boot_image_begin_ = 0u;
   uint32_t boot_image_size_ = 0u;  // Includes heap (*.art) and code (.oat).
 
+  // Number of boot image components that this image depends on and their composite checksum
+  // (only applies to boot image extension and app image headers).
+  uint32_t boot_image_component_count_ = 0u;
+  uint32_t boot_image_checksum_ = 0u;
+
   // Absolute address of an Object[] of objects needed to reinitialize from an image.
   uint32_t image_roots_ = 0u;