Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

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

.pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

798

C H A P T E R 1 9 S E C U R I T Y

Note InheritanceDemand can only be applied declaratively.

Requests

Requesting permissions is a different approach to handling permission in code access security. Instead of letting the code run up to the point where the permission is demanded, request permissions don’t even let the assembly load into memory.

You apply requests using declarative syntax on the assembly. That way, the CLR can check at the time when the assembly is loading to see if the appropriate permissions are satisfied. If the permissions requested are not satisfied by the evidence, then the assembly itself does not load.

RequestMinimum

The RequestMinimum is an all-or-nothing proposition for an assembly. It is the permission that the code must have to run. The failure to have the permissions causes the CLR to not load the assembly.

using namespace System; using namespace System::IO;

using namespace System::Security;

using namespace System::Security::Permissions;

[assembly:FileIOPermission(SecurityAction::RequestMinimum, Write="C:\\")];

namespace MustWriteTOCRoot

{

}

RequestOptional

The RequestOptional allows you to request a set of permissions while refusing all other permissions the CLR might otherwise have given. The RequestOptional does not indicate that all the permissions specified are needed. Instead, it says these are the permissions it is going to let your code have when this assembly runs.

Note that if your code tries to implement a permission not granted by RequestOptional then a SecurityException will be thrown. You might also note that if your code tries to use a permission granted by RequestOptional but not granted to the executing assembly, then the CLR is going to throw an exception just like it would have if you hadn’t used any RequestOptional permissions.

To get CASSecurity.exe to run with RequestOptional permissions, you need the following four lines because all of these permissions are required for the application to run successfully:

[assembly:FileIOPermission(SecurityAction::RequestOptional, Read="C:\\")]; [assembly:FileIOPermission(SecurityAction::RequestOptional, Write="C:\\")]; [assembly:UIPermission(SecurityAction::RequestOptional,Unrestricted=true)]; [assembly:SecurityPermission(SecurityAction::RequestOptional,

Unrestricted=true)];

RequestRefuse

RequestRefuse is basically the opposite of RequestOptional. With RequestRefuse you specify which permissions the assembly will refuse. Any other permission that you don’t list is allowed.

C H A P T E R 1 9 S E C U R I T Y

799

I normally use RequestOptional instead of RequestRefuse as I feel it provides a more secure environment—you know exactly which permissions you are allowing. The only time I would

use RequestRefuse is when I want a very specific set of permissions to be refused. If I were to use RequestRefuse instead of RequestOptional in the CASSecurity.exe example, I would have to include refusals for all the permissions available in .NET except the four lines listed earlier.

The following line shows what you would need to code to refuse an assembly any access to the Registry:

[assembly:RegistryPermission(SecurityAction::RequestRefuse,Unrestricted=true)];

Overrides

There will come a time where you will find that your application has FullTrust and yet your assembly still throws permission errors. This can’t happen, so you must have coded something incorrectly, right? Well, actually you may have coded everything correctly. What most likely happened is one of the assemblies down the stack walk did not have FullTrust or a permission was overridden.

It is with these last three actions on permissions that we override the standard stack walk.

Assert/RevertAssert

The Assert override is probably one of the most dangerous features of code access security and must be used carefully. The reason is that with Assert you can accidentally add permissions that the stack walk would normally have denied. This is because the Assert stops the stack walk at the stack frame where the Assert is made. For those of you more visually inclined, Figure 19-14 might help.

Figure 19-14. Possible Assert problem

Caution Microsoft warns that “because calling Assert removes the requirement that all code in the call chain must be granted permission to access the specified resource, it can open up security vulnerabilities if used incorrectly or inappropriately. Therefore, it should be used with great caution.”

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

C H A P T E R 2 0

■ ■ ■

Unsafe C++ .NET Programming

Well, I think that’s enough about safe/managed code. Let’s take a look at another major area of C++/CLI: the ability to create unsafe/unmanaged code. Sounds kind of scary, doesn’t it?

I’m not sure I understand why C++ .NET books spend so much time on this area of C++ programming as it is (usually) rather simple. It’s what C++ programmers have been doing for years, and there are literally hundreds of books on the topic. The only real differences to a C++/CLI code developer are a few extra classes and attributes.

If there really is any complexity, it is on the side of the unsafe/unmanaged code and not the safe/managed code that it interfaces with. Most of this complexity revolves around forcing safe/ managed code to be executed within a block of unsafe/unmanaged code, which is really the opposite of what you should normally be doing. Safe/managed code should be the new code being developed, and it should be referencing only when really necessary the old unsafe/unmanaged code.

One nice simplifying feature and a big time saver is that Visual Studio 2005 even auto-generates a lot of the interfacing code for you—most notably in the area of COM development, but I’ll get to that in the next chapter.

In this chapter, I will look at some of the more basic areas of unsafe/unmanaged C++/CLI programming. One thing about these more basic areas is that, when used, they are the most common reason you end up not being able to compile your code with the /clr:safe option. Once you finish this chapter, you should be able to figure out what needs to be changed in your code to make it safe/managed.

After we have the basics down in this chapter, I’ll then take a look at some of the more advanced topics like interfacing save/managed code with unsafe/unmanaged DLLs and COM objects in the next chapter.

If you are new to unmanaged C++, you might want to consult Beginning ANSI C++: The Complete Language, Third Edition by Ivor Horton (Apress, 2004).

Note Basically, unmanaged C++ syntax == ANSI C++ syntax.

What Is Unsafe Code?

I’ve kind of glossed over it in all the proceeding chapters of the book, but the ability to create safe code is probably the biggest enhancement made to C++/CLI over Managed Extensions for C++. Before C++/CLI, you could only create unsafe C++ code. Yes, Managed Extensions for C++ code enabled memory to be maintained by the CLR, but there was no such thing as safe C++ .NET code, at least not in the sense that it could be verified by the CLR. Of course, you could code C++ in a safe manner, but the user who executed your code was given no guarantee that it was actually safe.

805

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