Do not crash on out-of-date oat files.

Check that the OatHeader is valid before searching
the key-value store for "debuggable".

(cherry picked from commit ef8c3376a812e943d4e7c4ef96f17e218d11bc7c)

Test: New test in dex2oat_test.cc .
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 179221298
Change-Id: I77585ae3e78d19117e31a6ad0ebee6e32bff8730
Merged-In: Ib10c919883b31b71810cc876fb38105b48a58bcb
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index ab33c19..4de8265 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -44,6 +44,8 @@
 #include "dex/dex_file_loader.h"
 #include "dex2oat_environment_test.h"
 #include "dex2oat_return_codes.h"
+#include "elf_file.h"
+#include "elf_file_impl.h"
 #include "gc_root-inl.h"
 #include "intern_table-inl.h"
 #include "oat.h"
@@ -2489,4 +2491,108 @@
   RunTest();
 }
 
+// Regression test for bug 179221298.
+TEST_F(Dex2oatTest, LoadOutOfDateOatFile) {
+  std::unique_ptr<const DexFile> dex(OpenTestDexFile("ManyMethods"));
+  std::string out_dir = GetScratchDir();
+  const std::string base_oat_name = out_dir + "/base.oat";
+  ASSERT_TRUE(GenerateOdexForTest(dex->GetLocation(),
+                                  base_oat_name,
+                                  CompilerFilter::Filter::kSpeed,
+                                  { "--deduplicate-code=false" },
+                                  /*expect_success=*/ true,
+                                  /*use_fd=*/ false,
+                                  /*use_zip_fd=*/ false));
+
+  // Check that we can open the oat file as executable.
+  {
+    std::string error_msg;
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+                                                     base_oat_name.c_str(),
+                                                     base_oat_name.c_str(),
+                                                     /*executable=*/ true,
+                                                     /*low_4gb=*/ false,
+                                                     dex->GetLocation(),
+                                                     &error_msg));
+    ASSERT_TRUE(odex_file != nullptr) << error_msg;
+  }
+
+  // Rewrite the oat file with wrong version and bogus contents.
+  {
+    std::unique_ptr<File> file(OS::OpenFileReadWrite(base_oat_name.c_str()));
+    ASSERT_TRUE(file != nullptr);
+    // Retrieve the offset and size of the embedded oat file.
+    size_t oatdata_offset;
+    size_t oatdata_size;
+    {
+      std::string error_msg;
+      std::unique_ptr<ElfFile> elf_file(ElfFile::Open(file.get(),
+                                                      /*writable=*/ false,
+                                                      /*program_header_only=*/ true,
+                                                      /*low_4gb=*/ false,
+                                                      &error_msg));
+      ASSERT_TRUE(elf_file != nullptr) << error_msg;
+      ASSERT_TRUE(elf_file->Load(file.get(),
+                                 /*executable=*/ false,
+                                 /*low_4gb=*/ false,
+                                 /*reservation=*/ nullptr,
+                                 &error_msg)) << error_msg;
+      const uint8_t* base_address = elf_file->Is64Bit()
+          ? elf_file->GetImpl64()->GetBaseAddress()
+          : elf_file->GetImpl32()->GetBaseAddress();
+      const uint8_t* oatdata = elf_file->FindDynamicSymbolAddress("oatdata");
+      ASSERT_TRUE(oatdata != nullptr);
+      ASSERT_TRUE(oatdata > base_address);
+      // Note: We're assuming here that the virtual address offset is the same
+      // as file offset. This is currently true for all oat files we generate.
+      oatdata_offset = static_cast<size_t>(oatdata - base_address);
+      const uint8_t* oatlastword = elf_file->FindDynamicSymbolAddress("oatlastword");
+      ASSERT_TRUE(oatlastword != nullptr);
+      ASSERT_TRUE(oatlastword > oatdata);
+      oatdata_size = oatlastword - oatdata;
+    }
+
+    // Check that we have the right `oatdata_offset`.
+    int64_t length = file->GetLength();
+    ASSERT_GE(length, static_cast<ssize_t>(oatdata_offset + sizeof(OatHeader)));
+    alignas(OatHeader) uint8_t header_data[sizeof(OatHeader)];
+    ASSERT_TRUE(file->PreadFully(header_data, sizeof(header_data), oatdata_offset));
+    const OatHeader& header = reinterpret_cast<const OatHeader&>(header_data);
+    ASSERT_TRUE(header.IsValid()) << header.GetValidationErrorMessage();
+
+    // Overwrite all oat data from version onwards with bytes with value 4.
+    // (0x04040404 is not a valid version, we're using three decimal digits and '\0'.)
+    //
+    // We previously tried to find the value for key "debuggable" (bug 179221298)
+    // in the key-value store before checking the oat header. This test tries to
+    // ensure that such early processing of the key-value store shall crash.
+    // Reading 0x04040404 as the size of the key-value store yields a bit over
+    // 64MiB which should hopefully include some unmapped memory beyond the end
+    // of the loaded oat file. Overwriting the whole embedded oat file ensures
+    // that we do not match the key within the oat file but we could still
+    // accidentally match it in the additional sections of the elf file, so this
+    // approach could fail to catch similar issues. At the time of writing, this
+    // test crashed when run without the fix on 64-bit host (but not 32-bit).
+    static constexpr size_t kVersionOffset = sizeof(OatHeader::kOatMagic);
+    static_assert(kVersionOffset < sizeof(OatHeader));
+    std::vector<uint8_t> data(oatdata_size - kVersionOffset, 4u);
+    ASSERT_TRUE(file->PwriteFully(data.data(), data.size(), oatdata_offset + kVersionOffset));
+    UNUSED(oatdata_size);
+    CHECK_EQ(file->FlushClose(), 0) << "Could not flush and close oat file";
+  }
+
+  // Check that we reject the oat file without crashing.
+  {
+    std::string error_msg;
+    std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/ -1,
+                                                     base_oat_name.c_str(),
+                                                     base_oat_name.c_str(),
+                                                     /*executable=*/ true,
+                                                     /*low_4gb=*/ false,
+                                                     dex->GetLocation(),
+                                                     &error_msg));
+    ASSERT_FALSE(odex_file != nullptr);
+  }
+}
+
 }  // namespace art
