private static void ValidateAndRewriteType(TypeDefinition type, RuntimeGuardReferences guard, AssemblyValidator validator, AssemblyGuardSettings settings) { validator.ValidateDefinition(type); foreach (var nested in type.NestedTypes) { ValidateAndRewriteType(nested, guard, validator, settings); } foreach (var method in type.Methods) { ValidateAndRewriteMethod(method, guard, validator, settings); } }
internal static RuntimeGuardToken Rewrite(AssemblyDefinition assembly, AssemblyGuardSettings? settings = null) { var id = Guid.NewGuid(); settings = settings ?? AssemblyGuardSettings.Default; var validator = new AssemblyValidator(settings, assembly, new StackSizeValidator(settings), new PointerOperationValidator(settings)); foreach (var module in assembly.Modules) { var guardInstanceField = EmitGuardInstance(module, id); var guard = new RuntimeGuardReferences(guardInstanceField, module); validator.ValidateDefinition(module); foreach (var type in module.Types) { ValidateAndRewriteType(type, guard, validator, settings); } } return new RuntimeGuardToken(id); }
private static void ValidateAndRewriteMethod(MethodDefinition method, RuntimeGuardReferences guard, AssemblyValidator validator, AssemblyGuardSettings settings) { if (method.DeclaringType == guard.InstanceField.DeclaringType) { return; } validator.ValidateDefinition(method); if (!method.HasBody) { return; } if (method.Body.Instructions.Count == 0) { return; // weird, but happens with 'extern' } var isStaticConstructor = method.Name == ".cctor" && method.IsStatic && method.IsRuntimeSpecialName; var il = method.Body.GetILProcessor(); var guardVariable = new VariableDefinition(guard.InstanceField.FieldType); il.Body.Variables.Add(guardVariable); var instructions = il.Body.Instructions; var start = instructions[0]; var skipFirst = 4; il.InsertBefore(start, il.Create(OpCodes.Ldsfld, guard.InstanceField)); il.InsertBefore(start, il.Create(OpCodes.Dup)); il.InsertBefore(start, il.CreateStlocBest(guardVariable)); il.InsertBefore(start, il.Create(OpCodes.Call, isStaticConstructor ? guard.GuardEnterStaticConstructorMethod : guard.GuardEnterMethod)); for (var i = skipFirst; i < instructions.Count; i++) { var instruction = instructions[i]; var memberRule = validator.ValidateInstructionAndGetPolicy(instruction, method); var code = instruction.OpCode.Code; if (code == Code.Newarr) { il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.CreateCall(guard.FlowThroughGuardCountIntPtrMethod)); i += 2; continue; } if (memberRule != null && memberRule.Rewriters.Count > 0) { var instructionCountBefore = instructions.Count; var rewritten = false; var context = new MemberRewriterContext(il, guardVariable, guard); foreach (var rewriter in memberRule.InternalRewriters) { rewritten = rewriter.Rewrite(instruction, context) || rewritten; } if (rewritten) { i += instructions.Count - instructionCountBefore; continue; } } if (isStaticConstructor && instruction.OpCode.Code == Code.Ret) { il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.CreateCall(guard.GuardExitStaticConstructorMethod)); i += 2; continue; } if (!ShouldInsertJumpGuardBefore(instruction)) { continue; } il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.Create(OpCodes.Call, guard.GuardJumpMethod)); i += 2; } il.CorrectAllAfterChanges(); }
private static void ValidateAndRewriteMethod(MethodDefinition method, RuntimeGuardReferences guard, AssemblyValidator validator, AssemblyGuardSettings settings) { if (method.DeclaringType == guard.InstanceField.DeclaringType) { return; } validator.ValidateDefinition(method); if (!method.HasBody) { return; } if (method.Body.Instructions.Count == 0) { return; // weird, but happens with 'extern' } var isStaticConstructor = method.Name == ".cctor" && method.IsStatic && method.IsRuntimeSpecialName; var il = method.Body.GetILProcessor(); // IAsyncStateMachine.MoveNext has special rules, but to be safe, only apply them when its type is compiler-generated // which is checked by verifying that it has unspeakable name var isStateMachineMoveNext = method.Name == "MoveNext" && method.Overrides.Count == 1 && method.Overrides[0].FullName == "System.Void System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()" && method.DeclaringType.Name.Contains("<") && il.Body.ExceptionHandlers.Count >= 1; var guardVariable = new VariableDefinition(guard.InstanceField.FieldType); il.Body.Variables.Add(guardVariable); var instructions = il.Body.Instructions; var start = instructions[0]; var skipStart = 0; var skipCount = 4; ExceptionHandler?stateMachineExceptionHandler = null; if (isStateMachineMoveNext) { // in IAsyncStateMachine.MoveNext, the GuardEnter call has to be inside the try block, so that the exception is propagated to the Task stateMachineExceptionHandler = il.Body.ExceptionHandlers.OrderBy(eh => eh.TryStart.Offset).First(); start = stateMachineExceptionHandler.TryStart; skipStart = instructions.IndexOf(start); } il.InsertBeforeAndRetargetJumps(start, il.Create(OpCodes.Ldsfld, guard.InstanceField)); il.InsertBefore(start, il.Create(OpCodes.Dup)); il.InsertBefore(start, il.CreateStlocBest(guardVariable)); il.InsertBefore(start, il.Create(OpCodes.Call, isStaticConstructor ? guard.GuardEnterStaticConstructorMethod : guard.GuardEnterMethod)); for (var i = 0; i < instructions.Count; i++) { if (i >= skipStart && i < skipStart + skipCount) { continue; } // in IAsyncStateMachine.MoveNext, guard calls are only allowed inside the try block if (stateMachineExceptionHandler != null && (i < instructions.IndexOf(stateMachineExceptionHandler.TryStart) || i > instructions.IndexOf(stateMachineExceptionHandler.TryEnd))) { continue; } var instruction = instructions[i]; var memberRule = validator.ValidateInstructionAndGetPolicy(instruction, method); var code = instruction.OpCode.Code; if (code == Code.Newarr) { il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.CreateCall(guard.FlowThroughGuardCountIntPtrMethod)); i += 2; continue; } if (memberRule != null && memberRule.Rewriters.Count > 0) { var instructionCountBefore = instructions.Count; var rewritten = false; var context = new MemberRewriterContext(il, guardVariable, guard); foreach (var rewriter in memberRule.InternalRewriters) { rewritten = rewriter.Rewrite(instruction, context) || rewritten; } if (rewritten) { i += instructions.Count - instructionCountBefore; continue; } } if (isStaticConstructor && instruction.OpCode.Code == Code.Ret) { il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.CreateCall(guard.GuardExitStaticConstructorMethod)); i += 2; continue; } if (!ShouldInsertJumpGuardBefore(instruction)) { continue; } il.InsertBeforeAndRetargetJumps(instruction, il.CreateLdlocBest(guardVariable)); il.InsertBefore(instruction, il.Create(OpCodes.Call, guard.GuardJumpMethod)); i += 2; } il.CorrectAllAfterChanges(); }