使用allowInsecureProtocol属性解决gradle的仓库地址不安全警告

使用 allowInsecureProtocol 属性解决 gradle 的仓库地址不安全警告,在 IDEATerminal 中使用命令

可以打印出当前 gradle 存在的所有警告信息。

如果有报以下警告:

说明你配置了除 maven 中央仓库之外的其他不安全的仓库(至于“不安全”在这里的定义,我也不是很清楚,一些国内的镜像仓库例如阿里的也是“不安全”的)gradle 中有一个属性可以允许 gradle 使用“不安全”的仓库并且不报警告信息,该属性是 allowInsecureProtocol,官方的

翻译过来就是指定通过不安全的HTTP连接与仓库通信是否可接受,如果该属性的值设置为 true,则表示接受“不安全”的仓库地址。

目前,升级项目的 gradle 7.0.2 版本之后,报错如下:

只需要在 build.gradle 中进行如下的配置即可:

参考链接


使用 allowInsecureProtocol 属性解决 gradle 的仓库地址不安全警告

How to repair a broken/unmountable btrfs filesystem

最近磁盘上的Btrfs分区在调整大小的时候,报错:

How to repair a broken/unmountable btrfs filesystem

The below are the recommended steps for any major btrfs filesystem issue, especially if its unmountable. Reading dmesg or syslog might help you identify which step you could skip to in order to fix a particular problem, but the initial steps are normally useful regardless as btrfs scrub is a very safe repair tool.

  • Boot to a suitable alternative system, such as a rescue shell, different installation of openSUSE, a liveCD, or an openSUSE installation DVD. The installation DVD for the version of openSUSE you are running is usually a good choice as it will certainly use the same kernel/btrfs version. A recent Tumbleweed disk might be better as it will include newer kernel/btrfs
  • Go to a suitable console and make sure you do the below as root
  • Try to mount your partition to /mnt, just to confirm it's really broken

  • If it mounts - are you sure it's broken? if Yes - run

to scrub the system, and

to monitor it

  • If it doesn't mount, try to scrub the device just in case it works

and

to monitor. Once complete, try mounting, if yes, you're fixed.

  • If scrubbing is not an option or does not resolve the issue

then instead try mount -o usebackuproot

Warning: All of the above steps are considered safe and should make no destructive changes to disk. If the above doesn't fix things for you, you can continue with the below steps but the situation is serious enough to justify a bug report, please!

  • Run "btrfs check <device>"

This isn't going to help, but save the log somewhere, it will be useful for the bug report.

  • Seriously consider running "btrfs restore <device> <somewhereto copy data>"

This won't fix anything but it will scan the filesystem and recover everything it can to the mounted device. This especially useful if your btrfs issues are actually caused by failing hardware and not btrfs fault.

  • Run "btrfs rescue super-recover <device>"

Then try to mount the device normally. If it works, stop going.

  • Run "btrfs rescue zero-log <device>"

Then try to mount the device normally. If it works, stop going.

  • Run "btrfs rescue chunk-recover <device>"

This will take a LONG while. Then try to mount the device normally. If it works, stop going.

  • If you didn't run it earlier, be sure to now run "btrfs restore <device> <somewhere to copy data>"

  • Failure to use btrfs restore at this point but continuing to attempt repairs means you are at a very high risk of data loss. It is advisable to use btrfs restore to recover as much data as possible before continuing.

Warning: The above tools had a small chance of making unwelcome changes. Below this point there is a higher risk of damage. Do not continue unless you're prepared to accept the consequences of your choice.

  • Now, ONLY NOW, try btrfsck aka "btrfs check --repair <device>"

最后,上面的步骤都不能解决问题,因此最终是把磁盘上的文件全部拷贝出来,然后重新格式化分区,再把文件全部拷贝回去,解决问题。

参考链接


Flutter单元测试报错“Error: Not found: 'dart:ui'”

开发环境:macOS Big Sur (11.6.2)/Flutter 2.8.1/Android Studio Atrctic Fox (2020.3.1 Patch 4)

参照 将Flutter module集成到Android项目(Android Studio Arctic Fox 2020.3.1/Flutter 2.8.1) 建立项目,在执行 Flutter 单元测试代码的时候报错

详细的错误日志如下:

但是,如果在命令行下执行:

则可以正常执行成功。

导致上述问题的原因在于,从Android Studio的单元测试菜单点击进行的单元测试,生成的配置信息没有把Flutter相关的依赖库关联起来,默认只生成了的Dart代码的测试。

解决方法就是手工创建Flutter测试用例,步骤如下图:

测试用例的执行,需要参考下图:

另外,注意如果手工创建Flutter测试用例的时候,Flutter 的测试用例跟 Dart 的测试用例名相同的情况下,删除以前的同名 Dart 测试用例,可以解决在项目测试用例上右键运行会执行 Dart 测试用例,导致上述报错的问题。

也就是如果同名了,就运行已经存在的测试用例,否则创建一个新的。

如下图配置的情况下:

可以在 widget_test.dart 上直接右击成功运行测试用例。

参考链接


How to solve Not found: 'dart:ui' error while running integration tests on Flutter

Android Studio 2020.3.1 编译报错 "Installed Build Tools revision 32.0.0 is corrupted"

最近,在使用升级老项目的 compileSdkVersion 32 进行 Android 12L适配的时候,报错

详细的报错信息如下:

报错产生的原因是从 Android SDK 31.0.0 开始,SDK里面的构建工具 dx.jar 被改名成 d8.jar,早期(7.x之前)的构建工具在尝试调用的时候找不到原来的的 dx.jar 导致报错。

解决方法有两种:

  1. 升级根目录下的 build.gradle 中的 'com.android.tools.build:gradle:4.1.3'7.0.4 以及以上的版本。
  2. 复制或者创建快捷方式把 d8.jar 复制并改名成 dx.jar

参考链接


Android Studio error "Installed Build Tools revision 31.0.0 is corrupted"

Flutter 2.5 更新详解

Flutter 2.5 正式版已于上周正式发布!这是一次重要的版本更新,也是 Flutter 发布历史上各项统计数据排名第二的版本。我们关闭了 4600 个 Issue,合并了 3932 个 PR,它们分别来自 252 个贡献者和 216 个审核者。回顾去年 -- 我们收到来自 1337 个贡献者提交的 21072 个 PR,其中有 15172 个被合并。在详述本次更新的内容之前,我们想强调,Flutter 的首要工作始终是高质量交付开发者们所需要功能。

Flutter 2.5 带来了一些重要的性能和工具改进,以帮助开发者们追踪应用中的性能问题。同时,加入了一些新的功能,包括对 Android 的全屏支持、 对 Material You (也称 v3) 的更多支持、对文本编辑的更新以支持切换键盘快捷键、在 Widget Inspector 中查看 widget 详情、在 Visual Studio Code 项目中添加依赖关系的新支持、从 IntelliJ / Android Studio 的测试运行中获得测试覆盖率信息的新支持,以及一个更贴近 Flutter 应用在真实的使用场景下的应用模板等。这个版本充满了令人兴奋的新更新,让我们开始介绍吧!

