Code Access Security (CAS) is a large area of the .NET Framework that doesn’t often get the developer attention it deserves—at least, until it causes problems. Very recently I fielded a question on why the Entity Framework throws a SecurityException when executing a query in a Sharepoint-hosted web site run under medium trust. The answer, I thought, is definitely worth sharing.
The Repro
The failure looks very similar to the exception described on this forum post. However, the forum post describes a SecurityException thrown from an XBAP application, not an ASP.NET application. The reason for the failure, however, is the same. Take the following EF query, for example:
var customer = (from c in db.Customers
where c.CustomerID == "ALFKA"
select c).FirstOrDefault();
It’s a very simple query, which runs in medium trust without any errors. But consider the more useful use case:
public Customer GetCustomerById(string customerId)
{
using (NorthwindEntities db = new NorthwindEntities())
{
return (from c in db.Customers
where c.CustomerID == customerId
select c).FirstOrDefault();
}
}
The fundamental difference is that we’re using a parameter value to supply the criterion for our query. This throws the following exception in Sharepoint under medium trust:
System.MethodAccessException was unhandled by user code
Message="System.Runtime.CompilerServices.StrongBox`1..ctor(System.__Canon)"
Source="mscorlib"
StackTrace:
at System.Reflection.MethodBase.PerformSecurityCheck(Object obj, RuntimeMethodHandle method, IntPtr parent, UInt32 invocationFlags)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture, Object[] activationAttributes)
at System.Linq.Expressions.ExpressionCompiler.AddGlobal(Type type, Object value)
at System.Linq.Expressions.ExpressionCompiler.GenerateConstant(ILGenerator gen, Type type, Object value, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateMemberAccess(ILGenerator gen, Expression expression, MemberInfo member, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.Generate(ILGenerator gen, Expression node, StackType ask)
at System.Linq.Expressions.ExpressionCompiler.GenerateLambda(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.CompileDynamicLambda(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.Compile(LambdaExpression lambda)
at System.Linq.Expressions.ExpressionCompiler.Compile[D](Expression`1 lambda)
at System.Linq.EnumerableExecutor`1.Execute()
at System.Linq.EnumerableExecutor`1.ExecuteBoxed()
at System.Data.Objects.ELinq.ClosureBinding.ParameterBinding.EvaluateBinding()
at System.Data.Objects.ELinq.ClosureBinding.TryCreateClosureBinding(Expression expression, ClrPerspective perspective, Boolean allowLambda, HashSet`1 closureCandidates, ClosureBinding& binding, TypeUsage& typeUsage)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.EqualsTranslator.TypedTranslate(ExpressionConverter parent, BinaryExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateLambda(LambdaExpression lambda, DbExpression input)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call, DbExpression& source, DbExpressionBinding& sourceBinding, DbExpression& lambda)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.OneLambdaTranslator.Translate(ExpressionConverter parent, MethodCallExpression call)
at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.TypedTranslate(ExpressionConverter parent, MethodCallExpression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.TranslateExpression(Expression linq)
at System.Data.Objects.ELinq.ExpressionConverter.Convert()
at System.Data.Objects.ELinq.ELinqQueryState.GetExecutionPlan(Nullable`1 forMergeOption)
at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
…
InnerException: System.Security.SecurityException
Message="Request for the permission of type ‘System.Security.Permissions.ReflectionPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′ failed."
Source="mscorlib"
StackTrace:
at System.Security.CodeAccessSecurityEngine.ThrowSecurityException(Assembly asm, PermissionSet granted, PermissionSet refused, RuntimeMethodHandle rmh, SecurityAction action, Object demand, IPermission permThatFailed)
at System.Security.CodeAccessSecurityEngine.CheckSetHelper(PermissionSet grants, PermissionSet refused, PermissionSet demands, RuntimeMethodHandle rmh, Object assemblyOrString, SecurityAction action, Boolean throwException)
at System.Security.PermissionSetTriple.CheckSetDemand(PermissionSet demandSet, PermissionSet& alteredDemandset, RuntimeMethodHandle rmh)
at System.Security.PermissionListSet.CheckSetDemand(PermissionSet pset, RuntimeMethodHandle rmh)
at System.Security.PermissionListSet.DemandFlagsOrGrantSet(Int32 flags, PermissionSet grantSet)
at System.Security.CodeAccessSecurityEngine.ReflectionTargetDemandHelper(Int32 permission, PermissionSet targetGrant, CompressedStack securityContext)
at System.Security.CodeAccessSecurityEngine.ReflectionTargetDemandHelper(Int32 permission, PermissionSet targetGrant)
InnerException:
A Closer Look
The bolded text clues us into the real problem—a demand for ReflectionPermission failed. But we’re not doing reflection…or are we?
In order for our C# code to access the value of the customerId parameter, the compiler creates a new private nested class with a single public field for the customerId. When creating the expression tree to represent the query above, the compiler inserts code to instantiate this new class and adds that instance as a node in the tree. When the Entity Framework later processes the expression tree, it attempts to create an instance of that private class. (See above—RuntimeConstructorInfo.Invoke)
So what does permissions does this call to RuntimeConstructorInfo.Invoke require? In .NET 3.5, the demand is satisfied when all assemblies in the stack trace have one of the following sets of permissions:
- ReflectionPermission with ReflectionPermissionFlag.MemberAccess
- ReflectionPermission with ReflectionPermissionFlag.RestrictedMemberAccess UNION PermissionSet of the assembly which contains the member being reflected.
In .NET 2.0, ASP.NET medium trust did not allow reflection on non-public members. When Microsoft released .NET 3.5, the ReflectionPermission featured a new option: RestrictedMemberAccess (RMA). The concept is a bit tricky to understand the first time, so here’s an example of what RMA really means.
Take two assemblies, A and B. Assembly A is loaded in medium trust, and Assembly B is loaded in medium trust. Because both assemblies have the same trust level and because medium trust grants RMA, Assembly A can use reflection to discover non-public members in Assembly B. If a new assembly, Assembly C, is loaded in full trust, then Assembly A cannot use reflection to discover non-public members in Assembly C. If Assembly A is granted ReflectionPermission with MemberAccess, then it can use reflection on any assembly to discover any non-public member. Shawn Farkas discusses RMA in more detail here.
Assuming all this makes sense, let’s return to the problem. Our assembly is loaded in medium trust, and the code tries to reflect into the same assembly to find this non-public type generated by the compiler. However, the demand for ReflectionPermission still fails. This doesn’t make sense; the assembly’s permission set is equal to itself, and medium trust grants RMA…
The Solution
The key here is that medium trust in Sharepoint is not what we expect. The web.config file for our application points us in the right direction:
<trust level="WSS_Medium" originUrl="" />
The WSS_Medium trust level is not the same as the medium trust level offered by ASP.NET. On further investigation in the same web.config file, we see that WSS_Medium is defined in an external policy file:
<trustLevel
name="WSS_Medium"
policyFile="C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions12configwss_mediumtrust.config" />
If you open the policy file, you’ll see this scavenger hunt leads us to one conclusion: Sharepoint medium trust does not include ReflectionPermission with RestrictedMemberAccess. To add this permission, you’ll need to make two changes to the policy file. First, add the following SecurityClass element to the configuration/mscorlib/security/policy/PolicyLevel/SecurityClasses element.
<SecurityClass
Name="ReflectionPermission"
Description="System.Security.Permissions.ReflectionPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
Second, add the RMA permission to the configuration/mscorlib/security/policy/PolicyLevel/NamedPermissionSets/PermissionSet[@Name='SPRestricted'] element. It should be the only one with child elements.
<IPermission
class="ReflectionPermission"
version="1"
Flags="RestrictedMemberAccess"/>
Once the application pool is recycled, the CLR will grant you the RMA permission, and you will be able to run LINQ to SQL and EF queries like those shown above without any fear of SecurityExceptions.
Very well explained and really helped me out when I was trying to use LINQ for querying and binding to a SPList, thanks for the great article.
Excelent article, simple and really clear, With all this Sharepoint Development Madness