Roslyn slow startup time - c#

I've noticed that the startup time for Roslyn parsing/compilation is a fairly significant one-time cost. EDIT: I am using the Roslyn CTP MSI (the assembly is in the GAC). Is this expected? Is there any workaround?
Running the code below takes almost the same amount of time with 1 iteration (~3 seconds) as with 300 iterations (~3 seconds).
[Test]
public void Test()
{
var iters = 300;
foreach (var i in Enumerable.Range(0, iters))
{
// Parse the source file using Roslyn
SyntaxTree syntaxTree = SyntaxTree.ParseText(#"public class Foo" + i + #" { public void Exec() { } }");
// Add all the references we need for the compilation
var references = new List<MetadataReference>();
references.Add(new MetadataFileReference(typeof(int).Assembly.Location));
var compilationOptions = new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
// Note: using a fixed assembly name, which doesn't matter as long as we don't expect cross references of generated assemblies
var compilation = Compilation.Create("SomeAssemblyName", compilationOptions, new[] {syntaxTree}, references);
// Generate the assembly into a memory stream
var memStream = new MemoryStream();
// if we comment out from this line and down, the runtime drops to ~.5 seconds
EmitResult emitResult = compilation.Emit(memStream);
var asm = Assembly.Load(memStream.GetBuffer());
var type = asm.GetTypes().Single(t => t.Name == "Foo" + i);
}
}

I think one issue is using a memory stream, instead you should try using a dynamic module and ModuleBuilder instead. Overall the code is executing faster but still has a heavier first load scenario. I'm pretty new to Roslyn myself so I'm not sure why this is faster but here is the changed code.
var iters = 300;
foreach (var i in Enumerable.Range(0, iters))
{
// Parse the source file using Roslyn
SyntaxTree syntaxTree = SyntaxTree.ParseText(#"public class Foo" + i + #" { public void Exec() { } }");
// Add all the references we need for the compilation
var references = new List<MetadataReference>();
references.Add(new MetadataFileReference(typeof(int).Assembly.Location));
var compilationOptions = new CompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary);
// Note: using a fixed assembly name, which doesn't matter as long as we don't expect cross references of generated assemblies
var compilation = Compilation.Create("SomeAssemblyName", compilationOptions, new[] { syntaxTree }, references);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new System.Reflection.AssemblyName("CustomerA"),
System.Reflection.Emit.AssemblyBuilderAccess.RunAndCollect);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule");
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
// if we comment out from this line and down, the runtime drops to ~.5 seconds
var emitResult = compilation.Emit(moduleBuilder);
watch.Stop();
System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds);
if (emitResult.Diagnostics.LongCount() == 0)
{
var type = moduleBuilder.GetTypes().Single(t => t.Name == "Foo" + i);
System.Diagnostics.Debug.Write(type != null);
}
}
By using this technique the compilation took just 96 milliseconds, on subsequent iterations it takes around 3 - 15ms. So I think you could be right in terms of the first load scenario adding some overhead.
Sorry I can't explain why it's faster! I'm just researching Roslyn myself and will do more digging later tonight to see if I can find any more evidence of what the ModuleBuilder provides over the memorystream.

I have came across the same issue using the Microsoft.CodeDom.Providers.DotNetCompilerPlatform package of ASP.net. It turns out this package launches csc.exe which uses VBCSCompiler.exe as a compilation server. By default the VBCSCompiler.exe server lives for 10 seconds and its boot time is of about 3 seconds. This explains why it takes about the same time to run your code once or multiple times. It seems like Microsoft is using this server as well in Visual Studio to avoid paying an extra boot time each time you run a compilation.
With the this package You can monitor your processes and will find a command line which looks like csc.exe /keepalive:10
The nice part is that if this server stays alive (even between two sessions of your application), you can get a fast compilation all the times.
Unfortunately, the Roslyn package is not really customizable and the easiest way I found to change this keepalive constant is to use the reflection to set non public variables value. On my side, I defined it to a full day as it always keep the same server even if I close and restart my application.
/// <summary>
/// Force the compiler to live for an entire day to avoid paying for the boot time of the compiler.
/// </summary>
private static void SetCompilerServerTimeToLive(CSharpCodeProvider codeProvider, TimeSpan timeToLive)
{
const BindingFlags privateField = BindingFlags.NonPublic | BindingFlags.Instance;
var compilerSettingField = typeof(CSharpCodeProvider).GetField("_compilerSettings", privateField);
var compilerSettings = compilerSettingField.GetValue(codeProvider);
var timeToLiveField = compilerSettings.GetType().GetField("_compilerServerTimeToLive", privateField);
timeToLiveField.SetValue(compilerSettings, (int)timeToLive.TotalSeconds);
}

