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