Antecedentes: Noda Time contiene muchas estructuras serializables. Si bien no me gusta la serialización binaria, recibimos muchas solicitudes para admitirla, en la línea de tiempo 1.x. Lo apoyamos implementando la ISerializable
interfaz.
Recibimos un informe de un problema reciente de Noda Time 2.x fallando dentro de .NET Fiddle . El mismo código que usa Noda Time 1.x funciona bien. La excepción lanzada es esta:
Se violaron las reglas de seguridad de herencia al anular el miembro: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. La accesibilidad de seguridad del método de anulación debe coincidir con la accesibilidad de seguridad del método que se anula.
Lo he reducido al marco al que apunta: 1.x apunta a .NET 3.5 (perfil de cliente); 2.x apunta a .NET 4.5. Tienen grandes diferencias en términos de compatibilidad con PCL frente a .NET Core y la estructura de archivos del proyecto, pero parece que esto es irrelevante.
Me las arreglé para reproducir esto en un proyecto local, pero no he encontrado una solución.
Pasos para reproducir en VS2017:
- Crea una nueva solución
- Cree una nueva aplicación de consola clásica de Windows destinada a .NET 4.5.1. Lo llamé "CodeRunner".
- En las propiedades del proyecto, vaya a Firma y firme el ensamblado con una nueva clave. Desmarque el requisito de contraseña y use cualquier nombre de archivo de clave.
- Pegue el siguiente código para reemplazarlo
Program.cs
. Ésta es una versión abreviada del código de este ejemplo de Microsoft . He mantenido todas las rutas iguales, por lo que si desea volver al código más completo, no debería necesitar cambiar nada más.
Código:
using System;
using System.Security;
using System.Security.Permissions;
class Sandboxer : MarshalByRefObject
{
static void Main()
{
var adSetup = new AppDomainSetup();
adSetup.ApplicationBase = System.IO.Path.GetFullPath(@"..\..\..\UntrustedCode\bin\Debug");
var permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var fullTrustAssembly = typeof(Sandboxer).Assembly.Evidence.GetHostEvidence<System.Security.Policy.StrongName>();
var newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
var handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
Sandboxer newDomainInstance = (Sandboxer) handle.Unwrap();
newDomainInstance.ExecuteUntrustedCode("UntrustedCode", "UntrustedCode.UntrustedClass", "IsFibonacci", new object[] { 45 });
}
public void ExecuteUntrustedCode(string assemblyName, string typeName, string entryPoint, Object[] parameters)
{
var target = System.Reflection.Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
target.Invoke(null, parameters);
}
}
- Cree otro proyecto llamado "UntrustedCode". Debe ser un proyecto de biblioteca de clases de escritorio clásico.
- Firmar la asamblea; puede usar una nueva clave o la misma que para CodeRunner. (Esto es en parte para imitar la situación de Noda Time y en parte para mantener contento a Code Analysis).
- Pegue el siguiente código en
Class1.cs
(sobrescribiendo lo que está allí):
Código:
using System;
using System.Runtime.Serialization;
using System.Security;
using System.Security.Permissions;
// [assembly: AllowPartiallyTrustedCallers]
namespace UntrustedCode
{
public class UntrustedClass
{
// Method named oddly (given the content) in order to allow MSDN
// sample to run unchanged.
public static bool IsFibonacci(int number)
{
Console.WriteLine(new CustomStruct());
return true;
}
}
[Serializable]
public struct CustomStruct : ISerializable
{
private CustomStruct(SerializationInfo info, StreamingContext context) { }
//[SecuritySafeCritical]
//[SecurityCritical]
//[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
throw new NotImplementedException();
}
}
}
La ejecución del proyecto CodeRunner da la siguiente excepción (reformateada para facilitar la lectura):
Excepción no controlada: System.Reflection.TargetInvocationException:
el objetivo de una invocación ha lanzado una excepción.
--->
System.TypeLoadException:
reglas de seguridad de herencia violadas al anular el miembro:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
La accesibilidad de seguridad del método de anulación debe coincidir con la
accesibilidad de seguridad del método que se anula.
Los atributos comentados muestran cosas que he probado:
SecurityPermission
es recomendado por dos artículos de MS diferentes ( primero , segundo ), aunque curiosamente hacen cosas diferentes en torno a la implementación de interfaz explícita / implícitaSecurityCritical
es lo que Noda Time tiene actualmente, y es lo que sugiere la respuesta de esta preguntaSecuritySafeCritical
es algo sugerido por los mensajes de reglas de análisis de código- Sin ningún atributo, las reglas de análisis de código están contentas: con cualquiera de los dos
SecurityPermission
oSecurityCritical
presente, las reglas le dicen que elimine los atributos, a menos que lo tengaAllowPartiallyTrustedCallers
. Seguir las sugerencias en cualquier caso no ayuda. - Noda Time se le ha
AllowPartiallyTrustedCallers
aplicado; el ejemplo aquí no funciona con o sin el atributo aplicado.
El código se ejecuta sin excepción si lo agrego [assembly: SecurityRules(SecurityRuleSet.Level1)]
al UntrustedCode
ensamblado (y descomento el AllowPartiallyTrustedCallers
atributo), pero creo que esa es una mala solución al problema que podría obstaculizar otro código.
Admito plenamente que estoy bastante perdido cuando se trata de este tipo de aspecto de seguridad de .NET. Entonces, ¿qué puedo hacer para apuntar a .NET 4.5 y, sin embargo, permitir que mis tipos se implementen ISerializable
y se sigan usando en entornos como .NET Fiddle?
(Aunque me dirijo a .NET 4.5, creo que son los cambios en la política de seguridad de .NET 4.0 los que causaron el problema, de ahí la etiqueta).
AllowPartiallyTrustedCallers
debería funcionar, pero no parece hacer una diferencia