Logo
Windows XP
Windows Vista
Windows 7
Windows Azure
Windows Server
Windows Phone
EPL Standings
 
 
Windows Server

SQL Server 2005 : SQLCLR Security and Reliability Features (part 2) - Selective Privilege Escalation via Assembly References

9/6/2011 3:15:01 PM

Selective Privilege Escalation via Assembly References

In an ideal world, SQLCLR module permissions could be made to work like T-SQL module permissions : outer modules would be granted the least possible privileges, but would be able to selectively and temporarily escalate their privileges in order to do certain operations that require more access. This would lessen the privileged surface area significantly, which would mean that there would be less need to do a stringent security review on outer (less-privileged) module layers, which undoubtedly constitute the majority of code written for a given system—the engine would make sure they behave.

The general solution to this problem is to split up code into separate assemblies based on permissions requirements, but to not do so without regard for both maintenance overhead and reuse. For example, consider the 5,000-line module, mentioned in the previous section, that needs to read a few lines from a text file. The entire module could be granted a high enough level of privileges to read the file, or the code to read the file could be taken out and placed into its own assembly. This external assembly would expose a method that takes a filename as input and returns a collection of lines. As I'll show in the following sections, this solution would let you catalog the bulk of the code as SAFE yet still do the file I/O operation. Plus, future modules that need to read lines from text files could reference the same assembly, and therefore not have to reimplement this logic.

The encapsulation story is, alas, not quite as straightforward as creating a new assembly with the necessary logic and referencing it. Due to the different behavior of CAS and HPA exceptions, you might have to perform some analysis of what the code is doing in order to properly encapsulate the permissions in the inner modules. In the following sections, I'll cover each of the permission types separately in order to illustrate how to design a solution.

Working with Host Protection Privileges

A fairly common SQLCLR pattern is to create static collections that can be shared among callers. However, as with any shared data set, proper synchronization is essential in case you need to update some of the data after its initial load. From a SQLCLR standpoint, this gets dicey due to the fact that threading and synchronization require UNSAFE access—granting such an open level of permission is not something to be taken lightly.

For an example of a scenario that might make use of a static collection, consider a SQLCLR UDF used to do currency conversions based on exchange rates:

[SqlFunction]
public static SqlDecimal GetConvertedAmount(
SqlDecimal InputAmount,
SqlString InCurrency,
SqlString OutCurrency)
{
//Convert the input amount to the base
decimal BaseAmount =
GetRate(InCurrency.Value) *
InputAmount.Value;

//Return the converted base amount
return (new SqlDecimal(
GetRate(OutCurrency.Value) * BaseAmount));
}

The GetConvertedAmount method internally makes use of another method, GetRate:

private static decimal GetRate(string Currency)
{
decimal theRate;
rwl.AcquireReaderLock(100);

try
{
theRate = rates[Currency];
}
finally
{
rwl.ReleaseLock();
}

return (theRate);
}

GetRate performs a lookup in a static generic instance of Dictionary<string, decimal>, called rates. This collection contains exchange rates for the given currencies in the system. In order to protect against problems that will occur if another thread happens to be updating the rates, synchronization is handled using a static instance of ReaderWriterLock, called rwl. Both the dictionary and the ReaderWriterLock are instantiated when a method on the class is first called, and both are marked readonly in order to avoid being overwritten after instantiation:

static readonly Dictionary<string, decimal>
rates = new Dictionary<string, decimal>();
static readonly ReaderWriterLock
rwl = new ReaderWriterLock();

If cataloged using either the SAFE or EXTERNAL_ACCESS permission sets, this code fails due to its use of synchronization (the ReaderWriterLock), and running it produces a HostProtectionException. The solution is to move the affected code into its own assembly, cataloged as UNSAFE. Because the host protection check is evaluated at the moment of just-in-time compilation of a method in an assembly, rather than dynamically as the method is running, the check is done as the assembly boundary is being crossed. This means that an outer method can be marked SAFE and temporarily escalate its permissions by calling into an UNSAFE core.

