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