1 // Copyright 2016 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6
7 #include <vm/vm_address_region.h>
8
9 #include "vm_priv.h"
10 #include <assert.h>
11 #include <err.h>
12 #include <fbl/alloc_checker.h>
13 #include <inttypes.h>
14 #include <lib/vdso.h>
15 #include <pow2.h>
16 #include <trace.h>
17 #include <vm/vm.h>
18 #include <vm/vm_aspace.h>
19 #include <vm/vm_object.h>
20 #include <zircon/types.h>
21
22 #define LOCAL_TRACE MAX(VM_GLOBAL_TRACE, 0)
23
VmAddressRegion(VmAspace & aspace,vaddr_t base,size_t size,uint32_t vmar_flags)24 VmAddressRegion::VmAddressRegion(VmAspace& aspace, vaddr_t base, size_t size, uint32_t vmar_flags)
25 : VmAddressRegionOrMapping(base, size, vmar_flags | VMAR_CAN_RWX_FLAGS,
26 &aspace, nullptr) {
27
28 // We add in CAN_RWX_FLAGS above, since an address space can't usefully
29 // contain a process without all of these.
30
31 strlcpy(const_cast<char*>(name_), "root", sizeof(name_));
32 LTRACEF("%p '%s'\n", this, name_);
33 }
34
VmAddressRegion(VmAddressRegion & parent,vaddr_t base,size_t size,uint32_t vmar_flags,const char * name)35 VmAddressRegion::VmAddressRegion(VmAddressRegion& parent, vaddr_t base, size_t size,
36 uint32_t vmar_flags, const char* name)
37 : VmAddressRegionOrMapping(base, size, vmar_flags, parent.aspace_.get(),
38 &parent) {
39
40 strlcpy(const_cast<char*>(name_), name, sizeof(name_));
41 LTRACEF("%p '%s'\n", this, name_);
42 }
43
VmAddressRegion(VmAspace & kernel_aspace)44 VmAddressRegion::VmAddressRegion(VmAspace& kernel_aspace)
45 : VmAddressRegion(kernel_aspace, kernel_aspace.base(), kernel_aspace.size(),
46 VMAR_FLAG_CAN_MAP_SPECIFIC) {
47
48 // Activate the kernel root aspace immediately
49 state_ = LifeCycleState::ALIVE;
50 }
51
VmAddressRegion()52 VmAddressRegion::VmAddressRegion()
53 : VmAddressRegionOrMapping(0, 0, 0, nullptr, nullptr) {
54
55 strlcpy(const_cast<char*>(name_), "dummy", sizeof(name_));
56 LTRACEF("%p '%s'\n", this, name_);
57 }
58
CreateRoot(VmAspace & aspace,uint32_t vmar_flags,fbl::RefPtr<VmAddressRegion> * out)59 zx_status_t VmAddressRegion::CreateRoot(VmAspace& aspace, uint32_t vmar_flags,
60 fbl::RefPtr<VmAddressRegion>* out) {
61 DEBUG_ASSERT(out);
62
63 fbl::AllocChecker ac;
64 auto vmar = new (&ac) VmAddressRegion(aspace, aspace.base(), aspace.size(), vmar_flags);
65 if (!ac.check()) {
66 return ZX_ERR_NO_MEMORY;
67 }
68
69 vmar->state_ = LifeCycleState::ALIVE;
70 *out = fbl::AdoptRef(vmar);
71 return ZX_OK;
72 }
73
CreateSubVmarInternal(size_t offset,size_t size,uint8_t align_pow2,uint32_t vmar_flags,fbl::RefPtr<VmObject> vmo,uint64_t vmo_offset,uint arch_mmu_flags,const char * name,fbl::RefPtr<VmAddressRegionOrMapping> * out)74 zx_status_t VmAddressRegion::CreateSubVmarInternal(size_t offset, size_t size, uint8_t align_pow2,
75 uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
76 uint64_t vmo_offset, uint arch_mmu_flags,
77 const char* name,
78 fbl::RefPtr<VmAddressRegionOrMapping>* out) {
79 DEBUG_ASSERT(out);
80
81 Guard<fbl::Mutex> guard{aspace_->lock()};
82 if (state_ != LifeCycleState::ALIVE) {
83 return ZX_ERR_BAD_STATE;
84 }
85
86 if (size == 0) {
87 return ZX_ERR_INVALID_ARGS;
88 }
89
90 // Check if there are any RWX privileges that the child would have that the
91 // parent does not.
92 if (vmar_flags & ~flags_ & VMAR_CAN_RWX_FLAGS) {
93 return ZX_ERR_ACCESS_DENIED;
94 }
95
96 bool is_specific_overwrite = static_cast<bool>(vmar_flags & VMAR_FLAG_SPECIFIC_OVERWRITE);
97 bool is_specific = static_cast<bool>(vmar_flags & VMAR_FLAG_SPECIFIC) || is_specific_overwrite;
98 if (!is_specific && offset != 0) {
99 return ZX_ERR_INVALID_ARGS;
100 }
101
102 // Check to see if a cache policy exists if a VMO is passed in. VMOs that do not support
103 // cache policy return ERR_UNSUPPORTED, anything aside from that and ZX_OK is an error.
104 if (vmo) {
105 uint32_t cache_policy = vmo->GetMappingCachePolicy();
106 // Warn in the event that we somehow receive a VMO that has a cache
107 // policy set while also holding cache policy flags within the arch
108 // flags. The only path that should be able to achieve this is if
109 // something in the kernel maps into their aspace incorrectly.
110 if ((arch_mmu_flags & ARCH_MMU_FLAG_CACHE_MASK) != 0 &&
111 (arch_mmu_flags & ARCH_MMU_FLAG_CACHE_MASK) != cache_policy) {
112 TRACEF("warning: mapping %s has conflicting cache policies: vmo %02x "
113 "arch_mmu_flags %02x.\n",
114 name, cache_policy, arch_mmu_flags & ARCH_MMU_FLAG_CACHE_MASK);
115 }
116 arch_mmu_flags |= cache_policy;
117 }
118
119 // Check that we have the required privileges if we want a SPECIFIC mapping
120 if (is_specific && !(flags_ & VMAR_FLAG_CAN_MAP_SPECIFIC)) {
121 return ZX_ERR_ACCESS_DENIED;
122 }
123
124 if (offset >= size_ || size > size_ - offset) {
125 return ZX_ERR_INVALID_ARGS;
126 }
127
128 vaddr_t new_base = -1;
129 if (is_specific) {
130 new_base = base_ + offset;
131 if (!IS_PAGE_ALIGNED(new_base)) {
132 return ZX_ERR_INVALID_ARGS;
133 }
134 if (align_pow2 > 0 && (new_base & ((1ULL << align_pow2) - 1))) {
135 return ZX_ERR_INVALID_ARGS;
136 }
137 if (!IsRangeAvailableLocked(new_base, size)) {
138 if (is_specific_overwrite) {
139 return OverwriteVmMapping(new_base, size, vmar_flags,
140 vmo, vmo_offset, arch_mmu_flags, out);
141 }
142 return ZX_ERR_NO_MEMORY;
143 }
144 } else {
145 // If we're not mapping to a specific place, search for an opening.
146 zx_status_t status = AllocSpotLocked(size, align_pow2, arch_mmu_flags, &new_base);
147 if (status != ZX_OK) {
148 return status;
149 }
150 }
151
152 // Notice if this is an executable mapping from the vDSO VMO
153 // before we lose the VMO reference via ktl::move(vmo).
154 const bool is_vdso_code = (vmo &&
155 (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) &&
156 VDso::vmo_is_vdso(vmo));
157
158 fbl::AllocChecker ac;
159 fbl::RefPtr<VmAddressRegionOrMapping> vmar;
160 if (vmo) {
161 vmar = fbl::AdoptRef(new (&ac)
162 VmMapping(*this, new_base, size, vmar_flags,
163 ktl::move(vmo), vmo_offset, arch_mmu_flags));
164 } else {
165 vmar = fbl::AdoptRef(new (&ac)
166 VmAddressRegion(*this, new_base, size, vmar_flags, name));
167 }
168
169 if (!ac.check()) {
170 return ZX_ERR_NO_MEMORY;
171 }
172
173 if (is_vdso_code) {
174 // For an executable mapping of the vDSO, allow only one per process
175 // and only for the valid range of the image.
176 if (aspace_->vdso_code_mapping_ ||
177 !VDso::valid_code_mapping(vmo_offset, size)) {
178 return ZX_ERR_ACCESS_DENIED;
179 }
180 aspace_->vdso_code_mapping_ = fbl::RefPtr<VmMapping>::Downcast(vmar);
181 }
182
183 vmar->Activate();
184 *out = ktl::move(vmar);
185 return ZX_OK;
186 }
187
CreateSubVmar(size_t offset,size_t size,uint8_t align_pow2,uint32_t vmar_flags,const char * name,fbl::RefPtr<VmAddressRegion> * out)188 zx_status_t VmAddressRegion::CreateSubVmar(size_t offset, size_t size, uint8_t align_pow2,
189 uint32_t vmar_flags, const char* name,
190 fbl::RefPtr<VmAddressRegion>* out) {
191 DEBUG_ASSERT(out);
192
193 if (!IS_PAGE_ALIGNED(size)) {
194 return ZX_ERR_INVALID_ARGS;
195 }
196
197 // Check that only allowed flags have been set
198 if (vmar_flags & ~(VMAR_FLAG_SPECIFIC | VMAR_FLAG_CAN_MAP_SPECIFIC | VMAR_FLAG_COMPACT | VMAR_CAN_RWX_FLAGS)) {
199 return ZX_ERR_INVALID_ARGS;
200 }
201
202 fbl::RefPtr<VmAddressRegionOrMapping> res;
203 zx_status_t status = CreateSubVmarInternal(offset, size, align_pow2, vmar_flags, nullptr, 0,
204 ARCH_MMU_FLAG_INVALID, name, &res);
205 if (status != ZX_OK) {
206 return status;
207 }
208 // TODO(teisenbe): optimize this
209 *out = res->as_vm_address_region();
210 return ZX_OK;
211 }
212
CreateVmMapping(size_t mapping_offset,size_t size,uint8_t align_pow2,uint32_t vmar_flags,fbl::RefPtr<VmObject> vmo,uint64_t vmo_offset,uint arch_mmu_flags,const char * name,fbl::RefPtr<VmMapping> * out)213 zx_status_t VmAddressRegion::CreateVmMapping(size_t mapping_offset, size_t size, uint8_t align_pow2,
214 uint32_t vmar_flags, fbl::RefPtr<VmObject> vmo,
215 uint64_t vmo_offset, uint arch_mmu_flags, const char* name,
216 fbl::RefPtr<VmMapping>* out) {
217 DEBUG_ASSERT(out);
218 LTRACEF("%p %#zx %#zx %x\n", this, mapping_offset, size, vmar_flags);
219
220 // Check that only allowed flags have been set
221 if (vmar_flags & ~(VMAR_FLAG_SPECIFIC | VMAR_FLAG_SPECIFIC_OVERWRITE | VMAR_CAN_RWX_FLAGS)) {
222 return ZX_ERR_INVALID_ARGS;
223 }
224
225 // Validate that arch_mmu_flags does not contain any prohibited flags
226 if (!is_valid_mapping_flags(arch_mmu_flags)) {
227 return ZX_ERR_ACCESS_DENIED;
228 }
229
230 // If size overflows, it'll become 0 and get rejected in
231 // CreateSubVmarInternal.
232 size = ROUNDUP(size, PAGE_SIZE);
233
234 // Make sure that vmo_offset is aligned and that a mapping of this size
235 // wouldn't overflow the vmo offset.
236 if (!IS_PAGE_ALIGNED(vmo_offset) || vmo_offset + size < vmo_offset) {
237 return ZX_ERR_INVALID_ARGS;
238 }
239
240 // If we're mapping it with a specific permission, we should allow
241 // future Protect() calls on the mapping to keep that permission.
242 if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_READ) {
243 vmar_flags |= VMAR_FLAG_CAN_MAP_READ;
244 }
245 if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_WRITE) {
246 vmar_flags |= VMAR_FLAG_CAN_MAP_WRITE;
247 }
248 if (arch_mmu_flags & ARCH_MMU_FLAG_PERM_EXECUTE) {
249 vmar_flags |= VMAR_FLAG_CAN_MAP_EXECUTE;
250 }
251
252 fbl::RefPtr<VmAddressRegionOrMapping> res;
253 zx_status_t status =
254 CreateSubVmarInternal(mapping_offset, size, align_pow2, vmar_flags, ktl::move(vmo),
255 vmo_offset, arch_mmu_flags, name, &res);
256 if (status != ZX_OK) {
257 return status;
258 }
259 // TODO(teisenbe): optimize this
260 *out = res->as_vm_mapping();
261 return ZX_OK;
262 }
263
OverwriteVmMapping(vaddr_t base,size_t size,uint32_t vmar_flags,fbl::RefPtr<VmObject> vmo,uint64_t vmo_offset,uint arch_mmu_flags,fbl::RefPtr<VmAddressRegionOrMapping> * out)264 zx_status_t VmAddressRegion::OverwriteVmMapping(
265 vaddr_t base, size_t size, uint32_t vmar_flags,
266 fbl::RefPtr<VmObject> vmo, uint64_t vmo_offset,
267 uint arch_mmu_flags, fbl::RefPtr<VmAddressRegionOrMapping>* out) {
268
269 canary_.Assert();
270 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
271 DEBUG_ASSERT(vmo);
272 DEBUG_ASSERT(vmar_flags & VMAR_FLAG_SPECIFIC_OVERWRITE);
273
274 fbl::AllocChecker ac;
275 fbl::RefPtr<VmAddressRegionOrMapping> vmar;
276 vmar = fbl::AdoptRef(new (&ac)
277 VmMapping(*this, base, size, vmar_flags,
278 ktl::move(vmo), vmo_offset, arch_mmu_flags));
279 if (!ac.check()) {
280 return ZX_ERR_NO_MEMORY;
281 }
282
283 zx_status_t status = UnmapInternalLocked(base, size, false /* can_destroy_regions */,
284 false /* allow_partial_vmar */);
285 if (status != ZX_OK) {
286 return status;
287 }
288
289 vmar->Activate();
290 *out = ktl::move(vmar);
291 return ZX_OK;
292 }
293
DestroyLocked()294 zx_status_t VmAddressRegion::DestroyLocked() {
295 canary_.Assert();
296 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
297 LTRACEF("%p '%s'\n", this, name_);
298
299 // The cur reference prevents regions from being destructed after dropping
300 // the last reference to them when removing from their parent.
301 fbl::RefPtr<VmAddressRegion> cur(this);
302 while (cur) {
303 // Iterate through children destroying mappings. If we find a
304 // subregion, stop so we can traverse down.
305 fbl::RefPtr<VmAddressRegion> child_region = nullptr;
306 while (!cur->subregions_.is_empty() && !child_region) {
307 VmAddressRegionOrMapping* child = &cur->subregions_.front();
308 if (child->is_mapping()) {
309 // DestroyLocked should remove this child from our list on success.
310 zx_status_t status = child->DestroyLocked();
311 if (status != ZX_OK) {
312 // TODO(teisenbe): Do we want to handle this case differently?
313 return status;
314 }
315 } else {
316 child_region = child->as_vm_address_region();
317 }
318 }
319
320 if (child_region) {
321 // If we found a child region, traverse down the tree.
322 cur = child_region;
323 } else {
324 // All children are destroyed, so now destroy the current node.
325 if (cur->parent_) {
326 DEBUG_ASSERT(cur->subregion_list_node_.InContainer());
327 cur->parent_->RemoveSubregion(cur.get());
328 }
329 cur->state_ = LifeCycleState::DEAD;
330 VmAddressRegion* cur_parent = cur->parent_;
331 cur->parent_ = nullptr;
332
333 // If we destroyed the original node, stop. Otherwise traverse
334 // up the tree and keep destroying.
335 cur.reset((cur.get() == this) ? nullptr : cur_parent);
336 }
337 }
338 return ZX_OK;
339 }
340
RemoveSubregion(VmAddressRegionOrMapping * region)341 void VmAddressRegion::RemoveSubregion(VmAddressRegionOrMapping* region) {
342 subregions_.erase(*region);
343 }
344
FindRegion(vaddr_t addr)345 fbl::RefPtr<VmAddressRegionOrMapping> VmAddressRegion::FindRegion(vaddr_t addr) {
346 Guard<fbl::Mutex> guard{aspace_->lock()};
347 if (state_ != LifeCycleState::ALIVE) {
348 return nullptr;
349 }
350 return FindRegionLocked(addr);
351 }
352
FindRegionLocked(vaddr_t addr)353 fbl::RefPtr<VmAddressRegionOrMapping> VmAddressRegion::FindRegionLocked(vaddr_t addr) {
354 canary_.Assert();
355
356 // Find the first region with a base greather than *addr*. If a region
357 // exists for *addr*, it will be immediately before it.
358 auto itr = --subregions_.upper_bound(addr);
359 if (!itr.IsValid() || itr->base() > addr || addr > itr->base() + itr->size() - 1) {
360 return nullptr;
361 }
362
363 return itr.CopyPointer();
364 }
365
AllocatedPagesLocked() const366 size_t VmAddressRegion::AllocatedPagesLocked() const {
367 canary_.Assert();
368 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
369
370 if (state_ != LifeCycleState::ALIVE) {
371 return 0;
372 }
373
374 size_t sum = 0;
375 for (const auto& child : subregions_) {
376 sum += child.AllocatedPagesLocked();
377 }
378 return sum;
379 }
380
PageFault(vaddr_t va,uint pf_flags)381 zx_status_t VmAddressRegion::PageFault(vaddr_t va, uint pf_flags) {
382 canary_.Assert();
383 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
384
385 auto vmar = WrapRefPtr(this);
386 while (auto next = vmar->FindRegionLocked(va)) {
387 if (next->is_mapping()) {
388 return next->PageFault(va, pf_flags);
389 }
390 vmar = next->as_vm_address_region();
391 }
392
393 return ZX_ERR_NOT_FOUND;
394 }
395
IsRangeAvailableLocked(vaddr_t base,size_t size)396 bool VmAddressRegion::IsRangeAvailableLocked(vaddr_t base, size_t size) {
397 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
398 DEBUG_ASSERT(size > 0);
399
400 // Find the first region with base > *base*. Since subregions_ has no
401 // overlapping elements, we just need to check this one and the prior
402 // child.
403
404 auto prev = subregions_.upper_bound(base);
405 auto next = prev--;
406
407 if (prev.IsValid()) {
408 vaddr_t prev_last_byte;
409 if (add_overflow(prev->base(), prev->size() - 1, &prev_last_byte)) {
410 return false;
411 }
412 if (prev_last_byte >= base) {
413 return false;
414 }
415 }
416
417 if (next.IsValid() && next != subregions_.end()) {
418 vaddr_t last_byte;
419 if (add_overflow(base, size - 1, &last_byte)) {
420 return false;
421 }
422 if (next->base() <= last_byte) {
423 return false;
424 }
425 }
426 return true;
427 }
428
CheckGapLocked(const ChildList::iterator & prev,const ChildList::iterator & next,vaddr_t * pva,vaddr_t search_base,vaddr_t align,size_t region_size,size_t min_gap,uint arch_mmu_flags)429 bool VmAddressRegion::CheckGapLocked(const ChildList::iterator& prev,
430 const ChildList::iterator& next,
431 vaddr_t* pva, vaddr_t search_base, vaddr_t align,
432 size_t region_size, size_t min_gap, uint arch_mmu_flags) {
433 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
434
435 vaddr_t gap_beg; // first byte of a gap
436 vaddr_t gap_end; // last byte of a gap
437
438 uint prev_arch_mmu_flags;
439 uint next_arch_mmu_flags;
440
441 DEBUG_ASSERT(pva);
442
443 // compute the starting address of the gap
444 if (prev.IsValid()) {
445 if (add_overflow(prev->base(), prev->size(), &gap_beg) ||
446 add_overflow(gap_beg, min_gap, &gap_beg)) {
447 goto not_found;
448 }
449 } else {
450 gap_beg = base_;
451 }
452
453 // compute the ending address of the gap
454 if (next.IsValid()) {
455 if (gap_beg == next->base()) {
456 goto next_gap; // no gap between regions
457 }
458 if (sub_overflow(next->base(), 1, &gap_end) ||
459 sub_overflow(gap_end, min_gap, &gap_end)) {
460 goto not_found;
461 }
462 } else {
463 if (gap_beg == base_ + size_) {
464 goto not_found; // no gap at the end of address space. Stop search
465 }
466 if (add_overflow(base_, size_ - 1, &gap_end)) {
467 goto not_found;
468 }
469 }
470
471 DEBUG_ASSERT(gap_end > gap_beg);
472
473 // trim it to the search range
474 if (gap_end <= search_base) {
475 return false;
476 }
477 if (gap_beg < search_base) {
478 gap_beg = search_base;
479 }
480
481 DEBUG_ASSERT(gap_end > gap_beg);
482
483 LTRACEF_LEVEL(2, "search base %#" PRIxPTR " gap_beg %#" PRIxPTR " end %#" PRIxPTR "\n",
484 search_base, gap_beg, gap_end);
485
486 prev_arch_mmu_flags = (prev.IsValid() && prev->is_mapping())
487 ? prev->as_vm_mapping()->arch_mmu_flags()
488 : ARCH_MMU_FLAG_INVALID;
489
490 next_arch_mmu_flags = (next.IsValid() && next->is_mapping())
491 ? next->as_vm_mapping()->arch_mmu_flags()
492 : ARCH_MMU_FLAG_INVALID;
493
494 *pva = aspace_->arch_aspace().PickSpot(gap_beg, prev_arch_mmu_flags, gap_end,
495 next_arch_mmu_flags, align, region_size, arch_mmu_flags);
496 if (*pva < gap_beg) {
497 goto not_found; // address wrapped around
498 }
499
500 if (*pva < gap_end && ((gap_end - *pva + 1) >= region_size)) {
501 // we have enough room
502 return true; // found spot, stop search
503 }
504
505 next_gap:
506 return false; // continue search
507
508 not_found:
509 *pva = -1;
510 return true; // not_found: stop search
511 }
512
AllocSpotLocked(size_t size,uint8_t align_pow2,uint arch_mmu_flags,vaddr_t * spot)513 zx_status_t VmAddressRegion::AllocSpotLocked(size_t size, uint8_t align_pow2, uint arch_mmu_flags,
514 vaddr_t* spot) {
515 canary_.Assert();
516 DEBUG_ASSERT(size > 0 && IS_PAGE_ALIGNED(size));
517 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
518
519 LTRACEF_LEVEL(2, "aspace %p size 0x%zx align %hhu\n", this, size,
520 align_pow2);
521
522 if (aspace_->is_aslr_enabled()) {
523 if (flags_ & VMAR_FLAG_COMPACT) {
524 return CompactRandomizedRegionAllocatorLocked(size, align_pow2, arch_mmu_flags, spot);
525 } else {
526 return NonCompactRandomizedRegionAllocatorLocked(size, align_pow2, arch_mmu_flags,
527 spot);
528 }
529 }
530 return LinearRegionAllocatorLocked(size, align_pow2, arch_mmu_flags, spot);
531 }
532
EnumerateChildrenLocked(VmEnumerator * ve,uint depth)533 bool VmAddressRegion::EnumerateChildrenLocked(VmEnumerator* ve, uint depth) {
534 canary_.Assert();
535 DEBUG_ASSERT(ve != nullptr);
536 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
537
538 const uint min_depth = depth;
539 for (auto itr = subregions_.begin(), end = subregions_.end(); itr != end;) {
540 DEBUG_ASSERT(itr->IsAliveLocked());
541 auto curr = itr++;
542 VmAddressRegion* up = curr->parent_;
543
544 if (curr->is_mapping()) {
545 VmMapping* mapping = curr->as_vm_mapping().get();
546 DEBUG_ASSERT(mapping != nullptr);
547 if (!ve->OnVmMapping(mapping, this, depth)) {
548 return false;
549 }
550 } else {
551 VmAddressRegion* vmar = curr->as_vm_address_region().get();
552 DEBUG_ASSERT(vmar != nullptr);
553 if (!ve->OnVmAddressRegion(vmar, depth)) {
554 return false;
555 }
556 if (!vmar->subregions_.is_empty()) {
557 // If the sub-VMAR is not empty, iterate through its children.
558 itr = vmar->subregions_.begin();
559 end = vmar->subregions_.end();
560 depth++;
561 continue;
562 }
563 }
564 if (depth > min_depth && itr == end) {
565 // If we are at a depth greater than the minimum, and have reached
566 // the end of a sub-VMAR range, we ascend and continue iteration.
567 do {
568 itr = up->subregions_.upper_bound(curr->base());
569 if (itr.IsValid()) {
570 break;
571 }
572 up = up->parent_;
573 } while (depth-- != min_depth);
574 if (!itr.IsValid()) {
575 // If we have reached the end after ascending all the way up,
576 // break out of the loop.
577 break;
578 }
579 end = up->subregions_.end();
580 }
581 }
582 return true;
583 }
584
has_parent() const585 bool VmAddressRegion::has_parent() const {
586 Guard<fbl::Mutex> guard{aspace_->lock()};
587 return parent_ != nullptr;
588 }
589
Dump(uint depth,bool verbose) const590 void VmAddressRegion::Dump(uint depth, bool verbose) const {
591 canary_.Assert();
592 for (uint i = 0; i < depth; ++i) {
593 printf(" ");
594 }
595 printf("vmar %p [%#" PRIxPTR " %#" PRIxPTR "] sz %#zx ref %d '%s'\n", this,
596 base_, base_ + size_ - 1, size_, ref_count_debug(), name_);
597 for (const auto& child : subregions_) {
598 child.Dump(depth + 1, verbose);
599 }
600 }
601
Activate()602 void VmAddressRegion::Activate() {
603 DEBUG_ASSERT(state_ == LifeCycleState::NOT_READY);
604 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
605
606 state_ = LifeCycleState::ALIVE;
607 parent_->subregions_.insert(fbl::RefPtr<VmAddressRegionOrMapping>(this));
608 }
609
Unmap(vaddr_t base,size_t size)610 zx_status_t VmAddressRegion::Unmap(vaddr_t base, size_t size) {
611 canary_.Assert();
612
613 size = ROUNDUP(size, PAGE_SIZE);
614 if (size == 0 || !IS_PAGE_ALIGNED(base)) {
615 return ZX_ERR_INVALID_ARGS;
616 }
617
618 Guard<fbl::Mutex> guard{aspace_->lock()};
619 if (state_ != LifeCycleState::ALIVE) {
620 return ZX_ERR_BAD_STATE;
621 }
622
623 return UnmapInternalLocked(base, size, true /* can_destroy_regions */,
624 false /* allow_partial_vmar */);
625 }
626
UnmapAllowPartial(vaddr_t base,size_t size)627 zx_status_t VmAddressRegion::UnmapAllowPartial(vaddr_t base, size_t size) {
628 canary_.Assert();
629
630 size = ROUNDUP(size, PAGE_SIZE);
631 if (size == 0 || !IS_PAGE_ALIGNED(base)) {
632 return ZX_ERR_INVALID_ARGS;
633 }
634
635 Guard<fbl::Mutex> guard{aspace_->lock()};
636 if (state_ != LifeCycleState::ALIVE) {
637 return ZX_ERR_BAD_STATE;
638 }
639
640 return UnmapInternalLocked(base, size, true /* can_destroy_regions */,
641 true /* allow_partial_vmar */);
642 }
643
UpperBoundInternalLocked(vaddr_t base)644 VmAddressRegion::ChildList::iterator VmAddressRegion::UpperBoundInternalLocked(vaddr_t base) {
645 // Find the first region with a base greater than *base*. If a region
646 // exists for *base*, it will be immediately before it.
647 auto itr = --subregions_.upper_bound(base);
648 if (!itr.IsValid()) {
649 itr = subregions_.begin();
650 } else if (base >= itr->base() + itr->size()) {
651 // If *base* isn't in this region, ignore it.
652 ++itr;
653 }
654 return itr;
655 }
656
UnmapInternalLocked(vaddr_t base,size_t size,bool can_destroy_regions,bool allow_partial_vmar)657 zx_status_t VmAddressRegion::UnmapInternalLocked(vaddr_t base, size_t size,
658 bool can_destroy_regions,
659 bool allow_partial_vmar) {
660 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
661
662 if (!is_in_range(base, size)) {
663 return ZX_ERR_INVALID_ARGS;
664 }
665
666 if (subregions_.is_empty()) {
667 return ZX_OK;
668 }
669
670 // Any unmap spanning the vDSO code mapping is verboten.
671 if (aspace_->vdso_code_mapping_ &&
672 aspace_->vdso_code_mapping_->base() >= base &&
673 aspace_->vdso_code_mapping_->base() - base < size) {
674 return ZX_ERR_ACCESS_DENIED;
675 }
676
677 const vaddr_t end_addr = base + size;
678 auto end = subregions_.lower_bound(end_addr);
679 auto begin = UpperBoundInternalLocked(base);
680
681 if (!allow_partial_vmar) {
682 // Check if we're partially spanning a subregion, or aren't allowed to
683 // destroy regions and are spanning a region, and bail if we are.
684 for (auto itr = begin; itr != end; ++itr) {
685 const vaddr_t itr_end = itr->base() + itr->size();
686 if (!itr->is_mapping() && (!can_destroy_regions ||
687 itr->base() < base || itr_end > end_addr)) {
688 return ZX_ERR_INVALID_ARGS;
689 }
690 }
691 }
692
693 bool at_top = true;
694 for (auto itr = begin; itr != end;) {
695 // Create a copy of the iterator, in case we destroy this element
696 auto curr = itr++;
697 VmAddressRegion* up = curr->parent_;
698
699 if (curr->is_mapping()) {
700 const vaddr_t curr_end = curr->base() + curr->size();
701 const vaddr_t unmap_base = fbl::max(curr->base(), base);
702 const vaddr_t unmap_end = fbl::min(curr_end, end_addr);
703 const size_t unmap_size = unmap_end - unmap_base;
704
705 if (unmap_base == curr->base() && unmap_size == curr->size()) {
706 // If we're unmapping the entire region, just call Destroy
707 __UNUSED zx_status_t status = curr->DestroyLocked();
708 DEBUG_ASSERT(status == ZX_OK);
709 } else {
710 // VmMapping::Unmap should only fail if it needs to allocate,
711 // which only happens if it is unmapping from the middle of a
712 // region. That can only happen if there is only one region
713 // being operated on here, so we can just forward along the
714 // error without having to rollback.
715 //
716 // TODO(teisenbe): Technically arch_mmu_unmap() itself can also
717 // fail. We need to rework the system so that is no longer
718 // possible.
719 zx_status_t status = curr->as_vm_mapping()->UnmapLocked(unmap_base, unmap_size);
720 DEBUG_ASSERT(status == ZX_OK || curr == begin);
721 if (status != ZX_OK) {
722 return status;
723 }
724 }
725 } else {
726 vaddr_t unmap_base = 0;
727 size_t unmap_size = 0;
728 __UNUSED bool intersects = GetIntersect(base, size, curr->base(), curr->size(),
729 &unmap_base, &unmap_size);
730 DEBUG_ASSERT(intersects);
731 if (allow_partial_vmar) {
732 // If partial VMARs are allowed, we descend into sub-VMARs.
733 fbl::RefPtr<VmAddressRegion> vmar = curr->as_vm_address_region();
734 if (!vmar->subregions_.is_empty()) {
735 begin = vmar->UpperBoundInternalLocked(base);
736 end = vmar->subregions_.lower_bound(end_addr);
737 itr = begin;
738 at_top = false;
739 }
740 } else if (unmap_base == curr->base() && unmap_size == curr->size()) {
741 __UNUSED zx_status_t status = curr->DestroyLocked();
742 DEBUG_ASSERT(status == ZX_OK);
743 }
744 }
745
746 if (allow_partial_vmar && !at_top && itr == end) {
747 // If partial VMARs are allowed, and we have reached the end of a
748 // sub-VMAR range, we ascend and continue iteration.
749 do {
750 begin = up->subregions_.upper_bound(curr->base());
751 if (begin.IsValid()) {
752 break;
753 }
754 at_top = up == this;
755 up = up->parent_;
756 } while (!at_top);
757 if (!begin.IsValid()) {
758 // If we have reached the end after ascending all the way up,
759 // break out of the loop.
760 break;
761 }
762 end = up->subregions_.lower_bound(end_addr);
763 itr = begin;
764 }
765 }
766
767 return ZX_OK;
768 }
769
Protect(vaddr_t base,size_t size,uint new_arch_mmu_flags)770 zx_status_t VmAddressRegion::Protect(vaddr_t base, size_t size, uint new_arch_mmu_flags) {
771 canary_.Assert();
772
773 size = ROUNDUP(size, PAGE_SIZE);
774 if (size == 0 || !IS_PAGE_ALIGNED(base)) {
775 return ZX_ERR_INVALID_ARGS;
776 }
777
778 Guard<fbl::Mutex> guard{aspace_->lock()};
779 if (state_ != LifeCycleState::ALIVE) {
780 return ZX_ERR_BAD_STATE;
781 }
782
783 if (!is_in_range(base, size)) {
784 return ZX_ERR_INVALID_ARGS;
785 }
786
787 if (subregions_.is_empty()) {
788 return ZX_ERR_NOT_FOUND;
789 }
790
791 const vaddr_t end_addr = base + size;
792 const auto end = subregions_.lower_bound(end_addr);
793
794 // Find the first region with a base greater than *base*. If a region
795 // exists for *base*, it will be immediately before it. If *base* isn't in
796 // that entry, bail since it's unmapped.
797 auto begin = --subregions_.upper_bound(base);
798 if (!begin.IsValid() || begin->base() + begin->size() <= base) {
799 return ZX_ERR_NOT_FOUND;
800 }
801
802 // Check if we're overlapping a subregion, or a part of the range is not
803 // mapped, or the new permissions are invalid for some mapping in the range.
804 vaddr_t last_mapped = begin->base();
805 for (auto itr = begin; itr != end; ++itr) {
806 if (!itr->is_mapping()) {
807 return ZX_ERR_INVALID_ARGS;
808 }
809 if (itr->base() != last_mapped) {
810 return ZX_ERR_NOT_FOUND;
811 }
812 if (!itr->is_valid_mapping_flags(new_arch_mmu_flags)) {
813 return ZX_ERR_ACCESS_DENIED;
814 }
815 if (itr->as_vm_mapping() == aspace_->vdso_code_mapping_) {
816 return ZX_ERR_ACCESS_DENIED;
817 }
818
819 last_mapped = itr->base() + itr->size();
820 }
821 if (last_mapped < base + size) {
822 return ZX_ERR_NOT_FOUND;
823 }
824
825 for (auto itr = begin; itr != end;) {
826 DEBUG_ASSERT(itr->is_mapping());
827
828 auto next = itr;
829 ++next;
830
831 const vaddr_t curr_end = itr->base() + itr->size();
832 const vaddr_t protect_base = fbl::max(itr->base(), base);
833 const vaddr_t protect_end = fbl::min(curr_end, end_addr);
834 const size_t protect_size = protect_end - protect_base;
835
836 zx_status_t status = itr->as_vm_mapping()->ProtectLocked(protect_base, protect_size,
837 new_arch_mmu_flags);
838 if (status != ZX_OK) {
839 // TODO(teisenbe): Try to work out a way to guarantee success, or
840 // provide a full unwind?
841 return status;
842 }
843
844 itr = ktl::move(next);
845 }
846
847 return ZX_OK;
848 }
849
LinearRegionAllocatorLocked(size_t size,uint8_t align_pow2,uint arch_mmu_flags,vaddr_t * spot)850 zx_status_t VmAddressRegion::LinearRegionAllocatorLocked(size_t size, uint8_t align_pow2,
851 uint arch_mmu_flags, vaddr_t* spot) {
852 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
853
854 const vaddr_t base = 0;
855
856 if (align_pow2 < PAGE_SIZE_SHIFT) {
857 align_pow2 = PAGE_SIZE_SHIFT;
858 }
859 const vaddr_t align = 1UL << align_pow2;
860
861 // Find the first gap in the address space which can contain a region of the
862 // requested size.
863 auto before_iter = subregions_.end();
864 auto after_iter = subregions_.begin();
865
866 do {
867 if (CheckGapLocked(before_iter, after_iter, spot, base, align, size, 0, arch_mmu_flags)) {
868 if (*spot != static_cast<vaddr_t>(-1)) {
869 return ZX_OK;
870 } else {
871 return ZX_ERR_NO_MEMORY;
872 }
873 }
874
875 before_iter = after_iter++;
876 } while (before_iter.IsValid());
877
878 // couldn't find anything
879 return ZX_ERR_NO_MEMORY;
880 }
881
882 template <typename F>
ForEachGap(F func,uint8_t align_pow2)883 void VmAddressRegion::ForEachGap(F func, uint8_t align_pow2) {
884 const vaddr_t align = 1UL << align_pow2;
885
886 // Scan the regions list to find the gap to the left of each region. We
887 // round up the end of the previous region to the requested alignment, so
888 // all gaps reported will be for aligned ranges.
889 vaddr_t prev_region_end = ROUNDUP(base_, align);
890 for (const auto& region : subregions_) {
891 if (region.base() > prev_region_end) {
892 const size_t gap = region.base() - prev_region_end;
893 if (!func(prev_region_end, gap)) {
894 return;
895 }
896 }
897 prev_region_end = ROUNDUP(region.base() + region.size(), align);
898 }
899
900 // Grab the gap to the right of the last region (note that if there are no
901 // regions, this handles reporting the VMAR's whole span as a gap).
902 const vaddr_t end = base_ + size_;
903 if (end > prev_region_end) {
904 const size_t gap = end - prev_region_end;
905 func(prev_region_end, gap);
906 }
907 }
908
909 namespace {
910
911 // Compute the number of allocation spots that satisfy the alignment within the
912 // given range size, for a range that has a base that satisfies the alignment.
AllocationSpotsInRange(size_t range_size,size_t alloc_size,uint8_t align_pow2)913 constexpr size_t AllocationSpotsInRange(size_t range_size, size_t alloc_size, uint8_t align_pow2) {
914 return ((range_size - alloc_size) >> align_pow2) + 1;
915 }
916
917 } // namespace {}
918
919 // Perform allocations for VMARs that aren't using the COMPACT policy. This
920 // allocator works by choosing uniformly at random from the set of positions
921 // that could satisfy the allocation.
NonCompactRandomizedRegionAllocatorLocked(size_t size,uint8_t align_pow2,uint arch_mmu_flags,vaddr_t * spot)922 zx_status_t VmAddressRegion::NonCompactRandomizedRegionAllocatorLocked(size_t size, uint8_t align_pow2,
923 uint arch_mmu_flags,
924 vaddr_t* spot) {
925 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
926 DEBUG_ASSERT(spot);
927
928 align_pow2 = fbl::max(align_pow2, static_cast<uint8_t>(PAGE_SIZE_SHIFT));
929 const vaddr_t align = 1UL << align_pow2;
930
931 // Calculate the number of spaces that we can fit this allocation in.
932 size_t candidate_spaces = 0;
933 ForEachGap([align, align_pow2, size, &candidate_spaces](vaddr_t gap_base, size_t gap_len) -> bool {
934 DEBUG_ASSERT(IS_ALIGNED(gap_base, align));
935 if (gap_len >= size) {
936 candidate_spaces += AllocationSpotsInRange(gap_len, size, align_pow2);
937 }
938 return true;
939 },
940 align_pow2);
941
942 if (candidate_spaces == 0) {
943 return ZX_ERR_NO_MEMORY;
944 }
945
946 // Choose the index of the allocation to use.
947 size_t selected_index = aspace_->AslrPrng().RandInt(candidate_spaces);
948 DEBUG_ASSERT(selected_index < candidate_spaces);
949
950 // Find which allocation we picked.
951 vaddr_t alloc_spot = static_cast<vaddr_t>(-1);
952 ForEachGap([align_pow2, size, &alloc_spot, &selected_index](vaddr_t gap_base,
953 size_t gap_len) -> bool {
954 if (gap_len < size) {
955 return true;
956 }
957
958 const size_t spots = AllocationSpotsInRange(gap_len, size, align_pow2);
959 if (selected_index < spots) {
960 alloc_spot = gap_base + (selected_index << align_pow2);
961 return false;
962 }
963 selected_index -= spots;
964 return true;
965 },
966 align_pow2);
967 ASSERT(alloc_spot != static_cast<vaddr_t>(-1));
968 ASSERT(IS_ALIGNED(alloc_spot, align));
969
970 // Sanity check that the allocation fits.
971 auto after_iter = subregions_.upper_bound(alloc_spot + size - 1);
972 auto before_iter = after_iter;
973
974 if (after_iter == subregions_.begin() || subregions_.size() == 0) {
975 before_iter = subregions_.end();
976 } else {
977 --before_iter;
978 }
979
980 ASSERT(before_iter == subregions_.end() || before_iter.IsValid());
981
982 if (CheckGapLocked(before_iter, after_iter, spot, alloc_spot, align, size, 0,
983 arch_mmu_flags) &&
984 *spot != static_cast<vaddr_t>(-1)) {
985 return ZX_OK;
986 }
987 panic("Unexpected allocation failure\n");
988 }
989
990 // The COMPACT allocator begins by picking a random offset in the region to
991 // start allocations at, and then places new allocations to the left and right
992 // of the original region with small random-length gaps between.
CompactRandomizedRegionAllocatorLocked(size_t size,uint8_t align_pow2,uint arch_mmu_flags,vaddr_t * spot)993 zx_status_t VmAddressRegion::CompactRandomizedRegionAllocatorLocked(size_t size, uint8_t align_pow2,
994 uint arch_mmu_flags,
995 vaddr_t* spot) {
996 DEBUG_ASSERT(aspace_->lock()->lock().IsHeld());
997
998 align_pow2 = fbl::max(align_pow2, static_cast<uint8_t>(PAGE_SIZE_SHIFT));
999 const vaddr_t align = 1UL << align_pow2;
1000
1001 if (unlikely(subregions_.size() == 0)) {
1002 return NonCompactRandomizedRegionAllocatorLocked(size, align_pow2, arch_mmu_flags, spot);
1003 }
1004
1005 // Decide if we're allocating before or after the existing allocations, and
1006 // how many gap pages to use.
1007 bool alloc_before;
1008 size_t num_gap_pages;
1009 {
1010 uint8_t entropy;
1011 aspace_->AslrPrng().Draw(&entropy, sizeof(entropy));
1012 alloc_before = entropy & 1;
1013 num_gap_pages = (entropy >> 1) + 1;
1014 }
1015
1016 // Try our first choice for *num_gap_pages*, but if that fails, try fewer
1017 for (size_t gap_pages = num_gap_pages; gap_pages > 0; gap_pages >>= 1) {
1018 // Try our first choice for *alloc_before*, but if that fails, try the other
1019 for (size_t i = 0; i < 2; ++i, alloc_before = !alloc_before) {
1020 ChildList::iterator before_iter;
1021 ChildList::iterator after_iter;
1022 vaddr_t chosen_base;
1023 if (alloc_before) {
1024 before_iter = subregions_.end();
1025 after_iter = subregions_.begin();
1026
1027 vaddr_t base;
1028 if (sub_overflow(after_iter->base(), size, &base) ||
1029 sub_overflow(base, PAGE_SIZE * gap_pages, &base)) {
1030 continue;
1031 }
1032
1033 chosen_base = base;
1034 } else {
1035 before_iter = --subregions_.end();
1036 after_iter = subregions_.end();
1037 DEBUG_ASSERT(before_iter.IsValid());
1038
1039 vaddr_t base;
1040 if (add_overflow(before_iter->base(), before_iter->size(), &base) ||
1041 add_overflow(base, PAGE_SIZE * gap_pages, &base)) {
1042 continue;
1043 }
1044
1045 chosen_base = base;
1046 }
1047
1048 if (CheckGapLocked(before_iter, after_iter, spot, chosen_base, align, size, 0,
1049 arch_mmu_flags) &&
1050 *spot != static_cast<vaddr_t>(-1)) {
1051 return ZX_OK;
1052 }
1053 }
1054 }
1055
1056 return ZX_ERR_NO_MEMORY;
1057 }
1058