服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

【第2872期】简单聊聊 ShadowRealm

日期: 来源:前端早读课收集编辑:贤重

前言

今天认识一个新的 api:ShadowRealm。今日前端早读课文章由 @贤重分享。公号:Fliggy FE 授权。

正文从这开始~~

1. ShadowRealm 是什么

ShadowRealm 是 TC39 的一个提案,现处于第三阶段。它提供了一种新机制,可以在一个新的高度隔离的运行环境(全局对象和内建对象)中执行代码。

ShadowRealm 的基本使用包含一个构造函数、两个方法。

提案:https://tc39.es/proposal-shadowrealm/

2. ShadowRealm API

 declare class ShadowRealm {
constructor();
evaluate(sourceText: string): PrimitiveValueOrCallable;
impotValue(specifier: string, bindingName: string): Promise<PrimitiveValueOrCallable>;
}

每个 ShadowRealm 实例都有自己独立的运行环境,并提供了如下两个方法来执行 realm 中的代码:

  • evaluate: 同步执行代码字符串,类似 eval

  • importValue: 导入并异步执行代码,以 Promise 形式返回执行结果,这意味我们可以非阻塞的执行第三方脚本。

2.1 shadowRealm.evaluate

函数签名:

 evaluate(sourceText: string): PrimitiveValueOrCallable;

evaluate 的工作原理和 eval 类似:

 const sr = new ShadowRealm();
console.assert(
sr.evaluate(`'ab' + 'cd'`) === 'abcd'
);

和 eval 不同点是,evaluate 是在独立环境中执行的:

 globalThis.realm = 'incubator realm';

const sr = new ShadowRealm();
sr.evaluate(`globalThis.realm = 'child realm'`);
console.assert(
sr.evaluate(`globalThis.realm`) === 'child realm'
);
console.assert(
globalThis.realm === 'incubator realm'
);

如果在 evaluate 中返回了一个函数,为了能够在外部调用则需要进行包装:

 globalThis.realm = 'incubator realm';

const sr = new ShadowRealm();
sr.evaluate(`globalThis.realm = 'child realm'`);

const wrappedFunc = sr.evaluate(`() => globalThis.realm`);
console.assert(wrappedFunc() === 'child realm');

evaluate 的参数或者返回值,必须是 primitive 的或者是 [Callable] 的,否则会抛错:

 new ShadowRealm().evaluate('[]')
// TypeError: value passing between realms must be callable or primitive
2.2 shadowRealm.importValue

importValue 的类型签名:

 importValue(specifier: string, bindingName: string): Promise<PrimitiveValueOrCallable>;

从 specifier 模块(module)导入名称为 bindingName 的代码,并且通过 Promise 异步返回执行结果。和 evaluate 方法 一样,返回的函数也会被包装:

 // main.js
const sr = new ShadowRealm();
const wrappedSum = await sr.importValue('./my-module.js', 'sum');
console.assert(wrappedSum('hi', ' ', 'folks', '!') === 'hi folks!');

// my-module.js
export function sum(...values) {
return values.reduce((prev, value) => prev + value);
}

在现在,参数 bindingName 是必选的,但如果我们只是想加载(load)一个模块(module),而不想导入(importing)任何代码,我们可以用一个变通的方式,用 'default' 作为 bindingName 的值。将来我们可能可以省略掉:

 // main.js
const sr = new ShadowRealm();
await sr.importValue('./my-module.js', 'default');

// my-module.js
export default true;
// ...

和 evaluate 函数一样,参数或者返回值,必须是 primitive 的或者是 [Callable] 的

3. realms 之间传值

这里有几种方式在容器环境(incubator realm)和子环境(child realm)之间传值:

  • 传入数据到 ShadowRealm

    • 通过参数传入

  • 从 ShadowRealm 接收数据:

    • evaluate 和 importValue 的结果

    • ShadowRealm 返回的函数的执行结果

只要通过 realms 传值,这个值都会通过内部规定操作 GetWrappedValue 包装:

  • primitive 类型的值不会有任何改变

  • [callable] 类型的值会通过 wrapped function 包装

  • 任何其他类型的值都会抛错

 const sr = new ShadowRealm();

sr.evaluate('globalThis')
// TypeError: value passing between realms must be callable or primitive
sr.evaluate('({prop: 123})')
// TypeError: value passing between realms must be callable or primitive
sr.evaluate('Object.prototype')
// TypeError: value passing between realms must be callable or primitive

typeof sr.evaluate('() => {}') // OK
// 'function'
typeof sr.evaluate('123') // OK
// 'number'
3.1 非 [callable] 对象不能在 realms 之间传递

为什么?目的是完全隔离 realmsShadowRealm 中执行的代码永远不能影响容器环境(incubator realm)。

