I am using a separate AppDomain
as a sandbox, and trying to execute a method that is constructed via reflection.
When the method is invoked, a
SecurityException
...is thrown, even though the sandbox AppDomain
has ReflectionPermission(PermissionState.Unrestricted)
set on its PermissionSet
.
The invocation does work when the PermissionSet
is set to PermissionState.Unrestricted
, but that defeats the purpose of a sandbox.
Here's an example that demonstrates the issue:
using System;
using System.Security;
using System.Security.Permissions;
namespace ConsoleTest
{
class Program
{
static void Main(string[] args)
{
var person = new Person();
var program = new Program();
var customDomain = program.CreateDomain();
var result = program.Execute(customDomain, (x) =>
{
var type = x.GetType();
var propertyInfo = type.GetProperty("Name");
var method = propertyInfo.GetMethod;
var res = method.Invoke(x, null) as string;
return res;
}, person);
Console.WriteLine(result);
Console.ReadLine();
}
public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
{
var proxy = new Proxy(toExecute, parameters);
var result = proxy.Invoke(domain);
return result;
}
private AppDomain CreateDomain()
{
var appDomainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
ApplicationName = "UntrustedAppDomain"
};
// Set up permissions
var permissionSet = new PermissionSet(PermissionState.None);
permissionSet.AddPermission(new SecurityPermission(PermissionState.Unrestricted));
permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));
// Create the app domain.
return AppDomain.CreateDomain("UntrustedAppDomain", null, appDomainSetup, permissionSet);
}
private sealed class Proxy : MarshalByRefObject
{
private Delegate method;
private object[] args;
private object result;
public Proxy(Delegate method, params object[] parameters)
{
this.method = method;
this.args = parameters;
}
public object Invoke(AppDomain customDomain)
{
customDomain.DoCallBack(Execute);
return this.result;
}
private void Execute()
{
this.result = this.method.DynamicInvoke(this.args);
}
}
}
public class Person
{
public Person()
{
this.Name = "Test Person";
}
public string Name { get; set; }
}
}
This is an addition to my comment above
SecurityException when executing reflected method in sandbox AppDomain
You are attempting to execute a method on an object that was created in the primary AppDomain from the context in the secondary domain. In other words, code in the sandbox is trying to call code in the primary domain which is not allowed. When using sandboxes via AppDomains
, Proxy
must be created in the sandbox domain via CreateInstanceAndUnwrap
from the primary app domain.
Change this:
public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
{
var proxy = new Proxy(toExecute, parameters);
var result = proxy.Invoke(domain);
return result;
}
...to:
public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
{
var t = typeof(Proxy); // add me
var args = new object[] {toExecute, parameters};
var proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false,
BindingFlags.Default,
null,
args,
null,
null) as Proxy; // add me
//var proxy = new Proxy(toExecute, parameters);
var result = proxy.Invoke(domain);
return result;
}
...and make Person
either [Serializable]
or derive from MarshalByRefObject
depending on whether you want to transfer copies to or pass indirectly modifyable objects to the sandbox respectively.
Thank you for your reply. The above sample code can work if the Proxy is created in the sandbox domain, and the Person class is Serializable, but then the person object used in the secondary domain is indeed not the same instance as the one in the primary domain - it gets serialized in the CreateInstanceAndUnwrap method
That's correct and is by design. Note your Person
as shown in your post isn't marked as Serializable
so that would have resulted in an error. I'm assuming you fixed that. If you want the "same" object to be passed about have the class derive from MarshalByRefObject
.
Here is the complete code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
namespace Sandboxes1
{
class Program
{
static void Main(string[] args)
{
var person = new Person();
Console.WriteLine("[{0}] Person's current name: {1}", AppDomain.CurrentDomain.FriendlyName, person.Name);
var program = new Program();
var customDomain = program.CreateDomain();
var result = program.Execute(customDomain, (x) =>
{
Console.WriteLine("[{0}] Inside delegate", AppDomain.CurrentDomain.FriendlyName);
var type = x.GetType();
var propertyInfo = type.GetProperty("Name");
var method = propertyInfo.GetMethod;
var res = method.Invoke(x, null) as string;
dynamic d = x;
d.Name = "Fozzy Bear";
Console.WriteLine("[{0}] delegate changed person's name to- {1}", AppDomain.CurrentDomain.FriendlyName, d.Name);
return res;
}, person);
Console.WriteLine("[{0}] Result: {1}", AppDomain.CurrentDomain.FriendlyName, result);
Console.WriteLine("[{0}] Person's current name: {1}", AppDomain.CurrentDomain.FriendlyName, person.Name);
Console.ReadLine();
}
public object Execute(AppDomain domain, Func<object, object> toExecute, params object[] parameters)
{
var t = typeof(Proxy); // add me
var args = new object[] {toExecute, parameters};
var proxy = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false,
BindingFlags.Default,
null,
args,
null,
null) as Proxy; // add me
//var proxy = new Proxy(toExecute, parameters);
var result = proxy.Invoke(domain);
return result;
}
private AppDomain CreateDomain()
{
var appDomainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
ApplicationName = "UntrustedAppDomain"
};
// Set up permissions
var permissionSet = new PermissionSet(PermissionState.None);
permissionSet.AddPermission(new SecurityPermission(PermissionState.Unrestricted));
permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));
// Create the app domain.
return AppDomain.CreateDomain("UntrustedAppDomain", null, appDomainSetup, permissionSet);
}
private sealed class Proxy : MarshalByRefObject
{
private Delegate method;
private object[] args;
private object result;
public Proxy(Delegate method, params object[] parameters)
{
Console.WriteLine("[{0}] Proxy()", AppDomain.CurrentDomain.FriendlyName);
this.method = method;
this.args = parameters;
}
public object Invoke(AppDomain customDomain)
{
Console.WriteLine("[{0}] Invoke()", AppDomain.CurrentDomain.FriendlyName);
customDomain.DoCallBack(Execute);
return this.result;
}
private void Execute()
{
Console.WriteLine("[{0}] Execute()", AppDomain.CurrentDomain.FriendlyName);
this.result = this.method.DynamicInvoke(this.args);
}
}
}
[Serializable]
public class Person
{
private string _name;
public Person()
{
Name = "Test Person";
}
public string Name
{
get
{
Console.WriteLine("[{0}] Person.getName()", AppDomain.CurrentDomain.FriendlyName);
return _name;
}
set { _name = value; }
}
}
}
...which produces the following output:
It is up to your design whether you want code in a sandbox to modify objects from another appdomain. In my opinion that is a security risk and arguably negates the purpose of sandboxes. So if you must pass inter-domain objects about, pass them as Serializable
where copies of objects are passed about.
The approach I tend to use in my code is to:
Manager
class that creates a secondary AppDomain
Manager
uses CreateInstanceAndUnwrap
to create a MarshalByRefObject
Proxy
class in the secondary AppDomain
Manager
calls into Proxy
to say LoadPlugins()
. This means that any plug-in created objects now automatically exist in the secondary AppDomain
Proxy
's methods that are called from the primary AppDomain are enclosed in try-catch
. Do not let any exception bubble up to the primary AppDomain because it's quite possible that dodgy code from a dubious plugin could throw a custom exception that, when finalising can execute any code it wishes with full permissions due to the call stack now being on the primary AppDomain. This is one of the few occassions in .NET programming where you must catch
everything and don't throw
Proxy
and have it call the method on your behalf. Again ensure that this proxy method is fully wrapped in a try-catch
allI wasn't aware of AppDomain.DoCallBack
and now that I see it I don't like it. There is too much risk in the delegate accidentally using objects it shouldn't particularly where the delegate code is defined in a method that will be a mixture of some code executed by one domain; other parts by another domain.