none
Loading plugins from a partial-trust Application Domain RRS feed

  • Question

  • Hello. I am having some difficulty understanding the documentation and figuring out how to do the subject. My intent is to limit the extent to which third-party plugins for my application could potentially affect the user's computer.

    The logical answer is to load plugins (in the form of DLLs) into an AppDomain specifically set up with limited trust, such that even if the plugin did attempt to perform some malicious activity, the OS would not allow it.

    However, simple as this idea is, I am having trouble after trouble. Most recently I stumbled upon this gem in the documentation for AppDomain.Load():

    "If the current AppDomain object represents application domain A, and the Load method is called from application domain B, the assembly is loaded into both application domains."

    This just confounded me. I am at a loss as to the best way to go about doing this. I have scoured the available documentation on the subject, and while there appear to be examples for each minor part individually (e.g. setting up a partial trust app domain, loading assemblies into an app domain, calling methods on a loaded assembly) I can't seem to find an example that does all of these things together, and can't seem to make any headway on my own.

    Currently I am using this code to establish a secure AppDomain and load a plugin from a known directory:

    Code Snippet

            private static AppDomain safeDomain;
            internal static AppDomain SafeDomain {
                get {
                    if ( safeDomain == null ) {
                        AppDomainSetup setup = new AppDomainSetup();
                        setup.ApplicationBase = RossApp.Path;
                        setup.PrivateBinPath = "\\Plugins\\Customization";
                        //setup.PrivateBinPath = "Plugins\\Customization\\";
                        safeDomain = AppDomain.CreateDomain( "FeedBeast Customization", null, setup );

                        PermissionSet permissionSet = new PermissionSet( PermissionState.None );
                        permissionSet.AddPermission( new SecurityPermission( SecurityPermissionFlag.Execution ) );
                        PolicyLevel policyLevel = PolicyLevel.CreateAppDomainLevel();
                        policyLevel.RootCodeGroup.PolicyStatement = new PolicyStatement( permissionSet );

                        safeDomain.SetAppDomainPolicy( policyLevel );
                    }
                    return safeDomain;
                }
            }

            private static bool initialized = false;
            private static ICustomizerPlugin customizerPlugin = null;
            public static ICustomizerPlugin CustomizerPlugin {
                get {
                    if ( initialized )
                        return customizerPlugin;

                    try {
                        initialized = true;
                        // look for any files in the /Plugins/Customization directory
                        string[] files = Directory.GetFiles( RossApp.CustomizerDirectory, "*.dll", SearchOption.TopDirectoryOnly );
                        // since we can only support one customizer at a time, we'll simply take the first (valid) one
                        foreach ( string file in files ) {
                            try {
                                //AssemblyName name = AssemblyName.GetAssemblyName( file );
                                Assembly plugin = SafeDomain.Load( file );
                                //Assembly plugin = Assembly.ReflectionOnlyLoadFrom( file );//Assembly.ReflectionOnlyLoad( name.FullName );
                                foreach ( Type type in plugin.GetTypes() ) {
                                    if ( type.GetInterface( typeof( ICustomizerPlugin ).Name ) != null ) {
                                        customizerPlugin = (ICustomizerPlugin)SafeDomain.CreateInstanceFromAndUnwrap( file, type.FullName );
                                        return customizerPlugin;
                                    }
                                }
                            } catch {
                                continue;
                            }
                        }
                        return null;
                    } catch {
                        return null;
                    }
                }
            }



    Howevery as my various commented-out lines indicate, I am having difficulty even loading the assembly. Then I discovered the part about Load() loading the assembly into both AppDomains, which would completely defeat the purpose of the secure context in the first place!

    Could someone please point me in the right direction, perhaps to some existing documentation/examples that would cover my needs? This does not have to be overly complicated, I just want to make sure an instance of my plugin interface executes in a secure domain when called.

    Thank you,

    Logan Murray
    Wednesday, March 19, 2008 12:24 AM

Answers

  • I think maybe you can write a proxy assembly loaded in both domains and let it load the assembly in the seperate domain.

    And even you cannot use the Addin model, you can check out the CLR AddIn Model in Paint.Net series on
    Jason He's WebLog to adopt it's ideas.

    Thursday, March 20, 2008 8:02 AM
  • Thanks I might check that out. In the meantime I seem to have gotten this to work, so long as the subclass of ICustomizerPlugin inherits from MarshalByRefObject:

    Code Snippet

    Assembly plugin = Assembly.ReflectionOnlyLoadFrom( path );
    foreach ( Type type in plugin.GetExportedTypes() ) {
        if ( type.GetInterface( typeof( ICustomizerPlugin ).Name ) != null ) {
            customizerPlugin = (ICustomizerPlugin)SafeDomain.CreateInstanceFromAndUnwrap( file, type.FullName );
            return customizerPlugin;
        }
    }



    Logan

    Sunday, March 23, 2008 12:38 PM

All replies

  • With .NET 3.5 comes the System.Addin library which tackles these kind of problems...
    Wednesday, March 19, 2008 7:14 AM
  • Good to know, however I'm using .NET 2.0 at the moment.
    Wednesday, March 19, 2008 10:32 AM
  • I think maybe you can write a proxy assembly loaded in both domains and let it load the assembly in the seperate domain.

    And even you cannot use the Addin model, you can check out the CLR AddIn Model in Paint.Net series on
    Jason He's WebLog to adopt it's ideas.

    Thursday, March 20, 2008 8:02 AM
  • Thanks I might check that out. In the meantime I seem to have gotten this to work, so long as the subclass of ICustomizerPlugin inherits from MarshalByRefObject:

    Code Snippet

    Assembly plugin = Assembly.ReflectionOnlyLoadFrom( path );
    foreach ( Type type in plugin.GetExportedTypes() ) {
        if ( type.GetInterface( typeof( ICustomizerPlugin ).Name ) != null ) {
            customizerPlugin = (ICustomizerPlugin)SafeDomain.CreateInstanceFromAndUnwrap( file, type.FullName );
            return customizerPlugin;
        }
    }



    Logan

    Sunday, March 23, 2008 12:38 PM
  • Thanks for sharing this!
    You did great job!
    Tuesday, March 25, 2008 6:23 AM