Latar Belakang: Waktu Noda berisi banyak struct yang dapat bersambung. Meskipun saya tidak suka serialisasi biner, kami menerima banyak permintaan untuk mendukungnya, kembali ke timeline 1.x. Kami mendukungnya dengan mengimplementasikan ISerializable
antarmuka.
Kami telah menerima laporan masalah baru-baru ini tentang Noda Time 2.x yang gagal dalam .NET Fiddle . Kode yang sama menggunakan Noda Time 1.x berfungsi dengan baik. Pengecualian yang diberikan adalah ini:
Aturan keamanan pewarisan dilanggar saat menimpa anggota: 'NodaTime.Duration.System.Runtime.Serialization.ISerializable.GetObjectData (System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Aksesibilitas keamanan dari metode penggantian harus sesuai dengan aksesibilitas keamanan dari metode yang akan diganti.
Saya telah mempersempit ini ke kerangka kerja yang ditargetkan: 1.x target. NET 3.5 (profil klien); 2.x menargetkan .NET 4.5. Mereka memiliki perbedaan besar dalam hal dukungan PCL vs .NET Core dan struktur file proyek, tetapi sepertinya ini tidak relevan.
Saya telah berhasil mereproduksi ini dalam proyek lokal, tetapi saya belum menemukan solusi untuk itu.
Langkah-langkah untuk mereproduksi di VS2017:
- Buat solusi baru
- Buat aplikasi konsol Windows klasik baru yang menargetkan .NET 4.5.1. Saya menyebutnya "CodeRunner".
- Di properti proyek, buka Menandatangani dan menandatangani rakitan dengan kunci baru. Hapus centang persyaratan kata sandi, dan gunakan nama file kunci apa saja.
- Tempel kode berikut untuk mengganti
Program.cs
. Ini adalah versi kode yang disingkat dalam contoh Microsoft ini . Saya telah menyimpan semua jalur yang sama, jadi jika Anda ingin kembali ke kode yang lebih lengkap, Anda tidak perlu mengubah apa pun.
Kode:
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);
}
}
- Buat proyek lain yang disebut "UntrustedCode". Ini harus menjadi proyek Perpustakaan Kelas Desktop Klasik.
- Tanda tangani majelis; Anda dapat menggunakan kunci baru atau yang sama seperti untuk CodeRunner. (Ini sebagian untuk meniru situasi Waktu Noda, dan sebagian lagi untuk membuat Analisis Kode senang.)
- Tempel kode berikut
Class1.cs
(menimpa apa yang ada di sana):
Kode:
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();
}
}
}
Menjalankan proyek CodeRunner memberikan pengecualian berikut (diformat ulang agar mudah dibaca):
Unhandled Exception: System.Reflection.TargetInvocationException:
Exception telah dilemparkan oleh target pemanggilan.
--->
System.TypeLoadException:
Aturan keamanan warisan dilanggar saat mengganti anggota:
'UntrustedCode.CustomStruct.System.Runtime.Serialization.ISerializable.GetObjectData (...).
Aksesibilitas keamanan
dari metode penggantian harus sesuai dengan aksesibilitas keamanan dari metode yang akan diganti.
Atribut yang diberi komentar menunjukkan hal-hal yang telah saya coba:
SecurityPermission
direkomendasikan oleh dua artikel MS yang berbeda ( pertama , kedua ), meskipun menariknya mereka melakukan hal yang berbeda seputar implementasi antarmuka eksplisit / implisitSecurityCritical
adalah apa yang saat ini Noda Waktu memiliki, dan apa jawaban ini pertanyaan ini menunjukkanSecuritySafeCritical
agak disarankan oleh pesan aturan Analisis Kode- Tanpa setiap atribut, aturan Analisis Kode senang - dengan baik
SecurityPermission
atauSecurityCritical
ini, aturan memberitahu Anda untuk menghapus atribut - kecuali Anda lakukan memilikiAllowPartiallyTrustedCallers
. Mengikuti saran dalam kedua kasus tidak membantu. - Waktu Noda telah
AllowPartiallyTrustedCallers
diterapkan padanya; contoh di sini tidak berfungsi baik dengan atau tanpa atribut diterapkan.
Kode berjalan tanpa pengecualian jika saya menambahkan [assembly: SecurityRules(SecurityRuleSet.Level1)]
ke UntrustedCode
assembly (dan menghapus komentar AllowPartiallyTrustedCallers
atribut), tetapi saya yakin itu adalah solusi yang buruk untuk masalah yang dapat menghambat kode lain.
Saya sepenuhnya mengaku sangat tersesat dalam hal aspek keamanan .NET semacam ini. Jadi apa yang dapat saya lakukan untuk menargetkan .NET 4.5 namun mengizinkan tipe saya untuk diimplementasikan ISerializable
dan masih digunakan di lingkungan seperti .NET Fiddle?
(Sementara saya menargetkan .NET 4.5, saya yakin itu adalah perubahan kebijakan keamanan .NET 4.0 yang menyebabkan masalah, oleh karena itu tagnya.)
sumber
AllowPartiallyTrustedCallers
, tetapi tampaknya tidak ada bedanyaJawaban:
Menurut MSDN , di .NET 4.0 pada dasarnya Anda tidak boleh menggunakan
ISerializable
kode tepercaya sebagian, dan sebaliknya Anda harus menggunakan ISafeSerializationDataMengutip dari https://docs.microsoft.com/en-us/dotnet/standard/serialization/custom-serialization
Jadi mungkin bukan yang ingin Anda dengar jika Anda membutuhkannya, tetapi menurut saya tidak ada cara lain untuk tetap menggunakannya
ISerializable
(selain kembali keLevel1
keamanan, yang Anda katakan tidak Anda inginkan).PS:
ISafeSerializationData
dokumen menyatakan bahwa ini hanya untuk pengecualian, tetapi tampaknya tidak terlalu spesifik, Anda mungkin ingin mencobanya ... Saya pada dasarnya tidak dapat mengujinya dengan kode sampel Anda (selain menghapusISerializable
karya, tetapi Anda sudah tahu itu) ... Anda harus melihat apakahISafeSerializationData
cukup cocok untuk Anda.PS2:
SecurityCritical
atribut tidak bekerja karena diabaikan saat perakitan dimuat dalam mode kepercayaan parsial ( pada keamanan Level2 ). Anda bisa melihatnya pada contoh kode Anda, jika Anda debugtarget
variabel dalamExecuteUntrustedCode
tepat sebelum memohon, itu harusIsSecurityTransparent
untuktrue
danIsSecurityCritical
untukfalse
bahkan jika Anda menandai metode denganSecurityCritical
atribut)sumber
Jawaban yang diterima begitu meyakinkan sehingga saya hampir percaya ini bukan bug. Tetapi setelah melakukan beberapa percobaan sekarang saya dapat mengatakan bahwa keamanan Level2 benar-benar kacau; setidaknya, ada sesuatu yang sangat mencurigakan.
Beberapa hari yang lalu saya mengalami masalah yang sama dengan perpustakaan saya. Saya dengan cepat membuat tes unit; namun, saya tidak dapat mereproduksi masalah yang saya alami di .NET Fiddle, sementara kode yang sama "berhasil" membuat pengecualian di aplikasi konsol. Akhirnya saya menemukan dua cara aneh untuk mengatasi masalah tersebut.
TL; DR : Ternyata jika Anda menggunakan jenis internal pustaka bekas dalam proyek konsumen Anda, maka kode tepercaya sebagian berfungsi seperti yang diharapkan: ia dapat membuat contoh
ISerializable
implementasi (dan kode penting keamanan tidak dapat dipanggil secara langsung, tapi lihat di bawah). Atau, yang lebih konyol lagi, Anda dapat mencoba membuat kotak pasir lagi jika tidak berfungsi untuk pertama kalinya ...Tapi mari kita lihat beberapa kode.
ClassLibrary.dll:
Mari kita pisahkan dua kasus: satu untuk kelas reguler dengan konten penting keamanan dan satu
ISerializable
implementasi:Salah satu cara untuk mengatasi masalah tersebut adalah dengan menggunakan tipe internal dari perakitan konsumen. Tipe apa pun akan melakukannya; sekarang saya mendefinisikan atribut:
Dan atribut yang relevan diterapkan pada perakitan:
Tanda tangani majelis, terapkan kunci ke
InternalsVisibleTo
atribut dan bersiap untuk proyek uji:UnitTest.dll (menggunakan NUnit dan ClassLibrary):
Untuk menggunakan trik internal, perakitan pengujian harus ditandatangani juga. Atribut perakitan:
Catatan : Atribut dapat diterapkan di mana saja. Dalam kasus saya, itu adalah metode di kelas tes acak yang membutuhkan beberapa hari untuk saya temukan.
Catatan 2 : Jika Anda menjalankan semua metode pengujian bersama-sama, mungkin saja pengujian tersebut akan lulus.
Kerangka kelas tes:
Dan mari kita lihat kasus uji satu per satu
Kasus 1: Implementasi yang dapat dialihkan
Masalah yang sama seperti di pertanyaan. Tes lulus jika
InternalTypeReferenceAttribute
diterapkanJika tidak, akan muncul
Inheritance security rules violated while overriding member...
pengecualian yang sama sekali tidak pantas saat Anda membuat instanceSerializableCriticalClass
.Kasus 2: Kelas reguler dengan anggota penting keamanan
Tes lulus dalam kondisi yang sama seperti yang pertama. Namun, masalahnya sangat berbeda di sini: kode yang dipercaya sebagian dapat mengakses anggota penting keamanan secara langsung .
Kasus 3-4: Versi kepercayaan penuh dari kasus 1-2
Demi kelengkapan, berikut adalah kasus yang sama dengan kasus di atas yang dijalankan dalam domain tepercaya sepenuhnya. Jika Anda menghapus
[assembly: AllowPartiallyTrustedCallers]
tes gagal karena Anda dapat mengakses kode kritis secara langsung (karena metode tidak lagi transparan secara default).Epilog:
Tentu saja, ini tidak akan menyelesaikan masalah Anda dengan .NET Fiddle. Tapi sekarang saya akan sangat terkejut jika itu bukan bug dalam framework.
Pertanyaan terbesar bagi saya sekarang adalah bagian yang dikutip dalam jawaban yang diterima. Bagaimana mereka keluar dengan omong kosong ini? Ini
ISafeSerializationData
jelas bukan solusi untuk apa pun: ini digunakan secara eksklusif olehException
kelas dasar dan jika Anda berlanggananSerializeObjectState
acara (mengapa itu bukan metode yang dapat diganti?), Maka negara juga akan dikonsumsi olehException.GetObjectData
akhirnya.The
AllowPartiallyTrustedCallers
/SecurityCritical
/SecuritySafeCritical
tiga serangkai atribut dirancang untuk persis penggunaan yang ditunjukkan di atas. Tampaknya tidak masuk akal bagi saya bahwa kode tepercaya sebagian bahkan tidak dapat membuat contoh suatu tipe terlepas dari upaya menggunakan anggota penting keamanannya. Tapi itu adalah omong kosong yang lebih besar ( sebenarnya lubang keamanan ) bahwa kode yang dipercaya sebagian dapat mengakses metode kritis keamanan secara langsung (lihat kasus 2 ) sedangkan ini dilarang untuk metode transparan bahkan dari domain yang sepenuhnya tepercaya.Jadi jika proyek konsumen Anda adalah tes atau perakitan terkenal lainnya, maka trik internal dapat digunakan dengan sempurna. Untuk .NET Fiddle dan lingkungan sandbox kehidupan nyata lainnya, satu-satunya solusi adalah kembali ke
SecurityRuleSet.Level1
hingga ini diperbaiki oleh Microsoft.Update: Sebuah tiket Komunitas Pengembang telah dibuat untuk masalah ini.
sumber
Menurut MSDN lihat:
sumber
GetObjectData
secara eksplisit, tetapi melakukannya secara implisit tidak membantu.