Security in Class Libraries
Class library designers must understand code access security in order to write secure class libraries. When writing a class library, be aware of two security principles: use permissions to help protect objects, and write fully trusted code. The degree to which these principles apply will depend upon the class you are writing. Some classes, such as the System.IO.FileStream Class, represent objects that need protection with permissions. The implementation of these classes is responsible for checking the permissions of callers and allowing only authorized callers to perform operations for which they have permission. The System.Security Namespace contains classes that can help you perform these checks in the class libraries that you write. Class library code often is fully trusted or at least highly trusted code. Because class library code often accesses protected resources and unmanaged code, any flaws in the code represent a serious threat to the integrity of the entire security system. To help minimize security threats, follow the guidelines described in this topic when writing class library code. For more information, see Writing Secure Class Libraries.
Protecting Objects with Permissions
Permissions are defined to help protect specific resources. A class library that performs operations on protected resources must be responsible for enforcing this protection. Before acting on any request on a protected resource, such as deleting a file, class library code first must check that the caller (and usually all callers, by means of a stack walk) has the appropriate delete permission for the resource. If the caller has the permission, the action should be allowed to complete. If the caller does not have the permission, the action should not be allowed to complete and a security exception should be raised. Protection is typically implemented in code with either a declarative or an imperative check of the appropriate permissions.
It is important that classes protect resources, not only from direct access, but from all possible kinds of exposure. For example, a cached file object is responsible for checking for file read permissions, even if the actual data is retrieved from a cache in memory and no actual file operation occurs. This is because the effect of handing the data to the caller is the same as if the caller had performed an actual read operation.
Fully Trusted Class Library Code
Many class libraries are implemented as fully trusted code that encapsulates platform-specific functionality as managed objects, such as COM or system APIs. Fully trusted code can expose a weakness to the security of the entire system. However, if class libraries are written correctly with respect to security, placing a heavy security burden on a relatively small set of class libraries and the core runtime security allows the larger body of managed code to acquire the security benefits of these core class libraries.
In a common class library security scenario, a fully trusted class exposes a resource that is protected by a permission; the resource is accessed by a native code API. A typical example of this type of resource is a file. The File class uses a native API to perform file operations, such as a deletion. The following steps are taken to protect the resource.
- A caller requests the deletion of file
c:\test.txtby calling the File.Delete Method.
- The Delete method creates a permission object representing the
- The File class's code checks all callers on the stack to see if they have been granted the demanded permission; if not, a security exception is raised.
- The File class asserts FullTrust in order to call native code, because its callers might not have this permission.
- The File class uses a native API to perform the file delete operation.
- The File class returns to its caller, and the file delete request is completed successfully.
Precautions for Highly Trusted Code
Code in a trusted class library is granted permissions that are not available to most application code. In addition, an assembly might contain classes that do not need special permissions but are granted these permissions because the assembly contains other classes that do require them. These situations can expose a security weakness to the system. Therefore, you must be take special care when writing highly or fully trusted code.
Design trusted code so that it can be called by any semi-trusted code on the system without exposing security holes. Resources are normally protected by a stack walk of all callers. If a caller has insufficient permissions, attempted access is blocked. However, any time trusted code asserts a permission, the code takes responsibility for checking for required permissions. Normally, an assert should follow a permission check of the caller as described earlier in this topic. In addition, the number of higher permission asserts should be minimized to reduce the risk of unintended exposure.
Fully trusted code is implicitly granted all other permissions. In addition, it is allowed to violate rules of type safety and object usage. Independent of the protection of resources, any aspect of the programmatic interface that might break type safety or allow access to data not normally available to the caller can lead to a security problem.
Security checks involve checking the stack for the permissions of all callers. Depending upon the depth of the stack, these operations have the potential to be very expensive. If one operation actually consists of a number of actions at a lower level that require security checks, it might greatly improve performance to check caller permissions once and then assert the necessary permission before performing the actions. The assert will stop the stack walk from propagating further up the stack so that the check will stop there and succeed. This technique typically results in a performance improvement if three or more permission checks can be covered at once.
Summary of Class Library Security Issues
- Any class library that uses protected resources must ensure that it does so only within the permissions of its callers.
- Assertion of permissions should be done only when necessary, and should be preceded by the necessary permission checks.
- To improve performance, aggregate operations that will involve security checks and consider the use of assert to limit stack walks without compromising security.
- Be aware of how a semi-trusted malicious caller might potentially use a class to bypass security.
- Do not assume that code will be called only by callers with certain permissions.
- Do not define non-type-safe interfaces that might be used to bypass security elsewhere.
- Do not expose functionality in a class that allows a semi-trusted caller to take advantage of the higher trust of the class.