Whidbey's New SecurityException

One of the more difficult things to debug with .NET 1.0 and 1.1 is the security exception.  With these frameworks generally the only information that you got was the state of the failed permission.  Due to the complexity of debugging security problems, most people just gave up and required that their code run in a fully trusted environment.  With Whidbey we've done a lot of work to beef up the security exception, and make debugging security issues even easier, in the process making it easier for developers to figure out the minimum set of permissions that their application needs to run under.

Limitations of the Current Implementation

Currently, the SecurityException contains four security specific properties.

  • GrantedSet provides the set of permissions that are granted to the assembly that failed.  This property does not get filled out every time an exception is thrown however.
  • PermissionState has the permission or permission set that caused the exception to be thrown
  • PermissionType is also not always filled out, when it is, it should contain if PermissionState is a permission, permission set, or permission set collection
  • RefusedSet contains the set of permissions that were refused by by the assembly that caused the security exception to be thrown

Generally, PermissionState is the only property that can be depended upon to always have a value.  This does not provide for a good user experience.

The Whidbey SecurityException

The Whidbey SecurityException adds several more properties, which together allow developers to figure out what code was causing a security problem.  The new properties are:

Name Type Description
Action SecurityAction the SecurityAction that failed the security check
Demanded object the permission, permission set, or permission sets that were demanded and triggered the exception
DenySetInstance object if a Deny stack frame caused the security exception to fail, then this property will contain that set, otherwise it will be null.
FailedAssemblyInfo AssemblyName AssemblyName of the assembly that caused the security check to fail
FirstPermissionThatFailed IPermission the first permission in failing PermissionSet (or PermissionSetCollection) that did not pass the security check
Method MethodInfo the method that the failed assembly was in when it encountered the security check that triggered the exception.  If a PermitOnly or Deny stack frame failed, this will contain the method that put the PermitOnly or Deny frame on the stack.
PermitOnlySetInstance object if the stack frame that caused the security exception had a PermitOnly permission set, this property will contain it, otherwise it will be null 
Url string URL of the assembly that failed the security check
Zone SecurityZone Zone of the assembly that failed the security check

As you can see, there's a ton more data available about the cause of the security exception in Whidbey.  However, as I noted above, even the current SecurityException properties don't get filled out properly all the time.  As part of the SecurityException enhancement work, we've gone through all the places in the BCL that throw exceptions and ensured that they've filled out as much of the SecurityException data as possible. 

So What Does a New Security Exception Look Like?

All of these extra properties provide a lot of information for developers to work with if they catch the SecurityException in their code.  But what happens if the exception goes unhandled?  The default behavior of spewing exception information to the screen will still provide a lot more information than was provided before.  For instance, if I tried to run my ProtectedData sample off a network share, with default policy in place, I'd end up with a SecurityException.  The debug spew for that exception would look similar to the following.

First comes the standard stack trace and exception type that failed.

Unhandled Exception: System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.DataProtectionPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.
at System.Security.CodeAccessSecurityEngine.Check(PermissionToken permToken,CodeAccessPermission demand, StackCrawlMark& stackMark, Int32 checkFrames, Int32 unrestrictedOverride)
at System.Security.CodeAccessSecurityEngine.Check(CodeAccessPermission cap, StackCrawlMark& stackMark)
at System.Security.CodeAccessPermission.Demand()
at System.Security.Cryptography.ProtectedData.Protect(Byte[] userData, Byte[] optionalEntropy, DataProtectionScope scope)
at CPD.Main() in \\shawnfa-build\c$\blog\ManagedDPAPI\CryptProtectData.cs:line 12

