Make CoreCLR work properly under PaX's RANDMMAP (#11382)
authorKoundinya Veluri <kouvel@microsoft.com>
Mon, 8 May 2017 19:16:21 +0000 (12:16 -0700)
committerGitHub <noreply@github.com>
Mon, 8 May 2017 19:16:21 +0000 (12:16 -0700)
Make CoreCLR work properly under PaX's RANDMMAP

Issues:
- The ExecutableMemoryAllocator is used to attempt to map native images into a memory range near libcoreclr so that its helper table can use short relative addresses for jumps to libcoreclr
  - RANDMMAP typically prevents mmap calls with a specific address from reserving memory at the requested address, so the executable memory allocator fails to reserve any memory. When Server GC is enabled, the large GC heap can exacerbate the issue by taking address space near libcoreclr.
- Native images are loaded far from libcoreclr, and now jump stub space needs to be allocated near the native image, but RANDMMAP typically prevents this too
- NGenReserveForJumpStubs is intended to reserve some memory near mapped native images for jump stubs, but that reservation is done with a separate mmap call in the same way as above and RANDMMAP typically prevents this too
- The JIT needs to allocate memory for code that may need to jump/call to a native image or libcoreclr, which may require jump stubs near the code that cannot be allocated. CodeHeapReserveForJumpStubs reserves space in code heap blocks without using a separate call to mmap, so this works, but without this environment variable by default there is still a good chance of failing.
- See https://github.com/dotnet/coreclr/blob/56d550d4f8aec2dd40b72a182205d0a2463a1bc9/Documentation/design-docs/jump-stubs.md for more details

Fixes #8480
- It would be ideal to fix all of the above properly, such that there would never be a need to attempt reserving memory within a certain range. Since we're running out of time for 2.0, I figured the following simpler, temporary solution that should cover most of the practical cases, may be appropriate for 2.0.
- Extended ExecutableMemoryAllocator to reserve address space even when it cannot do so near libcoreclr
- Had ClrVirtualAllocWithinRange use the executable memory allocator to reserve memory for jump stubs when the requested range is satisfied
- This covers a maximum of ~2 GB of executable code and should cover most of the practical cases. Once this space is exhausted, under RANDMMAP, native images loaded later will fail, and for jitted code the environment variable above can be used.

src/dlls/mscordac/mscordac_unixexports.src
src/pal/inc/pal.h
src/pal/src/include/pal/virtual.h
src/pal/src/map/map.cpp
src/pal/src/map/virtual.cpp
src/utilcode/util.cpp

index b0c3b04..9881def 100644 (file)
@@ -39,6 +39,7 @@ PAL_fprintf
 PAL__wcstoui64
 PAL_wcstoul
 PAL_iswprint
+PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange
 PAL_wcslen
 PAL_wcsncmp
 PAL_wcsrchr
index 35c9a51..0f470d9 100644 (file)
@@ -2853,6 +2853,14 @@ PAL_GetSymbolModuleBase(void *symbol);
 PALIMPORT
 LPVOID
 PALAPI
+PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(
+    IN LPCVOID lpBeginAddress,
+    IN LPCVOID lpEndAddress,
+    IN SIZE_T dwSize);
+
+PALIMPORT
+LPVOID
+PALAPI
 VirtualAlloc(
          IN LPVOID lpAddress,
          IN SIZE_T dwSize,
index 31d225f..36eaf81 100644 (file)
@@ -60,7 +60,7 @@ enum VIRTUAL_CONSTANTS
     
     VIRTUAL_PAGE_SIZE       = 0x1000,
     VIRTUAL_PAGE_MASK       = VIRTUAL_PAGE_SIZE - 1,
-    BOUNDARY_64K    = 0xffff
+    VIRTUAL_64KB            = 0x10000
 };
 
 /*++
@@ -130,11 +130,22 @@ public:
         AllocateMemory
 
         This function attempts to allocate the requested amount of memory from its reserved virtual
-        address space. The function will return NULL if the allocation request cannot
+        address space. The function will return null if the allocation request cannot
         be satisfied by the memory that is currently available in the allocator.
     --*/
     void* AllocateMemory(SIZE_T allocationSize);
 
