Security, Managed Code, and the CLR


At its most fundamental level, the security of the entire .NET platform is based on two things: the type safety of managed code and the vigilance of the Common Language Runtime (CLR). Type-safe managed code ensures that bounds checking is performed on all datatypes, including arrays and strings. It also does not permit inappropriate or dangerous type conversions or direct memory manipulations via pointers. In effect, type-safe code can access only memory locations that it is justifiably permitted to access, and those memory locations are accessible only in the intended legitimate manner. For example, type-safe managed code cannot directly access or modify the memory containing an object's fields or a class's executable code. It can access fields and methods only in a responsible and well-behaved manner.

The upshot of all this is that type-safe managed code effectively prevents buffer overruns and arbitrary code insertion. Effectively, this means that it is extremely difficult to trick a managed program into executing arbitrary attacker-provided code. Although, when talking about security, you should probably never say never, type safety does practically eliminate the possibilities of virus code fragment insertion and stack- overrun attacks. But even if it were somehow possible to inject such malicious code into a loaded assembly, further protection is provided by the CLR by applying administrator-defined security policy, limiting the possible actions to only those permissions that are actually granted to the code. On top of this protection, by digitally signing an assembly, programs that use that assembly can detect any masquerading or tampering of the assembly contents that may have occurred prior to assembly load time. Digital signatures and certificates also allow you to know who provided the assembly so that if any damage were caused by it, you would be in a better legal position in the case of litigation. That should make the disgruntled employee think twice before leaving any time-bomb program!

Microsoft Intermediate Language

Managed .NET code is initially compiled only to an intermediate level language, known as MSIL (Microsoft Intermediate Language). MSIL is sometimes called IL for short. MSIL contains nonnative managed code along with the associated metadata describing the contents and attributes of the assembly. This means that each assembly, which is typically in the form of an EXE or DLL file, is self-contained, requiring no external metadata to be placed in the system registry. MSIL is then dynamically converted to native code on the fly at runtime by the .NET platform's JIT (just-in-time) compiler.

The JIT compiler performs code verification, [11] which attempts to determine that the code is indeed type-safe and checks for any illegal operations, such as direct memory manipulation. It does this by examining intermediate language of each method and the metadata to see what risky operations may exist and what specific operations are explicitly denied . This means that even if a rogue compiler was used to generate the assembly, or if someone managed to insert a virus fragment into an unsigned assembly, it will be detected and prevented [12] at runtime. Type-safe managed code is secure because it is not permitted to directly manipulate memory or call into unmanaged (i.e., native) code unless it has been intentionally given the specific permission to do so.

[11] If the code has been granted the SkipVerification security permission, then it is possible to bypass this verification step.

[12] Because of technical limitations and performance constraints, there is no guarantee that all type-safety issues will be detected at JIT compile time (i.e., at application runtime). The JIT compiler does, however, make a terrific effort to catch most type-safety issues.

Verifiably Type-Safe Code

Security policy, which we discuss in more detail later, can be applied only to verifiably type-safe code. It is important to recognize that not all .NET languages necessarily generate type-safe managed code, and not even all type-safe code is necessarily verifiably type-safe according to the JIT's code verification efforts. Visual Basic .NET always produces verifiably type-safe managed code. [13] Visual C# generates verifiably type-safe managed code unless you use the unsafe keyword. Unsafe C# code is handy but risky when you need to pass arrays and pointers as parameters into unmanaged legacy C/C++ code via PInvoke. Visual C++ .NET can produce type-safe or non-type-safe managed code, or even native code; however, it cannot generate any code that is verifiably type-safe according to the JIT code verification efforts. For other languages, you must check with the provided documentation to see if and under which conditions verifiably type-safe managed code is generated.

[13] Unlike C#, VB.NET does not allow you to work with memory pointers. C# allows direct memory manipulation via pointers, but this requires the use of the unsafe keyword.

When using languages, such as C#, that can generate verifiably type-safe managed code only when you avoid certain language features, you can use the PEVerify.exe utility to test whether the generated code is verifiably type-safe. Assemblies that are not verifiably type-safe are permitted to run only if they are fully trusted. According to default security policy, assemblies that originate from the local machine are fully trusted. Fully trusted code is allowed to request the SkipVerification permission, allowing it to bypass verification, which is necessary for managed C++ code as well as C# compiled with the /unsafe option.

Denying and Demanding Permissions

Another powerful security feature is that code can request permissions that it expects it will need in carrying out its duties . In that case the CLR applies the currently configured security policy to determine whether or not it will grant a requested permission. In this way an assembly can determine upfront whether it has all the needed permissions before starting the first of several steps that could run into trouble partway through. It is far cleaner to deal with the security exception at the outset than to wait until you are halfway through a complex sequence of operations that are difficult to back out of cleanly. We shall see how to request a permission imperatively, using the Demand method, and how to request a permission declaratively , using the PermissionSetAttribute attribute and SecurityAction enumeration, which can be applied to constructs such as assemblies, classes, and methods.

Code can also specify to the CLR those permissions that it does not need and specifically does not want to have. This can be an effective defense tactic that limits risk, ensuring that the code will not be lured into performing unintended actions on behalf of a malicious client code. We will see how to specify permissions imperatively, using the Deny method, and how to specify permissions declaratively by applying the PermissionSetAttribute attribute and SecurityAction enumeration to program constructs.



.NET Security and Cryptography
.NET Security and Cryptography
ISBN: 013100851X
EAN: 2147483647
Year: 2003
Pages: 126

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net