Chromium将拒绝第三方软件注入

Windows上有好几种代码注入的方式,一个软件被其他软件注入是比较常见的事情,比如输入法、杀毒软件。往往这些注入的模块导致了软件的不稳定性。Chrome统计大概有15%的的崩溃是由于第三方模式的注入导致的。中国环境更加恶劣,各种软件更加流氓,它们肆无忌惮的注入到你的程序中,甚至破坏你程序的功能。 Chrome... Read More | Share it now!

使用Tabhelper解耦WebContents

WebContents是Chromium里面非常核心的一个对象,它本身代表着一个tab页面。通过WebContents我们几乎可以涉及到所有重要的浏览器对象概念。WebContents如此重要,我们做Chromium开发的时候势必会跟它有很多交互。但是我们又不能对WebContents代码的本身做太多的修改。因为随着业务的发展,会导致WebContents的逻辑越来越多,最后代码膨胀,难以维护。所以需要设计一套机制来对WebContents的逻辑进行解耦。 实际上Chromium已经设计好了一套很好的Tabhelper机制来解耦WebContents。我们可以做到完全不用改的WebContents代码而扩展WebContents的逻辑。介绍Tabhelper机制之前我先介绍一下SupportsUserData机制,下面是简化的代码例子: SupportsUserData机制就是不通过增加类的成员变量,把一种类型的实例绑定到另一个类的实例上。以上例子是我们把NameData、AgeData这种类型的实例动态的绑定到Host类的实例上。这样做的好处一是我们不用修改Host类的代码可以动态扩展Host类的功能,具有扩展性,二是相比类变量会绑定到类的所有实例上,而SupportsUserData机制可以让我们有选择的绑到到类的某些实例上,更具有伸缩性。 Tabhelper就是基于SupportsUserData实现的。因为WebContents继承自base::SupportsUserData,所以它支持别的类的实例动态的绑定到一个WebContents实例上。通常一个Tabhelper继承自WebContentsObserver和WebContentsUserData,而WebContentsUserData继承自base::SupportsUserData::Data。通过继承自WebContentsObserver,它可以获得WebContents各种状态和事件。 当一个WebContents在CreateTargetContents里面被创建的时候,会在TabHelpers::AttachTabHelpers里面绑定上很多功能的TabHelpers。我们以页面上的查找框功能FindTabHelper为例子,FindTabHelper就是在这个时候被绑定到一个WebContents上面,并且TabHelper的生命周期归对应的WebContents管理。 除了这种在WebContents创建的时候就绑定上TabHelper,其实可以在WebContents的生命周期内通过CreateForWebContents随时绑定上一个TabHelper。只要有WebContents对象,我们随时可以通过FromWebContents获取到对应的TabHelper。 参考: https://chromium.googlesource.com/chromium/src/+/lkcr/docs/tab_helpers.md   ... Read More | Share it now!

HTML5支持更多音视频格式

HTML5引进了新的的<audio>和<video>标签,可以直接播放音频和视频。但是不同浏览器对音频视频的支持格式有所不同。比如Chromium浏览器不支持MP3、MP4格式,而Chrome却支持。根本原因是浏览器内核所依赖的FFmpeg编译开关不一样导致的的。 在Chromium代码工程的生成gn工程的时候加上以上两个值: 就可以让Chromium编译出来的浏览器像Chrome一样支持MP3、MP4格式格式了。 proprietary_codecs的作用是开启代码中的USE_PROPRIETARY_CODECS宏,使Chromium的代码支持更多的音视频解码格式。ffmpeg_branding则是作用于Chromium所依赖的第三方ffmpeg工程的代码,使之编译相关的支持代码。 Chromium的音视频解码是调用的第三方库ffmpeg。ffmpeg几乎支持所有的音视频格式,那么Chromium理论上也是可以支持所有的音视频格式的能力。实际上由于ffmpeg这个库很大,为了优化浏览器的体积,Chromium只编译链接了它所需要支持格式的相关代码,只占整个ffmpeg很小的一部分。 我以3GP格式为例,介绍如何让Chromium内核的video标签支持更多视频格式。 我们先编写一个测试3gp格式视频的html文件: 然后在MimeUtil::AddSupportedMediaFormats里面添加如下2行: 这是让浏览器能够识别Video标签中的video/3gpp。 3gp只是视频的容器格式,实际上的音频和视频的编解码器是有多种可能的。通过工具查看这个test.3gp的音频格式,如下: 这个的视频格式是mpeg4,是Chromium内核默认所不支持的。而音频格式是aac,Chromium内核默认支持。所以为了支持这个浏览器播放这个3gp文件,只需要添加mepg4的解码器相关的代码即可。所以我们修改一下Chromium代码编译ffmpeg的开关。 因为之前我们已经把ffmpeg_branding设置成”Chrome”,因此我们的的配置文件是third_party\ffmpeg\chromium\config\Chrome\win\ia32\config.h。 在这个config.h里面搜索关键词MPEG4,找到了#define... Read More | Share it now!

