I have this HeaderlessTabControl, which subclasses the classic TabControl.
// From http://social.msdn.microsoft.com/forums/en-US/winforms/thread/c290832f-3b84-4200-aa4a-7a5dc4b8b5bb/
// Author: Hans Passant (nobugz)
public class HeaderlessTabControl : TabControl {
protected override void WndProc(ref Message m) {
// Hide tabs by trapping the TCM_ADJUSTRECT message
if (m.Msg == 0x1328 && !DesignMode) {
m.Result = (IntPtr)1;
} else {
base.WndProc(ref m);
}
}
}
When I run Code Analysis on my project, I get this warning:
Warning 1 CA2122 : Microsoft.Security :
'HeaderlessTabControl.WndProc(ref Message)' calls into
'Message.Msg.get()' which has a LinkDemand. By making this call,
'Message.Msg.get()' is indirectly exposed to user code. Review the
following call stack that might expose a way to circumvent security
protection: ->'HeaderlessTabControl.WndProc(ref Message)'
->'HeaderlessTabControl.WndProc(ref Message)'
...as well as two similar warnings relating to Message.Msg.set()
and TabControl.WndProc()
. I understand that I've exposed some code by doing this. Can someone explain what kind of security holes I might have opened up here, and possible ways to fix it?
I guess what I am asking is, what possible security holes should I be looking for?
Let me give you the five minute overview of "traditional" .NET code access security. (We have a newer, simplified security model that should be used for new code, but an understanding of the underlying security model is helpful.)
The idea is that assemblies provide evidence -- things like where they are located, who wrote them, and so on. Policy consumes evidence and produces a grant set of permissions associated with that assembly.
When an action that requires a particular permission -- say, creating a dialog box or accessing a printer or writing to a file -- is attempted, the runtime issues a demand for that permission. The demand examines the code currently "on the stack" to determine all the code that called the present code, directly or indirectly. (*)
The demand says that every caller on the stack must have been granted the required permission. That prevents the luring attack, whereby hostile low-trust code calls benign high-trust code and "lures" it into doing some dangerous operation on its behalf, to harm the user. Since the full demand checks direct and indirect callers the luring attack is thereby defeated.
An assert allows high-trust code to modify the demand semantics. An assert says "I am benign high trust code and I assert that I cannot be lured by a low-trust hostile caller into performing a dangerous operation on its behalf." An assert is typically paired with a weaker demand; that is, high-trust code asserts "I can call unmanaged code safely even if the caller cannot", and then demands "but the caller had better have permission to access the printer, because that's what I'm going to do with my unmanaged code permission".
The problem with demands is that they are expensive. You have to do a full stack walk and look at everyone's permission set. If the operation is cheap -- say, tweaking a pixel in a bitmap -- you don't want to do a full demand every time because you'd spend all your time doing redundant security checks.
Thus the link demand. A link demand is performed once per caller of the protected method, the first time the code that calls the protected method is used, and it only checks the immediate caller of the protected method, rather than doing a full stack walk. After that, the link-demanded code operations are performed with no security checks for that caller. (It really should be called a "jit demand", not a "link demand" because the mechanism used is that the demand is checked when the caller is jitted.)
Obviously that is way cheaper -- one check per caller that only looks at one assembly is cheaper than one check per call that looks at every assembly on the stack -- and way more dangerous.
The link demand is basically buck-passing. The link demand says "Caller, by passing my link demand check, you get to call me for cheap from now on. But I am turning the security system off now, and therefore you are now responsible for ensuring that your caller cannot successfully attack the user by taking advantage of the fact that I am granting you the right to call me without security checks in the future."
You are calling a method with a link demand. So the question you face is: are you willing to take that responsibility? You get to call that method for cheap. Are you willing to guarantee that no low-trust hostile caller can use the fact that you get to call that method without security checks to harm the user?
If you are not willing or able to make that guarantee, then issue your own demand for the link-demanded permission; that will then require all your callers to meet the requirements. Or, pass the buck to your caller: issue a link demand to your caller and make them do the work.
(*) As I am fond of pointing out, the call stack does not actually tell you who called you, it tells you where control is going next. Since those are usually the same thing, everything works out fine. It is possible to end up in situations where "who called you?" has become divorced from "where are you going next?"; in those environments you have to be very careful using traditional-style code access security. The newer "sandboxed" security model is better suited for those scenarios.
FxCop can be a bit overzealous with its warnings. That's certainly the case here, this code will always hit a CAS demand because it calls base.WndProc(). Which ultimately ends up calling Control.WndProc() which looks like this:
[SecurityPermission(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
[SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
protected virtual void WndProc(ref Message m) {
// etc...
}
The InheritanceDemand is sufficient. You can safely ignore this warning.