Writing managed code for semi-trusted environment

home

Say what?

In .NET’s CLR Microsoft has introduced the concept of semi-trusted code – a code that can do some things and can not do other things, for example – read files but not the registry. Well, the concept was not exactly new J, but the .NET implementation, IMO, got it right and it is actually very usable. In 3 words the CLR security system works like this:

-         A loader loads an assembly

o       It collects information about the assembly called the “evidence”. There are 5 major types of evidence that built into the system – Url, Zone, Site, StrongName, Publisher; evidence system is extendable and there could be custom types of evidence, too.

o       Once the evidence it gathered it is fed into the security policy system, which maps a set of evidence to a set of permissions.

o       That set of permissions is called an “assembly grant set”, and it is assigned to each assembly when it is loaded.

-         A protected resources is accessed – e.g. a file open operation is attempted

o       The FileStream class constructor creates an object of FileIOPermission describing what file and how it being accessed and does .Demand() on it.

o       This initiates an operation called a “stack walk”. Stack walk verifies that every method on the stack has the permission to open the file, by looking at the method’s assembly grant set.

o       If any of the methods on the stack belongs to an assembly whose grant set does not include FileIOPermission, a SecurityException is thrown. This prevents something called “luring attacks” (do I remind you of Dr. Evil?), where lower trusted code orchestrates highly trusted code into doing bad things.

o       There are things called “stack walk modifiers” – Assert(), Deny() and PermitOnly(). They make the security system much more flexible, but also make it easier for a trusted library writer to screw up.

 

If you had to write your own FileStream class you would have to use an Assert() in addition to a Demand(). The Assert() would stop a check for the permission to call into unmanaged code (which you will have to call because you need Win32 APIs to do files) – you wouldn’t want your callers to necessarily have that permission, you only want them to have the permission to work with files.

 

If you haven’t worked with code access security before the above is probably confusing (it took me about a year to fully and deeply understand this stuff J). If you want to know more about it, there are a couple of good books – “.NET Framework Security” by Brian LaMacchia et al, “Writing Secure Code 2nd editionby Michael Howard (look me up in the acknowledgements section J), also “Visual Basic .NET Code Security Handbook” by Eric Lippert is great for VB.NET people.

Also, Greg Fee has recently written a good article on stack walk in his blog.

The Problem

Anyway, semi-trusted environment is when your application does not get a full set of permissions that would allow it to do everything. If you look at the default security policy (run “.NET Framework Configuration” from the control panel or “caspol –l” if you are a hardcode command line guy), you can kind of see that everything coming from zone MyComputer has FullTrust (can do everything) and stuff coming from Internet gets a permission set named “Internet” (we will look at it in detail). Assemblies coming from Intranet or network shares get LocalIntranet permission set – that is why you get a security exception when running your favorite application (whose creator didn’t read this article) off a mapped drive (read Shawn’s blog on how you can fix their mess J).

 

If you have looked at the default Internet permission set in caspol tool output you have seen a bunch of xml goo that represents it. I will try to translate it for you. So, as an application coming from the Internet (in v1.1 of the Framework), you are entitled to:

-         Open files the user has explicitly pointed you to (FileDialogPermission – Open);

-         Use up to 10240 bytes of Isolated Storage (a cool feature, I will probably write another entry just about that in the future), with isolation by application (no cross app talking) – IsolatedStorageFilePermission;

-         You can actually execute code (didn’t expect it? J) – SecurityPermission – Execution;

-         You can display safe top level windows - you won’t be able to fully control the window title, it is going to say “Internet” and then the site name – UIPermission - SafeTopLevelWindows;

-         You can use your own application clipboard (no passing stuff between applications) – UIPermission – OwnClipboard

-         You can print (only with user’s explicit approval) – PrintingPermission – SafePrinting;

-         You can read web contents originating from the same location as your application - Same Site Web.

 

Not much, is it? Now, if we take LocalIntranet, it adds the following:

-         You can read ‘USERNAME’ environment variable (EnvironmentPermission);

-         You can read and save files with explicit user’s approval (FileDialogPermission);

-         You can use all the IsolatedStorage space you want, and you can use isolation by assembly – applications can share data this way (IsolatedStorageFilePermission);

-         You can dynamically create assemblies in memory (ReflectionPermission – ReflectionEmit);

-         You can redirect assembly bindings (SecurityPermission – BindingRedirects);

-         You can assert permissions (use .Assert() method) (SecurityPermission – Assertion);

-         You can display any kind of windows with full control over them and you can fully use the clipboard (UIPermission – Unrestricted);

-         You can use DNS services (DnsPermission);

-         You can print with the default printing settings without user’s explicit approval (PrintingPermission – DefaultPrinting);

-         You can access Event Log (EventLogPermission);

-         You can read files from the directory of your origin and its subdirectories, and you can enumerate files/folders there (Same directory FileIO - Read, PathDiscovery).

 

 

Now, how in the world I can write code that works with these restrictions?