NOTE

You might be wondering about the validity of this example, given the ease with which this system could be implemented in pure T-SQL, which would eliminate the permissions problem outright. I do feel that this is a realistic example, especially if the system needs to do a large number of currency translations on any given day. SQLCLR code will outperform T-SQL for even simple mathematical work, and caching the data in a shared collection rather than reading it from the database on every call is a huge efficiency win. I'm confident that this solution would easily outperform any pure T-SQL equivalent.

When designing the UNSAFE assembly, it is important from a reuse point of view to carefully analyze what functionality should be made available. In this case, use of the dictionary isn't causing the problem. Synchronization via the ReaderWriterLock is throwing the actual exception. However, wrapping methods around a ReaderWriterLock would probably not promote very much reuse. A better tactic, in my opinion, is to wrap the Dictionary and the ReaderWriterLock together, creating a new ThreadSafeDictionary class. This class could be used in any scenario in which a shared data cache is required.

Following is my implementation of the ThreadSafeDictionary; I have not implemented all of the methods that the generic dictionary exposes, but rather only those I commonly use:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SafeDictionary
{
public class ThreadSafeDictionary<K, V>
{
private readonly Dictionary<K, V> dict = new Dictionary<K,V>();
private readonly ReaderWriterLock theLock = new ReaderWriterLock();

public void Add(K key, V value)
{
theLock.AcquireWriterLock(2000);

try
{
dict.Add(key, value);
}
finally
{
theLock.ReleaseLock();
}
}

public V this[K key]
{

get
            {
theLock.AcquireReaderLock(2000);
try
{
return (this.dict[key]);
}
finally
{
theLock.ReleaseLock();
}
}

set
{
theLock.AcquireWriterLock(2000);
try
{
dict[key] = value;
}
finally
{
theLock.ReleaseLock();
}
}
}

public bool Remove(K key)
{
theLock.AcquireWriterLock(2000);
try
{
return (dict.Remove(key));
}
finally
{
theLock.ReleaseLock();
}
}

public bool ContainsKey(K key)
{
theLock.AcquireReaderLock(2000);
try
{
return (dict.ContainsKey(key));
}


finally
{
theLock.ReleaseLock();
}
}
}
}

This class should be placed into a new assembly, which should then be compiled and cataloged in SQL Server as UNSAFE. A reference to the UNSAFE assembly should be used in the exchange rates conversion assembly, after which a few lines of the example code will have to change. First of all, the only static object that must be created is an instance of ThreadSafeDictionary:

static readonly ThreadSafeDictionary<string, decimal> rates =
new ThreadSafeDictionary<string, decimal>();

Since the ThreadSafeDictionary is already thread safe, the GetRate method no longer needs to be concerned with synchronization. Without this requirement, its code becomes greatly simplified:

private static decimal GetRate(string Currency)
{
return (rates[Currency]);
}

The exchange rates conversion assembly can still be marked SAFE, and can now make use of the encapsulated synchronization code without throwing a HostProtectionException. And none of the code actually contained in the assembly will be able to use resources that violate the permissions allowed by the SAFE bucket—quite an improvement over the initial implementation, from a security perspective.

NOTE

Depending on whether your database has the TRUSTWORTHY option enabled and whether your assemblies are strong named, things may not be quite as simple as I've implied here. The examples in both this and the next section may fail either at deployment time if your core assembly doesn't have the correct permissions or at run time if you've decided to go with a strong named assembly.

Working with Code Access Security Privileges

HPA-protected resources are quite easy to encapsulate, thanks to the fact that permissions for a given method are checked when the method is just-in-time compiled. Alas, things are not quite so simple when working with CAS-protected resources, due to the fact that grants are checked dynamically at run time via a stack walk. This means that simply referencing a second assembly is not enough—the entire stack is walked each time, without regard to assembly boundaries.

