This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author eryksun
Recipients eryksun, paul.moore, steve.dower, tim.golden, zach.ware
Date 2020-06-08.23:08:02
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1591657683.26.0.992231826575.issue40915@roundup.psfhosted.org>
In-reply-to
Content
In Windows, mmap.resize() unmaps the view of the section [1], closes the section handle, resizes the file (without error checking), creates a new section (without checking for ERROR_ALREADY_EXISTS), and maps a new view. This code has several problems.

Case 1

If it tries to shrink a file that has existing section references, SetEndOfFile fails with ERROR_USER_MAPPED_FILE. Since the error isn't checked, the code proceeds to silently map a smaller view without changing the file size, which contradicts the documented behavior. For example:

    >>> f = open('C:/Temp/spam.txt', 'wb+')
    >>> f.truncate(8192)
    8192
    >>> m1 = mmap.mmap(f.fileno(), 0)
    >>> m2 = mmap.mmap(f.fileno(), 0)
    >>> m1.resize(4096)
    >>> os.fstat(f.fileno()).st_size
    8192

In this case, it should set dwErrCode = ERROR_USER_MAPPED_FILE; set new_size = self->size; remap the file; and fail the resize() call. The mmap object should remain open.

Case 2

Growing the file may succeed, but if it's a named section (i.e. self->tagname is set) with multiple handle references, then CreateFileMapping just reopens the existing section by name. Or there could be a race condition with another section that gets created with the same name in the window between closing the current section, resizing the file, and creating a new section. Generally mapping a new view will fail in this case. In particular, if the section isn't large enough, then the NtMapViewOfSection system call fails with STATUS_INVALID_VIEW_SIZE, which WinAPI MapViewOfFile translates to ERROR_ACCESS_DENIED. For example:

    >>> f = open('C:/Temp/spam.txt', 'wb+')
    >>> f.truncate(8192)
    8192
    >>> m1 = mmap.mmap(f.fileno(), 0, 'spam')
    >>> m2 = mmap.mmap(-1, 8192, 'spam')
    >>> m1.resize(16384)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    PermissionError: [WinError 5] Access is denied
    >>> m1[0]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: mmap closed or invalid
   >>> os.fstat(f.fileno()).st_size
    16384

If CreateFileMapping succeeds with the last error set to ERROR_ALREADY_EXISTS, then it either opened the previous section or there was a race condition in which another section was created with the same name. This case should be handled by closing the section and failing the call. It should not proceed to MapViewOfFile, which may 'succeed' unreliably.

Additionally, there could be a LBYL check at the outset to avoid having to close the section in the case of a named section that's opened multiple times. If self->tagname is set, get the handle count of the section via NtQueryObject: ObjectBasicInformation [2]. If the handle count is not exactly 1, fail the resize() call, but don't close the mmap object. 

Case 3

resize() is broken for sections that are backed by the system paging file. For example:

    >>> m = mmap.mmap(-1, 4096)
    >>> m.resize(8192)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    OSError: [WinError 87] The parameter is incorrect
    >>> m[0]
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: mmap closed or invalid

Since the handle value is INVALID_HANDLE_VALUE in this case, the calls to resize the 'file' via SetFilePointer and SetEndOfFile fail with ERROR_INVALID_HANDLE (due to an object type mismatch because handle value -1 is the current process). There's no error checking, so it proceeds to call CreateFileMapping with dwMaximumSizeHigh and dwMaximumSizeLow as 0, which fails immediately as an invalid parameter. Creating a section that's backed by the system paging file requires a non-zero size.

The view of a section can shrink, but the section itself cannot grow. Generally resize() in this case would have to create a copy as follows: close the section; create another section with the new size; map the new view; copy the contents of the old view to the new view; and unmap the old view. Note that creating a new named section may open the previous section, or open an unrelated section if there's a race condition, so ERROR_ALREADY_EXISTS has to be handled.

[1]: "section" is the native system name of a "file mapping".
[2]: https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryobject
History
Date User Action Args
2020-06-08 23:08:03eryksunsetrecipients: + eryksun, paul.moore, tim.golden, zach.ware, steve.dower
2020-06-08 23:08:03eryksunsetmessageid: <1591657683.26.0.992231826575.issue40915@roundup.psfhosted.org>
2020-06-08 23:08:03eryksunlinkissue40915 messages
2020-06-08 23:08:02eryksuncreate