diff --git a/src/AsyncBridge/Properties/SpecificAssemblyInfo.cs b/src/AsyncBridge/Properties/SpecificAssemblyInfo.cs new file mode 100644 index 0000000..fa00984 --- /dev/null +++ b/src/AsyncBridge/Properties/SpecificAssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Security; + +#if !NET35 +[assembly: AllowPartiallyTrustedCallers] +#if !PORTABLE +[assembly: SecurityRules(SecurityRuleSet.Level2)] +#endif +#endif \ No newline at end of file diff --git a/src/AsyncBridge/Runtime.CompilerServices/AsyncVoidMethodBuilder.cs b/src/AsyncBridge/Runtime.CompilerServices/AsyncVoidMethodBuilder.cs index 9b7ddca..ffef163 100644 --- a/src/AsyncBridge/Runtime.CompilerServices/AsyncVoidMethodBuilder.cs +++ b/src/AsyncBridge/Runtime.CompilerServices/AsyncVoidMethodBuilder.cs @@ -79,6 +79,7 @@ private AsyncVoidMethodBuilder(SynchronizationContext synchronizationContext) /// /// Registers with UnobservedTaskException to suppress exception crashing. /// + [SecuritySafeCritical] internal static void PreventUnobservedTaskExceptions() { if (Interlocked.CompareExchange(ref s_preventUnobservedTaskExceptionsInvoked, 1, 0) != 0) diff --git a/src/AsyncBridge/Runtime.CompilerServices/TaskAwaiter.cs b/src/AsyncBridge/Runtime.CompilerServices/TaskAwaiter.cs index da73d85..46e3f3c 100644 --- a/src/AsyncBridge/Runtime.CompilerServices/TaskAwaiter.cs +++ b/src/AsyncBridge/Runtime.CompilerServices/TaskAwaiter.cs @@ -254,17 +254,27 @@ private static void RunNoException(Action continuation) /// Copies the exception's stack trace so its stack trace isn't overwritten. /// /// The exception to prepare. + [SecuritySafeCritical] internal static Exception PrepareExceptionForRethrow(Exception exc) { if (s_prepForRemoting != null) { try { - s_prepForRemoting.Invoke(exc, s_emptyParams); +#if !PORTABLE + new PermissionSet(Security.Permissions.PermissionState.Unrestricted).Assert(); +#endif + return (Exception)s_prepForRemoting.Invoke(exc, s_emptyParams); } catch { } +#if !PORTABLE + finally + { + CodeAccessPermission.RevertAssert(); + } +#endif } return exc; } diff --git a/tests/AsyncBridge.Tests/PartialTrustTests.cs b/tests/AsyncBridge.Tests/PartialTrustTests.cs new file mode 100644 index 0000000..3c31614 --- /dev/null +++ b/tests/AsyncBridge.Tests/PartialTrustTests.cs @@ -0,0 +1,273 @@ +using System; +using System.Reflection; +using System.Runtime.Serialization; +using System.Security; +using System.Security.Permissions; +using System.Security.Policy; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; +#if NET45 +using TaskEx = System.Threading.Tasks.Task; +#endif + +#if NET45 +namespace ReferenceAsync.Tests +#elif NET35 +namespace AsyncBridge.Net35.Tests +#elif ATP +namespace AsyncTargetingPack.Tests +#else +namespace AsyncBridge.Tests +#endif +{ +#if !ATP + public sealed class Sandbox : IDisposable + { + private readonly AppDomain _Sandbox; + + public Sandbox() + { + var CurrentSetup = AppDomain.CurrentDomain.SetupInformation; + var MySetup = new AppDomainSetup() + { + ApplicationBase = CurrentSetup.ApplicationBase + }; + + var MyPermissions = new PermissionSet(PermissionState.None); + MyPermissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution)); + // Ensure we can read our own assemblies and files + MyPermissions.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read, CurrentSetup.ApplicationBase)); + + _Sandbox = AppDomain.CreateDomain( + "Partial Trust Tests", + AppDomain.CurrentDomain.Evidence, + MySetup, + MyPermissions, +#if NET45 + new StrongName[0] +#else + // AsyncBridge and AsyncTargetingPack need full trust to work. The test classes remain untrusted + new StrongName[] { CreateStrongName(typeof(TaskEx).Assembly.GetName()) } +#endif + ); + + _Sandbox.UnhandledException += OnUnhandledException; + } + + public TRemote Create() where TRemote : MarshalByRefObject + { + // This does not demand ReflectionPermission, so we don't need to give the test assembly full trust + return (TRemote)Activator.CreateInstanceFrom(_Sandbox, typeof(TRemote).Assembly.ManifestModule.FullyQualifiedName, typeof(TRemote).FullName).Unwrap(); + } + + public void Dispose() + { + AppDomain.Unload(_Sandbox); + } + + public static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) + { + Write.Line(string.Format("Unhandled Remote Exception {0}", e.ExceptionObject.ToString())); + throw new ApplicationException("Unhandled Remote Exception", (Exception)e.ExceptionObject); + } + + private static StrongName CreateStrongName(AssemblyName assemblyName) + { //**************************************** + var MyPublicKey = assemblyName.GetPublicKey(); + //**************************************** + + if (MyPublicKey == null || MyPublicKey.Length == 0) + throw new InvalidOperationException(string.Format("Assembly Name for {0} must specify the full Public Key", assemblyName.Name)); + + return new StrongName(new StrongNamePublicKeyBlob(MyPublicKey), assemblyName.Name, assemblyName.Version); + } + } + + [TestClass] + public class PartialTrustTests + { + public PartialTrustTests() + { + // Forces .Net to load the culture-specific assembly for exception messages BEFORE one gets raised from partial trust. + // If we don't do this, it will try to do so while in a partially trusted context, + // and get into an chain of failed AssemblyResolve calls, ending in a failed Assert for "mscorlib recursive resource lookup bug". + new ArgumentException(); + } + + [TestMethod] + public void CreateInSandbox() + { + using (var Sandbox = new Sandbox()) + { + var RemoteObject = Sandbox.Create(); + Assert.AreEqual("Hello", RemoteObject.SayHello()); + } + } + + public sealed class CreateInSandboxClass : MarshalByRefObject + { + public string SayHello() + { + return "Hello"; + } + } + + [TestMethod] + public void ExecuteTaskInSandbox() + { + using (var Sandbox = new Sandbox()) + { + var RemoteObject = Sandbox.Create(); + + RemoteObject.Execute(); + } + } + + public sealed class ExecuteTaskInSandboxClass : MarshalByRefObject + { + public void Execute() + { + TestUtils.RunAsync(async () => + { + await TaskEx.Yield(); + }); + } + } + + [TestMethod] + public void ThrowTaskInSandbox() + { + using (var Sandbox = new Sandbox()) + { + var RemoteObject = Sandbox.Create(); + + try + { + RemoteObject.Execute(); + + Assert.Fail(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual("Exception from Task", e.Message); + } + } + } + + public sealed class ThrowTaskClass : MarshalByRefObject + { + public void Execute() + { + TestUtils.RunAsync(async () => + { + await TaskEx.Yield(); + + throw new InvalidOperationException("Exception from Task"); + }); + } + } + + [TestMethod] + public void ThrowNestedTask() + { + var RemoteObject = new ThrowNestedTaskClass(); + + try + { + RemoteObject.Execute(); + + Assert.Fail(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual("Exception from Task", e.Message); + } + } + + [TestMethod] + public void ThrowNestedTaskInSandbox() + { + using (var Sandbox = new Sandbox()) + { + var RemoteObject = Sandbox.Create(); + + try + { + RemoteObject.Execute(); + + Assert.Fail(); + } + catch (InvalidOperationException e) + { + Assert.AreEqual("Exception from Task", e.Message); + } + } + } + + public sealed class ThrowNestedTaskClass : MarshalByRefObject + { + public void Execute() + { + TestUtils.RunAsync(async () => + { + await TaskEx.Run(async () => + { + await TaskEx.Yield(); + + throw new InvalidOperationException("Exception from Task"); + }); + }); + } + } + + [TestMethod] + public void ThrowCustomExceptionInSandbox() + { + using (var Sandbox = new Sandbox()) + { + var RemoteObject = Sandbox.Create(); + + try + { + RemoteObject.Execute(); + + Assert.Fail(); + } + catch (CustomException e) + { + Assert.AreEqual("Exception from Task", e.Message); + } + } + } + + public sealed class ThrowCustomExceptionInSandboxClass : MarshalByRefObject + { + public void Execute() + { + TestUtils.RunAsync(async () => + { + await TaskEx.Run(async () => + { + await TaskEx.Yield(); + + throw new CustomException("Exception from Task"); + }); + }); + } + } + + [Serializable] + public sealed class CustomException : Exception + { + public CustomException(string message) : base(message) + { + } + + public CustomException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } + } +#endif +}