From f504904693c103bf57b3fc66b125dab613c9a54a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:25:23 -0700 Subject: [PATCH 1/3] [release/11.0-rc1] Fix crossgen2 rejecting input files with uppercase .DLL/.EXE extensions (#132894) Backport of #132857 to release/11.0-rc1 /cc @jtschuster @Copilot ## Customer Impact - [x] Customer reported - [ ] Found internally Crossgen2 does not accept uppercase letters as the file extension for .dll and .exe files. On filesystems that always report uppercase file names, this makes it impossible to use the affected versions of crossgen2. ## Regression - [x] Yes - [ ] No Likely https://github.com/dotnet/runtime/pull/122947 ## Testing Testing was validated with a local build of crossgen2. ## Risk Low. The change is small and targeted, and no existing tests have failed. **IMPORTANT**: If this backport is for a servicing release, please verify that: - For .NET 8 and .NET 9: The PR target branch is `release/X.0-staging`, not `release/X.0`. - For .NET 10+: The PR target branch is `release/X.0` (no `-staging` suffix). ## Package authoring no longer needed in .NET 9 **IMPORTANT**: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version. Keep in mind that we still need package authoring in .NET 8 and older versions. Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- src/coreclr/tools/aot/crossgen2/Program.cs | 35 ++++++++++++---------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 691add6dd78b2a..ebd233ed68ff83 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -309,8 +309,8 @@ public int Run() { foreach (var inputFile in inputFilePaths) { - var tmpOutFile = inputFile.Value.Replace(".dll", ".ni.dll.tmp"); - var outFile = inputFile.Value.Replace(".dll", ".ni.dll"); + var tmpOutFile = GetNearOutputFilePath(inputFile.Value, temporary: true); + var outFile = GetNearOutputFilePath(inputFile.Value, temporary: false); Console.WriteLine($@"Moving R2R PE file: {tmpOutFile} to {outFile}"); System.IO.File.Move(tmpOutFile, outFile); } @@ -324,6 +324,23 @@ public int Run() return 0; } + private static string GetNearOutputFilePath(string inFilePath, bool temporary) + { + string inputFileExtension = Path.GetExtension(inFilePath); + if (inputFileExtension.Equals(".dll", StringComparison.OrdinalIgnoreCase)) + { + return Path.ChangeExtension(inFilePath, temporary ? ".ni.dll.tmp" : ".ni.dll"); + } + else if (inputFileExtension.Equals(".exe", StringComparison.OrdinalIgnoreCase)) + { + return Path.ChangeExtension(inFilePath, temporary ? ".ni.exe.tmp" : ".ni.exe"); + } + else + { + throw new CommandLineException(string.Format(SR.UnsupportedInputFileExtension, inputFileExtension)); + } + } + private void RunSingleCompilation(Dictionary inFilePaths, InstructionSetSupport instructionSetSupport, string compositeRootPath, Dictionary unrootedInputFilePaths, HashSet versionBubbleModulesHash, ReadyToRunCompilerContext typeSystemContext, Logger logger) { // @@ -332,19 +349,7 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru var e = inFilePaths.GetEnumerator(); e.MoveNext(); string inFilePath = e.Current.Value; - string inputFileExtension = Path.GetExtension(inFilePath); - string nearOutFilePath = inputFileExtension switch - { - ".dll" => Path.ChangeExtension(inFilePath, - _singleFileCompilation&& _inputBubble - ? ".ni.dll.tmp" - : ".ni.dll"), - ".exe" => Path.ChangeExtension(inFilePath, - _singleFileCompilation && _inputBubble - ? ".ni.exe.tmp" - : ".ni.exe"), - _ => throw new CommandLineException(string.Format(SR.UnsupportedInputFileExtension, inputFileExtension)) - }; + string nearOutFilePath = GetNearOutputFilePath(inFilePath, temporary: _singleFileCompilation && _inputBubble); string outFile = _outNearInput ? nearOutFilePath : _outputFilePath; string dgmlLogFileName = Get(_command.DgmlLogFileName); From 63e34cc9351d960a56898793a352c96b262b07a1 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:26:13 -0700 Subject: [PATCH 2/3] [release/11.0-rc1] Force runtime discovery for TypeMap entries when unable to encode a type in crossgen2's precomputed TypeMap (#132984) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #131610 enabled Crossgen2 to precompute TypeMap entries for types referenced only by type name strings in custom-attribute blobs. These references in attributes do not inherently require corresponding `TypeRef` or `AssemblyRef` metadata rows. Roslyn generally emits full assembly identities for external types and may also introduce metadata references, but other valid producers may emit only the serialized name. Crossgen2 successfully resolves that string to a `TypeDesc`. It therefore knows the target module and full assembly identity. The problem occurs when translating that result into a ReadyToRun type fixup. The mutable manifest currently represents an out-of-bubble reference using: ``` #: ``` At runtime this means: 1. Resolve the source module by index. 2. Find an `AssemblyRef` with that simple name in the source module. 3. Bind that `AssemblyRef`. 4. Resolve the synthetic `TypeRef`. PR #131610 supplied the target assembly’s simple name even but step 2 was impossible because the source module had no corresponding `AssemblyRef`. The resulting fixup is well-formed, but cannot be resolved. See https://github.com/dotnet/runtime/issues/132811 for additional info. To workaround the issue, we emit the (TypeMapGroup, ProxyOrExternalMap) entry with a `0` state, which tells the runtime to build the map from attributes instead of using the precomputed map. This was done previously for maps that are expected to throw a type system exception. This allows some parts of the type map to still be encoded if a single entry is unencodable. The fix going into main is https://github.com/dotnet/runtime/pull/133038, which is more involved, modifies the R2R format, and bumps the R2R version. Since there are workarounds for the mobile team, we can do this less risky change for the backport. ## Customer Impact Found internally. Crossgenned apps with a valid precomputed type map can throw at runtime. ## Regression Yes. The original TypeMap implementation would create the map only at runtime. The TypeMap precomputation PR introduced issue https://github.com/dotnet/runtime/issues/131527. The fix for that introduced issue https://github.com/dotnet/runtime/issues/132811, the one this fixes. ## Testing The existing test suite caught the issue but wasn't run in the PRs because crossgen tests aren't a part the default 'runtime' pipeline. The crossgen2 pipelines catch this issue and will be green if the issue is fixed. ## Risk Low. The fix doesn't introduce new features or a change to the TypeMap section format. It utilizes existing functionality to force runtime computation of the map. There is a risk that we haven't caught all possible edge cases. --- .../Compiler/ReadyToRunExternalTypeMapNode.cs | 11 +- .../Compiler/ReadyToRunProxyTypeMapNode.cs | 11 +- .../Compiler/ReadyToRunTypeMapManager.cs | 121 +++++++++++++++++- .../TypeSystem/Mutable/MutableModule.cs | 62 ++++++++- 4 files changed, 192 insertions(+), 13 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs index d02586c9093220..2ce3599a43c6b5 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunExternalTypeMapNode.cs @@ -11,7 +11,12 @@ namespace ILCompiler.ReadyToRun { - internal class ReadyToRunExternalTypeMapNode(ModuleDesc triggeringModule, TypeDesc group, TypeMapMetadata.IExternalTypeMap map, ImportReferenceProvider importProvider) : SortableDependencyNode, IExternalTypeMapNode + internal class ReadyToRunExternalTypeMapNode( + ModuleDesc triggeringModule, + TypeDesc group, + TypeMapMetadata.IExternalTypeMap map, + ImportReferenceProvider importProvider, + bool requiresRuntimeProcessing) : SortableDependencyNode, IExternalTypeMapNode { public TypeDesc TypeMapGroup => group; @@ -40,7 +45,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider externalReferences) { Vertex typeMapGroupVertex = externalReferences.EncodeReferenceToType(writer, TypeMapGroup, TriggeringModule); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { // We don't write out the throwing method stub for R2R // as emitting loose methods is not supported/very expensive. @@ -71,7 +76,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto { yield return new DependencyListEntry(importProvider.GetImportToType(TypeMapGroup, TriggeringModule), $"Type map '{TypeMapGroup}' key type"); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { yield break; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs index 6f5e80a00f2e4b..0690b6d0450ca6 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunProxyTypeMapNode.cs @@ -9,7 +9,12 @@ namespace ILCompiler.ReadyToRun { - internal class ReadyToRunProxyTypeMapNode(ModuleDesc triggeringModule, TypeDesc group, TypeMapMetadata.IProxyTypeMap map, ImportReferenceProvider importProvider) : SortableDependencyNode, IProxyTypeMapNode + internal class ReadyToRunProxyTypeMapNode( + ModuleDesc triggeringModule, + TypeDesc group, + TypeMapMetadata.IProxyTypeMap map, + ImportReferenceProvider importProvider, + bool requiresRuntimeProcessing) : SortableDependencyNode, IProxyTypeMapNode { public TypeDesc TypeMapGroup => group; @@ -38,7 +43,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer public Vertex CreateTypeMap(NodeFactory factory, NativeWriter writer, Section section, INativeFormatTypeReferenceProvider ProxyReferences) { Vertex typeMapGroupVertex = ProxyReferences.EncodeReferenceToType(writer, TypeMapGroup, TriggeringModule); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { // We don't write out the throwing method stub for R2R // as emitting loose methods is not supported/very expensive. @@ -71,7 +76,7 @@ public override IEnumerable GetStaticDependencies(NodeFacto { yield return new DependencyListEntry(importProvider.GetImportToType(TypeMapGroup, TriggeringModule), $"Type map '{TypeMapGroup}' key type"); - if (map.ThrowingMethodStub is not null) + if (map.ThrowingMethodStub is not null || requiresRuntimeProcessing) { yield break; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs index 60d36da324de64..ea38ebe4c315d9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunTypeMapManager.cs @@ -2,22 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysis.ReadyToRun; using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.ReadyToRun.TypeSystem; using Internal.Runtime; using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; namespace ILCompiler.ReadyToRun { public sealed class ReadyToRunTypeMapManager(ModuleDesc triggeringModule, TypeMapMetadata assemblyTypeMaps) : TypeMapManager { private ImportReferenceProvider _importReferenceProvider; + private readonly HashSet _externalTypeMapsRequiringRuntimeProcessing = []; + private readonly HashSet _proxyTypeMapsRequiringRuntimeProcessing = []; public override ModuleDesc AssociatedModule => triggeringModule; public override void AttachToDependencyGraph(DependencyAnalyzerBase graph) { + if (IsEmpty) + return; + base.AttachToDependencyGraph(graph); foreach (var map in GetExternalTypeMaps()) { @@ -39,7 +47,12 @@ internal override IEnumerable GetExternalTypeMaps() { foreach (var map in assemblyTypeMaps.Maps) { - yield return new ReadyToRunExternalTypeMapNode(triggeringModule, map.Key, map.Value, _importReferenceProvider); + yield return new ReadyToRunExternalTypeMapNode( + triggeringModule, + map.Key, + map.Value, + _importReferenceProvider, + _externalTypeMapsRequiringRuntimeProcessing.Contains(map.Key)); } } @@ -47,7 +60,12 @@ internal override IEnumerable GetProxyTypeMaps() { foreach (var map in assemblyTypeMaps.Maps) { - yield return new ReadyToRunProxyTypeMapNode(triggeringModule, map.Key, map.Value, _importReferenceProvider); + yield return new ReadyToRunProxyTypeMapNode( + triggeringModule, + map.Key, + map.Value, + _importReferenceProvider, + _proxyTypeMapsRequiringRuntimeProcessing.Contains(map.Key)); } } @@ -58,9 +76,108 @@ public void AddToReadyToRunHeader(ReadyToRunHeaderNode header, NodeFactory nodeF if (IsEmpty) return; + PrepareTypeMapsForEncoding(nodeFactory); + header.Add(ReadyToRunSectionType.ExternalTypeMaps, new ExternalTypeMapObjectNode(this, importReferenceProvider)); header.Add(ReadyToRunSectionType.ProxyTypeMaps, new ProxyTypeMapObjectNode(this, importReferenceProvider)); header.Add(ReadyToRunSectionType.TypeMapAssemblyTargets, new TypeMapAssemblyTargetsNode(assemblyTypeMaps, importReferenceProvider)); } + + // Some types referenced by TypeMap attributes may not have an existing TypeRef/AssemblyRef relationship to the + // module that declared the attribute. Mark just the affected (TypeMapGroup, ProxyOrExternalMap) entry for + // runtime attribute processing. + private void PrepareTypeMapsForEncoding(NodeFactory nodeFactory) + { + foreach (var mapEntry in assemblyTypeMaps.Maps) + { + TypeMapMetadata.Map map = mapEntry.Value; + + // The generic TypeMap attribute TypeSpec necessarily provides an encodable metadata + // reference to its group type. + Debug.Assert(CanEncodeReferenceToType(mapEntry.Key)); + + TypeMapMetadata.IExternalTypeMap externalTypeMap = map; + if (externalTypeMap.ThrowingMethodStub is null) + { + foreach ((TypeDesc type, _) in externalTypeMap.TypeMap.Values) + { + if (!CanEncodeReferenceToType(type)) + { + _externalTypeMapsRequiringRuntimeProcessing.Add(mapEntry.Key); + break; + } + } + } + + TypeMapMetadata.IProxyTypeMap proxyTypeMap = map; + if (proxyTypeMap.ThrowingMethodStub is null) + { + foreach (KeyValuePair typeMapEntry in proxyTypeMap.TypeMap) + { + if (!CanEncodeReferenceToType(typeMapEntry.Key) || + !CanEncodeReferenceToType(typeMapEntry.Value)) + { + _proxyTypeMapsRequiringRuntimeProcessing.Add(mapEntry.Key); + break; + } + } + } + } + + bool CanEncodeReferenceToType(TypeDesc type) + { + if (nodeFactory.CompilationModuleGroup.VersionsWithTypeReference(type)) + return true; + + if (type is EcmaType ecmaType) + { + return MutableModule.CanCreateReferenceToType( + triggeringModule, + ecmaType, + (ReadyToRunCompilationModuleGroupBase)nodeFactory.CompilationModuleGroup); + } + + if (type.IsParameterizedType) + { + return CanEncodeReferenceToType(((ParameterizedType)type).ParameterType); + } + + if (type.IsFunctionPointer) + { + MethodSignature signature = ((FunctionPointerType)type).Signature; + + if (!CanEncodeReferenceToType(signature.ReturnType)) + return false; + + for (int i = 0; i < signature.Length; i++) + { + if (!CanEncodeReferenceToType(signature[i])) + return false; + } + + return true; + } + + if (type.HasInstantiation) + { + if (!CanEncodeReferenceToType(type.GetTypeDefinition())) + return false; + + foreach (TypeDesc instantiationArgument in type.Instantiation) + { + if (!CanEncodeReferenceToType(instantiationArgument)) + return false; + } + + return true; + } + + // Generic parameters (encoded as ELEMENT_TYPE_VAR/MVAR) and other simple type + // shapes that don't reference any other type by name always encode safely. + // Anything else reaching here is an unexpected TypeDesc shape for a TypeMap + // key or value; treat it as unencodable rather than assuming successful encoding. + return type.IsSignatureVariable; + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs index e81aaae9ffe80e..54d1026d27b826 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/TypeSystem/Mutable/MutableModule.cs @@ -124,15 +124,25 @@ static Dictionary ComputeTypeLookupTable(ModuleDesc module return result; } - static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, MetadataType type) + // Looks up the AssemblyRef name used by an existing TypeRef in `moduleToSearch` that + // resolves to `referencedType` (potentially through a type-forwarder). + internal static bool TryGetAssemblyReferenceNameForTypeReference( + ModuleDesc moduleToSearch, + MetadataType referencedType, + out string assemblyReferenceName) { - if (!s_assemblyNameFromTypeLookups.TryGetValue(module, out var lookupTable)) + if (!s_assemblyNameFromTypeLookups.TryGetValue(moduleToSearch, out var lookupTable)) { - lookupTable = ComputeTypeLookupTable(module); - s_assemblyNameFromTypeLookups.AddOrUpdate(module, lookupTable); + lookupTable = ComputeTypeLookupTable(moduleToSearch); + s_assemblyNameFromTypeLookups.AddOrUpdate(moduleToSearch, lookupTable); } - if (lookupTable.TryGetValue(type, out string assemblyName)) + return lookupTable.TryGetValue(referencedType, out assemblyReferenceName); + } + + static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, MetadataType type) + { + if (TryGetAssemblyReferenceNameForTypeReference(module, type, out string assemblyName)) { return assemblyName; } @@ -149,6 +159,48 @@ static string GetNameOfAssemblyRefWhichResolvesToType(ModuleDesc module, Metadat } } + internal static bool CanCreateReferenceToType(ModuleDesc sourceModule, MetadataType type, ReadyToRunCompilationModuleGroupBase compilationGroup) + { + ModuleDesc targetModule = type.Module; + if (targetModule == type.Context.SystemModule || + compilationGroup.CrossModuleInlineableModule(targetModule) || + compilationGroup.VersionsWithModule(targetModule)) + { + return true; + } + + if (sourceModule is not EcmaModule sourceEcmaModule || targetModule is not EcmaModule targetEcmaModule) + { + return false; + } + + // An existing TypeRef in the source module (potentially resolving to `type` through a + // type-forwarder) already proves a matching AssemblyRef exists. + if (ManagedBinaryEmitterForInternalUse.TryGetAssemblyReferenceNameForTypeReference(sourceEcmaModule, type, out _)) + { + return true; + } + + // Otherwise falls back to the type's defining assembly name, ensuring the + // source module has an AssemblyRef to it. + string targetAssemblyName = targetEcmaModule.Assembly.GetName().Name; + return HasAssemblyReference(sourceEcmaModule, targetAssemblyName); + } + + private static bool HasAssemblyReference(EcmaModule module, string assemblyName) + { + foreach (AssemblyReferenceHandle assemblyReferenceHandle in module.MetadataReader.AssemblyReferences) + { + AssemblyReference assemblyReference = module.MetadataReader.GetAssemblyReference(assemblyReferenceHandle); + if (module.MetadataReader.StringComparer.Equals(assemblyReference.Name, assemblyName)) + { + return true; + } + } + + return false; + } + class Cache { private List> _values = new List>(); From 6a45529909d57f44c3ec3f5575f32c86e7d06add Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:04:01 -0700 Subject: [PATCH 3/3] Check for SIG_IGN and SIG_DFL before calling previous signal handler (#132900) macOS doesn't clear sa_flags when calling execve. When dotnet is started from a process that sets a signal handler, the handler is cleared, but not the sa_flags. When the runtime gets a signal from an external source, it sees a stale SA_SIGINGO and tries to call the previous signal handler, but the pointer is set to SIG_IGN or SIG_DFL, which causes a crash. This change checks for those values before calling the previous signal handler. Also adds a regression test for the issue. Fixes https://github.com/dotnet/runtime/issues/132581 --------- Co-authored-by: Jan Kotas --- .../nativeaot/Runtime/unix/PalUnix.cpp | 35 ++++++--- src/coreclr/pal/src/exception/signal.cpp | 15 ++-- .../coreclr/GitHub_132581/CMakeLists.txt | 8 +++ .../GitHub_132581/nativetest132581.cpp | 72 +++++++++++++++++++ .../coreclr/GitHub_132581/test132581.cs | 40 +++++++++++ .../coreclr/GitHub_132581/test132581.csproj | 17 +++++ 6 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 src/tests/Regressions/coreclr/GitHub_132581/CMakeLists.txt create mode 100644 src/tests/Regressions/coreclr/GitHub_132581/nativetest132581.cpp create mode 100644 src/tests/Regressions/coreclr/GitHub_132581/test132581.cs create mode 100644 src/tests/Regressions/coreclr/GitHub_132581/test132581.csproj diff --git a/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp index 37648c15db835d..a0952e926fb570 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp @@ -1055,6 +1055,26 @@ uint16_t PalCaptureStackBackTrace(uint32_t arg1, uint32_t arg2, void* arg3, uint #ifdef FEATURE_HIJACK static struct sigaction g_previousActivationHandler; +static bool IsSaSigInfo(struct sigaction* action) +{ + return (action->sa_flags & SA_SIGINFO) != 0; +} + +static bool IsSigDfl(struct sigaction* action) +{ + // macOS can return sigaction with SIG_DFL and SA_SIGINFO. + // SA_SIGINFO means we should use sa_sigaction, but here we want to check sa_handler. + // So we ignore SA_SIGINFO when sa_sigaction and sa_handler are at the same address. + return (&action->sa_handler == (void*)&action->sa_sigaction || !IsSaSigInfo(action)) && + action->sa_handler == SIG_DFL; +} + +static bool IsSigIgn(struct sigaction* action) +{ + return (&action->sa_handler == (void*)&action->sa_sigaction || !IsSaSigInfo(action)) && + action->sa_handler == SIG_IGN; +} + static void ActivationHandler(int code, siginfo_t* siginfo, void* context) { Thread* pThread = ThreadStore::GetCurrentThreadIfAvailableAsyncSafe(); @@ -1079,15 +1099,14 @@ static void ActivationHandler(int code, siginfo_t* siginfo, void* context) } // Call the original handler when it is not ignored or default (terminate). - if (g_previousActivationHandler.sa_flags & SA_SIGINFO) - { - _ASSERTE(g_previousActivationHandler.sa_sigaction != NULL); - g_previousActivationHandler.sa_sigaction(code, siginfo, context); - } - else + if (!IsSigDfl(&g_previousActivationHandler) && !IsSigIgn(&g_previousActivationHandler)) { - if (g_previousActivationHandler.sa_handler != SIG_IGN && - g_previousActivationHandler.sa_handler != SIG_DFL) + if (IsSaSigInfo(&g_previousActivationHandler)) + { + _ASSERTE(g_previousActivationHandler.sa_sigaction != NULL); + g_previousActivationHandler.sa_sigaction(code, siginfo, context); + } + else { _ASSERTE(g_previousActivationHandler.sa_handler != NULL); g_previousActivationHandler.sa_handler(code); diff --git a/src/coreclr/pal/src/exception/signal.cpp b/src/coreclr/pal/src/exception/signal.cpp index 74fa88db04e409..ed2b72d69c6ce1 100644 --- a/src/coreclr/pal/src/exception/signal.cpp +++ b/src/coreclr/pal/src/exception/signal.cpp @@ -975,15 +975,14 @@ static void inject_activation_handler(int code, siginfo_t *siginfo, void *contex else { // Call the original handler when it is not ignored or default (terminate). - if (g_previous_activation.sa_flags & SA_SIGINFO) + if (!IsSigDfl(&g_previous_activation) && !IsSigIgn(&g_previous_activation)) { - _ASSERTE(g_previous_activation.sa_sigaction != NULL); - g_previous_activation.sa_sigaction(code, siginfo, context); - } - else - { - if (g_previous_activation.sa_handler != SIG_IGN && - g_previous_activation.sa_handler != SIG_DFL) + if (IsSaSigInfo(&g_previous_activation)) + { + _ASSERTE(g_previous_activation.sa_sigaction != NULL); + g_previous_activation.sa_sigaction(code, siginfo, context); + } + else { _ASSERTE(g_previous_activation.sa_handler != NULL); g_previous_activation.sa_handler(code); diff --git a/src/tests/Regressions/coreclr/GitHub_132581/CMakeLists.txt b/src/tests/Regressions/coreclr/GitHub_132581/CMakeLists.txt new file mode 100644 index 00000000000000..af07c1df472bc0 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_132581/CMakeLists.txt @@ -0,0 +1,8 @@ +# The test is valid only on macOS. +if (CLR_CMAKE_TARGET_OSX) + include_directories(${INC_PLATFORM_DIR}) + + add_library(nativetest132581 ${TEST_LIB_TYPE} nativetest132581.cpp) + + install(TARGETS nativetest132581 DESTINATION bin) +endif() diff --git a/src/tests/Regressions/coreclr/GitHub_132581/nativetest132581.cpp b/src/tests/Regressions/coreclr/GitHub_132581/nativetest132581.cpp new file mode 100644 index 00000000000000..5c92a316385af1 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_132581/nativetest132581.cpp @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include + +#include +#include +#include +#include +#include +#include + +static void SignalHandler(int, siginfo_t*, void*) +{ +} + +extern "C" DLL_EXPORT int InstallSignalHandlerAndExec(const char* executable, const char* managedAssembly) +{ + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_sigaction = SignalHandler; + action.sa_flags = SA_SIGINFO | SA_RESTART; + sigemptyset(&action.sa_mask); + + if (sigaction(SIGUSR1, &action, nullptr) != 0) + { + return errno; + } + + if (setenv("DOTNET_TEST_132581_CHILD", "1", 1) != 0) + { + return errno; + } + + char* arguments[4]; + int index = 0; + arguments[index++] = const_cast(executable); + if (managedAssembly[0] != '\0') + { + arguments[index++] = const_cast(managedAssembly); + } + arguments[index++] = const_cast("--child"); + arguments[index] = nullptr; + + execv(executable, arguments); + return errno; +} + +extern "C" DLL_EXPORT int SendSignalFromChildProcess() +{ + pid_t child = fork(); + if (child == -1) + { + return errno; + } + + if (child == 0) + { + int result = kill(getppid(), SIGUSR1); + _exit(result == 0 ? 0 : 1); + } + + int status; + pid_t result; + do + { + result = waitpid(child, &status, 0); + } + while ((result == -1) && (errno == EINTR)); + + return (result == child) && WIFEXITED(status) && (WEXITSTATUS(status) == 0) ? 0 : -1; +} diff --git a/src/tests/Regressions/coreclr/GitHub_132581/test132581.cs b/src/tests/Regressions/coreclr/GitHub_132581/test132581.cs new file mode 100644 index 00000000000000..e0136090f52ecd --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_132581/test132581.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using Xunit; + +public class HandleMacosSAFlagsOddityAcrossExec +{ + private const string ChildEnvironmentVariable = "DOTNET_TEST_132581_CHILD"; + + [DllImport("nativetest132581")] + private static extern int InstallSignalHandlerAndExec(string executable, string managedAssembly); + + [DllImport("nativetest132581")] + private static extern int SendSignalFromChildProcess(); + + /// + /// MacOS leaks the exec-ing process's sa_flags across execve, but clears the sa_handler and sa_sigaction function + /// pointers, which creates a mismatched sa_flags in the process that is exec-ed into that says to use sa_sigaction + /// to handle the signal (instead of sa_handler). If the runtime doesn't also check that sa_sigaction is not SIG_IGN + /// or SIG_DFL, a crash occurs. This test ensures that the cleared signal handler, sa_sigaction, is not called when + /// it is set to SIG_IGN (ignore signal) or SIG_DFL (use default handler) even if the sa_flags says to use it. + /// + [Fact] + public static void DoNotCallPreviousSignalHandlerIfHandlerIsNotSet() + { + if (Environment.GetEnvironmentVariable(ChildEnvironmentVariable) is null) + { + string executable = Environment.ProcessPath ?? throw new InvalidOperationException("The process path is unavailable."); + string command = Environment.GetCommandLineArgs()[0]; + string managedAssembly = string.Equals(executable, command, StringComparison.Ordinal) ? string.Empty : command; + + int error = InstallSignalHandlerAndExec(executable, managedAssembly); + throw new InvalidOperationException($"execv failed with error {error}."); + } + + Assert.Equal(0, SendSignalFromChildProcess()); + } +} diff --git a/src/tests/Regressions/coreclr/GitHub_132581/test132581.csproj b/src/tests/Regressions/coreclr/GitHub_132581/test132581.csproj new file mode 100644 index 00000000000000..fa6b51b1c5ee01 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_132581/test132581.csproj @@ -0,0 +1,17 @@ + + + + true + true + 1 + + + + + + + + + + +