Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions AElf.All.sln
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Kernel.FeatureDisable.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Kernel.FeatureDisable.Core", "src\AElf.Kernel.FeatureDisable.Core\AElf.Kernel.FeatureDisable.Core.csproj", "{659A7C7A-44C9-424E-B4F6-D1D3656F7AD4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Kernel.BlockPruning", "src\AElf.Kernel.BlockPruning\AElf.Kernel.BlockPruning.csproj", "{651281CD-F268-49FC-9305-B19EE65552F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AElf.Kernel.BlockPruning.Tests", "test\AElf.Kernel.BlockPruning.Tests\AElf.Kernel.BlockPruning.Tests.csproj", "{147343C3-BFDF-4C20-A990-975BB82CB73B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1107,6 +1111,14 @@ Global
{659A7C7A-44C9-424E-B4F6-D1D3656F7AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{659A7C7A-44C9-424E-B4F6-D1D3656F7AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{659A7C7A-44C9-424E-B4F6-D1D3656F7AD4}.Release|Any CPU.Build.0 = Release|Any CPU
{651281CD-F268-49FC-9305-B19EE65552F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{651281CD-F268-49FC-9305-B19EE65552F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{651281CD-F268-49FC-9305-B19EE65552F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{651281CD-F268-49FC-9305-B19EE65552F7}.Release|Any CPU.Build.0 = Release|Any CPU
{147343C3-BFDF-4C20-A990-975BB82CB73B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{147343C3-BFDF-4C20-A990-975BB82CB73B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{147343C3-BFDF-4C20-A990-975BB82CB73B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{147343C3-BFDF-4C20-A990-975BB82CB73B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1301,5 +1313,7 @@ Global
{A4ACE6D2-4CF8-4B52-93C9-BB8BEC0C098E} = {90B310B4-C2DB-419E-B5EE-97FA096B62CC}
{8C0D86A4-D1A7-4B61-AC44-755F5AC75D67} = {4E54480A-D155-43ED-9736-1A5BE7957211}
{659A7C7A-44C9-424E-B4F6-D1D3656F7AD4} = {90B310B4-C2DB-419E-B5EE-97FA096B62CC}
{651281CD-F268-49FC-9305-B19EE65552F7} = {90B310B4-C2DB-419E-B5EE-97FA096B62CC}
{147343C3-BFDF-4C20-A990-975BB82CB73B} = {4E54480A-D155-43ED-9736-1A5BE7957211}
EndGlobalSection
EndGlobal
9 changes: 9 additions & 0 deletions protobuf/block_pruning.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package aelf;

option csharp_namespace = "AElf.Kernel.BlockPruning";

message BlockPruningInfo {
int64 last_pruned_block_height = 1;
}
14 changes: 14 additions & 0 deletions src/AElf.CSharp.CodeOps/CSharpContractAuditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ public void Audit(byte[] code, RequiredAcs requiredAcs, bool isSystemContract)
}


// Reject on static findings BEFORE ACS validation. AcsValidator instantiates the
// contract type and invokes its static BindService method, i.e. it executes untrusted
// code inside the node process; if this ran first, a contract whose audit has already
// failed would still get its constructor/BindService executed.
// TODO: A statically CLEAN contract is still executed by AcsValidator today — ACS
// validation should be made metadata-only (read the base list with Mono.Cecil instead
// of Activator.CreateInstance + MethodInfo.Invoke).
if (findings.Count > 0)
{
throw new CSharpCodeCheckException(
$"Contract code did not pass audit. Audit failed for contract: {modDef.Assembly.MainModule.Name}\n" +
string.Join("\n", findings), findings.ToList());
}

// Perform ACS validation
if (requiredAcs != null)
{
Expand Down
23 changes: 17 additions & 6 deletions src/AElf.CSharp.CodeOps/ExecutionObserverProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,13 @@ public class ExecutionObserverProxyChecker
private readonly ModuleDefinition _module;

private TypeDefinition _contractImplementationType;
private bool _contractImplementationTypeInitialized;

private TypeDefinition ContractImplementationType
{
get
{
if (_contractImplementationType == null)
if (!_contractImplementationTypeInitialized)
{
bool BaseTypeIsInTheSameAssembly(TypeDefinition t)
{
Expand All @@ -48,15 +49,24 @@ bool BaseTypeIsInTheSameAssembly(TypeDefinition t)

_contractImplementationType = _module.GetAllTypes()
.Where(t => t.IsContractImplementation())
.First(BaseTypeIsInTheSameAssembly);
.FirstOrDefault(BaseTypeIsInTheSameAssembly);
_contractImplementationTypeInitialized = true;
}

return _contractImplementationType;
}
}

private string ObserverFieldName =>
$"AElf.Kernel.SmartContract.IExecutionObserver {ContractImplementationType.Namespace}.{nameof(ExecutionObserverProxy)}::_observer";
private string ObserverFieldName
{
get
{
var contractImplementationType = ContractImplementationType;
return contractImplementationType == null
? null
: $"AElf.Kernel.SmartContract.IExecutionObserver {contractImplementationType.Namespace}.{nameof(ExecutionObserverProxy)}::_observer";
}
}

public ExecutionObserverProxyChecker(ModuleDefinition module)
{
Expand All @@ -65,6 +75,7 @@ public ExecutionObserverProxyChecker(ModuleDefinition module)

public bool IsObserverFieldThatRequiresResetting(FieldDefinition field)
{
return field.FullName == ObserverFieldName;
var observerFieldName = ObserverFieldName;
return observerFieldName != null && field.FullName == observerFieldName;
}
}
}
166 changes: 166 additions & 0 deletions src/AElf.CSharp.CodeOps/Validators/Method/ReflectionValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Volo.Abp.DependencyInjection;

namespace AElf.CSharp.CodeOps.Validators.Method;

/// <summary>
/// Denies the dynamic dispatch / dynamic code-loading API surface.
///
/// The whitelist validator only inspects statically-typed member references and cannot see through
/// reflection: a contract that calls <c>Type.GetType("System.Reflection.Assembly")</c> followed by
/// <c>Type.InvokeMember("Load", ...)</c> reaches <c>Assembly.Load(byte[])</c> without ever emitting a
/// static reference to a forbidden type (the type and member names are plain string literals). The
/// second-stage assembly loaded this way is never audited and runs with full trust inside the node
/// process, which is enough to walk the host object graph by reflection and exfiltrate the node's
/// signing key.
///
/// This validator closes that hole by rejecting the reflection-invocation, activation and
/// assembly-loading methods by (declaring type, method name), independent of the whitelist. It is
/// intentionally narrow: it targets only the methods that enable dynamic dispatch or dynamic code
/// loading, so legitimate contract code (including <c>typeof(...)</c>, which lowers to
/// <c>Type.GetTypeFromHandle</c>) is unaffected.
/// </summary>
public class ReflectionValidator : IValidator<MethodDefinition>, ITransientDependency
{
public bool SystemContactIgnored => false;

// Methods on System.Type that perform dynamic member lookup or invocation.
// Note: typeof(x) => Type.GetTypeFromHandle, and obj.GetType() => Object.GetType, neither of which
// is listed here, so both remain allowed.
private static readonly HashSet<string> TypeReflectionMethods = new()
{
"GetType", // the static Type.GetType(string) overloads (instance Object.GetType has a different declaring type)
"InvokeMember",
"GetMethod", "GetMethods",
"GetField", "GetFields",
"GetProperty", "GetProperties",
"GetConstructor", "GetConstructors",
"GetMember", "GetMembers",
"GetEvent", "GetEvents",
"GetNestedType", "GetNestedTypes",
"GetInterface", "GetInterfaceMap",
"MakeGenericType", "MakeArrayType", "MakePointerType", "MakeByRefType",
"GetTypeFromProgID", "GetTypeFromCLSID"
};

// Fully-qualified declaring types whose *every* method is a dynamic dispatch / load / marshal /
// codegen primitive. Any call into these is rejected.
private static readonly HashSet<string> BannedDeclaringTypes = new()
{
"System.Activator",
"System.AppDomain",
"System.Reflection.Assembly",
"System.Reflection.MethodBase",
"System.Reflection.MethodInfo",
"System.Reflection.ConstructorInfo",
"System.Reflection.FieldInfo",
"System.Reflection.PropertyInfo",
"System.Reflection.EventInfo",
"System.Reflection.MemberInfo",
"System.Reflection.Module",
// IReflect is the reflection-dispatch interface System.Type implements: a cast
// ((IReflect)typeof(X)).InvokeMember(...) reaches the same dynamic dispatch as
// Type.InvokeMember while declaring the call on a type this validator did not match.
"System.Reflection.IReflect",
// Custom binders steer overload resolution for dynamic invocation.
"System.Reflection.Binder",
// The COM-facing interface System.Type also implements; it exposes InvokeMember too.
"System.Runtime.InteropServices._Type",
"System.Reflection.Emit.ILGenerator",
"System.Reflection.Emit.MethodBuilder",
"System.Reflection.Emit.TypeBuilder",
"System.Reflection.Emit.AssemblyBuilder",
"System.Reflection.Emit.DynamicMethod",
"System.Runtime.Loader.AssemblyLoadContext",
"System.Runtime.InteropServices.Marshal",
"System.Runtime.InteropServices.NativeLibrary",
"System.Runtime.CompilerServices.RuntimeHelpers" // GetUninitializedObject etc. (InitializeArray is on this
// type but is whitelisted elsewhere and handled by
// ArrayValidator; see MethodIsBanned).
};

// Specific (declaringType, method) pairs to ban without banning the whole declaring type.
private static readonly HashSet<string> BannedMethods = new()
{
"System.Delegate::DynamicInvoke",
"System.Delegate::CreateDelegate" // dynamic delegate construction = dynamic dispatch
};

// Expression-tree compilation is runtime code generation. Compile()/CompileToMethod() may be
// declared on LambdaExpression or on the generic Expression`1<TDelegate>, so match by namespace.
private static bool IsExpressionCompile(MethodReference called)
{
return (called.Name == "Compile" || called.Name == "CompileToMethod")
&& (called.DeclaringType?.FullName?.StartsWith("System.Linq.Expressions.") ?? false);
}

// RuntimeHelpers members that ARE allowed (used by hardcoded array initialization).
private static readonly HashSet<string> AllowedRuntimeHelpersMembers = new()
{
"InitializeArray"
};

public IEnumerable<ValidationResult> Validate(MethodDefinition method, CancellationToken ct)
{
if (ct.IsCancellationRequested)
throw new ContractAuditTimeoutException();

if (!method.HasBody)
return Enumerable.Empty<ValidationResult>();

var errors = new List<ValidationResult>();

foreach (var instruction in method.Body.Instructions)
{
if (!(instruction.Operand is MethodReference called))
continue;

if (!MethodIsBanned(called))
continue;

errors.Add(new ReflectionValidationResult(
$"Usage of reflection/dynamic code API is not allowed: {called.DeclaringType?.FullName}.{called.Name}")
.WithInfo(method.Name, method.DeclaringType.Namespace, method.DeclaringType.Name, called.Name));
}

return errors;
}

private static bool MethodIsBanned(MethodReference called)
{
var declaringType = called.DeclaringType;
if (declaringType == null)
return false;

var declaringFullName = declaringType.FullName;

// RuntimeHelpers: allow only the explicitly-permitted members (e.g. InitializeArray), ban the rest.
if (declaringFullName == "System.Runtime.CompilerServices.RuntimeHelpers")
return !AllowedRuntimeHelpersMembers.Contains(called.Name);

if (BannedDeclaringTypes.Contains(declaringFullName))
return true;

if (declaringFullName == "System.Type" && TypeReflectionMethods.Contains(called.Name))
return true;

if (BannedMethods.Contains($"{declaringFullName}::{called.Name}"))
return true;

if (IsExpressionCompile(called))
return true;

return false;
}
}

public class ReflectionValidationResult : ValidationResult
{
public ReflectionValidationResult(string message) : base(message)
{
}
}
32 changes: 30 additions & 2 deletions src/AElf.CSharp.CodeOps/Validators/Whitelist/IWhitelistProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ private void WhitelistAssemblies(Whitelist whitelist)
.Assembly(System.Reflection.Assembly.Load("System.ObjectModel"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.Text.RegularExpressions"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.Linq"), Trust.Full)
.Assembly(System.Reflection.Assembly.Load("System.Linq.Expressions"), Trust.Full)
// System.Linq.Expressions is intentionally NOT fully trusted: Expression.Compile() /
// CompileToMethod() are runtime code generators (a sandbox-escape primitive). Partial trust
// means its types are validated against the whitelist. The namespace rule below allows
// expression-tree construction/inspection, while ReflectionValidator bans Compile /
// CompileToMethod regardless of the whitelist. LINQ-to-objects lives in System.Linq
// (above) and is unaffected.
.Assembly(System.Reflection.Assembly.Load("System.Linq.Expressions"), Trust.Partial)
.Assembly(System.Reflection.Assembly.Load("System.Collections"), Trust.Full)
.Assembly(System.Reflection.Assembly.Load("Google.Protobuf"), Trust.Full)
.Assembly(typeof(CSharpSmartContract).Assembly, Trust.Full) // AElf.Sdk.CSharp
Expand Down Expand Up @@ -93,10 +99,21 @@ private void WhitelistSystemTypes(Whitelist whitelist)
.Member(nameof(DateTime.Today), Permission.Denied))
.Type(typeof(void).Name, Permission.Allowed)
.Type(nameof(Object), Permission.Allowed)
// Reflection and dynamic-code capabilities are checked at their actual call sites by
// ReflectionValidator. Keep Type itself compatible with existing generated and deployed
// contracts; treating all Type metadata access as a capability rejects safe operations
// such as GetEnumName without adding call-path coverage.
.Type(nameof(Type), Permission.Allowed)
.Type(nameof(IDisposable), Permission.Allowed)
.Type(nameof(Convert), Permission.Allowed)
.Type(nameof(Math), Permission.Allowed)
// Events lower to Delegate.Combine/Remove and delegate equality. Dynamic dispatch
// through delegates (DynamicInvoke, CreateDelegate) is banned by ReflectionValidator.
.Type(nameof(Delegate), Permission.Denied, member => member
.Member(nameof(Delegate.Combine), Permission.Allowed)
.Member(nameof(Delegate.Remove), Permission.Allowed)
.Member("op_Equality", Permission.Allowed)
.Member("op_Inequality", Permission.Allowed))
// Primitive types
.Type(nameof(Boolean), Permission.Allowed)
.Type(nameof(Byte), Permission.Allowed)
Expand All @@ -112,6 +129,12 @@ private void WhitelistSystemTypes(Whitelist whitelist)
.Member(nameof(String.Concat), Permission.Denied)
)
.Type(typeof(Byte[]).Name, Permission.Allowed)
// Opaque runtime handles occur in generated typeof()/array-initialization call shapes.
// Allowing the handle types keeps those shapes compatible; members that expose a raw
// pointer still return IntPtr, which remains denied by method-return validation.
.Type(nameof(RuntimeTypeHandle), Permission.Allowed)
.Type(nameof(RuntimeFieldHandle), Permission.Allowed)
.Type(nameof(RuntimeMethodHandle), Permission.Allowed)
);
}

Expand All @@ -133,6 +156,11 @@ private void WhitelistLinqAndCollections(Whitelist whitelist)
{
whitelist
.Namespace("System.Linq", Permission.Allowed)
// Expression-tree construction and inspection (Expression.Constant/Parameter/Lambda, ...)
// are safe: producing executable code from a tree requires Compile()/CompileToMethod(),
// which ReflectionValidator bans outright, and referencing a member in an expression
// requires a MethodInfo/PropertyInfo, which is unreachable with reflection banned.
.Namespace("System.Linq.Expressions", Permission.Allowed)
.Namespace("System.Collections", Permission.Allowed)
.Namespace("System.Collections.Generic", Permission.Allowed)
.Namespace("System.Collections.ObjectModel", Permission.Allowed)
Expand Down Expand Up @@ -227,4 +255,4 @@ private void WhitelistAElfTypes(Whitelist whitelist)
.Type(typeof(SecretSharingHelper), Permission.Denied, member => member
.Member(nameof(SecretSharingHelper.DecodeSecret), Permission.Allowed)));
}
}
}
Loading
Loading