views:

237

answers:

3

I have some IO code that reads a stream within a try..catch. It catches IOException and calls System.Runtime.InteropServices.Marshal.GetHRForException() within the catch, in an attempt to take different actions based on the HResult. Something like this:

try 
{
    stream.Read(...);
}
catch (IOException ioexc1)
{
   uint hr = (uint) Marshal.GetHRForException(ioexc1);
   if (hr == ...) 
      do_one_thing();
   else
      do_another();
}

The assembly is signed and marked with AllowPartiallyTrustedCallersAttribute.

But running this code within ASP.NET with trust="medium", I get this exception:

Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.

A couple questions:

  1. I think the exception is occurring because GetHRForException calls into unmanaged code, which is not permitted in medium trust. Correct?
  2. This exception is being thrown, not at the time of execution of GetHRForException, but at the time the method is being JIT'ed - Correct? (The stacktrace shows my method, but I am 99% certain that an IO exception has not occurred)
  3. If so, is there a way for me to vary the behavior in a partial trust environment, so that I don't call the GetHRForException (unmanaged code) where it is not permitted? In other words, how can I allow the JIT to succeed at compile time, while also evaluating at runtime whether the code should call GetHRForException()? Something like this:


catch (IOException ioexc1)
{
   if (!OkToCallUnmanaged())
       throw ioexc1; 

   uint hr = (uint) Marshal.GetHRForException(ioexc1);
   if (hr == ...) 
      do_one_thing();
   else
      do_another();
}

I think there is a runtime mechanism for testing if permissions are available, but haven't been able to find it.


EDIT: Is this blog article the answer? ShawnFa of Microsoft says that you cannot do a try ... catch(SecurityException) around a method protected by a LinkDemand. If MethodA() calls MethodB(), and MethodB() is marked with LinkDemand for full trust, then the LinkDemand is checked with MethodA is Jit'ed. Therefore to avoid the SecurityException, I need to extract Marshal.GetHRForException into a separate method. Is that correct?

Applied to my code, MethodA() might be the code that calls Read, and then in the catch tries to call GetHRForException(). GetHRForException is MethodB(). The LinkDemand is evaluated when MethodA() is JIT'd. (This LinkDemand fails in my medium-trust ASP.NET scenario). If I move the GetHRForException into a new method, MethodC(), and conditionally call MethodC() only after an imperative permission.Demand() succeeds, theoretically I should be able to avoid the SecurityException at JIT time, because MethodC() will be JIT'd only after the permission.Demain() succeeds.

+1  A: 
  1. Yes - medium trust will not allow calls into unmanaged code. The only trust level that allows it is full trust.
  2. It depends. CAS demands can take place at runtime, but the hosting environment can also go on a wander and look for things it can't do.
  3. You can test to see if you can make a call to unmanaged code by using a CAS demand with an instance of SecurityPermission.

The code to make a CAS demand looks like this