To illustrate this issue, create a new assembly containing the following method, which reads all of the lines from a text file and returns them as a collection of strings:

public static string[] ReadFileLines(string FilePath)
{
List<string> theLines = new List<string>();

using (System.IO.StreamReader sr =
new System.IO.StreamReader(FilePath))
{
string line;
while ((line = sr.ReadLine()) != null)
theLines.Add(line);
}

return (theLines.ToArray());
}

Catalog the assembly in SQL Server with the EXTERNAL_ACCESS permission set, and reference it from the assembly that contains the CAS_Exception stored procedure created in the section "SQLCLR Security and Reliability Features." Modify that stored procedure as follows:

[SqlProcedure]
public static void CAS_Exception()
{
SqlContext.Pipe.Send("Starting...");

string[] theLines =
FileLines.ReadFileLines(@"C:\b.txt");

SqlContext.Pipe.Send("Finished...");

return;
}

Note that I created my ReadFileLines method inside a class called FileLines; reference yours appropriately depending on what class name you used. Once you've finished the modifications, redeploy the outer assembly, making sure that it is cataloged as SAFE.

Running the modified version of this stored procedure, you'll find that even though an assembly boundary is crossed, you will receive the same exception as before. The CAS grant did not change simply because a higher privileged assembly was referenced, due to the fact that the stack walk does not take into account permissions held by referenced assemblies.

Working around this issue requires taking control of the stack walk within the referenced assembly. Due to the fact that the assembly has enough privilege to do file operations, it can internally demand that the stack walk discontinue checks for file I/O permissions, even when called from another assembly that does not have the requisite permissions. This is done by using the Assert method of the IStackWalk interface, exposed in .NET's System.Security namespace.

Taking a second look at the CAS violation shown previously, note that the required permission is FileIOPermission, which is in the System.Security.PermissionsFileIOPermission class—in addition to other "permission" classes in that namespace—implements the IStackWalk interface. To avoid the CAS exception, simply instantiate an instance of the FileIOPermission class and call the Assert method. The following code is a modified version of the ReadFileLines namespace. The method that uses this technique:

public static string[] ReadFileLines(string FilePath)
{
//Assert that anything File IO-related that this
//assembly has permission to do, callers can do
FileIOPermission fp = new FileIOPermission(
PermissionState.Unrestricted);
fp.Assert();

List<string> theLines = new List<string>();

using (System.IO.StreamReader sr =
new System.IO.StreamReader(FilePath))
{
string line;
while ((line = sr.ReadLine()) != null)
theLines.Add(line);
}

return (theLines.ToArray());
}

This version of the method instantiates the FileIOPermission class with the PermissionState.Unrestricted enumeration, thereby enabling all callers to do whatever file I/O-related activities that the assembly has permission to do. The use of the term "unrestricted" in this context is not as dangerous as it sounds; the access is unrestricted in the sense that permission is allowed for only as much access as the assembly already has to the file system. After making the modifications shown here and redeploying both assemblies, the CAS exception will no longer be an issue.

To allow you to control things on a more granular level, the FileIOPermission class exposes other constructor overloads with different options. The most useful of these for this example uses an enumeration called FileIOPermissionAccess in conjunction with the path to a file, allowing you to limit the permissions the caller has to only specific operations on a specific file. For instance, to limit access so that the caller can only read the file specified in the input path, use the following constructor:

FileIOPermission fp = new FileIOPermission(
FileIOPermissionAccess.Read,
FilePath);

File I/O is only one of many kinds of permissions for which you might see a CAS exception. The important thing is being able to identify the pattern. In all cases, violations will throw a SecurityException and reference a permission class in the System.Security.Permissions namespace. Each class follows the same basic pattern outlined here, so you should be able to easily use this technique in order to design any number of privilege escalation solutions.

