4

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.

Community
  • 1
  • 1
Nogwater
  • 2,777
  • 3
  • 28
  • 28
  • You could provide your own `MarshalByRefObject`-deriving base-type in your plugin manager, which would be trusted. Plugins would derive from this without overriding `InitializeLifetimeService`. Depending on your requirements, you would allow your plugins to specify parameters deemed safe enough by you. – Luaan Jan 21 '16 at 13:11

0 Answers0