SharedMemoryPlatform_windows.cpp (8413B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* This source code was derived from Chromium code, and as such is also subject 8 * to the [Chromium license](ipc/chromium/src/LICENSE). */ 9 10 #include "SharedMemoryPlatform.h" 11 12 #include <windows.h> 13 14 #include "nsDebug.h" 15 #ifdef MOZ_MEMORY 16 # include "mozmemory_stall.h" 17 #endif 18 19 namespace { 20 // NtQuerySection is an internal (but believed to be stable) API and the 21 // structures it uses are defined in nt_internals.h. 22 // So we have to define them ourselves. 23 typedef enum _SECTION_INFORMATION_CLASS { 24 SectionBasicInformation, 25 } SECTION_INFORMATION_CLASS; 26 27 typedef struct _SECTION_BASIC_INFORMATION { 28 PVOID BaseAddress; 29 ULONG Attributes; 30 LARGE_INTEGER Size; 31 } SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; 32 33 typedef ULONG(__stdcall* NtQuerySectionType)( 34 HANDLE SectionHandle, SECTION_INFORMATION_CLASS SectionInformationClass, 35 PVOID SectionInformation, ULONG SectionInformationLength, 36 PULONG ResultLength); 37 38 // Checks if the section object is safe to map. At the moment this just means 39 // it's not an image section. 40 bool IsSectionSafeToMap(HANDLE aHandle) { 41 static NtQuerySectionType nt_query_section_func = 42 reinterpret_cast<NtQuerySectionType>( 43 ::GetProcAddress(::GetModuleHandle(L"ntdll.dll"), "NtQuerySection")); 44 MOZ_DIAGNOSTIC_ASSERT(nt_query_section_func, 45 "NtQuerySection function not found in ntdll.dll"); 46 47 // The handle must have SECTION_QUERY access for this to succeed. 48 SECTION_BASIC_INFORMATION basic_information = {}; 49 ULONG status = nt_query_section_func(aHandle, SectionBasicInformation, 50 &basic_information, 51 sizeof(basic_information), nullptr); 52 if (status) { 53 return false; 54 } 55 56 return (basic_information.Attributes & SEC_IMAGE) != SEC_IMAGE; 57 } 58 59 // Wrapper around CreateFileMappingW for pagefile-backed regions. When out of 60 // memory, may attempt to stall and retry rather than returning immediately, in 61 // hopes that the page file is about to be expanded by Windows. (bug 1822383, 62 // bug 1716727) 63 // 64 // This method is largely a copy of the MozVirtualAlloc method from 65 // mozjemalloc.cpp, which implements this strategy for VirtualAlloc calls, 66 // except re-purposed to handle CreateFileMapping. 67 HANDLE MozCreateFileMappingW(LPSECURITY_ATTRIBUTES lpFileMappingAttributes, 68 DWORD flProtect, DWORD dwMaximumSizeHigh, 69 DWORD dwMaximumSizeLow, LPCWSTR lpName) { 70 #ifdef MOZ_MEMORY 71 constexpr auto IsOOMError = [] { 72 return ::GetLastError() == ERROR_COMMITMENT_LIMIT; 73 }; 74 75 { 76 HANDLE handle = ::CreateFileMappingW( 77 INVALID_HANDLE_VALUE, lpFileMappingAttributes, flProtect, 78 dwMaximumSizeHigh, dwMaximumSizeLow, lpName); 79 if (MOZ_LIKELY(handle)) { 80 MOZ_DIAGNOSTIC_ASSERT(handle != INVALID_HANDLE_VALUE, 81 "::CreateFileMapping should return NULL, not " 82 "INVALID_HANDLE_VALUE, on failure"); 83 return handle; 84 } 85 86 // We can't do anything for errors other than OOM. 87 if (!IsOOMError()) { 88 return nullptr; 89 } 90 } 91 92 // Retry as many times as desired (possibly zero). 93 const mozilla::StallSpecs stallSpecs = mozilla::GetAllocatorStallSpecs(); 94 95 const auto ret = 96 stallSpecs.StallAndRetry(&::Sleep, [&]() -> std::optional<HANDLE> { 97 HANDLE handle = ::CreateFileMappingW( 98 INVALID_HANDLE_VALUE, lpFileMappingAttributes, flProtect, 99 dwMaximumSizeHigh, dwMaximumSizeLow, lpName); 100 101 if (handle) { 102 MOZ_DIAGNOSTIC_ASSERT(handle != INVALID_HANDLE_VALUE, 103 "::CreateFileMapping should return NULL, not " 104 "INVALID_HANDLE_VALUE, on failure"); 105 return handle; 106 } 107 108 // Failure for some reason other than OOM. 109 if (!IsOOMError()) { 110 return nullptr; 111 } 112 113 return std::nullopt; 114 }); 115 116 return ret.value_or(nullptr); 117 #else 118 return ::CreateFileMappingW(INVALID_HANDLE_VALUE, lpFileMappingAttributes, 119 flProtect, dwMaximumSizeHigh, dwMaximumSizeLow, 120 lpName); 121 #endif 122 } 123 124 } // namespace 125 126 namespace mozilla::ipc::shared_memory { 127 128 static Maybe<PlatformHandle> CreateImpl(size_t aSize, bool aFreezable) { 129 // If the shared memory object has no DACL, any process can 130 // duplicate its handles with any access rights; e.g., re-add write 131 // access to a read-only handle. To prevent that, we give it an 132 // empty DACL, so that no process can do that. 133 SECURITY_ATTRIBUTES sa, *psa = nullptr; 134 SECURITY_DESCRIPTOR sd; 135 ACL dacl; 136 137 if (aFreezable) { 138 psa = &sa; 139 sa.nLength = sizeof(sa); 140 sa.lpSecurityDescriptor = &sd; 141 sa.bInheritHandle = FALSE; 142 143 if (NS_WARN_IF(!::InitializeAcl(&dacl, sizeof(dacl), ACL_REVISION)) || 144 NS_WARN_IF(!::InitializeSecurityDescriptor( 145 &sd, SECURITY_DESCRIPTOR_REVISION)) || 146 NS_WARN_IF(!::SetSecurityDescriptorDacl(&sd, TRUE, &dacl, FALSE))) { 147 return Nothing(); 148 } 149 } 150 151 auto handle = MozCreateFileMappingW(psa, PAGE_READWRITE, 0, 152 static_cast<DWORD>(aSize), nullptr); 153 if (!handle) { 154 return Nothing(); 155 } else { 156 return Some(handle); 157 } 158 } 159 160 bool Platform::Create(MutableHandle& aHandle, size_t aSize) { 161 if (auto ph = CreateImpl(aSize, false)) { 162 aHandle.mHandle = std::move(*ph); 163 aHandle.SetSize(aSize); 164 return true; 165 } 166 return false; 167 } 168 169 bool Platform::CreateFreezable(FreezableHandle& aHandle, size_t aSize) { 170 if (auto ph = CreateImpl(aSize, true)) { 171 aHandle.mHandle = std::move(*ph); 172 aHandle.SetSize(aSize); 173 return true; 174 } 175 return false; 176 } 177 178 PlatformHandle Platform::CloneHandle(const PlatformHandle& aHandle) { 179 HANDLE h = INVALID_HANDLE_VALUE; 180 if (::DuplicateHandle(::GetCurrentProcess(), aHandle.get(), 181 ::GetCurrentProcess(), &h, 0, false, 182 DUPLICATE_SAME_ACCESS)) { 183 return PlatformHandle(h); 184 } 185 NS_WARNING("DuplicateHandle Failed!"); 186 return nullptr; 187 } 188 189 bool Platform::Freeze(FreezableHandle& aHandle) { 190 HANDLE ro_handle; 191 if (!::DuplicateHandle(::GetCurrentProcess(), aHandle.mHandle.get(), 192 ::GetCurrentProcess(), &ro_handle, 193 GENERIC_READ | FILE_MAP_READ, false, 0)) { 194 return false; 195 } 196 197 aHandle.mHandle.reset(ro_handle); 198 return true; 199 } 200 201 Maybe<void*> Platform::Map(const HandleBase& aHandle, uint64_t aOffset, 202 size_t aSize, void* aFixedAddress, bool aReadOnly) { 203 DWORD fileOffsetHigh = (aOffset >> 32) & 0xffffffff; 204 DWORD fileOffsetLow = aOffset & 0xffffffff; 205 void* mem = ::MapViewOfFileEx( 206 aHandle.mHandle.get(), 207 aReadOnly ? FILE_MAP_READ : FILE_MAP_READ | FILE_MAP_WRITE, 208 fileOffsetHigh, fileOffsetLow, aSize, aFixedAddress); 209 if (mem) { 210 MOZ_ASSERT(!aFixedAddress || mem == aFixedAddress, 211 "MapViewOfFileEx returned an expected address"); 212 return Some(mem); 213 } 214 return Nothing(); 215 } 216 217 void Platform::Unmap(void* aMemory, size_t aSize) { 218 ::UnmapViewOfFile(aMemory); 219 } 220 221 bool Platform::Protect(char* aAddr, size_t aSize, Access aAccess) { 222 DWORD flags; 223 if ((aAccess & AccessReadWrite) == AccessReadWrite) 224 flags = PAGE_READWRITE; 225 else if (aAccess & AccessRead) 226 flags = PAGE_READONLY; 227 else 228 flags = PAGE_NOACCESS; 229 230 DWORD oldflags; 231 return ::VirtualProtect(aAddr, aSize, flags, &oldflags); 232 } 233 234 void* Platform::FindFreeAddressSpace(size_t aSize) { 235 void* memory = ::VirtualAlloc(NULL, aSize, MEM_RESERVE, PAGE_NOACCESS); 236 if (memory) { 237 ::VirtualFree(memory, 0, MEM_RELEASE); 238 } 239 return memory; 240 } 241 242 size_t Platform::PageSize() { 243 SYSTEM_INFO si; 244 ::GetSystemInfo(&si); 245 return si.dwPageSize; 246 } 247 248 size_t Platform::AllocationGranularity() { 249 SYSTEM_INFO si; 250 ::GetSystemInfo(&si); 251 return si.dwAllocationGranularity; 252 } 253 254 bool Platform::IsSafeToMap(const PlatformHandle& aHandle) { 255 return IsSectionSafeToMap(aHandle.get()); 256 } 257 258 } // namespace mozilla::ipc::shared_memory