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]
Comments
- Anonymous
October 14, 2005
The comment has been removed - Anonymous
November 04, 2005
The comment has been removed - Anonymous
March 19, 2006
Hello. I am looking for a way to prevent new AppDomains that I create from starting new threads (this is for a 'plugin' module that I'm playing with.) Doing this in a CLR host app is not an option for me because I want my hosting application to be able to create threads, just not the plugins. Can you suggest anything?
-mdb - Anonymous
March 21, 2006
Hi Michael,
As I mentioned in this post, HostProtection is actually not a security feature but a reliability feature. If you're trying to sandbox your addins, I recommend not using HostProtection, but instead using the AppDomain.CreateDomain overload which takes a permission set.
Pass in a permission set which does not contain the ControlThread permission. Then, add your assemblies to the FullTrust list of that domain. This allows you to continue creating threads while preventing your addins from doing it. Note that you'll have to Assert ControlThread before you actually kick off a thread otherwise the demand will fail at the AppDomain boundary.
-Shawn