-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Disable W^X in Rosetta emulated x64 containers on macOS #102509
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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 | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am wondering whether it is possible to make it work by issuing flush instruction cache syscall. Is there a syscall like that on Linux x64? Similar to what we do for Windows: runtime/src/coreclr/vm/amd64/cgencpu.h Lines 588 to 605 in 362a95d
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apple folks told me in the past that Rosetta doesn't support this scenario, but I can add the cache flushing. In fact, it should be in this testing method anyways. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be specific, the problem is that Rosetta JITs the x64 code into arm64 on first execution and uses the result ever after. Since it doesn't get any notification on the executable code modification via a secondary mapping, it doesn't re-JIT it and keeps using the old code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As for the cache flushing, there is the |
||||||||||||||||||||||||||||||||||||||
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; | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this going to result in W^X being disabled? If I am reading the code correctly, it is going to result into startup failure. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it is going to result in W^X being disabled - I've tested it in a x64 docker container on my M1. See the caller of the runtime/src/coreclr/utilcode/executableallocator.cpp Lines 282 to 298 in 7452305
|
||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
#endif // TARGET_AMD64 && !TARGET_OSX | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
*pMaxExecutableCodeSize = MaxDoubleMappedSize; | ||||||||||||||||||||||||||||||||||||||
*pHandle = (void*)(size_t)fd; | ||||||||||||||||||||||||||||||||||||||
#else // !TARGET_OSX | ||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any technical issue that require us to restrict this run-time introspection for these platforms? If we don't restrict it, then it will cover qemu cases for other archs as well (e.g. #97729 (comment)), and prevent user from manually setting
DOTNET_EnableWriteXorExecute=0
to run .NET applications.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am actually not convinced whether such detection is required? Perhaps we can just document that the config needs to be disabled for emulated cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember once @jkotas mentioned that we wanted to support rosetta2 on macOS as a first-class platform, and therefore we are handling it explicitly with
sysctl.proc_translated
in a few places (coreclr, nativelibs and shell scripts and even in official build infra for downloading x64 on arm64 in translated process). I think it might be better to bring this toIsProcessTranslated
plan as well without the run-time detection?runtime/src/coreclr/minipal/Unix/doublemapping.cpp
Line 28 in 362a95d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sysctl.proc_translated
is not going to help. This is about running Linux docker containers. We do not know aboutsysctl.proc_translated
equivalent for Linux docker containers running under Rosetta.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, it makes sense. Could we enable this feature detection for other platforms as well (which will cover qemu scenarios)? (assuming the overhead due to this one-time check is negligible)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@am11 I've tried to look at the discussion you have linked, but I have not seen any evidence of the culprit being inability to double map. But I might have missed that. Can you please point me to such information? The point is, if it is not a problem of double mapping, this check may not be able to check the other issue.
When I started to investigate this issue, I actually still had a docker desktop that used QEMU for x64 emulation on arm64 and even with W^X disabled, attempt to build .NET runtime was failing at some point due to SIGSEGV. Only after that I've realized I need to upgrade docker to get the version that used Rosetta to be able to make it work even with W^X disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@janvorli, I was running into
qemu: uncaught target signal 11 (Segmentation fault)
which was fixed withDOTNET_EnableWriteXorExecute=0
. Later dotnet publish was failing a runtime assertion on qemu arm target. But to get to that late assertion sigsegv was the roadblock thatDOTNET_EnableWriteXorExecute=0
fixed. In the context of this PR, I thought it might be useful to keep the condition a bit relaxed.