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

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

$ gradlew --warning-mode all

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

如果有报以下警告:

Using insecure protocols with repositories, without explicit opt-in, has been deprecated. This is scheduled to be removed in Gradle 7.0. Switch Maven repository 'm
aven(http://maven.aliyun.com/nexus/content/groups/public/)' to a secure protocol (like HTTPS) or allow insecure protocols. See https://docs.gradle.org/6.8.3/dsl/or
g.gradle.api.artifacts.repositories.UrlArtifactRepository.html#org.gradle.api.artifacts.repositories.UrlArtifactRepository:allowInsecureProtocol for more details.
Configuration on demand is an incubating feature.

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

Description :Specifies whether it is acceptable to communicate with a repository over an insecure HTTP connection.

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

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

* What went wrong:
A problem occurred configuring root project 'xxxx'.
> Could not resolve all dependencies for configuration ':classpath'.
   > Using insecure protocols with repositories, without explicit opt-in, is unsupported. Switch Maven repository 'maven(http://xxx.xxx.xxx.xxx/jcenter/)' to red
irect to a secure protocol (like HTTPS) or allow insecure protocols. See https://docs.gradle.org/7.0.2/dsl/org.gradle.api.artifacts.repositories.UrlArtifactRepos
itory.html#org.gradle.api.artifacts.repositories.UrlArtifactRepository:allowInsecureProtocol for more details.

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

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

repositories {
	...
        
    maven {
        allowInsecureProtocol = true
        ...
    }
}

参考链接


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

How to repair a broken/unmountable btrfs filesystem

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

$ sudo btrfs check --force --repair /dev/sdb7
enabling repair mode
Opening filesystem to check...
WARNING: filesystem mounted, continuing because of --force
Checking filesystem on /dev/sdb7
UUID: 57e881c6-4aac-4f0c-aed9-74341f525e79
repair mode will force to clear out log tree, are you sure? [y/N]: y
parent transid verify failed on 83801812992 wanted 276510 found 277808
parent transid verify failed on 83801812992 wanted 276510 found 277808
parent transid verify failed on 83801812992 wanted 276510 found 277808
Ignoring transid failure
[1/7] checking root items
parent transid verify failed on 83801812992 wanted 276510 found 277808
Ignoring transid failure
parent transid verify failed on 83800657920 wanted 276512 found 277808
parent transid verify failed on 83800657920 wanted 276512 found 277808
parent transid verify failed on 83800657920 wanted 276512 found 277808
Ignoring transid failure
ERROR: child eb corrupted: parent bytenr=194117632 item=11 parent level=1 child level=1
ERROR: failed to repair root items: Input/output error

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
$ mount /dev/sda1 /mnt
  • If it mounts - are you sure it's broken? if Yes - run
$ btrfs scrub start /mnt

to scrub the system, and

$ btrfs scrub status /mnt

to monitor it

  • If it doesn't mount, try to scrub the device just in case it works
$ btrfs scrub start /dev/sda1

and

$ btrfs scrub status /dev/sda1

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

$ mount -o usebackuproot /dev/sda1 /mnt

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>"
$ btrfs check /dev/sda1

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>"
$ btrfs restore /dev/sda1 /mnt/usbdrive

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>"
$ btrfs rescue super-recover /dev/sda1

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

  • Run "btrfs rescue zero-log <device>"
$ btrfs rescue zero-log /dev/sda1

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

  • Run "btrfs rescue chunk-recover <device>"
$ btrfs rescue chunk-recover /dev/sda1

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>"
$ btrfs restore /dev/sda1 /mnt/usbdrive
  • 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>"
$ btrfs check --repair /dev/sda1

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

参考链接


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 单元测试代码的时候报错

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

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

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

Build-tool 32.0.0 is missing DX at C:\Users\xxxx\AppData\Local\Android\Sdk\build-tools\32.0.0\dx.bat

详细的报错信息如下:

Starting Gradle Daemon...
Gradle Daemon started in 2 s 200 ms

> Configure project :app
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Observed package id 'system-images;android-31;google_apis;x86_64' in inconsistent location 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-S\google_apis\x86_64' (Expected 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-31\google_apis\x86_64')
Build-tool 32.0.0 is missing DX at C:\Users\xxxx\AppData\Local\Android\Sdk\build-tools\32.0.0\dx.bat
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Observed package id 'system-images;android-31;google_apis;x86_64' in inconsistent location 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-S\google_apis\x86_64' (Expected 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-31\google_apis\x86_64')
File C:\Users\xxxx\.android\repositories.cfg could not be loaded.
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Mapping new ns http://schemas.android.com/repository/android/common/02 to old ns http://schemas.android.com/repository/android/common/01
Mapping new ns http://schemas.android.com/repository/android/generic/02 to old ns http://schemas.android.com/repository/android/generic/01
Mapping new ns http://schemas.android.com/sdk/android/repo/addon2/02 to old ns http://schemas.android.com/sdk/android/repo/addon2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/repository2/02 to old ns http://schemas.android.com/sdk/android/repo/repository2/01
Mapping new ns http://schemas.android.com/sdk/android/repo/sys-img2/02 to old ns http://schemas.android.com/sdk/android/repo/sys-img2/01
Observed package id 'system-images;android-31;google_apis;x86_64' in inconsistent location 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-S\google_apis\x86_64' (Expected 'C:\Users\xxxx\AppData\Local\Android\Sdk\system-images\android-31\google_apis\x86_64')
Build-tool 32.0.0 is missing DX at C:\Users\xxxx\AppData\Local\Android\Sdk\build-tools\32.0.0\dx.bat

