On Tue, Dec 24, 2024 at 12:44:49PM +0300, Sergey Bugaev wrote:
> > +  ASSERT(err == KERN_SUCCESS, "cannot set VM limits");
> 
> There's ASSERT_RET, which also stringifies the error code.
> 

Good, that's much better.

> > +  /* check limits are actually saved */
> > +  err = vm_get_size_limit(mach_task_self(), &cur, &max);
> > +  ASSERT(err == KERN_SUCCESS, "getting the VM limit 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 = vm_set_size_limit(mach_host_self(), mach_task_self(), M_128M, 
> > M_512M * 2);
> > +  ASSERT(err == KERN_INVALID_HOST, "raising VM hard limit shall fail");
> 
> Well, uh. Looking at it like this, I wonder if this should return
> KERN_INVALID_ARGUMENT or something in this case, not
> KERN_INVALID_HOST. "Invalid host" makes sense if the way you think of
> it is "I want to increase the size limit, would this host port
> suffice"; but when written out like this it reads the other way
> around: here's a host port, I'm attempting to set the limit ("no
> sorry, increasing the limit is not allowed" => KERN_INVALID_ARGUMENT).
>

I've been expanding the comment in gnumach.defs:

    /*
     *      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 name (as returned by mach_host_self()) can
     *      be provided.
     *
     *      Returns:
     *        - KERN_SUCESS when the size limit has been set
     *        - KERN_INVALID_ARGUMENT in the following cases:
     *            * current_limit > max_limit
     *            * map is NULL
     *            * attemt to increase max limi without providing
     *              the privileged control host port.
     */

Should I still return KERN_INVALID_HOST in case the "host port dance" fails?
Or is it ok to map every error to KERN_INVALID_ARGUMENT?

> > +  vm_deallocate(mach_task_self(), mem, (128l * 1024l));
> 
> Could check for the error here as well :)
> 
> > +  err = vm_set_size_limit(host_priv(), mach_task_self(), M_128M, M_512M * 
> > 2);
> > +  ASSERT(err == KERN_SUCCESS, "privileged tasks shall be allowed to 
> > increase the max limit");
> 
> And perhaps check that you can alloc that bigger chunk now?
> 

Ok

> > diff --git a/vm/vm_map.c b/vm/vm_map.c
> > index 03d22ea1..eded31a0 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->size_cur_limit = vm_page_mem_size() / 2;
> > +       map->size_max_limit = vm_page_mem_size() / 2;
> 
> Perhaps also skip this for kernel maps?
> 

Like this?

    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);
> > @@ -268,6 +272,49 @@ void vm_map_unlock(struct vm_map *map)
> >         lock_write_done(&map->lock);
> >  }
> > @@ -789,6 +836,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_limit(map, size, "vm_map_find_entry")) != 
> > KERN_SUCCESS)
> > +               return err;
> 
> Hm, so the map is said to be locked, good. But: we don't have
> max_protection here (?), so we cannot skip the check for max_prot ==
> VM_PROT_NONE. Oh well.
>

This is called by projected buffers (which seems to be kernel memory mapped
into user maps and are locate in vm/vm_kern.c).

There it says:

     *      The user is precluded from manipulating the VM entry of this buffer
     *      (i.e. changing protection, inheritance or machine attributes).

So hopefully it doesn't make sense to make the protection VM_PROT_NONE.

Also, I couldn't find any callers to projected_buffer_allocate :/

> > @@ -1160,6 +1221,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);
> 
> I'd rather have written this as just
> 
> if (max_protection == VM_PROT_NONE)
>         map->size_none += size;
>

I changed to your suggestion. Better not to try to "smart" unneedlessly.

> > @@ -2882,6 +2945,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;
> > +       }
> 
> Ok. Perhaps add a comment mentioning that the new entries are
> protected VM_PROT_DEFAULT / VM_PROT_ALL, hence no need to adjust
> map->size_none.
>

Added the comment where `dst_map->size` is incremented.

> > +
> > +/*
> > + *     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)
> > +{
> 
> Missing the check for map == VM_MAP_NULL.
>

Hmm, I've got an error from mach_msg when the receiver is NULL.
But I'll add it just in case.

> > +       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;
> > +}
> 
> And remember, you still have to handle the cases of:
> - deallocating a VM_PROT_NONE mapping,

Isn't this hunk enough?

    @@ -2042,6 +2104,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);
     }

> - downgrading an existing mapping to VM_PROT_NONE.

Hmm, maybe I should start tracking the protection instead of the max_protection?
Or do you mean calls to vm_map_protect with set_max == TRUE?


Reply via email to