AppDomain woes



  • So I'm writing a thing (in .NET, like God intended). This thing is supposed to support plugins - looks like a natural fit for MEF, but I also want the plugins to have limited access to the system and run in separate AppDomains.

    Currently, I have three projects - the main project which will call the various plugins, the SDK-ish project with all the necessary interfaces, and the plugin project. Both the main project and the plugin project reference the SDK, but not each other, and they live in separate directories. And that seems to be a bit of a problem.

    In my main project, I want to find a type in the DLL which implements a particular interface from the SDK, instantiate that type and call a method on the instance. I assume the assembly might be malicious, so I don't want to execute any code from it in my own AppDomain (which might run with high permissions), but spin off a separate one.

    So far, here's the code I have:

    public class CrossAppDomainCallbackWrapper : MarshalByRefObject
            {
                public Type DiscoveredType;
                public AssemblyName AssemblyName;
    
                public void DoCallback()
                {
                    var assembly = Assembly.Load(AssemblyName);
                    var types = assembly.GetTypes().Where(y => typeof(ITsptPlugin).IsAssignableFrom(y)).ToList();
                    if (types.Count > 1) throw new MultiplePluginsException(AssemblyName.FullName);
                    if (types.Count == 0) throw new NoPluginException(AssemblyName.FullName);
                    DiscoveredType = types.First();
                }
            }
    
            public static Plugin DiscoverPlugin(string dllPath)
            {
                var directory = Path.GetDirectoryName(dllPath);
                var setup = new AppDomainSetup()
                {
                    ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                    PrivateBinPath = directory + ";" + AppDomain.CurrentDomain.BaseDirectory
                };
                var loadingSetup = new AppDomainSetup()
                {
                    ApplicationBase = directory,
                    PrivateBinPath = directory + ";" + AppDomain.CurrentDomain.BaseDirectory
                };
                //todo set permissions properly
                var domain = AppDomain.CreateDomain(dllPath, AppDomain.CurrentDomain.Evidence, setup);
                var loadingDomain = AppDomain.CreateDomain(dllPath, AppDomain.CurrentDomain.Evidence, loadingSetup);
                var name = AssemblyName.GetAssemblyName(dllPath);
                var wrapper = new CrossAppDomainCallbackWrapper();
                wrapper.AssemblyName = name;
                domain.DoCallBack(new CrossAppDomainDelegate(wrapper.DoCallback));            
                var instance = (ITsptPlugin)loadingDomain.CreateInstanceAndUnwrap(name.FullName, wrapper.DiscoveredType.FullName);
                var manifest = instance.GetManifest();
                return new Plugin
                {
                    PluginName = manifest.PluginName,
                    TypeName = wrapper.DiscoveredType.FullName,
                    DllPath = dllPath,
                    HandledVerbs = manifest.HandledVerbs,
                    Version = manifest.Version
                };
            }        
    

    The major problem is that apparently .NET doesn't give a flying fuck about the PrivateBinPath and I haven't found a good way to avoid spinning up two separate AppDomains. If I use the one with ApplicationBase set to DLL's directory, it crashes on DoCallback because it can't find the .exe with the callback wrapper. If I use the other one, it crashes on CreateInstanceAndUnwrap because it can't find the concrete plugin type.

    I tried some tricks with ReflectionOnlyLoad/ReflectionOnlyLoadFrom, but that crashes due to the DLL referencing the SDK. Is there a better way than discovering the type within one AppDomain and instantiating it in another? Or am I just too paranoid and there's no way Assembly.Load() into my own AppDomain could do anything malicious if I only use it to discover types?



  • @Maciejasjmj

    Maybe the stuff at this page will help? I have toyed with that before and it seems to work.



  • @BrisingrAerowing said in AppDomain woes:

    @Maciejasjmj

    Maybe the stuff at this page will help? I have toyed with that before and it seems to work.

    Almost, but it seems like I don't get the chance to specify the security permissions for the domains, it just protects me from an unhandled exception bringing the app down?



  • @Maciejasjmj since it's open source, that feature could be added easily enough, possibly through an additional attribute.

    I might try that when I get back to my computer (using my iPad right now).



  • Hmm, I seem to remember that the private bin path is a ; separated list of relative directories (i.e. relative to your new appdomain's Base directory). But it's been 12 years ago I last messed with appdomains, so I might be wrong...



  • @robo2 said in AppDomain woes:

    Hmm, I seem to remember that the private bin path is a ; separated list of relative directories (i.e. relative to your new appdomain's Base directory).

    Private assemblies are deployed in the same directory structure as the application. If the directories specified for PrivateBinPath are not under ApplicationBase, they are ignored.

    Eugh, you're right.

    I guess I could force the plugins being copied under the main program's directory, each getting its separate folder to play in...


Log in to reply