FAILURE: Build failed with an exception.

* What went wrong:
Could not resolve all dependencies for configuration ':app:uatOutDebugRuntimeClasspath'.
Could not create task ':app:minifyPreProOutReleaseWithR8'.
Cannot query the value of this provider because it has no value available.

* Try:
Run with --info or --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
com.intellij.openapi.externalSystem.model.ExternalSystemException: Could not resolve all dependencies for configuration ':app:uatOutDebugRuntimeClasspath'.
Could not create task ':app:minifyPreProOutReleaseWithR8'.
Cannot query the value of this provider because it has no value available.
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.addBuildModels(ProjectImportAction.java:258)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:116)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:41)
	at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.runAction(AbstractClientProvidedBuildActionRunner.java:134)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.buildFinished(AbstractClientProvidedBuildActionRunner.java:119)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:398)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:380)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:368)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:355)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:43)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:245)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:157)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:346)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:249)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy19.buildFinished(Unknown Source)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:196)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:132)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:66)
	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:59)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:58)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:34)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:28)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
org.gradle.api.artifacts.ResolveException: Could not resolve all dependencies for configuration ':app:uatOutDebugRuntimeClasspath'.
	at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingConfigurationResolver.wrapException(ErrorHandlingConfigurationResolver.java:105)
	at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingConfigurationResolver.resolveGraph(ErrorHandlingConfigurationResolver.java:76)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$1.call(DefaultConfiguration.java:645)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$1.call(DefaultConfiguration.java:636)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveGraphIfRequired(DefaultConfiguration.java:636)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.lambda$resolveExclusively$4(DefaultConfiguration.java:616)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$CalculatedModelValueImpl.update(DefaultProjectStateRegistry.java:362)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveExclusively(DefaultConfiguration.java:613)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveToStateOrLater(DefaultConfiguration.java:600)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$1900(DefaultConfiguration.java:150)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1341)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1331)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getSelectedArtifacts(DefaultConfiguration.java:1403)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.access$3500(DefaultConfiguration.java:1346)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.lambda$new$0(DefaultConfiguration.java:1970)
	at org.gradle.internal.model.CalculatedValueContainerFactory$SupplierBackedCalculator.calculateValue(CalculatedValueContainerFactory.java:78)
	at org.gradle.internal.model.CalculatedValueContainer$CalculationState.lambda$attachValue$0(CalculatedValueContainer.java:216)
	at org.gradle.internal.Try.ofFailable(Try.java:39)
	at org.gradle.internal.model.CalculatedValueContainer$CalculationState.attachValue(CalculatedValueContainer.java:211)
	at org.gradle.internal.model.CalculatedValueContainer.finalizeIfNotAlready(CalculatedValueContainer.java:184)
	at org.gradle.internal.model.CalculatedValueContainer.finalizeIfNotAlready(CalculatedValueContainer.java:175)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.ensureResolved(DefaultConfiguration.java:2012)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.getArtifacts(DefaultConfiguration.java:1989)
	at com.android.build.gradle.internal.ide.dependencies.ArtifactDependencyGraph.getRuntimeComponentIdentifiers(ArtifactDependencyGraph.java:362)
	at com.android.build.gradle.internal.ide.dependencies.ArtifactDependencyGraph.createDependencies(ArtifactDependencyGraph.java:192)
	at com.android.build.gradle.internal.ide.ModelBuilder.getDependencies(ModelBuilder.java:841)
	at com.android.build.gradle.internal.ide.ModelBuilder.createAndroidArtifact(ModelBuilder.java:869)
	at com.android.build.gradle.internal.ide.ModelBuilder.createVariant(ModelBuilder.java:609)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildVariant(ModelBuilder.java:564)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildAll(ModelBuilder.java:221)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildAll(ModelBuilder.java:144)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuilderWithParameter.build(DefaultToolingModelBuilderRegistry.java:194)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.lambda$build$0(DefaultToolingModelBuilderRegistry.java:211)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.build(DefaultToolingModelBuilderRegistry.java:211)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder$1.call(DefaultToolingModelBuilderRegistry.java:246)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder.build(DefaultToolingModelBuilderRegistry.java:243)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getParameterizedModel(DefaultBuildController.java:156)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getModel(DefaultBuildController.java:104)
	at org.gradle.tooling.internal.consumer.connection.ParameterAwareBuildControllerAdapter.getModel(ParameterAwareBuildControllerAdapter.java:39)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.getModel(UnparameterizedBuildController.java:113)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.getModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:97)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction$MyBuildController.findModel(ProjectImportAction.java:574)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.findVariantModel(AndroidExtraModelProviderWorker.kt:521)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.access$findVariantModel(AndroidExtraModelProviderWorker.kt:48)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker$getVariantAndModuleDependenciesAction$1.invoke(AndroidExtraModelProviderWorker.kt:450)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker$getVariantAndModuleDependenciesAction$1.invoke(AndroidExtraModelProviderWorker.kt:449)
	at com.android.tools.idea.gradle.project.sync.SequentialSyncActionRunner.runAction(SyncActionRunner.kt:61)
	at com.android.tools.idea.gradle.project.sync.SequentialSyncActionRunner.runActions(SyncActionRunner.kt:57)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.chooseSelectedVariants(AndroidExtraModelProviderWorker.kt:364)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.populateAndroidModels(AndroidExtraModelProviderWorker.kt:154)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.populateBuildModels(AndroidExtraModelProviderWorker.kt:65)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProvider.populateBuildModels(AndroidExtraModelProvider.kt:52)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.addBuildModels(ProjectImportAction.java:246)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:116)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:41)
	at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.runAction(AbstractClientProvidedBuildActionRunner.java:134)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.buildFinished(AbstractClientProvidedBuildActionRunner.java:119)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:398)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:380)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:368)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:355)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:43)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:245)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:157)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:346)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:249)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy19.buildFinished(Unknown Source)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:196)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:132)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:66)
	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:59)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:58)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:34)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:28)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreationException: Could not create task ':app:minifyPreProOutReleaseWithR8'.
	at org.gradle.api.internal.tasks.DefaultTaskContainer.taskCreationException(DefaultTaskContainer.java:719)
	at org.gradle.api.internal.tasks.DefaultTaskContainer.access$600(DefaultTaskContainer.java:77)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.domainObjectCreationException(DefaultTaskContainer.java:711)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:948)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.access$1401(DefaultTaskContainer.java:658)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider$1.run(DefaultTaskContainer.java:684)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.api.internal.tasks.DefaultTaskContainer$TaskCreatingProvider.tryCreate(DefaultTaskContainer.java:680)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.calculateOwnValue(DefaultNamedDomainObjectCollection.java:929)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.provider.FlatMapProvider.calculateOwnValue(FlatMapProvider.java:46)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.provider.MappingProvider.calculateOwnValue(MappingProvider.java:55)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:128)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:25)
	at org.gradle.api.internal.provider.AbstractProperty.doCalculateValue(AbstractProperty.java:133)
	at org.gradle.api.internal.provider.AbstractProperty.calculateOwnValue(AbstractProperty.java:127)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:128)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:25)
	at org.gradle.api.internal.provider.AbstractProperty.doCalculateValue(AbstractProperty.java:133)
	at org.gradle.api.internal.provider.AbstractProperty.calculateOwnValue(AbstractProperty.java:127)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.calculateValue(AbstractMinimalProvider.java:103)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:128)
	at org.gradle.api.internal.provider.DefaultProperty.calculateValueFrom(DefaultProperty.java:25)
	at org.gradle.api.internal.provider.AbstractProperty.doCalculateValue(AbstractProperty.java:133)
	at org.gradle.api.internal.provider.AbstractProperty.calculateOwnValue(AbstractProperty.java:127)
	at org.gradle.api.internal.provider.AbstractMinimalProvider.get(AbstractMinimalProvider.java:84)
	at org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact.getDelegate(LazyPublishArtifact.java:94)
	at org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact.getName(LazyPublishArtifact.java:64)
	at org.gradle.util.GUtil.getOrDefault(GUtil.java:167)
	at org.gradle.api.internal.artifacts.publish.DecoratingPublishArtifact.getName(DecoratingPublishArtifact.java:51)
	at org.gradle.api.internal.artifacts.publish.DecoratingPublishArtifact_Decorated.getName(Unknown Source)
	at org.gradle.internal.component.model.DefaultIvyArtifactName.forPublishArtifact(DefaultIvyArtifactName.java:36)
	at org.gradle.internal.component.local.model.PublishArtifactLocalArtifactMetadata.<init>(PublishArtifactLocalArtifactMetadata.java:40)
	at org.gradle.internal.component.local.model.DefaultLocalComponentMetadata.addVariant(DefaultLocalComponentMetadata.java:160)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultLocalComponentMetadataBuilder$1.visitChildVariant(DefaultLocalComponentMetadataBuilder.java:66)
	at org.gradle.api.internal.artifacts.configurations.DefaultVariant.visit(DefaultVariant.java:72)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfigurationPublications.collectVariants(DefaultConfigurationPublications.java:95)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.collectVariants(DefaultConfiguration.java:1052)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultLocalComponentMetadataBuilder.addConfiguration(DefaultLocalComponentMetadataBuilder.java:53)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultRootComponentMetadataBuilder.addConfiguration(DefaultRootComponentMetadataBuilder.java:102)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultRootComponentMetadataBuilder.getRootComponentMetadata(DefaultRootComponentMetadataBuilder.java:96)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultRootComponentMetadataBuilder.lambda$buildRootComponentMetadata$0(DefaultRootComponentMetadataBuilder.java:86)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:277)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultRootComponentMetadataBuilder.buildRootComponentMetadata(DefaultRootComponentMetadataBuilder.java:84)
	at org.gradle.api.internal.artifacts.ivyservice.moduleconverter.DefaultRootComponentMetadataBuilder.toRootComponentMetaData(DefaultRootComponentMetadataBuilder.java:70)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.toRootComponentMetaData(DefaultConfiguration.java:1194)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultArtifactDependencyResolver$DefaultResolveContextToComponentResolver.resolve(DefaultArtifactDependencyResolver.java:227)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.graph.builder.DependencyGraphBuilder.resolve(DependencyGraphBuilder.java:138)
	at org.gradle.api.internal.artifacts.ivyservice.resolveengine.DefaultArtifactDependencyResolver.resolve(DefaultArtifactDependencyResolver.java:145)
	at org.gradle.api.internal.artifacts.ivyservice.DefaultConfigurationResolver.resolveGraph(DefaultConfigurationResolver.java:180)
	at org.gradle.api.internal.artifacts.ivyservice.ShortCircuitEmptyConfigurationResolver.resolveGraph(ShortCircuitEmptyConfigurationResolver.java:85)
	at org.gradle.api.internal.artifacts.ivyservice.ErrorHandlingConfigurationResolver.resolveGraph(ErrorHandlingConfigurationResolver.java:74)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$1.call(DefaultConfiguration.java:645)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$1.call(DefaultConfiguration.java:636)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveGraphIfRequired(DefaultConfiguration.java:636)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.lambda$resolveExclusively$4(DefaultConfiguration.java:616)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$CalculatedModelValueImpl.update(DefaultProjectStateRegistry.java:362)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveExclusively(DefaultConfiguration.java:613)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveToStateOrLater(DefaultConfiguration.java:600)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$1900(DefaultConfiguration.java:150)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1341)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1331)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getSelectedArtifacts(DefaultConfiguration.java:1403)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.access$3500(DefaultConfiguration.java:1346)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.lambda$new$0(DefaultConfiguration.java:1970)
	at org.gradle.internal.model.CalculatedValueContainerFactory$SupplierBackedCalculator.calculateValue(CalculatedValueContainerFactory.java:78)
	at org.gradle.internal.model.CalculatedValueContainer$CalculationState.lambda$attachValue$0(CalculatedValueContainer.java:216)
	at org.gradle.internal.Try.ofFailable(Try.java:39)
	at org.gradle.internal.model.CalculatedValueContainer$CalculationState.attachValue(CalculatedValueContainer.java:211)
	at org.gradle.internal.model.CalculatedValueContainer.finalizeIfNotAlready(CalculatedValueContainer.java:184)
	at org.gradle.internal.model.CalculatedValueContainer.finalizeIfNotAlready(CalculatedValueContainer.java:175)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.ensureResolved(DefaultConfiguration.java:2012)
	at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationArtifactCollection.getArtifacts(DefaultConfiguration.java:1989)
	at com.android.build.gradle.internal.ide.dependencies.ArtifactDependencyGraph.getRuntimeComponentIdentifiers(ArtifactDependencyGraph.java:362)
	at com.android.build.gradle.internal.ide.dependencies.ArtifactDependencyGraph.createDependencies(ArtifactDependencyGraph.java:192)
	at com.android.build.gradle.internal.ide.ModelBuilder.getDependencies(ModelBuilder.java:841)
	at com.android.build.gradle.internal.ide.ModelBuilder.createAndroidArtifact(ModelBuilder.java:869)
	at com.android.build.gradle.internal.ide.ModelBuilder.createVariant(ModelBuilder.java:609)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildVariant(ModelBuilder.java:564)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildAll(ModelBuilder.java:221)
	at com.android.build.gradle.internal.ide.ModelBuilder.buildAll(ModelBuilder.java:144)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuilderWithParameter.build(DefaultToolingModelBuilderRegistry.java:194)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.lambda$build$0(DefaultToolingModelBuilderRegistry.java:211)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.lambda$withProjectLock$3(DefaultProjectStateRegistry.java:310)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.withProjectLock(DefaultProjectStateRegistry.java:310)
	at org.gradle.api.internal.project.DefaultProjectStateRegistry$ProjectStateImpl.fromMutableState(DefaultProjectStateRegistry.java:291)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$LockSingleProjectBuilder.build(DefaultToolingModelBuilderRegistry.java:211)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder$1.call(DefaultToolingModelBuilderRegistry.java:246)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.tooling.provider.model.internal.DefaultToolingModelBuilderRegistry$BuildOperationWrappingBuilder.build(DefaultToolingModelBuilderRegistry.java:243)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getParameterizedModel(DefaultBuildController.java:156)
	at org.gradle.tooling.internal.provider.runner.DefaultBuildController.getModel(DefaultBuildController.java:104)
	at org.gradle.tooling.internal.consumer.connection.ParameterAwareBuildControllerAdapter.getModel(ParameterAwareBuildControllerAdapter.java:39)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.getModel(UnparameterizedBuildController.java:113)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.getModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.gradle.tooling.internal.consumer.connection.UnparameterizedBuildController.findModel(UnparameterizedBuildController.java:97)
	at org.gradle.tooling.internal.consumer.connection.NestedActionAwareBuildControllerAdapter.findModel(NestedActionAwareBuildControllerAdapter.java:31)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction$MyBuildController.findModel(ProjectImportAction.java:574)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.findVariantModel(AndroidExtraModelProviderWorker.kt:521)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.access$findVariantModel(AndroidExtraModelProviderWorker.kt:48)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker$getVariantAndModuleDependenciesAction$1.invoke(AndroidExtraModelProviderWorker.kt:450)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker$getVariantAndModuleDependenciesAction$1.invoke(AndroidExtraModelProviderWorker.kt:449)
	at com.android.tools.idea.gradle.project.sync.SequentialSyncActionRunner.runAction(SyncActionRunner.kt:61)
	at com.android.tools.idea.gradle.project.sync.SequentialSyncActionRunner.runActions(SyncActionRunner.kt:57)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.chooseSelectedVariants(AndroidExtraModelProviderWorker.kt:364)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.populateAndroidModels(AndroidExtraModelProviderWorker.kt:154)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProviderWorker.populateBuildModels(AndroidExtraModelProviderWorker.kt:65)
	at com.android.tools.idea.gradle.project.sync.AndroidExtraModelProvider.populateBuildModels(AndroidExtraModelProvider.kt:52)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.addBuildModels(ProjectImportAction.java:246)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:116)
	at org.jetbrains.plugins.gradle.model.ProjectImportAction.execute(ProjectImportAction.java:41)
	at org.gradle.tooling.internal.consumer.connection.InternalBuildActionAdapter.execute(InternalBuildActionAdapter.java:64)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.runAction(AbstractClientProvidedBuildActionRunner.java:134)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner$ActionRunningListener.buildFinished(AbstractClientProvidedBuildActionRunner.java:119)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
	at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:398)
	at org.gradle.internal.event.DefaultListenerManager$ListenerDetails.dispatch(DefaultListenerManager.java:380)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:368)
	at org.gradle.internal.event.DefaultListenerManager$EventBroadcast$ListenerDispatch.dispatch(DefaultListenerManager.java:355)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:43)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:245)
	at org.gradle.internal.event.BroadcastDispatch$SingletonDispatch.dispatch(BroadcastDispatch.java:157)
	at org.gradle.internal.event.AbstractBroadcastDispatch.dispatch(AbstractBroadcastDispatch.java:61)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:346)
	at org.gradle.internal.event.BroadcastDispatch$CompositeDispatch.dispatch(BroadcastDispatch.java:249)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:141)
	at org.gradle.internal.event.ListenerBroadcast.dispatch(ListenerBroadcast.java:37)
	at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
	at com.sun.proxy.$Proxy19.buildFinished(Unknown Source)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:196)
	at org.gradle.initialization.DefaultGradleLauncher.finishBuild(DefaultGradleLauncher.java:132)
	at org.gradle.internal.invocation.GradleBuildController$1.create(GradleBuildController.java:72)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:213)
	at org.gradle.internal.invocation.GradleBuildController.doBuild(GradleBuildController.java:67)
	at org.gradle.internal.invocation.GradleBuildController.run(GradleBuildController.java:56)
	at org.gradle.tooling.internal.provider.runner.AbstractClientProvidedBuildActionRunner.runClientAction(AbstractClientProvidedBuildActionRunner.java:53)
	at org.gradle.tooling.internal.provider.runner.ClientProvidedPhasedActionRunner.run(ClientProvidedPhasedActionRunner.java:47)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.ChainingBuildActionRunner.run(ChainingBuildActionRunner.java:35)
	at org.gradle.launcher.exec.BuildOutcomeReportingBuildActionRunner.run(BuildOutcomeReportingBuildActionRunner.java:66)
	at org.gradle.tooling.internal.provider.ValidatingBuildActionRunner.run(ValidatingBuildActionRunner.java:32)
	at org.gradle.tooling.internal.provider.FileSystemWatchingBuildActionRunner.run(FileSystemWatchingBuildActionRunner.java:90)
	at org.gradle.launcher.exec.BuildCompletionNotifyingBuildActionRunner.run(BuildCompletionNotifyingBuildActionRunner.java:41)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:49)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner$3.call(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:200)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:195)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:62)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$call$2(DefaultBuildOperationExecutor.java:76)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.callWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:54)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:76)
	at org.gradle.launcher.exec.RunAsBuildOperationBuildActionRunner.run(RunAsBuildOperationBuildActionRunner.java:44)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.lambda$execute$0(InProcessBuildActionExecuter.java:59)
	at org.gradle.composite.internal.DefaultRootBuildState.run(DefaultRootBuildState.java:86)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:58)
	at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:30)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.lambda$execute$0(BuildTreeScopeLifecycleBuildActionExecuter.java:34)
	at org.gradle.internal.buildtree.BuildTreeState.run(BuildTreeState.java:53)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:33)
	at org.gradle.launcher.exec.BuildTreeScopeLifecycleBuildActionExecuter.execute(BuildTreeScopeLifecycleBuildActionExecuter.java:28)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:104)
	at org.gradle.tooling.internal.provider.ContinuousBuildActionExecuter.execute(ContinuousBuildActionExecuter.java:55)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:64)
	at org.gradle.tooling.internal.provider.SubscribableBuildActionExecuter.execute(SubscribableBuildActionExecuter.java:37)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.lambda$execute$0(SessionScopeLifecycleBuildActionExecuter.java:54)
	at org.gradle.internal.session.BuildSessionState.run(BuildSessionState.java:67)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:50)
	at org.gradle.tooling.internal.provider.SessionScopeLifecycleBuildActionExecuter.execute(SessionScopeLifecycleBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:36)
	at org.gradle.tooling.internal.provider.GradleThreadBuildActionExecuter.execute(GradleThreadBuildActionExecuter.java:25)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:59)
	at org.gradle.tooling.internal.provider.StartParamsValidatingActionExecuter.execute(StartParamsValidatingActionExecuter.java:31)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:58)
	at org.gradle.tooling.internal.provider.SessionFailureReportingActionExecuter.execute(SessionFailureReportingActionExecuter.java:42)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:47)
	at org.gradle.tooling.internal.provider.SetupLoggingActionExecuter.execute(SetupLoggingActionExecuter.java:31)
	at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:65)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:39)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:29)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.RequestStopIfSingleUsedDaemon.execute(RequestStopIfSingleUsedDaemon.java:35)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:78)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.create(ForwardClientInput.java:75)
	at org.gradle.util.Swapper.swap(Swapper.java:38)
	at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:75)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogAndCheckHealth.execute(LogAndCheckHealth.java:55)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:63)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:84)
	at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:37)
	at org.gradle.launcher.daemon.server.api.DaemonCommandExecution.proceed(DaemonCommandExecution.java:104)
	at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:52)
	at org.gradle.launcher.daemon.server.DaemonStateCoordinator$1.run(DaemonStateCoordinator.java:297)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.gradle.api.internal.provider.MissingValueException: Cannot query the value of this provider because it has no value available.
	at org.gradle.api.internal.provider.AbstractMinimalProvider.get(AbstractMinimalProvider.java:86)
	at com.android.build.gradle.internal.scope.GlobalScope.getFullBootClasspath(GlobalScope.java:435)
	at com.android.build.gradle.internal.tasks.R8Task$CreationAction.configure(R8Task.kt:250)
	at com.android.build.gradle.internal.tasks.R8Task$CreationAction.configure(R8Task.kt:172)
	at com.android.build.gradle.internal.tasks.factory.TaskConfigurationActions.execute(TaskFactoryUtils.kt:94)
	at com.android.build.gradle.internal.tasks.factory.TaskConfigurationActions.execute(TaskFactoryUtils.kt:80)
	at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:44)
	at org.gradle.api.internal.DefaultMutationGuard$2.execute(DefaultMutationGuard.java:44)
	at org.gradle.configuration.internal.DefaultUserCodeApplicationContext$CurrentApplication$1.execute(DefaultUserCodeApplicationContext.java:100)
	at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction$1.run(DefaultCollectionCallbackActionDecorator.java:95)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:75)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$3.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:153)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:68)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:56)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.lambda$run$1(DefaultBuildOperationExecutor.java:71)
	at org.gradle.internal.operations.UnmanagedBuildOperationWrapper.runWithUnmanagedSupport(UnmanagedBuildOperationWrapper.java:45)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:71)
	at org.gradle.api.internal.DefaultCollectionCallbackActionDecorator$BuildOperationEmittingAction.execute(DefaultCollectionCallbackActionDecorator.java:92)
	at org.gradle.internal.ImmutableActionSet$SetWithManyActions.execute(ImmutableActionSet.java:329)
	at org.gradle.api.internal.DefaultDomainObjectCollection.doAdd(DefaultDomainObjectCollection.java:264)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection.doAdd(DefaultNamedDomainObjectCollection.java:113)
	at org.gradle.api.internal.DefaultDomainObjectCollection.add(DefaultDomainObjectCollection.java:258)
	at org.gradle.api.internal.DefaultNamedDomainObjectCollection$AbstractDomainObjectCreatingProvider.tryCreate(DefaultNamedDomainObjectCollection.java:944)
	... 244 more



