Click here to Skip to main content
Click here to Skip to main content

Things I learned while implementing my first Level2 SecurityCritical assembly

By , 16 Aug 2012
Rate this:
Please Sign up or sign in to vote.

Introduction

I don't know what possessed me to try this, but I decided to implement an assembly for a stand-alone desktop application using .NET 4.0 and Level 2 security.

  • IDisposable
  • IEnumerable<T>
  • IEnumerable
  • IComparable
  • IEquatable
  • IXmlSerializable

and inheriting from classes that implement those interfaces.

The common characteristic of these interfaces is that they are all SecurityTransparent.

Background

Security Levels

Security Level 1 was introduced with .NET Framework 2.0. It was primarily intended to make it easier to locate the sections of code requiring a security audit. It relies on Demand, LinkDemand, and Assert.

In contrast, Security Level 2, introduced with .NET Framework 4.0, also provides an enforcement capability. It provides three declarative security layers:

  • SecurityTransparent
  • SecuritySafeCritical
  • SecurityCritical

It also provides rules for calling between code in these layers, inheriting from code in these layers, and overriding code in these layers. The documentation is reasonably clear on these rules: Code in any layer can call other code in the same layer or any less secure layer. Code in any layer can also call code in the next more secure layer. This confines the security risks to the transitions from SecuritySafeCritical code to SecurityCritical code. These are the areas requiring security audits.

Code Analyzer

The Code Analyzer can be configured to implement any subset of a large set of rules. You can select a subset that is appropriate to your needs.

Not being sure exactly what my needs were, I used the Code Analyzer full strength, and had hundreds of warnings. After trying to work solely from the VS 2010 documentation, supplemented by a number of Google searches to overcome searching limitations of the Microsoft Help Viewer, I decided that I needed a little more fundamental orientation to get into the spirit of things. I eventually came up with the following two-part series by Matteo Slaviero, a .NET security consultant, on www.simple-talk.com:

The Adventure Begins ...

As informative and useful as this article is, sections such as the following:

The examples provided in this section seem to state that Level2 Security Transparence is, de facto, an all or nothing model. If the assembly is fully trusted it can do anything, and if we set it to be SecurityTransparent it cannot use protected resources at all. However, a more granular approach is possible when we need to protect specific resources, and it is based on the Allow Partially Trusted Caller Attribute (APTCA) which we can set for an assembly. With it, we can set code as SecuritySafeCritical, thereby creating a bridge between SecurityTransparent and SecurityCritical code. We will discuss this in detail in the next article.

left me with the impression that the SecuritySafeCritical attribute could only be used within the assembly if the assembly is marked with the AllowPartiallyTrustedCaller attribute. This emphatically turns out not to be the case. In particular, if the assembly is marked with the SecurityCritical attribute rather than the AllowPartiallyTrustedCaller attribute, you can still use the SecurityCritical and SecuritySafeCritical attributes within the assembly. Now, you might be saying, "How would that be useful if you are only allowed to increase the existing security level of types, methods, etc. by using these attributes?" While that's a very reasonable question, there is also a very reasonable answer: Whenever you use the C# override keyword, the default security level is not the expected SecurityCritical. In fact, the default in this case is SecurityTransparent. You will likely always want to override this default by using either the SecurityCritical or the SecuritySafeCritical attribute. Likewise, whenever you inherit a type (class or struct, e.g.) the default security level is SecurityTransparent and you will likely want to use one of the same two overrides.

One of the most troubling situations I ran into involved inherited properties. Each one would trigger a CA2134 warning, a CA2123 warning and a string of CA2140 warnings. All of these warnings told me that SomeClass.SomeInheritedProperty.get() needed the SecurityCritical attribute. All of the properties involved happened to be read only. I assume that if they had been read-write properties, I would have also gotten warnings for SomeClass.SomeInheritedProperty.set(), but I haven't verified that assumption.

My response was to add the SecurityCritical attribute as follows:

[SecurityCritical]
public override SomeType SomeInheritedProperty
{
    get
    {
        return someValue;
    }
}

This resulted in the following error:

Attribute 'SecurityCritical' is not valid on this declaration type. It is only valid on 'assembly, class, struct, enum, constructor, method, field, interface, delegate' declarations.

This is an error of type CS0592, which I was able to determine by examining the Output window, rather than the Error window (an annoying requirement, considering that the security warnings are identified by type in the Warning tab of the Error window).

Checking the documentation for this error led me to look up the documentation for the SecurityCriticalAttribute class, which provides the following information (the SecuritySafeCriticalAttribute is similar, and indeed the same situation obtains when that attribute is used in this way):

