Fix #5807 - Race condition in Invoker

This change addressed a race condition in the ObjectMethodExecutor where
the default argument values array can become visible before it is
initialized. If a second observer accesses the array while it is being
initialized, it can observe a null value for a reference type parameter,
leading to a nullref.

The fix here is to make everything immutable and initialize it all up
front. There's no reason to create an OME without eventually running it,
so there's no downside to doing the initialization up front.
This commit is contained in:
Ryan Nowak 2017-03-06 17:29:48 -08:00
parent 6f7717a381
commit 8f4ca32f48
1 changed files with 42 additions and 46 deletions

View File

@ -8,26 +8,26 @@ using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ObjectMethodExecutor
{
private object[] _parameterDefaultValues;
private ActionExecutorAsync _executorAsync;
private ActionExecutor _executor;
private readonly object[] _parameterDefaultValues;
private readonly ActionExecutorAsync _executorAsync;
private readonly ActionExecutor _executor;
private static readonly MethodInfo _convertOfTMethod =
typeof(ObjectMethodExecutor).GetRuntimeMethods().Single(methodInfo => methodInfo.Name == nameof(ObjectMethodExecutor.Convert));
private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
{
if (methodInfo == null)
{
throw new ArgumentNullException(nameof(methodInfo));
}
MethodInfo = methodInfo;
TargetTypeInfo = targetTypeInfo;
ActionParameters = methodInfo.GetParameters();
@ -35,6 +35,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal
IsMethodAsync = typeof(Task).IsAssignableFrom(MethodReturnType);
TaskGenericType = IsMethodAsync ? GetTaskInnerTypeOrNull(MethodReturnType) : null;
IsTypeAssignableFromIActionResult = typeof(IActionResult).IsAssignableFrom(TaskGenericType ?? MethodReturnType);
if (IsMethodAsync && TaskGenericType != null)
{
// For backwards compatibility we're creating a sync-executor for an async method. This was
// supported in the past even though MVC wouldn't have called it.
_executor = GetExecutor(methodInfo, targetTypeInfo);
_executorAsync = GetExecutorAsync(TaskGenericType, methodInfo, targetTypeInfo);
}
else
{
_executor = GetExecutor(methodInfo, targetTypeInfo);
}
_parameterDefaultValues = GetParameterDefaultValues(ActionParameters);
}
private delegate Task<object> ActionExecutorAsync(object target, object[] parameters);
@ -58,29 +72,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public bool IsTypeAssignableFromIActionResult { get; }
private ActionExecutorAsync TaskOfTActionExecutorAsync
{
get
{
if (_executorAsync == null)
{
_executorAsync = GetExecutorAsync(TaskGenericType, MethodInfo, TargetTypeInfo);
}
return _executorAsync;
}
}
public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo)
{
var executor = new ObjectMethodExecutor(methodInfo, targetTypeInfo);
executor._executor = GetExecutor(methodInfo, targetTypeInfo);
return executor;
}
public Task<object> ExecuteAsync(object target, object[] parameters)
{
return TaskOfTActionExecutorAsync(target, parameters);
return _executorAsync(target, parameters);
}
public object Execute(object target, object[] parameters)
@ -95,8 +95,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
throw new ArgumentOutOfRangeException(nameof(index));
}
EnsureParameterDefaultValues();
return _parameterDefaultValues[index];
}
@ -216,42 +214,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return CastToObject<T>(task);
}
private void EnsureParameterDefaultValues()
private static object[] GetParameterDefaultValues(ParameterInfo[] parameters)
{
if (_parameterDefaultValues == null)
var values = new object[parameters.Length];
for (var i = 0; i < parameters.Length; i++)
{
var count = ActionParameters.Length;
_parameterDefaultValues = new object[count];
var parameterInfo = parameters[i];
object defaultValue;
for (var i = 0; i < count; i++)
if (parameterInfo.HasDefaultValue)
{
var parameterInfo = ActionParameters[i];
object defaultValue;
defaultValue = parameterInfo.DefaultValue;
}
else
{
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (parameterInfo.HasDefaultValue)
if (defaultValueAttribute?.Value == null)
{
defaultValue = parameterInfo.DefaultValue;
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
var defaultValueAttribute = parameterInfo
.GetCustomAttribute<DefaultValueAttribute>(inherit: false);
if (defaultValueAttribute?.Value == null)
{
defaultValue = parameterInfo.ParameterType.GetTypeInfo().IsValueType
? Activator.CreateInstance(parameterInfo.ParameterType)
: null;
}
else
{
defaultValue = defaultValueAttribute.Value;
}
defaultValue = defaultValueAttribute.Value;
}
_parameterDefaultValues[i] = defaultValue;
}
values[i] = defaultValue;
}
return values;
}
}
}