356 lines
14 KiB
C#
356 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.IO;
|
|
using System.Reflection.Metadata;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Reflection.PortableExecutable;
|
|
|
|
namespace io.github.hatayama.uLoopMCP
|
|
{
|
|
/// <summary>
|
|
/// Reads ECMA-335 metadata before Assembly.Load so load-time side effects never run before strict validation.
|
|
/// </summary>
|
|
public sealed class SystemReflectionMetadataPreloadValidator : IPreloadAssemblySecurityValidator
|
|
{
|
|
private const string ModuleInitializerAttributeName = "System.Runtime.CompilerServices.ModuleInitializerAttribute";
|
|
|
|
public SecurityValidationResult Validate(byte[] assemblyBytes)
|
|
{
|
|
SecurityValidationResult result = new SecurityValidationResult
|
|
{
|
|
IsValid = true,
|
|
Violations = new List<SecurityViolation>()
|
|
};
|
|
|
|
HashSet<string> seenViolations = new HashSet<string>(System.StringComparer.Ordinal);
|
|
|
|
using MemoryStream stream = new MemoryStream(assemblyBytes, writable: false);
|
|
using PEReader peReader = new PEReader(stream);
|
|
MetadataReader reader = peReader.GetMetadataReader();
|
|
|
|
this.ValidateTypeReferences(reader, result, seenViolations);
|
|
this.ValidateTypeDefinitions(reader, result, seenViolations);
|
|
this.ValidateMemberReferences(reader, result, seenViolations);
|
|
this.ValidateCustomAttributes(reader, result, seenViolations);
|
|
|
|
result.IsValid = result.Violations.Count == 0;
|
|
return result;
|
|
}
|
|
|
|
private void ValidateTypeReferences(
|
|
MetadataReader reader,
|
|
SecurityValidationResult result,
|
|
HashSet<string> seenViolations)
|
|
{
|
|
foreach (TypeReferenceHandle handle in reader.TypeReferences)
|
|
{
|
|
string typeName = this.GetTypeReferenceFullName(reader, handle);
|
|
if (!DangerousApiCatalog.IsDangerousType(typeName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.AddViolation(
|
|
result,
|
|
seenViolations,
|
|
typeName,
|
|
$"Dangerous metadata reference detected before assembly load: {typeName}",
|
|
$"Metadata TypeReference contains '{typeName}'");
|
|
}
|
|
}
|
|
|
|
private void ValidateTypeDefinitions(
|
|
MetadataReader reader,
|
|
SecurityValidationResult result,
|
|
HashSet<string> seenViolations)
|
|
{
|
|
foreach (TypeDefinitionHandle handle in reader.TypeDefinitions)
|
|
{
|
|
TypeDefinition typeDefinition = reader.GetTypeDefinition(handle);
|
|
string declaringTypeName = this.GetTypeDefinitionFullName(reader, handle);
|
|
|
|
if (!typeDefinition.BaseType.IsNil)
|
|
{
|
|
string baseTypeName = this.GetEntityTypeName(reader, typeDefinition.BaseType);
|
|
if (DangerousApiCatalog.IsDangerousType(baseTypeName))
|
|
{
|
|
this.AddViolation(
|
|
result,
|
|
seenViolations,
|
|
baseTypeName,
|
|
$"Dangerous metadata inheritance detected before assembly load: {baseTypeName}",
|
|
$"Metadata base type '{baseTypeName}' exists on '{declaringTypeName}'");
|
|
}
|
|
}
|
|
|
|
foreach (InterfaceImplementationHandle interfaceHandle in typeDefinition.GetInterfaceImplementations())
|
|
{
|
|
InterfaceImplementation interfaceImplementation = reader.GetInterfaceImplementation(interfaceHandle);
|
|
string interfaceTypeName = this.GetEntityTypeName(reader, interfaceImplementation.Interface);
|
|
if (!DangerousApiCatalog.IsDangerousType(interfaceTypeName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.AddViolation(
|
|
result,
|
|
seenViolations,
|
|
interfaceTypeName,
|
|
$"Dangerous metadata interface detected before assembly load: {interfaceTypeName}",
|
|
$"Metadata interface '{interfaceTypeName}' exists on '{declaringTypeName}'");
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ValidateMemberReferences(
|
|
MetadataReader reader,
|
|
SecurityValidationResult result,
|
|
HashSet<string> seenViolations)
|
|
{
|
|
foreach (MemberReferenceHandle handle in reader.MemberReferences)
|
|
{
|
|
MemberReference memberReference = reader.GetMemberReference(handle);
|
|
string parentTypeName = this.GetMemberReferenceParentTypeName(reader, memberReference.Parent);
|
|
string memberName = reader.GetString(memberReference.Name);
|
|
|
|
if (!DangerousApiCatalog.IsDangerousApi(parentTypeName, memberName))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
string apiName = $"{parentTypeName}.{memberName}";
|
|
this.AddViolation(
|
|
result,
|
|
seenViolations,
|
|
apiName,
|
|
$"Dangerous metadata member detected before assembly load: {apiName}",
|
|
$"Metadata MemberReference contains '{apiName}'");
|
|
}
|
|
}
|
|
|
|
private void ValidateCustomAttributes(
|
|
MetadataReader reader,
|
|
SecurityValidationResult result,
|
|
HashSet<string> seenViolations)
|
|
{
|
|
foreach (CustomAttributeHandle handle in reader.CustomAttributes)
|
|
{
|
|
CustomAttribute attribute = reader.GetCustomAttribute(handle);
|
|
string attributeTypeName = this.GetCustomAttributeTypeName(reader, attribute.Constructor);
|
|
if (attributeTypeName != ModuleInitializerAttributeName)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
this.AddViolation(
|
|
result,
|
|
seenViolations,
|
|
attributeTypeName,
|
|
$"Dangerous metadata attribute detected before assembly load: {attributeTypeName}",
|
|
$"Metadata CustomAttribute contains '{attributeTypeName}'");
|
|
}
|
|
}
|
|
|
|
private string GetCustomAttributeTypeName(MetadataReader reader, EntityHandle constructorHandle)
|
|
{
|
|
if (constructorHandle.Kind == HandleKind.MemberReference)
|
|
{
|
|
MemberReference memberReference = reader.GetMemberReference((MemberReferenceHandle)constructorHandle);
|
|
return this.GetMemberReferenceParentTypeName(reader, memberReference.Parent);
|
|
}
|
|
|
|
if (constructorHandle.Kind == HandleKind.MethodDefinition)
|
|
{
|
|
MethodDefinition methodDefinition = reader.GetMethodDefinition((MethodDefinitionHandle)constructorHandle);
|
|
return this.GetTypeDefinitionFullName(reader, methodDefinition.GetDeclaringType());
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private string GetMemberReferenceParentTypeName(MetadataReader reader, EntityHandle parentHandle)
|
|
{
|
|
if (parentHandle.Kind == HandleKind.TypeReference ||
|
|
parentHandle.Kind == HandleKind.TypeDefinition ||
|
|
parentHandle.Kind == HandleKind.TypeSpecification)
|
|
{
|
|
return this.GetEntityTypeName(reader, parentHandle);
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private string GetEntityTypeName(MetadataReader reader, EntityHandle handle)
|
|
{
|
|
if (handle.Kind == HandleKind.TypeReference)
|
|
{
|
|
return this.GetTypeReferenceFullName(reader, (TypeReferenceHandle)handle);
|
|
}
|
|
|
|
if (handle.Kind == HandleKind.TypeDefinition)
|
|
{
|
|
return this.GetTypeDefinitionFullName(reader, (TypeDefinitionHandle)handle);
|
|
}
|
|
|
|
if (handle.Kind == HandleKind.TypeSpecification)
|
|
{
|
|
return this.DecodeTypeSpecification(reader, (TypeSpecificationHandle)handle);
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private string GetTypeReferenceFullName(MetadataReader reader, TypeReferenceHandle handle)
|
|
{
|
|
TypeReference typeReference = reader.GetTypeReference(handle);
|
|
string typeName = reader.GetString(typeReference.Name);
|
|
string namespaceName = reader.GetString(typeReference.Namespace);
|
|
|
|
if (typeReference.ResolutionScope.Kind == HandleKind.TypeReference)
|
|
{
|
|
string parentTypeName = this.GetTypeReferenceFullName(reader, (TypeReferenceHandle)typeReference.ResolutionScope);
|
|
return $"{parentTypeName}.{typeName}";
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(namespaceName))
|
|
{
|
|
return typeName;
|
|
}
|
|
|
|
return $"{namespaceName}.{typeName}";
|
|
}
|
|
|
|
private string GetTypeDefinitionFullName(MetadataReader reader, TypeDefinitionHandle handle)
|
|
{
|
|
TypeDefinition typeDefinition = reader.GetTypeDefinition(handle);
|
|
string typeName = reader.GetString(typeDefinition.Name);
|
|
TypeDefinitionHandle declaringTypeHandle = typeDefinition.GetDeclaringType();
|
|
|
|
if (!declaringTypeHandle.IsNil)
|
|
{
|
|
string parentTypeName = this.GetTypeDefinitionFullName(reader, declaringTypeHandle);
|
|
return $"{parentTypeName}.{typeName}";
|
|
}
|
|
|
|
string namespaceName = reader.GetString(typeDefinition.Namespace);
|
|
if (string.IsNullOrEmpty(namespaceName))
|
|
{
|
|
return typeName;
|
|
}
|
|
|
|
return $"{namespaceName}.{typeName}";
|
|
}
|
|
|
|
private string DecodeTypeSpecification(MetadataReader reader, TypeSpecificationHandle handle)
|
|
{
|
|
TypeSpecification typeSpecification = reader.GetTypeSpecification(handle);
|
|
MetadataTypeNameDecoder decoder = new MetadataTypeNameDecoder(reader, this);
|
|
return typeSpecification.DecodeSignature(decoder, genericContext: null);
|
|
}
|
|
|
|
private void AddViolation(
|
|
SecurityValidationResult result,
|
|
HashSet<string> seenViolations,
|
|
string apiName,
|
|
string message,
|
|
string description)
|
|
{
|
|
if (!seenViolations.Add($"{apiName}|{message}"))
|
|
{
|
|
return;
|
|
}
|
|
|
|
result.Violations.Add(new SecurityViolation
|
|
{
|
|
Type = SecurityViolationType.DangerousApiCall,
|
|
ApiName = apiName,
|
|
Message = message,
|
|
Description = description,
|
|
Location = "metadata"
|
|
});
|
|
}
|
|
|
|
private sealed class MetadataTypeNameDecoder : ISignatureTypeProvider<string, object>
|
|
{
|
|
private readonly MetadataReader _reader;
|
|
private readonly SystemReflectionMetadataPreloadValidator _validator;
|
|
|
|
public MetadataTypeNameDecoder(MetadataReader reader, SystemReflectionMetadataPreloadValidator validator)
|
|
{
|
|
_reader = reader;
|
|
_validator = validator;
|
|
}
|
|
|
|
public string GetArrayType(string elementType, ArrayShape shape)
|
|
{
|
|
return elementType;
|
|
}
|
|
|
|
public string GetByReferenceType(string elementType)
|
|
{
|
|
return elementType;
|
|
}
|
|
|
|
public string GetFunctionPointerType(MethodSignature<string> signature)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
public string GetGenericInstantiation(string genericType, ImmutableArray<string> typeArguments)
|
|
{
|
|
return genericType;
|
|
}
|
|
|
|
public string GetGenericMethodParameter(object genericContext, int index)
|
|
{
|
|
return $"!!{index}";
|
|
}
|
|
|
|
public string GetGenericTypeParameter(object genericContext, int index)
|
|
{
|
|
return $"!{index}";
|
|
}
|
|
|
|
public string GetModifiedType(string modifierType, string unmodifiedType, bool isRequired)
|
|
{
|
|
return unmodifiedType;
|
|
}
|
|
|
|
public string GetPinnedType(string elementType)
|
|
{
|
|
return elementType;
|
|
}
|
|
|
|
public string GetPointerType(string elementType)
|
|
{
|
|
return elementType;
|
|
}
|
|
|
|
public string GetPrimitiveType(PrimitiveTypeCode typeCode)
|
|
{
|
|
return typeCode.ToString();
|
|
}
|
|
|
|
public string GetSZArrayType(string elementType)
|
|
{
|
|
return elementType;
|
|
}
|
|
|
|
public string GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
|
|
{
|
|
return _validator.GetTypeDefinitionFullName(reader, handle);
|
|
}
|
|
|
|
public string GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
|
|
{
|
|
return _validator.GetTypeReferenceFullName(reader, handle);
|
|
}
|
|
|
|
public string GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
|
|
{
|
|
return _validator.DecodeTypeSpecification(reader, handle);
|
|
}
|
|
}
|
|
}
|
|
}
|