There are three kinds of declarative security, depending on when it has effect, all of which take the form of an action together with permission(s) the action applies to.
The action types are defined as follows:
Declaration of security action | Time of action | Targets supported | Description |
---|---|---|---|
public enum SecurityAction { | |||
LinkDemand, | JIT-time | Class, Method | Demand permission of a caller |
InheritanceDemand, | Load-time | Class, Method | Demand permission of a subclass |
Demand, | Run-time | Class, Method | Demand permissions of all callers |
Assert, | Run-time | Class, Method | Assert permission so callers don’t need |
Deny, | Run-time | Class, Method | Deny permission so checks will fail |
PermitOnly, | Run-time | Class, Method | Reduce permissions so checks will fail |
RequestMinimum, | Grant-time | Assembly, | Request minimum permissions to run |
RequestOptional, | Grant-time | Assembly, | Request optional additional permissions |
RequestRefuse, | Grant-time | Assembly, | Refuse to be granted these permissions |
} |
Grant-time code requests are only valid for use in the assembly manifest, and are ignored elsewhere.
Demands at JIT-time that any code binding to the target being protected has the specified permission. The link-demand has a limitation in that it checks for the permission of the immediate caller only. It does not check all callers on the call chain. Specifically, linking means any bind to the type reference – this includes not just method calls but function pointer references as well. In a subclass, for methods inherited from the superclass the link-demand declaration, if any, of the superclass method applies; if the subclass overrides a method then the link-demand declaration, if any, of the subclass applies.
For example, suppose class A declares link-time demand of permission X. If another class B contains a call to a method of A then the runtime will demand that B has permission X granted before linking B to call A. If it does not have the requisite permission the link is not allowed. Code still loads but an attempt to bind to the method references will result in a security exception at runtime. Rationale for runtime exception is that only when the code actually tries the reference will the exception happen, plus if loaded the linking code itself may attempt to catch the exception and response.
Note that since link-time demands are limited to immediate caller, and not the full chain of callers, are subject to luring attacks. For instance class C that does not have permission X will be allowed to call B (assuming no additional declarative security in our example) so it is B’s responsibility to not weaken the security demand imposed by A by becoming a “gateway” for indirect calls to A, thwarting the intended security restriction. Only if B (and all other callers, and callers’ callers) also has the same declarative security or equivalent protection in place can these attacks be prevented.
Important: Developer documentation needs to be clear that this is not a stack walk and code must guard itself against luring attacks as described above. Use runtime demand when runtime security is needed.
This security declaration has different meaning for classes (allows subclassing) than for methods (allows overriding). If both class and some methods of the class have inheritance demands, then the class declaration applies to the class and methods without declarations, and for methods with declarations the class declaration is ignored.
Classes: Demands at load-time that any code inheriting (subclassing) from the target class being protected has the specified permission. Note that this demand applies to all superclasses. For example, suppose class A declares inheritance demand of permission X. If class B subclasses A then the runtime will demand that B has permission X granted, and B will only load if it has the requisite permission. Then if class C that does not have permission X subclass B it will fail to load: which B has no declarative inheritance demand, A does and that is what fails.
Methods: For a given method with the declaration in a superclass, demands that any subclass overriding the method must have the specified permission. By using this declaration on specific methods it is possible to selectively control the ability of subclasses to override the methods.
Demands at run-time that all callers in the call chain have the specified permission. If any caller does not have the permission a security exception is thrown. The permissions of the code itself are not involved; rather the check begins from the immediate caller of the code and proceeds up the stack.
Implementation note: tail calls work differently because the stack walk starts one level higher than a call.
Asserts that it has the specified permission to access a resource even if its callers do not have the specified permission. Normally a permission demand involves walking the stack and checking each caller for the necessary permission. However, an assertion will stop the stack walk and succeed for any permission matching the permission asserted. The effect of an assertion is to force permission checks to succeed even if some callers above it on the stack do not have the requisite permission – the asserting code is taking responsibility at its interface not to compromise security by thus overriding the security system.
This is an extremely dangerous statement and must be used with care. The security model is built on the premise that all code in the call chain has the requisite permission to access a resource. Using an assert removes that requirement and, if improperly used, is a source of security holes.
Since assertion is potentially a source of security weakness, code must be granted the SecurityPermission in order to be able to assert. Further, only granted permissions may be asserted.
Declarative assertion of permission to call unmanaged code (SecurityPermission.UnmanagedCode, or a permission set containing it) is an important special case. Since a permission demand on UnmanagedCode is guaranteed to succeed within the method code, these normal permission demands for calls to unmanaged code will be omitted, providing a technique for code to avoid the performance cost of security checks when the code is “vouching” for security. Since only fully trusted code may so assert, integrity of security is ensured, however such code needs to be scrupulously written and checked against possible abuse by malicious callers. Even with imperative security overrides, no UnmanagedCode demand ever happens.
Denies the specified permission(s), effectively removing them from the granted permission set for the duration of the method. As a result, subsequent demands (until the method with the deny returns) of any denied permission will fail even if the code and its callers all have the permission granted. Like an assertion, denying permission will preemptively stop the stack walk, however deny forces the demand to fail.
Deny of any permission that is not actually granted has no effect, as stack walk would fail in any case.
Reduces the effective granted permissions for the duration of the method to be only the specified permission(s), effectively removing from the granted permission set any permissions not in the permit-only. This declaration is just another form of deny, with the set of remaining permissions specified, rather than the set by which granted permissions should be reduced. As a result, subsequent demands (until the method with the permit-only returns) of any granted permission not in the permit-only will fail even if the code and its callers all have the permission granted. Like deny, permit-only will preemptively stop the stack walk failing a demand.
Permit-only of any permissions not actually granted has no effect, as stack walk would fail in any case.
Request a specified set of permissions as the minimum required for the code to be run. Grant-time declarative security may only be used on an assembly basis. Declarations of request on other security targets are not valid and are ignored. If omitted, code will be granted all permissions allowed by policy.
Applications are guaranteed to always have the requested permissions whenever the code runs, so it is not normally necessary to write security exception handling code so long as these permissions are all that are needed. However, it is possible for other code to reduce the effective permissions (such as by means of deny declaration) in which case security exceptions for lack of even these permissions may arise.
Request an additional set of permissions that should be granted if policy allows, but are not required for the code to run successfully. This allows code to request permissions beyond the minimum required, so ever attempted use of these permissions needs to be done with attendant exception handling to gracefully handle execution when permissions may not be granted. Grant-time declarative security may only be used on an assembly basis. Declarations of request on other security targets are not valid and are ignored. If omitted, the default meaning is whatever additional permissions policy chooses to grant will be given to the code.
Request a specified set of permissions should never be granted. Grant-time declarative security may only be used on an assembly basis. Declarations of request on other security targets are not valid and are ignored. If omitted, any permission may be granted to the code by policy. This request allows a large set of permissions to be requested optionally, yet ensure that certain specific permissions are not in the grant.
Applications can use request refuse to ensure that they never get certain permission. For example, an application that browses data but never modifies it may refuse any file write permissions – doing so ensures that even in the event of a bug or malicious use the code will not be able to damage the data it operates on.
Another way to refuse permissions is to make the optional request set small or empty. However, in some cases unspecified permissions may be desired (requires request optional = “Everything”) yet some specific permissions are to be refused – this is the situation when this declaration becomes most useful.
Request refuse is only meaningful to reduce the optional permission set if it cannot be completely specified. The following are the common permission request situations that arise (last one is when refuse is needed):
A special optimization is available to code that has permission to call unmanaged code (SecurityPermission.UnmanacedCode), allowing it to do so without the performance cost of asserting its permission before every transition to unmanaged code (either via P/Invoke or interoperability to COM).
Normally a call into unmanaged code triggers a demand of unmanaged code permission, which walks the stack checking that all callers have that permission. Applying the custom attribute SuppressUnmanagedCodeSecurityAttribute to the call into unmanaged code suppresses the demand, in essence creating a "open door" into unmanaged code. Only code that has the unmanaged code permission may use this attribute: it has no effect if used elsewhere.
WARNING: use this attribute with extreme care; incorrect use can create security weaknesses
Use of this attribute should never allow less trusted code (that does not have unmanaged code permission) to call into unmanaged code. This attribute is best applied only to privately declared entry points to unmanaged code so that code in other assemblies cannot access and take advantage of the security suppression. Typically the highly trusted managed code that uses it first demands some permission of callers before invoking the unmanaged code on the caller’s behalf.
In the rare case that the unmanaged code is completely safe for all possible circumstances, it can be exposed to other managed code directly (made public instead of private). Not only must the functionality of the unmanaged code be safe, but it must be impervious to attack by malicious callers: for example, the code must operate safely even if with unintended arguments fabricated to specifically cause the code to malfunction.
EXAMPLE (C#)
[SuppressUnmanagedCodeSecurityAttribute()] [sysimport(dll = "some.dll")] private static extern EntryPoint(args);