स्ट्रक्चर्स का उपयोग करते समय .NET फ्रेमवर्क में VerificationException
निम्नलिखित कोड पर विचार करें:
private static readonly ReadonlyStruct Struct;
public static void Read()
{
Console.WriteLine(Struct.Value);
}
public struct ReadonlyStruct
{
public ReadonlyStruct(int value)
{
Value = value;
}
public int Value;
}
फिर भी, यदि हम इस प्रोग्राम को .NET Framework में आंशिक विश्वास के अंतर्गत चलाने का प्रयास करते हैं तो क्या होता है?
static void Main(string[] args)
{
var permissionSet = new PermissionSet(PermissionState.None);
permissionSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
permissionSet.AddPermission(new ReflectionPermission(PermissionState.Unrestricted));
var appDomain = AppDomain.CreateDomain("ChildDomain", null, AppDomain.CurrentDomain.SetupInformation, permissionSet);
try
{
appDomain.DoCallBack(RRead);
}
catch (VerificationException ex)
{
Console.WriteLine("Read failed: " + ex);
}
}

जैसे ही मैं फ़ील्ड readonly
से कीवर्ड हटाता हूं, एप्लिकेशन क्रैश होना बंद हो जाता है। यदि आप C# के पुराने संस्करण को लक्षित करते हैं, उदाहरण के लिए csproj फ़ाइल में Struct
सेट करके, यहां तक कि निराला भी, यह दुर्घटनाग्रस्त होना भी बंद कर देता है।<LangVersion>7</LangVersion>
स्पष्ट "आजकल कोई आंशिक भरोसे का उपयोग क्यों करेगा" प्रश्न के अलावा, मैं वास्तव में इस व्यवहार से हैरान था। इस कोड में स्पष्ट रूप से असुरक्षित कुछ भी नहीं है, इसलिए मैंने आगे खुदाई करने का फैसला किया।
आंशिक विश्वास का उपयोग करने के बारे में उत्तर देने के लिए, हमने इस त्रुटि को डेटाडॉग .NET ट्रैसर में पकड़ा, जो अब तक आंशिक विश्वास का समर्थन करता था। ऐसी अप्रचलित सुविधा का समर्थन करने का कारण केवल इतना है कि हमारे पास समर्थन छोड़ने का कोई बाध्यकारी कारण नहीं था। अब हम करते हैं।
आंशिक भरोसे के बारे में एक संक्षिप्त अनुस्मारक
उन लोगों के लिए जो काफी भाग्यशाली हैं जिन्हें कभी भी आंशिक भरोसे का सामना नहीं करना पड़ता है, यह एक ऐसी सुविधा है जो .NET फ्रेमवर्क के शुरुआती दिनों से मौजूद है। यह सीमित करने की अनुमति देता है कि किसी दिए गए एपडोमेन (या पूरे एप्लिकेशन में) में किस प्रकार का कोड चलाया जा सकता है, सैद्धांतिक रूप से संभावित असुरक्षित स्रोत से आने वाले कोड को सुरक्षित रूप से निष्पादित करने के लिए (एक प्लगइन सिस्टम के उदाहरण के लिए सोचें)। अन्य बातों के अलावा, आंशिक विश्वास खोजशब्द के उपयोग की मनाही करता है unsafe
, क्योंकि अन्यथा इसका उपयोग सीमाओं को दरकिनार करने के लिए किया जा सकता है।
मैंने इसके बारे में तब तक नहीं सोचा जब तक मुझे यह एहसास नहीं हुआ कि मना करना unsafe
उतना सीधा नहीं है जितना लगता है। unsafe
एक C# भाषा सुविधा है, लेकिन आंशिक विश्वास IL स्तर पर संचालित होता है, जहाँ यह अवधारणा मौजूद नहीं है। आप कल्पना कर सकते हैं कि असुरक्षित सी # कोड संकलित करते समय एक विशेष विशेषता जोड़ा जाता है, लेकिन यह आपको सीधे आईएल से तैयार की गई असेंबली से नहीं बचाएगा। तो रनटाइम के उपयोग का पता कैसे लगाता है unsafe
? यह आईएल को स्कैन करके और उन निर्देशों की जांच करके करता है जो संभावित रूप से असुरक्षित हो सकते हैं (उदाहरण के लिए, कुछ भी जो सीधे एक पते को लोड करता है), और उनके उपयोग को अच्छी तरह से परिभाषित (“सत्यापन योग्य”) स्थितियों तक सीमित करता है।
तो "संभावित रूप से असुरक्षित" क्या है?
अब जब हम जानते हैं कि कोड सत्यापन IL स्तर पर संचालित होता है, आइए देखें कि हमारा नमूना कोड क्या उत्पन्न करता है। मैंने इसे उन लोगों के लिए समझना आसान बनाने के लिए व्याख्या की है जो IL से परिचित नहीं हैं:
.field private static initonly valuetype PartialTrust.ReadonlyStruct Struct
.method public hidebysig static void Read() cil managed
{
.maxstack 8
// Load the address of the static Program.Struct field
ldsflda valuetype PartialTrust.ReadonlyStruct PartialTrust.Program::Struct
// Read the Value field as an int32
ldfld int32 PartialTrust.ReadonlyStruct::Value
// Invoke Console.WriteLine
call void [mscorlib]System.Console::WriteLine(int32)
// Return
ret
}
.field private static initonly valuetype PartialTrust.ReadonlyStruct Struct
.method public hidebysig static void Read() cil managed
{
.maxstack 8
// Load the value of the static Program.Struct field
ldsfld valuetype PartialTrust.ReadonlyStruct PartialTrust.Program::Struct
// Read the Value field as an int32
ldfld int32 PartialTrust.ReadonlyStruct::Value
// Invoke Console.WriteLine
call void [mscorlib]System.Console::WriteLine(int32)
// Return
ret
}
ऐसा लगता है कि यह परिवर्तन सी # 7.2 में पेश किया गया था। इस संस्करण ने रीडोनली स्ट्रक्चर पेश किए, जो अपरिवर्तनीय होने की गारंटी है और संकलक को अतिरिक्त अनुकूलन करने की अनुमति देता है (रक्षात्मक प्रति करने के बजाय संदर्भ द्वारा संरचना को पढ़ना)। मेरे नमूने में, संरचना वास्तव में केवल पढ़ने के लिए नहीं है, लेकिन क्योंकि यह केवल पढ़ने के लिए फ़ील्ड में संग्रहीत है, ऐसा लगता है कि यह संकलक के लिए समान अनुकूलन करने के लिए पर्याप्त है।
संक्षेप में, सी # 7.1 तक, कंपाइलर संरचना की एक प्रति बनाता है क्योंकि यह केवल पढ़ने योग्य क्षेत्र में संग्रहीत है और यह गारंटी देनी चाहिए कि मूल मूल्य में कोई बदलाव नहीं किया गया है। C# 7.2 से शुरू करते हुए, कंपाइलर इसके बारे में होशियार है और यह महसूस करता है कि संरचना से किसी फ़ील्ड को पढ़ने से कोई साइड-इफ़ेक्ट नहीं होने वाला है, और इसलिए कॉपी बनाना बंद कर देता है।
नोट: ऐसी कई अन्य स्थितियाँ हैं जहाँ C # 7.2+ एक रक्षात्मक प्रतिलिपि बनाना बंद कर देता है, यदि आप संरचना को रीडोनली कीवर्ड से सजाते हैं। इसी तरह, वे आंशिक विश्वास में एक सत्यापन अपवाद का कारण बनेंगे। लेकिन इस आलेख में जो नमूना मैं दिखा रहा हूं वह मेरी राय में अधिक दिलचस्प है क्योंकि यह उस नए कीवर्ड का उपयोग नहीं करता है, और कोड सी # के पुराने संस्करणों के साथ बिना किसी बदलाव के संकलित करता है।
ldsflda
मुद्दा क्यों है
अब तक हमने दो बातों की व्याख्या की है:
- आंशिक विश्वास यह सुनिश्चित करने के लिए IL की पुष्टि करता है कि कोड कुछ भी खतरनाक नहीं कर रहा है
- सी # 7.2 से शुरू होने से
ldsfld
इस नमूने में निर्देश को बदल दिया जाता हैldsflda
, जिससे संरचना को मूल्य के बजाय संदर्भ द्वारा पढ़ा जा सकता है
मैं यह समझना चाहता था कि वास्तव में कौन सा नियम के उपयोग से टूट जाता है ldsflda
, इसलिए मैंने जेआईटी के स्रोत कोड की जांच की। .NET Core में आंशिक विश्वास को हटा दिया गया है, इसलिए हमें .NET Framework के स्रोत कोड की जाँच करने की आवश्यकता है। यह सार्वजनिक रूप से उपलब्ध नहीं है, लेकिन आप जीथब पर CoreCLR रिपॉजिटरी की पहली कमिट की जांच करके एक करीबी सन्निकटन प्राप्त कर सकते हैं , इससे पहले कि यह बहुत अधिक हो जाए।
एक देशी डिबगर के साथ कोड को निष्पादित करके ( x64dbg की डिसएस्पेशन विंडो बहुत अच्छी है, मैं इसकी अनुशंसा करता हूं), और CoreCLR C++ कोड को क्रॉस-रेफरेंस करते हुए, मैं अंततः इसे उन कुछ पंक्तियों तक सीमित करने में सक्षम था:
CORINFO_CLASS_HANDLE enclosingClass = pResolvedToken->hClass;
unsigned fieldFlags = fieldInfo.fieldFlags;
CORINFO_CLASS_HANDLE instanceClass = info.compClassHnd; // for statics, we imagine the instance is the current class.
bool isStaticField = ((fieldFlags & CORINFO_FLG_FIELD_STATIC) != 0);
if (mutator)
{
Verify(!(fieldFlags & CORINFO_FLG_FIELD_UNMANAGED), "mutating an RVA bases static");
if ((fieldFlags & CORINFO_FLG_FIELD_FINAL))
{
Verify((info.compFlags & CORINFO_FLG_CONSTRUCTOR) &&
enclosingClass == info.compClassHnd && info.compIsStatic == isStaticField,
"bad use of initonly field (set or address taken)");
}
}
x64dbg is great to annotate the assembly
Verify((info.compFlags & CORINFO_FLG_CONSTRUCTOR) &&
enclosingClass == info.compClassHnd && info.compIsStatic == isStaticField,
"bad use of initonly field (set or address taken)");
जोड़
यदि किसी कारण से आपको अभी भी आंशिक विश्वास का समर्थन करने की आवश्यकता है, तो मैं इस ध्वज को csproj में जोड़ने की अनुशंसा करता हूं:
<Features>peverify-compat</Features>