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