I'm running code in a locked-down AppDomain sandbox. Exceptions thrown from within this AppDomain don't include line numbers, even though pdbs are available. The code that tries to access the stack trace is fully trusted: assembly is signed and loaded as strong assembly into the app domain. So I would've expected the stack trace to contain file names and line numbers. I cannot mark the AppDomain as fully trusted as that would defeat the purpose of the sandbox. How can I have my stack traces contain file names and line numbers?
Update I have updated the code to show how the external code is loaded using Assembly.LoadFile
. My original question used a single assembly, which appeared to me to show the same behaviour as in my 'real' application. However as the answer from @simon-mourier worked for this simplified code, it doesn't work in my 'real' application. I have updated the code to reflect this.
The following sample code (based on this example) shows the problem. There are two assemblies:
- the executing (parent) assembly which is signed and has full trust
- the child assembly which is not signed and must not be trusted.
I've posted the example application on my github.
// Executing signed assembly
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Permissions;
using System.Security.Policy;
namespace Parent
{
public class Worker : MarshalByRefObject
{
private static string childpath = Path.Combine(Path.GetDirectoryName(typeof(Worker).Assembly.Location), "Child.dll");
private static void Main()
{
var w = new Worker();
w.TestExceptionStacktrace();
var adSandbox = GetInternetSandbox();
var handle = Activator.CreateInstanceFrom(
adSandbox,
typeof(Worker).Assembly.ManifestModule.FullyQualifiedName,
typeof(Worker).FullName);
w = (Worker)handle.Unwrap();
w.TestExceptionStacktrace();
}
public void TestExceptionStacktrace()
{
TestInner();
}
private void TestInner()
{
var ass = Assembly.LoadFile(childpath);
var playMethod = ass.GetTypes()[0].GetMethod("Play");
try
{
playMethod.Invoke(null, Array.Empty<object>());
}
catch (Exception e)
{
var s = e.ToString();
Console.WriteLine("Stack trace contains {0}line numbers for Child assembly:",
s.Split(new[] { Environment.NewLine }, StringSplitOptions.None)
.Single(x => x.Contains("Play()")).Contains("line")
? ""
: "no ");
Console.WriteLine($" {s}");
}
}
// ------------ Helper method ---------------------------------------
private static AppDomain GetInternetSandbox()
{
// Create the permission set to grant to all assemblies.
var hostEvidence = new Evidence();
hostEvidence.AddHostEvidence(new Zone(
System.Security.SecurityZone.Internet));
var pset =
System.Security.SecurityManager.GetStandardSandbox(hostEvidence);
// add this to the permission set
pset.AddPermission(new FileIOPermission(
FileIOPermissionAccess.PathDiscovery, typeof(Worker).Assembly.Location)
);
pset.AddPermission(new FileIOPermission(
FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, Path.GetDirectoryName(childpath))
);
// Identify the folder to use for the sandbox.
var ads = new AppDomainSetup();
ads.ApplicationBase = System.IO.Directory.GetCurrentDirectory();
var fullTrustAssemblies = new[]
{
typeof(Worker).Assembly.Evidence.GetHostEvidence<StrongName>(),
};
// Create the sandboxed application domain.
return AppDomain.CreateDomain("Sandbox", hostEvidence, ads, pset, fullTrustAssemblies);
}
}
}
// Child assembly (not signed)
using System;
namespace Child
{
public class Child
{
public static void Play()
{
var ad = AppDomain.CurrentDomain;
Console.WriteLine("\r\nApplication domain '{0}': IsFullyTrusted = {1}",
ad.FriendlyName, ad.IsFullyTrusted);
Console.WriteLine(" IsFullyTrusted = {0} for the current assembly {1}",
typeof(Child).Assembly.IsFullyTrusted,
typeof(Child).Assembly);
Console.WriteLine(" IsFullyTrusted = {0} for mscorlib",
typeof(int).Assembly.IsFullyTrusted);
throw new Exception("Some exception");
}
}
}
The output of this code is:
Application domain 'Parent.exe': IsFullyTrusted = True
IsFullyTrusted = True for the current assembly Child, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
IsFullyTrusted = True for mscorlib
Stack trace contains line numbers for Child assembly:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Exception: Some exception
at Child.Child.Play() in C:\Users\Bouke\Developer\SandboxStacktrace\Child\Child.cs:line 20
(...)
Application domain 'Sandbox': IsFullyTrusted = False
IsFullyTrusted = False for the current assembly Child, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
IsFullyTrusted = True for mscorlib
Stack trace contains no line numbers for Child assembly:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Exception: Some exception
at Child.Child.Play()
(...)
I've so far discovered that using Assembly.Load(byte[], byte[], SecurityContextSource.CurrentAssembly)
works, but defeats the sandbox (loading assembly has full trust). Assembly.Load(byte[], byte[], SecurityContextSource.CurrentAppDomain)
doesn't work.