This is followed up by the SecurityAction that failed, the first failed permission, and the details of the demanded permissions. (From now on, I'm going to abbreviate the full assembly names.)

The action that failed was:
Demand
The type of the first permission that failed was:
System.Security.Permissions.DataProtectionPermission
The first permission that failed was:
<IPermission class="System.Security.Permissions.DataProtectionPermission, mscorlib ... "
version="1"
Flags="ProtectData"/>

The demand was for:
<IPermission class="System.Security.Permissions.DataProtectionPermission, mscorlib ..."
version="1"
Flags="ProtectData"/>

Next is a list of all the permissions granted to the assembly that failed the demand:

The granted set of the failing assembly was:
<PermissionSet class="System.Security.PermissionSet"
version="1">
<IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib ..."
version="1"
Read="USERNAME"/>
<IPermission class="System.Security.Permissions.FileDialogPermission, mscorlib ..."
version="1"
Unrestricted="true"/>
<IPermission class="System.Security.Permissions.FileIOPermission, mscorlib ..."
version="1"
Read="\\SHAWNFA-BUILD\C$\blog\ManagedDPAPI\"
PathDiscovery="\\SHAWNFA-BUILD\C$\blog\ManagedDPAPI\"/>
<IPermission class="System.Security.Permissions.IsolatedStorageFilePermission, mscorlib ..."
version="1"
Allowed="AssemblyIsolationByUser"
UserQuota="9223372036854775807"
Expiry="9223372036854775807"
Permanent="True"/>
<IPermission class="System.Security.Permissions.ReflectionPermission, mscorlib ..."
version="1"
Flags="ReflectionEmit"/>
<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, ..."
version="1"
Flags="Assertion, Execution, BindingRedirects"/>
<IPermission class="System.Security.Permissions.UIPermission, mscorlib, ..."
version="1"
Unrestricted="true"/>
<IPermission class="System.Security.Permissions.UrlIdentityPermission, mscorlib, ..."
version="1"
Url="file://shawnfa-build/c$/blog/ManagedDPAPI/CryptProtectData.exe"/>
<IPermission class="System.Security.Permissions.ZoneIdentityPermission, mscorlib, ..."
version="1"
Zone="Intranet"/>
<IPermission class="System.Net.DnsPermission, System ..."
version="1"
Unrestricted="true"/>
<IPermission class="System.Windows.Forms.WebBrowserPermission, System ..."
version="1"
Level="Restricted"/>
<IPermission class="System.Drawing.Printing.PrintingPermission, System.Drawing ..."
version="1"
Level="DefaultPrinting"/>
</PermissionSet>

Finally, the assembly that failed the permission demand is listed along with the method that called into the demanding code, and the Zone and URL evidence for that assembly.

The assembly that failed was:
CryptProtectData, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
The method that caused the failure was:
Void Main()
The Zone of the assembly that failed was:
Intranet
The Url of the assembly that failed was:
file://shawnfa-build/c$/blog/ManagedDPAPI/CryptProtectData.exe

Visual Studio Gets In On The Action

In addition to all the work done in the CLR to make debugging security exceptions easier, Visual Studio 2005 has done quite a bit of work as well.  When an unhandled SecurityException is thrown under the VS debugger, Visual Studio will look at all of the extra properties that are populated on that exception, and provide you with context sensitive help to solve the problem.

Visual Studio 2005 Security Exception Handler

As you can see in the screen shot, VS will highlight the line that caused the SecurityException, and shows a pop up dialog that tells you the permission that failed and provides several possible solutions for the problem.  In this case, there's not a lot of ways to get around the DataProtectionPermission demand, so the options that VS presents include turning this into a ClickOnce application or installing locally.  This help is based around the failing permission, so if a FileIOPermission failed, VS might suggest using IsolatedStorage instead.

The View Details link will, oddly enough, open up the View Detail dialog where you can inspect each property of the Security Exception yourself, in order to help you further track down security issues.

Debug-In-Zone

All that's fine and good, but when you develop on your local machine, you end up with FullTrust by default.  Forcing developers to modify their security policy to debug is not a good user experience, so again Visual Studio steps up to the plate, this time with a feature called Debug In Zone.

Debug In Zone Settings

By going to your project properties, and accessing the security tab, you can specify the set of permissions that you'd like to debug your application with.  For instance, if your goal is to have your application run with Internet permissions, then just select Internet from the drop down list.  You can also create a custom permission set that reduces down to the exact set of permissions that your app needs; the Calculate Permissions button, which will interface with PermCalc will help you with this goal.

With the changes to the SecurityException class and enhancements to the Visual Studio debugger, figuring out the cause of those hard to diagnose SecurityExceptions.