Logo
programming4us
programming4us
programming4us
programming4us
Home
programming4us
XP
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server
programming4us
Windows Phone
 
Windows Server

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

- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019
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.

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
 
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
 
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server