CLR 全面透彻解析Silverlight 2 中的安全性
2008-10-26 11:49:35 来源:WEB开发网目录
权限和不受信任代码的沙箱处理
透明模型
CoreCLR 中的透明度
继承规则
向 Silverlight 透明度公开
在 2008 年 8 月《MSDN 杂志》的“CLR 全面透彻解析”专栏中,Andrew Pardoe 简要说明了 CoreCLR 安全模型。他介绍了为什么我们选择不使用 Silverlight 中的代码访问安全性 (CAS) 策略,还介绍了透明代码、SafeCritical 代码和关键代码之间的区别。在本期内容中,我将详细介绍透明模型是如何生成的、它在 Silverlight 中的工作原理以及预期会在未来的版本中实现哪些功能。
权限和不受信任代码的沙箱处理
在深入探讨透明模型之前,先来了解在完整的 Microsoft .NET Framework 中有关 CLR 安全性的一些背景知识对您会大有裨益。CLR 提供了一些机制,可根据代码中显示的代码运行位置、代码签名(如果存在)等证据来确定代码具有的功能或权利(由权限表示)。这可确保来自不受信任位置(如 Internet)的代码不具有来自受信任位置(如本地计算机的 Program Files 文件夹)的代码所具有的权限。
CLR 在其自己的应用程序域中运行各个托管应用程序。从不受信任位置安全运行代码的首选机制是在经过沙箱处理的应用程序域中运行它。在这些域中运行的代码被限定为只具有沙箱权限,在这里沙箱是指与部分受信任代码相对应的具有有限权限的环境。通常,经过沙箱处理的应用程序域会将代码分为两组,即完全信任和部分信任。在本节后面的部分我将详细介绍这两个组的用途以及如何区分它们。
为使这种分离更易于实现和使用,我们在 .NET Framework 2.0 中引入了简单沙箱处理 API,它们将为其沙箱创建具有给定权限集的各个应用程序域,还将创建完全受信任的程序集的列表(这些程序集并不在“全局程序集缓存”(GAC) 中,因为 GAC 中的所有程序集都已完全受信任)。已加载到此应用程序域中但不在完全信任程序集列表中的任何内容都将获得沙箱授予的权限集。如果是从 Internet 运行的应用程序,则这将是内置的 Internet 权限集。Shawn Farkas 是 CLR 安全团队中的一名软件设计工程师,他在博客中对有关如何使用此 API 做了精彩介绍,网址为 go.microsoft.com/fwlink/?LinkId=124952。
利用此模型,主机(例如浏览器插件)将为在其下运行的每个应用程序都创建一个应用程序域,这些将作为部分受信任的内容被加载并获得沙箱的权限集。主机可能还希望(安全地)封装一些完全信任的托管或本机函数,并以库的形式将它们提供给托管应用程序,这些库程序集将位于完全信任程序集列表中。例如,文件系统访问是一种特权操作,但将信息保存在本地非常有用,并且如果处理正确(位置固定、从不执行等),它也可以非常安全。但如果真正公开此功能还是会面临很大挑战。
透明模型
实际上,透明模型对 CoreCLR(Silverlight 版本的 CLR)而言并非新事物,自从 .NET Framework 2.0 推出以来它就一直存在。最初引入它是为了解决将服务公开给部分受信任代码的框架库数量不断增加的问题,以及它们在 .NET Framework 团队内引发的内部安全审计问题。具有 AllowPartiallyTrustedCallers (APTCA) 属性的库会向部分受信任代码公开完全信任服务,在这里部分受信任代码是指需要对 APTCA 库进行适当安全检查的具有潜在危险的一些内容。
在引入透明度之前,整个 APTCA 程序集都要进行代码审计。这要付出极其昂贵的代价,尤其是考虑到许多库可能是在调用常见的特权函数,而它们在其他情况下却是完全安全的。
我们开发透明度是为了在特权代码与非特权代码之间创建一个强大的隔离边界。在此模型中有两类代码,即透明代码和关键代码。透明代码有以下限制:
不能声明权限或进行提升。
不能包含不安全或不可验证的代码。这被视为一种关键操作,如果它们出现在透明代码中,则会导致各要求被注入。
不能直接调用关键代码。本章稍后会对例外情况加以说明。
不能调用本机代码或具有 SuppressUnmanagedCodeSecurity 属性的 API。
无法满足 LinkDemands。所有 LinkDemands 都将成为完整的堆栈遍历要求。
所有权限要求都流过透明代码且与应用程序域相悖;透明代码无法声明这些要求。因此,透明代码始终被限制为具有沙箱权限。关键代码没有这种限制,它可以执行上述操作,因此 APTCA 程序集的安全审计负担被减少到只有程序集的关键区域。
那么两种类型的代码是如何交互的?如果开发人员认为关键 API 可以安全地从透明代码进行调用(例如,调用操作系统来检索日期和时间的 API),则他可以将其标记为 TreatAsSafe(代码中的实际属性:SecurityTreatAsSafe)来实现这一点。另外需要注意的重要一点是,在 2.0 透明模型中,透明度规则仅限于程序集,因此,公共 Criticals 就是暗指 TreatAsSafe。(在本专栏的通篇,我都将此透明模型称为 2.0 透明模型。实际上,此透明模型也是 .NET Framework 3.0、3.5 和 3.5 SP1 版本的一部分。)因此,公共 Critical 方法可通过透明代码调用。
2.0 透明模型在 .NET Framework 的后续桌面版本中作用非常大,它使安全性更易于推理得出,还可以使团队能够更方便地向部分信任应用程序公开其服务。借助 CoreCLR,我们不但可以改进透明系统,而且还可以使其成为在整个运行时强制保证安全并基于它构建代码的主要机制。
CoreCLR 中的透明度
虽然透明度在访问程序集的安全性方面很有帮助,但它并不是 .NET Framework 中有关安全性的唯一决定因素。不同应用程序域的不同沙箱可能具有不同的权限集,从 Internet 运行的应用程序与从企业 Intranet 运行的应用程序具有不同的权限。因此,必须在代码中检查并声明这些权限。
我们知道,所有基于 CoreCLR 构建的丰富 Internet 应用程序都是在 Web 浏览器内运行的,因此使用 CoreCLR 我们可以做出一些简单假设。第一个假设是每个应用程序域都有一个固定的沙箱,我们不必处理各个单独的权限,因为我们已经知道了各个 Silverlight 应用程序将具有哪些权限。只有两个权限集,即完全信任和 Silverlight 沙箱。我们做出的第二个假设是所有 Silverlight 应用程序都是部分受信任的,因此,其权限不能提升到超过 Silverlight 沙箱的权限。
这些都属于很重要的简化处理,它们可以产生以下结果:所有 Silverlight 应用程序都是透明的并接收 Silverlight 沙箱权限集,Silverlight 平台库(其中大部分是完全信任的)将仅由 Silverlight 应用程序调用,因此我们不必要求特定的权限;将危险的 API 标记为 Critical 就足够了。最后,由于没有任何要求,所以我们永远也不需要停止权限堆栈遍历,并且也不需要声明。
这样一来,透明度成为 CoreCLR 中的唯一安全执行机制,我们可以忽略大部分 CAS。为使其成为更强大的执行机制,我们加强了透明代码的规则,并对透明系统做了更多改进,使其更加直观和有效。
您可能已经注意到,对于 2.0 透明模型,在透明代码中执行的不安全或不可验证的代码会导致注入 UnmanagedCodePermission 要求,而不是立刻导致故障/异常。通常,此权限只应在完全信任的情况下可用,所以此代码在部分信任情况下将失败,但在其他情况下均有效。
但是对于 CoreCLR,我们并没有引入权限要求的概念,因此在发生这种情况时将引发异常(而不是在新透明模型中注入要求)。虽然此类型的代码在两个模型中均会失败,但现在其性能却提高了,因为在最终失败之前我们不必处理要求的堆栈遍历。
而且在 Silverlight 中,所有代码默认都是透明的。这包括 Silverlight 平台库;所有关键代码都必须显式批注。这为我们提供了一个安全的默认设置,可减少意外创建隐式 Critical 方法的可能。此外还添加了许多关于类型继承和方法覆盖的规则,对此我将在“继承规则”部分加以解释。
我之前曾提到过,标有 TreatAsSafe 的关键 API 被视为可从透明代码调用。我还指出,由于桌面框架上的透明度仅适用于程序集,因此公共关键 API 就是暗指 TreatAsSafe。在 CoreCLR 中,我们创建了一个新属性 SafeCritical(再次重申,实际批注是 SecuritySafeCritical),用来描述既被视为关键代码又可以从透明代码调用的代码。现在,透明度不但在程序集内部应用,而且还跨程序集应用,因此关键代码(无论是否为公共)将无法从透明代码调用。
这一改变有助于消除对如何向透明代码公开关键代码所产生的混淆。首先,所有关键代码都受限制,公共代码也不例外,因此添加关键批注始终都意味着透明代码无法对其进行调用。而且,只有当 Critical 属性已经存在时,TreatAsSafe 才有意义(要求两个属性可能会导致混淆,因为可以将 TreatAsSafe 放在 Transparent 方法上,即使它并不执行任何操作)。最后,由于只有一种方法指定安全代码,因此关键代码可进一步简化模型。
对这一新属性需要了解的一件事就是当我们谈论关键代码时,通常也包括了 SafeCritical 代码。这是因为 SafeCritical 代码是关键代码,它具有与关键代码同样的功能,此外它还可以由透明代码调用。因此,为使其真正成为安全关键代码,库开发人员必须确保在调用关键代码(不是安全代码)之前,进行相应的输入验证或其他安全检查,并确保在之后进行必要的输出验证和副作用检查。在这里 SafeCritical 变成了一项约定,它声明此方法访问的是关键代码,但要在访问之前和之后进行所有相应检查。在新的透明模型中,安全审计的焦点是 SafeCritical 代码。
继承规则
为使透明模型有效,我们还必须确保类型或方法的闭包也是安全的。在派生类型或方法时,如果使用的资源访问权限不同于基类型或基类方法的权限,则会引发一些访问保护问题,尤其是在转换为更具访问性的父类型或接口时。例如,假设某人想创建透明虚拟方法的一个关键覆盖方法(如 ToString),然后又想通过转换为 Object.ToString 来防止透明代码调用该覆盖方法。
您可以假设对透明级别存在一个访问和功能层级,因此当您从透明代码依次转换为 SafeCritical 代码和关键代码时,访问将会更受限制和/或功能得到不断增强。(在这里,访问是指包含以及调用此级别代码的能力)。类型的继承规则是指派生类型的受限程度必须至少与基类型一样。这可防止开发人员意外创建透明代码所调用的关键类型的重载。图 1 显示了此模式所允许的内容。
图 1 允许的类型继承模式
基类型 | 允许的派生类型 |
透明 | 透明、SafeCritical 或关键 |
SafeCritical | SafeCritical、关键 |
关键 | 关键 |
虚拟方法覆盖规则有所不同 — 派生方法必须具有与基类方法相同的透明代码可访问性。图 2 显示了此模式所允许的内容。
图 2 允许的方法继承模式
基类(虚拟/接口)方法 | 允许的覆盖方法 |
透明 | 透明、SafeCritical |
SafeCritical | 透明、SafeCritical |
关键 | 关键 |
请注意,我们不允许透明虚拟方法的关键覆盖,因为这样一来,在调用时很容易地就可以转换为基类方法并绕过对覆盖的关键检查。我们允许透明函数的 SafeCritical 覆盖,因为这种转换不会导致任何后果 — 无论怎样透明代码都可以访问 SafeCritical 代码。我们还允许 SafeCritical 虚拟方法的透明覆盖,乍看起来这似乎有些令人惊讶,但这实际上是安全的,因为这种转换同样不会导致任何危险后果。
总而言之,对于 Silverlight 透明模型,请记住默认情况下所有代码都是透明的。所有 Silverlight 应用程序也是完全透明的。所有指示其他内容的标记将被忽略。此外,固定的权限集表示透明度是 CoreCLR 所需的唯一执行机制。图 3 总结了这三种类型的代码及其功能和特点。
图 3 比较透明代码、SafeCritical 代码和关键代码
透明 | SafeCritical | 关键 | |
访问限制 | 不能直接调用关键代码。 | 可以实现关键代码的所有功能。 必须确保在调用关键代码之前和之后执行相应的检查。 | 有权限且有能力调用任何代码。仅等效于完全信任。 永远不能由透明代码直接调用,但可以通过 SafeCritical 代码公开。 |
不安全代码 | 不能包含不安全或不可验证的代码。 | 可以包含不安全或不可验证的代码。 | 可以包含不安全或不可验证的代码。 |
可用于应用程序或平台代码 | 在应用程序和平台代码中均存在。 | 仅对 Silverlight 平台程序集可用。 | 仅对 Silverlight 平台程序集可用。 |
类型继承特征 | 类型必须从其他透明类型派生。 | 类型必须从透明或 SafeCritical 类型派生。 | 类型可以从所有类型派生(与批注无关)。 |
方法覆盖特征 | 方法必须覆盖透明或 SafeCritical 虚拟/接口方法。 | 方法必须从透明或 SafeCritical 虚拟/接口方法派生。 | 方法必须从关键虚拟/接口方法派生。 |
向 Silverlight 透明度公开
Silverlight 透明模型的许多优势都表现在简化 Silverlight 平台的安全执行方面。由于 Silverlight 是一个封闭式平台,因此目前没有受信任第三方库的概念。因而,开发人员与 Silverlight 透明模型的交互仅限于允许其调用的 API(透明或 SafeCritical),因为他的应用程序是完全透明的。但是对于 .NET Framework 平台,它并不是封闭的,因此可以开发附加库以使安全执行更为简单。
开发人员或许能够在下一个主要版本的桌面框架中看到这些改进。我们在 .NET Framework 2.0 中引用的透明模型使分析和判断 APTCA 库的安全性变得更加简单;Silverlight 透明模型在这一方面又向前迈进了一步,它使我们能够做出一些假设和保证,而这些在以前必须要通过仔细的审查得到确认。在未来,我们计划在 .NET Framework 中全面引入围绕透明代码的严格规则、SafeCritical 属性以及透明继承规则。我们还计划将其提供给外部开发人员。
桌面框架与 Silverlight 之间的一个主要区别在于桌面框架上的大多数应用程序是针对完全信任环境构建的,而所有 Silverlight 应用程序都是针对部分信任环境构建的。大部分桌面应用程序可以根据需要执行任何操作而且可以继续这样做,完全不必考虑透明问题。在实现过程中,它们将被视为关键应用程序。
APTCA 库开发人员将会体验到我们 Silverlight 平台程序集的许多优势。APTCA 库的可审核方面现在侧重于 SafeCritical 层,因为关键层无法从部分受信任的透明代码进行访问。而且,继承规则的引入可确保库的覆盖和派生不会在无意间暴露任何安全漏洞。(但请记住,由于桌面框架中存在各种可能的沙箱,因此 APTCA 库必须分别处理各个权限。这使得 SafeCritical API 可能必须执行权限要求以及其他输入验证检查。)
更为严格的透明代码规则还意味着部分受信任的应用程序将比以往更加安全、性能更强。在 .NET Framework 2.0 透明模型中,调用或包含不安全或不可验证代码的透明代码要面对被注入的 UnmanagedCodePermission 要求,还要在调用不安全/不可验证代码时面对代价高昂的堆栈遍历。这通常会导致故障,因为大部分沙箱的权限授予集中都不包含此权限。
在新的透明模型中,这些被视为硬故障,因为在 CoreCLR 中,没有注入的要求,因而也没有堆栈遍历。通过将新透明模型引入下一版本的桌面框架,我们希望使开发 APTCA 库的工作变得更加轻松,并改善在开发部分受信任应用程序时的体验。
请将您想询问的问题和提出的意见发送至 clrinout@microsoft.com。
Andrew Dai 是 Microsoft CLR 团队的一名项目经理。他负责 Silverlight 和桌面框架的安全性问题。
更多精彩
赞助商链接