Objects 拥有原型链(prototype chains)。当我们传递一个原型链到另外的 realm,我们有两种方式:

  • 只复制(copy)对象。原型链在其他环境依然存在,这样几乎给了源运行环境(source realm)执行这个原型链上任意代码的权限;

  • 完整的复制原型链。但是有些对象无法简单的复制例如 Array.prototype 和 Object.prototype

几乎所有对象都有标准属性(the standard property)constructor 作为对象的类型(class)

 > ({}).constructor === Object
// true
> [].constructor === Array
// true

通过 constructor 这个属性,我们可以拿到 globalThis:

 function handleObject(someObject) {
// Bad: the constructor of an object lets us create more instances
const constructor = someObject.constructor;

// Worse: the constructor of a constructor is `Function`
// which lets us execute arbitrary code in the realm of `someObject`
const _Function = constructor.constructor;

const _globalThis = (new _Function('return globalThis'))();
// We now have access to `globalThis` and can change global data
}
3.2 通过 realms 传递的函数会被包装

被包装(wrappee)是来自外部运行环境(realm)的函数。所谓的包装函数(wrapped function)包装 wrappee。这个包装(wrapper)保护 wrappee 和本地环境互不侵犯

如何保持隔离:

  • wrappee 的参数都会通过 GetWrappedValue 包装,隐形参数 this 也一样

  • 参数的类型 primitive 或 [callable]

  • wrappee 调用返回值也会被包装

  • 返回值的类型 primitive 或 [callable]

  • wrappee 在新的独立运行环境中执行

  • wrappee 只暴露了一个功能,外部可以执行函数调用。

  • 不能用 new 调用

  • wrappee 的属性和原型,在外部都无权访问(读写)

(注意:被包装的函数是无法解包装(unwrapped))

示例:包装函数的执行,this 是 ShadowRealm 的 globalThis,不是容器环境的 globalThis

 const sr = new ShadowRealm();
// .evaluate() executes code in sloppy mode
const sloppyFunc = sr.evaluate(`
(function () {
switch (this) {
case undefined:
return 'this is undefined';
case globalThis:
return 'this is globalThis';
default:
throw new Error();
}
})
`);
console.assert(sloppyFunc() === 'this is globalThis');

笔者在严格模式下测试结果发现和非严格模式一样,应该是现有的 polyfill 方案无法完美支持的原因

 const sr = new ShadowRealm();
const strictFunc = sr.evaluate(`
(function () {
'use strict';
switch (this) {
case undefined:
return 'this is undefined';
case globalThis:
return 'this is globalThis';
default:
throw new Error();
}
})
`);
console.assert(strictFunc() === 'this is undefined');

4. ShadowRealm 可以做什么

  • 在 web IDE 或 绘图应用等 web app 中运行插件等三方代码

  • 创建一个编程环境运行用户代码

  • 服务器可以在 ShadowRealms 中运行三方代码

  • 运行测试,外部环境不会被影响且每个测试都可以在新环境中运行(提高可复用)

  • 网页抓取(提取网页数据)和网页应用测试可以在 ShadowRealms 中运行

5. 与其他方案对比

简单对比一下和现有的一些相似方案的差异:

eval 和 Function

和 eval 和 Function 类似,但是有所改进:全新独立的运行环境执行代码;保护外部运行环境不受影响。

Web Workers

Web Worker 是一个比 ShadowRealms 更强大的隔离机制,它们的代码在独立进程中执行并且通过异步通信。但是,ShadowRealms 是一个更轻量的方式,可以同步计算,更便捷。

iframe

我们都知道,每个 iframe 都有自己的运行环境,我们可以在里面同步执行代码:

 <body>
<iframe>
</iframe>
<script>
globalThis.realm = 'incubator';
const iframeRealm = frames[0].window;
iframeRealm.globalThis.realm = 'child';
console.log(iframeRealm.eval('globalThis.realm')); // 'child'
</script>
</body>

与 ShadowRealms 相比,有以下缺点:

  • 只能在浏览器中使用

  • 我们需要在 DOM 中添加一个 iframe 节点用来初始化

  • 每个 iframe 运行环境都包含完整的 DOM,限制了一些自定义的灵活度

  • 默认情况下,对象(objects)是可以跨环境的,这导致我们需要额外的工作来保证代码运行安全

Node.js 的 vm 模块

与 ShadowRealms 类似,但是有更多的功能:缓存 JavaScript 引擎数据以便更快的启动,拦截 import,创建外部全局对象(通过 vm.createContext)。唯一的缺点只能在 node.js 环境下使用。

6. 总结

ShadowRealm 允许一个 JS 运行时创建多个高度隔离的 JS 运行环境(realm),每个 realm 具有独立的全局对象和内建对象。

