/// <summary> /// Destroy the AppDomain. /// </summary> private void DestroyDomain(bool disposing) { Debug.WriteLine("Unloading AppDomain '" + mAppDomain.FriendlyName + "', id=" + mAppDomain.Id + ", disposing=" + disposing); if (mKeepAliveTimer != null) { mKeepAliveTimer.Stop(); mKeepAliveTimer.Dispose(); mKeepAliveTimer = null; } if (mPluginManager != null) { mPluginManager.Dispose(); mPluginManager = null; } if (mAppDomain != null) { // We can't simply invoke AppDomain.Unload() from a finalizer. The unload is // handled by a thread that won't run at the same time as the finalizer thread, // so if we got here through finalization we will deadlock. Fortunately the // runtime sees the situation and throws an exception out of Unload(). // // If we don't have a finalizer, and we forget to make an explicit cleanup // call, the AppDomain will stick around and keep the DLL files locked, which // could be annoying if the user is trying to iterate on extension script // development. // // So we use a workaround from https://stackoverflow.com/q/4064749/294248 // and invoke it asynchronously. if (disposing) { AppDomain.Unload(mAppDomain); } else { new Action <AppDomain>(AppDomain.Unload).BeginInvoke(mAppDomain, null, null); } mAppDomain = null; } }
/// <summary> /// Creates a new AppDomain. If our plugin is just executing /// pre-compiled code we can lock the permissions down, but if /// it needs to dynamically compile code we need to open things up. /// </summary> /// <param name="appDomainName">The "friendly" name.</param> /// <param name="appBaseBath">Directory to use for ApplicationBase.</param> public void CreateDomain(string appDomainName, string appBaseBath) { // This doesn't seem to affect Sponsor. Doing this over in the PluginManager // does have the desired effect, but requires unrestricted security. //LifetimeServices.LeaseTime = TimeSpan.FromSeconds(5); //LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(3); //LifetimeServices.RenewOnCallTime = TimeSpan.FromSeconds(2); //LifetimeServices.SponsorshipTimeout = TimeSpan.FromSeconds(1); if (mAppDomain != null) { throw new Exception("Domain already created"); } PermissionSet permSet; // Start with everything disabled. permSet = new PermissionSet(PermissionState.None); //permSet = new PermissionSet(PermissionState.Unrestricted); // Allow code execution. permSet.AddPermission(new SecurityPermission( SecurityPermissionFlag.Execution)); // This appears to be necessary to allow the lease renewal to work. Without // this the lease silently fails to renew. permSet.AddPermission(new SecurityPermission( SecurityPermissionFlag.Infrastructure)); // Allow changes to Remoting stuff. Without this, we can't // register our ISponsor. permSet.AddPermission(new SecurityPermission( SecurityPermissionFlag.RemotingConfiguration)); // Allow read-only file access, but only in the plugin directory. // This is necessary to allow PluginLoader to load the assembly. FileIOPermission fp = new FileIOPermission( FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, appBaseBath); permSet.AddPermission(fp); // TODO(maybe): it looks like this would allow us to mark the PluginCommon dll as // trusted, so we wouldn't have to give the above permissions to everything. // That seems to require a cryptographic pair and some other voodoo. //StrongName fullTrustAssembly = // typeof(PluginManager).Assembly.Evidence.GetHostEvidence<StrongName>(); // Configure the AppDomain. Setting the ApplicationBase directory away from // the main app location is apparently very important, as it mitigates the // risk of certain exploits from untrusted plugin code. AppDomainSetup adSetup = new AppDomainSetup(); adSetup.ApplicationBase = appBaseBath; // Create the AppDomain. mAppDomain = AppDomain.CreateDomain(appDomainName, null, adSetup, permSet); Debug.WriteLine("Created AppDomain '" + appDomainName + "', id=" + mAppDomain.Id); //Debug.WriteLine("Loading '" + typeof(PluginManager).Assembly.FullName + "' / '" + // typeof(PluginManager).FullName + "'"); // Create a PluginManager in the remote AppDomain. The local // object is actually a proxy. PluginManager pm = (PluginManager)mAppDomain.CreateInstanceAndUnwrap( typeof(PluginManager).Assembly.FullName, typeof(PluginManager).FullName); // Wrap it so it doesn't disappear on us. mPluginManager = new Sponsor <PluginManager>(pm); Debug.WriteLine("IsTransparentProxy: " + System.Runtime.Remoting.RemotingServices.IsTransparentProxy(pm)); }