Maintaining LinkDemands across inheritance boundaries
As you already know, LinkDemands can be dangerous. But if you do use them, you need to know how to use them correctly. One problem that happens is when people add (or fail to maintain) a LinkDemand to a derived class when one did (or did not) exist on the base class.
Let's say you have the following code:
public class BaseClass
{
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public void DoSomething()
{
// Do something here
}
}
public class DerivedClass : BaseClass
{
public override void DoSomething()
{
// Do something else here...
// Now call base class' implementation
base.DoSomething();
}
}
Hopefully it is pretty clear that this represents a security risk: BaseClass has protected its method with a LinkDemand so that untrusted code cannot call it. Unfortunately, DerivedClass goes ahead and calls the method as part of its implementation of DoSomething, and in doing so will likely satisfy the LinkDemand. To fix this problem the author of DerivedClass needs to add the LinkDemand attribute to DerivedClass.DoSomething.
That one was easy. But what's wrong with this?
public class BaseClass
{
public void DoSomething()
{
// Do something safe here
}
}
public class DerivedClass : BaseClass
{
[SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
public override void DoSomething()
{
// Do something dangerous here
}
}
Surely it's OK since the DerivedClass is being more secure than the BaseClass?
The problem here is that LinkDemand checks are done as the calling method is being JITted. If you have a method in an untrusted piece of code that accepts a parameter of type BaseClass then it will be JITted without any problems since no LinkDemand will be done. The method can then be passed a DerivedClass instance (which wants the check to occur) but the check will never occur.
Taking advantage of this is easy:
void DoSomethingHelper()
{
CallDoSomething(new DerivedClass());
}
void CallDoSomething(BaseClass bc)
{
bc.DoSomething();
}
In this case, when the JITter compiles CallDoSomething all it knows is that you are calling BaseClass.DoSomething. It doesn't know that you are later going to pass in a DerivedClass instance as the parameter. In fact, in a more complicated scenario the JITter may not even know that DerivedClass exists -- it could be in an assembly that is dynamically loaded at some future point in time.
Obviously if you want to add a LinkDemand to your DerivedClass but you are not in control of BaseClass (and so can't add the LinkDemand to it) you are in a bit of a bind. Two reasonable approaches:
1) Create a new method! If the base class' implementation of DoSomething was safe, you're probably abusing it by making the override dangerous
2) Do a full demand; it will always work, and it's probably what you want anyway
Comments
- Anonymous
June 28, 2004
I don't understand how Full Demands work. In a future blog can you give you a run-down about how they work? In particular, I'm having trouble understanding the "deny" part of Full Demands. Can I demand that my assembly run without full trust, but that the callers code can run however it would like? How do I do that? - Anonymous
June 29, 2004
The best way to run without FullTrust is to add RequestRefuse declarative attributes to your assembly. See:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconrefusingpermissions.asp
for more information.
A Deny is used to temporarily terminate a stackwalk at the current method. - Anonymous
June 29, 2004
Thanks for the excellent explanation. Any chance you could tell us how you got that beautiful colorized text in for the code? I have been frustrated with that part of blogging. - Anonymous
June 29, 2004
I write my blogs in Word and then use a WordML -> HTML transform and a VSTO project to post to the blog web service.
I really need to upload the final version some day... ;-) - Anonymous
June 30, 2004
The comment has been removed - Anonymous
July 01, 2004
Thanks for the great comment, Nicole.
Please see my follow-up post at http://weblogs.asp.net/ptorr/archive/2004/07/01/170998.aspx