# HG changeset patch
# User Bob Owen <bobowencode@gmail.com>
# Date 1549645620 0
#      Fri Feb 08 17:07:00 2019 +0000
# Node ID fb5e7c1090a7ddfde22fd2fb5f8a957ccc61fa64
# Parent  5ef34aa8c8918649528048dd60907862a4355e29
Bug 1515088 Part 2: Set LoaderThreads to 1 in the RTL_USER_PROCESS_PARAMETERS structure on child process start-up. r=aklotz

diff --git a/sandbox/win/src/win_utils.cc b/sandbox/win/src/win_utils.cc
--- a/sandbox/win/src/win_utils.cc
+++ b/sandbox/win/src/win_utils.cc
@@ -188,16 +188,50 @@ void* GetProcessBaseAddress(HANDLE proce
                            &bytes_read) ||
       (sizeof(magic) != bytes_read)) {
     return nullptr;
   }
 
   if (magic[0] != 'M' || magic[1] != 'Z')
     return nullptr;
 
+#if defined(_M_ARM64)
+  // Windows 10 on ARM64 has multi-threaded DLL loading that does not work with
+  // the sandbox. (On x86 this gets disabled by hook detection code that was not
+  // ported to ARM64). This overwrites the LoaderThreads value in the process
+  // parameters part of the PEB, if it is set to the default of 0 (which
+  // actually means it defaults to 4 loading threads). This is an undocumented
+  // field so there is a, probably small, risk that it might change or move in
+  // the future. In order to slightly guard against that we only update if the
+  // value is currently 0.
+  auto processParameters = reinterpret_cast<uint8_t*>(peb.ProcessParameters);
+  const uint32_t loaderThreadsOffset = 0x40c;
+  uint32_t maxLoaderThreads = 0;
+  BOOL memoryRead = ::ReadProcessMemory(
+      process, processParameters + loaderThreadsOffset, &maxLoaderThreads,
+      sizeof(maxLoaderThreads), &bytes_read);
+  if (memoryRead && (sizeof(maxLoaderThreads) == bytes_read) &&
+      (maxLoaderThreads == 0)) {
+    maxLoaderThreads = 1;
+    auto address = processParameters + loaderThreadsOffset;
+    auto length = sizeof(maxLoaderThreads);
+ 
+    // First, remove the protection.
+    DWORD old_protection;
+    if (::VirtualProtectEx(process, address, length, PAGE_READWRITE,
+                           &old_protection)) {
+      ::WriteProcessMemory(process, address, &maxLoaderThreads, length, NULL);
+ 
+      // Attempt to restore the original protection.
+      ::VirtualProtectEx(process, address, length, old_protection,
+                         &old_protection);
+    }
+  }
+#endif
+ 
   return base_address;
 }
 
 bool ContainsNulCharacter(std::wstring_view str) {
   wchar_t nul = '\0';
   return str.find_first_of(nul) != std::wstring::npos;
 }
 