When you call Compilation.Emit() it is the first time you actually need metadata, so the metadata file access occurs. After that, its cached. Though that should not account for 3secs just for mscorlib.

tldr: NGEN-ing roslyn dlls shaves off 1.5s off of the initial compilation/execution time (in my case from ~2s to ~0.5s)
Investigated this just now.
With a brand new console application and a nuget reference to Microsoft.CodeAnalysis.Scripting, the initial execution of a small snippet ("1+2") took about 2s, while subsequent ones were a lot faster - around 80ms (still a bit high for my taste but that's a different topic).
Perfview revealed that the delay was predominantly due to jitting:
Microsoft.CodeAnalysis.CSharp.dll: 941ms (3,205 methods jitted)
Microsoft.CodeAnalysis.dll 426ms (1,600 methods jitted)
I used ngen on Microsoft.CodeAnalysis.CSharp.dll (making sure to specify the /ExeCondig:MyApplication.exe because of the binding redirects in app.config) and got a nice performance improvement, the first-execution time fell to ~580ms.
This of course would need to be done on end user machines. In my case, I'm using Wix as the installer for my software and there's support for NGEN-ing files at install time.

Related

How can I compile C# user code in a sandbox [duplicate]

Here is the reason why this question was being asked: www.devplusplus.com/Tests/CSharp/Hello_World.
While similar questions were asked before, the many answers online have several issues:
This must be done ".Net 4.0" style, not legacy mode.
The assembly is in-memory and will only be in memory, it cannot be written to the file system.
I would like to limit all access to the file-system, network, etc.
Something like this:
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
var permissionSet = SecurityManager.GetStandardSandbox(evidence);
So far, I cannot find a way to create an AppDomain and load an assembly THAT IS NOT ON THE FILE SYSTEM, but rather in RAM.
Again, the reasons why the other solutions didn't work are identified above: 1. Many were for pre-4.0, and 2. Many relied on the ".Load" method pointing to the file system.
Answer 2: I have an assembly reference due to it being generated by the CSharpCodeProvider class, so if you know a way to turn that into a byte array, that would be perfect!
Sample Code to Show The Security Flaw
var provider = new CSharpCodeProvider(new Dictionary<String, String>
{ { "CompilerVersion", "v4.0" } });
var compilerparams = new CompilerParameters
{ GenerateExecutable = false, GenerateInMemory = true, };
var compilerResults = provider.CompileAssemblyFromSource(compilerparams,
string_Of_Code_From_A_User);
var instanceOfSomeClass = compilerResults.CompiledAssembly
.CreateInstance(className);
// The 'DoSomething' method can write to the file system and I don't like that!
instanceOfSomeClass.GetType().GetMethod("DoSomething")
.Invoke(instanceOfSomeClass, null);
So why can't I just save the assembly to a file first?
For two reasons:
This code is on a shared web server with limited permissions to the file-system itself.
This code may need to be run potentially thousands of times, and I don't want 1,000 dlls, even temporarily.
OK, first things first: there's no actual way to use the CSharpCodeProvider to do dynamic compilation of C# source entirely in memory. There are methods that seem to support that functionality, but since the C# compiler is a native executable that cannot run in-process, the source string is saved to a temporary file, the compiler is invoked on that file, and then the resulting assembly is saved to disk and then loaded for you using Assembly.Load.
Secondly, as you've discovered, you should be able to use the Compile method from within the AppDomain to load the assembly and give it the desired permissions. I ran into this same unusual behavior, and after a lot of digging found that it was a bug in the framework. I filed an issue report for it on MS Connect.
Since the framework is already writing to the filesystem anyway, the workaround is to have the assembly written to a temporary file and then loaded as needed. When you load it however, you'll need to temporarily assert permissions in the AppDomain, since you've disallowed access to the file system. Here's an example snippet of that:
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
From there you can use the assembly and reflection to invoke your method. Note that this method lets you hoist the compilation process outside of the sandboxed AppDomain, which is a plus in my opinion.
For reference, here is my Sandbox class created to facilitate the launching of script assemblies in a nice clean separate AppDomain that has limited permissions and can be easily unloaded when necessary:
class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public 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 string Execute(string assemblyPath, string scriptType, string method, params object[] parameters)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(scriptType);
if (type == null)
return null;
var instance = Activator.CreateInstance(type);
return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters));
}
}
Quick note: if you use this method to supply security evidence for the new AppDomain, you need to sign your assembly to give it a strong name.
Note that this works fine when run in process, but if you really want a bullet-proof script environment, you need to go one step further and isolate the script in a separate process to ensure that scripts that do malicious (or just stupid) things like stack overflows, fork bombs, and out of memory situations don't bring down the whole application process. I can give you more information on doing that if you need it.