该版本进行了一些性能上的改进:首先是一项用于从离线训练运行中连接 Metal 着色器预编译的 PR (#25644),这将最坏情况下的光栅化时间减少了 2/3 (如我们的基准测试所示),将第 99 百分位的帧时间减少了一半。我们在减少 iOS 卡顿方面取得了持续性的进展,这也是在这条道路上迈出的另一步。然而,着色器预热只是卡顿的一个来源。在该版本以前,处理来自网络、文件系统、插件或其他 isolate 的异步事件可能导致动画中断,这是另一个卡顿的来源。在该版本中我们对 UI Isolate 的事件循环的调度策略 (#25789) 进行了改进,现在帧处理优先于其他异步事件的处理,在我们的测试中,其导致的卡顿已经被消除。

另一个原因是垃圾回收 (GC) 会暂停 UI 线程来回收内存。在该版本以前,一些图像的内存只能在 Dart VM 执行 GC 时以较慢的速度进行回收。在早期版本中,常用的做法是 Flutter 引擎会向 Dart VM 提示图像内存可以通过 GC 回收,理论上可以让内存回收更为及时。不幸的是,在实践中这造成了过多的回收,而且内存有时仍然不能被快速回收,导致无法避免在内存有限的设备上出现低内存的情况。在现在的版本中,未使用的图像的内存会尽可能快速地进行回收 (#26219#82883#84740),这大大减少了 GC 的次数。

举个例子,在我们的一个测试中,播放了一个 20 秒的 GIF 动画,GC 的次数从需要 400 次下降到只需 4 次。更少的主要 GC,意味着更少的涉及图像出现和消失的动画卡顿,更少的 CPU 和电量消耗。

Flutter 2.5 的另一项性能改进是优化了 iOS 上 Dart 和 Objective-C/Swift、Android 上 Dart 和 Java/Kotlin 之间相互通信的延迟。作为 调整消息通道 的一部分,我们从消息编解码器中移除了不必要的拷贝,在不同内容的大小和设备上减少了高达 50% 的延迟 (详见 #25988#26331)。

更多详情,请你参阅 Aaron Clarke 的文章:提高 Flutter 中的平台通道性能

如果你要构建 iOS 应用,我们还有最后一项性能更新:在该版本中,使用 Apple Silicon M1 Mac 构建的 Flutter 应用可以直接在 ARM 架构的 iOS 模拟器 (#pull/85642) 上运行。这意味着无需使用 Rosetta 对 Intel x86_64 指令和 ARM 进行转换,这提升了 iOS 应用测试的性能,并规避了一些微妙的 Rosetta 问题 (#74970#79641)。这是 Flutter 在全面支持 Apple Silicon 的路程上迈出的又一步,敬请继续关注。

当然,没有 Dart 语言和它的运行时环境,就不会有现在的 Flutter,它建立在 Dart 语言和 runtime 之上。Flutter 2.5 同时带来了 Dart 2.14。新发布的 Dart 版本 不仅带来了新的格式化使 级联 操作更加清晰,还带来了支持忽略文件的新 pub 命令工具,以及新的语言功能 (包括传说中的无符号右移操作符的回归)。此外,这个版本带来了一套新的 Dart 和 Flutter 项目之间共享的标准代码规范提示,开箱即用,这也是 Dart 2.14 最精彩的部分。

flutter create 开箱即有一个 analysis_options.yaml 文件,预先使用了推荐的 Flutter lint。

当你创建一个新的 Dart 或 Flutter 项目时,你不仅可以使用这些规范,而且 只需要几个步骤 就可以将这种相同的分析添加到你现有的应用中。关于这些规范的细节、新的语言功能和更多内容,请查阅:Dart 2.14 发布

Flutter 2.5 版本对框架进行了一些修复和改进。我们修复了关于 Android 全屏模式的一系列相关问题,该 Issue 获得了上百个点赞,全屏选项包括向后倾斜、沉浸模式、粘性沉浸模式和边到边四种。这一变化还增加了一种方法用来监听其他模式下的全屏变化。例如,如果用户在使用应用时,改变了系统界面的全屏模式,开发者现在可以通过代码让应用重新变为全屏,或执行其他操作。

在这个版本中,我们继续建立对新的 Material You (又称 v3) 规范的支持,包括对悬浮按钮尺寸和主题的更新 (#86441),以及一个新的 MaterialState.scrolledUnder 状态,你可以通过 PR (#79999) 中的示例代码看到它的效果。

当我们讨论滚动时,另一个改进是增加了额外的滚动指标通知 (#85221#85499),即使用户没有滚动,也会提供可滚动区域的通知。例如,如下示例展示了滚动条根据 ListView 的实际大小而适时出现或消失的效果。

在这种情况下,你不需要写任何代码,就可以捕获 ScrollMetricNotification 的变化。特别感谢社区贡献者 xu-baolin,他在这方面做了大量工作,并提出了一个很好的解决方案。

社区的另一杰出贡献是为 ScaffoldMessenger 增加了 Material 横幅的支持。在 Flutter 2.0 中新增的 ScaffoldMessenger,它提供了一种强大的方式,在屏幕底部显示 SnackBars 以向用户提供通知。在 Flutter 2.5 中,现在你可以在 Scaffold 顶部添加一个横幅,在用户将其关闭之前,它将一直保持在原位。

横幅的 Material 指南 规定你的应用一次只能显示一个横幅,所以如果你的应用多次调用 showMaterialBannerScaffoldMessenger 将持有一个队列,在前一个横幅被关闭时显示下一个新的横幅。感谢 Calamity210 为 Flutter 的 Material 支持提供了有力补充。

在 Flutter 2.0 及其新文本编辑功能的基础上,我们在这个版本中添加了如文本选择器、拦截覆写任何键盘事件,以及覆写文本编辑的键盘快捷方式的能力 (#85381)。如果你想让 Ctrl - A 做一些自定义操作,而不是选择所有文本,你可以自行定义。DefaultTextEditingShortcuts 类包含了 Flutter 在每个平台上支持的每个键盘快捷方式的列表。如果你想覆写其中的关联,请使用 Flutter 现有的 Shortcuts widget,将任一快捷键重新映射到现有或自定义的意图,您可以将该 widget 放置在你想要覆写的地方。示例请参阅:API 文档

另一个得到大量改进的插件是 camera 插件

  • 3795 [camera] android-rework 第 1 部分:支持 Android 相机功能的基础类
  • 3796 [camera] android-rework 第 2 部分:Android 自动对焦功能
  • 3797 [camera] android-rework 第 3 部分:Android 曝光相关功能
  • 3798 [camera] android-rework 第 4 部分:Android 闪光灯和变焦功能
  • 3799 [camera] android-rework 第 5 部分:Android FPS 范围、分辨率和传感器方向功能
  • 4039 [camera] android-rework 第 6 部分:Android 曝光和对焦点功能
  • 4052 [camera] android-rework 第 7 部分:Android 降噪功能
  • 4054 [camera] android-rework 第 8 部分:最终实现的支持模块
  • 4010 [camera] 在 iOS 上不触发平放时的设备方向
  • 4158 [camera] 修复 iOS 上设置焦点和曝光点的坐标旋转
  • 4197 [camera] 修复相机预览在设备方向改变时不总是重建的问题
  • 3992 [camera] 防止在设置不支持的 FocusMode 时崩溃
  • 4151 [camera] 引入 camera_web package

在 image_picker 插件 上也做了很多工作,专注于端到端的相机体验。

  • 3898 [image_picker] 图像采集器修复相机设备
  • 3956 [image_picker] 在 Android 中将相机捕捉的存储位置改为内部缓存,以符合新的 Google Play 存储要求
  • 4001 [image_picker] 删除了多余的相机权限请求
  • 4019 [image_picker] 修复当相机作为源时的旋转问题

这些工作改善了 Android 的相机和 image_picker 插件的功能和健壮性。此外,你也许会注意到 camera 插件 的 Web 功能已处于预览阶段 (#4151)。这个预览版提供了对查看相机预览、拍摄照片、使用闪光灯和变焦控制的基本支持,所有这些都可以在 Web 上进行。它目前不是一个 被认可的联合插件,因此在配置中,你需要明确这个插件仅能够在 Web 应用中 添加使用

最初的 Android 相机重构工作是由 acoutts 贡献完成的。camera 和 image_picker 的工作是由 Baseflow 完成的,这是一家专门从事 Flutter 的咨询公司,因其 在 pub.dev 上的 package 而闻名。camera_web 的工作主要由 Very Good Ventures 完成,这是一家位于美国的 Flutter 咨询公司。非常感谢你们对 Flutter 社区的贡献!

另一个有价值的社区贡献是由 fluttercommunity.dev 组织做出的,他们的代表作是 plus 系列插件。随着新的 Flutter 版本发布,之前由 Flutter 官方团队维护的插件现在 “交接” 给了 fluttercommunity.dev 组织,在每个插件下方都会有下面类似的提示:

此外,由于这些插件不再积极维护,所以我们已取消了它们的 Flutter Favorite 标记。如果你还没有迁移到 plus 系列插件,我们建议你按照以下的表格进行对照迁移。

Flutter 2.5 对 Flutter DevTools 进行了大量的改进。首先是在 DevTools 中增加了对引擎更新的支持 (#26205#26233#26237#26970#27074#26617)。其中一组更新使 Flutter 能够更好地将跟踪事件与特定的帧联系起来,这有助于开发人员确定一个帧可能会超出预算的原因。你可以在 DevTools 框架图中看到这一点,该图表重构之后已经支持了实时展示;当你的应用正在渲染时,它们的数据会被填入该图中。从这个图表中选择一个构建帧,就可以跳转到该帧的时间线事件。

Flutter 引擎现在也能识别时间线中的着色器编译事件。Flutter DevTools 使用这些事件来帮助您诊断应用中的着色器编译缺陷。

有了这个新功能,DevTools 可以检测到你因着色器编译而丢失的构建帧,以帮助你修复这个问题。如果你希望像首次运行应用一样,使用 flutter run 命令并加上 --purge-persistent-cache 这个标记。这将清除着色器的缓存,以确保你重现用户在「首次运行」或「重新打开」 (iOS) 应用时看到的效果。此功能仍在开发中,所以请将您发现的 问题或改进建议 提交给我们,以帮助发现和改进着色器编译工具。

此外,当你追踪应用中的 CPU 性能问题时,可能已经淹没在了来自 Dart 和 Flutter 库或引擎的原生代码的剖析数据中。如果你想关闭这些数据以专注自己的代码,那么你可以使用新的 CPU Profiler 功能 (#3236),使你能够隐藏来自任何这些源的 Profiler 信息。

对于你没有过滤掉的类别,它们现在已经用颜色进行了分类 (#3310#3324),这样你就可以很清晰地看到 CPU 火焰图内容对应的部分。

性能可能并不是你唯一想要调试的内容。新版本的 DevTools 带有对 Widget Inspector 的更新,当你将鼠标悬停在 widget 上时,可以评估对象、查看属性、widget 状态等等。

而且当你选择一个 widget 时,它会在新的 Widget Inspector Console 自动弹出,在那里你可以自由探索 widget 的属性。

当在断点处暂停时,你也可以在控制台执行表达式。

除了新功能,Widget Inspector 也进行了改头换面。为了使 DevTools 成为理解和调试 Flutter 应用的最佳工具,我们与芬兰的创意技术机构 Codemate 合作,进行了一些更新。

如上图所示,你可以看到以下变化:

  • 更好地传达调试切换按钮的作用 —— 这些按钮有新的图标、面向任务的标签,以及丰富的工具提示 (用于描述它们的功能和何时使用它们)。每个工具提示都进一步链接到了该功能的详细文档。
  • 更容易查找和定位感兴趣的 widget——Flutter 框架中经常使用的 widget 现在已在 Inspector 左侧的 widget 树视图中作为图标常驻。它们已经根据其类别使用颜色进行了分类。例如,布局 widget 显示为蓝色,内容 widget 显示为绿色。此外,每个文本 widget 现在会显示其内容预览。
  • 更一致的 Layout Explorer 和 widget 树的颜色方案 —— 现在更容易从 Layout Explorer 和 widget 树中识别出相同 widget。例如,如上图所示中的「Column」widget 在 Layout Explorer 中是蓝色背景,在 widget 树视图中也有一个蓝色图标。

我们很乐意听到你对这些更新产生的任何 问题和改进建议 以确保 DevTools 高效地运行。这些亮点仅是开始。关于 DevTools 在 Flutter 这个版本中的全部新内容,请查阅以下版本说明:

IntelliJ / Android Studio 的 Flutter 插件在这个版本中也有一些改进,首先改进是运行集成测试的能力 (#5459)。集成测试是在设备上运行的整个应用测试,在 integration_test 目录下运行,并使用与 widget 单元测试相同的 testWidgets() 功能。

要在您的项目中添加集成测试,请 遵循 flutter.dev 上的说明。要将测试与 IntelliJ 或 Android Studio 连接,请添加一个运行配置,启动集成测试,并连接一个设备供测试使用。运行配置可以让你在运行测试的同时,设置断点、步进等。

此外,Flutter 的最新 IntelliJ / Android Studio 系列插件允许您查看单元测试和集成测试运行的覆盖率信息。您可以通过「debug」按钮旁边的工具栏按钮来访问这个信息:

覆盖率信息将以红色和绿色的矩形显示在编辑窗口左侧的空隙中。在这个例子里,第 9 - 13 行被测试覆盖,但第 3 和 4 行没有被测试。

最新版本还包括预览来自 pub.dev 的 package 中使用的图标的新功能,这些 package 是围绕 TrueType 字体文件构建的 (#5504#5595#5677#5704),正如 Material 和 Cupertino 图标支持预览一样。

要启用图标预览,你需要告诉该插件你正在使用哪些 package。在插件的设置 / 偏好页面有一个新的文本字段。

注意,这对定义为类中静态常量的图标有效,如屏幕截图中的示例代码所示。它不会对表达式起作用,例如 LineIcons.addressBook() 或 LineIcons.values['code']。如果你是一个图标 package 的作者,而这个图标 package 并不适合这个功能,请 创建一个 Issue 进行反馈。

更多有关 Flutter 的 IntelliJ / Android Studio 插件的更新信息,请参阅下列发布说明:

Flutter 的 Visual Studio Code 插件在这个版本中也得到了改进,首先是两个新的命令「Dart: Add Dependency」和「Dart: Add Dev Dependency」 (#3306#3474)。

这些命令提供的功能与已经提供了一段时间的 Jeroen Meijer 的 Pubspec Assist 插件 类似。这些新命令开箱即用,提供了一个从 pub.dev 定期获取的 package 的类型过滤列表。

你可能还对「Fix All」命令感兴趣 (#3445#3469),该命令对 Dart 文件可用,可以在一个步骤中修复所有与 dart fix 相同的问题。

你也可以在 VS Code 中,通过在 editor.codeActionsOnSave 中添加 source.fixAll 来设置为保存时运行。

又或者如果你想尝试一下预览功能,你可以启用 dart.previewVsCodeTestRunner 设置,看到 Dart 和 Flutter 测试通过新的 Visual Studio Code 测试运行器运行。

Visual Studio Code 测试运行器看起来与当前的 Dart 和 Flutter 测试运行器有些不同,它会在不同的会话中显示结果。Visual Studio Code 测试运行器还在编辑界面的左侧增加了新的间距图标 (Gutter icon),显示测试的执行结果状态,可以点击它来运行测试 (或右键点击上下文菜单)。

在之后的版本,现有的 Dart 和 Flutter 测试运行器将被移除,而采用新的 Visual Studio Code 测试运行器。

而这仅仅是 Visual Studio Code 插件新功能和修正的冰山一角。所有详情,请查阅下列发布说明:

  • v3.26 VS Code Test Runner 集成,Flutter 创建设置,...
  • v3.25 额外的依赖性管理改进,在文件 / 保存时修复所有,...
  • v3.24 依赖关系树的改进,更容易启动配置,编辑器的改进
  • v3.23 配置文件模式的改进,改进依赖关系树,改进 LSP

在以前的 Flutter 版本中,你可能会被那些你不希望处理的异常所困扰,你可能希望它们触发调试器并找出它们的源头,但却发现 Flutter 框架没有让异常通过来触发调试器中的「未处理的异常」处理程序。在这个版本中,调试器现在可以正确地中断未处理的异常,而以前这些异常只是被框架捕获 (#17007)。这改善了调试的体验,调试器现在可以直接指向异常在代码中的抛出行,而不是指向框架深处的一个随机位置。与之相关的一个新功能是你能够决定 FutureBuilder 是否应该重新抛出或隐藏错误 (#84308)。这应该会给你提供更多的异常,以帮助你追踪 Flutter 应用中的问题。

自从 Flutter 诞生以来,就有了 Counter 应用模板,它有很多优点:它展示了 Dart 语言的很多特性,演示了几个关键的 Flutter 概念,而且它足够小,即使有很多解释性的注释,也能装进一个文件。然而,它并未对 Flutter 应用的实际使用场景提供一个特别好的展示。在这个版本中,你可以通过以下命令创建一个新的模板 (#83530)。

$ flutter create -t skeleton my_app

新的 Skeleton 模板,可生成包含两页的列表视图 Flutter 应用 (带详细视图),并遵循社区最佳实践。它的开发经过了大量的内部和外部评审,以提供一个更好的基础来构建一个达到产品级品质的应用。它支持以下功能:

  • 使用 ChangeNotifier 来协调多个小工具
  • 默认情况下,使用 arb 文件生成本地化。
  • 包括一个示例图像,并为图像资源建立了 1x、2x 和 3x 文件夹。
  • 使用「功能优先」的文件夹组织方式
  • 支持 shared_preference
  • 支持浅色和深色主题设计
  • 支持多页之间的导航

随着时间的推移和 Flutter 最佳实践的发展,希望这个新模板也能随之发展。

如果你正在开发一个插件而不是一个应用,你可能会对 Pigeon 1.0 版本感兴趣。Pigeon 是一个代码生成工具,用于生成 Flutter 和其宿主平台之间类型安全的交互代码。你可以定义插件的 API 描述,并为 Dart 与 Java / Objective-C / Kotlin / Swift 生成模板代码。

Pigeon 已经应用在 Flutter 团队的一些插件中。这个版本提供了更多有用的错误信息,增加了对泛型、原始数据类型作为参数和返回类型以及多参数的支持,在未来它会被更广泛地使用。如果你想在自己的插件或 add-to-app 的项目中使用 Pigeon,请查阅 pigeon 插件页面 找到更多信息。

以下是 Flutter 2.5 版本中的破坏性改动:

了解自 1.17 版本以来完整的破坏性改动列表,请参阅:Flutter 文档网站

随着我们继续更新 Flutter Fix (可在 IDE 中使用,也可通过 dart fix 命令使用),我们总共应用了 157 条规则,来迁移受破坏性改动以及任何弃用影响的代码。一如既往,我们非常感谢社区 提供的测试,帮助我们识别了这些破坏性改动。如需了解更多,请查阅: Flutter 破坏性改动政策

另外,随着 Flutter 2.5 的发布,我们将放弃对 iOS 8 的支持,正如 2020 年 9 月宣布 的那样。放弃对市场份额不足 1% 的 iOS 8 的支持,使 Flutter 团队能够专注于使用范围更广的新平台。弃用意味着这些平台可能可以正常使用 Flutter,但我们不会在这些平台上测试新版本的 Flutter 或插件。您可以在 Flutter 文档网站 上看到 目前 Flutter 支持的平台列表

最后,一如既往地感谢世界各地的 Flutter 社区组织和社区成员们,是社区让这一切成为可能。在本次更新中贡献和审核 1000 多个 PR 的数百位开发者,因为有你们每个人的努力才成就了本次的成果。让我们携手共同努力,为世界各地的开发者共同转变应用的开发流程,让开发者们可以从一个代码库中交付更多应用、更快开发、部署到更多你所关心的平台。

敬请关注 Flutter 团队的更多更新,这一年还没有结束,拭目以待!

感谢 flutter.cn 社区成员 (@AlexV525、@Vadaski、@MeandNi) 以及 Yuan 和 Lynn 对本文的审校和贡献。

参考链接


Dart中函数调用是值传递还是引用传递?

最近看到有人面试的时候被问到了 “Dart中函数调用是值传递还是引用传递?”

初看感觉跟Java应该是相同的,结果搜索到的链接里面言之凿凿的说是传值操作,顿时感觉有些迷茫。

针对大对象,如果进行拷贝传值,会诱发大量的内存复制操作,这种情况是不可想象的。而基本类型,比如整数,字符,传递地址又是得不偿失的性能损失,明明通过寄存器就可以直接传递参数,却非要从内存中读取一遍,是完全没有必要的。

Dart中,基本数据类型传值,类传引用

类对象传值

输出结果为:

基本类型传值

上面的代码,其实转换成 Java 也是一样的结果。

但是深入思考,在 Flutter 中,上面的结论并不全面,对于异构模型而言,取决于内存架构。

如果是非一致内存架构,则CPU跟GPU之间只能进行拷贝操作。

即使是一致内存架构,也分为物理一致还是软件一致,如果仅仅是软件一致,比如PCIE独立显卡,依旧是只能进行拷贝操作,当然拷贝是显卡驱动完成的。只有物理,驱动都完全支持的情况下,才会出现物理意义上的引用传值的情况。有时候,即是两者都支持,GPU对虚拟内存的支持也是一个决定因素。

还是蛮复杂的。

Dart VM 是如何运行你的代码的

Dart VM有多种方式去运行Dart代码,比如:

  • JIT模式运行源码或者Kernal binary
  • 通过snapshot方式:AOT snapshot 和 AppJIT shanpshot

两者的主要区别在于VMDart源码转换成可执行代码的时机和方式。

VM中的任何Dart代码都是运行在隔离的isolate当中,isolate具有自己的内存(堆)和线程控制的隔离运行环境。VM可以同时具有多个isolate执行Dart的代码,但不同的isolate之间不能直接共享任何的状态,只能通过消息端口来进行通信。 我们所说的线程和isolate之间的关系其实有点模糊,而且isolate也比较依赖VM是怎样嵌入到应用程序当中的。线程和isolate有下面这样的一些限制:

  • 一个线程一次只能进入一个isolate,如果线程要进入另一个isolate就要先退出当前的
  • 一次只能有一个mutator线程和isolate相关联,mutator是用来执行Dart代码和调用VM API的线程

所以一个线程只能进入一个isolate执行Dart代码,退出之后才能进入另一个isolate。 不同的线程也能进入同一个isolate,但不能同时。

当然除了拥有一个mutator线程之外,isolate还可以有多个helper线程,比如:

  • 后台JIT编译线程
  • GC线程
  • 并发的GC标记线程

VM内部使用了线程池(dart::ThreadPool)来管理系统的线程,而且内部是基于 dart::ThreadPool::Task 的概念去构建的,而不是直接使用的系统线程。例如,GC的过程就是生成一个 dart::ConcurrentSweeperTask 丢给VM的线程池去处理,而不是使用一个专门的线程来做垃圾回收,线程池可以选择一个空闲的线程或者在没有空闲线程的时候新建一个线程来处理这个任务。类似的,消息循环的处理也并没有使用一个专门的event loop线程,而是在有新消息的时候产生一个 dart::MessageHandlerTask 给线程池。

执行源码

你可以在命令行下直接给Dart的源码去执行,例如:


事实上Dart 2 VM之后就不再支持直接运行Dart源码了,VM使用了一种Kernel binaries(也就是 dill 文件)包含了序列化的 Kernel ASTs。所以源代码要先经过通用前端  common front-end (CFE) 处理成Kernel AST,而CFE是用Dart写的,可以给VM/dart2js/Dart Dev Compiler这些不同的Dart工具使用。

前端编译

那么为了保持直接执行Dart源码的便捷性,所以有一个叫做kernel serviceisolate,负责将Dart源码处理成KernelVM再将Kernel binary拿去运行。

但是CFE和用户的Dart代码是可以在不同的设备上执行,例如在Flutter当中,就是将Dart代码编译成Kernel,和执行Kernel的过程个隔离开来,编译Dart源码的步骤放在了用户的开发机上,执行Kernel放在了移动设备上,Flutter tools负责从开发机上将Kernel binary发送到移动设备上。

flutter tool并不能自己解析Dart源码,它使用了一个叫frontend_server的处理,frontend_server实际上就是CFE的封装和Flutter上特定的Kernel-to-Kernel的转换。frontend_server编译Dart源码到Kernel文件,flutter tools将它同步到执行设备上。Flutterhot reload也正是依赖frontend_server的,frontend_serverhot reload的过程中能够重用之前编译中的CFE状态,只重编已经更改了的部分。

Kernel binary 装载

只有Kernel binary能够被VM加载,并解析创建各种对象。不过这个过程是懒加载的,只有被使用到的库和类的信息才会被装载。每一个程序的实体都会保留指向对应Kernel binary的指针,在需要的时候可以去加载更多的信息。

类的信息只有在被使用的过程中(例如:查找类的成员,或新建对象)才会被完全反序列化出来,从Kernel binary读取类的成员信息,但是函数只会反序列化出函数签名信息,函数体只有在被调用运行的时候才会进一步反序列化出来。

Kernel binary加载了足够多的信息供运行时成功解析和调用方法之后,就会去解析和调用到main函数了。

函数编译

程序运行的最初所有的函数主体都不是实际可执行的代码,而是一个占位符,指向LazyCompileStub,它只是简单的要求运行时系统为当前的函数生成可执行的代码,然后尾部调用新生成的代码。

首次编译函数时,是通过未优化编译器来完成的。

未优化编译器通过两个步骤来生成机器码:

  1. 对函数主体的序列化AST进行遍历,以生成函数主体的控制流程图CFGCFG由填充了中间语言IL指令的基本块组成。这里使用的IL指令类似于基于堆栈的虚拟机的指令:从堆栈中获取操作数,执行操作,然后将结果压入同一堆栈。
  2. CFG使用一对多的低级IL指令直接生成机器码:每条IL指令扩展为多条机器指令

这个过程中还没有执行优化,未优化编译器的目标是快速的生成可执行指令。这也意味着不会尝试静态解析任何未从Kernel binary文件中加载的调用,所以调用的编译是动态完成的。VM在这个时候还不能使用任何基于vitual table或者interface table的调度,而是使用inline caching实现动态调用的。

inline caching的核心是在调用的时候缓存对应方法解析的结果,VM使用的inline caching机制包括:

  • 一个调用的特殊缓存,将接收的类映射到方法,如果接收者具有匹配的类型则调用方法,缓存还会有一些辅助信息,比如:调用频次计数器,跟踪特定类型出现的频次。
  • 一个共享的stub,实现方法调用的快速路径,stub在给定的缓存中查找是否有和接收者匹配的类型,如果找到了增加相应的频次计数器,并且尾部调用缓存的方法;否则,stub调用系统的查找解析逻辑,如果解析成功就更新缓存,并且后续的调用使用对应缓存的方法。

下图说明了inline cacheanimal.toFace()调用时的关系和状态,使用Dog实例调用两次,Cat实例调用一次:

未优化的编译器足以执行所有的Dart代码,只是它的执行速度会慢一些,所以呢VM还需要实现自适应的优化编译路径,自适应的优化是采用程序运行时的信息去驱动优化策略。未优化的代码在运行时会收集以下信息:

  • Inline caches过程中每一个方法调用接受的类型信息
  • 执行计数器收集的热点代码区

当某个函数的执行计数器达到某个阈值,这个函数就会提交给后台优化编译器进行优化。

优化编译

优化编译的方式和未优化编译有点类似,通过遍历序列化的Kernel AST为正在优化的函数构建未优化的IL,不同的是与其直接将IL转换为机器码,优化编译器会将未优化的IL转换成基于static single assignment (SSA)的优化IL。基于SSAIL根据收集到的类型信息,经典的优化手段和Dart的特殊优化:比如,inlining, range analysis, type propagation, representation selection, store-to-load and load-to-load forwarding, global value numbering, allocation sinking, etc。最后,使用线性扫描寄存器分配器和简单的一对多的IL指令,将优化的IL降低为机器码。

编译完成之后后端编译器请求mutator线程进入一个安全点(safepoint)并且将优化的代码attaches到对应的调用函数上,下次调用该函数的时候就能直接使用优化的代码。

需要注意的是,由优化编译器生成的代码是基于运行时收集到的特定信息完成的,例如一个接受动态类型的函数调用,只接收到某个特定的类型,就会被转换成直接的调用,然后检查接收到的类型是否一致。但是在程序的执行过程中,有可能接收到的类型是其他的。

反优化

优化代码是基于运行时信息对输入做了一些假设而产生的,如果在后续的运行过程中输入和假设不匹配,它就要防止违反这些假设,并且能够在违反的情况能够恢复正常运行。这个过程就叫着反优化:只要优化版本遇到无法处理的情况,它就会将执行转移到未优化函数的匹配点并继续运行。未优化的版本不做任何假设,可以处理所有可能的输入。

VM通常会在反优化后放弃优化的版本,然后在以后使用更新的类型反馈再次对其进行优化。VM防止违反优化假设一般有两种方式:

  • Inline checks (e.g. CheckSmi, CheckClass IL instructions)验证输入是否符合优化。例如,将动态调用转换为直接调用时,编译器会在直接调用之前添加这些检查。在此类检查中发生的反优化称为eager deoptimization,因为它很容易在 check 的时候被检测出来。
  • 全局保护程序,指令运行时在更改优化代码所依赖的内容时丢弃优化代码。例如,优化编译器可能发现某些类C从未扩展过,并在类型传播过程中使用了此信息。但是,随后的动态代码加载或类最终确定可能会引入C的子类-使得假设无效。这个时候,运行时需要查找并丢弃所有在C没有子类的假设下编译的优化代码。运行时可能会在执行堆栈上找到一些现在无效的优化代码,在这种情况下,受影响的frames将被标记,并且在执行返回时将对其进行反优化。这种反优化也称为延迟反优化:因为它会延迟到控制权返回到优化代码为止。

运行 Snapshots

VM有能力序列化isolate堆上的对象为二进制的snapshot文件,并且可以使用snapshot重新创建相同状态的isolate.

snapshot针对启动速度做了相应的优化,本质上是要创建的对象的列表和他们之间关系。相对于解析Dart源码并逐步创建VM内部的数据结构,VM可以将isolate所必须的数据结构全部打包在snapshot中。

但最初snapshot是不包括机器码的,在后来开发AOT编译的时候就加上去了,开发AOT编译和带机器码的snapshot是为了允许VM在一些无法JIT的平台上运行。带代码的snapshot几乎和普通的snapshot的工作方式是一样的,只是它带有一个代码块,这部分是不需要反序列化的,代码块可以直接map进堆内存。

运行 AppJIT snapshots

AppJIT snapshot可以减少大型Dart应用(比如:dartanalyzer 或者 dart2js)的JIT预热时间,在小型应用和VM使用JIT编译的时间差不多。

AppJIT snapshots其实是VM使用一些模拟的数据来训练程序,然后将生成的代码和VM内部的数据结构序列化而生成的,然后分发这个snapshot而不是源码或者Kernel binaryVM使用这个snapshot仍然可以在实际运行的过程中发现数据不匹配训练时而启用JIT

运行 AppAOT snapshots

AOT snapshot最初是为了无法进行JIT编译的平台而引入的,但也可以用来优化启动速度。无法进行JIT就意味着:

  1. AOT snapshot必须包含在应用程序执行期间可以调用的每个功能的可执行代码
  2. 可执行代码不能基于运行时的数据进行任何的假设

为了满足这些要求,AOT编译过程中会进行全局静态分析(type flow analysis or TFA),以从已知的入口点确定应用程序的哪些部分是被使用的,分配了哪些类以及类型是如何在程序中传递的。所有这些分析都是保守的,因为必须要保证正确性,有可能会牺牲一点性能,这跟JIT不太一样,JIT生成的代码还可以通过反优化来回到未优化的代码上运行。然后所有可达的代码块都将被编译成机器码,不会再进行任何的类型推测的优化。编译完所有的代码块之后,就可以获得堆的快照了。

然后,可以使用预编译的运行时来运行生成的snapshot,该运行时是Dart VM的特殊变体,其中不包括诸如JIT和动态代码加载工具之类的组件。

Switchable Calls

即使进行了全局和局部分析,AOT编译的代码仍可能包含无法静态虚拟化的调用操作。为了弥补这种情况,运行时使用了类似JIT过程中的inline cache,在这里叫着switchable callsJIT部分上面讲过了,inline cache主要包括两部分,一个缓存对象(通常是 dart::UntaggedICData )和一个VM的调用(例如:InlineCacheStub),在JIT模式下运行时只会更新 cache 的缓存,但是在AOT中,运行时可以根据inline cache的状态选择替换缓存和要调用的VM函数路径。

所有的动态调用最初都是unlinked状态,首次调用时会触发UnlinkedCallStub的调用,它又会调用DRT_UnlinkedCall去 link 当前的调用点。 如果DRT_UnlinkedCall尝试将调用点的状态切换为monomorphic,在这个状态下调用就会被替换成直接调用,它通过一个特殊的入口进入方法,并且在入口处验证类型。

在上图的例子中,当 obj.method() 首次执行时,obj 是 C 的实例,那么 obj.method 就会被解析成 C.method,下一次出现同样的调用就会直接调用到 C.method,跳过方法查找的过程。但是进入 C.method 仍然是通过一个特殊的入口进入的,验证 obj 是 C 的实例;如果不是的话,DRT_MonomorphicMiss 就会被调用尝试去进入下一个状态。C.method 有可能仍然是调用的目标函数,例如,obj 是类D的实例,D继承C并且没有overrideC.method。在这种情况下,我们检查是否可以进入single target状态,由 SingleTargetCallStub 实现(也可查看 dart::UntaggedSingleTargetCache)。

AOT编译过程中,大部分类会在继承结构的深度优先遍历过程分配一个 ID,如果类C具有D0..Dn这些子类,而且都没有override C.method,那么C.:cid <= classId(obj) <= max(D0.:cid, ..., Dn.:cid)表示 obj.method 会被解析成 C.method。在这种情况下,与其进行单态类(monomorphic状态)的比较,我们可以使用类的 ID 范围去检查C的所有子类。

换言之,调用的时候会使用线行扫描inline cache, 类似JIT模式(查看ICCallThroughCodeStubdart::UntaggedICData 以及 dart::PatchableCallHandler::DoMegamorphicMiss)

当然,如果线性数组中的检查数量超过阈值,将切换为使用类似字典的数据结构。(查看 MegamorphicCallStubdart::UntaggedMegamorphicCache 以及 dart::PatchableCallHandler::DoMegamorphicMiss)

参考链接


攻防启示:Chromium组件风险剖析与收敛

I. 背景

数月前我们在攻防两个方向经历了一场“真枪实弹”的考验,期间团队的目光曾一度聚焦到Chromium组件上。其实,早在 Microsoft 2018年宣布 Windows的新浏览器 Microsoft Edge 将基于Chromium内核进行构建之前,伴随互联网发展至今的浏览器之争其实早就已经有了定论,Chromium已然成为现代浏览器的事实标准,市场占有率也一骑绝尘。在服务端、桌面还是移动端,甚至据传SpaceX火箭亦搭载了基于Chromium开发的控制面板。

Chromium内核的安全问题,早已悄无声息地牵动着互联网生活方方面面。基于对实战经历的复盘,本文将从Chromium架构及安全机制概况入手,剖析Chromium组件在多场景下给企业带来的安全风险并一探收敛方案。

II. 浅析Chromium

2.1 Chromium涉及哪些组件?

Chromium主要包含两大核心组成部分:渲染引擎和浏览器内核。

2.1.1 渲染引擎

Chromium目前使用Blink作为渲染引擎,它是基于webkit定制而来的,核心逻辑位于项目仓库的third_party/blink/目录下。渲染引擎做的事情主要有:

  • 解析并构建DOM树。Blink引擎会把DOM树转化成C++表示的结构,以供V8操作。
  • 调用V8引擎处理JavaScript和Web Assembly代码,并对HTML文档做特定操作。
  • 处理HTML文档定义的CSS样式
  • 调用Chrome Compositor,将HTML对应的元素绘制出来。这个阶段会调用OpenGL,未来还会支持Vulkan。在Windows平台上,该阶段还会调用DirectX库处理;在处理过程中,OpenGL还会调用到Skia,DirectX还会调用到ANGLE。

Blink组件间的调用先后关系,可用下图概括:

可以说,几乎所有发生在浏览器页签中的工作,都有Blink参与处理。由于涉及许多组件库,不难想象过程中可能会出现的安全风险一定不少。据《The Security Architecture of the Chromium Browser》一文的统计数据,约67.4%的浏览器漏洞都出在渲染引擎中,这也是为什么要引入Sandbox这么重要。

2.1.2 浏览器内核

浏览器内核扮演连接渲染引擎及系统的“中间人”角色,具有一定“特权”,负责处理的事务包括但不限于:

1) 管理收藏夹、cookies以及保存的密码等重要用户信息

2) 负责处理网络通讯相关的事务

3) 在渲染引擎和系统间起中间人的角色。渲染引擎通过Mojo与浏览器内核交互,包含组件:download、payments等等。

2.2 Chromium的沙箱保护原理/机制

1、为什么要引入沙箱?

前述部分提到,Chromium渲染引擎涉及大量C++编写的组件,出现漏洞的概率不小。因此,基于纵深防御理念浏览器引入了涉及三层结构。渲染引擎等组件不直接与系统交互,而是通过一个被称为MOJO的IPC组件与浏览器引擎通讯(也被称为:broker),再与系统交互。进而可以实现:即便沙箱中的进程被攻破,但无法随意调用系统API产生更大的危害。有点类似:即便攻破了一个容器实例,在没有逃逸或提权漏洞的情况下,宿主机安全一定程度上不受影响(实际上,浏览器的Sandbox和容器隔离的部分技术原理是相似的)。

2、浏览器的哪些部分是运行在沙箱中的?

浏览器渲染引擎、GPU、PPAPI插件以及语音识别服务等进程是运行在沙箱中的。此外不同系统平台下的部分服务也会受沙箱保护,例如Windows下打印时调用的PDF转换服务、icon浏览服务;MacOS下NaCl loader、需要访问IOSurface的镜像服务等。

更多细节可查阅Chromium项目文件sandbox_type.h和sandbox_type.cc中的源码定义:

3、Windows和Linux下沙箱实现的技术细节

Windows

在Windows平台上,Chrome组合使用了系统提供的Restricted Token、Integrity Level、The Windows job object、The Windows desktop object机制来实现沙盒。其中最重要的一点是,把写操作权限限制起来,这样攻击这就无法通过写入文件或注册表键来攻击系统。

Linux

Chrome在Linux系统上使用的沙箱技术主要涉及两层:

层级 功能
Layer - 1 用于限制运行在其中的进程对资源的访问
Layer - 2 用于有关进程对系统内核某些攻击面的访问

第一层沙箱采用setuid sandbox方案。

其主要功能封装在二进制文件chrome_sandbox内,在编译项目时需要单独添加参数“ninja -C xxx chrome chrome_sandbox”编译,可以通过设置环境变量CHROME_DEVEL_SANDBOX指定Chrome调用的setuid sandbox二进制文件。

setuid sandbox主要依赖两项机制来构建沙盒环境:CLONE_NEWPID和CLONE_NEWNET方法。CLONE_NEWPID一方面会借助chroots,来限制相关进程对文件系统命名空间的访问;另一方面会在调用clone()时指定CLONE_NEWPID选项,借助PID namespace,让运行在沙盒中的进程无法调用ptrace()或kill()操作沙盒外的进程。而CLONE_NEWNET则用于限制在沙盒内进程的网络请求访问,值得一提的是,使用该方法需要CAP_SYS_ADMIN权限。这也使得当Chrome组件在容器内运行时,沙箱能力所需的权限会和容器所管理的权限有冲突;我们无法用最小的权限在容器里启动Chrome沙箱,本文4.2.2部分会详细阐述此处的解决之道。

更多详参见Linux Namespace及cgroups介绍说明:《Resource management: Linux kernel Namespaces and cgroups》- https://sites.cs.ucsb.edu/~rich/class/cs293b-cloud/papers/lxc-namespace.pdf

由于setuid sandbox方案存在一定短板。自Chrome 44版本起已推荐namespaces sandbox来替代setuid sandbox方案,其主要依赖于Linux内核提供的user namespaces机制,相关逻辑可在项目的如下行代码看到:

第二层沙箱采用Seccomp-BPF方案,用来限制进程访问内核特定攻击面。

其原理是:通过将Seccomp和BPF规则结合,实现基于用户配置的策略白名单,对系统调用及其参数进行过滤限制。

https://source.chromium.org/chromium/chromium/src/+/main:sandbox/policy/linux/bpf_audio_policy_linux.cc;l=34;drc=8d990c92df3d03ff3d313428f25dd11b7e509bcf;bpv=1;bpt=1

2.3 小结

Chromium涉及的组件众多,使用的C++语言天然决定了会潜在不少安全问题。例如:一个V8中的内存安全问题(如:CVE-2021-21220、CVE-2019–5782),组合Web Assembly将Shellcode写入RWX Pages,在未受沙箱保护的情况下,就能实现远程代码执行。

沙箱机制组合使用了OS相关的隔离能力(如:Linux平台上的namespace、Seccomp-BPF机制),限制了被沙箱保护进程的资源访问以及syscall能力,能很好的防止出现在渲染引擎中的漏洞,被用于直接实现RCE :但沙箱机制也存在一些不足,历史上也出现过沙箱逃逸的漏洞,例如:Google Project Zero团队曾发布的《Virtually Unlimited Memory: Escaping the Chrome Sandbox》一文。

综上,在无法100%预防Chromium渲染进程出现内存安全问题的情况下,开启沙箱保护是一项必须落地的最佳安全实践。

III. Chromium漏洞攻击利用场景分析

作为一款客户端组件,在评估Chromium漏洞时,常常会聚焦于客户端的攻防场景。但根据我们的经验,受chromium漏洞影响的不仅有客户端应用,也包含了服务器上运行的程序,例如:部署在服务器端、基于Chrome Headless应用的爬虫程序等。

3.1 服务器端

3.1.1 禁用沙盒的chromium headless应用

随着Phantomjs项目停止维护,Chromium headless已经成为Headless Browser的首选。在日常开发、测试、安全扫描、运维中,有许多地方会用到Headless Browser,包括不限于以下场景:

在这些场景中,如果程序本身使用的Chromium存在漏洞,且访问的URL可被外部控制,那么就可能受到攻击最终导致服务器被外部攻击者控制。

以常见的使用Chrome headless的爬虫为例,如果在一些网站测试投放包含exploit的链接,有概率会被爬虫获取,相关爬取逻辑的通常做法是新建tab导航至爬取到的链接。此时,如果爬虫依赖的chromium应用程序更新不及时,且启动时设置了—no-sandbox参数,链接指向页面内的exploit会成功执行,进而允许攻击者控制爬虫对应的服务器。

为何 —no-sandbox 会如此泛滥呢?我们不妨来看一下,当我们在ROOT下启动Chrome,会有什么样的提示呢?

我们会得到 Running as root without —no-sandbox is not supported 的错误提示,且无法启动 Chrome;这对于以研发效率和产品功能优先的研发同学来说无异于提示“请使用 —no-sandbox 来启动 Chrome”, 应用容器化的进程也加剧了使用ROOT用户启动应用程序的情况。你不得不创建一个新的普通用户来启动Chrome服务,例如在 Dockerfile 里加入 RUN useradd chrome 和 USER chrome 语句;有些基于Chrome的著名第三方库甚至会在代码中隐形植入关闭 sandbox的代码,当研发同学在ROOT下启动应用程序时,第三方库会默认关闭sandbox,添加 —no-sandbox 参数,例如 Golang 第三方 package Chromedp 的代码:

此时,对于开发同学来说使用 —no-sandbox 参数甚至是无感的,直至自己的容器或服务器被攻击者入侵控制。

即使研发同学sandbox来避免安全风险的意识,在容器化的应用内启动chrome也是不易的;为镜像创建一个新的非ROOT用户并非唯一的条件,Chrome sandbox 需要调用一些特定的 syscall 或 linux capabilities 权限用于启动 sandbox 逻辑,同时容器镜像需要打入 chrome-sandbox二进制文件并写入环境变量以供Chrome进程找到sandbox程序。若未对Chrome容器进行特定的权限配置,chrome将输出 Operation not permitted 报错信息并退出。

所以,网络上有大量的文档和博客推荐启用 —no-sandbox 来解决 Chrome headless 的使用问题,这也间接助长了 —no-sandbox 参数这种错误用法的泛滥:

我们将在后面的章节里详细为您讲解 Chrome Sandbox 在容器以及容器集群中方便快捷且安全合理的部署解决方案。

3.1.2 浅议攻击方式

未知攻焉知防?虽然在已有Exploit的情况下进行漏洞利用并不困难,但知悉漏洞利用的流程和攻击行为有助于我们更好的构建安全能力。以下以最近的CVE-2021-21224漏洞为例,当服务端上程序使用的chromium版本存在漏洞时,且未开启Sandbox,可以利用这个漏洞来获取服务器的权限。

首先攻击者使用metasploit生成shellcode,这里假设chromium是在linux上运行且架构为x64。同时,考虑到爬虫运行结束后往往会结束浏览器进程,通过设置PrependFork为true可以保证session的持久运行。

生成shellcode后监听端口:

实战中,可以通过投递带exploit的链接到各个网站上,这里假设攻击者控制的服务器正在被爬取或者正在被渗透测试人员的扫描器扫描:

成功获取到爬虫/扫描器的服务器session:

meterpreter的进程是fork后的chrome子进程:

可以猜想,不仅是各种内嵌浏览器的客户端程序易受chromium相关漏洞影响,可能有相当多的服务端程序也暴露在chromium 0Day/Nday的攻击下。 chromium漏洞将会成为企业防御边界的新的突破口,而这个突破口是自内而外的,相比开放端口在外的服务漏洞,这种攻击可能会更隐蔽。

作为防御方,我们也可以利用chromium漏洞来反制一些攻击者,如果攻击者安全意识较差或者使用的工具安全性不强,防御方在服务器上托管带有exploit的网页,攻击者的爬虫/扫描器扫到了这些网页就可能被反制攻击。

3.2 客户端

在面对Chromium组件风险时,客户端场景往往首当其冲。通常,其风险成立条件有两点:1、使用了存在漏洞的Chromium组件;2、可以指定Webview组件访问特定的网站地址。

3.2.1 移动客户端

目前,移动客户端主要分两大“阵营”:安卓和iOS,最大相关风险是Webview类组件。前者 Android System Webview是基于Chromium源代码开发的,所以当1 Day披露时,需要及时跟进影响;iOS App一般会使用WKWebView和JavaScriptCore,Chromium 1 Day影响iOS应用的可能性较低。

客户端内置Webview浏览器窗口

除了使用系统自带的Webview组件,另外一种比较常见且更容易引起注意的方式是使用应用内置或独立于系统之外的浏览器组件;此时,应用会选用Chromium体系的概率较高。应用选择自己内置并维护浏览器组件的原因有很多,例如以下几类需求:

1、在浏览器内核层回收更多用于Debug的客户端信息;
2、支持如夜间模式、中文优化等用户需求;
3、支持更多的视频格式和文件格式;

也有应用为了应对此前App Store在WWDC大会提出的限制(即App Store中的所有应用都必须启用App Transport Security 安全功能并全量走HTTPS),使用改过的Webview组件曲线救国,以便达到App Store的合规需求。

也因为应用自己维护所使用的浏览器组件,当系统的WebView跟随系统升级而修复漏洞时,应用所使用的的浏览器组件并不跟着更新;作为应用开发者自己维护的硬分支,Chromium不断的功能变更和漏洞修复补丁都需要应用开发者自行合并和兼容;这不仅需要硬核的浏览器研发能力也需要日以继夜不断的坚持。再加上,无论在移动端还是桌面客户端,在使用应用内WebView时为了更加轻便和简洁,浏览器组件多是以单进程的方式启动;而在我们之前对Sandbox技术的介绍中,浏览器Sandbox和单进程WebView组件显然是冲突的;这也使得历史上关闭Sandbox能力的客户端程序,在漏洞修复过程中,对于开启Sandbox的修复操作存在历史包袱。

无论如何,我们始终不建议移动端应用的WebView组件可以由用户控制并打开开放性的页面;这会使得应用内加载的内容可能存在不可控或不可信的内容。WebView组件可以打开的URL,应该用白名单进行限制;特别是可以用 Deeplink 打开并且存在 URL 参数的 WebView。

3.2.2 桌面客户端

许多桌面客户端应用也是基于Chromium构建的。一类是基于Chromium定制的浏览器产品、或内置基于Chromium开发Webview组件的桌面客户端应用;另一类是基于Electron构建的桌面客户端应用。

前者与传统Chrome浏览器或是嵌入在移动客户端的Webview组件类似,如果未开启沙箱保护,面临很大的风险。而后者Electron则是在评估Chromium漏洞攻防利用场景时,比较容易被忽视的一块。Electron基于Chromium和Node构建,其主要特性之一就是能在渲染进程中运行Node.js。目前有许多客户端工具基于它开发,涉及:VS Code、Typora、Slack等。默认情况下,渲染器进程为受沙箱保护,这是因为:大多数Node.js 的API都需要系统权限,没有文件系统权限的情况下require()是不可用的,而该文件系统权限在沙箱环境下是不可用的,但功能性进程受沙箱保护。 Electron除面临渲染引擎本身的安全风险外,主要风险源自于其本身的功能特性 —— nodeIntegration。当该选项被设置为true,表示renderer有权限访问node.js API,进而执行“特权”操作。这时如果攻击者能自由控制渲染的页面内容,则可直接实现RCE。

IV. 风险收敛方案

回到我们今天的主题:修复和防御。如上我们知道,Chromium的安全问题是方方面面的,各类安全风险也会在不同的场景上产生,那么如何收敛就是企业安全建设永恒的话题;最后我们想分享我们的安全实践经验,力求解答在安全实践中我们遇到的以下几个问题,如:Chrome组件的漏洞都有哪些?Google又是如何跟进它们的?我们又该如何评估和检测Chrome持续更新过程中所公开的1Day风险?最终如何修复?Linux容器中开启Chrome沙盒的最佳实践又是什么?

4.1 风险监测和评估

4.1.1 风险情报

有两个渠道可以及时了解到Chromium漏洞披露情况:

● Chromium工单系统。该平台上收录了所有已公开的Chrome安全Issue,可借助特定关键词检索。如检索已公开的高风险安全问题,可访问:https://bugs.chromium.org/p/chromium/issues/list?can=1&q=Security_Severity%3DHigh%20&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified&sort=-modified&num=100&start=

● Chrome发布日志。Chrome稳定版本发布消息会在https://chromereleases.googleblog.com/上发出,和稳定版本发布消息一起的还有该版本做了哪些安全更新以及对应漏洞的奖金。

事实上,甲方安全人员还可以借助一些技巧,提前了解安全问题的修复细节。Gerrit是基于git的一款Code Review平台,chrome team使用该平台进行code review:https://chromium-review.googlesource.com/。该平台上的主题会关联对应的issue id,通过对应修复commit的主题可以了解到issue的修复方案和代码。

chromium使用https://bugs.chromium.org对chromium的bug进行跟踪。可以用短链来访问对应的issue,例如issue 1195777可以用该链接访问:https://crbug.com/1195777。

chromium安全问题对应关联的issue在修复期间并且在补丁发布后也不一定是可见的,官方给出的披露原则是在补丁广泛应用后才会开放issue的限制。但是Gerrit上对issue修复代码的code review和关联信息是一直可见的,我们如果想了解某个issue具体的修复代码和方案可以在Gerrit上找到。

以issue 1195777为例,在Gerrit使用bug搜索关键字可以搜到对应commit的code review主题:

而如果只有CVE编号,CVE的References一般会给出issue的短链,虽然通常该issue限制访问,但是仍可以通过Gerrit了解相关issue的具体修复代码,安全研究者可以根据这些修复代码对该问题进行分析,进而推测出漏洞复现代码。

难怪Twitter上某位研究员会说:“如果0-Day有Chromium Bug Tracker的编号,那它就不算0-Day了”。

4.1.2 风险评估

通常,在Chromium官方披露漏洞或外部已出现在野利用的案例后,应进行风险评估,主要聚两个问题:

● 公司内哪些产品受漏洞影响?
● 外部披露的exp是否能真实利用形成危害?

在获悉一个漏洞的存在后,安全人员需要评估漏洞对公司的影响如何。通常一个可利用的漏洞在披露后会马上有安全人员写出exploit,而公开的exploit将导致利用门槛的大幅降低。因此,常常需要监控公开信息渠道的exploit信息,例如:监控Github、Twitter等平台的消息。但是早在exploit披露前,就可以通过

Chromium Monorail系统中的issues、代码CL或者更新日志提前了解风险。

一个漏洞的影响评估流程可以按下面几步走:

1、 确定存在漏洞组件为哪个部分。

2、 采集使用了该组件的产品(包括:使用了嵌入式浏览器的客户端、单纯使用v8引擎等组件的软件、使用了chrome headless的服务端程序);有些产品仅使用chrome的一部分组件可能不受影响。例如:v8就会影响所有用Chromium内核的产品,但iOS客户端如果用JavaScriptCore,则不受影响。

3、 确认使用存在漏洞组件的产品使用的版本是否受影响,如果产品本身对chromium进行定制化开发的情况下,难以根据版本确定,可以通过PoC(部分场景下,可借助Chromium项目中的单元测试用例)进行黑盒测试或者白盒审计受影响代码是否存在,是否存在漏洞的触发路径。

4、 原则上内存破坏类的漏洞在没有exploit公开的情况下也需要尽快修复,存在公开exploit的情况下,需要立即修复;有时候exploit使用到的exploit技术可能仅适用于某些版本的chromium,但是并不代表这些版本之外的chromium完全没有利用的可能。例如使用WebAssembly创建RWX pages来写入shellcode的技术在客户端使用的chromium版本不支持,但依旧存在通过ROP等技术来到达RCE的可能性。

4.1.3 风险检测

4.1.3.1 黑盒测试

V8等组件会编写单元测试js文件,可以基于此修改形成页面,来通过黑盒的方式判断组件是否受对应漏洞影响。对于漏洞测试来说,这个资源也是极好的TestCase。

以CVE-2021-21224为例,编写黑盒测试用例过程如下:

1、 通过Issue编号定位到对应的Chromium Gerrit工单
https://chromium-review.googlesource.com/c/v8/v8/+/2838235

2、 定位到官方提供的、针对该漏洞的单元测试文件
https://chromium-review.googlesource.com/c/v8/v8/+/2838235/4/test/mjsunit/compiler/regress-1195777.js

3、 基于单元测试文件修改生成黑盒测试用例
如果仔细观察,会发现上述单元测试代码中包含%开头的函数。它们是v8引擎内置的runtime函数,用于触发v8引擎的某些功能特性,需要在v8的debug版本d8命令行工具启动时,追加—allow-natives-syntax参数才会生效。因此,直接将上述单元测试js来测试是无法准确测出是否存在漏洞的。但可以通过编写js代码,实现相同的效果,例如:

值得一提的是,前述漏洞的单元测试用例并不会造成浏览器tab崩溃,而只是输出的数值与预期不符。因此,可以看到上述单元测试用例中引入了assertTrue、assertEquals等断言方法,用于判断单元测试数值是否与预期相等。如果不等,则认为存在漏洞。在进行改造时,也要一并用自己的JavaScript代码替换。最终,前述官方提供的测试用例可改造如下:

4、 最终效果如下


4.1.3.2 静态代码扫描

如上面所述,由于Chrome漏洞即便在没有正式发布安全公告前,就已经有Issue ID,且能通过Gerrit平台找到涉及的代码变动。因此,开发人员可以抢先在公司内部代码仓库进行全局静态代码扫描并修复问题。

目的 策略 目的
收集包含chromium组件的仓库 扫描特定文件名特征(如有需要可添加一些代码特征) 掌握企业内应用的组件指纹
精确判断某个Issue对应的代码是否已修复 扫描文件名特征 + 每个Issue对应的代码特征 追踪特定漏洞的修复情况

● 收集包含chromium组件的仓库
不同的项目可能会引入Chromium整体或部分相关的组件,通常可结合文件名、或特定的代码片段,在公司的代码仓库中收集包含相关指纹的仓库。

● 精确判断某个Issue对应的代码是否已修复
以要精准扫描全局代码仓库中是否存在涉及v8组件的CVE-2021-21224的漏洞代码为例。可基于semgrep类方案,对公司代码仓库进行全局检查,编写静态代码扫描步骤如下:

1、 根据Issue号找到对应的漏洞修复代码变动
● https://chromium-review.googlesource.com/c/v8/v8/+/2838235
● https://chromium-review.googlesource.com/c/v8/v8/+/2838235/4/src/compiler/representation-change.cc

2、确定涉及文件representation-change.cc,存在漏洞的代码特征为

3、可编写semgrep规则如下

4、调用命令扫描

5、最终效果,如下

4.1.3.3 主机Agent采集

针对部署在服务器端、且使用了Chromium的程序,除了上述方法之外,可以考虑借助HIDS、EDR或RASP等系统采集进程特征,排查存在风险的实例。

同时满足下面两个条件的 cmdline,其进程我们就可以认为是存在风险的:

● 程序名包含 Chrome 或 Chromium
● 且 Cmdline 中包含 —no-sandbox 参数或 —disable-setuid-sandbox

关于误报

这里大家可能会产生疑问,这里为什么单独检测 Sandbox 的开启与关闭就判断风险呢?若Chromium组件已经使用最新发布的commit编译而成,包含了所有的漏洞补丁,也一样不会受到1Day和NDay漏洞的影响。其实,这里主要考虑到Chrome在对漏洞修复是十分频繁的,持续的升级存在一定的维护成本,且不排除攻击者拥有Chromium 0Day的可能。相较之下,逃逸Sandbox以控制浏览器所在的主机,是比较困难的;所以要求线上业务,尽可能开启 Chromium Sandbox特性。

关于漏报

另外,以上方案若Chrome可执行文件被修改了文件名,则容易产生漏报。另一种可选的方案是:提取出多个Chrome的特有选项进行过滤。例如,headless浏览器启动时一般不会导航至特定url,此时命令行会存在about:blank,再用Chrome特定的区别于其他浏览器的选项进行排除。

更复杂的方案可以提取出Chrome执行文件的文件特征,或者建立Chrome执行文件的hashsum数据库来判断进程的执行文件是否是Chrome浏览器,进而再筛选启动时用了不安全配置的进程。其实,我们在大规模观察相关的进程数据和运营之后,发现利用 —no-sandbox 单个因素进行进程数据分析并获取未开启Sandbox的Chromium进程,这样简单粗暴的做法并不会产生太多误报;有些进程看似非 Chromium 浏览器,但其实也集成了 Chromium 并使用 no-sandbox 参数。

4.2 风险修复

4.2.1 通用修复方案

无论是客户端还是服务端,为了解决Chrome漏洞的远程命令执行风险,启用Chrome Sandbox,去除启动Chrome组件时的 —no-sandbox参数都是必须推进的安全实践。

如果客户端程序直接使用了Chrome的最新版本,且未进行过于复杂的二次开发和迁移,没有历史包袱的话,在客户端里开启Chrome Sandbox,其实就是使用Chrome组件的默认安全设计,障碍是比较小的。

此处根据不同场景和需求,存在三种不同的修复方案:

方案1. 启用Sandbox

1、启动 Chrome 时切勿使用 —no-sandbox 参数,错误的例子如:./bin/chrome --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 --disable-setuid-sandbox --no-sandbox

2、使用普通用户而非 root 用户启动 chrome headless 进程

方案2. 更新Chromium内核版本(后续维护成本极高):

下载 https://download-chromium.appspot.com/ 中的最新版本进行更新,并在后续迭代中持续升级到最新版(Chromium的最新版本会编译最新的MR和Commit,因此也会修复Chrome未修复的0.5Day漏洞,下载链接包含了所有的操作系统的 Chromium ,例如Linux 可访问 https://download-chromium.appspot.com/?platform=Linux_x64&type=snapshots 下载)。

请注意,如果不希望相似的安全风险如之前的Fastjson那样需要反复跟进并且高频推动业务修复,强烈建议安全团队推动业务参考方案一开启Sandbox,方案二可以当成短期方案规避当前风险。经统计,2010年至今Google共对外公开Chromium高危漏洞1800多个;Chromium的漏洞修复十分频繁,若不开启Sandbox,需持续更新最新版本。

若要启用Sandbox,需要解决一定的依赖:首先,Chrome的Sandbox技术依赖于Linux内核版本,低版本的内核无法使用。各Sandbox技术Linux内核依赖可参考下图

(图片来源 官方文档 https://chromium.googlesource.com/chromium/src/+/master/docs/linux/sandboxing.md#sandbox-types-summary):

Chrome 运行时会寻找 chrome-sandbox 文件,一般下载 Chrome 的 Release 时,Chrome程序目录下都包含了 Sandbox 程序,若无法寻找到 chrome-sandbox 文件可能会产生下述 Error 信息:

[0418/214027.785590:FATAL:zygote_host_impl_linux.cc(116)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live dangerously and need an immediate workaround, you can try using —no-sandbox.

可参考
https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#alternative-setup-setuid-sandbox 进行配置。若服务器的 Chrome 目录下包含了 chrome-sandbox 文件,则可以直接修改配置运行,若不包含,可前往 https://download-chromium.appspot.com/ 下载对应版本的 chrome-sandbox 文件使用。(注:Chrome 可执行文件的同一目录内包含 chrome-sandbox 程序,则无需手动设置 CHROME_DEVEL_SANDBOX 环境变量)

方案3、客户端选择系统默认浏览器打开外链URL

另外一个更加合适合理的设计是尽量避免使用应用内置的浏览器打开开放性URL页面。我们应该尽量使用系统的浏览器去打开非公司域名的URL链接(同时应该注意公司域名下的URL跳转风险);把打开URL的能力和场景交还给系统浏览器或专门的浏览器应用;保障应用内加载的资源都是可控的。

此方案同样适用于:客户端内置的Chromium Webview组件短时间内无法随系统快速更新,且由于历史包袱无法Webview组件无法开启沙箱。此时,在客户端引入一个“降级”逻辑,将不可信的页面跳转交给系统默认的浏览器打开。由于系统默认的浏览器通常默认是打开沙箱的,因此不失为一种“缓兵之计”。

4.2.2 云原生时代下,针对Chrome组件容器化的风险修复指引

业界云原生实践的发展非常迅速,企业应用容器化、组件容器化的脚步也势不可挡。从当前的Kubernetes应用设计的角度出发,Chrome Headless组件在逻辑上是非常适用于无状态应用的设计的,所以Chrome组件在容器化的进程也比较快。也因此,在HIDS进程大盘中, 启用 —no-sandbox 的 Chrome headless 进程也一直在持续增多。

如果 Chrome 浏览器组件已经实现了容器化,那么您想使用 Chrome sandbox 肯定会遇到各种麻烦;网络上有很多不完全安全的建议和文档,请尽量不要给容器添加 privileged 权限和 SYS_ADMIN 权限,这将可能引入新的风险,详情可参考我们之前的文章《红蓝对抗中的云原生漏洞挖掘及利用实录》。

我们应该尽量使用例如 —security-opt 的方案对容器权限进行可控范围内的限制,构建一个 Seccomp 白名单用于更安全的支持容器场景,这是一个足够优雅且较为通用的方式。如果企业已经践行了K8s容器集群安全管理的规范和能力,在集群内新建带有privileged 权限或 SYS_ADMIN 权限的应用容器是会被集群管理员明确拒绝的,Seccomp是一个安全且可管理的方案。

你可以参考下述方式启动一个带有 seccomp 配置的容器:
docker run -it —security-opt seccomp:./chrome.json chrome-sandbox-hub-image-near —headless —dump-dom https://github.com/neargle

实际上seccomp配置文件规定了一个可管理的syscall白名单,我们的配置文件就是需要把Sandbox所需的系统权限用白名单方式赋予给容器,使得容器可以调用多个原本默认禁止的 syscall。可以使用下列命令来检测当前的操作系统是否支持 seccomp:
➜ grep CONFIG_SECCOMP= /boot/config-$(uname -r)
CONFIG_SECCOMP=y

如果你的容器使用K8s进行部署,那你可以在 spec.securityContext.seccompProfile 中配置上述 chrome.json 文件。

通过白名单设置 Chrome 所需的 syscall 以最小化容器权限,避免容器逃逸的风险,同时也符合多租户容器集群的安全设计,是一个推荐的方案;设置 Seccomp 后,容器内可正常启用 chrome-sandbox,如下图。

根据在HIDS收集到的资产和内部操作系统的特性,可以利用 strace 工具很容易收集到启动 Sandbox 所需的 SysCall,并根据 SysCall 编写所需的 seccomp 配置文件。当然直接使用开源社区里现成的配置文件也能适用于绝大部分环境,著名前端测试工具 lighthouse 所用的配置文件是一个非常不错的参考:https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/recipes/docker-client/seccomp-chrome.json。

V. 总结

随着Chromium在企业各场景下的广泛应用,需要针对性地设置风险例行检测及应急响应方案,涉及的风险与应用场景、检查及修复方式,可概括如下:

除Chromium外,企业开发时也不乏会涉及到Safari、Firefox等其他浏览器类组件的场景,在进行风险排查和响应时可借鉴类似的思路。

参考及引用

[1] Linux Sandboxing
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux/sandboxing.md
[2] The Security Architecture of the Chromium Browser
https://seclab.stanford.edu/websec/chromium/chromium-security-architecture.pdf
[3] My Take on Chrome Sandbox Escape Exploit Chain
https://medium.com/swlh/my-take-on-chrome-sandbox-escape-exploit-chain-dbf5a616eec5
[4] Linux SUID Sandbox
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux/suid_sandbox.md
[5] How Blink Works
https://docs.google.com/document/d/1aitSOucL0VHZa9Z2vbRJSyAIsAz24kX8LFByQ5xQnUg/edit
[6] Chrome浏览器引擎 Blink & V8
https://zhuanlan.zhihu.com/p/279920830
[7] Blink-in-JavaScript
https://docs.google.com/presentation/d/1XvZdAF29Fgn19GCjDhHhlsECJAfOR49tpUFWrbtQAwU/htmlpresent
[8] core/script: How a Script Element Works in Blink
https://docs.google.com/presentation/d/1H-1U9LmCghOmviw0nYE_SP_r49-bU42SkViBn539-vg/edit#slide=id.gc6f73
[9] [TPSA21-12] 关于Chrome存在安全问题可能影响Windows版本微信的通告
https://mp.weixin.qq.com/s/qAnxwM1Udulj1K3Wn2awVQ
[10] Hacking Team Android Browser Exploit代码分析
https://security.tencent.com/index.php/blog/msg/87
[11] 物联网安全系列之远程破解Google Home
https://security.tencent.com/index.php/blog/msg/141
[12] Android Webview UXSS 漏洞攻防
https://security.tencent.com/index.php/blog/msg/70

参考链接


攻防启示:Chromium组件风险剖析与收敛

macOS Big Sur(11.6.2) 编译Flutter engine

准备同步代码:

生成配置代码同步配置文件:

内容如下:

注意,上面的 name 字段不能变更,否则会在同步代码的时候报错