March 2010

Volume 25 Number 03

CLR Inside Out - Migrating an APTCA Assembly to the .NET Framework 4

By Mike Rousos | March 2010

In the Microsoft .NET Framework 4, the common language runtime (CLR) security model has undergone some substantial changes. One of these changes, the adoption of Level2 transparency (much like Silverlight’s security model), is likely to impact authors of AllowPartiallyTrustedCallers (APTCA) libraries.

The reason for the impact is that the underlying workings of APTCA have changed in CLR v4. The APTCA attribute retains the ability to make a fully trusted library available to partially trusted callers, but the details of how that happens are different, and some modifications to APTCA library code are likely necessary as a result.

Note: references to v2 in this article refer to CLR v2, which includes everything from the .NET Framework versions 2.0 through 3.5 SP1.

APTCA Before V4

Prior to v4, all signed assemblies were protected from partially trusted callers by implicit link demands for full trust on all entry points. This meant any partially trusted code attempting to access a strong-named assembly would fail with a security exception. This prevented (potentially dangerous) fully trusted code from being called maliciously from partial trust.

Adding the AllowPartiallyTrustedCallers attribute to a signed full-trust library made it available to partial trust by removing these implicit link demands. Consequently, APTCA libraries allowed partially trusted code controlled access to privileged operations through APTCA-exposed methods. It was the responsibility of APTCA authors to ensure that only safe operations were exposed to partial trust in this way, and that any potentially dangerous operations were protected with explicit link demands or full demands.

APTCA in V4

The AllowPartiallyTrustedCallers attribute has changed. In v4, it no longer has anything to do with link demands. In fact, the implicit link demand that was present on signed libraries in v2 is gone. Instead, all fully trusted assemblies in v4 are, by default, SecurityCritical. On the other hand, all partially trusted assemblies are automatically SecurityTransparent in v4. As explained in the transparency overview in the next section, SecurityCritical code can’t be called from SecurityTransparent code.

Thus, the new v4 transparency system provides the same protection of full trust code as the old link demands; because of the automatic 
SecurityCritical and SecurityTransparent transparency levels, partially trusted code can’t call into fully trusted libraries by default.

As you may have guessed, the v4 change to AllowPartiallyTrustedCallers is related to this. In v4, the effect of APTCA is to remove the automatic SecurityCritical behavior from the assembly to which it’s applied. The assembly then defaults to SecurityTransparent, but allows the APTCA assembly author to apply more granular SecurityCritical and SecuritySafeCritical attributes to specific types and methods as necessary.

Crash Course in Transparency

The effect of transparency attributes like SecurityTransparent and SecurityCritical will be known to readers familiar with the Silverlight security model, because the new v4 transparency model is quite similar.

Let’s take a look at the three primary transparency attributes: SecurityTransparent, SecuritySafeCritical and SecurityCritical.

SecurityTransparent Code marked as SecurityTransparent is safe from a security perspective. It’s unable to complete any dangerous operations, such as asserting a permission, executing unverifiable code or calling native code. It’s also unable to call SecurityCritical code directly.

  • As noted, all partial trust code is forced to be SecurityTransparent for security reasons. It’s also the default transparency of APTCA libraries.

SecurityCritical SecurityCritical code, by contrast, is able to perform any operations it wishes. It can assert, call native code and more. It can call other methods regardless of transparency markings.

  • Only fully trusted code can be SecurityCritical. And, in fact, (non-APTCA) fully trusted code is assumed to be SecurityCritical, by default, to protect it from transparent, partially trusted callers.

SecuritySafeCritical SecuritySafeCritical code acts as a bridge, allowing transparent code to call into critical methods. SecuritySafeCritical code has all the same rights as SecurityCritical code, but it’s callable from SecurityTransparent code. It is, therefore, extremely important that SecuritySafeCritical code expose underlying SecurityCritical methods only in a safe manner (lest some malicious, partially trusted code attempts to exploit the methods through the SecuritySafeCritical layer).

  • Like SecurityCritical code, SecuritySafeCritical code must be fully trusted.

