using Microsoft.CodeAnalysis;
using Mono.Cecil;
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Security.Cryptography;
namespace Sandbox;
///
/// Test for access rules compliance. Can be shared to prevent unnecessary re-checking or resolving.
///
[SkipHotload]
public partial class AccessControl : IAssemblyResolver
{
internal ConcurrentDictionary SafeAssemblies = new();
internal ConcurrentDictionary Assemblies = new( AssemblyNameComparer.Instance );
internal AccessRules Rules;
public AccessControl()
{
Rules = new AccessRules();
}
public void Dispose()
{
foreach ( var assm in Assemblies.Values )
{
assm.Dispose();
}
Assemblies = null;
}
///
/// Dangerous! Create a of the given stream
/// without actually passing it through access control.
///
public TrustedBinaryStream TrustUnsafe( byte[] dll )
{
var instance = new AssemblyAccess( this, dll );
return TrustedBinaryStream.CreateInternal( dll );
}
///
/// Checks if an assembly passes whitelist checks.
///
///
/// wrapper around .
/// Will close if gets closed.
///
public AccessControlResult VerifyAssembly( Stream dll, out TrustedBinaryStream outStream, bool addToWhitelist = true )
{
var ms = new MemoryStream();
dll.CopyTo( ms );
var bytes = ms.ToArray();
ms.Dispose();
var instance = new AssemblyAccess( this, bytes );
instance.Verify( out outStream );
if ( addToWhitelist )
{
AddSafeAssembly( instance.Assembly.Name.Name, bytes );
}
return instance.Result;
}
///
/// If we're definitely never going to see this assembly again (because it's being unloaded for instance)
/// We can totally get rid of it and free all that lovely memory.
///
public void ForgetAssembly( string name )
{
RemoveSafeAssembly( name );
var matches = Assemblies
.Where( x => x.Key.Name.Equals( name, StringComparison.OrdinalIgnoreCase ) )
.ToArray();
foreach ( var match in matches )
{
if ( Assemblies.Remove( match.Key, out var assembly ) )
{
assembly.Dispose();
}
}
}
///
/// Forget all versions of the named assembly strictly older than this one.
///
public void ForgetOlderAssemblyDefinitions( AssemblyName name )
{
ForgetOlderAssemblyDefinitions( new AssemblyNameReference( name.Name, name.Version ) );
}
///
/// Forget all versions of the named assembly strictly older than this one.
///
public void ForgetOlderAssemblyDefinitions( AssemblyNameReference name )
{
var matches = Assemblies
.Where( x => x.Key.Name.Equals( name.Name, StringComparison.OrdinalIgnoreCase ) )
.Where( x => x.Key.Version.CompareTo( name.Version ) < 0 )
.ToArray();
foreach ( var match in matches )
{
if ( Assemblies.Remove( match.Key, out var assembly ) )
{
assembly.Dispose();
}
}
}
internal void AddSafeAssembly( string name, byte[] data )
{
using var sha256 = SHA256.Create();
var hash = Convert.ToBase64String( sha256.ComputeHash( data ) );
SafeAssemblies[name] = hash;
}
internal bool CheckSafeAssembly( string name, byte[] data )
{
using var sha256 = SHA256.Create();
var hash = Convert.ToBase64String( sha256.ComputeHash( data ) );
return SafeAssemblies.TryGetValue( name, out var existing ) && existing == hash;
}
internal bool RemoveSafeAssembly( string name ) => SafeAssemblies.Remove( name, out _ );
public struct CodeLocation
{
public string Text;
public Location RoslynLocation = null;
public CodeLocation( string text )
{
// most definitions will not have accurate symbols/locations, this is the best we can get
Text = text;
}
public CodeLocation( Location location )
{
Text = location.ToString();
RoslynLocation = location;
}
}
}