Jump to navigation

Poupou's Corner of the Web

Looking for perfect security? Try a wireless brick.
Otherwise you may find some unperfect stuff here...

Weblog

Mono Security Manager Part VI - P/Invoke and SUCS

So far we've been talking alot about declarative and imperative security demands, both supported since Mono 1.1.4. Truth is that many people may never use them - at least directly (as the framework classes are full of them). But there's also a special and very common case of declarative demands - namely: P/Invokes [1].

By using a [DllImport] attribute on a method declaration the CLR automatically imply the demands (yes that plural was intended ;-) to execute unmanaged code. This (not very) new feature is done in two steps:

First the security manager automatically add a LinkDemand for UnmanagedCode when JIT-ting the method. This is done once, like any JIT checks, has little performance impact (the stack walk is limited to the caller) and is little known (most books don't cover it).

The second part requires the JIT to generate the code for a full demand (i.e. a complete stack walk) to ask, at run-time, the permission to execute UnmanagedCode. This generated security check is then executed everytime before calling the unmanaged method.

This means that everytime a native method is called, via p/invoke, a stack walk occurs to ensure that every callers on the stack have the right to call unmanaged code. Because everytime may be too often in some cases (e.g. loops, recursion...) and because there can be relatively safe calls being made in unmanaged libraries (but not that many!) the security manager won't generate code for a Demand if the [SuppressUnmanagedCodeSecurity] attribute is present on the method or on it's class [2].

Since there isn't a single best way to do this, let's see some examples...

Example #1

You have big assembly that process a lot of data but requires to P/Invoke for a very few options. It would be possible, and useful, for someone to use the assembly in partial trust (e.g. a web app, a store proc) even if the p/invoked methods wouldn't be available.

In this case you have nothing to add/remove/change in your assembly - the runtime will deal with it, i.e. a stack walk will occur when the p/invoke method is called and a SecurityException will be thrown if one of the caller doesn't have the required rights to call unmanaged code.

Example #2

You have the same assembly as example #1 but you want the calls to unmanaged code (or some of them) to succeed even in partial trust. So after a proper API and code audit to ensure the unmanaged code do not bypass the security manager you apply the [SuppressUnmanagedCodeSecurity] attribute to the (or parts of) unmanaged methods declarations.

In this case the JIT will see the special attribute and will not generate the code to do a stack walk before calling the unmanaged code.

Note: This pose a important security risk as the security manager could be corrupted (willingly or not) by the executed unmanaged code. It should be properly reviewed before each release (yuck).

Note: The [SuppressUnmanagedCodeSecurity] has no effect on the LinkDemand check that the JIT will do when compiling methods that calls p/invoked methods. I.e. the direct caller still need to have the UnmanagedCode rights.

Example #3

You have a serious performance problem in a method that calls an unmanaged methods trillions (or maybe a little less) of times.

Now you know after the first call if you have the rights to call unmanaged code or not (i.e. you already got a SecurityException) so there is no need to repeat this. In this case you can change:

[DllImport ("UnmanagedCode")] private extern void UnmanagedCode (); void Loop64TheHardWay () { for (ulong i = 0; i < UInt64.MaxValue; i++) { // we suffer 2^64 stack walks (stack length unknown) CallUnmanagedCode (); } }

for this (as recommended by Microsoft [3])

[DllImport ("UnmanagedCode")] private extern void UnmanagedCode (); void Loop64TheMSWay () { // we do a single (complete and potentially long) stack walk SecurityPermission sp = new SecurityPermission (SecurityPermissionFlag.UnmanagedCode); sp.Demand (); // then we assert the UnmanagedCode permissions (marking the stack at the highest level) sp.Assert (); for (ulong i = 0; i < UInt64.MaxValue; i++) { // we still suffer 2^64 stack walks - but very short ones CallUnmanagedCode (); } // important if the method doesn't return immediately CodeAssertPermission.RevertAssert (); }

or for this:

// SECURITY: make sure no one else is calling this!!! [DllImport ("UnmanagedCode"), SuppressUnmanagedCodeSecurity] private extern void DirectUnmanagedCode (); // to be safer we re-declared the original method so the old one can still be // used safely (with the stack walk) outside the hellish loop void Loop64MyWay () { // we do a single (complete and potentially long) stack walk new SecurityPermission (SecurityPermissionFlag.UnmanagedCode).Demand (); for (ulong i = 0; i < UInt64.MaxValue; i++) { // but we don't have to suffer anything else in the loop DirectCallUnmanagedCode (); } }

Example #4

You have a big assembly that is only (or mostly) wrapper code to an unmanaged library. It wouldn't be possible to use the API in most partial trust scenarios because this assembly provides indirect access to framework-protected resources (e.g. files, sockets...).

In this case you could do nothing to your assembly and let the runtime deal with it (easy option). However that would mean that each p/invoke call would provoke a stack walk to ensure every caller has the right to call unmanaged code. Now if you're looping some of the calls (inside or outside your assembly) you'll probably end up with multiple example #3 on your hands - and spend most of your time in the security manager and not in your unmanaged library.

As a real life example running small match (4 bots) with NRobot currently results in more than 50000 stack walks (for both the Gtk# and SWF versions). About 50 of them (0.1%) aren't for UnmanagedCode checks, that leaves plenty (99.9%) to optimize away (actually it makes a nice stress test on the security manager ;-).

Since the rights to call unmanaged code is fairly static (unless you deal with multiple appdomains) the best solution would be to do a single check. This is possible by placing [SuppressUnmanagedCodeSecurity] on every class (or methods) of the assembly that p/invokes. As for the single check it can be done at the assembly level like this:

[assembly: SecurityPermission (RequestMinimum, UnmanagedCode = true)]

This will ensure that the assembly won't load if the security policy cannot provide it with the right to call unmanaged code. Note that the end result is similar but not identical to doing a stack walk for each unmanaged call. E.g. some stack modifiers, Assert, Deny and PermitOnly, could affect the stack walk results (but can't if there's no stack walk!).

Final note: Methods declared as P/Invoke should be considered as critical and be audited for safety if you intend to use them in partial trust scenarios. Methods, or classes, using [SuppressUnmanagedCodeSecurity] attributes should be considered very critical and should be audited to be sure the attribute is really necessary and doesn't reduce (or even obliterate) the security offered by the CLR.

[1] There is a similar case, supported by the MS runtime, for COM interop but Mono doesn't support COM - even when running on Windows.

[2] However the [SuppressUnmanagedCodeSecurity] attribute doesn't supress the LinkDemand that checks the immediate caller rights to call unmanaged code at JIT time.

[3] Imperative stack modifiers, like Assert aren't yet working in Mono (but the sample code be changed to use a declarative Assert if you want to try it);


6/23/2005 16:32:53 | Comments | Permalink

Updates...

CAS

Mono 1.1.8 been released this week. Just like the 1.1.4 release this one includes some important new CAS features, like:

  • AppDomain based sandboxes;
  • Stack propagation for async code, threads and SWF; and
  • some default policies (like Internet...).

NRobot

The new CAS features allows Mono to execute NRobot in a sandbox. Both the GTK# version and the System.Windows.Form version can be played in the sandbox by doing a single change in the NRobot/Engine/GameArena.cs source file (to change an imperative Assert to a declarative Assert). Future version of NRobot shouldn't require this change. Once done you can start the client like this:

~/cvs/nrobot> mono --security bin/NRobotGTK.exe Bots/Mono

You can substitute bin/NRobotGTK.exe with bin/NRobotWin.exe to try the prettier but slower (probably both because of Cairo) SWF client.

Now we'll need a BadBot to test the sandbox. The easiest way is to modify an existing bot, so I modified the aggresive Follower.cs bot to read the current username, i.e. Environment.UserName, after more than 256 times. Executing the client with this BadBot will result in the bot's suicide while the game continue with the remaining bots.

Quite cool :-) but remember that most of the class libraries doesn't have their permissions and that even the parts that have them hasn't been audited.

Integer overflows

I do read a lot but but I didn't knew integer overflows added a six week delay to Windows XP SP2. Not that it means much without knowing how many people were fixing them, but still interesting knowing the resources available.

Hash collisions

MD5 hash collisions made on certificates and for some "meaningful" documents. As they say: Security attacks only gets better, never worst.


6/17/2005 15:38:37 | Comments | Permalink

Watch out for overflows

I've been wanting to write this entry for about one year now. Not quite sure why it didn't occur before (but lazyness would be my prime suspect ;-).

Using .NET makes it hard, but not impossible, for buffer overflows to occurs. However other overflows, like integer overflows, exists and are still very real in a managed environment (and can even lead to buffer overflows).

Example #1:

// Note: I used unsafe to keep the example simple but the same problem // would exists if we were calling an internal call or p/invoking. public unsafe void Zeroize (byte[] buffer, int startIndex, int count) { int i = 0; fixed (byte* ptr = &buffer[startIndex]) { byte* p = ptr; while (i++ < count) *p++ = 0x00; } }

Now it's easy to imagine a lot of nasty problem with this code as none of the parameters are validated before being used. It's even worst as we have declared this method as public - so we can't assume the caller knows "how to call it". Anyway we should never assume that so let's check them...

Example #2:

public unsafe void Zeroize (byte[] buffer, int startIndex, int count) { if (buffer == null) throw new ArgumentNullException ("buffer"); if (startIndex < 0) throw new ArgumentOutOfRangeException ("negative", "startIndex"); if (count < 0) throw new ArgumentOutOfRangeException ("negative", "count"); if (startIndex + count > buffer.length) throw new ArgumentOutOfRangeException ("startIndex + count > this.length"); int i = 0; fixed (byte* ptr = &buffer[startIndex]) { byte* p = ptr; while (i++ < count) *p++ = 0x00; } }

This second version is better because it adds validation of all parameters. However it doesn't validate all of them correctly. For example what if startIndex or count is big, very big like Int32.MaxValue...

byte[] array = new byte[16]; Zeroize (array, 15, Int32.MaxValue);

In this case we have:

  • buffer is non null, first check is ok;
  • startIndex == 15, which is greater than 0, second check is ok;
  • count == Int32.MaxValue, which is also greater than 0, third check is ok;
  • startIndex + count = 15 + Int32.MaxValue == -2147483634 which is smaller than 16, fourth check is ok :(

All checks are positive so the unsafe code can execute, problem is (i++ < count) won't happen anytime soon - at least not before a lot of data gets zeroized. It this common ?

You bet! The .NET documentation often (or maybe always ?) documents the check as an addition (or doesn't even document the check) but the framework itself isn't coded like that. I have yet to found one integer overflow in MS implementation of the framework but I did found other interesting stuff, which were all reported, so I guess they have some automated tool to check for this (it's too good to be true ;-).

Integer overflow are usually much easier to fix than to find (even with a common pattern). In this case the problem can be fixed by reversing the operation, i.e. doing a substraction instead of an addition.

// re-ordered to avoid possible integer overflow // (experience shows that it helps to comment this stuff) if (startIndex > this.length - count) throw new ArgumentOutOfRangeException ("startIndex + count > this.length");

We could have an integer underflow if we were only using this check (e.g. if startIndex or count were equals to Int32.MinValue). This is why there are (or there should be) checks for both the startIndex and count parameters to ensure they can't be negative.

This isn't the only case where integer [over|under]flow can bite you. In many case the VM will protect you against this problem (e.g. if we haven't used unsafe code). However this can be very dangerous if your code, like it's often the case inside mscorlib.dll, System.dll and in all interop assemblies, use those values to deal with unmanaged ressources (e.g. memory).


6/10/2005 15:19:27 | Comments | Permalink

The views expressed on this website/weblog are mine alone and do not necessarily reflect the views of my employer.