Figure 1 illustrates the interactions of SecurityTransparent, SecuritySafeCritical and SecurityCritical code.


Figure 1 Interactions of SecurityTransparent, SecuritySafeCritical and SecurityCritical Code

Note that in addition to the transitions shown in the diagram, all transparency levels can access themselves and any less critical code (for example, SecuritySafeCritical code can access SecurityTransparent code). Because the AllowPartiallyTrustedCallers attribute causes the entire assembly to be SecurityTransparent by default, the assembly’s author must specifically mark methods needing to perform privileged operations as SecurityCritical or SecuritySafeCritical. Without such marking, the APTCA author will find that his code fails with MethodAccessExceptions, TypeAccessExceptions and other errors indicating that the APTCA library is attempting to call dangerous APIs from SecurityTransparent code.

This is just a brief introduction to the model; you’ll find a more thorough examination in MSDN documentation and in a previous CLR Inside Out article by Andrew Dai, available at msdn.microsoft.com/magazine/ee677170.aspx.

Migrating from V2 to V4: Which Attributes to Apply

Most of the work necessary to migrate a v2 APTCA assembly to v4 involves identifying and applying the correct transparency attributes to methods that need them. Following are guidelines indicating when each of the attributes is appropriate.

SecurityTransparent Code that does not perform any security-sensitive operations should be SecurityTransparent.

Unlike the other transparency settings, SecurityTransparent behavior is the default in an APTCA assembly and, therefore, does not need to be marked explicitly. Code is considered transparent in the absence of other attributes.

One advantage of transparent code is that it’s safe (because dangerous operations are disallowed) and, as a result, it does not need as thorough a security review as SecurityCritical or, especially, SecuritySafeCritical code. It’s recommended that as much code as possible be SecurityTransparent.

The following are disallowed in SecurityTransparent code:

  • Calling SecurityCritical methods
  • Asserting a permission or permission set
  • Unverifiable code
  • Calling unmanaged code
  • Overriding SecurityCritical virtual methods
  • Implementing SecurityCritical interfaces
  • Deriving from any type that is not SecurityTransparent

SecuritySafeCritical Code that is callable from partial trust but needs to be able to call potentially dangerous APIs should be marked as SecuritySafeCritical. Often, methods that demand permissions will fall into this category because they represent a protected boundary between partially trusted code and privileged operations.

Because SecuritySafeCritical code allows partially trusted callers to indirectly access dangerous APIs, it’s a very powerful attribute and should be applied carefully and sparingly. It’s important that SecuritySafeCritical code expose SecurityCritical functionality to its callers only in specific, safe ways. It’s usually a good idea for SecuritySafeCritical code to contain demands to ensure that callers can access particular resources that the SecuritySafeCritical code will be using. It’s also important for SecuritySafeCritical code to validate both inputs and outputs (to be sure that invalid values are not passed through, and that any returned information is safe to give to partial trust).

Because of the potential security risks, it’s recommended that SecuritySafeCritical code be kept to a minimum.

SecurityCritical Code that is not safe to expose to partially trusted callers should be marked as SecurityCritical. Methods that previously were protected by a link demand are likely to require this attribute.

SecurityCritical code is less dangerous than SecuritySafeCritical because it’s not directly callable from transparent (partially trusted) callers. However, the code can perform many high-security operations so, in order to keep the need for security reviews to a minimum, it’s a good idea to keep SecurityCritical code to a minimum, as well.

Good general guidance is that any code that can be SecurityTransparent should be. Other code should be SecurityCritical unless it’s specifically expected that transparent code will access SecurityCritical code through it, in which case SecuritySafeCritical is appropriate.

Using SecAnnotate.exe

To help with the correct application of transparency attributes, there is a new .NET Framework SDK tool, the Security Annotator (SecAnnotate.exe). This tool consumes a user’s binary (or collection of binaries) and provides guidance on where transparency attributes should be applied. It can be very helpful when migrating an APTCA library to v4.

