Hello,

dnie...@gmail.com, le lun. 16 juin 2025 23:58:06 +0100, a ecrit:
> diff --git a/tests/test-vm.c b/tests/test-vm.c
> index 4ece792e..8e4ad884 100644
> --- a/tests/test-vm.c
> +++ b/tests/test-vm.c
> @@ -75,11 +75,96 @@ static void test_wire()
>    // TODO check that all memory is actually wired or unwired
>  }
>  
> +void test_vm_limit()
> +{
[...]
> +  /* check that allocations demoted to VM_PROT_NONE no longer counts towards 
> the VM limit */
> +  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");

It would be also useful to check that trying to vm_protect the PROT_NONE
into PROT_ALL fails when that comes above the limit.

> diff --git a/vm/vm_kern.c b/vm/vm_kern.c
> index 51223d98..6b03d014 100644
> --- a/vm/vm_kern.c
> +++ b/vm/vm_kern.c
> @@ -144,6 +144,19 @@ projected_buffer_allocate(
>       u_entry->protection = protection;
>       u_entry->max_protection = protection;
>       u_entry->inheritance = inheritance;
> +
> +     /*
> +      *      vm_map_find_entry allocated an entry of size `size`
> +      *      without knowing its protection.

Mmmm, then better make vm_map_find_entry aware of the protection? We can
add protection and max_protection parameters to vm_map_find_entry and
make it use that instead of hardcoding VM_PROT_DEFAULT and VM_PROT_ALL.

So that we really have all code modifying map->size that gets a
potential map->size_none update (or comment why not), making it more
obvious to further contributors how it's supposed to be managed.

> +      *      For this to work, the VM limit must not be reached
> +      *      in the allocation, even though we are going to free
> +      *      the chunk from being accounted for the current map
> +      *      limit by increasing map->size_none here.
> +      */
> +     if (protection == VM_PROT_NONE)
> +             map->size_none += size;
> +
>       vm_map_unlock(map);
>               *user_p = addr;
>  
> diff --git a/vm/vm_map.c b/vm/vm_map.c
> index 2e243b78..51a8dc79 100644
> --- a/vm/vm_map.c
> +++ b/vm/vm_map.c
> @@ -268,6 +277,49 @@ void vm_map_unlock(struct vm_map *map)
>       lock_write_done(&map->lock);
>  }
>  
> +/*
> + *     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 usable_size = map->size - map->size_none;

"usable" is ambiguous here, it's not obvious whether it's the area that
the task has allocated non-NONE, or the addressing space that is still
available for more allocation. Better call it "really_allocated_size".

> +     vm_size_t new_size = size + usable_size;

Better use usable_size + size to explicit the way augmentation works
here.

> +     /* Check for integer overflow */
> +     if (new_size < size) {
> +             return KERN_INVALID_ARGUMENT;
> +     }
> +
> +     if (new_size > map->size_cur_limit) {
> +#if MACH_KDB
> +             SoftDebugger("attempt to allocate above VM limit");
> +#endif

The debugger is useful for now, but we don't want to put it in master.

> +             return KERN_NO_SPACE;
> +     }
> +
> +     return KERN_SUCCESS;
> +}
> +
>  /*
>   *   vm_map_entry_create:    [ internal use only ]
>   *
> @@ -5168,6 +5272,8 @@ void vm_map_print(db_expr_t addr, boolean_t have_addr, 
> db_expr_t count, const ch
>        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("max_limit=%lu,cur_limit=%lu,size_none=%lu\n",
> +             map->size_max_limit, map->size_cur_limit, map->size_none);

Better integrate size_none=%lu in the line above, as none=%lu.

>        printf("version=%d\n", map->timestamp);
>       indent += 1;
>       for (entry = vm_map_first_entry(map);
> diff --git a/vm/vm_user.c b/vm/vm_user.c
> index 7f706d71..0c9d11c6 100644
> --- a/vm/vm_user.c
> +++ b/vm/vm_user.c
> @@ -808,3 +808,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)
> +{
[...]
> +     vm_map_lock(map);
> +     if (max_limit > map->size_max_limit && ikot_host != IKOT_HOST_PRIV) {
> +             vm_map_unlock(map);
> +             return KERN_INVALID_ARGUMENT;

I'd say KERN_NO_ACCESS?

> +     }
> +
> +     map->size_cur_limit = current_limit;
> +     map->size_max_limit = max_limit;
> +     vm_map_unlock(map);
> +
> +     return KERN_SUCCESS;
> +}

Samuel

Reply via email to