Octopus client, getting version from project name in C#

First of, I am completely new to octopus client, used it for the first time just before posting this.
So, I've been landed with this project to update the version number on a webpage monitoring some of our octopus deployed projects. I have been looking around the octopus client and not really gotten anywhere. The best I have so far is:
OctopusServerEndpoint endPoint = new OctopusServerEndpoint(server, apiKey);
OctopusRepository repo = new OctopusRepository(endPoint);
var releases = repo.Releases.FindAll();
From these releases I can get the ProjectId and even the Version, the issue is that releases is 600 strong and I am only looking for 15 of them.
The existing code I have to work from used to parse the version from local files so that is all out the window. Also, the existing code only deals with the actual names of the projects, like "AWOBridge", not their ProjectId, which is "Projects-27".
Right now my only option is to manually write up a keyList or map to correlate the names I have with the IDs in the octopus client, which I of course rather not since it is not very extendable or good code practice in my opinion.
So if anyone has any idea on how to use the names directly with octopus client and get the version number from that I would very much appriciate it.
I'll be getting down into octopus client while waiting. Let's see if I beat you to it!
Guess I beat you to it!
I'll just leave an answer here if anyone ever has the same problem.
I ended up using the dashboardto get what I needed:
OctopusServerEndpoint endPoint = new OctopusServerEndpoint(server, apiKey);
OctopusRepository repo = new OctopusRepository(endPoint);
DashboardResource dash = repo.Dashboards.GetDashboard();
List<DashboardItemResource> items = dash.Items;
DashboardItemResource item = new DashboardItemResource();
List<DashboardProjectResource> projs = dash.Projects;
var projID = projs.Find(x => x.Name == projectName).Id;
item = items.Find(x => x.ProjectId == projID && x.IsCurrent == true);
The dashboard is great since it contains all the info that the web dashboard shows. So you can use Project, Release, Deployment and Environment with all the information they contain.
Hope this helps someone in the future!
I'm using LINQPad to run C# snippets for Octopus automation using the Octopus Client library and I have come up with following to get any version of a project making use of Regular expression pattern. It works quite well if you use Pre-release semantic versioning.
For example to get latest release for a project:
var project = Repo.Projects.FindByName("MyProjectName");
var release = GetReleaseForProject(project);
To get specific release use that has 'rc1' in the version for example (also useful if you use source code branch name in the version published to Octopus:
var release = GetReleaseForProject(project, "rc1");
public ReleaseResource GetReleaseForProject(ProjectResource project, string versionPattern = "")
{
// create compiled regex expression to use for search
var regex = new Regex(versionPattern, RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
var releases = Repo.Projects.GetReleases(project);
if (!string.IsNullOrWhiteSpace(versionPattern) && !releases.Items.Any(r => regex.IsMatch(r.Version)))
{
return null;
}
return (!string.IsNullOrWhiteSpace(versionPattern)) ? releases.Items.Where(r => regex.IsMatch(r.Version))?.First() : releases.Items?.First();;
}

How do i execute 3rd party code in a sandbox [duplicate]

Here is the reason why this question was being asked: www.devplusplus.com/Tests/CSharp/Hello_World.
While similar questions were asked before, the many answers online have several issues:
This must be done ".Net 4.0" style, not legacy mode.
The assembly is in-memory and will only be in memory, it cannot be written to the file system.
I would like to limit all access to the file-system, network, etc.
Something like this:
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
var permissionSet = SecurityManager.GetStandardSandbox(evidence);
So far, I cannot find a way to create an AppDomain and load an assembly THAT IS NOT ON THE FILE SYSTEM, but rather in RAM.
Again, the reasons why the other solutions didn't work are identified above: 1. Many were for pre-4.0, and 2. Many relied on the ".Load" method pointing to the file system.
Answer 2: I have an assembly reference due to it being generated by the CSharpCodeProvider class, so if you know a way to turn that into a byte array, that would be perfect!
Sample Code to Show The Security Flaw
var provider = new CSharpCodeProvider(new Dictionary<String, String>
{ { "CompilerVersion", "v4.0" } });
var compilerparams = new CompilerParameters
{ GenerateExecutable = false, GenerateInMemory = true, };
var compilerResults = provider.CompileAssemblyFromSource(compilerparams,
string_Of_Code_From_A_User);
var instanceOfSomeClass = compilerResults.CompiledAssembly
.CreateInstance(className);
// The 'DoSomething' method can write to the file system and I don't like that!
instanceOfSomeClass.GetType().GetMethod("DoSomething")
.Invoke(instanceOfSomeClass, null);
So why can't I just save the assembly to a file first?
For two reasons:
This code is on a shared web server with limited permissions to the file-system itself.
This code may need to be run potentially thousands of times, and I don't want 1,000 dlls, even temporarily.
OK, first things first: there's no actual way to use the CSharpCodeProvider to do dynamic compilation of C# source entirely in memory. There are methods that seem to support that functionality, but since the C# compiler is a native executable that cannot run in-process, the source string is saved to a temporary file, the compiler is invoked on that file, and then the resulting assembly is saved to disk and then loaded for you using Assembly.Load.
Secondly, as you've discovered, you should be able to use the Compile method from within the AppDomain to load the assembly and give it the desired permissions. I ran into this same unusual behavior, and after a lot of digging found that it was a bug in the framework. I filed an issue report for it on MS Connect.
Since the framework is already writing to the filesystem anyway, the workaround is to have the assembly written to a temporary file and then loaded as needed. When you load it however, you'll need to temporarily assert permissions in the AppDomain, since you've disallowed access to the file system. Here's an example snippet of that:
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
From there you can use the assembly and reflection to invoke your method. Note that this method lets you hoist the compilation process outside of the sandboxed AppDomain, which is a plus in my opinion.
For reference, here is my Sandbox class created to facilitate the launching of script assemblies in a nice clean separate AppDomain that has limited permissions and can be easily unloaded when necessary:
class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public 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 string Execute(string assemblyPath, string scriptType, string method, params object[] parameters)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(scriptType);
if (type == null)
return null;
var instance = Activator.CreateInstance(type);
return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters));
}
}
Quick note: if you use this method to supply security evidence for the new AppDomain, you need to sign your assembly to give it a strong name.
Note that this works fine when run in process, but if you really want a bullet-proof script environment, you need to go one step further and isolate the script in a separate process to ensure that scripts that do malicious (or just stupid) things like stack overflows, fork bombs, and out of memory situations don't bring down the whole application process. I can give you more information on doing that if you need it.