SecAnnotate works by making several passes through the target binary, looking for methods that, according to the CLR’s rules, need to be marked with a transparency attribute. On subsequent passes, the tool looks for attributes that are necessary because of modifications suggested in previous passes. For example, consider this short code snippet (which is assumed to come from an APTCA assembly):

static void Method1()
{
      Console.WriteLine("In method 1!");
      Method2();
}

static void Method2()
{
      PermissionSet ft = new PermissionSet(PermissionState.Unrestricted);
      ft.Assert();
      DangerousAPI();
      PermissionSet.RevertAssert();
}

SecAnnotate.exe would immediately notice that Method2 can’t be transparent because it asserts for some permissions. After the first pass, the tool would know that Method2 must be either SecurityCritical or SecuritySafeCritical (with SecurityCritical being preferred unless transparent code needs to specifically access this method).

On the first pass through the binary, Method1 would not seem interesting to the tool. On the second pass, however, it would be noted that Method1 is calling Method2, which, during the first pass, SecAnnotate suggested become SecurityCritical. Because of this, Method1 would also need to be SecurityCritical (or, at the author’s discretion, SecuritySafeCritical). After two passes, the guidance to mark as SecurityCritical would be given for both methods.

Understanding SecAnnotate.exe Output

Security Annotator’s output is an XML file that contains the problems it has identified and the recommended fixes. Sometimes, Security Annotator reverses an earlier recommendation after subsequent passes. In such cases, both recommendations appear in the XML. You’ll need to look at the pass number in these cases to understand which recommendation is more recent and, therefore, correct.

For example, consider the output from Security Annotator in Figure 2. Notice that there are two elements under the annotations tag for method Logging.MethodA—a SecuritySafeCritical tag and a SecurityCritical tag. This means that SecAnnotate recommended both SecurityCritical and SecuritySafeCritical attributes for this method during its analysis.

Figure 2 Output from Security Annotator

<requiredAnnotations>
    <assembly name="Logging">
      <type name="Logging">
        <method name="MethodA()">
          <annotations>
            <safeCritical>
              <rule name="MethodsMustOverrideWithConsistentTransparency">
                <reason pass="2" sourceFile="d:\repro\aptca\logging.cs" sourceLine="67">Critical method Logging.MethodA()’ is overriding transparent or safe critical method ‘Logging.MethodA()’ in violation of method override rules.  Logging.MethodA()’ must become transparent or safe-critical in order to override a transparent or safe-critical virtual method or implement a transparent or safe-critical interface method.</reason>
              </rule>
            </safeCritical>
            <critical>
              <rule name="TransparentMethodsMustNotSatisfyLinkDemands">
                <reason pass="1" sourceFile="d:\repro\aptca\logging.cs" sourceLine="68">Security transparent method Logging.MethodA()’ satisfies a LinkDemand for ‘FileIOPermissionAttribute’ on method ‘Logging.set_LogLocation(System.String)’.  Logging.MethodA()’ should become critical or safe-critical in order to call ‘Logging.set_LogLocation(System.String)’.</reason>
              </rule>
            </critical>
          </annotations>
        </method>
      </type>
    </assembly>
  </requiredAnnotations>

The SecurityCritical element’s explanation says that because this method is calling something protected with a link demand, it must be either SecurityCritical or SecuritySafeCritical. SecAnnotate.exe defaults to recommending SecurityCritical because it’s more secure. Note that the pass attribute has a value of 1 here, meaning this suggestion resulted from SecAnnotate.exe’s first pass through the code.

The next recommendation—for SecuritySafeCritical—notes that MethodA is overriding a transparent base method and so must be SecurityTransparent or SecuritySafeCritical (it must have the same accessibility as the base method). Putting this information together with the previous recommendation, SecAnnotate.exe suggests that MethodA should be SecuritySafeCritical.

Note that pass=“2” means this recommendation came during SecAnnotate.exe’s second pass through the code. This is because during the first pass, the tool didn’t  know that MethodA could not be transparent, so it wasn’t aware of  this SecuritySafeCritical requirement.

