chore(unity): 固定Unity CLI Loop本地包并移除UnitySkills依赖
This commit is contained in:
@@ -0,0 +1,564 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Reflection.Emit;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads IL directly from PE metadata before Assembly.Load so restricted-mode validation
|
||||
/// never needs to materialize the assembly just to inspect method bodies.
|
||||
/// </summary>
|
||||
public sealed class PreloadIlSecurityValidator : IPreloadAssemblySecurityValidator
|
||||
{
|
||||
private const char DecodedTypeNameSeparator = '|';
|
||||
private static readonly OpCode[] SingleByteOpCodes = BuildOpCodeTable(singleByte: true);
|
||||
private static readonly OpCode[] MultiByteOpCodes = BuildOpCodeTable(singleByte: false);
|
||||
|
||||
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();
|
||||
|
||||
foreach (MethodDefinitionHandle methodHandle in reader.MethodDefinitions)
|
||||
{
|
||||
this.ValidateMethod(peReader, reader, methodHandle, result, seenViolations);
|
||||
}
|
||||
|
||||
result.IsValid = result.Violations.Count == 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ValidateMethod(
|
||||
PEReader peReader,
|
||||
MetadataReader reader,
|
||||
MethodDefinitionHandle methodHandle,
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations)
|
||||
{
|
||||
MethodDefinition methodDefinition = reader.GetMethodDefinition(methodHandle);
|
||||
string declaringTypeName = this.GetTypeDefinitionFullName(reader, methodDefinition.GetDeclaringType());
|
||||
string methodName = reader.GetString(methodDefinition.Name);
|
||||
|
||||
this.ValidateParameterTypes(reader, methodDefinition, declaringTypeName, methodName, result, seenViolations);
|
||||
this.ValidateMethodBody(peReader, reader, methodDefinition, declaringTypeName, methodName, result, seenViolations);
|
||||
}
|
||||
|
||||
private void ValidateParameterTypes(
|
||||
MetadataReader reader,
|
||||
MethodDefinition methodDefinition,
|
||||
string declaringTypeName,
|
||||
string methodName,
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations)
|
||||
{
|
||||
MetadataTypeNameDecoder decoder = new MetadataTypeNameDecoder(reader, this);
|
||||
MethodSignature<string> signature = methodDefinition.DecodeSignature(decoder, genericContext: null);
|
||||
|
||||
foreach (string parameterTypeName in signature.ParameterTypes)
|
||||
{
|
||||
this.AddDecodedTypeViolations(
|
||||
result,
|
||||
seenViolations,
|
||||
parameterTypeName,
|
||||
dangerousTypeName => $"Dangerous parameter type detected before assembly load: {dangerousTypeName}",
|
||||
dangerousTypeName => $"Parameter type '{dangerousTypeName}' exists in {declaringTypeName}.{methodName}");
|
||||
}
|
||||
|
||||
this.AddDecodedTypeViolations(
|
||||
result,
|
||||
seenViolations,
|
||||
signature.ReturnType,
|
||||
dangerousTypeName => $"Dangerous return type detected before assembly load: {dangerousTypeName}",
|
||||
dangerousTypeName => $"Return type '{dangerousTypeName}' exists in {declaringTypeName}.{methodName}");
|
||||
}
|
||||
|
||||
private void ValidateMethodBody(
|
||||
PEReader peReader,
|
||||
MetadataReader reader,
|
||||
MethodDefinition methodDefinition,
|
||||
string declaringTypeName,
|
||||
string methodName,
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations)
|
||||
{
|
||||
if (methodDefinition.RelativeVirtualAddress == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MethodBodyBlock methodBody = peReader.GetMethodBody(methodDefinition.RelativeVirtualAddress);
|
||||
this.ValidateLocalVariableTypes(reader, methodBody, declaringTypeName, methodName, result, seenViolations);
|
||||
this.ValidateIlInstructions(reader, methodBody, declaringTypeName, methodName, result, seenViolations);
|
||||
}
|
||||
|
||||
private void ValidateLocalVariableTypes(
|
||||
MetadataReader reader,
|
||||
MethodBodyBlock methodBody,
|
||||
string declaringTypeName,
|
||||
string methodName,
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations)
|
||||
{
|
||||
if (methodBody.LocalSignature.IsNil)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StandaloneSignature standaloneSignature = reader.GetStandaloneSignature(methodBody.LocalSignature);
|
||||
MetadataTypeNameDecoder decoder = new MetadataTypeNameDecoder(reader, this);
|
||||
ImmutableArray<string> localTypes = standaloneSignature.DecodeLocalSignature(decoder, genericContext: null);
|
||||
|
||||
foreach (string localTypeName in localTypes)
|
||||
{
|
||||
this.AddDecodedTypeViolations(
|
||||
result,
|
||||
seenViolations,
|
||||
localTypeName,
|
||||
dangerousTypeName => $"Dangerous local variable type detected before assembly load: {dangerousTypeName}",
|
||||
dangerousTypeName => $"Local variable type '{dangerousTypeName}' exists in {declaringTypeName}.{methodName}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateIlInstructions(
|
||||
MetadataReader reader,
|
||||
MethodBodyBlock methodBody,
|
||||
string declaringTypeName,
|
||||
string methodName,
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations)
|
||||
{
|
||||
byte[] ilBytes = methodBody.GetILBytes();
|
||||
int offset = 0;
|
||||
|
||||
while (offset < ilBytes.Length)
|
||||
{
|
||||
OpCode opCode = ReadOpCode(ilBytes, ref offset);
|
||||
|
||||
if (opCode.OperandType == OperandType.InlineMethod)
|
||||
{
|
||||
int token = ReadInt32(ilBytes, offset);
|
||||
EntityHandle methodHandle = MetadataTokens.EntityHandle(token);
|
||||
|
||||
if (this.TryGetMethodReference(methodHandle, reader, out string targetTypeName, out string targetMethodName) &&
|
||||
DangerousApiCatalog.IsDangerousApi(targetTypeName, targetMethodName))
|
||||
{
|
||||
string apiName = $"{targetTypeName}.{targetMethodName}";
|
||||
this.AddViolation(
|
||||
result,
|
||||
seenViolations,
|
||||
apiName,
|
||||
$"Dangerous IL method reference detected before assembly load: {apiName}",
|
||||
$"IL {opCode.Name} references '{apiName}' in {declaringTypeName}.{methodName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (opCode == OpCodes.Ldtoken && opCode.OperandType == OperandType.InlineTok)
|
||||
{
|
||||
int token = ReadInt32(ilBytes, offset);
|
||||
string referencedTypeName = this.TryGetTypeReference(MetadataTokens.EntityHandle(token), reader);
|
||||
this.AddDecodedTypeViolations(
|
||||
result,
|
||||
seenViolations,
|
||||
referencedTypeName,
|
||||
dangerousTypeName => $"Dangerous IL type token detected before assembly load: {dangerousTypeName}",
|
||||
dangerousTypeName => $"IL ldtoken references '{dangerousTypeName}' in {declaringTypeName}.{methodName}");
|
||||
}
|
||||
|
||||
offset += GetOperandSize(opCode.OperandType, ilBytes, offset);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetMethodReference(
|
||||
EntityHandle handle,
|
||||
MetadataReader reader,
|
||||
out string typeName,
|
||||
out string methodName)
|
||||
{
|
||||
typeName = string.Empty;
|
||||
methodName = string.Empty;
|
||||
|
||||
if (handle.Kind == HandleKind.MemberReference)
|
||||
{
|
||||
MemberReference memberReference = reader.GetMemberReference((MemberReferenceHandle)handle);
|
||||
typeName = this.GetMemberReferenceParentTypeName(reader, memberReference.Parent);
|
||||
methodName = reader.GetString(memberReference.Name);
|
||||
return !string.IsNullOrEmpty(typeName) && !string.IsNullOrEmpty(methodName);
|
||||
}
|
||||
|
||||
if (handle.Kind == HandleKind.MethodDefinition)
|
||||
{
|
||||
MethodDefinition methodDefinition = reader.GetMethodDefinition((MethodDefinitionHandle)handle);
|
||||
typeName = this.GetTypeDefinitionFullName(reader, methodDefinition.GetDeclaringType());
|
||||
methodName = reader.GetString(methodDefinition.Name);
|
||||
return !string.IsNullOrEmpty(typeName) && !string.IsNullOrEmpty(methodName);
|
||||
}
|
||||
|
||||
if (handle.Kind == HandleKind.MethodSpecification)
|
||||
{
|
||||
MethodSpecification methodSpecification = reader.GetMethodSpecification((MethodSpecificationHandle)handle);
|
||||
return this.TryGetMethodReference(methodSpecification.Method, reader, out typeName, out methodName);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string TryGetTypeReference(EntityHandle handle, MetadataReader reader)
|
||||
{
|
||||
if (handle.Kind == HandleKind.TypeReference ||
|
||||
handle.Kind == HandleKind.TypeDefinition ||
|
||||
handle.Kind == HandleKind.TypeSpecification)
|
||||
{
|
||||
return this.GetEntityTypeName(reader, handle);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private void AddDecodedTypeViolations(
|
||||
SecurityValidationResult result,
|
||||
HashSet<string> seenViolations,
|
||||
string decodedTypeName,
|
||||
System.Func<string, string> createMessage,
|
||||
System.Func<string, string> createDescription)
|
||||
{
|
||||
foreach (string typeName in ExpandDecodedTypeNames(decodedTypeName))
|
||||
{
|
||||
if (!DangerousApiCatalog.IsDangerousType(typeName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.AddViolation(
|
||||
result,
|
||||
seenViolations,
|
||||
typeName,
|
||||
createMessage(typeName),
|
||||
createDescription(typeName));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ExpandDecodedTypeNames(string decodedTypeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(decodedTypeName))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
string[] parts = decodedTypeName.Split(DecodedTypeNameSeparator);
|
||||
foreach (string part in parts)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
yield return part;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = "il"
|
||||
});
|
||||
}
|
||||
|
||||
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 static OpCode ReadOpCode(byte[] ilBytes, ref int offset)
|
||||
{
|
||||
byte first = ilBytes[offset++];
|
||||
if (first != 0xFE)
|
||||
{
|
||||
return SingleByteOpCodes[first];
|
||||
}
|
||||
|
||||
byte second = ilBytes[offset++];
|
||||
return MultiByteOpCodes[second];
|
||||
}
|
||||
|
||||
private static int ReadInt32(byte[] ilBytes, int offset)
|
||||
{
|
||||
return ilBytes[offset] |
|
||||
(ilBytes[offset + 1] << 8) |
|
||||
(ilBytes[offset + 2] << 16) |
|
||||
(ilBytes[offset + 3] << 24);
|
||||
}
|
||||
|
||||
private static int GetOperandSize(OperandType operandType, byte[] ilBytes, int offset)
|
||||
{
|
||||
switch (operandType)
|
||||
{
|
||||
case OperandType.InlineNone:
|
||||
return 0;
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.ShortInlineI:
|
||||
case OperandType.ShortInlineVar:
|
||||
return 1;
|
||||
case OperandType.InlineVar:
|
||||
return 2;
|
||||
case OperandType.InlineI:
|
||||
case OperandType.InlineBrTarget:
|
||||
case OperandType.InlineField:
|
||||
case OperandType.InlineMethod:
|
||||
case OperandType.InlineSig:
|
||||
case OperandType.InlineString:
|
||||
case OperandType.InlineTok:
|
||||
case OperandType.InlineType:
|
||||
case OperandType.ShortInlineR:
|
||||
return 4;
|
||||
case OperandType.InlineI8:
|
||||
case OperandType.InlineR:
|
||||
return 8;
|
||||
case OperandType.InlineSwitch:
|
||||
int count = ReadInt32(ilBytes, offset);
|
||||
return 4 + (count * 4);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static OpCode[] BuildOpCodeTable(bool singleByte)
|
||||
{
|
||||
OpCode[] table = new OpCode[256];
|
||||
System.Reflection.FieldInfo[] fields = typeof(OpCodes).GetFields();
|
||||
|
||||
foreach (System.Reflection.FieldInfo field in fields)
|
||||
{
|
||||
if (field.FieldType != typeof(OpCode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
OpCode opcode = (OpCode)field.GetValue(null);
|
||||
ushort value = unchecked((ushort)opcode.Value);
|
||||
|
||||
if (singleByte)
|
||||
{
|
||||
if (value <= 0xFF)
|
||||
{
|
||||
table[value] = opcode;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((value & 0xFF00) == 0xFE00)
|
||||
{
|
||||
table[value & 0xFF] = opcode;
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
private sealed class MetadataTypeNameDecoder : ISignatureTypeProvider<string, object>
|
||||
{
|
||||
private readonly MetadataReader _reader;
|
||||
private readonly PreloadIlSecurityValidator _validator;
|
||||
|
||||
public MetadataTypeNameDecoder(MetadataReader reader, PreloadIlSecurityValidator 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)
|
||||
{
|
||||
List<string> flattenedTypeNames = new List<string>();
|
||||
AddFlattenedTypeName(flattenedTypeNames, genericType);
|
||||
foreach (string typeArgument in typeArguments)
|
||||
{
|
||||
AddFlattenedTypeName(flattenedTypeNames, typeArgument);
|
||||
}
|
||||
|
||||
return string.Join(DecodedTypeNameSeparator.ToString(), flattenedTypeNames);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static void AddFlattenedTypeName(List<string> flattenedTypeNames, string decodedTypeName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(decodedTypeName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string[] parts = decodedTypeName.Split(DecodedTypeNameSeparator);
|
||||
foreach (string part in parts)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(part))
|
||||
{
|
||||
flattenedTypeNames.Add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user