[AttributeUsageAttribute(AttributeTargets.Assembly
                        |AttributeTargets.Class
                        |AttributeTargets.Struct
                        |AttributeTargets.Enum
                        |AttributeTargets.Constructor
                        |AttributeTargets.Method
                        |AttributeTargets.Field
                        |AttributeTargets.Interface
                        |AttributeTargets.Delegate,
                         AllowMultiple = false, 
                         Inherited = false)]
public sealed class SecurityCriticalAttribute : Attribute

What struck me about this was that AttributeTargets.Property was conspicuously absent. This seemed like a "Catch 22". After several days of experimentation, I finally tried this version of the above code:

public override SomeType SomeInheritedProperty
{
    [SecurityCritical]
    get
    {
        return someValue;
    }
}

The difference is subtle. I can only assume that getters and setters, individually, fall under the control of AttributeTargets.Method.

Implementing SecurityTransparent Interfaces

Here are some examples of interface implementations that worked for me:

IDisposable

using System.Security;

...

[SecuritySafeCritical]
public void Dispose()
{
    Dispose(true);
    GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
    if (!mDisposed)
    {
        if (disposing)
        {
            if (mSomeResource != null)
                mSomeResource.Dispose(); // or mSomeResource.Close();,
                                         // as appropriate
        }

        mSomeResource = null;
        mDisposed = true;
    }
}

IEnumerable<T> and IEnumerable

using System.Security;

...

[SecuritySafeCritical]
public IEnumerator<SomeContainedType> GetEnumerator()
{
    for (int i = 0; i < Count; ++i)
    {
        yield return mSomeArray[i];
    }
}

[SecuritySafeCritical]
IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

IComparable

using System.Security;

...

[SecuritySafeCritical]
public int CompareTo(ThisType other)
{
    if (other == null)
        throw new ArgumentNullException("other");

    if (SomeProperty == other.SomeProperty)
    {
        if (SomeOtherProperty == other.SomeOtherProperty)
            return 0;
        else if (SomeOtherProperty < other.SomeOtherProperty)
            return -1;
        else
            return 1;
    }
    else if (SomeProperty < other.SomeProperty)
        return -1;
    else
        return 1;
}

IEquatable

using System.Security;

...

// Notice that I carefully avoid using "==" to
// compare instances of ThisType to each other or
// to null.  This avoids infinite recursion.
// Don't go there.  It's painful to debug.

[SecuritySafeCritical]
public bool Equals(ThisType other)
{
    return (this.CompareTo(other) == 0);
}

[SecuritySafeCritical]
public override bool Equals(Object obj)
{
    if (Object.ReferenceEquals(obj, null))
        return false;

    ThisType other = obj as ThisType;

    if (Object.ReferenceEquals(other, null))
        return false;
    else
        return Equals(other);
}

[SecuritySafeCritical]
public override int GetHashCode()
{
    return SomeProperty.GetHashCode()
         ^ SomeOtherProperty.GetHashCode();
}

public static bool operator ==(ThisType instance1,
                               ThisType instance2)
{
    if (Object.ReferenceEquals(instance1, instance2))
        return true;

    if (Object.ReferenceEquals(instance1, null))
        return false;

    if (Object.ReferenceEquals(instance2, null))
        return false;

    return instance1.Equals(instance2);
}

public static bool operator !=(ThisType instance1, ThisType instance2)
{
    return !(instance1 == instance2);
}

public static bool operator <(ThisType instance1, ThisType instance2)
{
    if (Object.ReferenceEquals(instance1, instance2))
        return false;

    if (Object.ReferenceEquals(instance1, null))
        return false;

    if (Object.ReferenceEquals(instance2, null))
        return false;

    return (instance1.CompareTo(instance2) < 0);
}

public static bool operator >(ThisType instance1, ThisType instance2)
{
    if (Object.ReferenceEquals(instance1, instance2))
        return false;

    if (Object.ReferenceEquals(instance1, null))
        return false;

    if (Object.ReferenceEquals(instance2, null))
        return false;

    return (instance1.CompareTo(instance2) > 0);
}

IXmlSerializable

There's another article's worth of material in the implementation of this interface. Perhaps later.

using System.Security;

...

public override System.Xml.Schema.XmlSchema GetSchema()
{
    return null;
}

[SecuritySafeCritical]
public override void ReadXml(XmlReader reader)
{
    if (reader == null)
        throw new ArgumentNullException("reader");

    // ...
}

[SecuritySafeCritical]
public override void WriteXml(XmlWriter writer)
{
    if (writer == null)
        throw new ArgumentNullException("writer");

    // ...
}

