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).
fuente
AllowPartiallyTrustedCallers
debería funcionar, pero no parece hacer una diferenciaRespuestas:
De acuerdo con MSDN , en .NET 4.0 básicamente no debe usar
ISerializable
para código parcialmente confiable, sino que debe usar ISafeSerializationDataCitando de https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Así que probablemente no sea lo que querías escuchar si lo necesitas, pero no creo que haya otra forma de evitarlo mientras sigues usando
ISerializable
(aparte de volver a laLevel1
seguridad, que dijiste que no querías).PD: los
ISafeSerializationData
documentos dicen que es solo para excepciones, pero no parece tan específico, es posible que desee intentarlo ... Básicamente no puedo probarlo con su código de muestra (aparte de eliminarISerializable
trabajos, pero eso ya lo sabías) ... tendrás que ver siISafeSerializationData
te conviene lo suficiente.PS2: el
SecurityCritical
atributo no funciona porque se ignora cuando el ensamblaje se carga en modo de confianza parcial ( en seguridad de Nivel2 ). Se puede ver en su código de ejemplo, si se depura latarget
variable enExecuteUntrustedCode
la derecha antes de que lo alega, que tendráIsSecurityTransparent
atrue
yIsSecurityCritical
afalse
incluso si marca el método con elSecurityCritical
atributo)fuente
La respuesta aceptada es tan convincente que casi creí que esto no era un error. Pero después de hacer algunos experimentos ahora puedo decir que la seguridad de Nivel 2 es un completo desastre; al menos, algo es realmente sospechoso.
Hace un par de días me encontré con el mismo problema con mis bibliotecas. Creé rápidamente una prueba unitaria; sin embargo, no pude reproducir el problema que experimenté en .NET Fiddle, mientras que el mismo código arrojó "con éxito" la excepción en una aplicación de consola. Al final encontré dos formas extrañas de superar el problema.
TL; DR : resulta que si usa un tipo interno de la biblioteca utilizada en su proyecto de consumidor, entonces el código parcialmente confiable funciona como se esperaba: es capaz de instanciar una
ISerializable
implementación (y un código crítico de seguridad no se puede llamar directamente, pero ver más abajo). O, lo que es aún más ridículo, puede intentar crear la caja de arena nuevamente si no funcionó por primera vez ...Pero veamos algo de código.
ClassLibrary.dll:
Separemos dos casos: uno para una clase normal con contenido crítico para la seguridad y otro para la
ISerializable
implementación:Una forma de solucionar el problema es utilizar un tipo interno del ensamblaje del consumidor. Cualquier tipo lo hará; ahora defino un atributo:
Y los atributos relevantes aplicados al ensamblaje:
Firme el ensamblaje, aplique la clave al
InternalsVisibleTo
atributo y prepárese para el proyecto de prueba:UnitTest.dll (usa NUnit y ClassLibrary):
Para utilizar el truco interno, el conjunto de prueba también debe estar firmado. Atributos de ensamblaje:
Nota : el atributo se puede aplicar en cualquier lugar. En mi caso, me tomó un par de días encontrar un método en una clase de prueba aleatoria.
Nota 2 : Si ejecuta todos los métodos de prueba juntos, puede suceder que las pruebas pasen.
El esqueleto de la clase de prueba:
Y veamos los casos de prueba uno por uno.
Caso 1: implementación de ISerializable
El mismo problema que en la pregunta. La prueba pasa si
InternalTypeReferenceAttribute
Está aplicadoDe lo contrario, se produce la
Inheritance security rules violated while overriding member...
excepción totalmente inapropiada al crear una instanciaSerializableCriticalClass
.Caso 2: clase regular con miembros críticos para la seguridad
La prueba pasa en las mismas condiciones que la primera. Sin embargo, el problema es completamente diferente aquí: un código parcialmente confiable puede acceder directamente a un miembro crítico para la seguridad .
Caso 3-4: Versiones de plena confianza del caso 1-2
En aras de la integridad, aquí están los mismos casos que los anteriores ejecutados en un dominio de plena confianza. Si elimina,
[assembly: AllowPartiallyTrustedCallers]
las pruebas fallan porque entonces puede acceder al código crítico directamente (ya que los métodos ya no son transparentes por defecto).Epílogo:
Por supuesto, esto no resolverá su problema con .NET Fiddle. Pero ahora me sorprendería mucho si no fuera un error en el marco.
La pregunta más importante para mí ahora es la parte citada en la respuesta aceptada. ¿Cómo salieron con esta tontería? El
ISafeSerializationData
claramente no es una solución para cualquier cosa: se utiliza exclusivamente por la base deException
clase y si se suscribe alSerializeObjectState
evento (¿por qué no es que un método reemplazable?), Entonces el estado también será consumido por elException.GetObjectData
al final.El
AllowPartiallyTrustedCallers
/SecurityCritical
/SecuritySafeCritical
triunvirato de atributos fue diseñado exactamente para el uso que se muestra arriba. Me parece una tontería total que un código de confianza parcial ni siquiera pueda instanciar un tipo, independientemente del intento de utilizar sus miembros críticos de seguridad. Pero es una tontería aún mayor (un agujero de seguridad en realidad) que un código parcialmente confiable pueda acceder directamente a un método crítico de seguridad (ver caso 2 ) mientras que esto está prohibido para métodos transparentes incluso desde un dominio de plena confianza.Entonces, si su proyecto de consumidor es una prueba u otro ensamblaje conocido, entonces el truco interno se puede usar perfectamente. Para .NET Fiddle y otros entornos de espacio aislado de la vida real, la única solución es volver
SecurityRuleSet.Level1
hasta que Microsoft lo solucione.Actualización: Un billete comunidad de desarrolladores ha sido creado para el tema.
fuente
Según MSDN, consulte:
fuente
GetObjectData
explícitamente, pero hacerlo implícitamente no ayuda.