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:
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.
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));
}