I asked a while ago how to restrict plugins access ( I want to prevent them from writing to the disk or network ) and i was told to use AppDomain. I have searched and tried and failed on how to get this working.
Can anyone provide some information so i can get started, simply put make a AppDomain that does not allows writing to the file or network.
For .net framework 4.0, please follow the following code from this MSDN article.
The following example implements the procedure in the previous section. In the example, a project named Sandboxer in a Visual Studio solution also contains a project named UntrustedCode, which implements the class UntrustedClass. This scenario assumes that you have downloaded a library assembly containing a method that is expected to return true or false to indicate whether the number you provided is a Fibonacci number. Instead, the method attempts to read a file from your computer. The following example shows the untrusted code.
using System;
using System.IO;
namespace UntrustedCode
{
public class UntrustedClass
{
// Pretend to be a method checking if a number is a Fibonacci
// but which actually attempts to read a file.
public static bool IsFibonacci(int number)
{
File.ReadAllText("C:\\Temp\\file.txt");
return false;
}
}
}
The following example shows the Sandboxer application code that executes the untrusted code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security;
using System.Security.Policy;
using System.Security.Permissions;
using System.Reflection;
using System.Runtime.Remoting;
//The Sandboxer class needs to derive from MarshalByRefObject so that we can create it in another
// AppDomain and refer to it from the default AppDomain.
class Sandboxer : MarshalByRefObject
{
const string pathToUntrusted = #"..\..\..\UntrustedCode\bin\Debug";
const string untrustedAssembly = "UntrustedCode";
const string untrustedClass = "UntrustedCode.UntrustedClass";
const string entryPoint = "IsFibonacci";
private static Object[] parameters = { 45 };
static void Main()
{
//Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder
//other than the one in which the sandboxer resides.
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
//Setting the permissions for the AppDomain. We give the permission to execute and to
//read/discover the location where the untrusted code is loaded.
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
//We want the sandboxer assembly's strong name, so that we can add it to the full trust list.
StrongName fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<StrongName>();
//Now we have everything we need to create the AppDomain, so let's create it.
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
//Use CreateInstanceFrom to load an instance of the Sandboxer class into the
//new AppDomain.
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
//Unwrap the new domain instance into a reference in this domain and use it to execute the
//untrusted code.
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
newDomainInstance.ExecuteUntrustedCode(untrustedAssembly, untrustedClass, entryPoint, parameters);
}
public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
{
//Load the MethodInfo for a method in the new Assembly. This might be a method you know, or
//you can use Assembly.EntryPoint to get to the main function in an executable.
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
//Now invoke the method.
bool retVal = (bool)target.Invoke(null, parameters);
}
catch (Exception ex)
{
// When we print informations from a SecurityException extra information can be printed if we are
//calling it with a full-trust stack.
(new PermissionSet(PermissionState.Unrestricted)).Assert();
Console.WriteLine("SecurityException caught:\n{0}", ex.ToString());
CodeAccessPermission.RevertAssert();
Console.ReadLine();
}
}
}
I guess this is what you need, if I understand correctly your point.
System.Security.PermissionSet ps =
new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None);
ps.AddPermission(new System.Security.Permissions.FileIOPermission(System.Security.Permissions.FileIOPermissionAccess.NoAccess, "C:\\"));
System.Security.Policy.PolicyLevel pl = System.Security.Policy.PolicyLevel.CreateAppDomainLevel();
pl.RootCodeGroup.PolicyStatement = new System.Security.Policy.PolicyStatement(ps);
AppDomain.CurrentDomain.SetAppDomainPolicy(pl);
System.Reflection.Assembly myPluginAssembly = AppDomain.CurrentDomain.Load("MyPluginAssembly");
Is this more precisely what you meant?
Notice that you may provide an array of string containg the paths where you don't want the plugin to have access. You may provide if when initializing the new instance of FileIOPermission class.
Let me know if this helps. :-)
If you're using plugins, you might perhaps know about proxies.
While loading your assembly through a proxy, you can specify the security policy level for this particular assembly through the LoadAssembly() method or so, if I remember correctly. In other words, this is done through reflection.
I know my answer isn't that much detailed, but I hope it will give you an idea of where to look for your solution. I shall take an eye out to find further details on the subject so that I may be of better help. =)
Hope you will share your findings when you've done it.
Related
Suppose my code possesses the knowledge about the metadata of a
nonexistent class library "mytest.dll", such as the types in this library, the functions of the types, the parameters and return types of the functions, etc.
How does my code manufacture this DLL using techniques such as reflection?
I know my code can generate the "mytest.cs" text file, then execute the compiler to produce the DLL, then delete the "mytest.cs" file. Just want to know if there are "more advanced" or "cooler" ways to do it.
Thanks.
There are 4 main steps in the process to compile and execute dynamic .net scripts from your application, even really complex scenarios can be simplified in this way:
Generate the code
Compile the script
Load the assembly
Execute the code
Lets generate a simple Hello Generated C# World App right now!:
Create a method that will generate an assembly that has 1 class called HelloWorldApp, this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it.
This solution requires the following package to be installed:
PM> Install-Package 'Microsoft.CodeAnalysis.CSharp.Scripting'
And will require the following using statements:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
Orchestration
The following method encapsulates the above steps:
private static void GenerateAndExecuteApp(int numberOfParameters)
{
string nameSpace = "Dynamic.Example";
string className = "HelloWorldApp";
string methodName = "GenerateMessage";
// 1. Generate the code
string script = BuildScript(nameSpace, className, methodName, numberOfParameters);
// 2. Compile the script
// 3. Load the Assembly
Assembly dynamicAssembly = CompileScript(script);
// 4. Execute the code
int[] arguments = Enumerable.Range(1, numberOfParameters).ToArray();
string message = ExecuteScript(dynamicAssembly, nameSpace, className, methodName, arguments);
Console.Out.WriteLine(message);
}
Generate the code
You say you already have item 1 sorted out, you can use StringBuilder, T4 templates or other mechanisms to generate the code files.
generating the code itself is its own question if you need help with that.
However, for our demo app, the following would work:
private static string BuildScript(string nameSpace, string className, string methodName, int numberOfParameters)
{
StringBuilder code = new StringBuilder();
code.AppendLine("using System;");
code.AppendLine("using System.Linq;");
code.AppendLine();
code.AppendLine($"namespace {nameSpace}");
code.AppendLine("{");
code.AppendLine($" public class {className}");
code.AppendLine(" {");
var parameterNames = Enumerable.Range(0, numberOfParameters).Select(x => $"p{x}").ToList();
code.Append($" public string {methodName}(");
code.Append(String.Join(",", parameterNames.Select(x => $"int {x}")));
code.AppendLine(")");
code.AppendLine(" {");
code.Append(" return $\"");
code.Append(String.Join(",", parameterNames.Select(x => $"{x}={{{x}}}")));
code.AppendLine("\";");
code.AppendLine(" }");
code.AppendLine(" }");
code.AppendLine("}");
return code.ToString();
}
For an input value of 3, the following code is generated:
using System;
using System.Linq;
namespace Dynamic.Example
{
public class HelloWorldApp
{
public string GenerateMessage(int p0,int p1,int p2)
{
return $"p0={p0},p1={p1},p2={p2}";
}
}
}
Compile the script (and Load it)
These are two discrete steps, however it is easiest to code them together in the same method, for this example we will ignore the generated dll and load the assembly directly into memory, that is generally the more likely use case for this type of scripting scenario anyway.
The hardest element of this is usually the referencing of the relevant dlls. There are a number of ways to achieve this, including loading all the dlls that are in the current executing context, I find a simple way to do this is to access the Assembly reference from the Type reference for the types we want to use inside the dynamic script:
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
Cut a long story short, this method compiles and loads the assembly into memory. It includes some crude compilation error handling, just to demonstrate how to do it:
private static Assembly CompileScript(string script)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);
// use "mytest.dll" if you want, random works well enough
string assemblyName = System.IO.Path.GetRandomFileName();
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
MetadataReference[] references = dlls.Distinct().Select(x => MetadataReference.CreateFromFile(x)).ToArray();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Now we actually compile the script, this includes some very crude error handling, just to show you can
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
List<string> errors = new List<string>();
foreach (Diagnostic diagnostic in failures)
{
//errors.AddDistinct(String.Format("{0} : {1}", diagnostic.Id, diagnostic.Location, diagnostic.GetMessage()));
errors.Add(diagnostic.ToString());
}
throw new ApplicationException("Compilation Errors: " + String.Join(Environment.NewLine, errors));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}
Execute the code
Finally, we can use reflection to instantiate an instance of the new app and then we can obtain a reference to the method and it. The name of the parameters is irrelevant, as long
we pass them through in the correct order:
for this demo the order is sort of irrelevant to, given they are all the same type ;)
private static string ExecuteScript(Assembly assembly, string nameSpace, string className, string methodName, int[] arguments)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
MethodInfo method = appType.GetMethod(methodName);
object result = method.Invoke(app, arguments.Cast<object>().ToArray());
return result as string;
}
Output
The final output from all this for our method with 3 passed into it is:
p0=1,p1=2,p2=3
So that was super crude, you can bypass most of the indirect reflection aspects through the use of Interfaces. If your generated script inherits from types or interfaces that the calling code also has a strong reference to, then ExecuteScript in the above example might look like this:
private static string ExecuteScript(Assembly assembly, string nameSpace, string className)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
if (app is KnownInterface known)
{
return known.GenerateMessage(1,2,3);
}
throw new NotSupportedException("Couldn't resolve known type");
}
The major benefit to using an interface or base class reference is that you can natively set properties or call other methods without having to reflect references to them all or to resort to using dynamic which would work, but becomes a bit harder to debug.
Of course the interface solution is hard to implement when we had a variable number of parameters, so that's not the best example, usually with dynamic scripts you would construct a known environment, say a known class and methods, but you might want to inject custom code into the body of the method.
It's a bit of fun in the end, but this simple example shows that C# can be used as a runtime scripting engine without too much trouble.
I'm calling LogonUser with LOGON_TYPE_NEW_CREDENTIALS and LOGON32_PROVIDER_WINNT50 to get my thread to impersonate a user in the other domain. I'm able to connect to remote file shares and everything else just fine into the untrusted domain.
The problem I'm running into now is when I use GPMGMTLib to generate a GPO report I keep getting exception "HRESULT: 0x80072020" when it calls GenerateReport().
using GPMGMTLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace CrossDomainWork
{
class Program
{
static void Main(string[] args)
{
ImpersonationContext context = new ImpersonationContext("ourdmzdomain.com", "dmzuser", "dmzpassword");
context.Start();
GPM gpm = new GPM();
var constants = gpm.GetConstants();
var domain = gpm.GetDomain("ourdmzdomain.com", "", constants.UseAnyDC);
var gpo = domain.GetGPO("{31B2F340-016D-11D2-945F-00C04FB984F9}");
object missing = Type.Missing;
var result = gpo.GenerateReport(GPMReportType.repHTML, ref missing, out missing).Result;
context.Stop();
}
}
}
I have no experience here, so this is just a guess.
Looking at the documentation for GenerateReport, the last two parameters are pvarGPMProgress (for reporting progress), and pvarGPMCancel (some kind of cancellation token).
You are passing the same object for both. I wonder if that's what's making it choke. You can try creating a second object.
Maybe it's also possible that it doesn't like getting Type.Missing as the value. You can try just setting them to null.
Also, does the group policy have any special permissions on it?
What namespace is that ImpersonationContext in that you're using? I can't find it. We do have an untrusted domain at work that I can test with, if I can get your code to compile.
Edit:
If you have SetLastError = true in your DllImport statements, then you can use Marshal.GetLastWin32Error() to get some additional details. For example:
try {
result = gpo.GenerateReport(GPMReportType.repHTML, ref missing, out missing).Result;
} catch {
var win32 = new Win32Exception(Marshal.GetLastWin32Error());
Console.Write(win32.Message);
}
For me, it tells me
An attempt was made to reference a token that does not exist
Which doesn't solve the puzzle, but it's another piece to the puzzle.
My application should be scriptable by the users in C#, but the user's script should run in a restricted AppDomain to prevent scripts accidentally causing damage, but I can't really get it to work, and since my understanding of AppDomains is sadly limited, I can't really tell why.
The solution I am currently trying is based on this answer https://stackoverflow.com/a/5998886/276070.
This is a model of my situation (everything except Script.cs residing in a strongly named assembly). Please excuse the wall of code, I could not condense the problem any further.
class Program
{
static void Main(string[] args)
{
// Compile the script
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters()
{
GenerateExecutable = false,
OutputAssembly = System.IO.Path.GetTempFileName() + ".dll",
};
parameters.ReferencedAssemblies.Add(Assembly.GetEntryAssembly().Location);
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, "Script.cs");
// ... here error checks happen ....//
var sandbox = Sandbox.Create();
var script = (IExecutable)sandbox.CreateInstance(results.PathToAssembly, "Script");
if(script != null)
script.Execute();
}
}
public interface IExecutable
{
void Execute();
}
The Sandbox class:
public class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public static Sandbox Create()
{
var setup = new AppDomainSetup()
{
ApplicationBase = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, BaseDirectory),
ApplicationName = DomainName,
DisallowBindingRedirects = true,
DisallowCodeDownload = true,
DisallowPublisherPolicy = true
};
var permissions = new PermissionSet(PermissionState.None);
permissions.AddPermission(new ReflectionPermission(ReflectionPermissionFlag.RestrictedMemberAccess));
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var domain = AppDomain.CreateDomain(DomainName, null, setup, permissions,
typeof(Sandbox).Assembly.Evidence.GetHostEvidence<StrongName>());
return (Sandbox)Activator.CreateInstanceFrom(domain, typeof(Sandbox).Assembly.ManifestModule.FullyQualifiedName, typeof(Sandbox).FullName).Unwrap();
}
public object CreateInstance(string assemblyPath, string typeName)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(typeName); // ****** I get null here
if (type == null)
return null;
return Activator.CreateInstance(type);
}
}
The loaded Script:
using System;
public class Script : IExecutable
{
public void Execute()
{
Console.WriteLine("Boo");
}
}
In CreateInstance of SandBox, I always get null at the marked line. I tried various forms of giving the name, including reading the type name (or fuly qualified name) from results.CompiledAssembly using reflection.
What am I doing wrong here?
The first thing that i'll check is if there are compilation errors (i had several headache caused by this issues)
The second idea is about the resolution of assemblies. I always add as a security check an event handler for AppDomain.CurrentDomain.AssemblyResolve, where i seek on my known path for the missing Assemblies. When the not found assembly is the one i just compiled i add a static reference to it and return it.
What I usually do is this:
Create the new Assembly on file system with the compiler
Load its content with the File.ReadAllBytes
Load the dll with the Assembly.Load in the AppDomain in which i will be using the object
Add the AppDomain.CurrentDomain.AssemblyResolve event
Just in case (since i use this a lot) i created a small library to accomply this kind of things
The code and documentation are here: Kendar Expression Builder
While the nuget package is here: Nuget Sharp Template
I have written a small piece of code regarding the dynamic loading of assemblies and creating class instances from those assemblies, including an executable, a test lib to be dynamically loaded and a loader library to load dynamic assembly into a new Appdomain. Loader library is referenced by both executable and the dynamic library.
//executable
[System.STAThreadAttribute()]
[System.LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args)
{
AppDomainSetup domainSetup = new AppDomainSetup()
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
LoaderOptimization = LoaderOptimization.MultiDomain
};
AppDomain childDomain = AppDomain.CreateDomain("MyDomain", null, domainSetup);
Console.WriteLine(AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString());
Console.WriteLine(childDomain.SetupInformation.LoaderOptimization.ToString());
byte[] assembly = null;
string assemblyName = "CSTestLib";
using (FileStream fs = new FileStream(assemblyName+".dll",FileMode.Open))
{
byte[] byt = new byte[fs.Length];
fs.Read(byt,0,(int)fs.Length);
assembly = byt;
}
object[] pararmeters = {assemblyName,assembly};
string LoaderAssemblyName = typeof(AssemblyLoader).Assembly.FullName;
string LoaderClassName = typeof(AssemblyLoader).FullName;
AssemblyLoader assloader = (AssemblyLoader)childDomain.CreateInstanceAndUnwrap(LoaderAssemblyName,LoaderClassName , true, BindingFlags.CreateInstance, null, parameters, null, null);
object obj = assloader.Load("CSTestLib.Class1");
object obj2 = assloader.Load("CSTestLib.Class2");
AppDomain.Unload(childDomain);
Console.ReadKey();
}
//Dynamic Lib
using System;
namespace CSTestLib
{
public class Class1 :MarshalByRefObject
{
public Class1() { }
}
public class Class2 : MarshalByRefObject
{
public Class2() { }
}
}
//Loader Library
using System;
namespace LoaderLibrary
{
public class AssemblyLoader : MarshalByRefObject
{
string assemblyName;
public AssemblyLoader(string assName, byte[] ass)
{
assemblyName = assName;
AppDomain.CurrentDomain.Load(ass);
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + " " + AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString());
}
public object Load(string className)
{
object ret = null;
try
{
ret = AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, className);
}
catch (System.Exception ex)
{
Console.WriteLine(ex.Message);
}
return ret;
}
}
}
Here I set LoaderOptimizationAttribute on main() method but AppDomain.CurrentDomain.SetupInformation.LoaderOptimization.ToString(); says it is NotSpecified Why?
The differences between MultiDomain and MultiDomainHost is not so clear to me. Is MultiDomainHost for only GAC assemblies? For my situation which is more suitable?
According to this
JIT-compiled code cannot be shared for
assemblies loaded into the load-from
context, using the LoadFrom method of
the Assembly class, or loaded from
images using overloads of the Load
method that specify byte arrays.
So how can I detect if an assembly is loaded domain-neutral or not? How can assure I it is loaded domain-neutral?
This attribute has only an effect if you precompile your assemblies with NGen to speed up a warm start of your application. When you specify MultiDomain or MultiDomainHost you enable the usage of precompiled (ngenned) assemblies. You can verify this with Process Explorer where you can look at the list of loaded modules.
This is one of the biggest startup time savers if your application consists of several executable instances which share assemblies. This enables .NET to share the code pages between processes which in turn saves real memory (one assembly exists only once in the physical memory but it is shared between one or more processes) and prevents JITing the same code over and over again in each process which takes time at the cost that the generated code is a little less efficient as it could be when it would be compiled with the regular JIT which can use more dynamic data to generate the most efficient code.
In your example you load the assembly into a byte array which is located in the managed heap and increases your private byte count. This makes it impossible to share data between processes. Only read only pages which have a counterpart on your hard disc can be shared between processes. This is the reason why the attribute has no effect. If you are after a factor 2 of warm startup performance this is the attribute you were seeking for. For anything else it is not relevant.
Now back to your original question:
It is set but when you start your application under a debugger this MultiDomain attribute is ignored. When you start it outside of a debugger you will get the expected results.
Yes MultiDomainHost does enable AppDomain neutrality only for signed assemblies all others are not shared.
Code sharing can only happen when it is precompiled. The real question is: How to check if the assembly is precompiled? I do it with Process Explorer by looking at the list of loaded modules. When my loaded assembly shows up with a path to the Native Image cache and an .ni extension I am sure the precompiled image is beeing used. You can check this also with fuslogvw when you set the radio button to Native Images to check why a native images was not used by the runtime.
I'm trying to create a sandboxed AppDomain for loading extensions/plugins. I have a MarshalByRefObject that in instantiate inside the appdomain to load the dll. I'm getting SecurityExceptions when trying to load the dll and I can't figure out how to get around them while still limiting what the third party code can do. All my projects are .net 4.
The InDomainLoader class is in a fully trusted domain, the method is marked SecuritySafeCritical. From everything I've read, I think this should work.
Here is my Loader class that creates the AppDomain and jumps into it:
public class Loader
{
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
// Create new AppDomain
var setup = AppDomain.CurrentDomain.SetupInformation;
var permissions = new PermissionSet(null);
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var strongname = typeof(InDomainLoader).Assembly.Evidence.GetHostEvidence<StrongName>();
var strongname2 = typeof(IPlugin).Assembly.Evidence.GetHostEvidence<StrongName>();
AppDomain domain = AppDomain.CreateDomain("plugin", null, setup, permissions, strongname, strongname2);
// Create instance
var loader = (InDomainLoader)domain.CreateInstanceAndUnwrap(
typeof (InDomainLoader).Assembly.FullName, typeof (InDomainLoader).FullName);
// Jump into domain
loader.Load(dll, typeName);
}
}
And here's the bootstrap loader that runs in the domain:
public class InDomainLoader : MarshalByRefObject
{
[SecuritySafeCritical]
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
var assembly = Assembly.LoadFrom(dll); // <!-- SecurityException!
var pluginType = assembly.GetType(typeName);
var demoRepository = new DemoRepository();
var plugin = (IPlugin)Activator.CreateInstance(pluginType, demoRepository);
Console.WriteLine(plugin.Run());
}
}
Some logging statements tell me that the assembly's IsFullyTrusted is true and the method has both IsSecurityCritical and IsSecuritySafeCritical set to true, IsSecurityTransparent is false.
I zipped up the whole project to http://davidhogue.com/files/PluginLoader.zip in case that makes this easier.
If anyone has any ideas, I'd be very grateful. I seem to be stuck at a dead end here.
Well for a start you probably shouldn't be marking the function as SecuritySafeCritical as that implies untrusted callers can call you, which you probably don't really want (not that it should be a major issue).
As for your problem the issue is that by default you still don't run with any special permissions, the normal easy way to do the assembly loading is you create your own AppDomainSetup and point it's ApplicationBase at a Plugin directory of some kind (which isn't a bad idea in general), you can then use the normal Assembly.Load("AssemblyName") to load out of the base. However if you must load an arbitrary file then you need to assert FileIOPermission for the plugin dll (full path), i.e.
private Assembly LoadAssemblyFromFile(string file)
{
FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.AllAccess, file);
perm.Assert();
return Assembly.LoadFile(file);
}