Files
sbox-public/engine/Sandbox.Access/AssemblyAccess.Touch.cs
Matt Stevens 3f11417817 Whitelist: explicit Unsafe/Span/Memory (#5077)
* Whitelist System.Memory and System.ReadOnlyMemory (without pinning)
* Be very explicit with System.Span and System.ReadOnlySpan whitelists, do not allow pinnables
* Be completely explicit with Unsafe IL whitelist, allow Unsafe.SizeOf<T>
* BlacklistCodeWalker can have an allow list
* Compiler blacklist explicitly bans CreateSpan, allows Unsafe.SizeOf<T>
* Whitelist System.IO.InvalidDataException
* Better InlineArray whitelisting for compiler generated code
* WhitelistGen: Creates explicit signatures from wildcard rules
* Claude skill for whitelisting, uses the WhitelistGen tool to be very explicit and makes a good effort to security review
2026-06-17 18:28:47 +01:00

147 lines
3.4 KiB
C#

using Microsoft.CodeAnalysis;
using Mono.Cecil;
using System;
namespace Sandbox;
internal partial class AssemblyAccess
{
public class Access
{
public string Name { get; set; }
public string Type { get; set; }
public int Count { get; set; }
public List<AccessControl.CodeLocation> Locations { get; set; } = new( 5 );
}
bool Touch( string name, string type )
{
bool alreadyTouched = false;
Touched.AddOrUpdate( name,
add => new Access { Name = name, Type = type, Count = 1, Locations = new List<AccessControl.CodeLocation> { Location } },
( name, update ) =>
{
update.Count++;
if ( update.Locations.Count() < 5 )
update.Locations.Add( Location );
alreadyTouched = true;
return update;
} );
return alreadyTouched;
}
bool Touch( TypeDefinition typedef )
{
return Touch( AccessSignature.Type( typedef ), "type" );
}
bool Touch( TypeReference typeRef )
{
if ( typeRef == null )
return false;
switch ( typeRef.MetadataType )
{
case Mono.Cecil.MetadataType.Void:
case Mono.Cecil.MetadataType.Single:
case Mono.Cecil.MetadataType.Int32:
case Mono.Cecil.MetadataType.Int16:
case Mono.Cecil.MetadataType.Int64:
case Mono.Cecil.MetadataType.Boolean:
case Mono.Cecil.MetadataType.String:
return true;
}
if ( typeRef.IsGenericParameter )
return false;
if ( typeRef is IModifierType typeMod )
return Touch( typeMod.ModifierType ) || Touch( typeMod.ElementType );
if ( typeRef is GenericInstanceType git )
{
foreach ( var param in git.GenericArguments )
{
Touch( param );
}
}
if ( typeRef.IsArray || typeRef.IsByReference )
{
return Touch( typeRef.GetElementType() );
}
var typeDef = typeRef.Resolve();
if ( typeDef == null )
throw new Exception( $"TypeDefinition was null - couldn't resolve {typeRef}" );
//
// If this type is in a dll we've already approved, or in this assembly we're scanning
// then just let it slide because we're already gonna have whitelisted them
//
if ( typeDef.Module.Assembly == Assembly )
return true;
if ( Global.Assemblies.Any( x => x.Value == typeDef.Module.Assembly ) )
return true;
return Touch( typeDef );
}
/// <summary>
/// These methods show up a lot and unexplitable, easy to skip
/// </summary>
static string[] skipMethods = new string[]
{
"System.Void System.Runtime.CompilerServices.RuntimeHelpers::EnsureSufficientExecutionStack()"
};
bool Touch( MethodReference source )
{
var typedef = source.Resolve();
if ( typedef is null )
throw new System.Exception( $"MethodDefinition was null - couldn't resolve {source}" );
// No need to investigate this - we'll investigate in our crawl
if ( typedef.DeclaringType.Module.Assembly == Assembly )
return true;
// This is called a LOT, lets skip it when we can
if ( typedef.DeclaringType.Module.Assembly.Name.Name == "System.Private.CoreLib" &&
typedef.FullName == "System.Void System.Runtime.CompilerServices.RuntimeHelpers::EnsureSufficientExecutionStack()" )
{
return true;
}
return Touch( typedef );
}
bool Touch( MethodDefinition typedef )
{
var touchName = AccessSignature.Method( typedef );
if ( Touch( touchName, "method" ) )
return true;
//
// Note - this is the definition - we can't test the generic parameters here
// but they should be checked on usage !
//
Touch( typedef.ReturnType );
foreach ( var param in typedef.Parameters )
{
Touch( param.ParameterType );
}
return false;
}
}