+    /*++
+    Function:
+        AllocateMemory
+
+        This function attempts to allocate the requested amount of memory from its reserved virtual
+        address space, if memory is available within the specified range. The function will return
+        null if the allocation request cannot satisfied by the memory that is currently available in
+        the allocator.
+    --*/
+    void *AllocateMemoryWithinRange(const void *beginAddress, const void *endAddress, SIZE_T allocationSize);
+
 private:
     /*++
     Function:
@@ -160,12 +171,13 @@ private:
     // that can be used to calculate an approximate location of the memory that
     // is in 2GB range from the coreclr library. In addition, having precise size of libcoreclr
     // is not necessary for the calculations.
-    const int32_t CoreClrLibrarySize = 100 * 1024 * 1024;
+    static const int32_t CoreClrLibrarySize = 100 * 1024 * 1024;
 
     // This constant represent the max size of the virtual memory that this allocator
     // will try to reserve during initialization. We want all JIT-ed code and the
     // entire libcoreclr to be located in a 2GB range.
-    const int32_t MaxExecutableMemorySize = 0x7FFF0000 - CoreClrLibrarySize;
+    static const int32_t MaxExecutableMemorySize = 0x7FFF0000;
+    static const int32_t MaxExecutableMemorySizeNearCoreClr = MaxExecutableMemorySize - CoreClrLibrarySize;
 
     // Start address of the reserved virtual address space
     void* m_startAddress;
index 5fdb6fd..f3172c3 100644 (file)
@@ -2445,8 +2445,10 @@ void * MAPMapPEFile(HANDLE hFile)
 #else // defined(_AMD64_)    
     // First try to reserve virtual memory using ExecutableAllcator. This allows all PE images to be
     // near each other and close to the coreclr library which also allows the runtime to generate
-    // more efficient code (by avoiding usage of jump stubs).
-    loadedBase = ReserveMemoryFromExecutableAllocator(pThread, ALIGN_UP(virtualSize, GetVirtualPageSize()));
+    // more efficient code (by avoiding usage of jump stubs). Alignment to a 64 KB granularity should
+    // not be necessary (alignment to page size should be sufficient), but see
+    // ExecutableMemoryAllocator::AllocateMemory() for the reason why it is done.
+    loadedBase = ReserveMemoryFromExecutableAllocator(pThread, ALIGN_UP(virtualSize, VIRTUAL_64KB));
     if (loadedBase == NULL)
     {
         // MAC64 requires we pass MAP_SHARED (or MAP_PRIVATE) flags - otherwise, the call is failed.
index 7e00843..41bd37c 100644 (file)
@@ -18,15 +18,19 @@ Abstract:
 
 --*/
 
+#include "pal/dbgmsg.h"
+
+SET_DEFAULT_DEBUG_CHANNEL(VIRTUAL); // some headers have code with asserts, so do this first
+
 #include "pal/thread.hpp"
 #include "pal/cs.hpp"
 #include "pal/malloc.hpp"
 #include "pal/file.hpp"
 #include "pal/seh.hpp"
-#include "pal/dbgmsg.h"
 #include "pal/virtual.h"
 #include "pal/map.h"
 #include "pal/init.h"
+#include "pal/utils.h"
 #include "common.h"
 
 #include <sys/types.h>
@@ -43,8 +47,6 @@ Abstract:
 
 using namespace CorUnix;
 
-SET_DEFAULT_DEBUG_CHANNEL(VIRTUAL);
-
 CRITICAL_SECTION virtual_critsec;
 
 // The first node in our list of allocated blocks.
@@ -93,6 +95,7 @@ namespace VirtualMemoryLogging
         Decommit = 0x40,
         Release = 0x50,
         Reset = 0x60,
+        ReserveFromExecutableMemoryAllocatorWithinRange = 0x70
     };
 
     // Indicates that the attempted operation has failed