C# code completion with NRefactory 5

I just found out about NRefactory 5 and I would guess, that it is the most suitable solution for my current problem. At the moment I'm developing a little C# scripting application for which I would like to provide code completion. Until recently I've done this using the "Roslyn" project from Microsoft. But as the latest update of this project requires .Net Framework 4.5 I can't use this any more as I would like the app to run under Win XP as well. So I have to switch to another technology here.
My problem is not the compilation stuff. This can be done, with some more effort, by .Net CodeDomProvider as well. The problem ist the code completion stuff. As far as I know, NRefactory 5 provides everything that is required to provide code completion (parser, type system etc.) but I just can't figure out how to use it. I took a look at SharpDevelop source code but they don't use NRefactory 5 for code completion there, they only use it as decompiler. As I couldn't find an example on how to use it for code completion in the net as well I thought that I might find some help here.
The situation is as follows. I have one single file containing the script code. Actually it is not even a file but a string which I get from the editor control (by the way: I'm using AvalonEdit for this. Great editor!) and some assemblies that needs to get referenced. So, no solution files, no project files etc. just one string of source code and the assemblies.
I've taken a look at the Demo that comes with NRefactory 5 and the article on code project and got up with something like this:
var unresolvedTypeSystem = syntaxTree.ToTypeSystem();
IProjectContent pc = new CSharpProjectContent();
// Add parsed files to the type system
pc = pc.AddOrUpdateFiles(unresolvedTypeSystem);
// Add referenced assemblies:
pc = pc.AddAssemblyReferences(new CecilLoader().LoadAssemblyFile(
System.Reflection.Assembly.GetAssembly(typeof(Object)).Location));
My problem is that I have no clue on how to go on. I'm not even sure if it is the right approach to accomplish my goal. How to use the CSharpCompletionEngine? What else is required? etc. You see there are many things that are very unclear at the moment and I hope you can bring some light into this.
Thank you all very much in advance!
I've just compiled and example project that does C# code completion with AvalonEdit and NRefactory.
It can be found on Github here.
Take a look at method ICSharpCode.NRefactory.CSharp.CodeCompletion.CreateEngine. You need to create an instance of CSharpCompletionEngine and pass in the correct document and the resolvers. I managed to get it working for CTRL+Space compltition scenario. However I am having troubles with references to types that are in other namespaces. It looks like CSharpTypeResolveContext does not take into account the using namespace statements - If I resolve the references with CSharpAstResolver, they are resolved OK, but I am unable to correctly use this resolver in code completition scenario...
UPDATE #1:
I've just managed to get the working by obtaining resolver from unresolved fail.
Here is the snippet:
var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
var resolver3 = unresolvedFile.GetResolver(cmp, loc); // get the resolver from unresolvedFile
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );
Update #2:
Here is the complete method. It references classes from unit test projects, sou you would need to reference/copy them into your project:
public static IEnumerable<ICompletionData> DoCodeComplete(string editorText, int offset) // not the best way to put in the whole string every time
{
var doc = new ReadOnlyDocument(editorText);
var location = doc.GetLocation(offset);
string parsedText = editorText; // TODO: Why there are different values in test cases?
var syntaxTree = new CSharpParser().Parse(parsedText, "program.cs");
syntaxTree.Freeze();
var unresolvedFile = syntaxTree.ToTypeSystem();
var mb = new DefaultCompletionContextProvider(doc, unresolvedFile);
IProjectContent pctx = new CSharpProjectContent();
var refs = new List<IUnresolvedAssembly> { mscorlib.Value, systemCore.Value, systemAssembly.Value};
pctx = pctx.AddAssemblyReferences(refs);
pctx = pctx.AddOrUpdateFiles(unresolvedFile);
var cmp = pctx.CreateCompilation();
var resolver3 = unresolvedFile.GetResolver(cmp, location);
var engine = new CSharpCompletionEngine(doc, mb, new CodeCompletionBugTests.TestFactory(resolver3), pctx, resolver3.CurrentTypeResolveContext );
engine.EolMarker = Environment.NewLine;
engine.FormattingPolicy = FormattingOptionsFactory.CreateMono();
var data = engine.GetCompletionData(offset, controlSpace: false);
return data;
}
}
Hope it helps,
Matra
NRefactory 5 is being used in SharpDevelop 5. The source code for SharpDevelop 5 is currently available in the newNR branch on github. I would take a look at the CSharpCompletionBinding class which has code to display a completion list window using information from NRefactory's CSharpCompletionEngine.

