MapShadow: implement GetExecutableRegionSize

This will be used for accurate guest-exec mappings splitting in
/proc/self/maps emulation.
It can also be used to fix long-standing problem with executability
checks in TranslateRegion.

Flag: EXEMPT NDK
Bug: 382709531
Test: added unit test, tree-hugger
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:6c52f9c807e12af004fe661422f318840be2d056)
Merged-In: If2ce6c6073add687e75498c548a9a99f65ef6f7a
Change-Id: If2ce6c6073add687e75498c548a9a99f65ef6f7a
diff --git a/guest_os_primitives/guest_map_shadow.cc b/guest_os_primitives/guest_map_shadow.cc
index f5b4594..4fecbc5 100644
--- a/guest_os_primitives/guest_map_shadow.cc
+++ b/guest_os_primitives/guest_map_shadow.cc
@@ -18,7 +18,9 @@
 
 #include <sys/mman.h>
 #include <climits>  // CHAR_BIT
+#include <cstddef>
 #include <mutex>
+#include <tuple>
 
 #include "berberis/base/bit_util.h"
 #include "berberis/base/forever_alloc.h"
@@ -124,17 +126,24 @@
   MunmapOrDie(shadow_, kShadowSize);
 }
 
-BitValue GuestMapShadow::GetExecutable(GuestAddr start, size_t size) const {
+std::tuple<bool, size_t> GuestMapShadow::GetExecutableRegionSize(GuestAddr start,
+                                                                 size_t scan_size) const {
   GuestAddr pc = AlignDownGuestPageSize(start);
-  GuestAddr end = AlignUpGuestPageSize(start + size);
+  GuestAddr scan_end_pc = AlignUpGuestPageSize(start + scan_size);
 
   bool is_exec = IsExecAddr(pc);
-  pc += kGuestPageSize;
-  while (pc < end) {
+  for (pc += kGuestPageSize; pc < scan_end_pc; pc += kGuestPageSize) {
     if (is_exec != IsExecAddr(pc)) {
-      return kBitMixed;
+      break;
     }
-    pc += kGuestPageSize;
+  }
+  return {is_exec, pc - start};
+}
+
+BitValue GuestMapShadow::GetExecutable(GuestAddr start, size_t scan_size) const {
+  auto [is_exec, region_size] = GetExecutableRegionSize(start, scan_size);
+  if (region_size < scan_size) {
+    return kBitMixed;
   }
   return is_exec ? kBitSet : kBitUnset;
 }
diff --git a/guest_os_primitives/guest_map_shadow_test.cc b/guest_os_primitives/guest_map_shadow_test.cc
index 5475d71..a4a6798 100644
--- a/guest_os_primitives/guest_map_shadow_test.cc
+++ b/guest_os_primitives/guest_map_shadow_test.cc
@@ -32,12 +32,21 @@
     ::testing::Test::SetUp();
     InitLargeMmap();
   }
+
+  template <bool kExpectedExec, size_t kExpectedSize>
+  void ExpectExecRegionSize(GuestAddr start, size_t test_size) {
+    auto [is_exec, size] = shadow_.GetExecutableRegionSize(start, test_size);
+    EXPECT_EQ(is_exec, kExpectedExec);
+    EXPECT_EQ(size, kExpectedSize);
+  }
+
+  GuestMapShadow shadow_;
 };
 
 constexpr GuestAddr kGuestAddr{0x7f018000};
 constexpr size_t kGuestRegionSize{0x00020000};
 
-TEST_F(GuestMapShadowTest, smoke) {
+TEST_F(GuestMapShadowTest, Basic) {
   auto shadow = std::make_unique<GuestMapShadow>();
 
   ASSERT_EQ(kBitUnset, shadow->GetExecutable(kGuestAddr, kGuestRegionSize));
@@ -65,7 +74,7 @@
   ASSERT_TRUE(!shadow->IsExecutable(kGuestAddr, kGuestRegionSize));
 }
 
-TEST_F(GuestMapShadowTest, remap) {
+TEST_F(GuestMapShadowTest, Remap) {
   constexpr GuestAddr kRemapAddr = 0x00107000;
   constexpr size_t kRemapRegionSize1 = kGuestRegionSize / 2;
   constexpr size_t kRemapRegionSize2 = kGuestRegionSize * 2;
@@ -157,6 +166,21 @@
 
 #endif
 
+TEST_F(GuestMapShadowTest, GetExecutableRegionSize) {
+  shadow_.SetExecutable(kGuestAddr, kGuestRegionSize);
+
+  ExpectExecRegionSize<false, kGuestRegionSize>(kGuestAddr - kGuestRegionSize, kGuestRegionSize);
+  ExpectExecRegionSize<true, kGuestRegionSize>(kGuestAddr, kGuestRegionSize);
+  ExpectExecRegionSize<false, kGuestRegionSize>(kGuestAddr + kGuestRegionSize, kGuestRegionSize);
+
+  // Cases where region size is shorter than the tested size.
+  ExpectExecRegionSize<false, kGuestRegionSize / 2>(kGuestAddr - kGuestRegionSize / 2,
+                                                    kGuestRegionSize);
+  ExpectExecRegionSize<true, kGuestRegionSize / 2>(kGuestAddr + kGuestRegionSize / 2,
+                                                   kGuestRegionSize);
+  ExpectExecRegionSize<true, kGuestRegionSize>(kGuestAddr, kGuestRegionSize * 2);
+}
+
 }  // namespace
 
 }  // namespace berberis
diff --git a/guest_os_primitives/include/berberis/guest_os_primitives/guest_map_shadow.h b/guest_os_primitives/include/berberis/guest_os_primitives/guest_map_shadow.h
index 5cf3009..e9774d7 100644
--- a/guest_os_primitives/include/berberis/guest_os_primitives/guest_map_shadow.h
+++ b/guest_os_primitives/include/berberis/guest_os_primitives/guest_map_shadow.h
@@ -38,6 +38,8 @@
   GuestMapShadow();
   ~GuestMapShadow();
 
+  [[nodiscard]] std::tuple<bool, size_t> GetExecutableRegionSize(GuestAddr start,
+                                                                 size_t max_size) const;
   [[nodiscard]] BitValue GetExecutable(GuestAddr start, size_t size) const;
 
   // Check if region start..start+size is fully executable.