@@ -884,8 +887,13 @@ static LPVOID VIRTUALReserveMemory(
 
     // First, figure out where we're trying to reserve the memory and
     // how much we need. On most systems, requests to mmap must be
-    // page-aligned and at multiples of the page size.
-    StartBoundary = (UINT_PTR)lpAddress & ~BOUNDARY_64K;
+    // page-aligned and at multiples of the page size. Unlike on Windows, on
+    // Unix, the allocation granularity is the page size, so the memory size to
+    // reserve is not aligned to 64 KB. Nor should the start boundary need to
+    // to be aligned down to 64 KB, but it is expected that there are other
+    // components that rely on this alignment when providing a specific address
+    // (note that mmap itself does not make any such guarantees).
+    StartBoundary = (UINT_PTR)ALIGN_DOWN(lpAddress, VIRTUAL_64KB);
     /* Add the sizes, and round down to the nearest page boundary. */
     MemSize = ( ((UINT_PTR)lpAddress + dwSize + VIRTUAL_PAGE_MASK) & ~VIRTUAL_PAGE_MASK ) - 
                StartBoundary;
@@ -894,7 +902,14 @@ static LPVOID VIRTUALReserveMemory(
     // try to get memory from the executable memory allocator to satisfy the request.
     if (((flAllocationType & MEM_RESERVE_EXECUTABLE) != 0) && (lpAddress == NULL))
     {
-        pRetVal = g_executableMemoryAllocator.AllocateMemory(MemSize);
+        // Alignment to a 64 KB granularity should not be necessary (alignment to page size should be sufficient), but see
+        // ExecutableMemoryAllocator::AllocateMemory() for the reason why it is done
+        SIZE_T reservationSize = ALIGN_UP(MemSize, VIRTUAL_64KB);
+        pRetVal = g_executableMemoryAllocator.AllocateMemory(reservationSize);
+        if (pRetVal != nullptr)
+        {
+            MemSize = reservationSize;
+        }
     }
 
     if (pRetVal == NULL)
@@ -1227,6 +1242,72 @@ done:
 
 /*++
 Function:
+  PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange
+
+  This function attempts to allocate the requested amount of memory in the specified address range, from the executable memory
+  allocator. If unable to do so, the function returns nullptr and does not set the last error.
+
+  lpBeginAddress - Inclusive beginning of range
+  lpEndAddress - Exclusive end of range
+  dwSize - Number of bytes to allocate
+--*/
+LPVOID
+PALAPI
+PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(
+    IN LPCVOID lpBeginAddress,
+    IN LPCVOID lpEndAddress,
+    IN SIZE_T dwSize)
+{
+#ifdef BIT64
+    PERF_ENTRY(PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange);
+    ENTRY(
+        "PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(lpBeginAddress = %p, lpEndAddress = %p, dwSize = %Iu)\n",
+        lpBeginAddress,
+        lpEndAddress,
+        dwSize);
+
+    _ASSERTE(lpBeginAddress <= lpEndAddress);
+
+    // Alignment to a 64 KB granularity should not be necessary (alignment to page size should be sufficient), but see
+    // ExecutableMemoryAllocator::AllocateMemory() for the reason why it is done
+    SIZE_T reservationSize = ALIGN_UP(dwSize, VIRTUAL_64KB);
+
+    CPalThread *currentThread = InternalGetCurrentThread();
+    InternalEnterCriticalSection(currentThread, &virtual_critsec);
+
+    void *address = g_executableMemoryAllocator.AllocateMemoryWithinRange(lpBeginAddress, lpEndAddress, reservationSize);
+    if (address != nullptr)
+    {
+        _ASSERTE(IS_ALIGNED(address, VIRTUAL_PAGE_SIZE));
+        if (!VIRTUALStoreAllocationInfo((UINT_PTR)address, reservationSize, MEM_RESERVE | MEM_RESERVE_EXECUTABLE, PAGE_NOACCESS))
+        {
+            ASSERT("Unable to store the structure in the list.\n");
+            munmap(address, reservationSize);
+            address = nullptr;
+        }
+    }
+
+    LogVaOperation(
+        VirtualMemoryLogging::VirtualOperation::ReserveFromExecutableMemoryAllocatorWithinRange,
+        nullptr,
+        dwSize,
+        MEM_RESERVE | MEM_RESERVE_EXECUTABLE,
+        PAGE_NOACCESS,
+        address,
+        TRUE);
+
+    InternalLeaveCriticalSection(currentThread, &virtual_critsec);
+
+    LOGEXIT("PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange returning %p\n", address);
+    PERF_EXIT(PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange);
+    return address;
+#else // !BIT64
+    return nullptr;
+#endif // BIT64
+}
+
+/*++
+Function:
   VirtualAlloc
 
 Note:
@@ -1982,11 +2063,15 @@ Function :
 --*/
 void* ReserveMemoryFromExecutableAllocator(CPalThread* pThread, SIZE_T allocationSize)
 {
+#ifdef BIT64
     InternalEnterCriticalSection(pThread, &virtual_critsec);
     void* mem = g_executableMemoryAllocator.AllocateMemory(allocationSize);
     InternalLeaveCriticalSection(pThread, &virtual_critsec);
 
     return mem;
+#else // !BIT64
+    return nullptr;
+#endif // BIT64
 }
 
 /*++
@@ -2024,14 +2109,14 @@ Function:
 void ExecutableMemoryAllocator::TryReserveInitialMemory()
 {
     CPalThread* pthrCurrent = InternalGetCurrentThread();
-    int32_t sizeOfAllocation = MaxExecutableMemorySize;
-    int32_t startAddressIncrement;
-    UINT_PTR startAddress;
+    int32_t sizeOfAllocation = MaxExecutableMemorySizeNearCoreClr;
+    int32_t preferredStartAddressIncrement;
+    UINT_PTR preferredStartAddress;
     UINT_PTR coreclrLoadAddress;
     const int32_t MemoryProbingIncrement = 128 * 1024 * 1024;
 
     // Try to find and reserve an available region of virtual memory that is located
-    // within 2GB range (defined by the MaxExecutableMemorySize constant) from the
+    // within 2GB range (defined by the MaxExecutableMemorySizeNearCoreClr constant) from the
     // location of the coreclr library.
     // Potentially, as a possible future improvement, we can get precise information
     // about available memory ranges by parsing data from '/proc/self/maps'.
@@ -2045,40 +2130,69 @@ void ExecutableMemoryAllocator::TryReserveInitialMemory()
     // (thus avoiding reserving memory below 4GB; besides some operating systems do not allow that).
     // If libcoreclr is loaded at high addresses then try to reserve memory below its location.
     coreclrLoadAddress = (UINT_PTR)PAL_GetSymbolModuleBase((void*)VirtualAlloc);
-    if ((coreclrLoadAddress < 0xFFFFFFFF) || ((coreclrLoadAddress - MaxExecutableMemorySize) < 0xFFFFFFFF))
+    if ((coreclrLoadAddress < 0xFFFFFFFF) || ((coreclrLoadAddress - MaxExecutableMemorySizeNearCoreClr) < 0xFFFFFFFF))
     {
         // Try to allocate above the location of libcoreclr
-        startAddress = coreclrLoadAddress + CoreClrLibrarySize;
-        startAddressIncrement = MemoryProbingIncrement;
+        preferredStartAddress = coreclrLoadAddress + CoreClrLibrarySize;
+        preferredStartAddressIncrement = MemoryProbingIncrement;
     }
     else
     {
         // Try to allocate below the location of libcoreclr
-        startAddress = coreclrLoadAddress - MaxExecutableMemorySize;
-        startAddressIncrement = 0;
+        preferredStartAddress = coreclrLoadAddress - MaxExecutableMemorySizeNearCoreClr;
+        preferredStartAddressIncrement = 0;
     }
 
     // Do actual memory reservation.
     do
     {
-        m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)startAddress, sizeOfAllocation);
-        if (m_startAddress != NULL)
+        m_startAddress = ReserveVirtualMemory(pthrCurrent, (void*)preferredStartAddress, sizeOfAllocation);
+        if (m_startAddress != nullptr)
         {
-            // Memory has been successfully reserved.
-            m_totalSizeOfReservedMemory = sizeOfAllocation;
-
-            // Randomize the location at which we start allocating from the reserved memory range.
-            int32_t randomOffset = GenerateRandomStartOffset();
-            m_nextFreeAddress = (void*)(((UINT_PTR)m_startAddress) + randomOffset);
-            m_remainingReservedMemory = sizeOfAllocation - randomOffset;
             break;
         }
 
         // Try to allocate a smaller region
         sizeOfAllocation -= MemoryProbingIncrement;
-        startAddress += startAddressIncrement;
+        preferredStartAddress += preferredStartAddressIncrement;
 
     } while (sizeOfAllocation >= MemoryProbingIncrement);
+
+    if (m_startAddress == nullptr)
+    {
+        // We were not able to reserve any memory near libcoreclr. Try to reserve approximately 2 GB of address space somewhere
+        // anyway:
+        //   - This sets aside address space that can be used for executable code, such that jumps/calls between such code may
+        //     continue to use short relative addresses instead of long absolute addresses that would currently require jump
+        //     stubs.
+        //   - The inability to allocate memory in a specific range for jump stubs is an unrecoverable problem. This reservation
+        //     would mitigate such issues that can become prevalent depending on which security features are enabled and to what
+        //     extent, such as in particular, PaX's RANDMMAP:
+        //       - https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options
+        //   - Jump stubs for executable code residing in this region can request memory from this allocator
+        //   - Native images can be loaded into this address space, including any jump stubs that are required for its helper
+        //     table. This satisfies the vast majority of practical cases where the total amount of loaded native image memory
+        //     does not exceed approximately 2 GB.
+        //   - The code heap allocator for the JIT can allocate from this address space. Beyond this reservation, one can use
+        //     the COMPlus_CodeHeapReserveForJumpStubs environment variable to reserve space for jump stubs.
+        sizeOfAllocation = MaxExecutableMemorySize;
+        m_startAddress = ReserveVirtualMemory(pthrCurrent, nullptr, sizeOfAllocation);
+        if (m_startAddress == nullptr)
+        {
+            return;
+        }
+    }
+
+    // Memory has been successfully reserved.
+    m_totalSizeOfReservedMemory = sizeOfAllocation;
+
+    // Randomize the location at which we start allocating from the reserved memory range. Alignment to a 64 KB granularity
+    // should not be necessary, but see AllocateMemory() for the reason why it is done.
+    int32_t randomOffset = GenerateRandomStartOffset();
+    m_nextFreeAddress = ALIGN_UP((void*)(((UINT_PTR)m_startAddress) + randomOffset), VIRTUAL_64KB);
+    _ASSERTE(sizeOfAllocation >= (UINT_PTR)m_nextFreeAddress - (UINT_PTR)m_startAddress);
+    m_remainingReservedMemory =
+        ALIGN_DOWN(sizeOfAllocation - ((UINT_PTR)m_nextFreeAddress - (UINT_PTR)m_startAddress), VIRTUAL_64KB);
 }
 
 /*++
@@ -2086,7 +2200,7 @@ Function:
     ExecutableMemoryAllocator::AllocateMemory
 
     This function attempts to allocate the requested amount of memory from its reserved virtual
-    address space. The function will return NULL if the allocation request cannot
+    address space. The function will return null if the allocation request cannot
     be satisfied by the memory that is currently available in the allocator.
 
     Note: This function MUST be called with the virtual_critsec lock held.
@@ -2094,10 +2208,15 @@ Function:
 --*/
 void* ExecutableMemoryAllocator::AllocateMemory(SIZE_T allocationSize)
 {
-    void* allocatedMemory = NULL;
+#ifdef BIT64
+    void* allocatedMemory = nullptr;
 
-    // Allocation size must be in multiples of the virtual page size.
-    _ASSERTE((allocationSize & VIRTUAL_PAGE_MASK) == 0);
+    // Alignment to a 64 KB granularity should not be necessary (alignment to page size should be sufficient), but
+    // VIRTUALReserveMemory() aligns down the specified address to a 64 KB granularity, and as long as that is necessary, the
+    // reservation size here must be aligned to a 64 KB granularity to guarantee that all returned addresses are also aligned to
+    // a 64 KB granularity. Otherwise, attempting to reserve memory starting from an unaligned address returned by this function
+    // would fail in VIRTUALReserveMemory.
+    _ASSERTE(IS_ALIGNED(allocationSize, VIRTUAL_64KB));
 
     // The code below assumes that the caller owns the virtual_critsec lock.
     // So the calculations are not done in thread-safe manner.
@@ -2106,10 +2225,60 @@ void* ExecutableMemoryAllocator::AllocateMemory(SIZE_T allocationSize)
         allocatedMemory = m_nextFreeAddress;
         m_nextFreeAddress = (void*)(((UINT_PTR)m_nextFreeAddress) + allocationSize);
         m_remainingReservedMemory -= allocationSize;
-
     }
 
     return allocatedMemory;
+#else // !BIT64
+    return nullptr;
+#endif // BIT64
+}
+
+/*++
+Function:
+    AllocateMemory
+
+    This function attempts to allocate the requested amount of memory from its reserved virtual
+    address space, if memory is available within the specified range. The function will return
+    null if the allocation request cannot satisfied by the memory that is currently available in
+    the allocator.
+
+    Note: This function MUST be called with the virtual_critsec lock held.
+--*/
+void *ExecutableMemoryAllocator::AllocateMemoryWithinRange(const void *beginAddress, const void *endAddress, SIZE_T allocationSize)
+{
+#ifdef BIT64
+    _ASSERTE(beginAddress <= endAddress);
+
+    // Alignment to a 64 KB granularity should not be necessary (alignment to page size should be sufficient), but see
+    // AllocateMemory() for the reason why it is necessary
+    _ASSERTE(IS_ALIGNED(allocationSize, VIRTUAL_64KB));
+
+    // The code below assumes that the caller owns the virtual_critsec lock.
+    // So the calculations are not done in thread-safe manner.
+
+    if (allocationSize == 0 || allocationSize > m_remainingReservedMemory)
+    {
+        return nullptr;
+    }
+
+    void *address = m_nextFreeAddress;
+    if (address < beginAddress)
+    {
+        return nullptr;
+    }
+
+    void *nextFreeAddress = (void *)((UINT_PTR)address + allocationSize);
+    if (nextFreeAddress > endAddress)
+    {
+        return nullptr;
+    }
+
+    m_nextFreeAddress = nextFreeAddress;
+    m_remainingReservedMemory -= allocationSize;
+    return address;
+#else // !BIT64
+    return nullptr;
+#endif // BIT64
 }
 
 /*++
index a8786de..b7fca3e 100644 (file)
@@ -573,7 +573,9 @@ static DWORD ShouldInjectFaultInRange()
 // Reserves free memory within the range [pMinAddr..pMaxAddr] using
 // ClrVirtualQuery to find free memory and ClrVirtualAlloc to reserve it.
 //
-// This method only supports the flAllocationType of MEM_RESERVE
+// This method only supports the flAllocationType of MEM_RESERVE, and expects that the memory
+// is being reserved for the purpose of eventually storing executable code.
+//
 // Callers also should set dwSize to a multiple of sysInfo.dwAllocationGranularity (64k).
 // That way they can reserve a large region and commit smaller sized pages
 // from that region until it fills up.  
@@ -603,6 +605,11 @@ BYTE * ClrVirtualAllocWithinRange(const BYTE *pMinAddr,
     static unsigned countOfCalls = 0;  // We log the number of tims we call this method
     countOfCalls++;                    // increment the call counter
 
+    if (dwSize == 0)
+    {
+        return nullptr;
+    }
+
     //
     // First lets normalize the pMinAddr and pMaxAddr values
     //
@@ -618,18 +625,26 @@ BYTE * ClrVirtualAllocWithinRange(const BYTE *pMinAddr,
         pMaxAddr = (BYTE *) TOP_MEMORY;
     }
 
+    // If pMaxAddr is not greater than pMinAddr we can not make an allocation
+    if (pMaxAddr <= pMinAddr)
+    {
+        return nullptr;
+    }
+
     // If pMinAddr is BOT_MEMORY and pMaxAddr is TOP_MEMORY
     // then we can call ClrVirtualAlloc instead 
     if ((pMinAddr == (BYTE *) BOT_MEMORY) && (pMaxAddr == (BYTE *) TOP_MEMORY))
     {
-        return (BYTE*) ClrVirtualAlloc(NULL, dwSize, flAllocationType, flProtect);
+        return (BYTE*) ClrVirtualAlloc(nullptr, dwSize, flAllocationType, flProtect);
     }
 
-    // If pMaxAddr is not greater than pMinAddr we can not make an allocation
-    if (dwSize == 0 || pMaxAddr <= pMinAddr)
+#ifdef FEATURE_PAL
+    pResult = (BYTE *)PAL_VirtualReserveFromExecutableMemoryAllocatorWithinRange(pMinAddr, pMaxAddr, dwSize);
+    if (pResult != nullptr)
     {
-        return NULL;
+        return pResult;
     }
+#endif // FEATURE_PAL
 
     // We will do one scan from [pMinAddr .. pMaxAddr]
     // First align the tryAddr up to next 64k base address.