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