SecurityException instantiating a type defined by a script running in sandboxed AppDomain

Jun 2, 2011 at 5:49 AM

Hi all,

I'm currently trying out the DLR with IronPython -- in particular I'm trying to prototype a scenario where a user defined (and therefore untrustworthy) python script defines a class which extends a class/interface in the current Assembly, which can be instantiated and interacted with via a reference who's static type is that of the original interface/class. I've sandboxed the ScriptRuntime in its own AppDomain, unfortunately I can't seem to get it working with anything less than a completely unrestricted permission set. The example I'm working with is basically cobbled together froma couple of other examples I found elsewhere:

I could be doing something blatantly wrong here -- I'm a complete noob at just about everything involved here -- but any help would be greatly appreciated:

Code looks like:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Security.Policy;
using System.Security.Permissions;
using System.Security;
using System.IO;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
using System.Runtime.Remoting;

namespace python_in_appdomain
{
    class Program
    {
        static void Main(string[] args)
        {
            Remoting();
        }

        public static void Remoting()
        {
            AppDomainSetup setup = new AppDomainSetup();
            Assembly thisAssembly = Assembly.GetExecutingAssembly();
            setup.ApplicationBase = Path.GetDirectoryName(thisAssembly.Location);

            AssemblyName name = typeof(IFoo).Assembly.GetName();
            StrongName sn = new StrongName(
                new StrongNamePublicKeyBlob(name.GetPublicKey()),
                name.Name,
                name.Version
            );

            PermissionSet pset = new PermissionSet(PermissionState.None);
            pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
            pset.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));

            setup.PartialTrustVisibleAssemblies = new[] { sn.Name + ", PublicKey=" + sn.PublicKey.ToString() };
            AppDomain domain = AppDomain.CreateDomain("Sandbox", AppDomain.CurrentDomain.Evidence, setup, pset, sn);
                        
            String script = File.ReadAllText("Foo.py");

            ScriptRuntime py = Python.CreateRuntime(domain);
            ScriptEngine engine = py.GetEngine("py");
            ScriptScope scope = engine.CreateScope();
            engine.Execute(script, scope);

            ObjectHandle subclass = scope.GetVariableHandle("Foo");
            ObjectHandle instance = engine.Operations.Invoke(subclass, new ObjectHandle[] { });
            IFoo f = instance.Unwrap() as IFoo;
            f.bar();

            Console.ReadLine();
        }
    }

    public interface IFoo
    {
        void bar();
    }

    public abstract class FooBase : MarshalByRefObject, IFoo
    {
        [SecurityCritical]
        public override object InitializeLifetimeService()
        {
            return null;
        }

        public abstract void bar();
    }
}

and the contents of Foo.py is:
import clr
clr.AddReference('python-in-appdomain')

from python_in_appdomain import FooBase

class Foo(FooBase):
    def bar(self):
        print 'Hello, world!'


The call to engine.Operations.Invoke is where the security exception is thrown from -- I've tried playing around with the permission set given to the AppDomain, but none of that has worked, and I've been unable to otherwise determine what it is exactly that I'm missing. The code runs fine if the PermissionState of the PermissionSet passed to the AppDomain is set to Unrestricted, but I've been completely unable to get anything other than that working.

Thanks in advance.

Jun 2, 2011 at 1:43 PM

I get a TypeLoadException at Engine.Execute and the specific message is:

"Inheritance security rules violated while overriding member:
'python_in_appdomain.FooBase.InitializeLifetimeService()'.
Security accessibility of the overriding method must match
the security accessibility of the method being overriden."

This exception makes sense when the permission set is None since the
InitializeLifetimeService method is marked [SecurityCritical].

If I comment that override out, I get a SecurityException at engine.Operations.Invoke
with a Demand of:

"<PermissionSet class="System.Security.PermissionSet"version="1"Unrestricted="true"/>"

That demand is coming from the MarshalByRefObject base class. I'll work up a more
thorough example and upload this afternoon.

By the way, the Foo.py script you provided should add a reference to 'python_in_appdomain'
not 'python-in-appdomain'.

Kevin 

Jun 3, 2011 at 3:48 AM

I still can't get the code to run. I now understand your frustration. Although I must say that in all the IPy hosting
scenarios I've implmented, I've never tried to subclass a POCO object in Python and then use it back in C#.

To get this working, I even tried creating a restricted PermissionSet and adding all known permissions to it with
PermissionState.Unrestricted in the constructor of each IPermission-derived object. A PermissionSet that's built
this way isn't truly unrestricted but it contains all the standard .NET permissions in an unrestricted form. I thought
that by doing this, I'd be able to make the code work then remove them one at a time to see which demand
was failing. No such luck, though. The Demanded property of the SecurityException that's thrown is marked
Unrestricted="true" so it must truly be demanding full trust. The fact that the FirstPermissionThatFailed
property is null is further proof of that. No particular permission is failing. It's just demanding that the
in force PermissionSet is marked as unrestricted. By the way, the TypeLoadException I was getting is based
on changes in the .NET 4.0 security model that disallow so-called transparent code from invoking or
inheriting security critical code. This is called SecurityRuleSet.Level2. If you want to compile for .NET 4.0 but
use the transparency rules from .NET 2.0, you can set it with the following assembly level attribute:

[assemblySecurityRules(SecurityRuleSet.Level1)]

I'm anxious to see if anyone from Microsoft or the community will comment on this thread. This would be
an interesting example to show in Chapter 7 of my book.

Kevin

 

Jun 3, 2011 at 4:48 AM

Hi Kevin,

thanks for giving it a go for me. I've basically been stuck on this issue for days now (though to be honest, a lot of that time has been familiarising myself with the underlying principles in order to try and determine what I might be doing wrong). Similarly I tried manually adding an unrestricted instance of all implementers of IPermission I could find, with no success.

To explain the scenario: I'm trying to prototype a way of using the DLR to allow users of the application to provide custom business logic implementations, and even be able to extend types defined in the applications class library for their own purposes, but still be able to interact with the customised types in a type safe way in C# land. To be honest, I'm not really even all that fussed about it being type safe, just so long as the user can actually customise the behaviour of a predefined type.

Regards,

Angus.

Jun 3, 2011 at 2:05 PM

Angus: The more I think about this, the more it makes sense that it's behaving as it does. The subclass is a new type that's been created in an untrusted sandbox. Trying to unwrap and use it from a more secure context shouldn't be allowed. As I said before, in all of the IronPython hosting scenarios I've built so far, I haven't been giving my end users the ability to subclass types that are exposed back in the host application. For the most part, I build object models in C# that my users' scripts manipulate: rules engines and stuff like that. But I can see the value in giving the users the ability to derive from my models for certain types of applications.

If the appdomain in which the subclassed types are unwrapped has restricted and similar permissions as the DLR host domain, what you're trying to do might work. I'll experiment with that concept for an example in my book and post it back here if I have any luck.

Thanks,

Kevin