AppDomain in Azure Function - c#

I have tried to create an AppDomain within Azure Functions to run untrusted code. Creating the domain seems to work fine, but when I try to load in assemblies, it seems like they get loaded in incorrectly.
First I tried a simple AppDomain:
public class Sandboxer
{
public void Run()
{
AppDomain newDomain = AppDomain.CreateDomain("name");
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
I got an error
"System.IO.FileNotFoundException : Could not load file or assembly 'Sandboxer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2cd9cb1d6fdb50b4' or one of its dependencies. The system cannot find the file specified."
I then tried to set the appliactionBase to the folder with my dll in it.
public class Sandboxer
{
public void Run()
{
var location = typeof(OtherProgram).Assembly.Location;
AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationBase = Path.GetDirectoryName(location);
AppDomain newDomain = AppDomain.CreateDomain("name", null, ads);
var obj = newDomain.CreateInstance(typeof(OtherProgram).Assembly.FullName, typeof(OtherProgram).FullName).Unwrap();
var other = obj as OtherProgram;
var other2 = obj as MarshalByRefObject;
}
}
public class OtherProgram : MarshalByRefObject
{
public void Main(string[] args)
{
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName);
foreach (var item in args)
Console.WriteLine(item);
}
}
In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
How can I fix this problem? Thanks!
Cross posted here: https://social.msdn.microsoft.com/Forums/azure/en-US/59b119d8-1e51-4460-bf86-01b96ed55b12/how-can-i-create-an-appdomain-in-azure-functions?forum=AzureFunctions&prof=required

In this case, "other" is null at the end of the Run() method, but "other2" is a __TransparentProxy. It seems like it is finding and loading the dll, but doesn't understand the type.
According to your description, I could encounter the similar issue, I tried to create a Console application to check this issue and found that the code could work as expected under a Console application.
For Azure Function, obj as OtherProgram always returns null. Then I tried to instantiate OtherProgram under the current domain as follows:
var obj=AppDomain.CurrentDomain.CreateInstanceFromAndUnwrap(typeof(OtherProgram).Assembly.Location, typeof(OtherProgram).FullName);
OtherProgram op = obj as OtherProgram;
if (op != null)
op.PrintDomain(log);
The above code could work as expected, but I did not found why the object under a new AppDomain always returns null. You may try to add a issue under Azure/Azure-Functions.

This is how I would do it in a conventional .NET application, should work in Azure Functions:
Register to the AppDomain.AssemblyResolve event on the newly created AppDomain
In the event handler, resolve the assembly path using the Function Directory / Function App Directory in order to point to the bin folder

AppDomains are not usable with Azure Functions. In order to properly sandbox code in Azure Functions, you would have to create a new Azure Functions App and run the code there.
If you are allowing users to write scripts, you can use another language like Lua that allows easy sandboxing.

Related

Reflection only assembly loading in .Net Core

I have a .Net Framework WPF application that I'm currently migrating to .Net6. At startup it examines certain assemblies in the executable folder looking for any with a custom assembly attribute. Those that have this are then loaded into the current appdomain. (Note that some of these assemblies may already be in the appdomain, as they are projects in the running application's solution).
This is the 4.x code:
private void LoadAssemblies(string folder)
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve +=
(s, e) => Assembly.ReflectionOnlyLoad(e.Name);
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
foreach (var assemblyFile in assemblyFiles)
{
var reflectionOnlyAssembly = Assembly.ReflectionOnlyLoadFrom(assemblyFile);
if (ContainsCustomAttr(reflectionOnlyAssembly))
{
var assembly = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assembly);
}
}
}
The custom assembly attribute (that this code is looking for) has a string property containing a path to a XAML resource file within that assembly. The ProcessAssembly() method adds this resource file to the application's merged dictionary, something like this:
var resourceUri = string.Format(
"pack://application:,,,/{0};component/{1}",
assembly.GetName().Name,
mimicAssemblyAttribute.DataTemplatePath);
var uri = new Uri(resourceUri, UriKind.RelativeOrAbsolute);
application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = uri });
Just to reiterate, all this works as it should in the .Net 4.x application.
.Net6 on the other hand doesn't support reflection-only loading, nor can you create a second app domain in which to load the assemblies. I rewrote the above code by loading the assemblies being examined into what I understand is a temporary, unloadable context:
private void LoadAssemblies(string folder)
{
var assemblyFiles = Directory.GetFiles(folder, "*.Client.dll");
using (var ctx = new TempAssemblyLoadContext(AppDomain.CurrentDomain.BaseDirectory))
{
foreach (var assemblyFile in assemblyFiles)
{
var assm = ctx.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
ProcessAssembly(assm2);
}
}
}
}
private class TempAssemblyLoadContext : AssemblyLoadContext, IDisposable
{
private AssemblyDependencyResolver _resolver;
public TempAssemblyLoadContext(string readerLocation)
: base(isCollectible: true)
{
_resolver = new AssemblyDependencyResolver(readerLocation);
}
public void Dispose()
{
Unload();
}
protected override Assembly Load(AssemblyName assemblyName)
{
var path = _resolver.ResolveAssemblyToPath(assemblyName);
if (path != null)
{
return LoadFromAssemblyPath(path);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (path != null)
{
return LoadUnmanagedDllFromPath(path);
}
return IntPtr.Zero;
}
}
(Note the ProcessAssembly() method is unchanged).
This code "works" in so much as it goes through the motions without crashing. However at a later point when the application starts creating the views, I get the following exception:
The component '..ModeSelectorView' does not have a resource identified by the URI '/.;component/views/modeselector/modeselectorview.xaml'.
This particular view resides in a project of this application's solution, so the assembly will already be in the appdomain. The assembly also contains that custom attribute so the above code will be trying to load it, although I believe that Assembly.LoadFrom() should not load the same assembly again?
Just in case, I modified the "if" block in my LoadAssemblies() method to ignore assemblies already in the app domain:
if (ContainsCustomAttr(assm) && !AppDomain.CurrentDomain.GetAssemblies().Contains(assm))
Sure enough, a breakpoint shows that the assembly in question (containing that view) is ignored and not loaded into the app domain. However I still get the same exception further down the line.
In fact I can comment out the entire "if" block so no assemblies are being loaded into the app domain, and I still get the exception, suggesting that it's caused by loading the assembly into that AssemblyLoadContext.
Also, a breakpoint shows that context is being unloaded via its Dispose() method, upon dropping out of the "using" block in the LoadAssemblies() method.
Edit: even with the "if" block commented out, a breakpoint at the end of the method shows that all the assemblies being loaded by ctx.LoadFromAssemblyPath() are ending up in AppDomain.Current. What am I not understanding? Is the context part of the appdomain and not a separate "area"? How can I achieve this "isolated" loading of assemblies in a similar way to the "reflection only" approach that I was using in .Net 4.x?
Okay, so I found the answer, which is to use MetadataLoadContext. This is essentially the .Net Core replacement for reflection-only loading:
private void LoadAssemblies(string folder)
{
// The load context needs access to the .Net "core" assemblies...
var allAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.Client.dll").ToList();
// .. and the assemblies that I need to examine.
var assembliesToExamine = Directory.GetFiles(folder, "NuIns.CoDaq.*.Client.dll");
allAssemblies.AddRange(assembliesToExamine);
var resolver = new PathAssemblyResolver(allAssemblies);
using (var mlc = new MetadataLoadContext(resolver))
{
foreach (var assemblyFile in assembliesToExamine)
{
var assm = mlc.LoadFromAssemblyPath(assemblyFile);
if (ContainsCustomAttr(assm))
{
var assm2 = Assembly.LoadFrom(assemblyFile);
AddMimicAssemblyInfo(assm2);
}
}
}
}

Running a runtime compiled C# script in a sandbox AppDomain

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

ShadowCopy assemblies

I have a problem and I really have no clue why it doesn't work. I've read a lot of tutorials (also on stackoverflow) and still nothing.
My goal is to use reflection on some .dll files (they are not use by any program yet) and get inheritance types, methods, constructors etc. Everything works correctly but the problem is that dlls are locked and cannot be deleted until I turn off the program. This is part of my code where I try to resolve the problem.
var apds = new AppDomainSetup();
apds.ApplicationName = "MyAssemblies";
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain apd = AppDomain.CreateDomain("newdomain", adevidence, apds);
apd.AppendPrivatePath("Assemblies");
apd.SetCachePath("C:\\Cache");
apd.SetShadowCopyFiles();
foreach (var type in apd.Load(AssemblyName.GetAssemblyName(file.Path)).GetTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
{ //rest is ok
I know I use some deprecated methods but it's one of the try. Dlls are succesfully copied into cache directory but they seems to be loaded into current domain too.
Where's the problem?
Thanks in advance.
I modified my code to and use Loader class but i still have locked files.
class Loader : MarshalByRefObject
{
public Assembly assembly;
public void LoadAssembly(string path)
{
assembly = Assembly.Load(AssemblyName.GetAssemblyName(path));
}
public Types[] getTypes()
{
return assembly.getTypes();
}
}
//...
if (file.Type == ".dll")
{
var apds = new AppDomainSetup
{
ApplicationName = "MyAssemblies",
ApplicationBase = Path,
ShadowCopyFiles = "true",
ShadowCopyDirectories = Path
};
AppDomain apd = AppDomain.CreateDomain("newdomain", null, apds);
Loader loader = (Loader)apd.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.LoadAssembly(file.Path);
foreach (var type in loader.getTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
//...
Any idea?
You can load your assembly in the following way.
var types = Assembly.Load(File.ReadAllBytes("YourAssembly.dll")).GetTypes();
Now you can extract the types from the assembly, and you can delete the assembly while the application is still running.

Understanding AppDomains and strong naming

I'm trying to create a sandbox for where I can load plugins. I create the AppDomain and specify a StrongName for my host assembly, and my understanding is that any code running in the assembly that is strongly named runs in Full Trust. However this seems not the case.
Example,
static void Main(string[] args)
{
var permissions = new PermissionSet(PermissionState.None);
var setup = new AppDomainSetup()
{
ApplicationBase = "C:\\Temporary\\Sandbox"
};
var domain = AppDomain.CreateDomain("Sandbox",
null,
setup,
permissions,
typeof (PluginHost).Assembly.Evidence.GetHostEvidence<StrongName>());
var handle = Activator.CreateInstanceFrom(domain,
typeof (PluginHost).Assembly.ManifestModule.FullyQualifiedName,
typeof (PluginHost).FullName);
var host = (PluginHost) handle.Unwrap();
host.RunPlugin();
}
PluginHost (defined in the same assembly I'm strong naming),
public class PluginHost : MarshalByRefObject
{
public void RunPlugin()
{
File.ReadAllText("C:\\Passwords.txt");
var asm = Assembly.LoadFile(#"C:\Plugins\UnsafePlugin.dll");
var t = asm.GetType("UnsafePlugin.FooPlugin");
object plugin = Activator.CreateInstance(t);
plugin.GetType().GetMethod("Run").Invoke(plugin, null);
}
}
And this is the plugin (residing in an external assembly)
public class FooPlugin
{
public void Run()
{
File.ReadAllText("C:\\Passwords.txt");
}
}
I am strong naming the assembly containing PluginHost and therefore I expect to be able to read my passwords file from PluginHost and not from FooPlugin but the code throws a SecurityException when reading the file (in PluginHost).
My understanding from the MSDN documentation on the subject was that the fourth parameter containing StrongName-instances will give those assemblies full trust in the application domain.
From the documentation:
"An array of strong names representing assemblies to be considered
fully trusted in the new application domain."
Since I just proved myself wrong - how am I supposed to get this to work? Why am I not in full trust even though I've passed my StrongName to AppDomain.Create?

C# fully trusted assembly with SecuritySafeCritical funciton still throwing SecurityExceptions

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

Categories

Resources