SecurityPermission permission = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
try
{
  permission.Demand();
  // Got it, away we go
}
catch (SecurityException)
{
  // Could not get permission to make unmanaged code calls
  // React accordingly.
}
blowdart
OK, this is great info. This covers part b of Q3. But what about part a? how do I get the JIT compile to succeed? Can I mark my method with a security attribute or ... ? Remember, my theory is that the SecurityPermission error is not happening at runtime, it is happening during JIT - and I think you confirmed that this is *possible*. So the question is, how do I write the code to allow the JIT to compile.
Cheeso
It should be happening at runtime, otherwise the assembly wouldn't even load - and for that to happen the assembly needs to be marked as requiring the permission.Even then that's arguably a runtime check, as it will happen on assembly load, which could be at runtime.
blowdart
I think this is relevant: http://blogs.msdn.com/shawnfa/archive/2006/01/11/511716.aspx It basically says this, I think: Suppose MethodA() calls MethodB(), and MethodB() calls unmanaged code, such as Marshal.GetHRForException(), (which implies a LinkDemand for UnmanagedCode). The check for UnmanagedCode permission occurs at the time MethodA() is JITted. So in order to avoid the error during JIT, I need to separate the check (permission.Demand()) and the call (GetHRForException) by one method call.
Cheeso
Different type of check, link demands are attributes on a method, and are indeed checked at JIT time. It's pretty much used by the framework itself and it's rare to see it outside of the CLR source.What I demonstrate is an imperative demand, not a declarative one like a [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
blowdart
I understand imperative vs declarative, but the article seems to be exactly my situation. or am I wrong? The LinkDemand (implicit declarative) from GetHrForException is causing my method to throw. I can put in an imperative check, but that, alone, will not avoid the problem I observed. To be clear: it is not throwing in GetHRForException. If I do an if/then around GetHRForException(), it will still throw. In JIT ! The only way to avoid that is to introduce another level of method call. or have I misunderstood Mr Shawn Farkas' blog post?
Cheeso
Ah no, there's no such thing as implicit declarative. What Shawn is describing with the second nested method is more to do with Critical / Safe Critical, a CLR only feature (at least until .NET 4.0)
blowdart
After further review....the answer is, I need p.IsUnrestricted(). It does exactly what I described in my original post - tells me if I can call unmanaged code. p.Demand() throws an uncatchable SecurityException, because it DEMANDS a permission. I don't want to demand, I want to INQUIRE, and call Marshal.GetHRForException() only if allowed. If the code is not permitted, then I won't call that method. SecurityPermission.IsUnrestricted() is exactly the thing. No try..catch required.
Cheeso
A: 

Does System.Runtime.InteropServices.Marshal.GetHRForException() get you anything you can't get by checking the type of exception? It looks like it uses a static mapping.

John Saunders
It gets me the HResult, which is unavailable publicly from the Exception.
Cheeso
_which_ HResult? The one from inside the exception? Isn't that a fixed mapping? Doesn't it map one to one with the exception type?
John Saunders
A quick look in Reflector suggests that the HResult is set by Exception.SetErrorCode, and is always set in a constructor, and always set to a constant value. That suggests that ex.GetType() determines the HResult, and so could be used instead of it.
John Saunders
+1  A: 

The method required is SecurityPermission.IsUnrestricted(). It returns a true or false indicating whether the permission is allowed or not. It does not demand a permission, as does SecurityPermission.Demand(). I use IsUnresticted with SecurityPermissionFlag.UnmanagedCode to see if the assembly is allowed to call unmanaged code, and then call the unmanaged code only if allowed.

There is one additional twist. The JIT compiler, when compiling a method, checks for CodeAccessPermission LinkDemands on any method called my the method to be compiled. Marshal.GetHRForException() is marked with a LinkDemand. Hence, my method that calls Marshal.GetHRForException() will throw an uncatchable SecurityException at the time of JIT compile, when run in a restricted environment, like ASP.NET with medium trust. Therefore, we must never JIT the method that calls Marshal.GetHRForException() in that case, which means I need to break out Marshal.GetHRForException() into a separate method in my code that is called (and thus JITted) only when UnmanagedCode is unrestricted.

Here's some example code:

internal void DoTheThing()
{
    try
    {
        DoSomethingThatMayCauseAnException();
    }
    catch (System.IO.IOException ioexc1)
    {
        // Check if we can call GetHRForException, 
        // which makes unmanaged code calls.
        var p = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
        if (p.IsUnrestricted())
        {
            uint hresult = _HRForException(ioexc1);
            if (hresult == 0x80070021)  // ERROR_LOCK_VIOLATION
                TakeActionOnLockViolation();  // maybe notify the user
            else
                throw new Exception("Cannot handle", ioexc1);
        }
        else
        {
            // The permission is restricted. Therefore, we cannot call
            // GetHRForException, and cannot do the subtle handling of
            // ERROR_LOCK_VIOLATION.  Just bail.
            throw new Exception("Cannot handle", ioexc1);
        }
    }
}

// This method must remain separate, and must not be marked with a LinkDemand for
// UnmanagedCode.
//
// Marshal.GetHRForException() is needed to do special exception handling for
// the read.  But, that method requires UnmanagedCode permissions, and is marked
// with LinkDemand for UnmanagedCode.  In an ASP.NET medium trust environment,
// where UnmanagedCode is restricted, will generate a SecurityException at the
// time of JIT of the method that calls a method that is marked with LinkDemand
// for UnmanagedCode. The SecurityException, if it is restricted, will occur
// when this method is JITed.
//
// The Marshal.GetHRForException() is factored out of DoTheThing in order to
// avoid the SecurityException at JIT compile time. Because _HRForException is
// called only when the UnmanagedCode is allowed, .NET never
// JIT-compiles this method when UnmanagedCode is disallowed, and thus never
// generates the JIT-compile time exception.
//
private static uint _HRForException(System.Exception ex1)
{
    return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1));
}
Cheeso