Assembly loaded using Assembly.LoadFrom() on remote machine causes SecurityException - c#

I am loading an assembly using Assembly.LoadFrom(fileName). When fileName is on the local machine, everything works fine. When, however, the identical file (and dependencies) are on a remote network share, I get a System.Security.SecurityException the moment I try to create a new SqlConnection from the remote assembly:
System.Security.SecurityException: Request for the permission of type
'System.Data.SqlClient.SqlClientPermission, System.Data,
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
failed.
What's the cure?

You could load the assembly as bytes and load it with Assembly.Load(bytes), maybe this works.
Or you give the application the requested permission.
Edit:
I made a little test and it worked for me. Here is some code:
static Dictionary<Assembly, String> _Paths = new Dictionary<Assembly, String>();
static void Main(string[] args)
{
AppDomain current = AppDomain.CurrentDomain;
current.AssemblyResolve += new ResolveEventHandler(HandleAssemblyResolve);
// This line loads a assembly and retrieves all types of it. Only when
// calling "GetTypes" the 'AssemblyResolve'-event occurs and loads the dependency
Type[] types = LoadAssembly("Assemblies\\MyDLL.dll").GetTypes();
// The next line is used to test permissions, i tested the IO-Permissions
// and the Reflection permissions ( which should be denied when using remote assemblies )
// Also this test includes the creation of a Form
Object instance = Activator.CreateInstance(types[0]);
}
private static Assembly LoadAssembly(string file)
{
// Load the assembly
Assembly result = Assembly.Load(File.ReadAllBytes(file));
// Add the path of the assembly to the dictionary
_Paths.Add(result, Path.GetDirectoryName(file));
return result;
}
static Assembly HandleAssemblyResolve(object sender, ResolveEventArgs args)
{
// Extract file name from the full-quallified name
String name = args.Name;
name = name.Substring(0, name.IndexOf(','));
// Load the assembly
return LoadAssembly(Path.Combine(_Paths[args.RequestingAssembly], name + ".dll"));
}
Something important:
There may be files which do not have a matching name and file name, but you can resolve this by checking all files in folder with AssemblyName.GetAssemblyName(file).

The assembly is being loaded with LocalIntranet permission set. This restricts certain APIs. See:
http://msdn.microsoft.com/en-us/library/03kwzyfc.aspx and http://msdn.microsoft.com/en-us/library/0x4t63kb%28v=vs.80%29.aspx

