From 42e81bc2b9342aa0ab4621dab0d461668c0b84e4 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Mon, 20 May 2024 20:41:00 +0200 Subject: [PATCH 1/4] Disable W^X on x64 in rosetta based container The docker on macOS Arm64 uses Rosetta to run x64 containers. That has an effect on the double mapping. The Rosetta is unable to detect when an already executed code page is modified. So we cannot use double mapping on those containers. To detect that case, this change adds check that verifies that the double mapping works even when the code is modified. Close #102226 --- src/coreclr/minipal/Unix/doublemapping.cpp | 92 ++++++++++++++++++++++ src/coreclr/vm/amd64/asmhelpers.S | 11 +++ 2 files changed, 103 insertions(+) diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp index cb65e5e284e2b..35dab9747a3c3 100644 --- a/src/coreclr/minipal/Unix/doublemapping.cpp +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -47,6 +47,90 @@ static const off_t MaxDoubleMappedSize = UINT_MAX; #endif // TARGET_OSX +#if defined(TARGET_AMD64) && !defined(TARGET_OSX) + +extern "C" int VerifyDoubleMapping1(); +extern "C" void VerifyDoubleMapping1_End(); +extern "C" int VerifyDoubleMapping2(); +extern "C" void VerifyDoubleMapping2_End(); + +// Verify that the double mapping works correctly, including cases when the executable code page is modified after +// the code is executed. +bool VerifyDoubleMapping(int fd) +{ + bool result = false; + void *mapperHandle = (void*)(size_t)fd; + void *pCommittedPage = NULL; + void *pWriteablePage = NULL; + int testCallResult; + + typedef int (*VerificationFunctionPtr)(); + VerificationFunctionPtr pVerificationFunction; + + size_t pageSize = getpagesize(); + + void *pExecutablePage = VMToOSInterface::ReserveDoubleMappedMemory(mapperHandle, 0, pageSize, NULL, NULL); + + if (pExecutablePage == NULL) + { + goto Cleanup; + } + + pCommittedPage = VMToOSInterface::CommitDoubleMappedMemory(pExecutablePage, pageSize, true); + if (pCommittedPage == NULL) + { + goto Cleanup; + } + + pWriteablePage = VMToOSInterface::GetRWMapping(mapperHandle, pCommittedPage, 0, pageSize); + if (pWriteablePage == NULL) + { + goto Cleanup; + } + + // First copy a method of a simple function that returns 1 into the writeable mapping + memcpy(pWriteablePage, (void*)VerifyDoubleMapping1, (char*)VerifyDoubleMapping1_End - (char*)VerifyDoubleMapping1); + pVerificationFunction = (VerificationFunctionPtr)pExecutablePage; + // Invoke the function via the executable mapping. It should return 1. + testCallResult = pVerificationFunction(); + if (testCallResult != 1) + { + goto Cleanup; + } + + VMToOSInterface::ReleaseRWMapping(pWriteablePage, pageSize); + pWriteablePage = VMToOSInterface::GetRWMapping(mapperHandle, pCommittedPage, 0, pageSize); + if (pWriteablePage == NULL) + { + goto Cleanup; + } + + // Now overwrite the first function by a second one that returns 2 using the writeable mapping + memcpy(pWriteablePage, (void*)VerifyDoubleMapping2, (char*)VerifyDoubleMapping2_End - (char*)VerifyDoubleMapping2); + pVerificationFunction = (VerificationFunctionPtr)pExecutablePage; + testCallResult = pVerificationFunction(); + // Invoke the function via the executable mapping again. It should return 2 now. + // This doesn't work when running x64 code in docker on macOS Arm64 where the code is not re-translated by Rosetta + if (testCallResult == 2) + { + result = true; + } + +Cleanup: + if (pWriteablePage != NULL) + { + VMToOSInterface::ReleaseRWMapping(pWriteablePage, pageSize); + } + + if (pExecutablePage != NULL) + { + VMToOSInterface::ReleaseDoubleMappedMemory(mapperHandle, pExecutablePage, 0, pageSize); + } + + return result; +} +#endif // TARGET_AMD64 && !TARGET_OSX + bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) { #ifndef TARGET_OSX @@ -74,6 +158,14 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecu return false; } +#if defined(TARGET_AMD64) && !defined(TARGET_OSX) + if (!VerifyDoubleMapping(fd)) + { + close(fd); + return false; + } +#endif // TARGET_AMD64 && !TARGET_OSX + *pMaxExecutableCodeSize = MaxDoubleMappedSize; *pHandle = (void*)(size_t)fd; #else // !TARGET_OSX diff --git a/src/coreclr/vm/amd64/asmhelpers.S b/src/coreclr/vm/amd64/asmhelpers.S index 8d83938246a2c..77d0f5bcb4fb7 100644 --- a/src/coreclr/vm/amd64/asmhelpers.S +++ b/src/coreclr/vm/amd64/asmhelpers.S @@ -311,3 +311,14 @@ LEAF_ENTRY GetTlsIndexObjectDescOffset, _TEXT int 3 LEAF_END GetTlsIndexObjectDescOffset, _TEXT #endif + +# These functions are used to verify that double mapping used to implement W^X works. +LEAF_ENTRY VerifyDoubleMapping1, _TEXT + mov rax, 1 + ret +LEAF_END_MARKED VerifyDoubleMapping1, _TEXT + +LEAF_ENTRY VerifyDoubleMapping2, _TEXT + mov rax, 2 + ret +LEAF_END_MARKED VerifyDoubleMapping2, _TEXT From c737b9ade1cbbba25e1ef9f9e5b7fe6a76d70272 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 23 May 2024 02:34:53 +0200 Subject: [PATCH 2/4] Rework based on PR feedback --- src/coreclr/minipal/CMakeLists.txt | 1 + src/coreclr/minipal/Unix/doublemapping.cpp | 119 +----------------- src/coreclr/minipal/Windows/doublemapping.cpp | 7 ++ src/native/minipal/cpufeatures.c | 48 +++++++ src/native/minipal/cpufeatures.h | 1 + 5 files changed, 62 insertions(+), 114 deletions(-) diff --git a/src/coreclr/minipal/CMakeLists.txt b/src/coreclr/minipal/CMakeLists.txt index 3096237d2a2fe..78a1726af3e81 100644 --- a/src/coreclr/minipal/CMakeLists.txt +++ b/src/coreclr/minipal/CMakeLists.txt @@ -1,4 +1,5 @@ include_directories(.) +include_directories(${CLR_SRC_NATIVE_DIR}) if (CLR_CMAKE_HOST_UNIX) add_subdirectory(Unix) else (CLR_CMAKE_HOST_UNIX) diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp index 35dab9747a3c3..3de865c372f92 100644 --- a/src/coreclr/minipal/Unix/doublemapping.cpp +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -20,22 +20,7 @@ #define memfd_create(...) syscall(__NR_memfd_create, __VA_ARGS__) #endif // TARGET_LINUX && !MFD_CLOEXEC #include "minipal.h" - -#if defined(TARGET_OSX) && defined(TARGET_AMD64) -#include -#include - -bool IsProcessTranslated() -{ - int ret = 0; - size_t size = sizeof(ret); - if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) - { - return false; - } - return ret == 1; -} -#endif // TARGET_OSX && TARGET_AMD64 +#include "minipal/cpufeatures.h" #ifndef TARGET_OSX @@ -47,92 +32,14 @@ static const off_t MaxDoubleMappedSize = UINT_MAX; #endif // TARGET_OSX -#if defined(TARGET_AMD64) && !defined(TARGET_OSX) - -extern "C" int VerifyDoubleMapping1(); -extern "C" void VerifyDoubleMapping1_End(); -extern "C" int VerifyDoubleMapping2(); -extern "C" void VerifyDoubleMapping2_End(); - -// Verify that the double mapping works correctly, including cases when the executable code page is modified after -// the code is executed. -bool VerifyDoubleMapping(int fd) +bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) { - bool result = false; - void *mapperHandle = (void*)(size_t)fd; - void *pCommittedPage = NULL; - void *pWriteablePage = NULL; - int testCallResult; - - typedef int (*VerificationFunctionPtr)(); - VerificationFunctionPtr pVerificationFunction; - - size_t pageSize = getpagesize(); - - void *pExecutablePage = VMToOSInterface::ReserveDoubleMappedMemory(mapperHandle, 0, pageSize, NULL, NULL); - - if (pExecutablePage == NULL) - { - goto Cleanup; - } - - pCommittedPage = VMToOSInterface::CommitDoubleMappedMemory(pExecutablePage, pageSize, true); - if (pCommittedPage == NULL) - { - goto Cleanup; - } - - pWriteablePage = VMToOSInterface::GetRWMapping(mapperHandle, pCommittedPage, 0, pageSize); - if (pWriteablePage == NULL) - { - goto Cleanup; - } - - // First copy a method of a simple function that returns 1 into the writeable mapping - memcpy(pWriteablePage, (void*)VerifyDoubleMapping1, (char*)VerifyDoubleMapping1_End - (char*)VerifyDoubleMapping1); - pVerificationFunction = (VerificationFunctionPtr)pExecutablePage; - // Invoke the function via the executable mapping. It should return 1. - testCallResult = pVerificationFunction(); - if (testCallResult != 1) - { - goto Cleanup; - } - - VMToOSInterface::ReleaseRWMapping(pWriteablePage, pageSize); - pWriteablePage = VMToOSInterface::GetRWMapping(mapperHandle, pCommittedPage, 0, pageSize); - if (pWriteablePage == NULL) - { - goto Cleanup; - } - - // Now overwrite the first function by a second one that returns 2 using the writeable mapping - memcpy(pWriteablePage, (void*)VerifyDoubleMapping2, (char*)VerifyDoubleMapping2_End - (char*)VerifyDoubleMapping2); - pVerificationFunction = (VerificationFunctionPtr)pExecutablePage; - testCallResult = pVerificationFunction(); - // Invoke the function via the executable mapping again. It should return 2 now. - // This doesn't work when running x64 code in docker on macOS Arm64 where the code is not re-translated by Rosetta - if (testCallResult == 2) - { - result = true; - } - -Cleanup: - if (pWriteablePage != NULL) - { - VMToOSInterface::ReleaseRWMapping(pWriteablePage, pageSize); - } - - if (pExecutablePage != NULL) + if (minipal_detect_emulation()) { - VMToOSInterface::ReleaseDoubleMappedMemory(mapperHandle, pExecutablePage, 0, pageSize); + // Rosetta or QEMU doesn't support double mapping correctly + return false; } - return result; -} -#endif // TARGET_AMD64 && !TARGET_OSX - -bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) -{ #ifndef TARGET_OSX #ifdef TARGET_FREEBSD @@ -158,26 +65,10 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecu return false; } -#if defined(TARGET_AMD64) && !defined(TARGET_OSX) - if (!VerifyDoubleMapping(fd)) - { - close(fd); - return false; - } -#endif // TARGET_AMD64 && !TARGET_OSX - *pMaxExecutableCodeSize = MaxDoubleMappedSize; *pHandle = (void*)(size_t)fd; #else // !TARGET_OSX -#ifdef TARGET_AMD64 - if (IsProcessTranslated()) - { - // Rosetta doesn't support double mapping correctly - return false; - } -#endif // TARGET_AMD64 - *pMaxExecutableCodeSize = SIZE_MAX; *pHandle = NULL; #endif // !TARGET_OSX diff --git a/src/coreclr/minipal/Windows/doublemapping.cpp b/src/coreclr/minipal/Windows/doublemapping.cpp index 0d7033b567056..57d36b434d858 100644 --- a/src/coreclr/minipal/Windows/doublemapping.cpp +++ b/src/coreclr/minipal/Windows/doublemapping.cpp @@ -6,6 +6,7 @@ #include #include #include "minipal.h" +#include "minipal/cpufeatures.h" #define HIDWORD(_qw) ((ULONG)((_qw) >> 32)) #define LODWORD(_qw) ((ULONG)(_qw)) @@ -60,6 +61,12 @@ inline void *GetBotMemoryAddress(void) bool VMToOSInterface::CreateDoubleMemoryMapper(void **pHandle, size_t *pMaxExecutableCodeSize) { + if (minipal_detect_emulation()) + { + // Rosetta or QEMU doesn't support double mapping correctly + return false; + } + *pMaxExecutableCodeSize = (size_t)MaxDoubleMappedSize; *pHandle = CreateFileMapping( INVALID_HANDLE_VALUE, // use paging file diff --git a/src/native/minipal/cpufeatures.c b/src/native/minipal/cpufeatures.c index a7c2ae737badc..e03a65b5e0947 100644 --- a/src/native/minipal/cpufeatures.c +++ b/src/native/minipal/cpufeatures.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "cpufeatures.h" #include "cpuid.h" @@ -473,3 +475,49 @@ int minipal_getcpufeatures(void) return result; } + +bool minipal_detect_emulation(void) +{ +#ifdef HOST_AMD64 + // Check for CPU brand indicating emulation + int regs[4]; + char brand[49]; + + // Get the maximum value for extended function CPUID info + __cpuid(regs, (int)0x80000000); + if ((unsigned int)regs[0] < 0x80000004) + { + return false; // Extended CPUID not supported + } + + // Retrieve the CPU brand string + for (unsigned int i = 0x80000002; i <= 0x80000004; ++i) + { + __cpuid(regs, (int)i); + memcpy(brand + (i - 0x80000002) * sizeof(regs), regs, sizeof(regs)); + } + brand[sizeof(brand) - 1] = '\0'; + + // Check if CPU brand indicates emulation + if (strstr(brand, "VirtualApple") != NULL || strstr(brand, "QEMU") != NULL) + { + return true; + } +#endif + + // Check for process name of PID 1 indicating emulation + char cmdline[256]; + FILE *cmdline_file = fopen("/proc/1/cmdline", "r"); + if (cmdline_file != NULL) + { + fgets(cmdline, sizeof(cmdline), cmdline_file); + fclose(cmdline_file); + + if (strstr(cmdline, "qemu") != NULL || strstr(cmdline, "rosetta") != NULL) + { + return true; + } + } + + return false; +} diff --git a/src/native/minipal/cpufeatures.h b/src/native/minipal/cpufeatures.h index a5a803e5d2888..57e30b5d15439 100644 --- a/src/native/minipal/cpufeatures.h +++ b/src/native/minipal/cpufeatures.h @@ -77,6 +77,7 @@ extern "C" #endif // __cplusplus int minipal_getcpufeatures(void); +bool minipal_detect_emulation(void); #ifdef __cplusplus } From 7c734b0090fa63a465c1384376eb71f45dbf1525 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 23 May 2024 15:19:10 +0200 Subject: [PATCH 3/4] Check only for Rosetta --- src/coreclr/minipal/Unix/doublemapping.cpp | 10 +++++++--- src/coreclr/minipal/Windows/doublemapping.cpp | 4 ++-- src/coreclr/vm/amd64/asmhelpers.S | 11 ----------- src/native/minipal/cpufeatures.c | 19 +++---------------- src/native/minipal/cpufeatures.h | 2 +- 5 files changed, 13 insertions(+), 33 deletions(-) diff --git a/src/coreclr/minipal/Unix/doublemapping.cpp b/src/coreclr/minipal/Unix/doublemapping.cpp index 3de865c372f92..67d516fb322a5 100644 --- a/src/coreclr/minipal/Unix/doublemapping.cpp +++ b/src/coreclr/minipal/Unix/doublemapping.cpp @@ -22,7 +22,11 @@ #include "minipal.h" #include "minipal/cpufeatures.h" -#ifndef TARGET_OSX +#ifdef TARGET_OSX + +#include + +#else // TARGET_OSX #ifdef TARGET_64BIT static const off_t MaxDoubleMappedSize = 2048ULL*1024*1024*1024; @@ -34,9 +38,9 @@ static const off_t MaxDoubleMappedSize = UINT_MAX; bool VMToOSInterface::CreateDoubleMemoryMapper(void** pHandle, size_t *pMaxExecutableCodeSize) { - if (minipal_detect_emulation()) + if (minipal_detect_rosetta()) { - // Rosetta or QEMU doesn't support double mapping correctly + // Rosetta doesn't support double mapping correctly return false; } diff --git a/src/coreclr/minipal/Windows/doublemapping.cpp b/src/coreclr/minipal/Windows/doublemapping.cpp index 57d36b434d858..9bde3dc86d8b5 100644 --- a/src/coreclr/minipal/Windows/doublemapping.cpp +++ b/src/coreclr/minipal/Windows/doublemapping.cpp @@ -61,9 +61,9 @@ inline void *GetBotMemoryAddress(void) bool VMToOSInterface::CreateDoubleMemoryMapper(void **pHandle, size_t *pMaxExecutableCodeSize) { - if (minipal_detect_emulation()) + if (minipal_detect_rosetta()) { - // Rosetta or QEMU doesn't support double mapping correctly + // Rosetta doesn't support double mapping correctly return false; } diff --git a/src/coreclr/vm/amd64/asmhelpers.S b/src/coreclr/vm/amd64/asmhelpers.S index 77d0f5bcb4fb7..8d83938246a2c 100644 --- a/src/coreclr/vm/amd64/asmhelpers.S +++ b/src/coreclr/vm/amd64/asmhelpers.S @@ -311,14 +311,3 @@ LEAF_ENTRY GetTlsIndexObjectDescOffset, _TEXT int 3 LEAF_END GetTlsIndexObjectDescOffset, _TEXT #endif - -# These functions are used to verify that double mapping used to implement W^X works. -LEAF_ENTRY VerifyDoubleMapping1, _TEXT - mov rax, 1 - ret -LEAF_END_MARKED VerifyDoubleMapping1, _TEXT - -LEAF_ENTRY VerifyDoubleMapping2, _TEXT - mov rax, 2 - ret -LEAF_END_MARKED VerifyDoubleMapping2, _TEXT diff --git a/src/native/minipal/cpufeatures.c b/src/native/minipal/cpufeatures.c index e03a65b5e0947..be6a8b2929a05 100644 --- a/src/native/minipal/cpufeatures.c +++ b/src/native/minipal/cpufeatures.c @@ -476,7 +476,8 @@ int minipal_getcpufeatures(void) return result; } -bool minipal_detect_emulation(void) +// Detect if the current process is running under the Apple Rosetta x64 emulator +bool minipal_detect_rosetta(void) { #ifdef HOST_AMD64 // Check for CPU brand indicating emulation @@ -499,25 +500,11 @@ bool minipal_detect_emulation(void) brand[sizeof(brand) - 1] = '\0'; // Check if CPU brand indicates emulation - if (strstr(brand, "VirtualApple") != NULL || strstr(brand, "QEMU") != NULL) + if (strstr(brand, "VirtualApple") != NULL) { return true; } #endif - // Check for process name of PID 1 indicating emulation - char cmdline[256]; - FILE *cmdline_file = fopen("/proc/1/cmdline", "r"); - if (cmdline_file != NULL) - { - fgets(cmdline, sizeof(cmdline), cmdline_file); - fclose(cmdline_file); - - if (strstr(cmdline, "qemu") != NULL || strstr(cmdline, "rosetta") != NULL) - { - return true; - } - } - return false; } diff --git a/src/native/minipal/cpufeatures.h b/src/native/minipal/cpufeatures.h index 57e30b5d15439..472ce17833961 100644 --- a/src/native/minipal/cpufeatures.h +++ b/src/native/minipal/cpufeatures.h @@ -77,7 +77,7 @@ extern "C" #endif // __cplusplus int minipal_getcpufeatures(void); -bool minipal_detect_emulation(void); +bool minipal_detect_rosetta(void); #ifdef __cplusplus } From a6833c0bd7804e76a442f3794137650f85a3eea2 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 23 May 2024 16:56:27 +0200 Subject: [PATCH 4/4] Enable the rosetta check for x86 too This will help WINE running 32 bit code under rosetta emulation on macOS. --- src/coreclr/minipal/Windows/doublemapping.cpp | 2 +- src/native/minipal/cpufeatures.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/minipal/Windows/doublemapping.cpp b/src/coreclr/minipal/Windows/doublemapping.cpp index 9bde3dc86d8b5..9e8ddfed8e964 100644 --- a/src/coreclr/minipal/Windows/doublemapping.cpp +++ b/src/coreclr/minipal/Windows/doublemapping.cpp @@ -63,7 +63,7 @@ bool VMToOSInterface::CreateDoubleMemoryMapper(void **pHandle, size_t *pMaxExecu { if (minipal_detect_rosetta()) { - // Rosetta doesn't support double mapping correctly + // Rosetta doesn't support double mapping correctly. WINE on macOS ARM64 can be running under Rosetta. return false; } diff --git a/src/native/minipal/cpufeatures.c b/src/native/minipal/cpufeatures.c index be6a8b2929a05..a2ff83222140c 100644 --- a/src/native/minipal/cpufeatures.c +++ b/src/native/minipal/cpufeatures.c @@ -479,7 +479,7 @@ int minipal_getcpufeatures(void) // Detect if the current process is running under the Apple Rosetta x64 emulator bool minipal_detect_rosetta(void) { -#ifdef HOST_AMD64 +#if defined(HOST_AMD64) || defined(HOST_X86) // Check for CPU brand indicating emulation int regs[4]; char brand[49]; @@ -504,7 +504,7 @@ bool minipal_detect_rosetta(void) { return true; } -#endif +#endif // HOST_AMD64 || HOST_X86 return false; }