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