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:

// 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.