diff options
author | Diego Nieto Cid <dnietoc@gmail.com> | 2025-09-21 21:23:42 +0100 |
---|---|---|
committer | Samuel Thibault <samuel.thibault@ens-lyon.org> | 2025-09-22 00:51:29 +0200 |
commit | 9ae4d99c1d1e7c9c0977cebb4df9b86fa92cca94 (patch) | |
tree | 62e1baaaefde974b943755eb8771ad0a2ca182f4 | |
parent | 1269629d90b28a23ef9742645cfaf657ea3165bb (diff) |
* doc/mach.texi: add a "Memory Limitations" section to document the new interfaces.
* include/mach/gnumach.defs: (vm_set_size_limit) new routine
(vm_get_size_limit) likewise
* kern/task.c: (task_create_kernel) if parent_task is not null copy virtual memory limit
* tests/test-vm.c: (test_vm_limit) add test for the new routines
* vm/vm_map.h: (struct vm_map) new fields size_none, size_cur_limit and size_max_limit
(vm_map_find_entry) add new parameters cur_protection and max_protection
* vm/vm_map.c: (vm_map_setup) initialize new fields
(vm_map_enforce_limit) new function
(vm_map_copy_limits) new function
(vm_map_find_entry) add protection and max_protection parameters.
call limit enforcer function
(vm_map_enter) likewise
(vm_map_copyout) likewise
(vm_map_copyout_page_list) likewise
(vm_map_fork) copy parent limit to the new map and compute and set size_none of the new map
* vm/vm_user.c: (vm_set_size_limit) new function
(vm_get_size_limit) likewise
* xen/grant.c: update call to vm_map_find_entry to pass protection parameters
Message-ID: <0b71f4f89b7cc2b159893a805480d7493d522d60.1758485757.git.dnietoc@gmail.com>
-rw-r--r-- | doc/mach.texi | 29 | ||||
-rw-r--r-- | include/mach/gnumach.defs | 34 | ||||
-rw-r--r-- | kern/task.c | 5 | ||||
-rw-r--r-- | tests/test-vm.c | 96 | ||||
-rw-r--r-- | vm/vm_kern.c | 22 | ||||
-rw-r--r-- | vm/vm_map.c | 148 | ||||
-rw-r--r-- | vm/vm_map.h | 15 | ||||
-rw-r--r-- | vm/vm_user.c | 69 | ||||
-rw-r--r-- | xen/grant.c | 3 |
9 files changed, 400 insertions, 21 deletions
diff --git a/doc/mach.texi b/doc/mach.texi index e2328c1b..1f28cea3 100644 --- a/doc/mach.texi +++ b/doc/mach.texi @@ -203,6 +203,7 @@ Virtual Memory Interface * Data Transfer:: Reading, writing and copying memory. * Memory Attributes:: Tweaking memory regions. * Mapping Memory Objects:: How to map memory objects. +* Memory Limitations:: Tweaking virtual memory space limts. * Memory Statistics:: How to get statistics about memory usage. External Memory Management @@ -3029,6 +3030,7 @@ the kernel. * Data Transfer:: Reading, writing and copying memory. * Memory Attributes:: Tweaking memory regions. * Mapping Memory Objects:: How to map memory objects. +* Memory Limitations:: Tweaking virtual memory limits. * Memory Statistics:: How to get statistics about memory usage. * Memory physical addresses:: How to get physical addresses of memory. @end menu @@ -3476,6 +3478,33 @@ found, and @code{KERN_INVALID_ARGUMENT} if an invalid argument was provided. @end deftypefun +@node Memory Limitations +@section Memory Limitations + +@deftypefun kern_reutrn_t vm_get_size_limit (@w{vm_task_t @var{map}}, @w{vm_size_t *@var{current_limit}}, @w{vm_size_t *@var{max_limit}}) +This function will return the current limit and the maximum limit of the +virtual memory address space of the task indicated by the @var{map} argument +in the parameters @var{current_limit} and @var{max_limit}, respectively. +@end deftypefun + +@deftypefun kern_return_t vm_set_size_limit (@w{mach_port_t @var{host_port}}, @w{vm_task_t @var{map}}, @w{vm_size_t @var{current_limit}}, @w{vm_size_t @var{max_limit}}) +This function shall be used to update the virtual address space limits +of the task given by the @var{map} argument. + +If the value of the argument @var{current_limit} is greater than @var{max_limit}, +the function returns @code{KERN_INVALID_ARGUMENT}. The privileged host port +must be provided in the @var{host_port} argument when the function is used +to increase the current max limit of the address space. Otherwise the function +returns @code{KERN_NO_ACCESS}. + +The function returns @code{KERN_INVALID_TASK} or @code{KERN_INVALID_HOST} when +the arguments @var{map} and @var{host_port} are not a vlaid task or host, respectively. + +@code{KERN_SUCCESS} is returned when the map limits could be updated +successfuly. +@end deftypefun + + @node Memory Statistics @section Memory Statistics diff --git a/include/mach/gnumach.defs b/include/mach/gnumach.defs index f13e866b..f5b2f7f2 100644 --- a/include/mach/gnumach.defs +++ b/include/mach/gnumach.defs @@ -223,3 +223,37 @@ simpleroutine thread_set_name( routine thread_get_name( thread : thread_t; out name : kernel_debug_name_t); + +/* + * Set a task virtual memory limit parameters + * + * HOST_PORT must be the privileged host control port + * if the caller desires to increase the current max limit. + * + * On the other hand, if the max limit is being decreased, the + * unprivileged host control port (as returned by mach_host_self()) + * can be provided. + * + * Returns: + * - KERN_SUCCESS + * - KERN_INVALID_TASK + * - KERN_INVALID_HOST + * - KERN_INVALID_ARGUMENT + * * when current_limit > max_limit + * - KERN_NO_ACCESS + * * attempt to increase max limit without providing + * the privileged host control port. + */ +routine vm_set_size_limit( + host_port : mach_port_t; + map : vm_task_t; + current_limit : vm_size_t; + max_limit : vm_size_t); + +/* + * Get a task virtual memory limit parameters + */ +routine vm_get_size_limit( + map : vm_task_t; + out current_limit : vm_size_t; + out max_limit : vm_size_t); diff --git a/kern/task.c b/kern/task.c index bd57ca2a..e78e856f 100644 --- a/kern/task.c +++ b/kern/task.c @@ -126,6 +126,11 @@ task_create_kernel( trunc_page(VM_MAX_USER_ADDRESS)); if (new_task->map == VM_MAP_NULL) pmap_destroy(new_pmap); + else if (parent_task != TASK_NULL) { + vm_map_lock_read(parent_task->map); + vm_map_copy_limits(new_task->map, parent_task->map); + vm_map_unlock_read(parent_task->map); + } } } if (new_task->map == VM_MAP_NULL) { diff --git a/tests/test-vm.c b/tests/test-vm.c index 4ece792e..466dba7e 100644 --- a/tests/test-vm.c +++ b/tests/test-vm.c @@ -75,11 +75,107 @@ static void test_wire() // TODO check that all memory is actually wired or unwired } +void test_vm_limit() +{ + kern_return_t err; + vm_address_t mem, mem2, mem3; + const size_t M_128K = 128l * 1024l; + const size_t M_128M = 128l * 1024l * 1024l; + const size_t M_512M = 512l * 1024l * 1024l; + vm_size_t cur; + vm_size_t max; + + printf("set VM memory limitations\n"); + err = vm_set_size_limit(mach_host_self(), mach_task_self(), M_128M, M_512M); + ASSERT_RET(err, "cannot set VM limits"); + + printf("check limits are actually saved\n"); + err = vm_get_size_limit(mach_task_self(), &cur, &max); + ASSERT_RET(err, "getting the VM limit failed"); + ASSERT(cur == M_128M, "cur limit was not expected"); + ASSERT(max == M_512M, "max limit was not expected"); + + printf("check we can no longer increase the max limit\n"); + err = vm_set_size_limit(mach_host_self(), mach_task_self(), M_128M, M_512M * 2); + ASSERT(err == KERN_NO_ACCESS, "raising VM max limit shall fail with KERN_NO_ACCESS"); + + printf("alloc some memory below the limit\n"); + err = vm_allocate(mach_task_self(), &mem, M_128K, TRUE); + ASSERT_RET(err, "allocating memory below the limit must succeed"); + err = vm_deallocate(mach_task_self(), mem, M_128K); + ASSERT_RET(err, "deallocation failed"); + + printf("alloc a bigger chunk to make it hit the limit\n"); + err = vm_allocate(mach_task_self(), &mem, (M_128M * 2), TRUE); + ASSERT(err == KERN_NO_SPACE, "allocation must fail with KERN_NO_SPACE"); + + printf("check that privileged tasks can increase the hard limit\n"); + err = vm_set_size_limit(host_priv(), mach_task_self(), (M_512M + M_128M), M_512M * 2); + ASSERT_RET(err, "privileged tasks shall be allowed to increase the max limit"); + + printf("check limits are actually saved\n"); + err = vm_get_size_limit(mach_task_self(), &cur, &max); + ASSERT_RET(err, "getting the VM limit failed"); + ASSERT(cur == (M_512M + M_128M), "cur limit was not expected"); + ASSERT(max == (M_512M * 2), "max limit was not expected"); + + printf("allocating the bigger chunk with the new limit shall succeed\n"); + err = vm_allocate(mach_task_self(), &mem, (M_128M * 2), TRUE); + ASSERT_RET(err, "allocation should now succedd"); + err = vm_deallocate(mach_task_self(), mem, (M_128M * 2)); + ASSERT_RET(err, "deallocation failed"); + + printf("check that the limit does not apply to VM_PROT_NONE mappings\n"); + err = vm_map(mach_task_self(), + &mem, (M_512M * 3), 0, 0, MACH_PORT_NULL, 0, 1, + VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_COPY); + ASSERT_RET(err, "allocation of VM_PROT_NONE areas should not be subject to the limit"); + + printf("check that the VM_PROT_NONE allocation does not reduce the limit\n"); + err = vm_allocate(mach_task_self(), &mem2, M_512M, TRUE); + ASSERT_RET(err, "allocation should succedd in spite of the VM_PROT_NONE map"); + err = vm_deallocate(mach_task_self(), mem2, M_512M); + ASSERT_RET(err, "deallocation failed"); + err = vm_deallocate(mach_task_self(), mem, (M_512M * 3)); + ASSERT_RET(err, "deallocation failed"); + + printf("check that allocations demoted to VM_PROT_NONE no longer counts towards the VM limit\n"); + err = vm_allocate(mach_task_self(), &mem, M_512M, TRUE); + ASSERT_RET(err, "allocating memory below the limit must succeed"); + err = vm_allocate(mach_task_self(), &mem2, M_128M, TRUE); + /* the current limit is M_512M + M_128M, this allocation should hit the limit */ + ASSERT(err == KERN_NO_SPACE, "allocation must fail with KERN_NO_SPACE"); + err = vm_protect(mach_task_self(), mem, M_512M, TRUE, VM_PROT_NONE); + ASSERT_RET(err, "could not drop protection to VM_PROT_NONE"); + /* after dropping the protection there should be enough space again */ + err = vm_allocate(mach_task_self(), &mem2, M_128M, TRUE); + ASSERT_RET(err, "allocating memory below the limit must succeed"); + /* this allocation purpose is showing the failure message to check size_none value */ + err = vm_allocate(mach_task_self(), &mem3, M_512M, TRUE); + ASSERT(err == KERN_NO_SPACE, "going above the limit should still fail"); + err = vm_deallocate(mach_task_self(), mem2, M_128M); + ASSERT_RET(err, "deallocation failed"); + err = vm_deallocate(mach_task_self(), mem, M_512M); + ASSERT_RET(err, "deallocation failed"); + + printf("check that protection cannot be upgraded from VM_PROT_NONE\n"); + err = vm_map(mach_task_self(), + &mem, M_512M, 0, 0, MACH_PORT_NULL, 0, 1, + VM_PROT_NONE, VM_PROT_NONE, VM_INHERIT_COPY); + ASSERT_RET(err, "allocation failed"); + /* attempt to broaden the access to the mapped memory */ + err = vm_protect(mach_task_self(), mem, M_512M, TRUE, VM_PROT_ALL); + ASSERT(err == KERN_PROTECTION_FAILURE, "max protection can only be set to strictier states"); + err = vm_deallocate(mach_task_self(), mem, M_512M); + ASSERT_RET(err, "deallocation failed"); +} + int main(int argc, char *argv[], int envc, char *envp[]) { printf("VM_MIN_ADDRESS=0x%p\n", VM_MIN_ADDRESS); printf("VM_MAX_ADDRESS=0x%p\n", VM_MAX_ADDRESS); test_wire(); test_memobj(); + test_vm_limit(); return 0; } diff --git a/vm/vm_kern.c b/vm/vm_kern.c index 51223d98..37185687 100644 --- a/vm/vm_kern.c +++ b/vm/vm_kern.c @@ -108,7 +108,8 @@ projected_buffer_allocate( vm_map_lock(kernel_map); kr = vm_map_find_entry(kernel_map, &addr, size, (vm_offset_t) 0, - VM_OBJECT_NULL, &k_entry); + VM_OBJECT_NULL, &k_entry, + VM_PROT_DEFAULT, VM_PROT_ALL); if (kr != KERN_SUCCESS) { vm_map_unlock(kernel_map); vm_object_deallocate(object); @@ -125,7 +126,8 @@ projected_buffer_allocate( vm_map_lock(map); kr = vm_map_find_entry(map, &addr, size, (vm_offset_t) 0, - VM_OBJECT_NULL, &u_entry); + VM_OBJECT_NULL, &u_entry, + protection, protection); if (kr != KERN_SUCCESS) { vm_map_unlock(map); vm_map_lock(kernel_map); @@ -141,8 +143,6 @@ projected_buffer_allocate( /*Creates coupling with kernel mapping of the buffer, and also guarantees that user cannot directly manipulate buffer VM entry*/ - u_entry->protection = protection; - u_entry->max_protection = protection; u_entry->inheritance = inheritance; vm_map_unlock(map); *user_p = addr; @@ -209,7 +209,8 @@ projected_buffer_map( vm_map_lock(map); kr = vm_map_find_entry(map, &user_addr, size, (vm_offset_t) 0, - VM_OBJECT_NULL, &u_entry); + VM_OBJECT_NULL, &u_entry, + protection, protection); if (kr != KERN_SUCCESS) { vm_map_unlock(map); return kr; @@ -222,8 +223,6 @@ projected_buffer_map( /*Creates coupling with kernel mapping of the buffer, and also guarantees that user cannot directly manipulate buffer VM entry*/ - u_entry->protection = protection; - u_entry->max_protection = protection; u_entry->inheritance = inheritance; u_entry->wired_count = k_entry->wired_count; vm_map_unlock(map); @@ -393,7 +392,8 @@ kmem_alloc( retry: vm_map_lock(map); kr = vm_map_find_entry(map, &addr, size, (vm_offset_t) 0, - VM_OBJECT_NULL, &entry); + VM_OBJECT_NULL, &entry, + VM_PROT_DEFAULT, VM_PROT_ALL); if (kr != KERN_SUCCESS) { vm_map_unlock(map); @@ -465,7 +465,8 @@ kmem_valloc( retry: vm_map_lock(map); kr = vm_map_find_entry(map, &addr, size, (vm_offset_t) 0, - kernel_object, &entry); + kernel_object, &entry, + VM_PROT_DEFAULT, VM_PROT_ALL); if (kr != KERN_SUCCESS) { vm_map_unlock(map); @@ -585,7 +586,8 @@ kmem_alloc_aligned( retry: vm_map_lock(map); kr = vm_map_find_entry(map, &addr, size, size - 1, - kernel_object, &entry); + kernel_object, &entry, + VM_PROT_DEFAULT, VM_PROT_ALL); if (kr != KERN_SUCCESS) { vm_map_unlock(map); diff --git a/vm/vm_map.c b/vm/vm_map.c index e1f8af9e..ff466d35 100644 --- a/vm/vm_map.c +++ b/vm/vm_map.c @@ -189,6 +189,7 @@ void vm_map_setup( map->size = 0; map->size_wired = 0; + map->size_none = 0; map->ref_count = 1; map->pmap = pmap; map->min_offset = min; @@ -198,6 +199,14 @@ void vm_map_setup( map->first_free = vm_map_to_entry(map); map->hint = vm_map_to_entry(map); map->name = NULL; + /* TODO add to default limit the swap size */ + if (pmap != kernel_pmap) { + map->size_cur_limit = vm_page_mem_size() / 2; + map->size_max_limit = vm_page_mem_size() / 2; + } else { + map->size_cur_limit = (~0UL); + map->size_max_limit = (~0UL); + } vm_map_lock_init(map); simple_lock_init(&map->ref_lock); simple_lock_init(&map->hint_lock); @@ -269,6 +278,46 @@ void vm_map_unlock(struct vm_map *map) } /* + * Enforces the VM limit of a target map. + */ +static kern_return_t +vm_map_enforce_limit( + vm_map_t map, + vm_size_t size, + const char *fn_name) +{ + /* Limit is ignored for the kernel map */ + if (vm_map_pmap(map) == kernel_pmap) { + return KERN_SUCCESS; + } + + /* Avoid taking into account the total VM_PROT_NONE virtual memory */ + vm_size_t really_allocated_size = map->size - map->size_none; + vm_size_t new_size = really_allocated_size + size; + /* Check for integer overflow */ + if (new_size < size) { + return KERN_INVALID_ARGUMENT; + } + + if (new_size > map->size_cur_limit) { + return KERN_NO_SPACE; + } + + return KERN_SUCCESS; +} + +/* + * Copies the limits from source to destination map. + * Called by task_create_kernel with the src_map locked. + */ +void +vm_map_copy_limits(vm_map_t dst_map, vm_map_t src_map) +{ + dst_map->size_cur_limit = src_map->size_cur_limit; + dst_map->size_max_limit = src_map->size_max_limit; +} + +/* * vm_map_entry_create: [ internal use only ] * * Allocates a VM map entry for insertion in the @@ -779,6 +828,21 @@ error: * If an entry is allocated, the object/offset fields * are initialized to zero. If an object is supplied, * then an existing entry may be extended. + * + * Before allocating a new virtual address map the VM + * space limits are checked. The protection and max_protection + * arguments are essential for properly enforcing the limits + * at the point where the entry is allocated (i.e. skipping the + * checks when max_protextion is VM_PROT_NONE). + * + * Having the max_protection argument allows the size of + * the requested entry to be accounted as used virtual memory + * or unused virtual memory (VM_PROT_NONE), in which case the + * size_none field of the map is incremented by the requested + * size. + * + * As a result, the allocated entry will have its protection + * and max_protection fields set before return. */ kern_return_t vm_map_find_entry( vm_map_t map, @@ -786,11 +850,19 @@ kern_return_t vm_map_find_entry( vm_size_t size, vm_offset_t mask, vm_object_t object, - vm_map_entry_t *o_entry) /* OUT */ + vm_map_entry_t *o_entry, /* OUT */ + vm_prot_t protection, + vm_prot_t max_protection) { vm_map_entry_t entry, new_entry; vm_offset_t start; vm_offset_t end; + kern_return_t err; + + + if (max_protection != VM_PROT_NONE) + if ((err = vm_map_enforce_limit(map, size, "vm_map_find_entry")) != KERN_SUCCESS) + return err; entry = vm_map_find_entry_anywhere(map, size, mask, TRUE, &start); @@ -827,8 +899,8 @@ kern_return_t vm_map_find_entry( (entry->object.vm_object == object) && (entry->needs_copy == FALSE) && (entry->inheritance == VM_INHERIT_DEFAULT) && - (entry->protection == VM_PROT_DEFAULT) && - (entry->max_protection == VM_PROT_ALL) && + (entry->protection == protection) && + (entry->max_protection == max_protection) && (entry->wired_count != 0) && (entry->projected_on == 0)) { /* @@ -853,8 +925,8 @@ kern_return_t vm_map_find_entry( new_entry->needs_copy = FALSE; new_entry->inheritance = VM_INHERIT_DEFAULT; - new_entry->protection = VM_PROT_DEFAULT; - new_entry->max_protection = VM_PROT_ALL; + new_entry->protection = protection; + new_entry->max_protection = max_protection; new_entry->wired_count = 1; new_entry->wired_access = VM_PROT_DEFAULT; @@ -870,6 +942,8 @@ kern_return_t vm_map_find_entry( } map->size += size; + if (max_protection == VM_PROT_NONE) + map->size_none += size; /* * Update the free space hint and the lookup hint @@ -1043,6 +1117,16 @@ MACRO_END } /* + * If the allocation has protection equal to VM_PROT_NONE, + * don't check for limits as the map's size_none field is + * not yet incremented. + */ + if (max_protection != VM_PROT_NONE) { + if ((result = vm_map_enforce_limit(map, size, "vm_map_enter")) != KERN_SUCCESS) + RETURN(result); + } + + /* * At this point, * "start" and "end" should define the endpoints of the * available new range, and @@ -1082,6 +1166,8 @@ MACRO_END * new range. */ map->size += size; + if (max_protection == VM_PROT_NONE) + map->size_none += size; entry->vme_end = end; vm_map_gap_update(&map->hdr, entry); /* @@ -1118,6 +1204,8 @@ MACRO_END * new range. */ map->size += size; + if (max_protection == VM_PROT_NONE) + map->size_none += size; next_entry->vme_start = start; vm_map_gap_update(&map->hdr, entry); /* @@ -1165,6 +1253,8 @@ MACRO_END vm_map_entry_link(map, entry, new_entry); map->size += size; + if (max_protection == VM_PROT_NONE) + map->size_none += size; /* * Update the free space hint and the lookup hint @@ -1684,11 +1774,14 @@ kern_return_t vm_map_protect( vm_map_clip_end(map, current, end); old_prot = current->protection; - if (set_max) + if (set_max) { + if (current->max_protection != new_prot && new_prot == VM_PROT_NONE) + map->size_none += current->vme_end - current->vme_start; + current->protection = (current->max_protection = new_prot) & old_prot; - else + } else current->protection = new_prot; /* @@ -2047,6 +2140,8 @@ void vm_map_entry_delete( vm_map_entry_unlink(map, entry); map->size -= size; + if (entry->max_protection == VM_PROT_NONE) + map->size_none -= size; vm_map_entry_dispose(map, entry); } @@ -2887,6 +2982,11 @@ kern_return_t vm_map_copyout( return KERN_NO_SPACE; } + if ((kr = vm_map_enforce_limit(dst_map, size, "vm_map_copyout")) != KERN_SUCCESS) { + vm_map_unlock(dst_map); + return kr; + } + /* * Adjust the addresses in the copy chain, and * reset the region attributes. @@ -2990,6 +3090,10 @@ kern_return_t vm_map_copyout( SAVE_HINT(dst_map, vm_map_copy_last_entry(copy)); dst_map->size += size; + /* + * dst_map->size_none need no updating because the protection + * of all entries is VM_PROT_DEFAULT / VM_PROT_ALL + */ /* * Link in the copy @@ -3067,6 +3171,11 @@ kern_return_t vm_map_copyout_page_list( return KERN_NO_SPACE; } + if ((result = vm_map_enforce_limit(dst_map, size, "vm_map_copyout_page_lists")) != KERN_SUCCESS) { + vm_map_unlock(dst_map); + return result; + } + end = start + size; must_wire = dst_map->wiring_required; @@ -3155,6 +3264,11 @@ kern_return_t vm_map_copyout_page_list( * new range. */ dst_map->size += size; + /* + * dst_map->size_none need no updating because the protection + * of `last` entry is VM_PROT_DEFAULT / VM_PROT_ALL (otherwise + * the flow would have jumped to create_object). + */ last->vme_end = end; vm_map_gap_update(&dst_map->hdr, last); @@ -3211,6 +3325,10 @@ create_object: } SAVE_HINT(dst_map, entry); dst_map->size += size; + /* + * dst_map->size_none need no updating because the protection + * of `entry` is VM_PROT_DEFAULT / VM_PROT_ALL + */ /* * Link in the entry @@ -4395,6 +4513,7 @@ vm_map_t vm_map_fork(vm_map_t old_map) vm_map_entry_t new_entry; pmap_t new_pmap = pmap_create((vm_size_t) 0); vm_size_t new_size = 0; + vm_size_t new_size_none = 0; vm_size_t entry_size; vm_object_t object; @@ -4529,6 +4648,8 @@ vm_map_t vm_map_fork(vm_map_t old_map) old_entry->vme_start); new_size += entry_size; + if (old_entry->max_protection == VM_PROT_NONE) + new_size_none += entry_size; break; case VM_INHERIT_COPY: @@ -4577,6 +4698,8 @@ vm_map_t vm_map_fork(vm_map_t old_map) new_size += entry_size; + if (old_entry->max_protection == VM_PROT_NONE) + new_size_none += entry_size; break; } @@ -4614,6 +4737,8 @@ vm_map_t vm_map_fork(vm_map_t old_map) vm_map_copy_insert(new_map, last, copy); new_size += entry_size; + if (old_entry->max_protection == VM_PROT_NONE) + new_size_none += entry_size; /* * Pick up the traversal at the end of @@ -4635,6 +4760,8 @@ vm_map_t vm_map_fork(vm_map_t old_map) } new_map->size = new_size; + new_map->size_none = new_size_none; + vm_map_copy_limits(new_map, old_map); vm_map_unlock(old_map); return(new_map); @@ -5166,8 +5293,11 @@ void vm_map_print(db_expr_t addr, boolean_t have_addr, db_expr_t count, const ch iprintf("Map 0x%X: name=\"%s\", pmap=0x%X,", (vm_offset_t) map, map->name, (vm_offset_t) (map->pmap)); printf("ref=%d,nentries=%d\n", map->ref_count, map->hdr.nentries); - printf("size=%lu,resident:%lu,wired=%lu\n", map->size, - pmap_resident_count(map->pmap) * PAGE_SIZE, map->size_wired); + printf("size=%lu,resident:%lu,wired=%lu,none=%lu\n", map->size, + pmap_resident_count(map->pmap) * PAGE_SIZE, map->size_wired, + map->size_none); + printf("max_limit=%lu,cur_limit=%lu\n", + map->size_max_limit, map->size_cur_limit); printf("version=%d\n", map->timestamp); indent += 1; for (entry = vm_map_first_entry(map); diff --git a/vm/vm_map.h b/vm/vm_map.h index 900f1218..1b66dda4 100644 --- a/vm/vm_map.h +++ b/vm/vm_map.h @@ -184,6 +184,7 @@ struct vm_map { pmap_t pmap; /* Physical map */ vm_size_t size; /* virtual size */ vm_size_t size_wired; /* wired size */ + vm_size_t size_none; /* none protection size */ int ref_count; /* Reference count */ decl_simple_lock_data(, ref_lock) /* Lock for ref_count field */ vm_map_entry_t hint; /* hint for quick lookups */ @@ -198,6 +199,10 @@ struct vm_map { unsigned int timestamp; /* Version number */ const char *name; /* Associated name */ + + vm_size_t size_cur_limit; /* current limit on virtual memory size */ + vm_size_t size_max_limit; /* maximum size an unprivileged user can + change current limit to */ }; #define vm_map_to_entry(map) ((struct vm_map_entry *) &(map)->hdr.links) @@ -401,7 +406,7 @@ extern kern_return_t vm_map_enter(vm_map_t, vm_offset_t *, vm_size_t, /* Enter a mapping primitive */ extern kern_return_t vm_map_find_entry(vm_map_t, vm_offset_t *, vm_size_t, vm_offset_t, vm_object_t, - vm_map_entry_t *); + vm_map_entry_t *, vm_prot_t, vm_prot_t); /* Deallocate a region */ extern kern_return_t vm_map_remove(vm_map_t, vm_offset_t, vm_offset_t); /* Change protection */ @@ -582,4 +587,12 @@ void _vm_map_clip_end( vm_offset_t end, boolean_t link_gap); +/* + * This function is called to inherit the virtual memory limits + * from one vm_map_t to another. + */ +void vm_map_copy_limits( + vm_map_t dst, + vm_map_t src); + #endif /* _VM_VM_MAP_H_ */ diff --git a/vm/vm_user.c b/vm/vm_user.c index c6dbda68..ef2c39d7 100644 --- a/vm/vm_user.c +++ b/vm/vm_user.c @@ -813,3 +813,72 @@ kern_return_t vm_pages_phys( return KERN_SUCCESS; } + +/* + * vm_set_size_limit + * + * Sets the current/maximum virtual adress space limits + * of the `target_task`. + * + * The host privileged port must be provided to increase + * the max limit. + */ +kern_return_t +vm_set_size_limit( + const ipc_port_t host_port, + vm_map_t map, + vm_size_t current_limit, + vm_size_t max_limit) +{ + ipc_kobject_type_t ikot_host = IKOT_NONE; + + if (current_limit > max_limit) + return KERN_INVALID_ARGUMENT; + if (map == VM_MAP_NULL) + return KERN_INVALID_TASK; + + if (!IP_VALID(host_port)) + return KERN_INVALID_HOST; + ip_lock(host_port); + if (ip_active(host_port)) + ikot_host = ip_kotype(host_port); + ip_unlock(host_port); + + if (ikot_host != IKOT_HOST && ikot_host != IKOT_HOST_PRIV) + return KERN_INVALID_HOST; + + vm_map_lock(map); + if (max_limit > map->size_max_limit && ikot_host != IKOT_HOST_PRIV) { + vm_map_unlock(map); + return KERN_NO_ACCESS; + } + + map->size_cur_limit = current_limit; + map->size_max_limit = max_limit; + vm_map_unlock(map); + + return KERN_SUCCESS; +} + +/* + * vm_get_size_limit + * + * Gets the current/maximum virtual adress space limits + * of the provided `map`. + */ +kern_return_t +vm_get_size_limit( + vm_map_t map, + vm_size_t *current_limit, + vm_size_t *max_limit) +{ + if (map == VM_MAP_NULL) + return KERN_INVALID_TASK; + + vm_map_lock_read(map); + *current_limit = map->size_cur_limit; + *max_limit = map->size_max_limit; + vm_map_unlock_read(map); + + return KERN_SUCCESS; +} diff --git a/xen/grant.c b/xen/grant.c index 84758cfc..dd2fabf7 100644 --- a/xen/grant.c +++ b/xen/grant.c @@ -135,7 +135,8 @@ void hyp_grant_init(void) { simple_lock_init(&lock); vm_map_find_entry(kernel_map, &addr, NR_GRANT_PAGES * PAGE_SIZE, - (vm_offset_t) 0, kernel_object, &grants_map_entry); + (vm_offset_t) 0, kernel_object, &grants_map_entry, + VM_PROT_DEFAULT, VM_PROT_ALL); grants = (void*) addr; for (i = 0; i < NR_GRANT_PAGES; i++) |