How to: Run Partially Trusted Code in a Sandbox

Sandboxing is the practice of running an application in a restricted security environment, which limits the code access permissions granted to it. For example, controls that are downloaded to Internet Explorer run using the Internet permission set. Applications that reside on shares on your local area network run on your computer using the LocalIntranet permission set. (For more information about these permissions, see Named Permission Sets.)

You can use sandboxing to run partially trusted applications that you have downloaded to your computer. You can also use sandboxing to test applications you will be distributing that will run partially trusted in environments such as the intranet. For a complete description of code access security (CAS) and sandboxing, see Find Out What’s New with Code Access Security in the .NET Framework 2.0 on MSDN, the Microsoft Developer Network.

You can use the CreateDomain(String, Evidence, String, String, Boolean, AppDomainInitializer, array<String[]) method overload to specify the permission set for applications that run in a sandbox. This overload enables you to specify the permissions granted to an application, which provides the exact level of code access security you want. It does not utilize standard CAS policy (machine policy is not applied). For example, if the assembly that is called is signed with a strong name key and a custom code group exists for that strong name, the code group will not apply. Assemblies that are loaded by using this overload can either have the specified grant set only, or can have full trust. The assembly is granted full trust if it is in the global assembly cache or on the full trust list. However, targeting a full-trust assembly bypasses the use of a sandbox.

The overload has the following signature:

AppDomain.CreateDomain( string friendlyName,
                        Evidence securityInfo,
                        AppDomainSetup info,
                        PermissionSet grantSet,
                        params StrongName[] fullTrustAssemblies);

The parameters for the CreateDomain(String, Evidence, AppDomainSetup, PermissionSet, array<StrongName[]) method overload specify the name of the AppDomain, the evidence for the assembly, the AppDomainSetup object that identifies the application base for the sandbox, the permission set to use, and the strong name for trusted assemblies.

The application base specified in the info parameter should not be the application base for the hosting application. If it is, the hosted assembly will be able to use the Load method to load other assemblies into that folder, which may not have been designed to detect calls from partially trusted callers.

For the grantSet parameter, you can specify either a permission set you have explicitly created, or one of the named permission sets such as Internet or LocalIntranet. The complete example provided in this topic demonstrates how to use named permission sets instead of creating a custom permission set.

Unlike most AppDomain loads, the evidence for the assembly (which is provided by the securityInfo parameter) is not used to determine the grant set. Instead, it is independently specified by the grantSet parameter. However, the evidence can be used for other purposes such as determining isolated storage.

To run an application in a sandbox

  1. Create the permission set to be granted to the application.

    Note

    The application in this example requires Execution permission to run and UIPermission to write to the console. The following code creates a new permission set with those permissions. Alternatively, you can use an existing named permission set, such as LocalIntranet. For an example of how to use a named permission set, see the "Example" section later in this topic.

    PermissionSet pset = new PermissionSet(PermissionState.None);
    pset.AddPermission(new      SecurityPermission(SecurityPermissionFlag.Execution));
    pset.AddPermission(new UIPermission(PermissionState.Unrestricted));
    
  2. Initialize the folder that will be used as the sandbox. Do not use the folder that your hosting application is using. If you place the application in the hosting folder, the hosting assembly will have the ability to load any assembly in the folder.

    AppDomainSetup ads = new AppDomainSetup();
    // Identify the folder to use for the sandbox.
    ads.ApplicationBase = "C:\\Sandbox";
    // Copy the application you want to run to the sandbox. File.Copy("HelloWorld.exe","C:\\sandbox\\HelloWorld.exe",true);
    
  3. Use the CreateDomain(String, Evidence, String, String, Boolean, AppDomainInitializer, array<String[]) method overload to create the domain. In this case, the evidence and the strong name for the parent assembly is specified. See the "Example" section later in this topic for the code for the GetStrongName method.

    // Create the sandboxed application domain.
    AppDomain sandbox = AppDomain.CreateDomain(
    "Sandboxed Domain",
    AppDomain.CurrentDomain.Evidence,
    ads, pset, GetStrongName(Assembly.GetExecutingAssembly()));
    
  4. Run the application.

    sandbox.ExecuteAssemblyByName("HelloWorld");
    