In .NET 4.0, how do I 'sandbox' an in-memory assembly and execute a method?

Here is the reason why this question was being asked: www.devplusplus.com/Tests/CSharp/Hello_World.
While similar questions were asked before, the many answers online have several issues:
This must be done ".Net 4.0" style, not legacy mode.
The assembly is in-memory and will only be in memory, it cannot be written to the file system.
I would like to limit all access to the file-system, network, etc.
Something like this:
var evidence = new Evidence();
evidence.AddHostEvidence(new Zone(SecurityZone.Internet));
var permissionSet = SecurityManager.GetStandardSandbox(evidence);
So far, I cannot find a way to create an AppDomain and load an assembly THAT IS NOT ON THE FILE SYSTEM, but rather in RAM.
Again, the reasons why the other solutions didn't work are identified above: 1. Many were for pre-4.0, and 2. Many relied on the ".Load" method pointing to the file system.
Answer 2: I have an assembly reference due to it being generated by the CSharpCodeProvider class, so if you know a way to turn that into a byte array, that would be perfect!
Sample Code to Show The Security Flaw
var provider = new CSharpCodeProvider(new Dictionary<String, String>
{ { "CompilerVersion", "v4.0" } });
var compilerparams = new CompilerParameters
{ GenerateExecutable = false, GenerateInMemory = true, };
var compilerResults = provider.CompileAssemblyFromSource(compilerparams,
string_Of_Code_From_A_User);
var instanceOfSomeClass = compilerResults.CompiledAssembly
.CreateInstance(className);
// The 'DoSomething' method can write to the file system and I don't like that!
instanceOfSomeClass.GetType().GetMethod("DoSomething")
.Invoke(instanceOfSomeClass, null);
So why can't I just save the assembly to a file first?
For two reasons:
This code is on a shared web server with limited permissions to the file-system itself.
This code may need to be run potentially thousands of times, and I don't want 1,000 dlls, even temporarily.
OK, first things first: there's no actual way to use the CSharpCodeProvider to do dynamic compilation of C# source entirely in memory. There are methods that seem to support that functionality, but since the C# compiler is a native executable that cannot run in-process, the source string is saved to a temporary file, the compiler is invoked on that file, and then the resulting assembly is saved to disk and then loaded for you using Assembly.Load.
Secondly, as you've discovered, you should be able to use the Compile method from within the AppDomain to load the assembly and give it the desired permissions. I ran into this same unusual behavior, and after a lot of digging found that it was a bug in the framework. I filed an issue report for it on MS Connect.
Since the framework is already writing to the filesystem anyway, the workaround is to have the assembly written to a temporary file and then loaded as needed. When you load it however, you'll need to temporarily assert permissions in the AppDomain, since you've disallowed access to the file system. Here's an example snippet of that:
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
From there you can use the assembly and reflection to invoke your method. Note that this method lets you hoist the compilation process outside of the sandboxed AppDomain, which is a plus in my opinion.
For reference, here is my Sandbox class created to facilitate the launching of script assemblies in a nice clean separate AppDomain that has limited permissions and can be easily unloaded when necessary:
class Sandbox : MarshalByRefObject
{
const string BaseDirectory = "Untrusted";
const string DomainName = "Sandbox";
public 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 string Execute(string assemblyPath, string scriptType, string method, params object[] parameters)
{
new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery, assemblyPath).Assert();
var assembly = Assembly.LoadFile(assemblyPath);
CodeAccessPermission.RevertAssert();
Type type = assembly.GetType(scriptType);
if (type == null)
return null;
var instance = Activator.CreateInstance(type);
return string.Format("{0}", type.GetMethod(method).Invoke(instance, parameters));
}
}
Quick note: if you use this method to supply security evidence for the new AppDomain, you need to sign your assembly to give it a strong name.
Note that this works fine when run in process, but if you really want a bullet-proof script environment, you need to go one step further and isolate the script in a separate process to ensure that scripts that do malicious (or just stupid) things like stack overflows, fork bombs, and out of memory situations don't bring down the whole application process. I can give you more information on doing that if you need it.

Categories

Resources