Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/MetadataValidation/SystemReflectionMetadataPreloadValidator.cs

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);
}
}
}
}