Example

The following example is a complete example that implements the procedure in the previous section. This example shows how you can run an application by using the same permissions it would be granted in an intranet environment. You will have to create your own test application to replace the HelloWorld.exe assembly in the example.

Imports System
Imports System.Collections
Imports System.Diagnostics
Imports System.Security
Imports System.Security.Permissions
Imports System.Security.Policy
Imports System.Reflection
Imports System.IO



Class Program

    Shared Sub Main(ByVal args() As String)
        ' Create the permission set to grant to other assemblies. 
        ' In this case we are granting the permissions found in the LocalIntranet zone. 
        Dim pset As PermissionSet = GetNamedPermissionSet("LocalIntranet")
        If pset Is Nothing Then 
            Return 
        End If 
        ' Optionally you can create your own permission set by explicitly adding permissions. 
        '     PermissionSet pset = new PermissionSet(PermissionState.None); 
        '     pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); 
        '     pset.AddPermission(new UIPermission(PermissionState.Unrestricted)); 
        Dim ads As New AppDomainSetup()
        ' Identify the folder to use for the sandbox.
        ads.ApplicationBase = "C:\Sandbox" 
        ' Copy the application to be executed to the sandbox.
        File.Copy("HelloWorld.exe", "C:\sandbox\HelloWorld.exe", True)

        Dim hostEvidence As New Evidence()
        ' Commenting out the following two statements has no effect on the sample. 
        ' The grant set is determined by the grantSet parameter, not the evidence  
        ' for the assembly.  However, the evidence can be used for other reasons,  
        ' for example, isolated storage.
        hostEvidence.AddHost(New Zone(SecurityZone.Intranet))
        hostEvidence.AddHost(New Url("C:\Sandbox"))

        ' Create the sandboxed domain. 
        Dim sandbox As AppDomain = AppDomain.CreateDomain("Sandboxed Domain", hostEvidence, ads, pset, GetStrongName([Assembly].GetExecutingAssembly()))
        sandbox.ExecuteAssemblyByName("HelloWorld")

    End Sub 'Main


    '' <summary> 
    '' Get a strong name that matches the specified assembly. 
    '' </summary> 
    '' <exception cref="ArgumentNullException"> 
    '' if <paramref name="assembly"/> is null 
    '' </exception> 
    '' <exception cref="InvalidOperationException"> 
    '' if <paramref name="assembly"/> does not represent a strongly named assembly 
    '' </exception> 
    '' <param name="assembly">Assembly to create a StrongName for</param> 
    '' <returns>A StrongName for the given assembly</returns> 
    ''  
    Public Shared Function GetStrongName(ByVal [assembly] As [Assembly]) As StrongName
        If [assembly] Is Nothing Then 
            Throw New ArgumentNullException("assembly")
        End If 
        Dim assemblyName As AssemblyName = [assembly].GetName()
        Debug.Assert(Not (assemblyName Is Nothing), "Could not get assembly name")

        ' Get the public key blob. 
        Dim publicKey As Byte() = assemblyName.GetPublicKey()
        If publicKey Is Nothing OrElse publicKey.Length = 0 Then 
            Throw New InvalidOperationException("Assembly is not strongly named")
        End If 
        Dim keyBlob As New StrongNamePublicKeyBlob(publicKey)

        ' Return the strong name. 
        Return New StrongName(keyBlob, assemblyName.Name, assemblyName.Version)

    End Function 'GetStrongName

    Private Shared Function GetNamedPermissionSet(ByVal name As String) As PermissionSet
        Dim policyEnumerator As IEnumerator = SecurityManager.PolicyHierarchy()

        ' Move through the policy levels to the machine policy level. 
        While policyEnumerator.MoveNext()
            Dim currentLevel As PolicyLevel = CType(policyEnumerator.Current, PolicyLevel)

            If currentLevel.Label = "Machine" Then 
                Dim copy As NamedPermissionSet = currentLevel.GetNamedPermissionSet(name)
                Return CType(copy, PermissionSet)
            End If 
        End While 
        Return Nothing 

    End Function 'GetNamedPermissionSet
