I am going to be dynamically compiling and executing code using Roslyn like the example below. I want to make sure the code does not violate some of my rules, like:
- Does not use Reflection
- Does not use HttpClient or WebClient
- Does not use File or Directory classes in System.IO namespace
- Does not use Source Generators
- Does not call unmanaged code
Where in the following code would I insert my rules/checks and how would I do them?
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;
using System.Reflection;
using System.Runtime.CompilerServices;
string code = @"using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
namespace Customization
{
public class Script
{
public async Task<object?> RunAsync(object? data)
{
//The following should not be allowed
File.Delete(@""C:\Temp\log.txt"");
return await Task.FromResult(data);
}
}
}";
var compilation = Compile(code);
var bytes = Build(compilation);
Console.WriteLine("Done");
CSharpCompilation Compile(string code)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code);
string? dotNetCoreDirectoryPath = Path.GetDirectoryName(typeof(object).GetTypeInfo().Assembly.Location);
if (String.IsNullOrWhiteSpace(dotNetCoreDirectoryPath))
{
throw new ArgumentNullException("Cannot determine path to current assembly.");
}
string assemblyName = Path.GetRandomFileName();
List<MetadataReference> references = new();
references.Add(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Console).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Dictionary<,>).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(typeof(Task).Assembly.Location));
references.Add(MetadataReference.CreateFromFile(Path.Combine(dotNetCoreDirectoryPath, "System.Runtime.dll")));
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
SemanticModel model = compilation.GetSemanticModel(syntaxTree);
CompilationUnitSyntax root = (CompilationUnitSyntax)syntaxTree.GetRoot();
//TODO: Check the code for use classes that are not allowed such as File in the System.IO namespace.
//Not exactly sure how to walk through identifiers.
IEnumerable<IdentifierNameSyntax> identifiers = root.DescendantNodes()
.Where(s => s is IdentifierNameSyntax)
.Cast<IdentifierNameSyntax>();
return compilation;
}
[MethodImpl(MethodImplOptions.NoInlining)]
byte[] Build(CSharpCompilation compilation)
{
using (MemoryStream ms = new())
{
//Emit to catch build errors
EmitResult emitResult = compilation.Emit(ms);
if (!emitResult.Success)
{
Diagnostic? firstError =
emitResult
.Diagnostics
.FirstOrDefault
(
diagnostic => diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error
);
throw new Exception(firstError?.GetMessage());
}
return ms.ToArray();
}
}