Chromium中的utility进程

有时候Chromium浏览器主进程需要做一些“危险”的事情,比如图片解码、文件解压缩。如果这些“危险”的操作发生了失败,会导致整个主进程发生异常崩溃,这是我们不愿意看到的。因此Chromium设计出了一个utility进程的机制。主进程临时需要做一些不方便的任务的情况下,可以启动一个utility进程来代替主进程执行,主进程与utility进程之间通过IPC消息来通信。我写一个收集电脑硬件信息的例子来介绍如何使用utility进程。 首先我们需要继承UtilityProcessHostClient创建一个自己的类HardwareProber。我们在HardwareProber里面处理与utility进程的IPC通信。在HardwareProbe里面我们会通过UtilityProcessHost::Create创建一个UtilityProcessHost,通过UtilityProcessHost的Send方法,我们可以向utility进程发送IPC消息。我们不用管理UtilityProcessHost的生命周期,所以持有UtilityProcessHost的WeakPtr即可。 UtilityProcessHost提供了一些接口: SetExposedDir。在沙箱里暴露一个utility进程可以操作的目录。 DisableSandbox。禁用utility进程的沙箱。 ElevatePrivileges。提权utility进程。 SetEnv。设置utility进程的环境变量。 通常需要执行某个任务,然后启动一个utility进程。当任务完成,utility进程就退出。有时候我们需要做很多同样的事情,如果频繁的启动和退出utility进程,效率比较低。因此UtilityProcessHost还有个Batch模式,就是在一个utility进程里处理多个相同的任务。一般utility进程退出都是utility进程自己主动调用content::UtilityThread::Get()->ReleaseProcessIfNeeded()。如果在Batch模式下,那边则由主进程来控制utility进程的退出,需要调用StartBatchMode和EndBatchMode。 UtilityProcessHostClient还具有RefCountedThreadSafe属性。它的生命周期是当utility进程退出后,IPC断开了BrowserChildProcessHostImpl来销毁。所以我们也不用管理UtilityProcessHostClient对象生命周期。 主进程里面HardwareProber的IPC消息首先会发到utility进程的ChromeContentUtilityClient里面来。我们可以在ChromeContentUtilityClient里面处理消息,也可以继承UtilityMessageHandler创建一个HardwareProberHandler类来处理IPC消息。 HardwareProberHandler... Read More | Share it now!

Chromium installer介绍

mini_installer是Chromium的安装和升级系统的一部分。它的有两个主要作用: 从自身资源中抽取出chrome.packed.7z和setup.exe程序 调用setup.exe进行实际的安装操作 mini_installer很小 mini_installer之所以叫mini,是因为它真的很小。除去资源的大小,它本身大约只有5kb,而我们创建一个空的控制台的程序也有100多kb。 mini_installer做的这么小的原因是:Chromium的差量升级包实际上也是一个mini_installer。为了使差量包尽可能的小,所以mini_installer本身也要非常小。 mini_installer只是一个壳,本身并没有很复杂的逻辑,它是调用setup.exe做真正的安装和升级逻辑。另外它还没有链接CRT库。没有链接CRT库带来了一个副作用就是不能调用CRT库里面的函数,只能调用Windows... Read More | Share it now!

调试Utility进程崩溃

