mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-08-01 08:18:20 -04:00
* 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
118 lines
3.9 KiB
C#
118 lines
3.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text.RegularExpressions;
|
|
using Mono.Cecil;
|
|
using Sandbox;
|
|
|
|
namespace Sandbox.WhitelistGen;
|
|
|
|
/// <summary>
|
|
/// Expands an access-control whitelist wildcard pattern into the full, explicit list of every type
|
|
/// and method it would match - in the exact "touch name" format the scanner compares against (via
|
|
/// <see cref="AccessSignature"/>). Paste the output into a Rules file and comment out the entries you
|
|
/// don't want to allow, so the whitelist stays explicit (no wildcards) and new members can't sneak in.
|
|
/// </summary>
|
|
internal static class Program
|
|
{
|
|
static int Main( string[] args )
|
|
{
|
|
if ( args.Length < 1 )
|
|
{
|
|
Console.Error.WriteLine( "Usage: WhitelistGen \"<Assembly>/<Pattern>\" [--dll <path>] [--all]" );
|
|
Console.Error.WriteLine( " e.g. WhitelistGen \"System.Private.CoreLib/System.Memory*\"" );
|
|
Console.Error.WriteLine( " --dll load a specific assembly instead of resolving from the runtime dir" );
|
|
Console.Error.WriteLine( " --all include non-public types/methods (default: public + protected only)" );
|
|
return 1;
|
|
}
|
|
|
|
var pattern = args[0];
|
|
string dllOverride = null;
|
|
var includeNonPublic = false;
|
|
|
|
for ( var i = 1; i < args.Length; i++ )
|
|
{
|
|
if ( args[i] == "--dll" && i + 1 < args.Length ) dllOverride = args[++i];
|
|
else if ( args[i] == "--all" ) includeNonPublic = true;
|
|
}
|
|
|
|
var slash = pattern.IndexOf( '/' );
|
|
if ( slash <= 0 )
|
|
{
|
|
Console.Error.WriteLine( "Pattern must be '<Assembly>/<Pattern>', e.g. System.Private.CoreLib/System.Memory*" );
|
|
return 1;
|
|
}
|
|
|
|
var asmName = pattern.Substring( 0, slash );
|
|
var runtimeDir = RuntimeEnvironment.GetRuntimeDirectory();
|
|
var dllPath = dllOverride ?? Path.Combine( runtimeDir, asmName + ".dll" );
|
|
|
|
if ( !File.Exists( dllPath ) )
|
|
{
|
|
Console.Error.WriteLine( $"Couldn't find '{dllPath}'. Pass --dll <path> to point at the assembly." );
|
|
return 1;
|
|
}
|
|
|
|
// Same wildcard -> regex translation as AccessRules.AddRule.
|
|
var regex = new Regex( "^" + Regex.Escape( pattern ).Replace( "\\*", ".*" ) + "$", RegexOptions.Compiled );
|
|
|
|
var resolver = new DefaultAssemblyResolver();
|
|
resolver.AddSearchDirectory( runtimeDir );
|
|
if ( dllOverride != null ) resolver.AddSearchDirectory( Path.GetDirectoryName( Path.GetFullPath( dllPath ) ) );
|
|
|
|
using var assembly = AssemblyDefinition.ReadAssembly( dllPath, new ReaderParameters { AssemblyResolver = resolver } );
|
|
|
|
var lines = new SortedSet<string>( StringComparer.Ordinal );
|
|
|
|
foreach ( var type in AllTypes( assembly.MainModule.Types ) )
|
|
{
|
|
if ( !includeNonPublic && !IsTypeVisible( type ) )
|
|
continue;
|
|
|
|
var typeLine = AccessSignature.Type( type );
|
|
if ( regex.IsMatch( typeLine ) )
|
|
lines.Add( typeLine );
|
|
|
|
foreach ( var method in type.Methods )
|
|
{
|
|
if ( !includeNonPublic && !IsMethodVisible( method ) )
|
|
continue;
|
|
|
|
var methodLine = AccessSignature.Method( method );
|
|
if ( regex.IsMatch( methodLine ) )
|
|
lines.Add( methodLine );
|
|
}
|
|
}
|
|
|
|
foreach ( var line in lines )
|
|
Console.WriteLine( $"\t\t\t\"{line}\"," );
|
|
|
|
Console.Error.WriteLine( $"// {lines.Count} entries matched \"{pattern}\"" );
|
|
return 0;
|
|
}
|
|
|
|
static IEnumerable<TypeDefinition> AllTypes( IEnumerable<TypeDefinition> types )
|
|
{
|
|
foreach ( var type in types )
|
|
{
|
|
yield return type;
|
|
|
|
if ( type.HasNestedTypes )
|
|
{
|
|
foreach ( var nested in AllTypes( type.NestedTypes ) )
|
|
yield return nested;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sandboxed code can only reference public (and, for types it derives from, protected) members,
|
|
// so those are the only ones the whitelist ever needs to authorize.
|
|
static bool IsTypeVisible( TypeDefinition t )
|
|
=> t.IsPublic || t.IsNestedPublic || t.IsNestedFamily || t.IsNestedFamilyOrAssembly;
|
|
|
|
static bool IsMethodVisible( MethodDefinition m )
|
|
=> m.IsPublic || m.IsFamily || m.IsFamilyOrAssembly;
|
|
}
|