ShardArrayBuffer、幽灵漏洞与跨域

背景

最近项目有需求需要在前端使用ffmpeg,于是我把ffmpeg编译好的wasm版本直接引入到项目中进行使用,本以为会比较顺利,结果一运行,直接爆红:

ShardArrayBuffer介绍

从上面的爆红信息中可以直白地看出浏览器提示我们ShardArrayBuffer没有定义,那么ShardArrayBuffer是什么?

SharedArrayBuffer 对象用来表示一个通用的、固定长度的原始二进制数据缓冲区,类似于 ArrayBuffer 对象,它们都可以用来在共享内存(shared memory)上创建视图。与 ArrayBuffer 不同的是,SharedArrayBuffer 不能被转移

上面给出的是MDN上的介绍,读起来比较晦涩,要了解ShardArrayBuffer,首先要理解ArrayBuffer是什么。

ArrayBuffer

ArrayBuffer是一个无法被直接操作的字节数组,只能通过类型化数组对象DataView 对象来操作。

那么为什么要使用ArrayBuffer

我们都知道,JavaScript是一门单线程语言,这意味着我们无法把复杂的计算放在页面上执行,为了弥补这一缺点,子线程Web Worker应运而生,Web Worker独立于我们负责页面逻辑的主线程,不会影响页面的性能,但这也导致了一个问题,在Web Worker中,我们无法获取当前页面的信息,于是便有了postmessage

postmessage可以接受任何对象,对其进行序列化,然后在各个线程之间进行通信,在反序列化后放入内存,解决了线程之间的通信问题,又一个问题出现了,那就是通信的性能问题。

我们注意到,每次通信都需要对数据进行序列化和反序列化,这是非常低效的一个过程,例如数组、字符串这些数据类型,需要先复制他们,将它们转化为字节数组,然后才能进行传输,传输之后还要进行反序列化,转化为原来的数据结构。

这里就引出了另外一个点,ArrayBuffer是一个可转移对象 ,这意味着我们可以将ArrayBuffer从一个线程之间传输到另外一个线程,而无需复制和序列化,使用ArrayBuffer能很大程度上提高线程通信的效率,但这样也有一个问题,就是,传输过后,发送ArrayBuffer的线程将无法再次访问它,这对于想要拥有高性能并行性的项目来说是不能接受的。

于是,SharedArrayBuffer诞生了。

ShardArrayBuffer

从名字上就能看出,这是ArrayBuffer的升级版,事实也的确如此,ShardArrayBuffer最大的优点就是它是一个真正的共享内存,允许多个进程(WebWorker)对其进行读写,不再具有通信开销和延迟,这让JS真正可以模拟Java、C等语言所拥有的多线程高性能。

好,现在回到文章开头,为什么会出现ShardArrayBuffer is not defined这个错误呢?

百度一下,得到的解决方案是为响应头设置:

makefile 复制代码
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

我加上这两个请求头之后,报错果然消失了,ffmpeg也成功运行了!但原理还没搞明白,不禁心里痒痒,决定对其一探究竟。

幽灵漏洞(Spectre)

现代CPU为了优化运行效率,采取了分支预测的优化方式,当在运行到if这样的判断时,CPU通常会进行分支预测,而预测的结果受多方影响,比如在本次判断之前的十次判断结果都为正确,那么CPU会先预测这次的结果也为正确,然后去执行之后的代码逻辑,并将结果进行缓存,当判断结果吻合预测结果时,直接从CPU缓存中取出数据存入,如果判断结果不吻合,则进行回滚,但是缓存中的数据不会删除!

但是分支预测和缓存机制导致了一个很严重的CPU漏洞,也就是大名鼎鼎的幽灵漏洞。理解幽灵漏洞需要对计算机和CPU的底层原理有一定的理解,这里将通俗地介绍一下什么是幽灵漏洞。

先看一段代码:

js 复制代码
if(x < A.length) {
    y = instrument[A[x]]
}

在这段代码中,我们试图去访问instrument数组中索引为A[x]的值,我们先令x小于A的长度,重复运行这段代码,经过重复运行后,CPU对这个判断的分支预测趋于'正确',此时我们令x大于A的长度,基于分支预测的策略,CPU会先将instrument[A[x]]的值读取并缓存,然后当判断结束后再进行回滚,此时,问题出现了,instrument[A[x]]的值依然保留在缓存之中!而A[X]这个值是非法的,正常来说,我们不应该能拿到这个值,因为它已经超过了A数组的长度,也就是非法越界,但是通过幽灵漏洞,我们可以拿到这个值。

