• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 "berberis/guest_os_primitives/guest_map_shadow.h"
18 
19 #include <sys/mman.h>
20 #include <climits>  // CHAR_BIT
21 #include <cstddef>
22 #include <mutex>
23 #include <tuple>
24 
25 #include "berberis/base/bit_util.h"
26 #include "berberis/base/forever_alloc.h"
27 #include "berberis/base/large_mmap.h"
28 #include "berberis/base/logging.h"
29 #include "berberis/base/mmap.h"
30 #include "berberis/guest_state/guest_addr.h"
31 #include "berberis/runtime_primitives/runtime_library.h"  // InvalidateGuestRange
32 
33 namespace berberis {
34 
35 namespace {
36 
37 // One bit per each 4K page.
38 constexpr size_t kGuestPageSizeLog2 = 12;
39 #if defined(BERBERIS_GUEST_LP64)
40 // On LP64 the address space is limited to 48 bits
41 constexpr size_t kGuestAddressSizeLog2 = 48;
42 constexpr size_t kMaxGuestAddress{0xffff'ffff'ffff};
43 #else
44 constexpr size_t kGuestAddressSizeLog2 = sizeof(GuestAddr) * CHAR_BIT;
45 constexpr size_t kMaxGuestAddress{0xffff'ffff};
46 #endif
47 constexpr size_t kGuestPageSize = 1 << kGuestPageSizeLog2;  // 4096
48 constexpr size_t kShadowSize = 1UL << (kGuestAddressSizeLog2 - kGuestPageSizeLog2 - 3);
49 
AlignDownGuestPageSize(GuestAddr addr)50 inline GuestAddr AlignDownGuestPageSize(GuestAddr addr) {
51   return AlignDown(addr, kGuestPageSize);
52 }
53 
AlignUpGuestPageSize(GuestAddr addr)54 inline GuestAddr AlignUpGuestPageSize(GuestAddr addr) {
55   return AlignUp(addr, kGuestPageSize);
56 }
57 
DoIntervalsIntersect(const void * start,const void * end,const void * other_start,const void * other_end)58 bool DoIntervalsIntersect(const void* start,
59                           const void* end,
60                           const void* other_start,
61                           const void* other_end) {
62   bool not_intersect = (other_end <= start) || (other_start >= end);
63   return !not_intersect;
64 }
65 
66 }  // namespace
67 
GetInstance()68 GuestMapShadow* GuestMapShadow::GetInstance() {
69   static auto* g_map_shadow = NewForever<GuestMapShadow>();
70   return g_map_shadow;
71 }
72 
IsExecAddr(GuestAddr addr) const73 bool GuestMapShadow::IsExecAddr(GuestAddr addr) const {
74   if (addr > kMaxGuestAddress) {
75     // Addresses outside the supported range are always non-executable.
76     // In practice we may end up here when parsing kernel addresses
77     // from /proc/self/maps.
78     return false;
79   }
80   uintptr_t page = addr >> kGuestPageSizeLog2;
81   return shadow_[page >> 3] & (1 << (page & 7));
82 }
83 
84 // Returns true if value changed.
SetExecAddr(GuestAddr addr,int set)85 bool GuestMapShadow::SetExecAddr(GuestAddr addr, int set) {
86   if (addr > kMaxGuestAddress) {
87     // See IsExecAddr for explanation.
88     return false;
89   }
90   uintptr_t page = addr >> kGuestPageSizeLog2;
91   uint8_t mask = 1 << (page & 7);
92   int old = shadow_[page >> 3] & mask;
93   if (set) {
94     shadow_[page >> 3] |= mask;
95     return old == 0;
96   } else {
97     shadow_[page >> 3] &= ~mask;
98     return old != 0;
99   }
100 }
101 
CopyExecutable(GuestAddr from,size_t from_size,GuestAddr to,size_t to_size)102 void GuestMapShadow::CopyExecutable(GuestAddr from,
103                                     size_t from_size,
104                                     GuestAddr to,
105                                     size_t to_size) {
106   CHECK_EQ(from, AlignDownGuestPageSize(from));
107   CHECK_EQ(to, AlignDownGuestPageSize(to));
108   // Regions must not overlap.
109   CHECK((from + from_size) <= to || (to + to_size) <= from);
110 
111   if (IsExecutable(from, from_size)) {
112     SetExecutable(to, to_size);
113   } else {
114     // Note, we also get here if old region is partially
115     // executable, to be on the safe side.
116     ClearExecutable(to, to_size);
117   }
118 }
119 
GuestMapShadow()120 GuestMapShadow::GuestMapShadow() : protected_maps_(&arena_) {
121   shadow_ = static_cast<uint8_t*>(LargeMmapImplOrDie(
122       {.size = kShadowSize, .flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE}));
123 }
124 
~GuestMapShadow()125 GuestMapShadow::~GuestMapShadow() {
126   MunmapOrDie(shadow_, kShadowSize);
127 }
128 
GetExecutableRegionSize(GuestAddr start,size_t scan_size) const129 std::tuple<bool, size_t> GuestMapShadow::GetExecutableRegionSize(GuestAddr start,
130                                                                  size_t scan_size) const {
131   GuestAddr pc = AlignDownGuestPageSize(start);
132   GuestAddr scan_end_pc = AlignUpGuestPageSize(start + scan_size);
133 
134   bool is_exec = IsExecAddr(pc);
135   for (pc += kGuestPageSize; pc < scan_end_pc; pc += kGuestPageSize) {
136     if (is_exec != IsExecAddr(pc)) {
137       break;
138     }
139   }
140   return {is_exec, pc - start};
141 }
142 
GetExecutable(GuestAddr start,size_t scan_size) const143 BitValue GuestMapShadow::GetExecutable(GuestAddr start, size_t scan_size) const {
144   auto [is_exec, region_size] = GetExecutableRegionSize(start, scan_size);
145   if (region_size < scan_size) {
146     return kBitMixed;
147   }
148   return is_exec ? kBitSet : kBitUnset;
149 }
150 
IsExecutable(GuestAddr start,size_t size) const151 bool GuestMapShadow::IsExecutable(GuestAddr start, size_t size) const {
152   return GetExecutable(start, size) == kBitSet;
153 }
154 
SetExecutable(GuestAddr start,size_t size)155 void GuestMapShadow::SetExecutable(GuestAddr start, size_t size) {
156   ALOGV("SetExecutable: %zx..%zx", start, start + size);
157   GuestAddr end = AlignUpGuestPageSize(start + size);
158   GuestAddr pc = AlignDownGuestPageSize(start);
159   while (pc < end) {
160     SetExecAddr(pc, 1);
161     pc += kGuestPageSize;
162   }
163 }
164 
ClearExecutable(GuestAddr start,size_t size)165 void GuestMapShadow::ClearExecutable(GuestAddr start, size_t size) {
166   ALOGV("ClearExecutable: %zx..%zx", start, start + size);
167   GuestAddr end = AlignUpGuestPageSize(start + size);
168   GuestAddr pc = AlignDownGuestPageSize(start);
169   bool changed = false;
170   while (pc < end) {
171     changed |= SetExecAddr(pc, 0);
172     pc += kGuestPageSize;
173   }
174   if (changed) {
175     InvalidateGuestRange(start, end);
176   }
177 }
178 
RemapExecutable(GuestAddr old_start,size_t old_size,GuestAddr new_start,size_t new_size)179 void GuestMapShadow::RemapExecutable(GuestAddr old_start,
180                                      size_t old_size,
181                                      GuestAddr new_start,
182                                      size_t new_size) {
183   ALOGV("RemapExecutable: from %zx..%zx to %zx..%zx",
184         old_start,
185         old_start + old_size,
186         new_start,
187         new_start + new_size);
188 
189   CHECK_EQ(old_start, AlignDownGuestPageSize(old_start));
190   CHECK_EQ(new_start, AlignDownGuestPageSize(new_start));
191   GuestAddr old_end_page = AlignUpGuestPageSize(old_start + old_size);
192   GuestAddr new_end_page = AlignUpGuestPageSize(new_start + new_size);
193 
194   // Special processing if only size is changed and regions overlap.
195   if (old_start == new_start) {
196     if (new_end_page <= old_end_page) {
197       ClearExecutable(new_end_page, old_end_page - new_end_page);
198     } else {
199       CopyExecutable(old_start, old_size, old_end_page, new_end_page - old_end_page);
200     }
201     return;
202   }
203 
204   // Otherwise, regions must not overlap.
205   CHECK((old_start + old_size) <= new_start || (new_start + new_size) <= old_start);
206 
207   CopyExecutable(old_start, old_size < new_size ? old_size : new_size, new_start, new_size);
208   ClearExecutable(old_start, old_size);
209 }
210 
AddProtectedMapping(const void * start,const void * end)211 void GuestMapShadow::AddProtectedMapping(const void* start, const void* end) {
212   std::lock_guard<std::mutex> lock(mutex_);
213   protected_maps_.emplace_back(std::make_pair(start, end));
214 }
215 
IntersectsWithProtectedMapping(const void * start,const void * end)216 bool GuestMapShadow::IntersectsWithProtectedMapping(const void* start, const void* end) {
217   std::lock_guard<std::mutex> lock(mutex_);
218   for (auto pair : protected_maps_) {
219     if (DoIntervalsIntersect(pair.first, pair.second, start, end)) {
220       return true;
221     }
222   }
223   return false;
224 }
225 
226 }  // namespace berberis
227