Because the SecuritySafeCritical recommendation was made during the second (more recent) pass, it is the correct annotation in this case.

SecAnnotate.exe Best Practices

In cases where SecurityCritical and SecuritySafeCritical would both be correct markings, Security Annotator first prefers any attribute already on the code, and then SecurityCritical because it’s less risky. Unfortunately, this often results in code that is secure but unusable in a sandbox because all entry points are blocked to partially trusted callers.

Remember that SecuritySafeCritical is appropriate on APIs that are meant to be called directly from transparent/partial-trust code and have been reviewed for security with that in mind. Because Security Annotator can’t know which APIs are meant to be called from partial trust or are safe to be called in that way, it will mark very little as SecuritySafeCritical. The library’s author must manually apply the SecuritySafeCritical attribute to some methods, even when using Security Annotator.

Because a single action prohibited in transparent code can “spider web” out into many SecurityCritical markings in Security Annotator’s successive passes without strategic placement of the SecuritySafeCritical attribute, it’s good practice to use SecAnnotate.exe with the /p command-line switch. The switch /p:x (where x is a number) instructs Security Annotator to run only x passes, instead of running until no more changes are necessary. Here is good way to use Security Annotator:

  1. Run SecAnnotate.exe /p:1 /d:<Path to referenced assemblies> <FileName.dll>
          a. This adds transparency attributes where needed, but only with a single pass. Stopping at this point allows the author to manually check the attributes.
          b. By default, SecAnnotate.exe looks only in the GAC for dependencies of the assembly it’s annotating. Other assemblies must have their paths specified with the /d switch.
  2. Update the library’s source files with the suggested attributes. Consider cases with multiple possible attributes, though, and decide which is correct. In some cases, SecuritySafeCritical will be the correct attribute, despite SecAnnotate favoring SecurityCritical.
  3. Rebuild the assemblies and repeat at step 1 without /p:1. You could rerun the program using /p:1 repeatedly, but you shouldn’t need to—the necessary SecuritySafeCritical attributes are already present after the first iteration of step 2.

This iterative process with manual developer interaction will result in a correctly annotated assembly that maximizes transparent code.

Identifying and Reviewing SecuritySafeCritical APIs

As noted earlier, it’s common for SecAnnotate.exe to recommend that an API should be either SecurityCritical or SecuritySafeCritical. The key differentiator is whether the API can safely be called from partial trust. If the API does all validation necessary to be sure that underlying critical or native APIs will be called safely (through demands or input and output validation, for example), then it can be SecuritySafeCritical, which is sometimes desirable because it allows callers of the API to be transparent. If, on the other hand, there is any way that malicious code could access protected resources through the API, the API must remain SecurityCritical.

It’s important that all SecuritySafeCritical code be carefully reviewed for security impact of exposure to partial trust. Although both SecuritySafeCritical and SecurityCritical code should be minimized, if there is doubt as to which attribute is correct, SecurityCritical is the safer option.

Applying Transparency Attributes

Applying transparency attributes is as simple as applying any other .NET attributes in code. Documentation regarding usage for the attributes can be found in MSDN documentation for these types:

  • SecurityTransparentAttribute
    Note that this attribute may be applied only at the assembly level. Its meaning, in that case, is that all types and methods in the assembly are transparent. It’s unnecessary at the type or method level because it’s the default transparency setting in APTCA assemblies.
  • SecuritySafeCriticalAttribute
  • SecurityCriticalAttribute

In C#, applying the attributes looks like this:

[SecurityCritical]
public static void Method1()
{ /* Do something potentially dangerous*/ }

[SecuritySafeCritical]
public static void Method2()
{ /* Do something potentially dangerous in a safe way that can be called from partial trust */ }

Level1 and Level2

One final note regarding transparency and APTCA is that it’s possible, through the use of an assembly-level attribute, to use the old v2 APTCA behavior instead of the new v4 behavior. This is not recommended because the new model is more secure, easier to audit and common between Silverlight and desktop CLRs. Still, sometimes compatibility is needed in the short term until you can migrate. In these cases, the SecurityRules attribute can be used to force an assembly to use old v2 rules.

