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.)
fonte
AllowPartiallyTrustedCallers
deve resolver, mas não parece fazer diferençaRespostas:
De acordo com o MSDN , no .NET 4.0 basicamente você não deve usar
ISerializable
para código parcialmente confiável e, em vez disso, deve usar ISafeSerializationDataCitando https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Provavelmente não é o que você gostaria de ouvir se precisar, mas não acho que haja como evitar enquanto continua usando
ISerializable
(além de voltar àLevel1
segurança, que você disse que não queria).PS: os
ISafeSerializationData
documentos afirmam que é apenas para exceções, mas não parece tão específico, você pode querer tentar ... Basicamente, não posso testá-lo com o seu código de amostra (exceto removerISerializable
obras, mas você já sabia disso) ... você terá que ver seISafeSerializationData
combina com você o suficiente.PS2: o
SecurityCritical
atributo não funciona porque é ignorado quando o assembly é carregado no modo de confiança parcial ( na segurança Nível2 ). Você pode ver isso em seu código de amostra, se você depurar atarget
variávelExecuteUntrustedCode
antes de invocá-la, terá queIsSecurityTransparent
fazertrue
eIsSecurityCritical
atéfalse
mesmo se você marcar o método com oSecurityCritical
atributo)fonte
A resposta aceita é tão convincente que quase acreditei que não era um bug. Mas, depois de fazer alguns experimentos, posso dizer que a segurança do Nível 2 é uma bagunça completa; pelo menos, algo é realmente suspeito.
Alguns dias atrás, encontrei o mesmo problema com minhas bibliotecas. Rapidamente criei um teste de unidade; no entanto, não consegui reproduzir o problema que experimentei no .NET Fiddle, enquanto o mesmo código "com êxito" lançou a exceção em um aplicativo de console. No final, descobri duas maneiras estranhas de superar o problema.
TL; DR : Acontece que, se você usar um tipo interno da biblioteca usada em seu projeto de consumidor, o código parcialmente confiável funciona conforme o esperado: é capaz de instanciar uma
ISerializable
implementação (e um código crítico de segurança não pode ser chamado diretamente, mas veja abaixo). Ou, o que é ainda mais ridículo, você pode tentar criar a sandbox novamente se não funcionar pela primeira vez ...Mas vamos ver algum código.
ClassLibrary.dll:
Vamos separar dois casos: um para uma aula regular com conteúdo crítico de segurança e um
ISerializable
implementação:Uma maneira de superar o problema é usar um tipo interno do conjunto do consumidor. Qualquer tipo fará isso; agora eu defino um atributo:
E os atributos relevantes aplicados à montagem:
Assine a montagem, aplique a chave ao
InternalsVisibleTo
atributo e prepare-se para o projeto de teste:UnitTest.dll (usa NUnit e ClassLibrary):
Para usar o truque interno, o assembly de teste também deve ser assinado. Atributos de montagem:
Observação : o atributo pode ser aplicado em qualquer lugar. No meu caso, foi em um método em uma aula de teste aleatória que me levou alguns dias para encontrar.
Nota 2 : Se você executar todos os métodos de teste juntos, pode acontecer que os testes sejam aprovados.
O esqueleto da classe de teste:
E vamos ver os casos de teste um por um
Caso 1: Implementação serializável
O mesmo problema da pergunta. O teste passa se
InternalTypeReferenceAttribute
é aplicadoDo contrário, surge a
Inheritance security rules violated while overriding member...
exceção totalmente inadequada ao instanciarSerializableCriticalClass
.Caso 2: aula regular com membros críticos de segurança
O teste passa nas mesmas condições do primeiro. No entanto, o problema é completamente diferente aqui: um código parcialmente confiável pode acessar um membro crítico de segurança diretamente .
Caso 3-4: versões de confiança total do caso 1-2
Para fins de integridade, aqui estão os mesmos casos que os anteriores executados em um domínio totalmente confiável. Se você remover
[assembly: AllowPartiallyTrustedCallers]
os testes, falhará porque poderá acessar o código crítico diretamente (já que os métodos não são mais transparentes por padrão).Epílogo:
Claro, isso não resolverá seu problema com o .NET Fiddle. Mas agora eu ficaria muito surpreso se não fosse um bug no framework.
A maior questão para mim agora é a parte citada na resposta aceita. Como eles chegaram a esse absurdo? O
ISafeSerializationData
claramente não é uma solução para nada: é usado exclusivamente pelaException
classe base e se você inscrever oSerializeObjectState
evento (por que não é um método substituível?), Então o estado também será consumido peloException.GetObjectData
no final.O
AllowPartiallyTrustedCallers
/SecurityCritical
/SecuritySafeCritical
triunvirato de atributos foi projetado exatamente para o uso mostrado acima. Parece totalmente absurdo para mim que um código parcialmente confiável não pode nem mesmo instanciar um tipo, independentemente da tentativa de usar seus membros críticos de segurança. Mas é um absurdo ainda maior (uma falha de segurança na verdade) que um código parcialmente confiável pode acessar um método crítico de segurança diretamente (ver caso 2 ), ao passo que isso é proibido para métodos transparentes, mesmo de um domínio totalmente confiável.Portanto, se seu projeto de consumidor for um teste ou outro assembly bem conhecido, o truque interno pode ser usado perfeitamente. Para .NET Fiddle e outros ambientes de área restrita da vida real, a única solução é voltar a
SecurityRuleSet.Level1
isso até que isso seja corrigido pela Microsoft.Atualização: um tíquete da comunidade de desenvolvedores foi criado para o problema.
fonte
De acordo com o MSDN, consulte:
fonte
GetObjectData
explicitamente, mas fazê-lo implicitamente não ajuda.