From: Diego Nieto Cid <dnie...@gmail.com> * include/mach/gnumach.defs: (task_set_vm_limit) new routine (task_get_vm_limit) likewise * kern/task.c: (task_create_kernel) if parent_task is not null copy virtual memory limit (task_set_vm_limit) new function (task_get_vm_limit) likewise * tests/test-task.c: (test_vm_limits) add test for the new routines * vm/vm_map.h: (struct vm_map) new fields size_none, map_size_cur_limit and map_size_max_limit * vm/vm_map.c: (vm_map_setup) initialize new fields (vm_map_enforce_limits) new function (vm_map_copy_limits) new function (vm_map_find_entry) call limit enforcer function (vm_map_enter) likewise (vm_map_copyout) likewise (vm_map_copyout_page_list) likewise (vm_map_fork) compute and set size_none of the new map --- include/mach/gnumach.defs | 18 ++++++++ kern/task.c | 74 ++++++++++++++++++++++++++++++ tests/test-task.c | 45 ++++++++++++++++++ vm/vm_map.c | 96 ++++++++++++++++++++++++++++++++++++++- vm/vm_map.h | 13 ++++++ 5 files changed, 244 insertions(+), 2 deletions(-)
diff --git a/include/mach/gnumach.defs b/include/mach/gnumach.defs index f13e866b..5c0b8b3d 100644 --- a/include/mach/gnumach.defs +++ b/include/mach/gnumach.defs @@ -223,3 +223,21 @@ simpleroutine thread_set_name( routine thread_get_name( thread : thread_t; out name : kernel_debug_name_t); + +/* + * Set the target_task virtual memory limit + */ +routine task_set_vm_limit( + target_task : task_t; + host_priv : host_priv_t; + current_limit : vm_size_t; + max_limit : vm_size_t); + +/* + * Get the target_task virtual memory limit + */ +routine task_get_vm_limit( + target_task : task_t; + host_priv : host_priv_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..e8074bfa 100644 --- a/kern/task.c +++ b/kern/task.c @@ -167,6 +167,10 @@ task_create_kernel( pset_reference(pset); new_task->priority = parent_task->priority; task_unlock(parent_task); + + vm_map_lock(parent_task->map); + vm_map_copy_limits(parent_task->map, new_task->map); + vm_map_unlock(parent_task->map); } else { pset = &default_pset; @@ -1355,3 +1359,73 @@ register_new_task_notification( new_task_notification = notification; return KERN_SUCCESS; } + +/* + * task_set_vm_limit + * + * Sets the current/maximum virtual adress space limits + * of the `target_task`. + * + * The host privileged port must be provided to alter the + * limits in any of the following ways: + * + * - limit an arbitrary task; otherwise target_task must be the current task + * - increase the maximum limit + */ +kern_return_t +task_set_vm_limit( + task_t target_task, + host_t host, + vm_size_t current_limit, + vm_size_t max_limit) +{ + if (target_task == TASK_NULL || (current_task() != target_task && host == HOST_NULL)) { + return KERN_INVALID_ARGUMENT; + } + + if (current_limit > max_limit) { + return KERN_INVALID_ARGUMENT; + } + + vm_map_lock(target_task->map); + if (host == HOST_NULL) { + if (target_task->map->map_size_max_limit < max_limit) { + vm_map_unlock(target_task->map); + return KERN_INVALID_ARGUMENT; + } + } + + target_task->map->map_size_cur_limit = current_limit; + target_task->map->map_size_max_limit = max_limit; + vm_map_unlock(target_task->map); + + return KERN_SUCCESS; +} + +/* + * task_get_vm_limit + * + * Gets the current/maximum virtual adress space limits + * of the `target_task`. + * + * The host privileged port must be provided to operate + * on task other than the current task. + */ +kern_return_t +task_get_vm_limit( + task_t target_task, + host_t host, + vm_size_t *current_limit, + vm_size_t *max_limit) +{ + if (target_task == TASK_NULL || (current_task() != target_task && host == HOST_NULL)) { + return KERN_INVALID_ARGUMENT; + } + + vm_map_lock(target_task->map); + *current_limit = target_task->map->map_size_cur_limit; + *max_limit = target_task->map->map_size_max_limit; + vm_map_unlock(target_task->map); + + return KERN_SUCCESS; +} diff --git a/tests/test-task.c b/tests/test-task.c index cbc75e23..789eb680 100644 --- a/tests/test-task.c +++ b/tests/test-task.c @@ -160,6 +160,50 @@ int test_errors() ASSERT(err == MACH_SEND_INVALID_DEST, "task DEAD"); } +void test_vm_limits() +{ +#define HOST_NULL ((host_t) 0) + kern_return_t err; + vm_address_t mem; + const size_t M_128M = 128l * 1024l * 1024l; + const size_t M_512M = 512l * 1024l * 1024l; + vm_size_t cur; + vm_size_t max; + + /* set VM memory limitations */ + err = task_set_vm_limit(mach_task_self(), HOST_NULL, M_128M, M_512M); + ASSERT(err == KERN_SUCCESS, "cannot set VM limits"); + + /* check limits are actually saved */ + err = task_get_vm_limit(mach_task_self(), HOST_NULL, &cur, &max); + ASSERT(err == KERN_SUCCESS, "getting the VM limits failed"); + ASSERT(cur == M_128M, "cur limit was not expected"); + ASSERT(max == M_512M, "max limit was not expected"); + + /* check we can no longer increase the hard limit */ + err = task_set_vm_limit(mach_task_self(), HOST_NULL, M_128M, M_512M * 2); + ASSERT(err == KERN_INVALID_ARGUMENT, "raising VM hard limit shall fail"); + + /* alloc some memory below the limit */ + err = vm_allocate(mach_task_self(), &mem, (128l * 1024l), TRUE); + ASSERT(err == KERN_SUCCESS, "allocating memory below the limit must succeed"); + vm_deallocate(mach_task_self(), mem, (128l * 1024l)); + + /* alloc a bigger chunk to make it hit the limit */ + err = vm_allocate(mach_task_self(), &mem, (M_512M * 2), TRUE); + ASSERT(err == KERN_NO_SPACE, "allocation must fail with KERN_NO_SPACE"); + + /* check that "root" can increase the hard limit */ + err = task_set_vm_limit(mach_task_self(), host_priv(), M_128M, M_512M * 2); + ASSERT(err == KERN_SUCCESS, "\"root\" shall be allowed to increase the hard limit"); + + /* check limits are actually saved */ + err = task_get_vm_limit(mach_task_self(), HOST_NULL, &cur, &max); + ASSERT(err == KERN_SUCCESS, "getting the VM limits failed"); + ASSERT(cur == M_128M, "cur limit was not expected"); + ASSERT(max == (M_512M * 2), "max limit was not expected"); +#undef HOST_NULL +} int main(int argc, char *argv[], int envc, char *envp[]) { @@ -167,5 +211,6 @@ int main(int argc, char *argv[], int envc, char *envp[]) test_task_threads(); test_new_task(); test_errors(); + test_vm_limits(); return 0; } diff --git a/vm/vm_map.c b/vm/vm_map.c index 03d22ea1..cf1da0f1 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,9 @@ 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 */ + map->map_size_cur_limit = vm_page_mem_size() / 2; + map->map_size_max_limit = vm_page_mem_size() / 2; vm_map_lock_init(map); simple_lock_init(&map->ref_lock); simple_lock_init(&map->hint_lock); @@ -268,6 +272,50 @@ void vm_map_unlock(struct vm_map *map) lock_write_done(&map->lock); } +/* + * Enforces the VM limits of a target map. + */ +static kern_return_t +vm_map_enforce_limits( + vm_map_t map, + vm_size_t size, + const char *fn_name) +{ + /* Limits are 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 usable_size = map->size - map->size_none; + vm_size_t new_size = size + usable_size; + /* Check for integer overflow */ + if (new_size < size) { + return KERN_INVALID_ARGUMENT; + } + + + if (new_size > map->map_size_cur_limit) { + task_t task = current_task(); + printf("[%s] [task %s] map size: %lu, none: %lu, requested: %lu, limit: %lu\n", + fn_name, task->name, map->size, map->size_none, size, map->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 src_map, vm_map_t dst_map) +{ + dst_map->map_size_cur_limit = src_map->map_size_cur_limit; + dst_map->map_size_max_limit = src_map->map_size_max_limit; +} + /* * vm_map_entry_create: [ internal use only ] * @@ -789,6 +837,10 @@ kern_return_t vm_map_find_entry( vm_map_entry_t entry, new_entry; vm_offset_t start; vm_offset_t end; + kern_return_t err; + + if ((err = vm_map_enforce_limits(map, size, "vm_map_find_entry")) != KERN_SUCCESS) + return err; entry = vm_map_find_entry_anywhere(map, size, mask, TRUE, &start); @@ -979,6 +1031,21 @@ kern_return_t vm_map_enter( if (size == 0) return KERN_INVALID_ARGUMENT; + /* + * 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) { + /* + * Lock map to check for limits + */ + vm_map_lock(map); + if ((result = vm_map_enforce_limits(map, size, "vm_map_enter")) != KERN_SUCCESS) + RETURN(result); + vm_map_unlock(map); + } + start = *address; if (anywhere) { @@ -1160,6 +1227,7 @@ kern_return_t vm_map_enter( vm_map_entry_link(map, entry, new_entry); map->size += size; + map->size_none += ((max_protection == VM_PROT_NONE) ? size : 0); /* * Update the free space hint and the lookup hint @@ -2042,6 +2110,7 @@ void vm_map_entry_delete( vm_map_entry_unlink(map, entry); map->size -= size; + map->size_none -= entry->max_protection == VM_PROT_NONE ? size : 0; vm_map_entry_dispose(map, entry); } @@ -2870,11 +2939,24 @@ kern_return_t vm_map_copyout( return(vm_map_copyout_page_list(dst_map, dst_addr, copy)); /* - * Find space for the data + * Compute space required for the data */ - vm_copy_start = trunc_page(copy->offset); size = round_page(copy->offset + copy->size) - vm_copy_start; + + /* + * Lock dst_map to check for limits + */ + vm_map_lock(dst_map); + if ((kr = vm_map_enforce_limits(dst_map, size, "vm_map_copyout")) != KERN_SUCCESS) { + vm_map_unlock(dst_map); + return kr; + } + vm_map_unlock(dst_map); + + /* + * Find space for the data + */ last = vm_map_find_entry_anywhere(dst_map, size, 0, FALSE, &start); if (last == NULL) { @@ -3055,6 +3137,11 @@ kern_return_t vm_map_copyout_page_list( vm_map_lock(dst_map); + if ((result = vm_map_enforce_limits(dst_map, size, "vm_map_copyout_page_lists")) != KERN_SUCCESS) { + vm_map_unlock(dst_map); + return result; + } + last = vm_map_find_entry_anywhere(dst_map, size, 0, TRUE, &start); if (last == NULL) { @@ -4390,6 +4477,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; @@ -4524,6 +4612,7 @@ vm_map_t vm_map_fork(vm_map_t old_map) old_entry->vme_start); new_size += entry_size; + new_size_none += ((old_entry->max_protection == VM_PROT_NONE) ? entry_size : 0); break; case VM_INHERIT_COPY: @@ -4572,6 +4661,7 @@ vm_map_t vm_map_fork(vm_map_t old_map) new_size += entry_size; + new_size_none += ((old_entry->max_protection == VM_PROT_NONE) ? entry_size : 0); break; } @@ -4609,6 +4699,7 @@ vm_map_t vm_map_fork(vm_map_t old_map) vm_map_copy_insert(new_map, last, copy); new_size += entry_size; + new_size_none += ((old_entry->max_protection == VM_PROT_NONE) ? entry_size : 0); /* * Pick up the traversal at the end of @@ -4630,6 +4721,7 @@ 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_unlock(old_map); return(new_map); diff --git a/vm/vm_map.h b/vm/vm_map.h index 900f1218..722a50d8 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 map_size_cur_limit; /* current limit on virtual memory size */ + vm_size_t map_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) @@ -582,4 +587,12 @@ void _vm_map_clip_end( vm_offset_t end, boolean_t link_gap); +/* + * This funciton is called to inherit the virtual memory limits + * from one vm_map_t to another. + */ +void vm_map_copy_limits( + vm_map_t src, + vm_map_t dst); + #endif /* _VM_VM_MAP_H_ */ -- 2.45.2