Luring attacks represent an inherent security problem with partially trusted mobile code interacting with more and less trusted code within the system. The basic attack is for less trusted code to call more trusted code and get it to do something on its behalf since it does not have the permissions itself. The traditional solution to this threat is to make the more trusted code inaccessible, or to ensure that trusted code always checks its caller’s permissions or doesn’t expose dangerous functionality in the first place. System APIs that afford access to kernel services are an example of the former; COM “safe for scripting” object safety is an example of the latter approach.
The runtime provides protection against luring attack by demanding the permissions of all callers, rather than just the permission of the code attempting a protected operation. Since in a luring attack the less trusted code will always be somewhere in the causal sequence of calls, it will never be able to manipulate more trusted code to do things it does not have permissions for itself. Managed code type safety ensures that the chain of callers can always be reliably determined so attack code cannot hide its identity or raise its level of trust.
The runtime cost of demanding permissions of all callers is considerable, however without it all application code is exposed to potential luring attacks by less trusted code. Where necessary for performance code may be written to reduce the extent of stack walk permission demands, however, whenever this is done the programmer is taking responsibility for not exposing a security weakness.
Application code using the files via the base class libraries must have the corresponding file permission for the operation to be allowed. Implementations of the base class libraries for standard resources such as files or network perform permission demands for the corresponding permission. For example, before opening a file read-only the file class demands that all callers have read access for that file, and will only proceed with the operation if the demand succeeds.
Class libraries that expose resources via the underlying O/S API implementation such as file require high trust to be able to call unmanaged code. Again in the case of the file class, after demanding the permission of the callers the file class asserts its permission to call the native API (unmanaged code), and proceeds with the operation. The assertion of permission incurs an obligation on the class library code to only allow operations to be performed within the constraints of the permission demands it is responsible for making, and never more. Such a transition from managed to unmanaged code – which is only possible by highly trusted managed code – is analogous to the security checks that typically protect kernel services exposed to application APIs.
A stack walk demand of permissions of all callers starts with the lower stack frames are works up to the top of the stack. The demand succeeds only if the code of all frames above the point of demand on the stack have the required permission. To allow certain trusted code to perform operations beyond the permissions of some of its callers, a set of overrides to the stack walk are provided. Overrides are defined for a certain permission or permissions and have the effect of stopping the stack walk in the case of a demand of those permissions.
Here is how the most commonly used override – assert – works. Suppose method A (top of the stack) calls B which calls C, and the code for these methods have different sets of permission granted. If C does a demand of some permission P1, then only if both B and A have P1 granted will the demand succeed. However, suppose B wants to “vouch for” A which does not have P1: in this case B may assert P1. Now when C does a demand the stack walk checks B for P1, succeeds, and then notes the assert of P1 which matches the demand, with the effect that the stack walk succeeds at this point without going further up.
Another override – deny – works similarly but instead fails the stack walk instead; deny is useful to allow trusted code to “decline” some of its permissions on a temporary basis. A third form – permit-only – is similar to deny in that it causes a premature stack walk failure; the difference is that with this override the permissions that should continue to succeed are named instead of those that should be failed.
This section defines the basic model for stack walk overrides; following sections describe coding situations where these overrides are useful. For complete details on how overrides work, see Permission object.