I have some code for loading an assembly into a sandboxed AppDomain (PermissionSet is initiallized with PermissionState.None). I want to pass data back and forth from my main AppDomain to assemblies in the sandboxed AppDomain. By making my data extend MarshalByRefObject, it's able to be passed by reference. The problem is that these objects then hang around in memory. I'd like to have them get collected as quickly as possible (within a few seconds in most cases). When I try to override InitializeLifetimeService I get a runtime error.
I've put together some sample code that demonstrates this problem. My code is organized into three projects:
Class Library: Sandbox (this must be signed)
File Name: Sandbox.cs (based on this stackoverflow answer)
using System;
using System.IO;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
namespace Sandbox
{
public class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
Assembly assembly;
public Sandbox()
{
}
public static Sandbox Create()
{
var setup = new AppDomainSetup()
{
ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory),
ApplicationName = DomainName,
DisallowBindingRedirects = true,
DisallowCodeDownload = true,
DisallowPublisherPolicy = true
};
var permissions = new PermissionSet(PermissionState.None);
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions,
typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>());
return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap();
}
public void LoadAssembly(string assemblyPath)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
this.assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
}
public object CreateInstance(string typeName)
{
Type type = this.assembly.GetType(typeName);
return Activator.CreateInstance(type);
}
public object InvokeStaticMethod(string typeName, string methodName, params object[] parameters)
{
Type type = this.assembly.GetType(typeName);
return type.GetMethod(methodName).Invoke(null, parameters);
}
public override object InitializeLifetimeService()
{
return null; // sandboxes don't expire
}
}
}
Class Library: Plugins
File Name: Plugin.cs
namespace Plugins
{
public static class Plugin
{
public static string DoSomething(SharedData data)
{
data.Set("updated", "yes");
return data.Get("name");
}
}
}
File Name: SharedData.cs
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Lifetime;
namespace Plugins
{
public class SharedData : MarshalByRefObject
{
Dictionary<string, string> dict = new Dictionary<string, string>();
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromMinutes(1);
lease.RenewOnCallTime = TimeSpan.FromMinutes(1);
lease.SponsorshipTimeout = TimeSpan.FromMinutes(1);
}
return lease;
}
public void Set(string key, string value)
{
this.dict[key] = value;
}
public string Get(string key)
{
if (this.dict.ContainsKey(key))
{
return this.dict[key];
}
return null;
}
}
}
Console Project: Example
File Name: Program.cs
using System;
using System.IO;
namespace Example
{
class Program
{
static void Main(string[] args)
{
// create sandbox domain
var sandbox = Sandbox.Sandbox.Create();
// load a "plugin" assembly into it
string assemblyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Plugins.dll");
sandbox.LoadAssembly(assemblyPath);
// create some data in the sandbox
var data = (Plugins.SharedData)sandbox.CreateInstance("Plugins.SharedData");
data.Set("name", "Nogwater");
// pass some data into the plugin
object objRet = sandbox.InvokeStaticMethod("Plugins.Plugin", "DoSomething", data);
// get updates back from the plugin
string updated = data.Get("updated");
Console.WriteLine("{0} {1}", objRet, updated);
}
}
}
The error happens in Sandbox.CreateInstance
on the line that calls this.assembly.GetType()
. The message that I get is:
Inheritance security rules violated while overriding member: 'Plugins.SharedData.InitializeLifetimeService()'. Security accessibility of the overriding method must match the security accessibility of the method being overriden.
It seems that InitializeLifetimeService requires more security than I want to give to the untrusted plugin. I get a similar error if I define the SharedData class outside the Plugins library and try to pass it in (once it tries to marshal the object by reference).
Is there any way to make this work without having to resort to copy-by-value and serialization?
Edit: FYI, I did end up using serialization. It's a pain, especially for the places where data gets changed and I have to serialize again to return it, but at least it works. I'd be curious to know if anyone can get this working with marshaling.