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