Well, that’s the hard part. First there are some things that make your code un-runnable in default semi-trusted environment for Internet or Intranet right away:

-         Unverifiable code: C# code with unsafe blocks or compiled with /unsafe option (you will have it, for example, if you are using pointers), any C++ code, code written in IL that uses unverifiable constructs (see ECMA specs);

-         Corrupted code or code changed by some obfuscators that made it unverifiable;

-         Code that has declarative assembly permission requests for more trust than the security policy gives them (you can see assembly requests with permview.exe tool);

-         Code that calls into unmanaged code (by using native COM or DllImport attribute);

-         Code using classes/methods that require permissions outside of the security sandbox (for example Reflection, some file i/o, Registry, etc.). The documentation that comes with the Framework tries to document the security requirements for all APIs, however you may here and there stumble upon a method where the doc does not list its real requirements.

 

(Note that we are talking about the default security policy here. Remember that you can always adjust policy on the client machine to give more permissions to a certain assembly).

 

It is in fact very hard to figure out whether your code is going to work off the Internet or Intranet at the development stage without trying it out. In the next release of the .NET Framework we are working very hard to make this analysis possible. Meanwhile, trying and failing is the most reliable method. To make it easier you can use something called “assembly permission requests”. You can put a declarative attribute like the following in your assembly to ensure that no more permissions are granted to it, even if it is loaded from the local drive – this will help you catch sandbox violations easier:

 

[assembly: FileIOPermissionAttribute(SecurityAction.RequestOptional, Unrestricted=true)]

[assembly: SecurityPermissionAttribute(SecurityAction.RequestOptional, Execution=true)]

 

The attribute above will ensure that your assembly only gets FilIOPermission and the permission to execute. It is really a good practice to have those assembly requests on your assemblies in general; by using them you are limiting your liability – imagine your library has a security hole that would allow attackers to make it do bad things you didn’t intend it to do – having a self-restriction like this may help to avoid such situation.

 

I am sure you will get a sense of what’s allowed and what is not pretty soon.

 

I have done something you can call a case study of this problem by trying to implement a useful tool that would work in Internet or Intranet environment. I have created an assembly metadata parser and IL disassembler (you can think of it as a mini-version of ILDASM), along with a simple managed code obfuscator. You can find them at http://www.dotnetthis/Samples/mangler.htm, and I encourage you to check them out because they are really cool. So, as you can see the exercise was successful and I got some nice apps out of it and some knowledge to share with you.

 

Some problems I hit while developing these applications and their solutions.

Writing a metadata parser / IL disassembler is a problem in itself, and doing it in partially-trusted code is even harder. First, you can not use Reflection (not that it helped much anyway) or unmanaged metadata reading APIs (like IMetaDataImport), so you have to parse the PE files from scratch. Then, you can not use pointers to walk through the memory and pull bytes. Then, when you found the data you are interested in you can not just map it to a struct and be done (in fully trusted code you could use something like Marshal.PtrToStructure).

 

So let me give you a few specific examples.

 

Reading files from disk

In Internet/Intranet sandbox you can not just open a file and read its contents – you will get a security exception. However if you look closer at the set of allowed permissions you can see a FileDialogPermission there. This basically means that you can display an OpenFileDialog, and if the user selects a file, you can grab an already opened Stream object off of it. The code goes like this:

 

// create an OpenFileDialog instance

      OpenFileDialog openFileDialog = new OpenFileDialog();

      // see if the user picked a file

      if (openFileDialog.ShowDialog() != DialogResult.OK)

            return;

      // check if the file actually exists

      if (!openFileDialog.CheckFileExists)

            return;

      // open the file for read and grab the stream

      FileStream fs = (FileStream)openFileDialog.OpenFile();

      // read the contects into a byte array

      byte[] array = new byte[fs.Length];

      fs.Read(array, 0, (int)fs.Length);

 

This will get you the contents of a file the user has picked.

 

Saving a file to disk

Saving file to disk is accomplished approximately the same way, except you use a SaveFileDialog. Intranet zone, by default, gets the permission to use SaveFileDialog. Internet zone however, does not. That is why in my sample obfuscator, if you are running it off the Internet, you won’t be able to save the obfuscated files (you will be able to view them though) unless you adjust the policy to give my application something less restrictive, for example LocalIntranet permission set. Another thing about SaveFileDialog is that with the default policy you are not going to get permissions to control the dialog caption, so in case you have to save multiple files, you won’t be able to say “Saving file X” in the title. As you can see, in my sample I am using the main control window’s status bar for it. This restriction is done to prevent evil people from confusing you by trustworthy-looking window captions.

 

Calling into your other components

If your application consists of several assemblies it is important that it is able to load them and run them. Both Internet and Intranet zone in the default v1.1 policy give assemblies the permission to connect back to the server of origin to fetch components – this is needed for loading referenced libraries. If you are on v1.0, your Internet zone may get not permission at all and you will have to grant some permissions to it manually, including the permission to read things from the server if your application has multiple assemblies.

