Avoiding Assembly Level Declarative Security

I've written in the past about the three assembly level declarative security actions: RequestMinimum, RequestOptional, and RequestRefuse.  Although the CLR has supported these since v1.0, I tend to stay away from using them as much as I possibly can, and also recommend that others avoid them as well.  Let me go through each one individually:

RequestMinimum

RequestMinimum is the most common of the three, and in fact is mentioned by a FxCop rule and automatically inserted by the C# compiler in some cases.  There are several reasons I don't like to use this security action.

The first problem I have with RequestMinimum is a usability problem stemming from the fact that if your assembly is not granted its RequestMinimum permission set, the CLR will refuse to load it by throwing an exception.  If the assembly in question happens to be the entry point of my application, that means the user of my app ends up staring down an unhandled exception dialog (since my code never got loaded, and therefore never had a chance to handle this error more gracefully).  If my mother runs an app I give her and it throws a FileLoadException due to insufficient permissions, she's not going to have any idea what to do with that.

Even if the assembly is not the entry point assembly, gracefully handling failure to load due to RequestMinimum means that an application needs a try ... catch around every code path which could potentially lead to loading the assembly in question; that's not something that a lot of applications are setup to do.

The other problem I find with RequestMinimum is that in the case of writing a shared library it's very difficult to figure out what the correct grant set to request is.  Unless all of the APIs being exposed require the same set of permissions, having only a single RequestMinimum set doesn't help much.  For instance, an assembly like System.dll exposes hundreds of classes each with indivdual permission requirements varying from SecurityPermission/Execution to FullTrust.  If we decided to use the minimum grant set of any API in the assembly as the ReuqestMinimum, we've somewhat defeated the purpose of the security action; now I cannot be assured that the assembly loading means that it has the required permissions to work in all cases.  On the other hand, if I use the maximum grant set required, the assembly won't load even if an application was only intending to use APIs that would work in the grant set that the assembly is loaded into.

Finally, since security demands go against the entire call stack, even if the assembly in question has all the permissions that it needs to run doesn't mean that all assemblies and AppDomains on the call stack will have those permissions.  So even if an assembly does have its minimum grant set met, it doesn't have any guarantee that its APIs will all work without throwing a SecurityException.

RequestOptional

RequestOptional is probably the least commonly used assembly level security action, and with good reason.  This security action is somewhat poorly named, and people who use it for the first time often don't realize that it will actually remove permissions from your grant set.  In fact, I run across RequestOptional most commonly when helping someone debug a SecurityException that they can't figure out.  ("Why does this exception occur? All assemblies should be fully trusted!")

Even once you do understand the full effect of RequestOptional, the fact that it is confusingly named and that most people do not understand it is argument enough for me to stay away.  Code readability is extremely important, and if I can expect that the next person to read through my code will be confused by that RequestOptional I stuck on my assembly, that's not a good thing -- especially when it comes to security.

RequestRefuse

I expect this is the most controversial security action to take a stance against.  A good number of people probably use RequestRefuse as a least-privilege mechanism.  My problem with that is that you're stating "I don't want permissions A and B", however when someone comes along and introduces permission C you haven't refused it, and therefore you still get it.  Personally, I believe the CLR has other, much better, least-privilege mechanisms available -- and I prefer to use those whenever possible.  (Sounds like the topic of a future blog post :-) ).

Finally, and this should come as no surprise to any regular visitors, I'm a huge fan of the model where every sandboxed AppDomain has exactly two permission sets -- FullTrust and one sandbox grant set.  RequestRefuse (and RequestOptional) both detract from this model by creating multiple permission sets within an otherwise homogenous AppDomain.  By keeping AppDomains entirely homogenous, it's much easier to reason about security within the domain.  And, in my opinion, keeping the security model as simple as possible can only be goodness -- complexity can lead to mistakes in reasoning, which leads to security holes. 

So What Instead?

Some of the abilities promised by the assembly level declarative security actions seem to be very useful at first glance.  Notably the ability to specify a grant set that the code was tested to run in (which is what RequestMinimum seems to promise) and the ability to run with least privilege (via RequestRefuse).

For RequestMinimum, I think that ClickOnce is a far superior alternative for a variety of reasons.  Most importantly, ClickOnce gives the user of the application a nicer experience when the application does not by default meet the minimum grant set.  Rather than throwing an exception in the face of a non-developer, a friendly user interface is displayed which may even offer the ability to grant the application the ability to run.

Also, the grant set specified for a ClickOnce application applies to all of the assemblies in the application as well as the AppDomain  (hey -- it's my favorite homogenous model again!).  This means that you don't have to worry about the scenario where your assembly meets its minimum grant set, but some APIs still fail with SecurityExceptions because another assembly further up the call stack did not meet the minimum grant set.

That covers RequestMinimum -- but what about the least privilege promise of RequestRefuse?  As AB might say -- that's another show.