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