Fody/Costura

AppContext switches initialization can _still_ be run before the `AssemblyLoader.Attach()` code is run

0xced opened this issue · 3 comments

0xced commented

Even with #638 and #642, now included in Costura 5.1.0, I'm still experiencing the symptoms of AppContext switches initialized with the wrong target framework. The exact same binary run on two different machines would produce different results (using <Costura CreateTemporaryAssemblies="true" />).

So in order to diagnose this issue, I installed dnSpy on the machine producing weird results. I set two breakpoints: one on AssemblyLoader.Attach and the other on AppContextDefaultValues.PopulateDefaultValues. To my surprise, the PopulateDefaultValues breakpoint was hit first! So I looked at the stack trace and here is what I saw:

mscorlib.dll!System.AppContextDefaultValues.PopulateDefaultValues() (IL=0x0000, Native=0x065B3978+0x1F)
mscorlib.dll!System.AppContext.InitializeDefaultSwitchValues() (IL=0x001E, Native=0x065B38B0+0x69)
mscorlib.dll!System.AppContext.TryGetSwitch(string switchName, out bool isEnabled) (IL=0x0039, Native=0x065B1808+0x111)
mscorlib.dll!System.AppContextSwitches.AppContextSwitches() (IL≈0x0000, Native=0x065B13B0+0x2B)
mscorlib.dll!System.AppContextSwitches.BlockLongPaths.get() (IL=prolog, Native=0x065B0F30+0x1A)
mscorlib.dll!System.IO.Path.NormalizePath(string path, bool fullCheck) (IL≈0x0000, Native=0x065B0E88+0x3B)
mscorlib.dll!System.IO.Path.GetFullPathInternal(string path) (IL≈0x000E, Native=0x065B0DF8+0x6E)
mscorlib.dll!System.Reflection.RuntimeAssembly.VerifyCodeBase(string codebase) (IL≈0x0080, Native=0x065B0C08+0x153)
mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, System.IntPtr pPrivHostBinder, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks) (IL≈0x005D, Native=0x065B0040+0x177)
mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, System.Reflection.RuntimeAssembly reqAssembly, ref System.Threading.StackCrawlMark stackMark, bool throwOnFileNotFound, bool forIntrospection, bool suppressSecurityChecks) (IL≈0x0000, Native=0x0232F750+0x3F)
mscorlib.dll!System.Reflection.RuntimeAssembly.InternalLoadFrom(string assemblyFile, System.Security.Policy.Evidence securityEvidence, byte[] hashValue, System.Configuration.Assemblies.AssemblyHashAlgorithm hashAlgorithm, bool forIntrospection, bool suppressSecurityChecks, ref System.Threading.StackCrawlMark stackMark) (IL≈0x0042, Native=0x0232F3B8+0x11E)
mscorlib.dll!System.Reflection.Assembly.LoadFrom(string assemblyFile, System.Security.Policy.Evidence securityEvidence) (IL≈0x0002, Native=0x0232F1F8+0x41)
mscorlib.dll!System.Activator.CreateInstanceFromInternal(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes, System.Security.Policy.Evidence securityInfo) (IL≈0x0000, Native=0x0232E908+0x48)
mscorlib.dll!System.Activator.CreateInstanceFrom(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder binder, object[] args, System.Globalization.CultureInfo culture, object[] activationAttributes) (IL≈0x0000, Native=0x0232E818+0x41)
mscorlib.dll!System.Activator.CreateInstanceFrom(string assemblyFile, string typeName, object[] activationAttributes) (IL≈0x0000, Native=0x0232E7C0+0x3B)
mscorlib.dll!System.Activator.CreateInstanceFrom(string assemblyFile, string typeName) (IL≈0x0000, Native=0x0232E708+0x2D)
mscorlib.dll!System.AppDomain.CreateInstanceFrom(string assemblyFile, string typeName) (IL≈0x0009, Native=0x0232E2A8+0x52)

The values for assemblyFile and typeName at the bottom of the stack trace are respectively C:\Windows\SysNative\CrowdStrike.Sensor.ScriptControl12806.dll and CrowdStrike.Sensor.ScriptControl.DllMain. What is CrowdStrike you ask? Well, it's a security product!

I'm not sure exactly how all of this works but it turns out that using LoadAtModuleInit="false" and manually calling CosturaUtility.Initialize() in the program static constructor works around this issue and the AppContext switches are properly initialized.

There's not much to do about this issue, I just wrote it to document my findings.

This is probably because multiple products are trying to hook up on the assembly constructor (e.g. we know about LoadAssembliesOnStartup, Costura, etc). There might be others (including CrowdStrike). This is exactly the reason why the manual loading was introduced.

I think you applied the correct solution here.

@0xced , do you agree we can close this ticket?

0xced commented

Yes, it can be closed.