diff --git a/runtime/elf_file_impl.h b/runtime/elf_file_impl.h
index 9900c76..a5937ff 100644
--- a/runtime/elf_file_impl.h
+++ b/runtime/elf_file_impl.h
@@ -59,6 +59,10 @@
     return file_path_;
   }
 
+  uint8_t* GetBaseAddress() const {
+    return base_address_;
+  }
+
   uint8_t* Begin() const {
     return map_.Begin();
   }
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index d72fc7e..48e34af 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -275,7 +275,17 @@
   // We sometimes load oat files without a runtime (eg oatdump) and don't want to do anything in
   // that case. If we are debuggable there are no -quick opcodes to unquicken. If the runtime is not
   // debuggable we don't care whether there are -quick opcodes or not so no need to do anything.
-  return Runtime::Current() != nullptr && !IsDebuggable() && Runtime::Current()->IsJavaDebuggable();
+  Runtime* runtime = Runtime::Current();
+  return (runtime != nullptr && runtime->IsJavaDebuggable()) &&
+         // Note: This is called before `OatFileBase::Setup()` where we validate the
+         // oat file contents. Check that we have at least a valid header, including
+         // oat file version, to avoid parsing the key-value store for a different
+         // version (out-of-date oat file) which can lead to crashes. b/179221298.
+         // TODO: While this is a poor workaround and the correct solution would be
+         // to postpone the unquickening check until after `OatFileBase::Setup()`,
+         // we prefer to avoid larger rewrites because quickening is deprecated and
+         // should be removed completely anyway. b/170086509
+         (GetOatHeader().IsValid() && !IsDebuggable());
 }
 
 bool OatFileBase::LoadVdex(const std::string& vdex_filename,