
Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]
.pdf

800 |
C H A P T E R 1 9 ■ S E C U R I T Y |
Personally, I only use Assert when I have complete control of the call stack that is being walked. Keep in mind that Assert does not grant permission to a demand. The demand works as it
normally would for that stack frame, so if that frame would normally have denied the permission the Assert point would also be denied permission.
The actual code involved in an Assert is fairly simple:
CodeAccessPermission ^permission =
gcnew FileIOPermission(FileIOPermissionAccess::Read, "C:\\");
permission->Assert(); // Do stuff
permission->RevertAssert();
Since only one Assert is allowed to be in effect at a time for a frame, you should make sure that you call the RevertAssert() method when you are done with your Assert. This basically turns off your Assert.
Deny/RevertDeny
As I’m sure you suspect, this form of override causes the current stack frame to be denied for the resource type specified. Using this override enables you to disable permissions for accessing resources even though the application is running under a code group that has a permission set with the appropriate permissions.
CodeAccessPermission ^permissionRead =
gcnew FileIOPermission(FileIOPermissionAccess::Read, "C:\\");
permissionRead->Deny(); // Do stuff
permissionRead->RevertDeny();
The RevertDeny is used to restore the previous permissions to the specified resource. Note that if the resource was denied permissions before the Deny was called, then the resource continues to not have permission.
PermitOnly/RevertPermitOnly
If you want to be specific with which resources are available on a stack walk, then the PermitOnly may be what you want. This override identifies the only resources that will have permissions on the call stack from the time the PermitOnly is specified to its corresponding RevertPermitOnly.
CodeAccessPermission ^permissionWrite =
gcnew FileIOPermission(FileIOPermissionAccess::Write, "C:\\");
permissionWrite->PermitOnly(); // Do Stuff
permissionWrite->RevertPermitOnly();
Overrides can be a bit difficult to understand without an example. Listing 19-7 shows how you can use Deny and PermitOnly on the call stack and then have Assert overrule them.
C H A P T E R 1 9 ■ S E C U R I T Y |
801 |
Listing 19-7. Assert, Deny, and PermitOnly
#include "stdafx.h"
using namespace System; using namespace System::IO;
using namespace System::Security;
using namespace System::Security::Permissions;
void AssertRead()
{
CodeAccessPermission ^permission =
gcnew FileIOPermission(FileIOPermissionAccess::Read, "C:\\");
permission->Assert();
StreamReader ^sr = File::OpenText("C:\\TestFile.txt"); String ^s = sr->ReadLine();
sr->Close(); permission->RevertAssert(); Console::WriteLine("Successful Read");
}
void NoAssertRead()
{
StreamReader ^sr = File::OpenText("C:\\TestFile.txt"); String ^s = sr->ReadLine();
sr->Close(); Console::WriteLine("Successful Read");
}
void main()
{
// Deny Reading C: CodeAccessPermission ^permissionRead =
gcnew FileIOPermission(FileIOPermissionAccess::Read, "C:\\");
permissionRead->Deny(); try
{
AssertRead();
NoAssertRead();
}
catch(SecurityException^)
{
Console::WriteLine("Failed To Read");
}
permissionRead->RevertDeny();
// Only allow Writing to C: CodeAccessPermission ^permissionWrite =
gcnew FileIOPermission(FileIOPermissionAccess::Write, "C:\\");

802 |
C H A P T E R 1 9 ■ S E C U R I T Y |
permissionWrite->PermitOnly(); try
{
AssertRead();
NoAssertRead();
}
catch(SecurityException^)
{
Console::WriteLine("Failed To Read");
}
permissionWrite->RevertPermitOnly();
}
When you run this example, you do it from the console and thus it has all the rights of the Windows account running it—in my case administrative rights.. Notice that even though I have administrative rights I lose permissions with the Deny and PermitOnly. I only get them back with the Assert.
Figure 19-15 shows AssertDenyPermit.exe in action.
Figure 19-15. Results of AssertDenyPermit.exe
Summary
In this chapter you covered .NET security with a specific focus on how to implement it using C++/CLI. You started off with the easier of the two major types of security provided by .NET, role-based security, in particular the identity, principal, and permissions. Next you looked at code access security. You examined permissions, permission sets, policies, code groups, and evidence. Finally, with the basics of CAS covered, you learned how to secure your own code using demands, requests, and overrides.
Now that you have examined safe and managed code, in the next chapter you’ll change gears and look at coding with unsafe and unmanaged code.

■ ■ ■
P A R T 3
■ ■ ■
Unsafe/Unmanaged
C++/CLI


