Using Host Protection

Yesterday we looked at what host protection is and what it does.  Today lets modify the ADMHost sample code so that it disables access to self affecting and external threading operations.  We'll then attempt to run a bit of code that launches 10 threads.

The code that we'll be hosting is extremely basic -- this is compiled into ThreadTest.exe and put next to the ManagedHost.dll:

Thread[] threads = new Thread[10];
for(int i = 0; i < 10; i++)
{
    threads[i] = new Thread(delegate() { Console.WriteLine(i); });
    threads[i].Start();
}
        
for(int i = 0; i < 10; i++)
    threads[i].Join();

I'll modify the IManagedHost interface to support running a program, and implement this new method on the ManagedHost class. While I'm there, I'll modify the CreateAppDomain method to create a simple sandboxed domain (remember from yesterday that code which is fully trusted is not affected by HPA).  These changes are also pretty simple:

int IManagedHost.CreateAppDomain(string name)
{
    PermissionSet permissions = new PermissionSet(PermissionState.None);
    permissions.AddPermission(new SecurityPermission(PermissionState.Unrestricted));
    permissions.AddPermission(new UIPermission(PermissionState.Unrestricted));

    return AppDomain.CreateDomain(
        name,
        AppDomain.CurrentDomain.Evidence,
        AppDomain.CurrentDomain.SetupInformation,
        permissions,
        CreateStrongName(Assembly.GetExecutingAssembly())).Id;
}

void IManagedHost.Run(string path)
{
    new FileIOPermission(PermissionState.Unrestricted).Assert();
    string fullPath = Path.Combine(
        Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
        path);
    CodeAccessPermission.RevertAssert();

    new FileIOPermission(
        FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery,
        fullPath).Assert();
    AppDomain.CurrentDomain.ExecuteAssembly(fullPath);
    CodeAccessPermission.RevertAssert();
}

Now that the managed host can run an application of our choosing in a sandboxed domain, I'm going to quickly modify the main ADMHost logic in the RunApplication method to kick off ThreadTest.exe rather than printing Hello World:

int RunApplication(IUnmanagedHostPtr &pClr)
{
    // Get the default managed host
    IManagedHostPtr pManagedHost = pClr->DefaultManagedHost;

    // create a new AppDomain
    _bstr_t name(L"Second AppDomain");
    DWORD id = pManagedHost->CreateAppDomain(name);

    // get its host and run a multi threaded program
    IManagedHostPtr pSecondManagedHost = pClr->GetManagedHost(id);
    pSecondManagedHost->Run(_bstr_t(L"ThreadTest.exe"));

    return 0;
}

Now if we compile and run this, we'll see a string of 10 numbers between 0 and 10 ... everything is working as expected.  Let's say that the ADMHost application decides that it does not want any hosted code to be able to work with threads.  In order to accomplish this, it's going to set SelfAffectingThreading and ExternalThreading as protected categories.

In order to do this, the unmanaged half of the host will ask for the ICLRHostProtectionManager from the ICLRManager that it obtained while getting ready to start the runtime.  Once the host has a pointer to the host protection manager, it can simply call SetProtectedCategories with the categories it wishes to be marked protected.

The code to enable this is very straightforward.  In ClrHost.cpp, we'll modify the CClrHost::raw_Start method to enable host protection after we finished setting up the AppDomainManager but before starting the runtime:

// get the host protection manager
ICLRHostProtectionManager *pHostProtectionManager = NULL;
HRESULT hrGetProtectionManager = m_pClrControl->GetCLRManager(
    IID_ICLRHostProtectionManager,
    reinterpret_cast<void **>(&pHostProtectionManager));
if(FAILED(hrGetProtectionManager))
    return hrGetProtectionManager;

// setup host proctection
HRESULT hrHostProtection = pHostProtectionManager->SetProtectedCategories(
    (EApiCategories)(eExternalThreading | eSelfAffectingThreading));
    pHostProtectionManager->Release();

if(FAILED(hrHostProtection))
    return hrHostProtection;

And that's it ... after adding that handful of code, we've now prevented any hosted code from using APIs marked with the HostProtectionAttribute indicating that they belong to the external threading or self affecting threading categories.  If we rebuild and rerun the code now, we'll see:

D:\source\ADMHost\bin\Debug>ADMHost.exe
Error 0x80131640: Attempted to perform an operation that was forbidden by the CLR host.

HRESULT 0x80131640 maps to COR_E_HOSTPROTECTION in corerror.h.  Since Thread.Start is marked with a HostProtectionAttribute for ExternalThreading and this modified version of ADMHost enabled protection for that category, a demand for HostProtectionPermission was added to the Thread.Start method, and the ThreadTest assembly caused the stack walk to fail.  Because we were evaluating a demand for HostProtectionPermission the resulting exception was the HostProtectionException that we see here.

[Updated 11/4/2005: fixed a typeo in raw_Start]