dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.16k stars 4.72k forks source link

Calling a CsWinRT NAOT dll's DllCanUnloadNow export triggers CFG failure #105330

Closed jevansaks closed 2 months ago

jevansaks commented 3 months ago

Description

Using ControlFlowGuard when creating a native dll that uses CsWinRT (which generates the DllGetActivationFactory and DllCanUnloadNow native exports) and publishing that as AOT creates a dll with DllCanUnloadNow not in the icall table. This causes an external caller of DllCanUnloadNow (in my case, combase.dll since this is part of implementing the WinRT contract) to failfast with FAST_FAIL_GUARD_ICALL_CHECK_FAILURE.

Reproduction Steps

Minimal repro project with repro steps in readme:

https://github.com/jevansaks/NAOTControlFlowGuardRepro/tree/main

Expected behavior

All exports of the dll should be in the CFG table since they're designed to be called virtually.

Actual behavior

DllGetActivationFactory export is in the CFG table but DllCanUnloadNow is not.

Regression?

I don't know. Probably not a regression.

Known Workarounds

No response

Configuration

.NET 8, running on Windows 11, compiling for windows-x64 native. I didn't try any other windows native targets.

Other information

No response

jevansaks commented 3 months ago

FYI @MichalStrehovsky

dotnet-policy-service[bot] commented 3 months ago

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas See info in area-owners.md if you want to be subscribed.

jkoritzinsky commented 2 months ago

@MichalStrehovsky fixed by https://github.com/dotnet/runtime/pull/106132?

MichalStrehovsky commented 2 months ago

@MichalStrehovsky fixed by #106132?

No, that one only affects OptimizationPreference=Size and there's no observable failure mode. We still don't know what this one is. I'm going to spend time on this in the coming weeks.

MichalStrehovsky commented 2 months ago

I'll jot down what I found out:

  1. For a C program defining a DllCanUnloadNow, the output of cl.exe does not place DllCanUnloadNow in the gfids section of the object file. This is kind of expected - DllCanUnloadNow is not referenced from anywhere nor address taken.
  2. Once linker runs on the object file, after being instructed to export DllCanUnloadNow using a DEF file, linker will place the symbol in the gfids of the output DLL. The entrypoint is also marked "export suppressed" (the E letter next to the entry in the output of dumpbin /loadconfig on the output DLL).

1 also happens for native AOT, but only if StackTraceSupport=false is in the project file (like in the @jevansaks repro repository). If StackTraceSupport=false is not in the project file, the AOT compiler assumes the symbol is address exposed and places it in the gfids.

For some reason that I want not able to figure out, 2 does not happen for native AOT object files. Linker is not happy about something and won't automatically consider the symbol address exposed even though it's exported through the DEF file.

Of course, we can just work around by making sure 1 never happens and exported symbols are always considered address exposed in the compiler.

jkotas commented 2 months ago

For some reason that I want not able to figure out, 2 does not happen for native AOT object files.

You can ask linker dev to figure this out for you. This may be a symptom of some other problem that we may want to fix.

MichalStrehovsky commented 2 months ago

For some reason that I want not able to figure out, 2 does not happen for native AOT object files.

You can ask linker dev to figure this out for you. This may be a symptom of some other problem that we may want to fix.

I've sent them an email with a repro... will see.