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 cb65e5e284e2b3..35dab9747a3c35 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 8d83938246a2c9..77d0f5bcb4fb7c 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 3096237d2a2fe3..78a1726af3e815 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 35dab9747a3c35..3de865c372f92d 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 0d7033b567056c..57d36b434d858d 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 a7c2ae737badcd..e03a65b5e09474 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 a5a803e5d2888f..57e30b5d15439d 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 3de865c372f92d..67d516fb322a56 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 57d36b434d858d..9bde3dc86d8b52 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 77d0f5bcb4fb7c..8d83938246a2c9 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 e03a65b5e09474..be6a8b2929a05a 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 57e30b5d15439d..472ce178339613 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 9bde3dc86d8b52..9e8ddfed8e964d 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 be6a8b2929a05a..a2ff83222140cd 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; }