* Get more help at https://help.gradle.org

BUILD FAILED in 57s

报错产生的原因是从 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) 进行了改进,现在帧处理优先于其他异步事件的处理,在我们的测试中,其导致的卡顿已经被消除。

继续阅读Flutter 2.5 更新详解

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

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

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

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

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

类对象传值

main(){
  Test a = new Test(5);
  print("a的初始值为:${a.value}");
  setValue(a);
  print("修改后a的值为: ${a.value}");
}

class Test{
  int value = 1;
  Test(int newValue){
    this.value = newValue;
  }
}

setValue(Test s){
  print("修改value为100");
  s.value = 100;
}

输出结果为:

a的初始值为:5
修改value为100
修改后a的值为:100

基本类型传值

main(){
  int s = 6;
  setValue(s);
  print(s); //输出6,而不是7
}
class Test{
  int value = 1;
  Test(int newValue){
    this.value = newValue;
  }
}
setValue(int s){
  s += 1;
}

上面的代码,其实转换成 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的源码去执行,例如:

// hello.dart
main() => print('Hello, World!');
$ dart hello.dart
Hello, World!

事实上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到对应的调用函数上,下次调用该函数的时候就能直接使用优化的代码。

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

void printAnimal(obj) {
  print('Animal {');
  print('  ${obj.toString()}');
  print('}');
}

