So, how do you create partial-trust appdomains? Where do you come across them?
There are two main situations in which your assembly runs as partially-trusted using the Microsoft .NET stack:
Creating a CLR assembly in SQL Server with anything other than the UNSAFE permission set. The permissions available in each permission set are given here.
Loading an assembly in ASP.NET in any trust level other than Full. Information on ASP.NET trust levels can be found here. You can configure the specific permissions available to assemblies using ASP.NET policy files.
Alternatively, you can create your own partially-trusted appdomain in code and directly control the permissions and the full-trust API available to the assemblies you load into the appdomain. This is the scenario I’ll be concentrating on in this post.
Creating a partially-trusted appdomain
There is a single overload of AppDomain.CreateDomain that allows you to specify the permissions granted to assemblies in that appdomain – this one. This is the only call that allows you to specify a PermissionSet for the domain. All the other calls simply use the permissions of the calling code. If the permissions are restricted, then the resulting appdomain is referred to as a sandboxed domain.
There are three things you need to create a sandboxed domain:
The specific permissions granted to all assemblies in the domain.
The application base (aka working directory) of the domain.
The list of assemblies that have full-trust if they are loaded into the sandboxed domain.
The third item is what allows us to have a fully-trusted API that is callable by partially-trusted code. I’ll be looking at the details of this in a later post.
Granting permissions to the appdomain
Firstly, the permissions granted to the appdomain. This is encapsulated in a PermissionSet object, initialized either with no permissions or full-trust permissions. For sandboxed appdomains, the PermissionSet is initialized with no permissions, then you add permissions you want assemblies loaded into that appdomain to have by default:
PermissionSet restrictedPerms = new PermissionSet(PermissionState.None);
// all assemblies need Execution permission to run at all
restrictedPerms.AddPermission(
new SecurityPermission(SecurityPermissionFlag.Execution));
// grant general read access to C:\config.xml
restrictedPerms.AddPermission(
new FileIOPermission(FileIOPermissionAccess.Read, @"C:\config.xml"));
// grant permission to perform DNS lookups
restrictedPerms.AddPermission(
new DnsPermission(PermissionState.Unrestricted));
It’s important to point out that the permissions granted to an appdomain, and so to all assemblies loaded into that appdomain, are usable without needing to go through any SafeCritical code (see my last post if you’re unsure what SafeCritical code is). That is, partially-trusted code loaded into an appdomain with the above permissions (and so running under the Transparent security level) is able to create and manipulate a FileStream object to read from C:\config.xml directly. It is only for operations requiring permissions that are not granted to the appdomain that partially-trusted code is required to call a SafeCritical method that then asserts the missing permissions and performs the operation safely on behalf of the partially-trusted code.
The application base of the domain
This is simply set as a property on an AppDomainSetup object, and is used as the default directory assemblies are loaded from:
AppDomainSetup appDomainSetup = new AppDomainSetup {
ApplicationBase = @"C:\temp\sandbox",
};
If you’ve read the documentation around sandboxed appdomains, you’ll notice that it mentions a security hole if this parameter is set correctly. I’ll be looking at this, and other pitfalls, that will break the sandbox when using sandboxed appdomains, in a later post.
Full-trust assemblies in the appdomain
Finally, we need the strong names of the assemblies that, when loaded into the appdomain, will be run as full-trust, irregardless of the permissions specified on the appdomain. These assemblies will contain methods and classes decorated with SafeCritical and Critical attributes. I’ll be covering the details of creating full-trust APIs for partial-trust appdomains in a later post. This is how you get the strongnames of an assembly to be executed as full-trust in the sandbox:
// get the Assembly object for the assembly
Assembly assemblyWithApi = ...
// get the StrongName from the assembly's collection of evidence
StrongName apiStrongName = assemblyWithApi.Evidence.GetHostEvidence<StrongName>();
Creating the sandboxed appdomain
So, putting these three together, you create the appdomain like so:
AppDomain sandbox = AppDomain.CreateDomain(
"Sandbox", null, appDomainSetup, restrictedPerms, apiStrongName);
You can then load and execute assemblies in this appdomain like any other. For example, to load an assembly into the appdomain and get an instance of the Sandboxed.Entrypoint class, implementing IEntrypoint, you do this:
IEntrypoint o = (IEntrypoint)sandbox.CreateInstanceFromAndUnwrap(
"C:\temp\sandbox\SandboxedAssembly.dll", "Sandboxed.Entrypoint");
// call method the Execute method on this object within the sandbox
o.Execute();
The second parameter to CreateDomain is for security evidence used in the appdomain. This was a feature of the .NET 2 security model, and has been (mostly) obsoleted in the .NET 4 model. Unless the evidence is needed elsewhere (eg. isolated storage), you can pass in null for this parameter.
Conclusion
That’s the basics of sandboxed appdomains. The most important object is the PermissionSet that defines the permissions available to assemblies running in the appdomain; it is this object that defines the appdomain as full or partial-trust. The appdomain also needs a default directory used for assembly lookups as the ApplicationBase parameter, and you can specify an optional list of the strongnames of assemblies that will be given full-trust permissions if they are loaded into the sandboxed appdomain.
Next time, I’ll be looking closer at full-trust assemblies running in a sandboxed appdomain, and what you need to do to make an API available to partial-trust code.