End Class 'Program 
using System;
using System.Collections;
using System.Diagnostics;
using System.Security;
using System.Security.Permissions;
using System.Security.Policy;
using System.Reflection;
using System.IO;

namespace SimpleSandboxing
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the permission set to grant to other assemblies. 
            // In this case we are granting the permissions found in the LocalIntranet zone.
            PermissionSet pset = GetNamedPermissionSet("LocalIntranet");
            if (pset == null)
                return;
            // Optionally you can create your own permission set by explicitly adding permissions. 
            //     PermissionSet pset = new PermissionSet(PermissionState.None); 
            //     pset.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); 
            //     pset.AddPermission(new UIPermission(PermissionState.Unrestricted));
            AppDomainSetup ads = new AppDomainSetup();
            // Identify the folder to use for the sandbox.
            ads.ApplicationBase = "C:\\Sandbox";
            // Copy the application to be executed to the sandbox.
            File.Copy("HelloWorld.exe", "C:\\sandbox\\HelloWorld.exe", true);

            Evidence hostEvidence = new Evidence();
            // Commenting out the following two statements has no effect on the sample. 
            // The grant set is determined by the grantSet parameter, not the evidence  
            // for the assembly.  However, the evidence can be used for other reasons,  
            // for example, isolated storage.
            hostEvidence.AddHost(new Zone(SecurityZone.Intranet));
            hostEvidence.AddHost(new Url("C:\\Sandbox"));

            // Create the sandboxed domain.
            AppDomain sandbox = AppDomain.CreateDomain(
                "Sandboxed Domain",
                hostEvidence,
                ads,
                pset,
                GetStrongName(Assembly.GetExecutingAssembly()));
            sandbox.ExecuteAssemblyByName("HelloWorld");
        }

        /// <summary> 
        /// Get a strong name that matches the specified assembly. 
        /// </summary> 
        /// <exception cref="ArgumentNullException">
        /// if <paramref name="assembly"/> is null 
        /// </exception> 
        /// <exception cref="InvalidOperationException">
        /// if <paramref name="assembly"/> does not represent a strongly named assembly
        /// </exception> 
        /// <param name="assembly">Assembly to create a StrongName for</param>
        /// <returns>A StrongName for the given assembly</returns> 
        ///  
        public static StrongName GetStrongName(Assembly assembly)
        {
            if (assembly == null)
                throw new ArgumentNullException("assembly");

            AssemblyName assemblyName = assembly.GetName();
            Debug.Assert(assemblyName != null, "Could not get assembly name");

            // Get the public key blob. 
            byte[] publicKey = assemblyName.GetPublicKey();
            if (publicKey == null || publicKey.Length == 0)
                throw new InvalidOperationException("Assembly is not strongly named");

            StrongNamePublicKeyBlob keyBlob = new StrongNamePublicKeyBlob(publicKey);

            // Return the strong name. 
            return new StrongName(keyBlob, assemblyName.Name, assemblyName.Version);
        }
        private static PermissionSet GetNamedPermissionSet(string name)
        {
            IEnumerator policyEnumerator = SecurityManager.PolicyHierarchy();

            // Move through the policy levels to the machine policy level. 
            while (policyEnumerator.MoveNext())
            {
                PolicyLevel currentLevel = (PolicyLevel)policyEnumerator.Current;

                if (currentLevel.Label == "Machine")
                {
                    NamedPermissionSet copy = currentLevel.GetNamedPermissionSet(name);
                    return (PermissionSet)copy;
                }
            }
            return null;
        }

    }
}