Effective C# 原则47:选择安全的代码
.Net运行时已经设计好了,一些怀有恶意的代码不能渗透到远程计算机上并执行。目前一些分部式系统依懒于从远程机器上下载和执行代码。如果你可以通过Internet或者以太网来发布你的软件,或者直接从web上运行,但你须要明白CRL在你的程序集中的一些限制。如果CLR不是完全相信一个程序集,它会限制一些的行为。这些调用代码要有访问安全认证(CAS)。从另一方面来说,CLR强制要求基于角色的安全认证,这样这些代码才能或者不能在基于一个特殊的角色帐号下运行。
安全违例是运行时条件,编译器不能强制它们。幸运的是,它们绝不会在你的开发机器上出现,而且你所编译的代码从你自己的硬件上加载,这就是说,它有更高的信任级别。讨论所有潜在的.Net安全模型可以足足写上几本书,但你可以了解合理行为的一小部份,这样可以让你的程序集与.Net的安全模式更容易的交互。这些推荐只有在你创建一个组件程序库时,或者是开发一些通过网络发布的组件和程序集是才可以参考应用。
通过这个讨论,你应该记住.Net是一个托管的环境。这个环境保证有一个明确的安全环境。在安装时可以用.Net的配置策略来管理安全策略。大多数.Net框架库是在安装时对配置策略是安全信任的。它会查明安全问题,这就是说CLR可以检测IL而且确保它不会有什么潜在的危险行为,例如直接访问原始内存。它不会在访问本地资源时要求特殊的安全权限进行断言。你应该试着遵守同样的检测,如果你的代码不须要任何的安全权限,就应该避免使用CAS的API来对断定访问权限,否则你所做的只是降低程序性能。
你要使用CAS的API来访问一些受保护的资源,而这些资源是要求增加的特权的。很多通用的受保护资源是非托管的内存和文件系统。其它一些受保护的资源还包括数据库,网络端口,windows注册表,以及打印子系统。在每种情况下,如果调用代码没有足够的许可,试着访问这些资源都会引发一个异常。而且,访问这些资源可能引发运行时建立一个安全栈上的询访,以确保当前栈上的所有的程序集有恰当的许可。让我们看一下内存以及文件系统,讨论安全系统和机密问题中最实际的一些问题。
不管什么时候,你都可以通过创建恰当的安全程序集来避免非托管内存访问。一个安全的程序集,也就是一个不用使用任何指针来访问其它非托管,或者托管的堆内存。不管你是否知道,你所创建的所有C#代码几乎都是安全的。除非你在C#编译器上打开了不安全的编译开关/unsafe,否则你所创建的都是安全代码(译注:就算打开了开关也不是说就编译成不安全代码了,还要看你的代码是怎样写的。)。/unsafe充许用户使用CLR不进行的验证的指针。
要使用不安全代码的原因很少,特别是一常规的任务中。指向原始内存的指针比要检测的安全的引用快一些。在一些经典的数组中,它们可能要快上10倍以上。但当你使用不安全结构时,要明白任何的不安全代码都会影响整个程序集。当你创建不安全块时,应该考虑把这些算法独立到一个程序信中(参见原则32)。这样可以在整个程序上限制不安全代码的影响。如果它是独立的,只有实际调用它的访问者才会受到影响。其它剩下的,你还是可以在更严格的环境中使用安全机制。你可能还须要不安全代码来处理一些须要直接指针的P/Invoke或者COM接口。同样的推荐:独立它。不安全代码只会影响它自己的小程序集,不再有其它的。
对于访问的建议很简单:只要可能,都应该避免访问非托管内存。
接下来的安全核心就是文件系统。程序要存储数据。从Internet上下载回来的代码,文件系统中的大多数地方都不能访问,否则会有很大的安全漏洞。是的,完全不许访问文件系统就很难创建能使用的程序。通过使用独立存储可以解决这一问题。独立存储可以穿越基于程序集而独立的虚拟的目录,以及应用程序域,以及当前的用户。选择性的,你可以使用更一般的独立存储虚拟目录,该目录是基于程序集或者当前用户的。
实际上,受信任的程序集可以访问他们自己特殊的独立存储区域,但不能是文件系统的其它地方。独立的存储目录是隐藏在其它程序集以及其它用户中的。你可以使用System.IO.IsolatedStorage名字空间中的类来访问独立的存储。IsolatedStorageFile类包含的方法可以很简单的访问System.IO.File类。实际上,它是从 System.IO. FileStream 类派生下来的。写内容到独立存储的代码几乎与写内容到任何文件里是一样的:
IsolatedStorageFile iso =
IsolatedStorageFile.GetUserStoreForDomain( );
IsolatedStorageFileStream myStream = new
IsolatedStorageFileStream( "SavedStuff.txt",
FileMode.Create, iso );
StreamWriter wr = new StreamWriter( myStream );
// several wr.Write statements elided
wr.Close();
读操作也是完全和其它使用文件I/O相似的:
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForDomain( );
string[] files = isoStore.GetFileNames( "SavedStuff.txt" );
if ( files.Length > 0 )
{
StreamReader reader = new StreamReader( new
IsolatedStorageFileStream( "SavedStuff.txt",
FileMode.Open,isoStore ) );
// Several reader.ReadLines( ) calls elided.
reader.Close();
}
你可以独立存储来持久大小合适的数据元素,这些元素可以被代码部分信任,用于从一个安全分离的本地磁盘上的某个地方存储和载入信息。.Net环境为每个程序定义和限制了独立存储的大小。这可以预防一些恶意的代码占用磁盘空间,让系统就得不可用。独立存储对于其它程序和其它用户来说是不可见的。也就是说,它不应用于要管理员手动操作才能布署或者配置设置的情况。即使这它是隐藏的,然而,独立存储对于从受信任的用户那来里来的非托管代码来说也是不受保护的。不要用独立存储来存储一些高度机密的内容,除非你的程序给它加过密。
在文件系统中创建一个可能要许可安全策略的程序集时,要独立存储流的内容。当你的程序集可能在web上运行,或者可能被运行在web上的代码访问时,应该考虑使用独立存储。
你可能须要正确的使用一个受保护的资源。一般情况下,访问这些资源要指出你的程序要被完全信任。唯一可选的就是完全避免使用这些受保护的资源。例如,考虑windows的注册表,如果你和程序须要访问注册表,你必须安装你的程序到最终用户的机器上,这样才能有必须的权限来访问注册表。你想简单点,从web上运行的程序是不能建立注册表的修改的。安全策略就应该是这样的。
.Net的安全模型意味着你的程序的行为是要权得进行核对的。注意你的程序所要求的权利,而且试着最小化它们。不必要求你不使用的权利。你的程序集越是少的要求受保护资源,那么它们就越是可以保证安全策略异常不抛出。避免使用机密资源,如果可能,要考虑其它可选方案。当你在某个算法上确实须要更高安全的许可时,应该独立这些代码到它们自己的程序集中。