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