Conversation
CreateDoubleMemoryMapper clips the mapping size to RLIMIT_FSIZE, but still reports success when the clipped size is zero, which is what a process running under RLIMIT_FSIZE=0 gets. ExecutableAllocator:: Initialize() only falls back to single mapped memory when this function fails, so the runtime keeps W^X enabled and fails to start later in AllocateOffset() with 0x8007000E instead of running without W^X. Fixes dotnet#134524
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @agocke |
|
Verified end to end on a local build, as promised in the description. Build:
Before the patch the same trace showed Two notes on scope, so the table is not read as more than it is:
|
Fixes #134524.
CreateDoubleMemoryMapper()clips the double mapping toRLIMIT_FSIZE(#119316, which fixed #117819), but it reports success even when the clipped size is 0 — which is what a process running underRLIMIT_FSIZE=0gets. That matters because of who the caller is:ExecutableAllocator::Initialize()already knows how to continue without W^X, and only does it when this function fails. Reporting success with a zero sized mapper skips that, and the failure resurfaces later inAllocateOffset()(newFreeOffset > m_maxExecutableCodeSize), where there is no fallback left — so a process that can run perfectly well single mapped fails to start instead, withHRESULT: 0x8007000C/0x8007000E.This change fails mapper creation when the clipping leaves nothing to map, so the existing fallback runs.
Measured (shipped 10.0.401 /
Microsoft.NETCore.App 10.0.12,linux-arm64, Alpine 3.22 musl and Ubuntu 24.04 glibc — identical on both)RLIMIT_FSIZEDOTNET_EnableWriteXorExecute=0Failed to create CoreCLR, HRESULT: 0x8007000CFailed to load System.Private.CoreLib.dll (0x8007000E)Out of memory.straceatRLIMIT_FSIZE=0shows the zero sized mapper being created and accepted:memfd_create("doublemapper", MFD_CLOEXEC) = 8thenftruncate(8, 0) = 0. With this change that path returnsfalseinstead, which is the same state the last column reaches by hand.Scope, and what I deliberately left out
This only covers the unambiguous case — a mapper that cannot back a single allocation. The table shows 1 MiB and 2 MiB are unusable in practice too, but the threshold depends on how much code the runtime maps before
Main, and picking a minimum is a judgement call that belongs to you rather than to me; #134524 lays out the options. Happy to extend this PR in whichever direction you prefer.No behaviour change when
RLIMIT_FSIZEis unlimited or large enough, which is every normal configuration.I am building
clr+libslocally to confirm the end-to-end behaviour onlinux-arm64and will post the result as a comment — the reasoning above is from reading the code and measuring the shipped runtime, not yet from a build with this patch in it.