[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[RFC PATCH] Implement per task virtual memory limit
From: |
dnietoc |
Subject: |
[RFC PATCH] Implement per task virtual memory limit |
Date: |
Sun, 22 Dec 2024 19:47:12 -0300 |
From: Diego Nieto Cid <dnietoc@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
- [RFC PATCH] Implement per task virtual memory limit,
dnietoc <=
- Message not available