Another important thing that I mentioned is being able to run. If your assembly is strong name signed and you call into it from your partially trusted application this will not work unless you have AllowPartiallyTrustedCallersAttribute on it:

 

[assembly: AllowPartiallyTrustedCallersAttribute]

 

 

Working with binary data

As I already mentioned, parsing metadata is a lot of work involving walking binary data structures. In plain C I would just cast a pointer to byte to a pointer to a structure describing a certain metadata header and I have easy access to the fields. In unsafe managed code I would use Marshal class to do essentially the same. However in security sandboxed codes I don’t have that luxury and I need to invent my own way to make it easier. What I have come up with in my application is a pseudo-pointer class that makes it easier for me to emulate pointer operation C-style. It goes something like this (some error checking and other parts are omitted for better clarity):

 

      public struct SafePointer

      {

            internal byte[]         _array;

            internal int            _index;

 

                        // ...

 

            public static implicit operator byte(SafePointer pp)

            {

                  return pp._array[pp._index];

            }

                       

            public static explicit operator UInt16(SafePointer sp)

            {

                  return (ushort)(((byte)(sp+1)<<8) | (byte)(sp));

            }

 

                        // ... other conversion skipped

 

            public static SafePointer operator +(SafePointer pp, int n)

            {

                  return new SafePointer(pp._array, pp._index+n);

            }

 

                        // ... skipped

 

            public static SafePointer operator ++(SafePointer sp)

            {

                  sp._index++;

                  return sp;

            }

 

                        // ... the rest of operators and methods skipped

      }

 

A class like this allows a very convenient pointer operations, for example:

 

      SafePointer spBase = new SafePointer(array, 0);

 

      SafePointer spTemp = spBase + offset;

      while(spTemp < spEnd)

      {

            Console.WriteLine((UInt16)spTemp);

            spTemp+=2;

      }

 

This code will read 2 bytes words from a block of memory and print them.

 

I then created a universal Header class that, given information about the fields, such as their names, offsets and length, would accept a SafePointer and let you read the values of the fields based either on the field name or its index. It was then very easy to subclass that class and come up with customized header classes for reading various kinds of headers living in the metadata. Same deal for metadata tables and so forth.

This gave me a very powerful and flexible design which allowed many different things. If you saw a MetaInfo sample in the Tools Developers Guide, I wrote a similar tool using the library I have created, and the whole tool was less than 2 screens of code.

 

Checking your permissions

When writing for semi-trust, it is important for the application to be aware of the permissions it has, so that the user will not get an ugly security exception dialog. For example, I mentioned above that the default policy gives permissions to display a SaveFileDialog to Intranet, but not to Internet. If you try to invoke this functionality while working off the Internet, a SecurityException will be thrown. A more graceful way to deal with this kind of situation is checking your permissions. To do that you should create an instance of the permission class that describes the resource you are trying to access and then you have two options:

-         Call its Demand() methods and prepare to catch a SecurityException. If you caught it that means you (or your callers) do not have the permission.

-         Do SecurityManager.IsGranted(…). This will tell you if the permission is granted to your assembly.

There is an important difference between the two. The first approach initiates a stack walk and will check all the callers; it imitates what happens when you actually use the resource and is more accurate. The second approach will check your assembly’s permissions, but not its callers’. If you are working within the default security policy the 2nd approach is good – it is fast and it will give you the right result. The 1st approach you will need if you adjust the security policy to give your assembly more trust, in this case you want to check all the callers to make sure the thing will actually work (and if it does not you may want to use Assert() to stop the stack walk in your assembly).

Here is the code I used to check for save file dialog permission:

 

      FileDialogPermission fdp = new FileDialogPermission(FileDialogPermissionAccess.Save);

      if (!SecurityManager.IsGranted(fdp))

      {

            MessageBox.Show("Saving files is not allowed"

            return;

      }

      // do stuff

 

 

Getting the performance right

Writing fast semi-trusted code is a big problem in itself. When you need to process large amounts of data and are unable to use unsafe code your performance is going to be affected. First of all, you will likely have to use boxing/unboxing (casting to/from Object), which is expensive. Then, you won’t be able to use some processor/memory-friendly optimization. To give you an example, I have looked at two different functions comparing byte arrays. The first function works in semi-trust and goes through the arrays byte by byte. The second function uses unsafe code blocks, pointers and casts 8-byte chunks of data to UInt64, which are then compared. The second function is more than 3 times faster than the 1st. Finally, when you run FullTrust, there is a lot of performance optimizations in the security system that skip security checks when possible. In semi-trusted environment such as Internet or Intranet much less optimizations are possible and the security checks that are happening when you use some APIs will potentially take more time. Don’t try to turn the security off though, because if you do, next time you visit my obfuscator sample I will tell it to obfuscate your whole disk and nothing is going to stop it! J

 

Need to download mp3 to CD/DVD burner or converter, phone answering machine of fax tools? Here you may find all software that you need. At DotNetThis software section you may find the latest software from management, business financial software to image viewers

 

(c) Ivan Medvedev    2003-2007