Well, I found a workaround. I could not find any way to circumvent the SecurityException problem - so instead of loading the assembly from the remote folder, I simply copied the remote folder contents at runtime to the local computer and ran from there. Simple, neat and works perfectly. Plus, it's arguably a better way of working, so that the clients are running off their local copy instead of putting load on the server, and it makes it much easier to deploy an updated version of the original, without any file locking.
Caveat to anyone who tries to follow in my footsteps: don't try and copy to a subfolder of your app directory; for some reason this causes errors with dependencies. Rather copy to 'Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)` or some other folder to which you know you have write access.

Related

Embedding SQLite.Interop.dll in C#

I have an application that has all the DLL files it needs embedded into it so that it is a standalone exe. Here is how I am loading everything now:
public static Assembly ExecutingAssembly = Assembly.GetExecutingAssembly();
public static string[] EmbeddedLibraries = ExecutingAssembly.GetManifestResourceNames().Where(x => x.EndsWith(".dll")).ToArray();
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) {
// Get assembly name
var assemblyName = new AssemblyName(args.Name).Name + ".dll";
// Get resource name
var resourceName = EmbeddedLibraries.FirstOrDefault(x => x.EndsWith(assemblyName));
if (resourceName == null) {
return null;
}
// Load assembly from resource
using (var stream = ExecutingAssembly.GetManifestResourceStream(resourceName)) {
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
return Assembly.Load(bytes);
}
}
public static void Main(string[] arg) {
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
RealMain();
}
So the first interesting thing I noticed is that it only loads the DLL files that are used in the first page. It isn't enough to put using System.Data.SQLite; at the top to get it to include System.Data.SQLite.dll, you also have to do something that touches that namespace right away.
The next issue involves SQLite.Interop.dll which it won't load into the assembly. When you try you get the following error:
Exception thrown: 'System.BadImageFormatException' in mscorlib.dll
An unhandled exception of type 'System.BadImageFormatException' occurred in mscorlib.dll
The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)
Now I could unpack the DLL and copy it to the c:\Windows\System32 directory, but since everything else is self contained, it would be nice if this was as well. I noticed this SDK that claims to be able to create it as a virtual file:
https://www.boxedapp.com/boxedappsdk/usecases/embed_system.data.sqlite.dll_dependencies.html
Is there a way to do this without the extra SDK? Or as an alternative, is there another way of creating and using SQLite databases without that DLL using a different package? I tried 3-4 different packages yesterday and didn't get anywhere.
SOLUTION
Ok, so this is disappointing that it is so easy, and yet nowhere else did I see the solution in dozens of other SO questions. On the SQLite.org website, there are a couple different downloads to choose from. When you use the NuGet package manager, you get the wrong version if what you want to do is embed everything.
To do it right, go to: https://system.data.sqlite.org/index.html/doc/trunk/www/downloads.wiki
On that page you will want to download the BUNDLE version. In my case that was sqlite-netFx46-static-binary-bundle-Win32-2015-1.0.112.0.zip
Manually add that as a resource, include in your application as an Embedded Resource (Those are 2 separate steps), set it to not copy to output directory and use the code from the question to have it added.

AppDomain exception is thrown

I've found a lot of similar questions but couldn't find any solution.
I have following code:
string file = "c:\\abc.dll";
AppDomainSetup ads = new AppDomainSetup();
ads.PrivateBinPath = Path.GetDirectoryName(file);
AppDomain ad2 = AppDomain.CreateDomain("AD2", null, ads);
ProxyDomain proxy = (ProxyDomain)ad2.CreateInstanceAndUnwrap(typeof(ProxyDomain).Assembly.FullName, typeof(ProxyDomain).FullName);
Assembly asm = proxy.GetAssembly(file); // exception File.IO assembly not found is thrown here after succesfully running the funktion GetAssembly.
public class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
Assembly asm = Assembly.LoadFile(assemblyPath);
//...asm is initialized correctly, I can see everything with debugger
return asm;
}
catch
{
return null;
}
}
}
The most Interesting thing that then my GetAssembly funktion returns some other type, even my custom serializable class, everything is fine. Does someone know what I'm missing? Or It's just impossible to return loaded assembly to another domain?
Thank you
I imagine File.IO is sitting in your main application's bin directory? If so, your abc.dll will not know where to find it (unless your main application is also sitting in C:\\).
You need to do one of
Bind to the AppDomain.AssemblyResolve event and manually load the referenced dll
Change the AppDomainSetup's base directory (which is one of the places .NET knows to look for dlls)
Install File.IO to the GAC (which is another one of the places .NET knows to look for dlls)
Add the location of File.IO to your AppDomainSetup's private probing path (which is another one of the places .NET will try to look for dlls).

Unable to load executing assembly into new AppDomain, FileNotFoundException

I am trying to host a text template class proxy inside a new AppDomain.
I have some old scripting code that does something similar, that contains this working code:
_ScriptAppDomain = AppDomain.CreateDomain(scriptDomainFriendlyName);
_ScriptProxy = (IScriptEngineProxy)_ScriptAppDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName,
"LVK.Scripting.ScriptEngineProxy");
However, when I try this with my new class, with the following
_TemplateDomain = AppDomain.CreateDomain(templateDomainFriendlyName);
_TemplateProxy = (ITemplateProxy)_TemplateDomain.CreateInstanceAndUnwrap(
Assembly.GetExecutingAssembly().FullName,
"TextTemplate.TemplateProxy");
I just get "FileNotFoundException", with the following details:
Could not load file or assembly 'TextTemplate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bb70a2e62a722ace' or one of its dependencies. The system cannot find the file specified.
What am I missing?
Basically, I have a Template class in the TextTemplate namespace (and assembly), which tries to load a TemplateProxy class (descending from MarshalByRefObject) into the new appdomain, but it appears my main assembly is not loaded into this domain.
This works if I use the older code, but not with this new one, but I can't spot the difference.
Here's some more details:
Assembly is not registered with the GAC (neither was the old one, which works)
I have not overridden any AssemblyResolve event (neither did the old one, which works)
I'm not averse to handling the AssemblyResolve event, if that is what is needed. I just found it odd that my old code worked, and this didn't.
Assumying your assembly is in the same directory as your current application base, try specifying Application Base:
AppDomain.CreateDomain(templateDomainFriendlyName, null,
new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
});
If you don't know what you are doing, the best way to create a new domain is to copy all settings from the current one, like this:
var newDomain = AppDomain.CreateDomain("NAME",
AppDomain.CurrentDomain.Evidence,
AppDomain.CurrentDomain.SetupInformation);
I had similar issues, and making a copy resolved them for me

RegistrationServices.RegisterAssembly error - help!

I have an assembly on a shared folder (UNC-path only, no mapped drive). When I try to register it programmatically via RegistrationServices, I'm getting a strange error.
Here's the code:
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
namespace BLRegisterAssembly
{
public static class BlRegisterAssembly
{
public static void Register()
{
var asm = Assembly.LoadFile(#"\\myUNCPath\myAssembly.dll");
var rs = new RegistrationServices();
rs.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase);
// I've also tried AssemblyRegistrationFlags.None : same error.
}
}
}
This is the error I'm getting:
"Could not load file or assembly
'[xxxxxxxxxxxxx],
Version=1.0.0.0, Culture=neutral,
PublicKeyToken=[xxxxxxxxxxxxxx]' or
one of its dependencies. The system
cannot find the file specified."
(The file in question is a referenced assembly that the main assembly uses).
Some more points:
- the referenced assembly is located in the same folder as the main assembly that I'm trying to register.
- the folder cannot be mapped as a logical drive. Because of how the network folders are accessed, users in different groups have different drive mappings to the same network folders, and these cannot be modified, per IT policy...
Can anyone point me in the right direction to resolve the problem?
ANSWERED
Because I was using Assembly.LoadFile, the dependent assemblies have to be resolved manually via AssemblyResolve. The following code update fixed my woes:
public void Register()
{
AppDomain.CurrentDomain.AssemblyResolve +=
new ResolveEventHandler(CurrentDomain_AssemblyResolve);
var asm = Assembly.LoadFile(Path.Combine(m_path, assemblyName));
var rs = new RegistrationServices();
rs.RegisterAssembly(asm, AssemblyRegistrationFlags.SetCodeBase);
}
static Assembly CurrentDomain_AssemblyResolve(object sender,
ResolveEventArgs args)
{
//... code to resolve the path and load the dependent assembly...
}
You should never use LoadFile(), use LoadFrom() so the CLR has a shot at finding any dependent assemblies. If you still have trouble then use Fuslogvw.exe to get a trace of the assembly resolution attempt. The backup plan is to implement AppDomain.AssemblyResolve.

InvalidCastException for two Objects of the same type

I have this weird problem that I cannot handle myself. A class in the model of my mvp-project designed as singleton causes an InvalidCastException.
The source of error is found in this code line where the deserialised object is assigned to the instance variable of the class: engineObject = (ENGINE)xSerializer.Deserialize(str);. It occurs whenever I try to add one of my UserControls to a Form or to a different UC. All of my UCs have a special presenter that accesses the above mentioned instance variable of the singleton class.
This is what I get when trying to add a UC somewhere:
'System.TypeInitializationException: The type initializer for 'MVP.Model.EngineData' threw an exception. ---->
System.InvalidCastException: [A]Engine cannot be cast to [B]Engine. Type A originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\uankw1hh01\MVP.Model.dll'.
Type B originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\u_hge2de01\MVP.Model.dll'
...
So I somehow have two assemblies and they are not accessed from my project folder, but from a VS temp folder? I googled a lot and only found this: IronPython Exception: [A]Person cannot be cast to [B]Person. There is a solution offered, but it concerns IronPhyton and I don't know where to use it within my project.
Types are per-assembly; if you have "the same" assembly loaded twice, then types in each "copy" of the assembly are not considered to be the same type.
These issues normally crop up when the two assemblies are in the Load and LoadFrom contexts. See
Difference between LoadFile and LoadFrom with .NET Assemblies?
and the link to suzcook's blog for details on that issue.
Also, consider using the fusion log viewer to help diagnose the problem.
http://msdn.microsoft.com/en-us/library/e74a18c4%28VS.71%29.aspx
Judging by the context in which the assembly is getting loaded (the context is "LoadNeither"), some developers may be doing something like loading an assembly that has been internally packaged as a resource with your application. If you do this, you will be using the AppDomain.CurrentDomain.AssemblyResolve event handler, so that your application can specify where .NET should get any particular assembly that it needs.
My answer will not explain the machinations of how to do that - but I'm mentioning it because this process led directly to the same exact error encountered by the original poster. As Eric Lippert mentions, types are per-assembly. So if you load an individual assembly more than once, the same defined class will appear as different classes - even though visual inspection shows that they appear to be the same.
We've seen instances in which .NET will call the ResolveEventHandler more than once for the same DLL. I'm not sure why .NET sometimes does this (it happened on some machines, but not all machines). But to resolve the problem, we needed keep a global list of handles to loaded assemblies, so that if .NET wanted to load the assembly again, we returned a handle to the same Assembly that was originally loaded, instead of loading another copy into memory.
I have included the code that caused the issue for us, and notes about how to handle it properly.
public void AppStartup (object sender, StartupEventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
public System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll", "");
dllName = dllName.Replace(".", "_");
if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = null;
try
{
bytes = (byte[])rm.GetObject(dllName);
}
catch (Exception ex)
{
}
if (bytes != null)
{
// the following call will return a newly loaded assembly
// every time it is called
// if this function is called more than once for the same
// assembly, you'll load more than one copy into memory
// this can cause the InvalidCastException
// instead of doing this, you keep a global list of loaded
// assemblies, and return the previously loaded assembly
// handle, instead of loading it again
return System.Reflection.Assembly.Load(bytes);
}
return null;
}
My situation involved two copies of the same dll. One was in the bin folder and one was in a sub-folder of the same bin folder. Both were loaded, amazingly some things worked fine, but some things didn't and that's when this error message appeared:
System.InvalidOperationException; There was an error generating the XML document.; Source: System.Xml; TargetSite: Void Serialize(System.Xml.XmlWriter, System.Object, System.Xml.Serialization.XmlSerializerNamespaces, System.String, System.String);
Hidden in this was the following inner exception (this was to do with Microsoft Dynamics CRM 4.0, but could relate to anything)
System.InvalidCastException; [A]XXX.CRMCustomCode.YYY.CreateCompanyRequest cannot be cast to [B]XXX.CRMCustomCode.YYY.CreateCompanyRequest. Type A originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadFrom' at location 'C:\Program Files\Microsoft CRM\Server\bin\assembly\XXX.CRMCustomCode.dll'. Type B originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Program Files\Microsoft CRM\Server\bin\XXX.CRMCustomCode.dll'.;
I simply deleted the duplicate dll (in C:\Program Files\Microsoft CRM\Server\bin) and the error went away.
My particular case - class library referenced in web application was renamed and rebuilt. Older version of library was still in bin folder under old name. The framework loads whatever in bin folder (both libraries) and emits this error. So it happens not only when assemblies are loaded explicitly.
The obvious solution in my case is to clean bin folder.
I got it working when I tried changing this cast:
var t = (TeacherWebPages)Session["TeachersAD"];
To this:
var t = Session["TeachersAD"] as TeacherWebPages;
However the assembly/session/memcache was different and no data got back but no error occurred. So later I still had to delete specific temporary files every time source page was changed from the folder it was complaining about which would require me to kill IIS process from task manager. Or in my case I can just logout and it clears the session state.

Categories

Resources