[PATCH] Cygwin: mmap: allow remapping part of an existing anonymous, mapping

Ken Brown kbrown@cornell.edu
Tue Jan 7 21:09:46 GMT 2025


Hi Corinna,

On 1/7/2025 3:18 PM, Corinna Vinschen wrote:
> On Jan  2 16:42, Ken Brown wrote:
>> diff --git a/winsup/cygwin/mm/mmap.cc b/winsup/cygwin/mm/mmap.cc
>> index fc126a87072a..0224779458ef 100644
>> --- a/winsup/cygwin/mm/mmap.cc
>> +++ b/winsup/cygwin/mm/mmap.cc
>> @@ -494,18 +494,24 @@ mmap_record::map_pages (caddr_t addr, SIZE_T len, int new_prot)
>>     off_t off = addr - get_address ();
>>     off /= wincap.page_size ();
>>     len = PAGE_CNT (len);
>> -  /* First check if the area is unused right now. */
>> -  for (SIZE_T l = 0; l < len; ++l)
>> -    if (MAP_ISSET (off + l))
>> -      {
>> -	set_errno (EINVAL);
>> -	return false;
>> -      }
>> -  if (!noreserve ()
>> -      && !VirtualProtect (get_address () + off * wincap.page_size (),
>> -			  len * wincap.page_size (),
>> -			  ::gen_protect (new_prot, get_flags ()),
>> -			  &old_prot))
>> +  /* VirtualProtect can only be called on committed pages, so it's not
>> +     clear how to change protection in the noreserve case.  In this
>> +     case we will therefore require that the pages are unmapped, in
>> +     order to keep the behavior the same as it was before new_prot was
>> +     introduced.  FIXME: Is there a better way to handle this? */
>> +  if (noreserve ())
>> +    {
>> +      for (SIZE_T l = 0; l < len; ++l)
>> +	if (MAP_ISSET (off + l))
>> +	  {
>> +	    set_errno (EINVAL);
>> +	    return false;
>> +	  }
>> +    }
> 
> I think this is ok for the time being.  But since you're asking...

OK, I'll go ahead and push this, and then...

> When we talked about this last month, I already felt that the
> implementation is lacking.  Actually, it's missing two things which
> would improve MAP_NORESERVE mappings considerably:
> 
> 
> - mmap_record::prot flag, should be an array of protection bits per page
>    (POSIX page i e., 64K, not Windows page).  Right now we only store the
>    first protection set at mmap time for the entire map, rather than the
>    requested protection of every single page.  Consider this scenario:
> 
>      addr = mmap (NULL, 4 * PAGESIZE, PROT_READ | PROT_WRITE,
> 		 MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0L);
> 
>      /* At this point, mmap_record::prot is PROT_READ | PROT_WRITE */
> 
>      mprotect (addr + 2 * PAGESIZE, PAGESIZE, PROT_READ);
> 
>      /* At this point, mmap_record::prot *still* is PROT_READ | PROT_WRITE */
> 
>      /* This write to the memory region will commit page 3... */
>      addr[2 * PAGE_SIZE + 42] = 1;
> 
>      /* ...but should have raised a SIGSEGV because the page is supposedly
>         non-writable! */
> 
>    The reason is obvious: We only store the start protection PROT_READ |
>    PROT_WRITE.  So if we access the page, mmap_is_attached_or_noreserve()
>    calls VirtualAlloc() with the start protection bits.
>    If we store the protection per page, mmap_is_attached_or_noreserve()
>    can call VirtualAlloc with the correct page protection and we receive
>    the deserved SIGSEGV.
> 
> 
> - For mprotect() it doesn't in fact matter if a page is MAP_NORESERVE or
>    not.  It only matters if the page has been written to (that is, it has
>    been committed) or not (that is, it's still reserved).
> 
>    If the page is still only reserved map_pages() can just change the
>    protection bits in mmap_record::prot[page].  If the page was already
>    commited, it can additionally call VirtualProtect() with the new
>    per-page protection.
> 
>    We don't even need to keep track of reserved vs. commited, because
>    VirtualQuery() already does that for us.
I'll try to implement this idea.  Thanks for the suggestion.

Ken


More information about the Cygwin-patches mailing list