The SecurityRules attribute takes a parameter of the SecurityRuleSet enum type. SecurityRuleSet.Level1 specifies compatibility. SecurityRuleSet.Level2 specifies the new model, but a Level2 attribute is not necessary because it’s the default. It can be useful, however, to explicitly indicate which transparency rule set is in use and to protect against any future changes regarding which rule set is the .NET Framework’s default.

In C#, application of this attribute appears like this:

[assembly:SecurityRules(SecurityRuleSet.Level1)]

Common Pitfalls

Following are a few common “gotchas” that APTCA library authors should be wary of as they migrate from v2 to v4:

  • SecAnnotate.exe will recommend that LinkDemands become SecurityCritical attributes (which are very similar to a LinkDemands for FullTrust). If, however, a type (rather than a method) was protected with a LinkDemand, this is not the same as applying SecurityCritical to a type in v4. It’s better to apply SecurityCritical to all members of the type, as this will act more like a v2 type-level LinkDemand.
  • Note that some low-permissions LinkDemands that some partial trust code is expected to be able to satisfy may not be best translated into SecurityCritical. If a LinkDemand is for a low permission (for example, read permission to a particular safe path), it’s better to remove the LinkDemand and replace it with a full demand for the same permission. This allows partial trust code to still call the API (but the demand will make sure that only partial trust code with high enough permissions succeeds in making the call).
  • In general, type-level transparency attributes apply to members of the type they modify, as well. And the outermost attribute trumps others. So, applying [SecurityCritical] to a method is ineffectual if the type it’s in has [SecuritySafeCritical] applied to it, for example. Typically, [SecuritySafeCritical] is not a useful attribute on the type level. It’s too likely that someone will later introduce a new member to the type and not realize that it’s SecuritySafeCritical (thanks to the type-level attribute), potentially resulting in a security hole.

Although type-level attributes apply to new slot members of the types they modify, they do not apply to overridden members. Be sure that if you use type-level transparency attributes, you also add attributes to overridden members specifically, as necessary.

Migration Example

Figure 3 is a simple (and incomplete) logging library written in v2.

Figure 3 V2 APTCA Library

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

// This assembly is meant to be representative of a simple v2 APTCA assembly
// It has some dangerous code protected with demands/link demands
// It exposes some dangerous code in a controlled way with an assert

[assembly: AllowPartiallyTrustedCallers]
public class Logging
{
    private string logLocation = @"C:\temp\firstfoo.txt";

    public virtual string Usage()
    {
        return "This is a helpful string";
    }

    public virtual string LogLocation
    {
        get
        {
            return logLocation;
        }

        [FileIOPermissionAttribute(SecurityAction.LinkDemand, Unrestricted=true)]
        set
        {
            logLocation = value;
        }
    }

    public virtual void SetLogLocation(int index)
    {
        switch (index)
        {
            case 1: 
                LogLocation = @"C:\temp\foo.txt";
                break;
            case 2:
                LogLocation = @"D:\temp\foo.txt";
                break;
            case 3:
                LogLocation = @"D:\repro\temp\foo.txt";
                break;
            default:
                break;
        }
    }