(这里推荐观看b站视频【【计算机】15分钟读懂英特尔熔断幽灵漏洞-Emory】www.bilibili.com/video/BV1eW...)

接下来就是幽灵漏洞的关键,在这个漏洞中,还涉及到了另外一个经典的CPU漏洞,即旁信道攻击,这里不做赘述,你只需要知道,我们读取内存之中的值和读取缓存之中的值所需要的时间是不一样的,也就是说,如果我们能够获得精确的时间,只需要遍历instrument数组,然后判断读取每个值所花的时间,其中instrument[A[x]]由于已经在缓存之中,读取它所花费的时间是远低于其他值的,这样我们就可以拿到A[x]的值了。

为什么我要突然提到幽灵漏洞呢?聪明的你一定注意到了,幽灵漏洞中有一个很关键的地方,那就是高精度的时间。

在幽灵漏洞提出后,浏览器厂商已经降低了类似performance.now()这样的计时精度来阻止恶意代码获取高精度CPU时间,但通过SharedArrayBuffer依然可以制作一个高精度的计时器,基本原理就是在一个woker线程不停的递增SharedArrayBuffer的值,另外一个线程在任意计算代码的前后分别读取这个SharedArrayBuffer的值,取差值来作为时间的增量。浏览器没有办法去判断这种行为,所以只能先禁用SharedArrayBuffer

解决方案

在当前环境下前端需要高性能计算的场景越来越多,长久地禁用显然是不现实的,于是为了解决这一问题,Web标准提出了一种解决方案,那就是引入两个新的跨域策略:COOP/COEP,也就是上面提到的在html响应头上设置:

js 复制代码
Cross-Origin-Embedder-Policy: require-corp 
Cross-Origin-Opener-Policy: same-origin

其中,COEP禁止加载没有任何显示设置的CORP/CORS的跨域资源,COOP则是限制当前文档打开的跨域请求,设置为'same-origin',则表示对打开的窗口执行隔离,同时禁止两个窗口相互通信。

这种解决方案也就是将页面放入了一个封闭的隔离沙箱,所有的跨域请求都需要由拥有资源的服务器进行明确审查,如果未审查,则数据永远不会进入攻击者的浏览上下文,也就可以规避掉攻击者的幽灵漏洞攻击,此时,浏览器可以认为发出请求的站点不太危险,于是允许使用SharedArrayBuffer等高风险API。

本地调试

我们在本地运行Vite项目时,在proxy中设置响应头即可,不过这个设置只有使用localhost有效,如果使用network地址实际上是无效的,因为Vite的代理只是在本地进行了处理,对于外部的访问是无法处理的。

生产环境

在生产环境中。则需要在nginx进行配置,同样也是在响应头中进行设置。

跨域问题

经过我们上面的设置后,又带来了一个新的问题,所有的跨域资源都无法使用,包括存储在OSS中的图片与视频,这是不能接受的,对此,Web标准给出了两种解决方案:CORP和CORS

CORP

对于我们需要访问的远程跨域资源,为其设置跨域资源策略响应头:Cross-Origin-Resource-Policy: cross-origin,也就是允许跨域访问该资源。

CORS

这个大家应该很熟悉了,针对<audio>、<img>、<link>、<script>、<video>标签,我们也可以采用设置crossorigin属性,如果资源支持CORS,也可以访问(资源的响应头必须设置Access-Control-Allow-Origin,本地开发时该值只能为*

最后的问题

经过漫长的调试,就当我以为终于可以同时使用SharedArrayBuffer又解决了跨域问题时,突然意识到背景图片无法设置crossorigin!,那我们使用CORP来解决?存储背景图片的OSS目前还无法配置CORP策略!

最后有一个曲线救国的办法,为OSS配置CDN,在CDN上配置请求头来解决跨域问题。

如果你没有CDN,那只能妥协一下,把所有的背景图片换成<img>标签吧!

参考资料

(如有侵权,请联系作者删除)

相关推荐
y先森2 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy2 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189112 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿3 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡4 小时前
commitlint校验git提交信息
前端
虾球xz4 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇5 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒5 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员5 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐5 小时前
前端图像处理(一)
前端