My Attribute Disappears


The GetCustomAttributes scenario (ICustomAttributeProvider.GetCustomAttributes
or
Attribute.GetCustomAttributes
, referred to as GetCA in this post) involves
3 pieces:

  • a custom attribute type
  • an entity which is decorated with the custom attribute
  • a code snippet calling GetCA on the decorated entity.

These pieces could be residing together in one assembly; or separately in 3 different
assemblies. The following C# code shows each piece in separate files, and I will
compile them into 3 assemblies: the attribute type assembly (attribute.dll), the
decorated entity assembly (decorated.dll) and the querying assembly (getca.exe):

// file: attribute.cs 
public class MyAttribute
: System.Attribute { }
// file: decorated.cs
[My]
public class MyClass
{ }
// file: getca.cs

using System;

using System.Reflection;

class Demo {

  static void Main(string[]
args) {
    Assembly asm = Assembly.LoadFrom(args[0]);
    object[] attrs = asm.GetType(“MyClass”).GetCustomAttributes( class="cskeyword">true);
    Console.WriteLine(attrs.Length);
  }
}
D:\> sn.exe -k sn.key
D:\> csc /t:library /keyfile:sn.key attribute.cs
D:\> csc /t:library /r:attribute.dll decorated.cs
D:\> gacutil -i attribute.dll
D:\> del attribute.dll
D:\> csc getca.cs

D:\> getca.exe decorated.dll
1
D:\> getca.exe \\machine\d$\decorated.dll
0

attribute.dll is installed in GAC (no local copy, to avoid confusion); getca.exe
checks whether the loaded type MyClass has MyAttribute. As you see from the output,
MyAttribute disappeared when the decorated entity was loaded from a share (or as
a partially trusted assembly, to be precise).

GetCA is supposed to return an array of attribute objects. In order to do so, it
parses the custom attribute metadata, finds the right custom attribute constructor,
and then invokes that .ctor with some parameters (if any). It is a late-bound call,
reflection decides whether the querying assembly should invoke the attribute .ctor,
or avoid calling it for security reasons.

Let me quote something from
ShawnFa
‘s security blog: “by default, strongly named, fully trusted assemblies
are given an implicit LinkDemand for FullTrust on every public and protected method
of every publicly visible class”. This means, in a scenario where a library is strongly
named and fully trusted, partial trusted assemblies are unable to call into such
library.

The GetCA scenario is not exactly the same, but similar. The .ctor to be invoked
is in attribute.dll (in GAC, strongly named and fully trusted). The querying assembly
(runs locally, fully trusted too) is the code that makes the invocation (if that
were to happen). But to make this .ctor invocation, we need pass in the parameters,
which are provided by the decorated entity assembly. GetCA will take the decorated
entity as the caller to the attribute type constructor
. Based on what I
just quoted, if the decorated entity assembly is partially trusted, we will filter
out such attribute object creation, unless the attribute assembly is decorated with

AllowPartiallyTrustedCallersAttribute
. Note please read
Shawn’s blog entry
carefully about this attribute and its’ security
implications before taking this approach.

What if the attribute and decorated entity are in the same assembly? In this case,
it does not matter whether the assembly is loaded from a share or locally. GetCA
will try to create and return the attribute object. If the loaded assembly is partially
trusted, the runtime gives it a smaller set of permissions and running the .ctor
code is not going to do something terrible.

To close, GetCA will try to create the custom attribute object if any of the following
3 conditions is true:

  • the decorated entity and the custom attribute type are in one assembly,
  • the decorated entity is fully trusted,
  • the assembly which defines the custom attribute type is decorated with APTCA.

By the way, the new class
CustomAttributeData
in .NET 2.0 is designed to access custom attribute in
the reflection-only context, where no code will be executed (only metadata checking).
If we use CustomAttributeData.GetCustomAttributes instead in the above example,
it prints 1; one CustomAttributeData object, not one MyAttribute object.

Comments (3)

  1. Haibo just posted an excellent article about what happens when you use reflection to get a custom attribute…

  2. Garry says:

    Nice article Haibo, I have been looking for this information in all the places.

  3. Richard Urwin says:

    Hi Haibo,

    I have an ASP.NET 2 application which uses third-party (DevExpress) assemblies which are not marked with the APTCA attribute (although this may be a red herring.) I’m attempting to deploy to an ISP which is set to run as Medium Trust, plus a few extra bits and pieces (reflection being one.)

    I’m receiving the following security exception, (which incidentally I think is to do with the DevExpress grid control attempting to serialize it’s view state since it doesn’t happen if I turn enable ViewState off):

    Server Error in ‘/Appraisal360’ Application.

    Security Exception

    Description: The application attempted to perform an operation not allowed by the security policy.  To grant this application the required permission please contact your system administrator or change the application’s trust level in the configuration file.

    Exception Details: System.Security.SecurityException: Request failed.

    Source Error:

    An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

    Stack Trace:

    [SecurityException: Request failed.]

      System.Reflection.CustomAttribute._CreateCaObject(Void* pModule, Void* pCtor, Byte** ppBlob, Byte* pEndBlob, Int32* pcNamedArgs) +0

      System.Reflection.CustomAttribute.CreateCaObject(Module module, RuntimeMethodHandle ctor, IntPtr& blob, IntPtr blobEnd, Int32& namedArgs) +104

      System.Reflection.CustomAttribute.GetCustomAttributes(Module decoratedModule, Int32 decoratedMetadataToken, Int32 pcaCount, RuntimeType attributeFilterType, Boolean mustBeInheritable, IList derivedAttributes) +482

      System.Reflection.CustomAttribute.GetCustomAttributes(RuntimeType type, RuntimeType caType, Boolean inherit) +258

      System.RuntimeType.GetCustomAttributes(Type attributeType, Boolean inherit) +63

      System.ComponentModel.ReflectTypeDescriptionProvider.ReflectGetAttributes(Type type) +262

      System.ComponentModel.ReflectedTypeData.GetAttributes() +36

      System.ComponentModel.DefaultTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetAttributes() +50

      System.ComponentModel.TypeDescriptor.GetAttributes(Type componentType) +26

      System.ComponentModel.ReflectedTypeData.GetConverter(Object instance) +274

      System.ComponentModel.DefaultTypeDescriptor.System.ComponentModel.ICustomTypeDescriptor.GetConverter() +55

      System.ComponentModel.TypeDescriptor.GetConverter(Type type) +17

      System.Web.UI.ObjectStateFormatter.SerializeValue(SerializerBinaryWriter writer, Object value) +2261

    Have you any idea what causes it and if there is any way of getting around it (I don’t expect you to know about DevExpress of course!) I’m guessing that the only way to do it is to have my assemblies granted Full Trust, or have them installed in the GAC, neither of which my ISP is likely to do.

    Any suggestions?

    Cheers,

    Rich