Fundo: Noda Time contém muitas estruturas serializáveis. Embora eu não goste da serialização binária, recebemos muitas solicitações de suporte, na linha do tempo 1.x. Nós o apoiamos implementando a ISerializable
interface.
Recebemos um relatório de problema recente do Noda Time 2.x falha no .NET Fiddle . O mesmo código usando Noda Time 1.x funciona bem. A exceção lançada é esta:
Regras de segurança de herança violadas ao substituir o membro: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. A acessibilidade de segurança do método de substituição deve corresponder à acessibilidade de segurança do método que está sendo substituído.
Limitei isso à estrutura que é direcionada: 1.x direciona o .NET 3.5 (perfil de cliente); 2.x visa .NET 4.5. Eles têm grandes diferenças em termos de suporte PCL vs .NET Core e a estrutura de arquivos do projeto, mas parece que isso é irrelevante.
Consegui reproduzir isso em um projeto local, mas não encontrei uma solução para isso.
Passos para reproduzir no VS2017:
- Crie uma nova solução
- Crie um novo aplicativo de console clássico do Windows direcionado ao .NET 4.5.1. Eu o chamei de "CodeRunner".
- Nas propriedades do projeto, acesse Assinatura e assine a montagem com uma nova chave. Desmarque o requisito de senha e use qualquer nome de arquivo de chave.
- Cole o código a seguir para substituir
Program.cs
. Esta é uma versão abreviada do código neste exemplo da Microsoft . Eu mantive todos os caminhos iguais, então se você quiser voltar para o código mais completo, não deve precisar alterar mais nada.
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);
}
}
- Crie outro projeto chamado "UntrustedCode". Este deve ser um projeto Classic Desktop Class Library.
- Assine a assembleia; você pode usar uma nova chave ou a mesma do CodeRunner. (Isso é parcialmente para imitar a situação do Tempo Noda e, em parte, para manter a Análise de Código feliz.)
- Cole o seguinte código
Class1.cs
(substituindo o que está lá):
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();
}
}
}
Executar o projeto CodeRunner oferece a seguinte exceção (reformatado para facilitar a leitura):
Exceção não tratada: System.Reflection.TargetInvocationException: a
exceção foi lançada pelo destino de uma invocação.
--->
System.TypeLoadException:
Regras de segurança de herança violadas ao substituir o membro:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
A acessibilidade de segurança do método de substituição deve corresponder à
acessibilidade de segurança do método que está sendo substituído.
Os atributos comentados mostram coisas que tentei:
SecurityPermission
é recomendado por dois artigos diferentes de MS ( primeiro , segundo ), embora curiosamente eles façam coisas diferentes em torno da implementação de interface explícita / implícitaSecurityCritical
é o que Noda Time tem atualmente, e é o que a resposta desta pergunta sugereSecuritySafeCritical
é algo sugerido por mensagens de regra de análise de código- Sem quaisquer atributos, as regras de análise de código ficam satisfeitas - com um
SecurityPermission
ouSecurityCritical
presente, as regras dizem para você remover os atributos - a menos que você os tenhaAllowPartiallyTrustedCallers
. Seguir as sugestões em ambos os casos não ajuda. - O Tempo de Noda se
AllowPartiallyTrustedCallers
aplicou a ele; o exemplo aqui não funciona com ou sem o atributo aplicado.
O código é executado sem exceção se eu adicionar [assembly: SecurityRules(SecurityRuleSet.Level1)]
ao UntrustedCode
assembly (e descomentar o AllowPartiallyTrustedCallers
atributo), mas acredito que essa seja uma solução ruim para o problema que pode atrapalhar outro código.
Eu admito totalmente que estou muito perdido quando se trata desse tipo de aspecto de segurança do .NET. Então, o que posso fazer para direcionar o .NET 4.5 e ainda permitir que meus tipos implementem ISerializable
e ainda sejam usados em ambientes como o .NET Fiddle?
(Embora meu objetivo seja o .NET 4.5, acredito que foram as mudanças na política de segurança do .NET 4.0 que causaram o problema, daí a tag.)
AllowPartiallyTrustedCallers
deve resolver, mas não parece fazer diferença