Transparency as Least Privilege

In my last post I mentioned that there is a better alternative to RequestRefuse for achieving least privilege. The tool I like to use for least privilege is actually the security transparency model available in v2.0+ of the CLR (and which became the basis of the Silverlight security model).

On the desktop CLR, transparent code cannot elevate the privileges of the call stack in any way.  Let's take a quick look at how this is enforced:

Uverifiable code - if a transparent method contains unverifiable code, the CLR injects a demand for SecurityPermission/UnmanagedCode.

Satisfy a LinkDemand - LinkDemands satisfied by a transparent method are converted into full demands

Use the SuppressUnmanagedCodeAttribute - this is really an extension of the LinkDemand rule.  the SuppressUnmanagedCodeAttribute converts the full demand for SecurityPermission/UnmanagedCode into a LinkDemand for the same permission.  However, since link demands satisfied by transparent code are converted into full demands, the net effect is that SuppressUnmanagedCodeAttribute becomes a no-op.

Assert permissions - Asserts are an explicit attempt to elevate the permissions of the call stack, and are therefore disallowed in transparent code.  They are not converted into full demands

Call critical code without going through a TreatAsSafe layer - the treat as safe layer is responsible for validating inputs and outputs of critical code, and transparent code must access critical code via this layer.  Attempts to call critical code or access critical data directly are disallowed.

This effectively means that fully trusted transparent code runs "as caller".  It won't cause any security demands to fail, however it won't cause them to succeed either. If the caller of the transparent code is allowed to do the security operation it will succeed, otherwise it fails.

What's interesting is that if we take this principal to the extreme, and have the entire call stack be transparent (such as in Silverlight), transparency transitions from running "as caller" to running "as application".  This is because each level in the transparency call chain passes the security demand up to its caller, until we hit the top of the stack -- and find the AppDomain.  Since all security demands end up going against the AppDomain's permission set, that permission set alone controls what may happen within the domain.

In this way, my fully trusted code will be fully trusted if it is run within a FullTrust domain.  However, that same code run within an AppDomain with the Internet permission set (perhaps if it's being used by a control in the browser), will effectively be running with Internet permissions.  Any demand for a permission outside of the Internet permission set will be passed along until it hits the AppDomain and fail.

Let's say that you're writing an application that you know will never need to interact with native code.  You'd normally do a RequestRefuse for UnmanagedCode permission on your application's assemblies to ensure that it's never accidentally satisfying an UnmanagedCode demand.  Instead of doing that, you can make sure that your application assemblies are entirely transparent and that they are run in a domain without UnmanagedCode permission -- perhaps via ClickOnce deployment.

This way you can use ClickOnce to explicitly control the grant set of the applicaiton, and transparency to ensure that you're not accidentally causing an elevation outside of that call stack, even if some of your application is installed in the GAC and fully trusted.