I found one issue with InternalsVisibleTo attribute usage. The idea of InternalsVisibleTo attribute to allow some other assemblies to use internal classes\methods of this assembly. To make it work you need sign your assemblies. So, if other assemblies isn't specified in main assembly and if they have incorrect public key, then they can't use Internal members.
But the issue in Reflection Emit type generation. For example, we have CorpLibrary1 assembly and it has such class:
public class TestApi
{
internal virtual void DoSomething()
{
Console.WriteLine("Base DoSomething");
}
public void DoApiTest()
{
// some internal logic
// ...
// call internal method
DoSomething();
}
}
This assembly is marked with such attribute to allow another CorpLibrary2 to make inheritor for that TestAPI and override behaviour of DoSomething method.
[assembly: InternalsVisibleTo("CorpLibrary2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100434D9C5E1F9055BF7970B0C106AAA447271ECE0F8FC56F6AF3A906353F0B848A8346DC13C42A6530B4ED2E6CB8A1E56278E664E61C0D633A6F58643A7B8448CB0B15E31218FB8FE17F63906D3BF7E20B9D1A9F7B1C8CD11877C0AF079D454C21F24D5A85A8765395E5CC5252F0BE85CFEB65896EC69FCC75201E09795AAA07D0")]
The issue is that I'm able to override this internal DoSomething method and break class logic. My steps to do it:
Generate new assembly in runtime via AssemblyBuilder
Get AssemblyName from CorpLibrary1 and copy PublikKey to new assembly
Generate new assembly that will inherit TestApi class
As PublicKey and name of generated assembly is the same as in InternalsVisibleTo, then we can generate new DoSomething method that will override internal method in TestAPI assembly
Then we have another assembly that isn't related to this CorpLibrary1 and can't use internal members. We have such test code in it:
class Program
{
static void Main(string[] args)
{
var builder = new FakeBuilder(InjectBadCode, "DoSomething", true);
TestApi fakeType = builder.CreateFake();
fakeType.DoApiTest();
// it will display:
// Inject bad code
// Base DoSomething
Console.ReadLine();
}
public static void InjectBadCode()
{
Console.WriteLine("Inject bad code");
}
}
And this FakeBuilder class has such code:
///
/// Builder that will generate inheritor for specified assembly and will overload specified internal virtual method
///
/// Target type
public class FakeBuilder
{
private readonly Action _callback;
private readonly Type _targetType;
private readonly string _targetMethodName;
private readonly string _slotName;
private readonly bool _callBaseMethod;
public FakeBuilder(Action callback, string targetMethodName, bool callBaseMethod)
{
int randomId = new Random((int)DateTime.Now.Ticks).Next();
_slotName = string.Format("FakeSlot_{0}", randomId);
_callback = callback;
_targetType = typeof(TFakeType);
_targetMethodName = targetMethodName;
_callBaseMethod = callBaseMethod;
}
public TFakeType CreateFake()
{
// as CorpLibrary1 can't use code from unreferences assemblies, we need to store this Action somewhere.
// And Thread is not bad place for that. It's not the best place as it won't work in multithread application, but it's just a sample
LocalDataStoreSlot slot = Thread.AllocateNamedDataSlot(_slotName);
Thread.SetData(slot, _callback);
// then we generate new assembly with the same nameand public key as target assembly trusts by InternalsVisibleTo attribute
var newTypeName = _targetType.Name + "Fake";
var targetAssembly = Assembly.GetAssembly(_targetType);
AssemblyName an = new AssemblyName();
an.Name = GetFakeAssemblyName(targetAssembly);
// copying public key to new generated assembly
var assemblyName = targetAssembly.GetName();
an.SetPublicKey(assemblyName.GetPublicKey());
an.SetPublicKeyToken(assemblyName.GetPublicKeyToken());
AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyBuilder.GetName().Name, true);
// create inheritor for specified type
TypeBuilder typeBuilder = moduleBuilder.DefineType(newTypeName, TypeAttributes.Public | TypeAttributes.Class, _targetType);
// LambdaExpression.CompileToMethod can be used only with static methods, so we need to create another method that will call our Inject method
// we can do the same via ILGenerator, but expression trees are more easy to use
MethodInfo methodInfo = CreateMethodInfo(moduleBuilder);
MethodBuilder methodBuilder = typeBuilder.DefineMethod(_targetMethodName, MethodAttributes.Public | MethodAttributes.Virtual);
ILGenerator ilGenerator = methodBuilder.GetILGenerator();
// call our static method that will call inject method
ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
// in case if we need, then we put call to base method
if (_callBaseMethod)
{
var baseMethodInfo = _targetType.GetMethod(_targetMethodName, BindingFlags.NonPublic | BindingFlags.Instance);
// place this to stack
ilGenerator.Emit(OpCodes.Ldarg_0);
// call the base method
ilGenerator.EmitCall(OpCodes.Call, baseMethodInfo, new Type[0]);
// return
ilGenerator.Emit(OpCodes.Ret);
}
// generate type, create it and return to caller
Type cheatType = typeBuilder.CreateType();
object type = Activator.CreateInstance(cheatType);
return (TFakeType)type;
}
///
/// Get name of assembly from InternalsVisibleTo AssemblyName
///
private static string GetFakeAssemblyName(Assembly assembly)
{
var internalsVisibleAttr = assembly.GetCustomAttributes(typeof(InternalsVisibleToAttribute), true).FirstOrDefault() as InternalsVisibleToAttribute;
if (internalsVisibleAttr == null)
{
throw new InvalidOperationException("Assembly hasn't InternalVisibleTo attribute");
}
var ind = internalsVisibleAttr.AssemblyName.IndexOf(",");
var name = internalsVisibleAttr.AssemblyName.Substring(0, ind);
return name;
}
///
/// Generate such code:
/// ((Action)Thread.GetData(Thread.GetNamedDataSlot(_slotName))).Invoke();
///
private LambdaExpression MakeStaticExpressionMethod()
{
var allocateMethod = typeof(Thread).GetMethod("GetNamedDataSlot", BindingFlags.Static | BindingFlags.Public);
var getDataMethod = typeof(Thread).GetMethod("GetData", BindingFlags.Static | BindingFlags.Public);
var call = Expression.Call(allocateMethod, Expression.Constant(_slotName));
var getCall = Expression.Call(getDataMethod, call);
var convCall = Expression.Convert(getCall, typeof(Action));
var invokExpr = Expression.Invoke(convCall);
var lambda = Expression.Lambda(invokExpr);
return lambda;
}
///
/// Generate static class with one static function that will execute Action from Thread NamedDataSlot
///
private MethodInfo CreateMethodInfo(ModuleBuilder moduleBuilder)
{
var methodName = "_StaticTestMethod_" + _slotName;
var className = "_StaticClass_" + _slotName;
TypeBuilder typeBuilder = moduleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName, MethodAttributes.Static | MethodAttributes.Public);
LambdaExpression expression = MakeStaticExpressionMethod();
expression.CompileToMethod(methodBuilder);
var type = typeBuilder.CreateType();
return type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public);
}
}
remarks about sample: as we need to execute code from another assembly, CorpLibrary1 hasn't access to it, so we need to store this delegate somewhere. Just for testing I stored it in Thread NamedDataSlot. It won't work in multithreaded applications, but it's just a sample.
I know that we use Reflection to get private\internal members of any class, but within reflection we can't override them. But this issue is allows anyone to override internal class\method if that assembly has InternalsVisibleTo attribute.
I tested it on .Net 3.5\4 and it works for both of them.
How does it possible to just copy PublicKey without private key and use it in runtime?
The whole sample can be found there - https://github.com/sergey-litvinov/Tests_InternalsVisibleTo
UPDATE1:
That test code in Program and FakeBuilder classes hasn't access to key.sn file and that library isn't signed, so it hasn't public key at all. It just copying it from CorpLibrary1 by using Reflection.Emit