Applications like LMDB create a file-backed section sized to the current file, then map a view much larger than the section using MEM_RESERVE in the AllocationType of NtMapViewOfSection. This reserves address space for future file growth without requiring the file to be preallocated. Wine rejected these views with STATUS_INVALID_VIEW_SIZE. Reverse engineering ntoskrnl's MiMapViewOfSection shows that Windows gates three checks on MEM_RESERVE: 1. ViewSize + Offset > SectionSize -> STATUS_INVALID_VIEW_SIZE 2. CommitSize > ViewSize -> STATUS_INVALID_PARAMETER 3. Section must have writable page protection (PAGE_READWRITE or PAGE_EXECUTE_READWRITE) -> STATUS_ACCESS_DENIED Implement all three. Store the section's initial page protection in the mapping object so it can be validated at map time. When MEM_RESERVE is specified, only map the file-backed portion of the view and mark those pages committed, then clear the committed state from the excess so that VirtualQuery correctly reports MEM_RESERVE beyond the file extent. Relax the server-side view size check accordingly. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> --- dlls/ntdll/tests/virtual.c | 80 ++++++++++++++++++++++++++++++++++ dlls/ntdll/unix/sync.c | 1 + dlls/ntdll/unix/virtual.c | 38 +++++++++++----- include/wine/server_protocol.h | 8 ++-- server/mapping.c | 16 ++++--- server/protocol.def | 2 + server/request_handlers.h | 16 ++++--- server/request_trace.h | 2 + 8 files changed, 135 insertions(+), 28 deletions(-) diff --git a/dlls/ntdll/tests/virtual.c b/dlls/ntdll/tests/virtual.c index 809c45e..e6c1694 100644 --- a/dlls/ntdll/tests/virtual.c +++ b/dlls/ntdll/tests/virtual.c @@ -1750,6 +1750,86 @@ static void test_NtMapViewOfSection(void) TerminateProcess(process, 0); CloseHandle(process); + + /* test oversized view with MEM_RESERVE */ + file = CreateFileA(testfile, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0); + ok(file != INVALID_HANDLE_VALUE, "CreateFile failed\n"); + SetFilePointer(file, 0x3000, NULL, FILE_BEGIN); + SetEndOfFile(file); + + mapping = CreateFileMappingA(file, NULL, PAGE_READWRITE, 0, 0, NULL); + ok(mapping != 0, "CreateFileMapping failed\n"); + + /* MEM_RESERVE on a read-only section should fail */ + { + HANDLE ro_mapping = CreateFileMappingA(file, NULL, PAGE_READONLY, 0, 0, NULL); + ok(ro_mapping != 0, "CreateFileMapping failed\n"); + ptr = NULL; + size = 0x3000; + offset.QuadPart = 0; + status = NtMapViewOfSection(ro_mapping, NtCurrentProcess(), &ptr, 0, 0, &offset, + &size, 1, MEM_RESERVE, PAGE_READONLY); + ok(status == STATUS_ACCESS_DENIED, + "expected STATUS_ACCESS_DENIED for MEM_RESERVE on read-only section, got %08lx\n", status); + NtClose(ro_mapping); + } + + /* commit_size > view_size should fail without MEM_RESERVE */ + ptr = NULL; + size = 0x1000; + offset.QuadPart = 0; + status = NtMapViewOfSection(mapping, NtCurrentProcess(), &ptr, 0, 0x2000, &offset, + &size, 1, 0, PAGE_READWRITE); + ok(status == STATUS_INVALID_PARAMETER, "expected STATUS_INVALID_PARAMETER, got %08lx\n", status); + + /* commit_size > view_size should succeed with MEM_RESERVE */ + ptr = NULL; + size = 0x1000; + offset.QuadPart = 0; + status = NtMapViewOfSection(mapping, NtCurrentProcess(), &ptr, 0, 0x2000, &offset, + &size, 1, MEM_RESERVE, PAGE_READWRITE); + ok(status == STATUS_SUCCESS, "NtMapViewOfSection with MEM_RESERVE returned %08lx\n", status); + if (status == STATUS_SUCCESS) + NtUnmapViewOfSection(NtCurrentProcess(), ptr); + + /* without MEM_RESERVE, oversized views should fail */ + ptr = NULL; + size = 0x10000; + offset.QuadPart = 0; + status = NtMapViewOfSection(mapping, NtCurrentProcess(), &ptr, 0, 0, &offset, + &size, 1, 0, PAGE_READWRITE); + ok(status == STATUS_INVALID_VIEW_SIZE, "expected STATUS_INVALID_VIEW_SIZE, got %08lx\n", status); + + /* with MEM_RESERVE, oversized views should succeed */ + ptr = NULL; + size = 0x10000; + offset.QuadPart = 0; + status = NtMapViewOfSection(mapping, NtCurrentProcess(), &ptr, 0, 0, &offset, + &size, 1, MEM_RESERVE, PAGE_READWRITE); + ok(status == STATUS_SUCCESS, "NtMapViewOfSection with MEM_RESERVE returned %08lx\n", status); + if (status == STATUS_SUCCESS) + { + MEMORY_BASIC_INFORMATION info; + + /* file-backed pages should be committed */ + result = VirtualQuery(ptr, &info, sizeof(info)); + ok(result == sizeof(info), "VirtualQuery failed\n"); + ok(info.State == MEM_COMMIT, "expected MEM_COMMIT, got %#lx\n", info.State); + ok(info.RegionSize == 0x3000, "expected 0x3000, got %#Ix\n", info.RegionSize); + ok(info.Type == MEM_MAPPED, "expected MEM_MAPPED, got %#lx\n", info.Type); + + /* excess pages should be reserved */ + result = VirtualQuery((char *)ptr + 0x3000, &info, sizeof(info)); + ok(result == sizeof(info), "VirtualQuery failed\n"); + ok(info.State == MEM_RESERVE, "expected MEM_RESERVE, got %#lx\n", info.State); + ok(info.RegionSize == 0xd000, "expected 0xd000, got %#Ix\n", info.RegionSize); + + NtUnmapViewOfSection(NtCurrentProcess(), ptr); + } + + NtClose(mapping); + CloseHandle(file); + DeleteFileA(testfile); } static void test_NtMapViewOfSectionEx(void) diff --git a/dlls/ntdll/unix/sync.c b/dlls/ntdll/unix/sync.c index bc9bbf2..c594beb 100644 --- a/dlls/ntdll/unix/sync.c +++ b/dlls/ntdll/unix/sync.c @@ -3013,6 +3013,7 @@ NTSTATUS WINAPI NtCreateSection( HANDLE *handle, ACCESS_MASK access, const OBJEC req->flags = sec_flags; req->file_handle = wine_server_obj_handle( file ); req->file_access = file_access; + req->protect = protect; req->size = size ? size->QuadPart : 0; wine_server_add_data( req, objattr, len ); ret = wine_server_call( req ); diff --git a/dlls/ntdll/unix/virtual.c b/dlls/ntdll/unix/virtual.c index 9297a5f..2e6c2da 100644 --- a/dlls/ntdll/unix/virtual.c +++ b/dlls/ntdll/unix/virtual.c @@ -3281,7 +3281,8 @@ static void free_pe_mapping_info( struct pe_mapping_info *info ) * get_mapping_info */ static unsigned int get_mapping_info( HANDLE handle, ACCESS_MASK access, unsigned int *sec_flags, - mem_size_t *full_size, struct pe_mapping_info **info_ret ) + unsigned int *sec_protect, mem_size_t *full_size, + struct pe_mapping_info **info_ret ) { struct pe_mapping_info *info; SIZE_T total, size = 2048; @@ -3299,6 +3300,7 @@ static unsigned int get_mapping_info( HANDLE handle, ACCESS_MASK access, unsigne wine_server_set_reply( req, &info->image, size ); status = wine_server_call( req ); *sec_flags = reply->flags; + *sec_protect = reply->protect; *full_size = reply->size; total = reply->total; info->shared_file = wine_server_ptr_handle( reply->shared_file ); @@ -3492,7 +3494,7 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P struct pe_mapping_info *pe_mapping; void *base; int unix_handle = -1, needs_close; - unsigned int vprot, sec_flags; + unsigned int vprot, sec_flags, sec_protect; struct file_view *view; LARGE_INTEGER offset; sigset_t sigset; @@ -3519,7 +3521,7 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P return STATUS_INVALID_PAGE_PROTECTION; } - res = get_mapping_info( handle, access, &sec_flags, &full_size, &pe_mapping ); + res = get_mapping_info( handle, access, &sec_flags, &sec_protect, &full_size, &pe_mapping ); if (res) return res; offset.QuadPart = offset_ptr ? offset_ptr->QuadPart : 0; @@ -3550,7 +3552,8 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P if (*size_ptr) { size = *size_ptr; - if (size > full_size - offset.QuadPart) return STATUS_INVALID_VIEW_SIZE; + if (size > full_size - offset.QuadPart && !(alloc_type & MEM_RESERVE)) + return STATUS_INVALID_VIEW_SIZE; } else { @@ -3563,6 +3566,9 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P } } if (!(size = ROUND_SIZE( 0, size, page_mask ))) return STATUS_INVALID_PARAMETER; /* wrap-around */ + if (commit_size > size && !(alloc_type & MEM_RESERVE)) return STATUS_INVALID_PARAMETER; + if ((alloc_type & MEM_RESERVE) && !(sec_protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE))) + return STATUS_ACCESS_DENIED; get_vprot_flags( protect, &vprot, FALSE ); vprot |= sec_flags; @@ -3576,12 +3582,20 @@ static unsigned int virtual_map_section( HANDLE handle, PVOID *addr_ptr, ULONG_P if (res) goto done; TRACE( "handle=%p size=%lx offset=%s\n", handle, size, wine_dbgstr_longlong(offset.QuadPart) ); - res = map_file_into_view( view, unix_handle, 0, size, offset.QuadPart, vprot, needs_close ); + { + size_t map_size = size; + if (size > full_size - offset.QuadPart) + map_size = ROUND_SIZE( 0, full_size - offset.QuadPart, page_mask ); + res = map_file_into_view( view, unix_handle, 0, map_size, offset.QuadPart, vprot, needs_close ); + if (res == STATUS_SUCCESS) + { + mprotect_range( view->base, map_size, VPROT_COMMITTED, 0 ); + if (map_size < size) + set_page_vprot_bits( (char *)view->base + map_size, size - map_size, 0, VPROT_COMMITTED ); + } + } if (res == STATUS_SUCCESS) { - /* file mappings must always be accessible */ - mprotect_range( view->base, view->size, VPROT_COMMITTED, 0 ); - SERVER_START_REQ( map_view ) { req->mapping = wine_server_obj_handle( handle ); @@ -3785,11 +3799,11 @@ NTSTATUS virtual_map_builtin_module( HANDLE mapping, void **module, SIZE_T *size ULONG_PTR limit_high, WORD machine, BOOL prefer_native, off_t offset ) { mem_size_t full_size; - unsigned int sec_flags; + unsigned int sec_flags, sec_protect; struct pe_mapping_info *pe_mapping; NTSTATUS status; - if ((status = get_mapping_info( mapping, SECTION_MAP_READ, &sec_flags, &full_size, &pe_mapping ))) + if ((status = get_mapping_info( mapping, SECTION_MAP_READ, &sec_flags, &sec_protect, &full_size, &pe_mapping ))) return status; if (!pe_mapping) return STATUS_INVALID_PARAMETER; @@ -3830,10 +3844,10 @@ NTSTATUS virtual_map_module( HANDLE mapping, void **module, SIZE_T *size, SECTIO { unsigned int status; mem_size_t full_size; - unsigned int sec_flags; + unsigned int sec_flags, sec_protect; struct pe_mapping_info *pe_mapping; - if ((status = get_mapping_info( mapping, SECTION_MAP_READ, &sec_flags, &full_size, &pe_mapping ))) + if ((status = get_mapping_info( mapping, SECTION_MAP_READ, &sec_flags, &sec_protect, &full_size, &pe_mapping ))) return status; if (!pe_mapping) return STATUS_INVALID_PARAMETER; diff --git a/include/wine/server_protocol.h b/include/wine/server_protocol.h index 85f636d..62d2f43 100644 --- a/include/wine/server_protocol.h +++ b/include/wine/server_protocol.h @@ -2166,10 +2166,12 @@ struct create_mapping_request unsigned int access; unsigned int flags; unsigned int file_access; + unsigned int protect; + char __pad_28[4]; mem_size_t size; obj_handle_t file_handle; /* VARARG(objattr,object_attributes); */ - char __pad_36[4]; + char __pad_44[4]; }; struct create_mapping_reply { @@ -2209,6 +2211,7 @@ struct get_mapping_info_reply struct reply_header __header; mem_size_t size; unsigned int flags; + unsigned int protect; obj_handle_t shared_file; data_size_t name_len; data_size_t ver_len; @@ -2217,7 +2220,6 @@ struct get_mapping_info_reply /* VARARG(version,version_res,ver_len); */ /* VARARG(name,unicode_str,name_len); */ /* VARARG(exp_name,string); */ - char __pad_36[4]; }; @@ -7096,6 +7098,6 @@ union generic_reply struct d3dkmt_mutex_release_reply d3dkmt_mutex_release_reply; }; -#define SERVER_PROTOCOL_VERSION 931 +#define SERVER_PROTOCOL_VERSION 932 #endif /* __WINE_WINE_SERVER_PROTOCOL_H */ diff --git a/server/mapping.c b/server/mapping.c index f9a80bc..5e2f3a9 100644 --- a/server/mapping.c +++ b/server/mapping.c @@ -161,6 +161,7 @@ struct mapping struct object obj; /* object header */ mem_size_t size; /* mapping size */ unsigned int flags; /* SEC_* flags */ + unsigned int protect; /* initial page protection */ struct fd *fd; /* fd for mapped file */ struct pe_image_info image; /* image info (for PE image mapping) */ struct ranges *committed; /* list of committed ranges in this mapping */ @@ -1117,7 +1118,8 @@ static unsigned int get_mapping_flags( obj_handle_t handle, unsigned int flags ) static struct mapping *create_mapping( struct object *root, const struct unicode_str *name, unsigned int attr, mem_size_t size, unsigned int flags, - obj_handle_t handle, unsigned int file_access, + unsigned int protect, obj_handle_t handle, + unsigned int file_access, const struct security_descriptor *sd ) { struct mapping *mapping; @@ -1132,6 +1134,7 @@ static struct mapping *create_mapping( struct object *root, const struct unicode return mapping; /* Nothing else to do */ mapping->size = size; + mapping->protect = protect; mapping->fd = NULL; mapping->shared = NULL; mapping->committed = NULL; @@ -1428,7 +1431,7 @@ struct mapping *create_session_mapping( struct object *root, const struct unicod size_t size = max( sizeof(*shared_session) + sizeof(object_shm_t) * 512, 0x10000 ); size = round_size( size, host_page_mask ); - return create_mapping( root, name, attr, size, SEC_COMMIT, 0, access, sd ); + return create_mapping( root, name, attr, size, SEC_COMMIT, PAGE_READWRITE, 0, access, sd ); } void set_session_mapping( struct mapping *mapping ) @@ -1581,7 +1584,8 @@ struct object *create_user_data_mapping( struct object *root, const struct unico struct mapping *mapping; if (!(mapping = create_mapping( root, name, attr, sizeof(KUSER_SHARED_DATA), - SEC_COMMIT, 0, FILE_READ_DATA | FILE_WRITE_DATA, sd ))) return NULL; + SEC_COMMIT, PAGE_READWRITE, 0, + FILE_READ_DATA | FILE_WRITE_DATA, sd ))) return NULL; ptr = mmap( NULL, mapping->size, PROT_WRITE, MAP_SHARED, get_unix_fd( mapping->fd ), 0 ); if (ptr != MAP_FAILED) user_shared_data = ptr; return &mapping->obj; @@ -1599,7 +1603,7 @@ DECL_HANDLER(create_mapping) if (!objattr) return; if ((mapping = create_mapping( root, &name, objattr->attributes, req->size, req->flags, - req->file_handle, req->file_access, sd ))) + req->protect, req->file_handle, req->file_access, sd ))) { if (get_error() == STATUS_OBJECT_NAME_EXISTS) reply->handle = alloc_handle( current->process, &mapping->obj, req->access, objattr->attributes ); @@ -1630,6 +1634,7 @@ DECL_HANDLER(get_mapping_info) reply->size = mapping->size; reply->flags = mapping->flags; + reply->protect = mapping->protect; if (mapping->flags & SEC_IMAGE) { @@ -1705,8 +1710,7 @@ DECL_HANDLER(map_view) if ((mapping->flags & SEC_IMAGE) || req->start >= mapping->size || - req->start + req->size < req->start || - req->start + req->size > round_size( mapping->size, page_mask )) + req->start + req->size < req->start) { set_error( STATUS_INVALID_PARAMETER ); goto done; diff --git a/server/protocol.def b/server/protocol.def index 5bca381..bc8af70 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1794,6 +1794,7 @@ enum server_fd_type unsigned int access; /* wanted access rights */ unsigned int flags; /* SEC_* flags */ unsigned int file_access; /* file access rights */ + unsigned int protect; /* initial page protection */ mem_size_t size; /* mapping size */ obj_handle_t file_handle; /* file handle */ VARARG(objattr,object_attributes); /* object attributes */ @@ -1820,6 +1821,7 @@ enum server_fd_type @REPLY mem_size_t size; /* mapping size */ unsigned int flags; /* SEC_* flags */ + unsigned int protect; /* initial page protection */ obj_handle_t shared_file; /* shared mapping file handle */ data_size_t name_len; /* length of file name */ data_size_t ver_len; /* length of version resource */ diff --git a/server/request_handlers.h b/server/request_handlers.h index 3c51879..b5f7b19 100644 --- a/server/request_handlers.h +++ b/server/request_handlers.h @@ -1075,9 +1075,10 @@ C_ASSERT( sizeof(struct read_change_reply) == 8 ); C_ASSERT( offsetof(struct create_mapping_request, access) == 12 ); C_ASSERT( offsetof(struct create_mapping_request, flags) == 16 ); C_ASSERT( offsetof(struct create_mapping_request, file_access) == 20 ); -C_ASSERT( offsetof(struct create_mapping_request, size) == 24 ); -C_ASSERT( offsetof(struct create_mapping_request, file_handle) == 32 ); -C_ASSERT( sizeof(struct create_mapping_request) == 40 ); +C_ASSERT( offsetof(struct create_mapping_request, protect) == 24 ); +C_ASSERT( offsetof(struct create_mapping_request, size) == 32 ); +C_ASSERT( offsetof(struct create_mapping_request, file_handle) == 40 ); +C_ASSERT( sizeof(struct create_mapping_request) == 48 ); C_ASSERT( offsetof(struct create_mapping_reply, handle) == 8 ); C_ASSERT( sizeof(struct create_mapping_reply) == 16 ); C_ASSERT( offsetof(struct open_mapping_request, access) == 12 ); @@ -1091,10 +1092,11 @@ C_ASSERT( offsetof(struct get_mapping_info_request, access) == 16 ); C_ASSERT( sizeof(struct get_mapping_info_request) == 24 ); C_ASSERT( offsetof(struct get_mapping_info_reply, size) == 8 ); C_ASSERT( offsetof(struct get_mapping_info_reply, flags) == 16 ); -C_ASSERT( offsetof(struct get_mapping_info_reply, shared_file) == 20 ); -C_ASSERT( offsetof(struct get_mapping_info_reply, name_len) == 24 ); -C_ASSERT( offsetof(struct get_mapping_info_reply, ver_len) == 28 ); -C_ASSERT( offsetof(struct get_mapping_info_reply, total) == 32 ); +C_ASSERT( offsetof(struct get_mapping_info_reply, protect) == 20 ); +C_ASSERT( offsetof(struct get_mapping_info_reply, shared_file) == 24 ); +C_ASSERT( offsetof(struct get_mapping_info_reply, name_len) == 28 ); +C_ASSERT( offsetof(struct get_mapping_info_reply, ver_len) == 32 ); +C_ASSERT( offsetof(struct get_mapping_info_reply, total) == 36 ); C_ASSERT( sizeof(struct get_mapping_info_reply) == 40 ); C_ASSERT( offsetof(struct get_image_map_address_request, handle) == 12 ); C_ASSERT( sizeof(struct get_image_map_address_request) == 16 ); diff --git a/server/request_trace.h b/server/request_trace.h index d31263c..50f05be 100644 --- a/server/request_trace.h +++ b/server/request_trace.h @@ -850,6 +850,7 @@ static void dump_create_mapping_request( const struct create_mapping_request *re fprintf( stderr, " access=%08x", req->access ); fprintf( stderr, ", flags=%08x", req->flags ); fprintf( stderr, ", file_access=%08x", req->file_access ); + fprintf( stderr, ", protect=%08x", req->protect ); dump_uint64( ", size=", &req->size ); fprintf( stderr, ", file_handle=%04x", req->file_handle ); dump_varargs_object_attributes( ", objattr=", cur_size ); @@ -883,6 +884,7 @@ static void dump_get_mapping_info_reply( const struct get_mapping_info_reply *re { dump_uint64( " size=", &req->size ); fprintf( stderr, ", flags=%08x", req->flags ); + fprintf( stderr, ", protect=%08x", req->protect ); fprintf( stderr, ", shared_file=%04x", req->shared_file ); fprintf( stderr, ", name_len=%u", req->name_len ); fprintf( stderr, ", ver_len=%u", req->ver_len ); -- 2.53.0