Continuing on from yesterday's post on , I had a bit of an e-mail exchange with of . He wanted me to divulge all my secrets about AppDomains to him over e-mail, but I do intend to post them here as blog entries sooner or later (although, for , it might be later...).
Please don't everybody e-mail me and ask for more details; I can't scale to more than one request/week 🙂
That's a Good Question™
In my last blog, I hinted that you needed to setup AppDomain policy in the remote domain (ie, the one you just created) rather than in the host's domain to overcome a limitation in the way policy worked. Shawn posted today that (thankfully) , so you will be able to setup policy from the host's domain and not have to worry about injecting helper assemblies to do it. Yay!
Nevertheless, it is still likely that you'll want to load some assemblies in the remote domain, and if you want them to be granted FullTrust you'll probably have strong-named them so that they match a (note to self: blog why custom policies are bad). Being a security-conscious developer, you obviously don't want to put the AllowPartiallyTrustedCallersAttribute on your assembly because, well, there's no good reason to have those pesky little untrusted assemblies calling in to you.
But let's see what happens when you use AppDomain.CreateInstanceAndUnwrap to try and load your assembly into the new AppDomain (more or less; this is probably not how it actually works, since I don't have the code for it and don't care to look for it 😉 but it's gets the idea across):
1) The CLR locates the target assembly and loads it
2) Using reflection, the CLR locates the requested type and tries to create an instance of it
3) Since the assembly is not marked with APTCA, there is an implicit LinkDemand for FullTrust on the type
5) The Demand wanders through the CLR's reflection and marshalling code on the stack and eventually hits the AppDomain boundary
6) The AppDomain is only partially trusted, so the Demand fails
7) Reflection fails the request to create the type
8) CreateInstanceAndUnwrap fails the call
Notice that the assembly is still loaded in the remote domain; you just can't create any types inside it.
So, you have to put APTCA on the assembly so that it can be created inside the AppDomain, but now you've got a different kind of problem: the code inside your method is doing something "dangerous" (such as setting policy) that will require permissions not granted to the AppDomain itself. You have to Assert the right to use these permissions to avoid a Demand hitting the AppDomain boundary (as in the loading process above), but now you have an unprotected Assert for some really nasty permissions!
That's Bad News™
There are two solutions to this; the first (and ugliest) solution is to simply add a LinkDemand to the method for whatever permissions you are asserting (probably FullTrust). You can successfully call the method across the AppDomain boundary because the CLR will "do the right thing" with the LinkDemand and ensure that the "real" caller in the other AppDomain has the requested permissions (rather than the marshalling goo inbetween). This is different than the creation case above, because that (necessarily) uses reflection to create the type, whereas once the type is created you can early bind to its members and call them directly.
But as we know, and their use is fraught with peril, so it's A Good Thing™ that there's a better way to do this. The "better" way is to have your dangerous method (with the Assert) be an internal method, and then have another "proxy" class inside the same assembly protected with a FullTrust Demand to call it. Now you create an instance of the proxy class in your local domain, an instance of the dangerous class in the remote domain, hook them both together, and get the proxy to invoke the call for you. Now you have a full Demand-protected method that can't be called by partially trusted code, and can't easily be involved in a luring attack, either.
Here's some (very simplified) code to demonstrate the technique: