review(Core.Scripting): block Unsafe.As sandbox bypass (Security)

Re-review at 7286d320. Core.Scripting-017 (Medium, Security): System.Runtime.CompilerServices.Unsafe
added to ForbiddenFullTypeNames (Unsafe.As bypasses the type system without an unsafe context;
CWE-843 type-confusion into SetVirtualTag) + regression tests (rejects Unsafe.As, still allows
benign CompilerServices attributes). -018: refresh stale rejection message. Sandbox holds.
This commit is contained in:
Joseph Doherty
2026-06-19 11:06:56 -04:00
parent 65e6af6001
commit 38c48a009c
3 changed files with 174 additions and 10 deletions
@@ -82,11 +82,11 @@ public static class ForbiddenTypeAnalyzer
/// <summary>
/// Fully-qualified type names scripts are NOT allowed to reference, regardless of
/// namespace. These types live directly in the allow-listed <c>System</c>
/// namespace (in <c>System.Private.CoreLib</c>), so a namespace-prefix rule cannot
/// reach them without also blocking primitives. Matched by exact fully-qualified
/// name against the resolved <em>type</em> symbol — every member of the type
/// (including read-only ones) is therefore rejected.
/// namespace. Used when a dangerous type shares its namespace with legitimate types
/// (e.g. <c>System.Threading</c> hosts both <c>CancellationToken</c> and
/// <c>Thread</c>) so a namespace-prefix rule would over-block. Matched by exact
/// fully-qualified name against the resolved <em>type</em> symbol — every member of
/// the type (including read-only ones) is therefore rejected.
/// </summary>
/// <remarks>
/// <list type="bullet">
@@ -105,6 +105,14 @@ public static class ForbiddenTypeAnalyzer
/// namespace is <c>System.Threading</c> (shared with allowed types like
/// <c>CancellationToken</c>), so a namespace-prefix rule cannot reach it
/// without blocking unrelated types. (Core.Scripting-010.)</item>
/// <item><c>System.Threading.ThreadPool</c> / <c>System.Threading.Timer</c> —
/// background-work vectors that outlive the per-evaluation timeout; same
/// threat as <c>Task.Run</c> that Core.Scripting-003 closed. (Core.Scripting-012.)</item>
/// <item><c>System.Runtime.CompilerServices.Unsafe</c> — exposes raw type-
/// reinterpretation (<c>As&lt;TFrom,TTo&gt;</c>, <c>As&lt;T&gt;(object)</c>)
/// that bypasses the CLR type system without requiring an <c>unsafe</c> context at
/// the call site; enables type-confusion and managed heap corruption.
/// (Core.Scripting-017.)</item>
/// </list>
/// </remarks>
public static readonly IReadOnlyList<string> ForbiddenFullTypeNames =
@@ -136,6 +144,15 @@ public static class ForbiddenTypeAnalyzer
// namespace are denied by default.
"System.Threading.ThreadPool",
"System.Threading.Timer",
// Core.Scripting-017 — System.Runtime.CompilerServices.Unsafe exposes raw
// type-reinterpretation (Unsafe.As<TFrom, TTo>, Unsafe.As<T>(object)) that
// bypasses the CLR type system without requiring an 'unsafe' compilation context
// at the call site. In a script, calling Unsafe.As<string>(someObject) can corrupt
// managed heap references and cause type-confusion in downstream virtual-tag
// consumers. Type-granular (not namespace-prefix) because System.Runtime.CompilerServices
// also contains harmless compile-time-only types (CallerMemberNameAttribute,
// MethodImplAttribute, MethodImplOptions) that scripts may legitimately reference.
"System.Runtime.CompilerServices.Unsafe",
];
/// <summary>
@@ -296,9 +313,8 @@ public static class ForbiddenTypeAnalyzer
TypeName: typeSymbol.ToDisplayString(),
Namespace: ns,
Message: $"Type '{forbiddenType}' is on the Phase 7 sandbox forbidden-type " +
$"deny-list. Scripts cannot reach process-control types " +
$"(Environment / AppDomain / GC / Activator) even though they " +
$"live in the allowed 'System' namespace."));
$"deny-list. Scripts cannot reach this type per sandbox rules " +
$"(see ForbiddenTypeAnalyzer.ForbiddenFullTypeNames for the authoritative list)."));
return;
}
}