Nicole Calinoiu
November 2005
I'd been hoping that the details of the SQLCLR CAS permission sets might make it into the SQL Server Books Online or other relevant documentation by the RTM timeframe. Unfortunately, I can't seem to find anything that even begins to resemble a listing of the permissions, never mind coverage of some of the pickier details of their assessment and consequences. I'd already started trying to investigate some of this on my own during the beta and, after spending a bit more time with the RTM build (i.e.: pretty much wasting a perfectly good Saturday), here's what I think I've discovered so far...
Unsurprisingly, the UNSAFE permission set is the unrestricted permission set, or "full trust". The SAFE and EXTERNAL_ACCESS permission sets are restricted permission grants, which are loosely described in the Books Online topic Designing Assemblies. These two include a mix of identity and "regular" CAS permissions, all of which are based on permission types available in the mscorlib assembly. The story around identity permission assignment is potentially a bit more complex than in the stand-alone CLR, and I'll come back to that later. In the meantime, here are the the details of the non-identity permissions as dumped from the RTM build...
In case you hadn't already noticed the links above, you can grab the the XML definition of the SAFE permission set from here. If you're not familiar with "permission-ese", the following table might make for an easier read.
| Permission | Details |
|---|---|
| SecurityPermission | Execution |
| SqlClientPermission |
Only the context connection may be used. Besides "context connection=true"
or "context connection=yes", the connection string may only specify a
value for the "Type System Version" connection property.
Blank passwords are not allowed (AllowBlankPassword = false). |
The AllowBlankPassword = false restriction on the SqlClientPermission grant appears to have no effect. Since only the context connection can be used, and the Password property can't be specified in the connection string, the only situation in which a blank password might still apply is when the context connection was established using a SQL Server login account with a blank password. However, my tests indicate that use of a context connection with a blank password is actually allowed under the SAFE permission set, so this permission restriction appears to have no practical effect. I have no idea whether this is a bug or if the limitation was simply applied to the permission grant for reasons of defense-in-depth.
The XML definition of the EXTERNAL_ACCESS permission set is available here, and the English version is...
| Permission | Details |
|---|---|
| SecurityPermission | Execution, Assertion, ControlPrincipal, and SerializationFormatter |
| SqlClientPermission | Unrestricted |
| EnvironmentPermission | Unrestricted |
| FileIOPermission | Unrestricted |
| RegistryPermission | Read access to HKEY_CLASSES_ROOT, HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, HKEY_CURRENT_CONFIG, and HKEY_USERS |
| KeyContainerPermission | Unrestricted |
| EventLogPermission | Unrestricted access granted to all event logs on the local machine. |
| DnsPermission | Unrestricted |
| SocketPermission | Any outbound connections are permitted, but no inbound connections may be accepted. |
| WebPermission | Any outbound connections are permitted, but no inbound connections may be accepted. |
| SmtpPermission | An outbound connection to an SMTP host on the default port (port 25) is permitted. |
| NetworkInformationPermission | Pinging is permitted, but network interfaces may not be queried. |
| DistributedTransactionPermission | Unrestricted |
| StorePermission | Unrestricted |
The inclusion of SecurityPermission\Assertion gives code at the EXTERNAL_ACCESS level the right to assert its elevated permissions on behalf of less privileged code (usually code running at the SAFE level). While this might be useful when developing assemblies that access external resources, I tend to see assertion as a potentially very risky activity, and I'm a bit surprised that it was included at the EXTERNAL_ACCESS level instead of just at the UNSAFE level. At any rate, it's there now, and DBAs should be aware that by granting EXTERNAL_ACCESS, they're potentially allowing code at the SAFE level to access external resources in ways they might not have anticipated.
I'm guessing that the SecurityPermission\ControlPrincipal grant was probably included in order to enable code at the EXTERNAL_ACCESS level to impersonate the SQL session user when attempting to access external resources (assuming, of course, that Windows authentication was used when establishing the connection). However, such impersonation can actually be accomplished without this permission, leaving me wondering why it was actually included.
Code with SecurityPermission\ControlPrincipal is able to change the .NET-specific thread principal, which has nothing to do with access to external resources. However, the permission can be exploited to control user rights in some applications, which means that code with permission to control the principal may be able to elevate user privileges in such applications, and DBAs may need to consider this before granting EXTERNAL_ACCESS.
It's also worth noting that, contrary to at least some of the official documentation (e.g.: http://msdn2.microsoft.com/en-us/library/ms131108), even code at the SAFE level can impersonate the session user. This could lead to some potential security holes if such code can call into another assembly that asserts permissions for external actions that it does not have the right to perform on its own. Code running at either the EXTERNAL_ACCESS or UNSAFE level that accesses external resources after asserting the permissions required to do so should probably verify that it is using the expected Windows user context. Otherwise, less privileged invoking code may successfully initiate impersonation in a manner that could result in an escalation of privilege.
On top of the SQLCLR CAS permission set, assemblies hosted by the SQLCLR are also subject to restriction of permissions via the general CAS policy on the machine and the use of assembly-level permission attributes. An assembly hosted by the SQL CLR will be granted the intersection of the permissions granted under its SQLCLR CAS permission level, the general CAS policy on the machine, and its assembly-level permission attributes.
On a practical note, any changes to the general CAS policy on the machine may not be picked up until after the SQL Server service is restarted.
There are six identity permissions included in the core .NET Framework. Of the six, it would appear that only StrongNameIdentityPermission and ZoneIdentityPermission are ever assigned by the SQLCLR. Details of the apparent assignment of these permissions appear in the table below.
| Identity permission | Apparent SQLCLR assignment behaviour |
|---|---|
| GacIdentityPermission | Never assigned, but this shouldn't be surprising given that assemblies are loaded from inside the database, not from the GAC. |
| PublisherIdentityPermission | Never assigned, probably because verifying the certificate revocation list for the signing certificate's CA would be too costly an operation to run from within SQL Server. However, the authenticode signature does appear to be present in the internal representation of the assembly that is maintained within the database. |
| SiteIdentityPermission | Never assigned, presumably because assemblies are loaded from inside the database. |
| StrongNameIdentityPermission | Assigned if the assembly is strongly named. |
| UrlIdentityPermission | Never assigned, presumably because assemblies are loaded from inside the database. |
| ZoneIdentityPermission | Always assigned as Zone = MyComputer, presumably because assemblies are loaded from inside the database. |
From what I can tell, it looks like SQL Server doesn't even bother storing any information regarding the location from which an assembly was originally loaded. This actually makes quite a bit of sense since using such information could lead to potential escalation of CAS privilege by assemblies with low permission grants. After all, even SAFE assemblies can run arbitrary T-SQL against the context connection, which would mean that they could potentially alter any locally stored data about their own assembly. If the SQLCLR used any data at all from outside the assembly definition itself to determine the assembly's evidence, even very low privilege assemblies would potentially be able to cause their own permission grants to be increased by mucking about with their stored evidence.
Well, then, if the SQLCLR doesn't provide any evidence about the origin of the assembly (aside from the essentially useless Zone = MyComputer information), what evidence can you use if you want to decrease an assembly's permission grant by fiddling with the general CAS policy on the machine? Strong name evidence is provided for strongly named assemblies, so that's an obvious candidate. However, what can you do when an assembly isn't strongly named?
The good news is that the SQLCLR provides System.Security.Policy.Hash evidence for hosted assemblies, and this can be used as the membership criterion for a code group in the general CAS policy. The bad news is that the SQLCLR host doesn't derive the same hash value for any given assembly as does the stand-alone CLR if the assembly is signed with an authenticode signature (even though the signature does seem to appear in the assembly definition stored within SQL Server). Therefore, if the assembly does have an authenticode signature, you must read its hash evidence from within the SQLCLR instead of via any of the tools provided for use with the stand-alone CLR.
The really bad news is that the above approaches work only to decrease the permission grants of as-deployed assemblies. If a hosted assembly is altered for any reason (like, say a routine patch application), chances are excellent that any permissions you might have denied via membership in a code group that matches on strong name or assembly hash will become available to the new version of the assembly. This means granting, not denying, permissions based on strong name and/or hash would be a safer approach overall. However, this requires that assemblies running from the local computer zone would have to be granted little or no permissions by default, which can lead to some major troubleshooting headaches. If you decide to go this route, extensive testing of any deployments (including patches) on an identically configured staging server would be a very necessary step.