runtime: AccessViolationException when retrieving enum from dependency container

When trying to run our app on .NET 5 preview4 we ran into the following issue. Consider the following simplified code:

using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace Progress.WebApp
{
    public enum TheEnum
    {
        HelloWorld = -1,
    }

    public sealed class Startup
    {
        static void Main(string[] args)
            => WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build()
                .Run();

        public void ConfigureServices(IServiceCollection services)
            => services.AddSingleton(typeof(TheEnum), TheEnum.HelloWorld);

        public void Configure(IApplicationBuilder app)
            => app.UseMiddleware<Middleware>();
    }

    public sealed class Middleware
    {
        readonly RequestDelegate next;

        public Middleware(RequestDelegate next)
        {
            this.next = next;
        }

        public async Task Invoke(HttpContext context, TheEnum theEnum)
            => await context.Response.WriteAsync(theEnum.ToString());
    }
}

Project:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>
</Project>

Running this code using dotnet run and navigating to localhost:5000 prints “HelloWorld” the first time as expected, but after hitting F5 a few times the server crashes with the following exception:

Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at DynamicClass.lambda_method1(System.Runtime.CompilerServices.Closure, System.Object, Microsoft.AspNetCore.Http.HttpContext, System.IServiceProvider)
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions+<>c__DisplayClass4_1.<UseMiddleware>b__2(Microsoft.AspNetCore.Http.HttpContext)
   at Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext)
   at Microsoft.AspNetCore.Hosting.HostingApplication.ProcessRequestAsync(Context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequest>d__218`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequest>d__218`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Server.Kestrel.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]](<ProcessRequest>d__218`1<System.__Canon> ByRef)
   at System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder.Start[[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequest>d__218`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Server.Kestrel.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]](<ProcessRequest>d__218`1<System.__Canon> ByRef)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequest[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](Microsoft.AspNetCore.Hosting.Server.IHttpApplication`1<System.__Canon>)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__217`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__217`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Server.Kestrel.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ExecutionContextCallback(System.Object)
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread, System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__217`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Server.Kestrel.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext(System.Threading.Thread)
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol+<ProcessRequests>d__217`1[[System.__Canon, System.Private.CoreLib, Version=5.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]], Microsoft.AspNetCore.Server.Kestrel.Core, Version=5.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ExecuteFromThreadPool(System.Threading.Thread)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

Changing the underlying enum value to 1 (from -1) causes the server to no longer crash, but instead a NullReferenceException is thrown during request processing (despite the first few request still working as expected):

      Connection id "0HLVSO0VBBCEI", Request id "0HLVSO0VBBCEI:00000004": An unhandled exception was thrown by the application.
System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method1(Closure , Object , HttpContext , IServiceProvider )
   at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_1.<UseMiddleware>b__2(HttpContext context)
   at Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Hosting.HostingApplication.ProcessRequestAsync(Context context)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequest[TContext](IHttpApplication`1 application)

Output dotnet --info

.NET SDK (reflecting any global.json):
 Version:   5.0.100-preview.4.20258.7
 Commit:    65f0fc2cad

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.19041
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.100-preview.4.20258.7\

Host (useful for support):
  Version: 5.0.0-preview.4.20251.6
  Commit:  47ec733ba7

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (20 by maintainers)

Commits related to this issue

Most upvoted comments

So more things I’ve learned:

  1. The reason this only happens after the 3rd invocation is because of this code:

https://github.com/dotnet/runtime/blob/b1ca1f4eb040e42045718e265775f79eea982d32/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs#L23-L28

The first 2 times a service gets retrieved, it uses reflection (RuntimeResolver.Resolve) to get the service. But once it is called a 2nd time, it calls the base.RealizeService in the background, which generates the IL and sticks it in the cache. So the next time it is retrieved, the generated IL is invoked.

  1. The reason the A/V is happening is because 0xFFFFFFFF is being returned as an object from ServiceProvider.GetService. This “object” is then trying to be cast down/unboxed to a TheEnum value. 0xFFFFFFFF is -1, which is the HelloWorld value. So what is happening is a double unboxing of the enum, causing the runtime to A/V.

  2. The reason the double unboxing is occuring is what @ericstj pointed to above - https://github.com/dotnet/extensions/commit/b248ede5665ef0ba59252731a40f697cc4ac40ad that change added an unbox call during VisitConstant. The bug that was fixing was when a constant ValueType was being passed into a constructor of a service. In this scenario, the constant ValueType is the service itself. In this scenario, we shouldn’t be unboxing the value during VisitConstant.

To fix this, I think we can simply set a bool during VisitConstructor saying that we are inConstructor. And only when we are inConstructor during VisitConstant do we do the unboxing. When we are visiting a constant at the top-level, skip adding the unboxing code.

I’ll prepare a fix for this.

I looked through as well and found that

https://github.com/dotnet/runtime/blob/b1ca1f4eb040e42045718e265775f79eea982d32/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs#L217-L236

will call it as well. Both the Ctor and the IEnumerable code paths will need the value unboxed, from what I can tell. I’ve added a test for IEnumerable as well.