bug-hurd
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]