kernel/vm: check if page is in area

Checks if a `vm_page` is part of a `VMArea` before doing work with
it, as pages in a `VMCache` that an area is a part of might not
belong to that area.

This fixes a bug for copy-on-write areas when an application is
`fork`ing.

Change-Id: Ic5683c67865b41bf3708bb7ea4104502ddf31a19
Reviewed-on: https://review.haiku-os.org/c/haiku/+/6496
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: Jérôme Duval <[email protected]>
Reviewed-by: Axel Dörfler <[email protected]>
This commit is contained in:
Trung Nguyen
2023-06-19 15:59:11 +00:00
committed by Jérôme Duval
parent bdcc293fa8
commit 79572316c4
2 changed files with 77 additions and 6 deletions
+16 -6
View File
@@ -443,6 +443,15 @@ virtual_page_address(VMArea* area, vm_page* page)
}
static inline bool
is_page_in_area(VMArea* area, vm_page* page)
{
off_t pageCacheOffsetBytes = (off_t)(page->cache_offset << PAGE_SHIFT);
return pageCacheOffsetBytes >= area->cache_offset
&& pageCacheOffsetBytes < area->cache_offset + (off_t)area->Size();
}
//! You need to have the address space locked when calling this function
static VMArea*
lookup_area(VMAddressSpace* addressSpace, area_id id)
@@ -924,15 +933,10 @@ cut_area(VMAddressSpace* addressSpace, VMArea* area, addr_t address,
// Set the correct page protections for the second area.
VMTranslationMap* map = addressSpace->TranslationMap();
map->Lock();
page_num_t firstPageOffset
= secondArea->cache_offset / B_PAGE_SIZE;
page_num_t lastPageOffset
= firstPageOffset + secondArea->Size() / B_PAGE_SIZE;
for (VMCachePagesTree::Iterator it
= secondArea->cache->pages.GetIterator();
vm_page* page = it.Next();) {
if (page->cache_offset >= firstPageOffset
&& page->cache_offset <= lastPageOffset) {
if (is_page_in_area(secondArea, page)) {
addr_t address = virtual_page_address(secondArea, page);
uint32 pageProtection
= get_area_page_protection(secondArea, address);
@@ -2641,6 +2645,9 @@ vm_copy_on_write_area(VMCache* lowerCache,
// Change the protection of this page in all areas.
for (VMArea* tempArea = upperCache->areas; tempArea != NULL;
tempArea = tempArea->cache_next) {
if (!is_page_in_area(tempArea, page))
continue;
// The area must be readable in the same way it was
// previously writable.
addr_t address = virtual_page_address(tempArea, page);
@@ -2671,6 +2678,9 @@ vm_copy_on_write_area(VMCache* lowerCache,
map->Lock();
for (VMCachePagesTree::Iterator it = lowerCache->pages.GetIterator();
vm_page* page = it.Next();) {
if (!is_page_in_area(tempArea, page))
continue;
// The area must be readable in the same way it was
// previously writable.
addr_t address = virtual_page_address(tempArea, page);
@@ -4,9 +4,11 @@
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <OS.h>
@@ -72,6 +74,62 @@ map_protect_cut_test()
}
int
map_cut_fork_test()
{
char name[24];
sprintf(name, "/shm-mmap-cut-fork-test-%d", getpid());
name[sizeof(name) - 1] = '\0';
shm_unlink(name);
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, 0600);
shm_unlink(name);
if (fd < 0) {
printf("failed to create temporary file!\n");
return fd;
}
ftruncate(fd, B_PAGE_SIZE * 4);
uint8* ptr = (uint8*)mmap(NULL, B_PAGE_SIZE * 4, PROT_NONE, MAP_PRIVATE,
fd, 0);
// make the head accessible and also force the kernel to allocate the
// page_protections array
mprotect(ptr, B_PAGE_SIZE, PROT_READ | PROT_WRITE);
// store any value
ptr[0] = 'a';
// cut the area in the middle
mmap(ptr + B_PAGE_SIZE, B_PAGE_SIZE, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
// validate that the fork does not crash the kernel
int pid = fork();
if (pid == 0)
{
exit(0);
}
else if (pid < 0)
{
printf("failed to fork the test process!\n");
return pid;
}
int status;
waitpid(pid, &status, 0);
// validate that this does not crash
if (ptr[0] != 'a') {
printf("map-cut-fork test failed!\n");
return -1;
}
return 0;
}
int
main()
{
@@ -90,5 +148,8 @@ main()
if ((status = map_protect_cut_test()) != 0)
return status;
if ((status = map_cut_fork_test()) != 0)
return status;
return 0;
}