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(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.