Skip to content

Two CLR-based apps cannot coexist on Apple platforms #128867

@MichalStrehovsky

Description

@MichalStrehovsky

The duplicate is the Apple crypto native library ending up in the same process twice — once dynamically (the running .NET host) and once statically baked into our AOT dylib.

  1. Host already has it (dynamically): the normal dotnet host loads the shared framework, which includes libSystem.Security.Cryptography.Native.Apple.dylib. That dylib contains pal_swiftbindings.o, whose Swift interop registers ObjC classes (HashBox, X25519KeyBox) with the process-wide ObjC runtime at load time.

  2. NativeAOT bakes a second copy in (statically): in Microsoft.NETCore.Native.Unix.targets (ILCompiler 11.0.0-preview.5.26261.101), line ~150:

    <NetCoreAppNativeLibrary Include="System.Security.Cryptography.Native.Apple"
                             Condition="'$(_IsApplePlatform)' == 'true'" />

    The only condition is "is this an Apple platform" — there is no '$(NativeLib)' != 'Shared' guard. So our NativeLib=Shared output (libdotnet-aot.dylib) statically links the .a version of the same crypto library, including the same pal_swiftbindings.o and its HashBox/X25519KeyBox classes.

  3. ObjC class names are process-global → collision: our dylib is loaded into the host process that already registered those classes from the framework copy. The dynamic loader then prints to stderr:

    objc[...]: Class HashBox is implemented in both .../libSystem.Security.Cryptography.Native.Apple.dylib and .../libdotnet-aot.dylib. One of the two will be used. Which one is undefined.

    That stderr line is what trips the StdErr.Should().BeEmpty() assertions in our tests.

Root flaw: the NativeAOT targets assume the output is a self-contained app that needs crypto statically baked in. For a NativeLib=Shared library loaded into an already-running .NET process, that static copy is redundant and collides with the framework's dynamic copy. The proper upstream fix looks like guarding line ~150 (and the matching DirectPInvoke) with '$(NativeLib)' != 'Shared' so shared-library AOT outputs don't re-embed framework-provided native libs.

Our RemoveAppleCryptoFromNativeAotLink target is essentially doing that guard manually from the csproj after the fact. Does guarding these NetCoreAppNativeLibrary/DirectPInvoke items on NativeLib != Shared sound like the right runtime-side fix, or is there a preferred way to handle native-lib linking for shared AOT outputs that load into an existing runtime?

Originally posted by @NikolaMilosavljevic in dotnet/sdk#54384 (comment)

Metadata

Metadata

Assignees

Type

No type
No fields configured for issues without a type.

Projects

Status
No status

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions