Harden CodeOps against reflection-based whitelist bypass - #3643
Open
eanz17 wants to merge 8 commits into
Open
Conversation
The contract auditor's whitelist is a static IL member-reference scan. Because System.Type was whitelisted with all members allowed, contract code could use reflection (Type.GetType(string) + Type.InvokeMember(...)) to dispatch calls dynamically, reaching code paths that load a second assembly at runtime which is never submitted to the auditor. String-based reflection leaves no static type reference for the whitelist to catch, so this evaded static validation. Add two independent layers of defense: - Whitelist: System.Type is no longer fully allowed. Only Type.GetTypeFromHandle is permitted (what typeof(x) lowers to; used by protobuf-generated descriptor code). Type.GetType(string) / InvokeMember / GetMethod(s) / GetField(s) / GetProperty(ies) / MakeGenericType / ... are now denied. Bare Type references and typeof(x) still pass. - New ReflectionValidator (IValidator<MethodDefinition>): denies the dynamic dispatch and dynamic code-loading/generation surface by (declaring type, method): System.Type reflection methods, Activator, Assembly, MethodBase/MethodInfo/ConstructorInfo/FieldInfo/PropertyInfo, AssemblyLoadContext, Reflection.Emit.*, Marshal, Delegate.DynamicInvoke and Expression.Compile. typeof(x), obj.GetType() (Object.GetType) and RuntimeHelpers.InitializeArray remain allowed. Auto-registered via ITransientDependency like the other method validators. Add ReflectionValidatorTests covering both the rejected reflection patterns and the allowed typeof() / obj.GetType() cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…list Follow-up hardening layered on the reflection guard: - The whitelist now validates method parameter types and generic arguments (previously only declaring type, name and return type were checked) and method local-variable types. This closes the blind spots where a denied type is used only as a parameter (e.g. BindingFlags on Type.InvokeMember) or as a local. - System.Type: additionally allow op_Equality / op_Inequality so typeof(a) == typeof(b) keeps working under the tightened Type rule. - Whitelist System.RuntimeTypeHandle / RuntimeFieldHandle / RuntimeMethodHandle as bare type references. Required now that parameter/local types are validated: typeof(x) lowers to Type.GetTypeFromHandle(RuntimeTypeHandle) and hardcoded array initialization to RuntimeHelpers.InitializeArray(Array, RuntimeFieldHandle). - Downgrade System.Linq.Expressions from Trust.Full to Trust.Partial: Expression.Compile() is runtime code generation. LINQ-to-objects (System.Linq) is unaffected. Add WhitelistReflectionHardeningTests covering the still-allowed patterns (typeof, type equality, hardcoded array init) and the newly-rejected ones (reflection dispatch, denied parameter type). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Security (verified against a compiled-IL harness running these validators):
- WhitelistValidator scanned only top-level types and their immediate
NestedTypes; depth-2+ nested types were completely invisible, so
typeof(Assembly) + ((IReflect)t).InvokeMember("Load", ...) hidden two
levels down re-opened the runtime Assembly.Load(byte[]) bypass even with
System.Type locked down. Scan module.GetAllTypes() instead.
- ReflectionValidator matched System.Type.InvokeMember only by exact
declaring-type name; ban System.Reflection.IReflect, System.Reflection.Binder
and System.Runtime.InteropServices._Type (all expose InvokeMember), plus
Delegate.CreateDelegate.
- Full-trust early return ran before generic decomposition, so a trusted
container (RepeatedField<T>, List<T>, ...) laundered a denied type argument.
Unwrap byref/array/generic-instance first; apply trust to leaf types only.
- Own-method parameter/return types were never scanned (the previous test
passed only because Assembly has op_Equality). Scan the declared signature;
regression test now uses Binder + 'is null', which emits no call.
- CSharpContractAuditor ran ACS validation (Activator.CreateInstance +
BindService invoke = executing untrusted code in the node process) before
checking static findings. Reject on static findings first.
Compatibility (fixes the CI corpus breakage):
- Skip parameter scanning for compiler-generated delegate constructors
(object, IntPtr): every lambda in every contract references one, which
produced ~17k 'IntPtr is not allowed' false positives. No hole: function
pointers only enter via ldftn/ldvirtftn, whose operand is validated.
- Allow System.Linq.Expressions namespace for tree construction/inspection;
Compile/CompileToMethod remain banned by ReflectionValidator.
- Allow Delegate.Combine/Remove/op_Equality/op_Inequality for events.
Tests: depth-2 IReflect rejection (whitelist + reflection validators),
own-signature param/return rejection, generic-container laundering rejection,
lambda/LINQ and safe expression-tree compatibility, Delegate.CreateDelegate.
Collaborator
Author
|
Revision pushed (ee1a2e5) after an adversarial review of the previous head. All findings below were reproduced against a Mono.Cecil/Roslyn harness running the validators verbatim, then re-verified fixed. Residual bypasses closed
Compatibility (the CI breakage)
Not addressed here (needs its own change): already-deployed contracts are loaded by |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #3643 +/- ##
==========================================
- Coverage 90.62% 90.56% -0.07%
==========================================
Files 680 686 +6
Lines 26298 26507 +209
Branches 2369 2390 +21
==========================================
+ Hits 23833 24005 +172
- Misses 2350 2381 +31
- Partials 115 121 +6
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens the C# contract auditor (
AElf.CSharp.CodeOps) against a reflection-based whitelist bypass and closes the adjacent static-analysis blind spots that made it possible.The whitelist validator is a static IL member-reference scan. Because
System.Typewas whitelisted with all members allowed, contract code could use reflection —Type.GetType(string)+Type.InvokeMember(...)— to dispatch calls dynamically. String-based reflection leaves no static type reference for the whitelist to catch, so a contract could reach code paths (e.g. dynamic assembly loading) that let a second assembly execute at runtime without ever being submitted to the auditor — a sandbox-escape primitive.Changes
1. Whitelist:
System.Typeis no longer fully allowed (IWhitelistProvider.cs)Only
GetTypeFromHandle(whattypeof(x)lowers to; used by protobuf-generated descriptor code) andop_Equality/op_Inequality(sotypeof(a) == typeof(b)still works) are permitted.GetType(string)/InvokeMember/GetMethod(s)/GetField(s)/GetProperty(ies)/MakeGenericType/ … are denied.2. New
ReflectionValidator(Validators/Method/ReflectionValidator.cs)IValidator<MethodDefinition>, auto-registered viaITransientDependency. Denies the dynamic-dispatch / dynamic code-loading & generation surface by(declaring type, method):System.Typereflection methods,Activator,Assembly,MethodBase/MethodInfo/ConstructorInfo/FieldInfo/PropertyInfo,AssemblyLoadContext,Reflection.Emit.*,Marshal,Delegate.DynamicInvoke,System.Linq.Expressions.*.Compile. Still allowed:typeof(x),obj.GetType()(→Object.GetType),RuntimeHelpers.InitializeArray.3. Whitelist now validates parameter, generic-argument and local-variable types (
IWhitelistValidator.cs)Previously only declaring type, method name and return type were checked, so a denied type used only as a parameter (e.g.
BindingFlagsonType.InvokeMember) or a local slipped through.ValidateReferenceshort-circuits for fully-trusted and generic-parameter types, so this only tightens the untrusted surface.4. Compatibility additions (required by #3)
Whitelist
System.RuntimeTypeHandle/RuntimeFieldHandle/RuntimeMethodHandleas bare type references —typeof(x)lowers toGetTypeFromHandle(RuntimeTypeHandle)and hardcoded array init toRuntimeHelpers.InitializeArray(Array, RuntimeFieldHandle); without this, parameter validation would reject both in every contract.5.
System.Linq.Expressionsdowngraded from Trust.Full to Trust.PartialExpression.Compile()is a runtime code generator. LINQ-to-objects (System.Linq) is unaffected.Compatibility
ContractAuditorTestscorpus: Token, Genesis, AEDPoS, Election, Parliament, Association, …) uses no reflection dispatch and no hand-writtentypeof; generated protobuf reflection usesSystem.Typeonly viatypeof(...).obj.GetType()keeps working (declaring type isSystem.Object, notSystem.Type).typeof(a)==typeof(b), hardcoded array init,checkedarithmetic, LINQ generic methods and string interpolation all still pass; reflection dispatch,BindingFlags/Assemblyparameter/local types are rejected.Testing
ReflectionValidatorTestsandWhitelistReflectionHardeningTests(AElf.CSharp.CodeOps.UnitTests).AElf.CSharp.CodeOps.Tests+...UnitTests) to confirm every existing system/user contract still passes audit — that regression is the gate for the parameter/local-type validation.Notes
This PR scopes to the static-analysis layers. Further runtime-containment hardening is tracked separately.
🤖 Generated with Claude Code