    public virtual void DeleteLog()
    {
        FileIOPermission fp = new FileIOPermission(FileIOPermissionAccess.AllAccess, LogLocation);
        fp.Assert();
        if (File.Exists(LogLocation)) { File.Delete(LogLocation); }
        SecurityPermission.RevertAll();
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

public class OtherLogging : Logging
{
    public override string Usage()
    {
        LogLocation = null;
        return "This is a different useful string";
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

The same library is also shown in Figure 4, migrated to v4 with comments (in italics) explaining the changes.

Figure 4 V4 APTCA Library

using System;
using System.IO;
using System.Security;
using System.Security.Permissions;

// This assembly is meant to be representative of a simple v2 APTCA assembly
// It has some dangerous code protected with demands/link demands
// It exposes some dangerous code in a controlled way with an assert

[assembly: AllowPartiallyTrustedCallers]
public class Logging
{
    private string logLocation = @"C:\temp\firstfoo.txt";
    
    // This API can be transparent because it does nothing dangerous.
    // Transparent APIs need no attributes because it is the default behavior of a v4
    // APTCA assembly
    public virtual string Usage()
    {
        return "This is a helpful string";
    }

    // Note that transparency attributes do not go directly on properties.
    // Instead, they go on the getters and setters (even if the getter and setter
    // get the same attributes)
    public virtual string LogLocation
    {
        get
        {
            return logLocation;
        }

        // This API is made critical because it sets sensitive data (the path to write to)
        // which partial trust code should not be able to do.
        [SecurityCritical]
        // The previous LinkDemand is removed as the SecurityCritical attribute replaces it
    //[FileIOPermissionAttribute(SecurityAction.LinkDemand, Unrestricted=true)]
        set
        {
            logLocation = value;
        }
    }

    // This API accesses a critical member (LogLocation) and, therefore, cannot be transparent
    // However, the access is done in a limited, safe way and we expect transparent code
    // should be able to call this API. Therefore, it is SecuritySafeCritical
    [SecuritySafeCritical]
    public virtual void SetLogLocation(int index)
    {
        switch (index)
        {
            case 1: 
                LogLocation = @"C:\temp\foo.txt";
                break;
            case 2:
                LogLocation = @"D:\temp\foo.txt";
                break;
            case 3:
                LogLocation = @"D:\repro\temp\foo.txt";
                break;
            default:
                break;
        }
    }

    // This API is potentially dangerous; it asserts which means it can’t be transparent
    // Because setting LogLocation is protected, however, partial trust code can safely
    // call this API. In fact, it is intended that it is safe for partial trust code
    // to call this method. Therefore, it is SecuritySafeCritical
    [SecuritySafeCritical]
    public virtual void DeleteLog()
    {
        FileIOPermission fp = new FileIOPermission(FileIOPermissionAccess.AllAccess, LogLocation);
        fp.Assert();
        if (File.Exists(LogLocation)) { File.Delete(LogLocation); }
        SecurityPermission.RevertAll();
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

public class OtherLogging : Logging
{
    // The logic for attributing this method is complicated and it is an example of when
    // SecAnnotate.exe can be very helpful. This API cannot be transparent because it 
    // calls a critical member (LogLocation). However, because it overrides a transparent
    // method (Usage) it cannot be critical. Therefore, the only possible annotation here
    // is SecuritySafeCritical and it is the author’s responsibility to make sure that
    // a malicious caller cannot abuse that access.
    [SecuritySafeCritical]
    public override string Usage()
    {
        LogLocation = null;
        return "This is a different useful string";
    }

    // TODO : Put other APIs (creating log, writing to log, etc) here
}

Syncing CLR and the Silverlight CoreCLR Security Systems

Although the merging of APTCA and transparency in v4 may seem complex, it ultimately supplies straightforward and effective protection to sensitive system resources from partially trusted callers. What’s more, the change aligns the desktop CLR and Silverlight CoreCLR security systems.

SDK tools such as SecAnnotate.exe and FxCop rules (which can validate transparency) help to make migration easier. V4 APTCA assemblies are much easier to audit—looking closely at SecuritySafeCritical APIs (and the SecurityCritical calls they make) is all that is needed to have confidence that the assembly is secure.

Because transparent code often makes up 80 percent to 90 percent or more of an assembly, this is a great relief of audit burden. Readers interested in delving further into transparency can find more complete explanations in MSDN documentation.


 

Mike Rousos  has been a software design engineer in test on Microsoft’s CLR team since 2005. His work primarily deals with ensuring quality in the design and implementation of CLR security systems.

Thanks to the following technical experts for reviewing this article:  Andrew Dai, Cristian Eigel and Shawn Farkas