For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-preserve-virtualizability.md.
  • 简体中文
  • Preserve Host Virtualizability S1

    中文标题:保留宿主虚拟化能力

    提案概览
    提案速览

    该提案旨在防止宿主添加不可删除的扩展或以其他方式破坏虚拟化能力,确保 EcmaScript 启动代码可以完全模拟任何宿主。它解决了像 RegExp.leftContext 这样的历史问题,并提出了通用规则(例如,所有宿主添加的属性必须是可删除的),同时对浏览器的一个已知案例给予例外。该提案目前列出了剩余威胁,但尚未为它们提供详细解决方案。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    保留宿主虚拟化能力

    禁止宿主添加新的不可删除扩展,或通过其他方式意外破坏虚拟化能力。

    • Mark S. Miller @erights, Agoric
    • J.F. Paradis @jfparadis, Agoric
    • Caridy Patiño @caridy, Salesforce
    • Dan Finley @danfinlay, MetaMask
    • Alan Schmitt @brabalan, Inria

    背景:语言虚拟化能力

    JavaScript 是少数几种在语言和宿主环境之间做出强区分的语言之一。EcmaScript 标准语言几乎完全不含 I/O。不同的宿主——例如浏览器、服务器、嵌入式设备、区块链等——将 I/O 能力具体化为宿主对象。这些宿主对象最初仅提供在全局对象上,即全局作用域中,绑定到如 documentXMLHttpRequestprocessrequire 等名称。

    因此,EcmaScript 语言类似于指令集的用户态指令。它非常具有图灵完备性,即 EcmaScript 代码可以计算任何可计算函数,并具有强大的抽象机制来表达和组织计算。每个宿主类似于一组 I/O 设备和系统态指令。宿主对象的 API 类似于操作系统内核的系统调用接口。当 EcmaScript 对象调用宿主对象时,它实际上是在进行系统调用。所有 EcmaScript 对宿主对象的访问都始于在全局对象上查找名称。理想情况下,EcmaScript 启动代码可以替换这些全局名称绑定,以模拟任何其他可能的宿主,供同一环境中的启动后 EcmaScript 代码使用。

    根据 Popek 和 Goldberg 的开创性工作,我们说一个 EcmaScript 系统是完全可虚拟化的,当 EcmaScript 启动代码可以轻松且完美地模拟任何可能的宿主给任何启动后的 EcmaScript 代码,而无需重写启动后的代码。扩展我们的类比,这种启动代码就像第一个引导代码,它看到真实的系统态架构和设备。如果它安装了虚拟机——即对不同系统态架构和设备的模拟——接下来的代码可能是针对那个架构的启动代码,而不知道它不是首先运行,也不是在“实际”架构上运行。

    图灵完备性保证任何通用机器都可以完美模拟任何其他机器;那么“虚拟化能力”如何成为一个清晰的区别?再次遵循 Popek 和 Goldberg 的观点,这就是为什么无需重写是重要的试金石。x86 不是一个完全可虚拟化的架构。然而,QEMU 和 VMWare 可以在 x86 上完美模拟 x86,但只能通过复杂、昂贵且脆弱的方法,等同于解释或重写。由于 EcmaScript 解析成本高且难以准确解析,我们更有必要维护完全可虚拟化能力。

    TC39 历史教训

    在当前实际的 EcmaScript/宿主部署中,虚拟化能力的违反很少,所有都是偶然的,且没有一个是致命的。然而,EcmaScript 规范目前允许宿主以对任何有用目的都无益的方式不可挽回地破坏虚拟化能力。我们的历史表明,我们必须收紧规范,以避免仅仅因为不幸的意外而失去虚拟化能力。

    在 EcmaScript 5 之前,DOM 对象非常神奇。它们有各种奇特的行为,用 EcmaScript 编码的对象无法模拟。例如,对 DOM 对象属性的赋值可能触发行为。为了缩小这一差距,EcmaScript 5 标准化了访问器属性,即具有 getter 和 setter 的属性。DOM 对象可能具有不可赋值或不可删除的属性,因此 EcmaScript 5 引入了带有可写性和可配置性控制的显式属性描述符。然而,当 tc39 在缩小这一差距时,w3c/whatwg 同时在扩大它——引入了更多宿主对象行为,例如 local storage,即使 EcmaScript 5 对象也无法模拟。

    为了修复语言与宿主之间的这种不协调,我们发明了新的约束。在 EcmaScript 方面,我们编纂了一组对象不变量,任何对象,无论是 EcmaScript 还是宿主,都不得违反。我们提供了一种新的抽象机制,直接代理,它可以模拟几乎任何遵守这些不变量的宿主对象,并且自身也不能违反这些不变量。在 w3c/whatwg 方面,我们设计了一种新的 WebIDL,它只能指定代理可以模拟的行为。一旦修复,这个特定的虚拟化破坏就保持了修复状态。

    对虚拟化能力的剩余威胁

    从这些历史教训中概括,EcmaScript 规范还有哪些其他方式给了宿主过多的自由,从而可能意外破坏虚拟化能力?我们应该如何收紧规范以排除这些危险?

    不可删除的扩展

    EcmaScript 规范允许宿主向任何对象以及全局对象添加新属性,包含任何可能的属性配置。例如,宿主可以向 Array.prototype 添加不可配置、不可写的 peekpoke 数据属性,其值是用于直接读取或写入任何物理内存地址的内置函数。如果这些函数保持可用,内存安全将丧失。然而,启动代码无法在不重写后续代码的情况下消除它们。计算 [] 会创建一个继承自原始 Array.prototype 的数组。因此,原始 Array.prototype不可否认的,即使另一个对象绑定到名称 ArrayArrayprototype 属性,它仍然可用。

    SES-shim 的启动代码采用了白名单来删除任何它不认识的属性,这将消除这些假设的 peekpoke 属性,如果它能删除它们的话。(如果白名单机制无法删除这些属性,那么 SES-shim 将停止,安全失败,但仍然失败。)我们提议,宿主向任何对象(包括全局对象)添加的任何此类额外属性必须是可删除的。这比说属性必须是可配置的更强。由于不可配置的属性不能被删除,可删除的属性必须是可配置的。但可配置的属性可能仍然实际上不可删除。相反,我们要求对于每个额外提供的宿主属性,尝试使用 deleteReflect.delete 删除它必须成功消除该属性。

    最近的一个例子是 RegExp.leftContext,这是一个非标准的宿主添加,提供了一个破坏安全的全局通信渠道。在大多数浏览器上,这个属性是不可删除的。为了修复这个特殊情况,我们提出了 JavaScript 中的传统 RegExp 特性 来既将这些属性编纂为 规范性可选,以便符合规范的实现可以省略它们,又使其 可删除,以便启动代码在任何未省略它们的地方可靠地消除它们。(截至撰写本文时,RegExp.leftContext 在 Firefox 上仍然不可删除,这并不致命,只是因为 RegExp 构造函数是可否认的。)Error Stacks 是一个类似的特殊案例修复,针对破坏封装的、目前非标准的 error.stack 属性。

    与其提出永无止境的此类提案来修复出现的特殊情况,本提案旨在解决一般问题,从而无需更多的特殊情况。如果本提案已经是标准,那么不会存在非标准的不可删除的 RegExp.leftContext,也不需要特殊情况的修复。

    不可删除的全局属性

    在后 EcmaScript 5 时代,普通对象上危险的、非标准的、不可删除的属性,例如 RegExp.leftContext,已经罕见地出现。然而,在浏览器上,全局对象上仍然存在危险的、非标准的、不可删除的属性。直到最近,我们都认为这些是不可修复的。因此,所有支持虚拟化的努力——例如 8 行魔法代码 位于 RealmsSESEvaluator shim 的核心——都以隐藏全局对象本身开始。然而,最近,Salesforce 的 Caridy Patino 和 Agoric 的 JF Paradis 发现的一种技术组合,表明浏览器全局对象可以被漂白变得无害

    结果是 iframe 的全局对象与其浏览器上下文断开连接,禁用了许多宿主功能,而不损害该帧内的 EcmaScript 语言。全局的不可删除属性没有被删除,它们提供的宿主对象仍然可访问。由于这类不可删除属性的链条,总共有六个这样不可否认的宿主对象。这六个不可否认的宿主对象的所有行为完全通过应用识别这些宿主对象的内置方法来实现。这些内置方法都不是不可否认的。漂白移除所有可移除的内容,包括所有这些内置方法,使不可否认的宿主对象安全地无用。我们很幸运。浏览器只是勉强没有坏到无法修复,而且我们直到现在才弄清楚这一点。

    这种组合——将帧全局与其上下文断开连接,并漂白从该全局可达的所有内容——并没有给我们完美的虚拟化能力。由于这些六个剩余惰性宿主对象的可检测存在,在这些已知的属性名上,启动后的代码仍然可以感觉到它似乎是在浏览器上启动的,尽管启动代码尽力模拟另一个宿主。然而,这仅仅是关于平台信息的抽象泄露。它不以任何方式损害虚拟化不同宿主的能力无能力的能力。因为我们可能无法就使这些属性可删除达成一致,而且我们现在理解这个剩余违反不是致命的,本提案单独将其作为特殊情况予以保留。我们明确禁止在此祖父条款特殊情况之外的任何违反。

    不可检测的扩展

    宿主钩子的扩散

    标准属性的额外行为

    不可填充的原生对象

    隐藏的原生状态

    模块加载行为

    不可填充的内置模块

    隐藏的内置模块实例状态