diff --git a/headers/private/kernel/vm/vm.h b/headers/private/kernel/vm/vm.h index bf1ca3cbb8..9163e3bca1 100644 --- a/headers/private/kernel/vm/vm.h +++ b/headers/private/kernel/vm/vm.h @@ -86,6 +86,10 @@ area_id transfer_area(area_id id, void** _address, uint32 addressSpec, const char* vm_cache_type_to_string(int32 type); +status_t vm_prepare_kernel_area_debug_protection(area_id id, void** cookie); +status_t vm_set_kernel_area_debug_protection(void* cookie, void* _address, + size_t size, uint32 protection); + status_t vm_block_address_range(const char* name, void* address, addr_t size); status_t vm_unreserve_address_range(team_id team, void *address, addr_t size); status_t vm_reserve_address_range(team_id team, void **_address, diff --git a/src/system/kernel/vm/vm.cpp b/src/system/kernel/vm/vm.cpp index 65d195e825..902a1ec2bd 100644 --- a/src/system/kernel/vm/vm.cpp +++ b/src/system/kernel/vm/vm.cpp @@ -447,6 +447,26 @@ lookup_area(VMAddressSpace* addressSpace, area_id id) } +static status_t +allocate_area_page_protections(VMArea* area) +{ + // In the page protections we store only the three user protections, + // so we use 4 bits per page. + uint32 bytes = (area->Size() / B_PAGE_SIZE + 1) / 2; + area->page_protections = (uint8*)malloc_etc(bytes, + HEAP_DONT_LOCK_KERNEL_SPACE); + if (area->page_protections == NULL) + return B_NO_MEMORY; + + // init the page protections for all pages to that of the area + uint32 areaProtection = area->protection + & (B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA); + memset(area->page_protections, areaProtection | (areaProtection << 4), + bytes); + return B_OK; +} + + static inline void set_area_page_protection(VMArea* area, addr_t pageAddress, uint32 protection) { @@ -473,6 +493,17 @@ get_area_page_protection(VMArea* area, addr_t pageAddress) else protection >>= 4; + // If this is a kernel area we translate the user flags to kernel flags. + if (area->address_space == VMAddressSpace::Kernel()) { + uint32 kernelProtection = 0; + if ((protection & B_READ_AREA) != 0) + kernelProtection |= B_KERNEL_READ_AREA; + if ((protection & B_WRITE_AREA) != 0) + kernelProtection |= B_KERNEL_WRITE_AREA; + + return kernelProtection; + } + return protection | B_KERNEL_READ_AREA | (protection & B_WRITE_AREA ? B_KERNEL_WRITE_AREA : 0); } @@ -976,6 +1007,95 @@ wait_if_address_range_is_wired(VMAddressSpace* addressSpace, addr_t base, } +/*! Prepares an area to be used for vm_set_kernel_area_debug_protection(). + It must be called in a situation where the kernel address space may be + locked. +*/ +status_t +vm_prepare_kernel_area_debug_protection(area_id id, void** cookie) +{ + AddressSpaceReadLocker locker; + VMArea* area; + status_t status = locker.SetFromArea(id, area); + if (status != B_OK) + return status; + + if (area->page_protections == NULL) { + status = allocate_area_page_protections(area); + if (status != B_OK) + return status; + } + + *cookie = (void*)area; + return B_OK; +} + + +/*! This is a debug helper function that can only be used with very specific + use cases. + Sets protection for the given address range to the protection specified. + If \a protection is 0 then the involved pages will be marked non-present + in the translation map to cause a fault on access. The pages aren't + actually unmapped however so that they can be marked present again with + additional calls to this function. For this to work the area must be + fully locked in memory so that the pages aren't otherwise touched. + This function does not lock the kernel address space and needs to be + supplied with a \a cookie retrieved from a successful call to + vm_prepare_kernel_area_debug_protection(). +*/ +status_t +vm_set_kernel_area_debug_protection(void* cookie, void* _address, size_t size, + uint32 protection) +{ + // check address range + addr_t address = (addr_t)_address; + size = PAGE_ALIGN(size); + + if ((address % B_PAGE_SIZE) != 0 + || (addr_t)address + size < (addr_t)address + || !IS_KERNEL_ADDRESS(address) + || !IS_KERNEL_ADDRESS((addr_t)address + size)) { + return B_BAD_VALUE; + } + + // Translate the kernel protection to user protection as we only store that. + if ((protection & B_KERNEL_READ_AREA) != 0) + protection |= B_READ_AREA; + if ((protection & B_KERNEL_WRITE_AREA) != 0) + protection |= B_WRITE_AREA; + + VMAddressSpace* addressSpace = VMAddressSpace::GetKernel(); + VMTranslationMap* map = addressSpace->TranslationMap(); + VMArea* area = (VMArea*)cookie; + + addr_t offset = address - area->Base(); + if (area->Size() - offset < size) { + panic("protect range not fully within supplied area"); + return B_BAD_VALUE; + } + + if (area->page_protections == NULL) { + panic("area has no page protections"); + return B_BAD_VALUE; + } + + // Invalidate the mapping entries so any access to them will fault or + // restore the mapping entries unchanged so that lookup will success again. + map->Lock(); + map->DebugMarkRangePresent(address, address + size, protection != 0); + map->Unlock(); + + // And set the proper page protections so that the fault case will actually + // fail and not simply try to map a new page. + for (addr_t pageAddress = address; pageAddress < address + size; + pageAddress += B_PAGE_SIZE) { + set_area_page_protection(area, pageAddress, protection); + } + + return B_OK; +} + + status_t vm_block_address_range(const char* name, void* address, addr_t size) { @@ -6182,18 +6302,9 @@ _user_set_memory_protection(void* _address, size_t size, uint32 protection) if (area->protection == protection) continue; - // In the page protections we store only the three user protections, - // so we use 4 bits per page. - uint32 bytes = (area->Size() / B_PAGE_SIZE + 1) / 2; - area->page_protections = (uint8*)malloc(bytes); - if (area->page_protections == NULL) - return B_NO_MEMORY; - - // init the page protections for all pages to that of the area - uint32 areaProtection = area->protection - & (B_READ_AREA | B_WRITE_AREA | B_EXECUTE_AREA); - memset(area->page_protections, - areaProtection | (areaProtection << 4), bytes); + status_t status = allocate_area_page_protections(area); + if (status != B_OK) + return status; } // We need to lock the complete cache chain, since we potentially unmap