Top Search -----------------
- Windows Server 2008 R2 : Work with RAID Volumes - Understand RAID Levels & Implement RAID
- Windows Server 2008 R2 Administration : Managing Printers with the Print Management Console
- Configuring Email Settings in Windows Small Business Server 2011
- Windows Server 2008 R2 : Configuring Folder Security, Access, and Replication - Implement Permissions
- Monitoring Exchange Server 2010 : Monitoring Mail Flow
- Windows Server 2008 R2 :Task Scheduler
- Windows Server 2008 R2 : File Server Resource Manager
- Windows Server 2008 R2 : Installing DFS
- Exchange Server 2010 : Managing Anti-Spam and Antivirus Countermeasures
- Windows Server 2008 R2 : Configuring Folder Security, Access, and Replication - Share Folders
Other -----------------
- SQL Server 2005 : Wrapping Code to Promote Cross-Tier Reuse
- SharePoint 2010 Search : Setting Up the Crawler - Crawling Exchange Public Folders & Crawling Line-of-Business Data
- SharePoint 2010 Search : Setting Up the Crawler - Crawling File Shares & Crawling Web Sites
- Migrating to Windows Small Business Server 2011 Standard : Migrating Settings and Data (part 5) - Re-Enabling Folder Redirection
- Migrating to Windows Small Business Server 2011 Standard : Migrating Settings and Data (part 4) - Migrating Users and Groups
- Migrating to Windows Small Business Server 2011 Standard : Migrating Settings and Data (part 3) - Migrate Network Settings & Migrate Exchange Mailboxes and Settings
- Migrating to Windows Small Business Server 2011 Standard : Migrating Settings and Data (part 2) - Configure the Network
- Migrating to Windows Small Business Server 2011 Standard : Migrating Settings and Data (part 1) - Starting the Migration Wizard
- Migrating to Windows Small Business Server 2011 Standard : Creating a Migration Answer File
- Microsoft Dynamics CRM 2011 : Copying Campaign Records & Using Quick Campaigns
 
 
Most view of day
- Using Windows Media Player (part 1)
- Microsoft Project 2010 : Using the Resource Fields to Define Resource Details (part 2) - Using the Max Units and Resource Availability Table to Specify Resource Availability
- SQL Server 2012 : XML and the Relational Database - Shredding XML Using OPENXML
- Configuring Search and Indexing Options (part 4) - Refining a Search in Windows Explorer
- Microsoft Power Point 2010 : Inserting a Diagram & Editing SmartArt Text
- Windows Server 2003 : Using DNS Monitoring Tools (part 2) - Monitoring DNS Performance with System Monitor
- Vector Graphics : The Versatile PathGeometry
Top 10
- Windows Server 2012 : Enhanced security and compliance (part 2) - BitLocker enhancements, DNSSEC
- Windows Server 2012 : Enhanced security and compliance (part 1) - Dynamic Access Control
- Windows Server 2012 : Full Windows experience (part 2) - Configuring User Profile Disks
- Windows Server 2012 : Full Windows experience (part 1) - RemoteFX enhancements,Configuring RemoteFX, Enhanced USB redirection
- Windows Server 2012 : Support for open standards
- Microsoft SharePoint 2013 : Working with Visio Services - Customizing Visio Services solutions
- Microsoft SharePoint 2013 : Working with Visio Services - Designing dashboards - Data linking (part 4) - Adding data graphics , Web part connections
- Microsoft SharePoint 2013 : Working with Visio Services - Designing dashboards - Data linking (part 3) - Mapping external data to shapes
- Microsoft SharePoint 2013 : Working with Visio Services - Designing dashboards - Data linking (part 2) - Refreshing external data
- Microsoft SharePoint 2013 : Working with Visio Services - Designing dashboards - Data linking (part 1) - Obtaining external data
Windows XP
Windows Vista
Windows 7
Windows Azure
Windows Server
Windows Phone
2015 Camaro