806 |
C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G |
That has all changed in .NET version 2.0 and the C++/CLI compiler, as the /clr:safe switch generates verifiable code that the CLR can provide trust levels to, just like it can with C# or Visual Basic .NET.
■Note You may have noticed in the examples I provided for download that I almost always use the /clr:safe option. Personally, I think it should be the default, as I like the idea of the code being safe. I usually only resort to using the /clr option when I’m forced to work with unsafe/unmanaged code.
Unsafe, unmanaged, and native code are all terms that many writers seem to throw around as if they were interchangeable. But actually they are all different things.
When you speak of unsafe code, you are talking about the compiler’s inability to create verifiable code, which is thus unsafe in regards to security. Unsafe code, when allowed to run by the CLR, has as much control of the computer as you do. If you are, like me (and probably most other developers), an administrator, then the unsafe code has complete control of your computer. Now that is a scary thought.
Unmanaged code is unsafe by its very nature. This type of code has the ability to access and create instances of objects outside of the CLR sandbox. In most cases when you use pointers in your code, you are dealing with unmanaged code. You will see that this is not always true, as C++/CLI provides something called the interior pointer that, if handled correctly, can be verified and thus be compiled as safe.
Native code is code that is compiled outside of the C++/CLI world and cannot be verified in any way. Native code is usually in the form of machine language, but again, in theory, it need not be. Native code is usually found in DLLs and COM objects. Native code is generated using a non-.NET compiler or without any type of /clr switch if generated with a .NET compiler.
To confuse or simplify things (depending on how you look at it), Microsoft added the ability to place native code within your safe/managed code using the #pragma unmanaged directive. Personally, I think it should be called the native directive, because it would reflect more accurately what it is doing and because unmanaged code does not need to be native code.
What does this all mean? Unsafe code is code that contains embedded unmanaged code. Notice I added the word “embedded.” The reason for this is it is still possible to have safe code that accesses or runs unmanaged code, so long as the correct interfaces are implemented. What these interfaces do is allow the CLR to know when unsafe code is about to be used and then to use code access security (which I covered in the previous chapter) to determine whether the unsafe code can be executed.
Why Do We Still Need Unsafe Code?
The funny thing is one of the major reasons why unsafe code will continue to exist is due to unsafe code’s ability to do things that safe code can’t do simply because of its unsafe nature. One of the more obvious of these unsafe features is pointer arithmetic, or the ability to access memory frequently outside of the CLR sandbox and then manipulate the addresses of this memory directly.
Another reason, more obviously, is because there are millions of lines of unsafe code out there already (much of it C++), and it will take an awfully long time to convert to safe code. And, in most cases, there is really no need to do the conversion in the first place, as the code works just fine as it is.
You might think that most of the unsafe code in the case of C++ could simply be recompiled with the /clr:safe option and be made safe, but unfortunately in most cases it is not that simple, as pointers and pointer arithmetic are the main means of handling memory in C++ prior to C++/CLI, and as I noted previously, pointers are are in nearly all cases not verifiable, and pointer arithmetic is never verifiable.
C H A P T E R 2 0 ■ U N S A F E C + + . N E T P R O G R A M M I N G |
807 |
Unsafe code is usually needed for interfacing with computer hardware. Most hardware drivers are written in C++, C, or some form of assembly. In most cases, the code relies heavily on pointers to access the hardware, and these pointers point outside of the CLR sandbox.
Another issue about unsafe code is not all of it is in computer languages that can readily be converted to a safe version, as no mainstream .NET compiler is available. (With the growing number of .NET languages, this argument is losing its weight.) Interestingly, the resulting libraries generated by these languages are frequently wrapped by C++, if the function provided by this nonstandard coding language needs to be accessed by third parties. Unfortunately, in most cases the wrapper methods themselves rely heavily on pointers, which even make the interfacing wrapper method unsafe. Now, if you want to generate verifiable code for the main application calling this code, you have the requirement of building a safe wrapper around the unsafe wrapper. Can you say yuck?
Creating Unsafe Code
As I noted earlier, unsafe code is normally created by adding native code to your managed code. It is also possible to create unsafe code with only MSIL code by using unsafe operations or objects.
There are several ways of coding C++/CLI so that it is unsafe. The following are four of the more common ways of making your assembly unsafe. There are others, but these are the methods I’ve frequently come across in my travels.
•Managed and unmanaged #pragma directives
•Unmanaged arrays
•Unmanaged classes/structs
•Pointers and pointer arithmetic
The Managed and Unmanaged #pragma Directives
The most basic way of creating unsafe code is by mixing managed and unmanaged code together with the directives #pragma managed and #pragma unmanaged. When encountered by the compiler, these directives tell the compiler to generate MSIL (managed) code or native (unmanaged) code. The compiler continues to generate the specified type of code until it encounters a directive to switch the type of code generated.
Listing 20-1 shows a very simple example of using the #pragma managed and #pragma unmanaged directives.
Listing 20-1. The Managed and Unmanaged #pragma Directives
using namespace System;
#pragma unmanaged
int UMadd(int a, int b)
{
return a + b;
}
#pragma managed