1

I'm getting the System.Security.SecurityException The demand was for: <PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true"/> when attempting to create an AppDomain with restricted permissions defined as follows:

var permissionSet = new PermissionSet(PermissionState.None);
    permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, System.Reflection.Assembly.GetExecutingAssembly().Location));
    permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
    permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));

This error message suggests to me that it will accept no less than an unrestricted/full trust environment, but I don't understand why that would be demanded or how to fix it.

I create only one instance of Stub into the new restricted AppDomain:

public interface IHostStub // Implemented by a MarshalByRefObject object in the primary AppDomain
{
    void Ping();
    void SayTime(DateTimeOffset time);
}

// In the restricted AppDomain
class Stub : MarshalByRefObject
{
    public event EventHandler OnQuit;

    public void RequestTime(IHostStub host)
    {    
        host.SayTime(DateTimeOffset.Now);
    }

    public void Quit(IHostStub host)
    {
        if (this.OnQuit != null) 
            this.OnQuit(this, new EventArgs());
    }
}

Can you please explain this error message to me? Perhaps I misunderstand what Unrestricted means. But setting it to Unrestricted seems to give the app domain free reign (as I expect it would) regardless of any permissions I add or don't add to it.

Thanks!

====

In response to @Nicole and to add some discoveries as I work through this, here are two code samples below. This is a tricky scenario - sandboxing plugins - that would take too much code to show all of. The first code sample demonstrates the exception. It has an obvious fix, but it doesn't take into account the whole scenario.

class Program
{
    static void Main(string[] args)
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();

        var permissionSet = new PermissionSet(PermissionState.None);
        permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, currentAssembly.Location));
        permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
        permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));
        permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));

        var appDomain = AppDomain.CreateDomain(
            "Sandboxed",
            null,
            new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
            },
            permissionSet,
            Assembly.GetExecutingAssembly().Evidence.GetHostEvidence<StrongName>());

        try
        {
            var stub = (Stub)appDomain.CreateInstanceFromAndUnwrap(
                currentAssembly.Location,
                typeof(Stub).FullName);
        }
        catch (SecurityException ex)
        {
            Console.WriteLine(ex);
            throw;
        }
    }
}

// In the restricted AppDomain
class Stub : MarshalByRefObject
{
}

In that example, the ApplicationBase is the same as the primary AppDomain. Also, the assembly itself is listed as a FullTrust assembly for the AppDomain. The trivial fix is to remove that full-trust param.

In this next example, for protection against plugins loading host's assemblies, the ApplicationBase is set to a random path that doesn't even need to exist (works fine with PermissionState.Unrestricted, same error if the directory DOES exist). AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve; requires full trust AFAIK so I have to list the assembly with full-trust (which I believe should override any restrictions set on the AppDomain for that trusted assembly but not the others it may load) - this assembly can just be the "loader" and the plugins would be part of another assembly for which the restricted permissions should be enforced. I must custom AssemblyResolve because the directory doesn't exist (or is empty) and because I want to be in control of loading additional assemblies (say, from byte[]s).

class Program
{
    static void Main(string[] args)
    {
        var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();

        var permissionSet = new PermissionSet(PermissionState.None);
        permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, currentAssembly.Location));
        permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
        permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.SerializationFormatter));
        permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));

        string path;
        do
        {
            path = Path.GetTempPath() + Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
        } while (Directory.Exists(path));

        var appDomain = AppDomain.CreateDomain(
            "Sandboxed",
            null,
            new AppDomainSetup
            {
                ApplicationBase = path
            },
            permissionSet,
            Assembly.GetExecutingAssembly().Evidence.GetHostEvidence<StrongName>());

        try
        {
            var stub = (Stub)appDomain.CreateInstanceFromAndUnwrap(
                currentAssembly.Location,
                typeof(Stub).FullName);
        }
        catch (SecurityException ex)
        {
            Console.WriteLine(ex);
            throw;
        }
    }
}

class Stub : MarshalByRefObject
{
    static Stub()
    {
        AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
    }

    public static Assembly AssemblyResolve(object sender, ResolveEventArgs e)
    {
        if (e.Name == Assembly.GetExecutingAssembly().FullName)
            return Assembly.GetExecutingAssembly();
        else
            Console.WriteLine("Unable to load {0}", e.Name);

        return null;
    }
}
Jason Kleban
  • 20,024
  • 18
  • 75
  • 125