ShadowRealm 有意强化了隔离性,当前和所构造的 shadowrealm 之间不能直接互相访问对象,只能传递 primitive 值和 [Callable]。注意 [callable] 其实差不多就是函数,但是是高度受限的。因为 JS 中函数也是对象,所以当一个 realm 中的函数被传递到另一个 realm 里,不能是原本的函数对象,而是一个包装对象。除了 call(函数调用)以外,其他针对包装对象的操作(比如读写属性)都与原 realm 中的函数对象无关。而函数调用本身也是受限的,其参数和返回值也只能是 primitive 值和 [Callable]

早期的 realm 草案没有这些限制,更接近于 node 中的 vm 模块。之所以 ShadowRealm 做出这样的隔离性限制,主要是浏览器厂商认为 iframe 那样的方式,容易造成多个 realm 中的对象有意无意的互相引用,形成复杂的对象图,性能(垃圾回收)和安全性不佳。一直以来,浏览器厂商都在 web 标准中不断削弱和限制 iframe,当然不希望在 JS 规范上再开这样的口子。

7. 参考

  • https://2ality.com/2022/04/shadow-realms.html

  • https://github.com/tc39/proposal-shadowrealm

  • https://github.com/ambit-tsai/shadowrealm-api

关于本文
作者:@贤重
原文:https://mp.weixin.qq.com/s/nuQThAvH7ro54ibOqS-FhA

这期前端早读课
对你有帮助,帮” 赞 “一下,
期待下一期,帮” 在看” 一下 。

相关阅读

  • HikariCP 数据库连接池,太快了!

  • 点击“终码一生”,关注,置顶公众号每日技术干货,第一时间送达!耗时8个月联合打造 《 2023年Java高薪课程 》,已更新了 102G 视频,累计更新时长 500+ 个小时,需要的小伙伴可以了解下
  • php(phar)反序列化漏洞及各种绕过姿势

  • 本文为看雪论坛优秀文章看雪论坛作者ID:pank1s一简介序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。简单来说就是我在一个地方构造了一个类,但
  • 全局钩子注入--实现获取账号密码

  • 文章来源:None安全团队全局钩子注入-获取QQ密码实现全局钩子注入-获取QQ密码实现 SetWindowsHookExA将应用程序定义的挂钩过程安装到挂钩链中。您将安装一个挂钩程序来监视系
  • Pwn入门之ret2libc详解

  • 前言之前曾在Pwn入门之基础栈溢出里面曾经提过ret2libc的相关知识,但是写的比较笼统,感觉对新手还是不够友好,想通过本文对ret2libc的原理和利用进行详细讲解。前置知识GOT表和
  • 梅洲乡开展辖区“每月一法”矫正集中教育

  • 导报讯(通讯员 沈陆炜 记者 刘龙)近日,梅洲司法所对辖区内矫正人员开展集中教育活动,确保辖区矫正对象安全稳定。梅洲司法所工作人员结合当前犯罪类型和特点,有针对性的对辖区矫
  • 一次失败的SQL注入经历

  • 目录一次失败的SQL注入经历0x00 前言0x01 WAF识别0x02 篇章: Bypassed 0x2.1 漏洞证明 0x2.2 无限制注入0x03 后续:无疾而终0x04 总结某天,聚合扫描器推送了一份SQL注入的漏
  • PHP代码审计-某cms逻辑漏洞导致getshell

  • 前言 如果存在exec进行拼接的漏洞,该如何绕过 <mark>一黑俩匹配 </mark>?当前如果是拼接和编码这种手法就不说了,现在在看的师傅您是审计大牛的话,这文章可以忽略不看。黑名单..
  • “干净”的代码,贼差的性能

  • 作者 | Casey Muratori
    译者| 核子可乐
    策划| 褚杏娟
    如今很多机构里传授的所谓编程“最佳实践”,压根就是随时可能爆炸的性能灾难。 很多程序员还是一个“小萌新”时

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • 三雄极光发布业绩快报!净利润同比增长286.8%!

  • 最新消息!三雄极光近日披露《2022年度业绩快报》,受到业界广泛关注。2月27日,广东三雄极光照明股份有限公司(股票简称:三雄极光,股票代码:300625)披露《2022年度业绩快报》。公告显
  • 【第2872期】简单聊聊 ShadowRealm

  • 前言今天认识一个新的 api:ShadowRealm。今日前端早读课文章由 @贤重分享。公号:Fliggy FE 授权。正文从这开始~~1. ShadowRealm 是什么ShadowRealm 是 TC39 的一个提案,现处于第
  • 【第2873期】0.1 + 0.2 不等于 0.3?原来是因为这个

  • 前言从原理知识面解析,最后给出具体解法。今日前端早读课文章由 @沐洒投稿,公号:沐洒授权分享。@沐洒(musama2018),十年前端生涯,待过 BAT 也创过业,目前就职腾讯。一个有极度表达欲