// Call printAnimal(...) a lot of times with an intance of Cat.
// As a result printAnimal(...) will be optimized under the
// assumption that obj is always a Cat.
for (var i = 0; i < 50000; i++)
  printAnimal(Cat());

// Now call printAnimal(...) with a Dog - optimized version
// can not handle such an object, because it was
// compiled under assumption that obj is always a Cat.
// This leads to deoptimization.
printAnimal(Dog());

反优化

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

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中的源码定义:

// sandbox/policy/sandbox_type.h
Line 24 enum class SandboxType {

// sandbox/policy/sandbox_type.cc
Line 21 bool IsUnsandboxedSandboxType(SandboxType sandbox_type) {

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机制,相关逻辑可在项目的如下行代码看到:

https://source.chromium.org/chromium/chromium/src/+/main:sandbox/policy/linux/sandbox_linux.cc;drc=2311701cab51ef03be34fba491e0a855371d4f84;l=544

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

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

// sandbox/policy/linux/sandbox_linux.cc
Line 413 StartSeccompBPF(sandbox_type, std::move(hook), options);

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

(function() {
  function foo(b) {
    let y = (new Date(42)).getMilliseconds();
    let x = -1;
    if (b) x = 0xFFFF_FFFF;
    return y < Math.max(1 << y, x, 1 + y);
  }
  assertTrue(foo(true));
  %PrepareFunctionForOptimization(foo);
  assertTrue(foo(false));
  %OptimizeFunctionOnNextCall(foo);
  assertTrue(foo(true));
})();

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

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

function foo(b) {
    let y = (new Date(42)).getMilliseconds();
    let x = -1;
    if (b) x = 0xFFFF_FFFF;
    return y < Math.max(1 << y, x, 1 + y);
}
function check(val) {
    console.log(val);
    if(!val){
        alert("CVE-2021-21224 found!")
    }
}
// 断言函数,判断函数返回值是否为true
// assertTrue(foo(true));
let val1 = foo(true);
check(val1);
// v8内置runtime函数
// %PrepareFunctionForOptimization(foo);
for (i=0; i<100000; i++) {
foo(false);
}
// 断言函数,判断函数返回值是否为true
// assertTrue(foo(false));
let val2 = foo(false);
check(val2);
// v8内置runtime函数
// %OptimizeFunctionOnNextCall(foo);
for (i=0; i<100000; i++) {
foo(true);
}
// 断言函数,判断函数返回值是否为true
// assertTrue(foo(true));
let val3 = foo(true);
check(val3);

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,存在漏洞的代码特征为

if (output_type.Is(Type::Signed32()) ||
        output_type.Is(Type::Unsigned32())) {
      op = machine()->TruncateInt64ToInt32();
    } else if (output_type.Is(cache_->kSafeInteger) &&
               use_info.truncation().IsUsedAsWord32()) {
      op = machine()->TruncateInt64ToInt32();
}

3、可编写semgrep规则如下

rules:
- id: chromium-v8-1195777
  message: |
    A Type Confusion vulnerability in Chromium v8 Engine, labeled as CVE-2021-21224, is found in the project. Please fix this bug by following the [instructions](https://iwiki.woa.com/pages/viewpage.action?pageId=801201056) on iWiki.
  languages:
  - generic
  metadata:
    references:
    - https://chromereleases.googleblog.com/2021/04/stable-channel-update-for-desktop_20.html
    - https://chromium-review.googlesource.com/c/v8/v8/+/2817791/4/src/compiler/representation-change.cc
    category: security
  severity: WARNING
  pattern: |
    if (output_type.Is(Type::Signed32()) || output_type.Is(Type::Unsigned32())) {...} else if (output_type.Is(cache_->kSafeInteger) && use_info.truncation().IsUsedAsWord32()) {...}
  paths:
    include:
      - "representation-change.cc"

4、调用命令扫描

semgrep --include=representation-change.cc --config=1195777.yml thirdparty_v8

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/11.7.1) 编译Flutter engine

准备同步代码:

$ cd ~

$ mkdir flutter_engine

$ cd flutter_engine

# 检出构建依赖
# git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
# 国内用户可从gitlab中检出
$ git clone https://gitlab.com/chromiumsrc/depot_tools.git

# 添加到搜索路径
$ export PATH=$PATH:`pwd`/depot_tools

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

$ vim .gclient

内容如下:

solutions = [
  {
    "managed": False,
    "name": "src/flutter",
    "url": "https://github.com/flutter/engine.git",
    "custom_deps": {},
    "deps_file": "DEPS",
    "safesync_url": "",
  },
]

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

________ running 'python3 src/flutter/tools/pub_get_offline.py' in '~/flutter_engine'
/usr/local/bin/python3: can't open file '~/flutter_engine/src/flutter/tools/pub_get_offline.py': [Errno 2] No such file or directory
Error: Command 'python3 src/flutter/tools/pub_get_offline.py' returned non-zero exit status 2 in ~/flutter_src
/usr/local/bin/python3: can't open file '~/flutter_engine/src/flutter/tools/pub_get_offline.py': [Errno 2] No such file or directory

执行代码同步命令(国内需要配置代理):

$ gclient sync -D --force --reset

# 同步时间超长,要花费几个小时,同步代码约16-17GB,压缩之后约8.75GB

# 如果已经执行过同步,代码会被锁定到以前拉取到的某个特定版本。
# 如果想更新到主分钟的最新代码,目前没有很好的办法,只能先整个删除src目录,再次执行同步命令

# 当然,如果需要更新flutter的代码,相关依赖不更新的情况下,
# 也可以直接在src/flutter目录下执行git pull 命令来手动更新

从源代码开始构建:

$ cd src

$ ./flutter/tools/gn --ios --unoptimized

# 编译配置文件生成在out目录下

# 执行编译
$ ninja -C out/ios_debug_unopt

$ ./flutter/tools/gn --android --unoptimized

$ ninja -C out/android_debug_unopt

# release 
$ ./flutter/tools/gn --android --runtime-mode=release

$ ninja -C out/android_release

# 生成的 libflutter.so 很大,被压缩后的在 lib.stripped 目录下,但是这个版本的符号还在,进一步压缩可以移除符号

一般情况下我们使用的是官方版本的引擎,如果需要调试 libflutter.so 里面的符号,可以在flutter_infra页面直接下载带有符号表的SO文件,根据打包时使用的Flutter工具版本下载对应的文件即可。

比如2.8.1版本:

$ flutter --version # version命令可以看到Engine对应的版本 06afdfe54e
Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 77d935af4d (3 周前) • 2021-12-16 08:37:33 -0800
Engine • revision 890a5fca2e
Tools • Dart 2.15.1

$ cat `dirname $(which flutter)`/internal/engine.version # flutter安装目录下的engine.version文件也可以看到完整的版本信息 890a5fca2e34db413be624fc83aeea8e61d42ce6
890a5fca2e34db413be624fc83aeea8e61d42ce6

拿到引擎版本号后在https://console.cloud.google.com/storage/browser/flutter_infra_release/flutter/890a5fca2e34db413be624fc83aeea8e61d42ce6/ 看到该版本对应的所有构建产物,下载android-arm-release、android-arm64-release和android-x86目录下的symbols.zip。

参考链接