1 Answers1

3

Try creating the stub handle via Activator.CreateInstanceFrom instead of AppDomain.CreateInstanceFromAndUnwrap. The permission verifications differ, and using the Activator method should allow you to also avoid adding the extra permissions (besides SecurityPermission\Execution) to your sandboxed app domain. e.g.:

var currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();

var permissionSet = new PermissionSet(PermissionState.None);
permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));

var appDomain = AppDomain.CreateDomain(
    "Sandboxed",
    null,
    new AppDomainSetup { ApplicationBase = CreateFakePath() },
    permissionSet,
    currentAssembly.Evidence.GetHostEvidence<StrongName>());

var stub = (Stub)Activator.CreateInstanceFrom(appDomain, currentAssembly.Location, typeof(Stub).FullName).Unwrap();

var hostStub = new HostStub();
stub.RequestTime(hostStub);
Nicole Calinoiu
  • 20,843
  • 2
  • 44
  • 49
  • Ridiculous! I had played with that method too, but I guess not in the right combination. I gave up on that when I found an article explaining that AppDomain.Create ... just calls the Activator.Create ... methods. Thanks! – Jason Kleban Jan 07 '14 at 19:50
  • One more add-on so I don't have to repeat all this background ... When I call `host.SayTime(DateTimeOffset.Now);` in Stub, it throws an `TypeLoadException` "Inheritance security rules violated while overriding member ..." on something that the host *calls* itself. Even if I decorate the host or its methods with `[SecuritySafeCritical]` or `[SecuritySafe]' with the `[assembly: AllowPartiallyTrustedCallers]` (me flailing around) that next call is checking the call stack. 1) if the assembly in the new AppDomain is fully trusted, why is this check failing anyway? 2) how do I allow the call? – Jason Kleban Jan 07 '14 at 20:02
  • That is, what do I need to do to be able to make a call from the hosted to the host? – Jason Kleban Jan 08 '14 at 15:10
  • Does the hostee need to be invocable from partially trusted or transparent code? If not, leaving it as SecurityCritical (default) without APTCA should work fine. If this doesn't work for you, could you please supply sample code since I cannot repro the problem. – Nicole Calinoiu Jan 08 '14 at 15:30
  • The hostee is untrusted but it implements an interface and may call certain methods of the host (which in turn involve WCF requiring full trust). Got it working by removing `APTCA` and adding `[PermissionSet(SecurityAction.Assert, Unrestricted = true)]` to the IHostHub implementation methods. Thanks! – Jason Kleban Jan 08 '14 at 16:38
  • You should not need to add the assertion (which may create security issues). How is `Stub.RequestTime` being invoked? – Nicole Calinoiu Jan 08 '14 at 17:02
  • Another process spawns to this process and communicates over WCF (served by this process) through named pipes with bi-directional, one-way contracts. RequestTime is one of those operations. Trying to serve WCF across the AppDomain was trouble, so this WCF-free Stub is used to put the operations' implementations in the AppDomain. The implementation can also call back to the parent process through WCF, so the HostStub proxies that request back to WCF. – Jason Kleban Jan 08 '14 at 18:08
  • Not quite what I was asking about. Exactly which code calls Stub.RequestTime? (A repro example would be useful.) – Nicole Calinoiu Jan 08 '14 at 18:12
  • Aiming to fully sandbox code - more than AppDomains alone can achieve - so the guest can't crash the original process - but trying to avoid the poor performance of creating a new process for every operation (a frequent occurrence). Plus, I am disappointed to now learn that revoking `SecurityPermissionFlag.ControlThread` does not disallow thread creating from the guest assembly. I may need to mess with CLR hosting to achieve that as SQL Server does? – Jason Kleban Jan 08 '14 at 18:13
  • Hmm, well the repro code gets harder and harder to show. But all that happens is a `stub.RequestTime(new StubServerProxy(Host))` is called from the WCF service operation implementation. Is there a particular aspect of the call site that you're trying to verify? – Jason Kleban Jan 08 '14 at 21:28
  • I'm trying to understand the complete call stack from which the call is being originated. So far, I haven't heard anything that would lead me to believe that there is partially trusted and/or transparent code on that call stack, which is the only thing that should cause the problem you describe. – Nicole Calinoiu Jan 10 '14 at 13:28