M55内核升级中发现Utility进程崩溃很多。以前Utility进程的崩溃只占到总崩溃量1%不到,现在却增加到了10%以上。 Utility进程崩溃的堆栈都很类似,如下: 我们看0号线程,也就是主线程。这个调用栈没有wMain函数,却有exit函数。可以看出来程序崩溃在进程退出的时候。最后的我们的代码崩溃帧是在BrokerServicesBase::~BrokerServicesBase里面,其代码如下: 我认为0号线程是正常的。我们再看1号线程。1号线程是程序发生异常,生成dump的线程。可以看到是ntdll!TppWaiterpThread引发了ntdll!KiUserExceptionDispatcher。好奇怪,异常竟然来自系统模块。我们看下Windbg的dump分析: 可以看到ExceptionCode的值为c000070a,查一下ntstatus.h头文件,值c000070a表示STATUS_THREADPOOL_HANDLE_EXCEPTION,意思是线程池等待的句柄异常,可以看到句柄是0000012c。 然后用!handle命令行查看一下进程当前拥有的句柄情况: 可以看到进程句柄表里面并没有0000012c句柄。因此我猜测可能是因为0000012c句柄已经关闭了,而TppWaiterpThread还在使用0000012c这个无效的句柄导致的异常。 因此我想收集一下句柄分配和释放的信息,然后根据dump查看一下无效的句柄是怎么分配和释放的。幸好chromium里面有一个ActiveVerifier机制,ActiveVerifier是用来跟踪句柄使用情况的,它跟踪了一部分句柄创建的和Hook了CloseHandle函数。在StartTracking和CloseHandle里面加一个我自己写的LogHandle函数,这个函数会把句柄分配和释放的StackTrace跟进句柄值记录到环境变量中。 因为Utility进程创建和释放的句柄并不多,所以把这个调试信息放在环境变量中是方便和合适的。 灰度出去,收集崩溃信息。这次果然收集到了有用的信息,先看dump分析: 这次异常的句柄值是00000198,再看看环境变量中的调试信息: 00000198果然被CloseHandle了。然后根据后面的地址查看关闭的调用栈: 从代码里面可以看到00000198句柄已经随着ChildProcess的析构而关闭变成无效了,其他地方继续使用这个句柄是有问题的。 00000198句柄是ChildProcess的shutdown_event_成员变量。这个shutdown_event_会被ChildThreadImpl传递给其IPC::SyncChannel的channel_成员。而SyncChannel的shutdown_watcher_会调用系统的RegisterWaitForSingleObject去利用线程池去等待00000198句柄对应Event。 所以问题的根源是ChildProcess的析构函数CloseHandle了shutdown_event_,但是SyncChannel里面的shutdown_watcher_通过RegisterWaitForSingleObject继续等待shutdown_event_句柄。MSDN里RegisterWaitForSingleObjec介绍说:If... Read More | Share it now!

浏览器M55内核升级最后一个崩溃

最近做M55内核升级,我负责浏览器的稳定性。陆陆续续把各种崩溃都修复的差不多了,只剩下最后一个崩溃了,其崩溃堆栈如下: 在执行这行代码的时候发生了崩溃: 初步一看,这个崩溃点在Chromium原生的代码里面,所以怀疑是Chromium自己的bug。于是在https://bugs.chromium.org/p/chromium/issues/list里面搜索相关堆栈关键词,看看能不能找到类似的崩溃。很遗憾的是并没有搜到有用的信息。 然后再去https://groups.google.com/a/chromium.org/forum/#!forum/chromium-dev里面碰碰运气,仍然是一无所获。看来是指望不上Chromium社区的帮助了,只能自己动手,丰衣足食。 因为我们抓的崩溃dump只包含栈上的内存,另外浏览器Release版本做了编译器优化,导致很多栈上变量值都优化的不可见。因此崩溃dump里面包含的有用信息并不多,只能看到浏览器崩溃在哪行代码,却不能观察到浏览器崩溃时所处的状态,如Windbg里面查看到变量信息下图所示: 为此,通过Alias函数引用到栈上变量,防止它被优化,Alias函数如下图所示: 然后把自己关心的信息,都存到栈上分配的内存上,比如我想知道崩溃的时候请求的url、cookie等信息,如下图所示: 加上这些收集调试信息的代码,再灰度出去收集新的dump。通过分析新的dump果然有了一些有用的信息。可以看到浏览器崩溃的时候first_party_for_cookies对于的url都是天猫、淘宝的商品页面,而此时网络请求的url也都是https://gm.mmstat.com/tbdetail的url,如下图所示: 可以确定浏览器都是访问淘宝天猫出现的崩溃,但是我本地试了试,并不能复现问题。为了找到可以复现的崩溃场景,我在浏览器崩溃的时候,利用QQ的tencent://协议自动打开QQ跟我联系,代码如下: 继续灰度出去一版,等待崩溃的用户联系我。 我这边继续分析之前的崩溃dump,可以排除是空指针崩溃。发现cookie_store变量很有意思,其中一个dump如下: 可以看到cookie_store指向的net::CookieStore对象的虚函数表地址竟然是0xe43a00f2,已经跑到系统的地址空间里面去了,显然超出了合法的地址范围。而有的dump则是这样: 虽然虚函数表地址是正常的,但是其中虚函数指针都是错的,我猜可能是UAF(Use... Read More | Share it now!