Securing AppDomain Data

While we're on the topic of AppDomains ...

One feature of AppDomains that many people don't know about is that they expose a property dictionary of string/object pairs.  This dictionary is exposed through the GetData / SetData pair of methods.  In the remarks for the GetData documentation you'll find that the dictionary comes prepopulated with data such as the name of the configuration file and the application base.

GetData and SetData can be very useful as a quick and easy way to pass data between AppDomains.  Simply agree upon a key name and pass a serializable or MarshalByRefObject as the object data and you've got a simple way to provide configuration data to classes in the new AppDomain.  One scenario I use this for is to maintain a "singleton" object that has one instance per application, rather than the traditional one instance per AppDomain.  To pull that off, the singleton must derive from MarshalByRefObject.  When a new AppDomain is created, the singleton instance is pushed into an AppDomain data slot using SetData.  Finally, in the new AppDomain, rather than creating a new instance of the singleton object, you simply pull the value out of the AppDomain data using GetData.

public sealed class Singleton : MarshalByRefObject
{
    private static Singleton instance = null;
    
    public static Singleton Instance
    {
        get
        {
            if(instance == null)
            {
                // try to pull out of the AppDomain data
                instance = AppDomain.CurrentDomain.GetData("SingletonInstance") as Singleton;

                // if it wasn't there, we're the first AppDomain
                // so create the instance
                if(instance == null)
                    instance = new Singleton();
            }

            return instance;
        }
    }

    //
    // ...
    //
}

// when creating a new domain ...
AppDomain newDomain = AppDomain.CreateDomain("Second domain");
newDomain.SetData("SingletonInstance", Singleton.Instance);

// transfer control to the domain ...

Of course, this can be done much easier with the help of an AppDomainManager which could hook every call to CreateDomain to ensure that all domains get the Singleton reference set.  Notice that having a null Instance and null AppDomainData can only occur in the default domain, since we access the Instance property to set the data in every other domain, which has the effect of creating an Instance if one was not created already.  Obvious improvements would be support for multithreading or lazy creation of the instance.

Speaking of AppDomainManagers, one of the primary uses I have for the AppDomain data store is that I can keep track of the AppDomainManager in my default AppDomain.  If every instance of my AppDomainManager has a reference to this instance, they can then delegate work such as CreateDomain in order to ensure that my domain creation policy is followed.  For instance, if I wanted to have each request to create an AppDomain that has the same StrongName evidence to map to the same AppDomain, the AppDomainManager in my default domain could simply keep a dictionary of strong names -> AppDomains.  However, if I didn't delegate this work, each AppDomainManager would have its own dictionary, and several domains could be created for a single StrongName.

The obvious solution of simply putting the AppDomainManager instance in an AppDomainData slot, and have each new AppDomainManager pull that instance out in it's InitializeNewDomain method.  However, this solution has a flaw -- access to the AppDomainManager is currently a protected operation.  AppDomain::DomainManager is protected by ControlDomainPolicy.  Since AppDomain::GetData does not have any demand on it, partially trusted code could go ahead and read out the domain manager instance -- something they normally could not do.

To solve this problem, Whidbey introduces a new overload to SetData which takes a third parameter, an IPermission that the data should be protected with.  Whenever data protected in this way is accessed through GetData, a demand for the IPermission is done to ensure that the caller should be able to look at the data stored in the property.

Properly storing the default domain AppDomainManager instance would then look something like this:

public sealed class MyAppDomainManager : AppDomainManager
{
    private MyAppDomainManager defaultDomainManager;
    
    public override AppDomain CreateDomain(string friendlyName, Evidence securityInfo, AppDomainSetup appDomainInfo)
    {
        AppDomain newDomain = CreateDomainHelper(friendlyName, securityInfo, appDomainInfo);
        newDomain.SetData(
            "DefaultDomainManager",
            defaultDomainManager,
            new SecurityPermission(SecurityPermissionFlag.ControlDomainPolicy));

        //
        // ...
        //
        
        return newDomain;
    }

    public override void InitializeNewDomain(AppDomainSetup setupInfo)
    {
        if(AppDomain.CurrentDomain.Id == 1)
        {
            // I am the default domain manager
            defaultDomainManager = this;
        }
        else
        {
            // we don't want the demand hitting the domain boundary
            new SecurityPermission(SecurityPermissionFlag.ControlDomainPolicy).Assert();
            defaultDomainManager = AppDomain.CurrentDomain.GetData("DefaultDomainManager") as MyAppDomainManager;
            CodeAccessPermission.RevertAssert();
        }

        //
        // ...
        //
    }
}