Recipe 14.9 Making a Security Assert Safe
Problem
You want to assert that at a particular
point in the call stack, a given permission is understood to be
available for all subsequent calls. However, doing this can easily
open a security hole to allow other malicious code to spoof your code
or to create a back door into your component. You want to assert a
given security permission, but you want to do so in a secure and
efficient manner.
Solution
In order to make this approach secure,
we need to call Demand on the permissions that the
subsequent calls need and on which we are using
Assert in order to make sure that code that
doesn't have these permissions
can't slip by due to the Assert.
This is demonstrated by the function
CallSecureFunctionSafelyAndEfficiently, which
performs a Demand, then an
Assert before calling into
SecureFunction, which performs a
Demand for a
ReflectionPermission.
The code listing for
CallSecureFunctionSafelyAndEfficiently is:
public static void CallSecureFunctionSafelyAndEfficiently( )
{
// set up a permission to be able to access nonpublic members
// via reflection
ReflectionPermission perm =
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess);
// Demand the permission set we have compiled before using Assert
// to make sure we have the right before we Assert it. We do
// the Demand to insure that we have checked for this permission
// before using Assert to short-circuit stackwalking for it, which
// helps us stay secure, while performing better.
perm.Demand( );
// Assert this right before calling into the function that
// would also perform the Demand to short-circuit the stack walk
// each call would generate. The Assert helps us to optimize
// out use of SecureFunction
perm.Assert( );
// We call the secure function 100 times but only generate
// the stackwalk from the function to this calling function
// instead of walking the whole stack 100 times.
for(int i=0;i<100;i++)
{
SecureFunction( );
}
}
The code listing for SecureFunction is shown
here:
public static void SecureFunction( )
{
// set up a permission to be able to access nonpublic members
// via reflection
ReflectionPermission perm =
new ReflectionPermission(ReflectionPermissionFlag.MemberAccess);
// Demand the right to do this and cause a stackwalk
perm.Demand( );
// Perform the action here...
}
Discussion
In our demonstration function
CallSecureFunctionSafelyAndEfficiently, the
function we are calling (SecureFunction) performs
a Demand on a
ReflectionPermission to
ensure that the code can access nonpublic members of classes via
reflection. Normally, this would result in a stackwalk for every call
to SecureFunction. The Demand
in CallSecureFunctionSafelyAndEfficiently is only
there to protect against the usage of the Assert in the first
place. To make this more efficient, we can use
Assert to state that all functions called from
this one issuing Demands do not have to stack walk
any further as the Assert says stop checking for
this permission in the call stack. In order to do this, you need the
permission to call Assert.
The problem comes in with this Assert as it opens
up a potential luring attack where SecureFunction
is called via
CallSecureFunctionSafelyAndEfficiently, which
calls Assert to stop the Demand
stack walks from SecureFunction. If unauthorized
code without this ReflectionPermission were able
to call CallSecureFunctionSafelyAndEfficiently,
the Assert would prevent the
SecureFunction Demand call from
determining that there is some code in the call stack without the
proper rights. This is the beauty of the call stack-checking in
the CLR when a Demand occurs.
In order to protect against this, we issue a
Demand for the
ReflectionPermission needed by
SecureFunction in
CallSecureFunctionSafelyAndEfficiently to close
this hole before issuing the Assert. The
combination of this Demand and the
Assert causes us to do one stack walk instead of
the original 100 that would have been caused by the
Demand in SecureFunction but to
still maintain secure access to this functionality.
Security optimization techniques, such as using
Assert, in this case (even though it
isn't the primary reason to use
Assert), can help class library and controls
developers that are trusted to perform Asserts in
order to speed the interaction of their code with the runtime; but if
used improperly, these techniques can also open up holes in the
security picture as well. This example shows that you can have both
performance and security where secure access is concerned.
If you are using Assert, be mindful that stackwalk
overrides should never be made in a class constructor. Constructors
are not guaranteed to have any particular security context, nor are
they guaranteed to execute at a specific point in time. This lack
leads to the call stack not being well-defined, and
Assert used here can produce unexpected results.
One other thing to remember with Assert is that
you can only have one active Assert in a function
at a given time. If you Assert the same permission
twice, a
SecurityException is thrown by the CLR. You must revert
the original Assert first using RevertAssert and then
you can declare the second Assert.
You might have the idea that declarative demands would be faster due
to the CLR's knowledge of the call stack;
shouldn't it be able to perform the optimization we
have done manually here? In the 1.0 and 1.1 versions of the CLR, it
turns out that declarative demands are actually slower, since this
optimization does not occur.
See Also
See the "CodeAccessSecurity.Assert
Method," "CodeAccessSecurity.Demand
Method,"
"CodeAccessSecurity.RevertAssert
Method," and "Overriding Security
Checks" topics in the MSDN documentation.
|