Oops ...

Just when it looked as if all of the relevant issues had been resolved, reality set in. While the above scenario is a valid representation of things that need to be done in order to implement an unhosted application with a SecurityCritical attribute at the assembly level, the story doesn't end here.

Running my program produced the error message:

Method 'SecurityTest.MainWindow.InitializeComponent()' is security transparent, but is a member of a security critical type.

The Code Analyzer did not catch this as an error or as a warning.

WPF windows are typically split into two partial classes, one of which is generated by compiling a XAML representation of the window. InitializeComponent is part of the generated code. I would have attached a SecurityCritical attribute to InitializeComponent, but there is no realistic way to attach security attributes to the generated code. Rebuilding your code, at least a complete rebuild or a rebuild after modifying the XAML, replaces the generated code. The Data classes provide a hack that lets you attach an attribute to a class that associates a separate class with the original class. This separate class contains methods that parallel generated methods in the original class. The attributes of these parallel methods are effectively applied to the generated methods. Unfortunately, this mechanism is not implemented for Window-derived classes.

Plan B

At this point, I decided to go back to using the AllowPartiallyTrustedCaller attribute at the assembly level. I removed all of the SecurityCritical and SecuritySafeCritical attributes from my code. If I could contain the LinkDemand for FullTrust to the one class that directly references the FileSystemTracker class, most of the complications would go away, so I marked that one class with the SecuritySafeCritical attribute to allow it to reference the FileSystemTracker class.

This approach does not introduce any security loopholes, because the code can only be executed with FullTrust. The only code that could load it and use its entry points would also have to run with FullTrust. If you have someone else's code running on your system with FullTrust, it can already do anything it wants. Using your entry points doesn't buy it anything new.

Unfortunately, this approach generates a series of CA2122 warnings telling me that each of the public members of my SecuritySafeCritical class expose various methods of the FileSystemWatcher class, which has a LinkDemand. After a fair amount of experimentation, I finally figured what I had to do to satisfy this LinkDemand and the Code Analyzer. I added a Demand (not a LinkDemand) for FullTrust. My SecuritySafeCritical class is now decorated as follows:

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

...

[SecuritySafeCritical]
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class MySecuritySafeCricalClass : IDisposable
{
...
}

There was only one other place in my code where I had to add a security attribute. I have a ProgressWindow class that uses PlatformInvoke to modify the window style. The method that does this now looks as follows:

using System.Security;

...

[SecuritySafeCritical]
private void ProgressWindow_Loaded(object sender, RoutedEventArgs e)
{
    if (!mLoaded)
    {
        mLoaded = true;
        IntPtr hWnd = new WindowInteropHelper(this).Handle;
        SetWindowLong(hWnd, GWL_STYLE, GetWindowLong(hWnd, GWL_STYLE) & ~WS_SYSMENU);
    }
}

Points of Interest

Along the way, I discovered a lot of things about trying to mark as much of my code as possible as internal with the assembly marked with the AllowPartiallyTrustedCaller attribute. I discovered that this approach was useless in my situation: All of the classes that implement public interfaces need to be public. The use of FileSystemWatcher cannot be contained by making everything internal. You can't make your start-up window internal if your App class is implemented using XAML. Such is life. However, as you can see in Plan B, above, the use of FileSystemWatcher can be contained by using a combination of the SecuritySafeCritical and PermissionSet attributes.

Conclusion

Even though Level 2 Security is a big improvement over Level 1 Security, implementing it is not without its pitfalls. There is a lot of room for improvement in the documentation. I hope that this article helps to ease your journey, especially when developing desktop applications designed to run with full trust. I think that this exercise has also taught me a lot about Level 2 Security in general, and would make it easier to implement hosted assemblies designed to run in either a SecurityTransparent environment or an AllowPartiallyTrustedCaller environment.

History

  • 2/18/12: "Background" section expanded. "Oops ..." and "Plan B" sections added.
  • 2/14/12: Original version submitted.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ronald M. Martin
Retired
United States United States
Began programming at the age of 15 in 1961.
 
As a student at Dartmouth College, was one of the developers of three computer time-sharing systems.
 
In 1969, was a founder of The Cyphernetics Corporation, a computer time-sharing services company, purchased by Automatic Data Processing (ADP) in 1975.
 
Spent most of career working in and around the automotive industry, working in areas such as Gauging, Size Control, Balancing and Paint Quality Measurement.
 
For amusement, creates software development tools and explores an ever-widening range of specialties.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberSpliffa21-Aug-12 4:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140421.2 | Last Updated 16 Aug 2012
Article Copyright 2012 by Ronald M. Martin
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid