最近笔者遇到这样一个相对比较疑难的事件,某个在 Linux 下运行的杀毒软件启动后,在某些情况下 CPU 占用率会持续升高,而且在交易量较高的情况下极易复现。而奇怪的是,我们之前已经对于杀毒软件的 CPU 使用率进行了上限限定。出现这样异常事件表明:杀毒软件并没有执行之前设定的资源占用控制策略,CPU 使用率始终持续异常偏高。由于此事件涉及一些敏感信息,具体不便公开的细节就不透露了,仅把可以公开的情况梳理一下,供各位读者参考。首先我们先明确一下钩子(hook)函数的概念,简单来讲这就是一类改变其它函数行为的函数。举个简单的例子,我每次进入会议室的时候都是直接推开门然后进入的,但是现在我在进入门之前要先向向会议室主持人申请,得到许可才能进入,那么向主持人申请的动作就被 attatch 到了进入会议室这个动作上了,整个过程就可以简单的理解为 hook。我们知道在 Linux 下想改变系统的行为,需要代码运行的内核态。比如 kprobe、fsnotify 等机制,提供了 root 用户 hook 到内核代码的权限,并最终将自己的代码段 attach 到内核调用中。
经确认这款杀毒软件的 CPU 占用率控制模型如下图,其守护模块会定时判断 agent 资源使用情况,如果超标则将释放扫描模块使用的 CPU 与内存资源。但是具体分析下来,这样的机制在 IO 频繁的系统上存在缺陷,具体原因扫描模块在内核态下执行时下无法释放 CPU 资源。经确认杀毒软件 agent 在行为监测时,在进程将文件加载到内存前,会使用 hook 技术对于 open 等系统调用进行 attach,确定加载的文件不含恶意代码后,才允许进程加载该文件。因此在 Linux 内核找到系统调用的 attatch 机制的相关代码进行分析。1. 系统调用中 sys_open 函数,使用 fsnotify 机制对于 attach 注入到 sys_open 函数的进程进行回调通知。(具体代码位置在 kernel/open.c)2. attach 到 sys_open 的代码执行过程始终是处于内核态中的,同时 Linux 的 fsnotify 机制也会加内核锁,在内核锁解锁前该进程无法释放 CPU,不能被打断。(具体代码位置在 kernel/fsnotify.c)杀毒软件扫描模块 attach 内核函数的机制与 fsnotify 类似,因此其扫描模块在进行行为检测时会在内核态执行且不能被打断,而在系统中原本就有大量 IO 操作的情况下,守护模块将失效。在 POC 测试时,该杀毒软件在文件扫描时其 CPU 占用率始终不高,这其中的原因是由于在扫描文件时该杀毒软件全部运行于用户态下,不存在内核态运行的情况,因此守护模块可正常调节 CPU 使用情况。
先说一下实测结论:在加入 attach 延时操作后,IO 吞吐量巨幅下降。经访照该杀毒软件的机制进行实测模拟,在内核 sys_open 函数 attach 加入延时操作,观察对于系统 IO 的影响。在加入将内核 sys_open 延时一倍的操作后,我在华为的在鲲鹏 4C/8G 的平台实测上,每秒钟文件打开、关闭文件操作的次数,由每秒 867次 的峰值下降到了 72 次,出现了 90%以上的下降。这可能与内核锁的雪崩效应有关。经确认,在之前的版本之所以没有出现问题,是由于当守护进程在确认 CPU 调节失效后会对自身 agent 进行整体自毁操作(modelu_exit),因此不会触发类似于 CPU 占用率持续升高的案例。- 对于内核态代码执行,加入全局并发数限制,对于所有执行在内核态的扫描线程,进行全局并发锁限制,具体并发数的设置还需要进行进一步测试后得出结果,在鲲鹏 4C/8G 的平台上测试最大并发数设置为 4 基本不会对系统正常调用产生影响,建议先将系统 CPU 个数设定为最大并发数,进行测试。
- 对于对于内核态代码执行加入每秒执行次数限制,对于所有执行在内核态的扫描线程,进行全局的执行次数限制,加入执行令牌,每秒执行次数不应该大于最大 IO 数量的 10%,在此方案下也可避免对于系统正常调用的影响。
- 加入扫描任务调度机制:避免在内核态执行耗时的扫描任务,只是快速收到系统的 open 调用指令后,将相关的扫描任务加入调试队列,就立刻返回,在用户态统一执行扫描任务,也可避免由于代码长时间运行于内核态造成的问题。
声明:本文为作者独立观点,不代表 CSDN 立场。
☞程序员的反击!每天一个离职小技巧☞人均 11878 元,2020 年研发岗年终奖最高!技术、产品岗均榜上有名☞被法拉第夸、狄更斯为她读诗、英王参加她的成人礼,程序员祖师的人生有多传奇?
☞程序员因拒绝带电脑回家被开除,获赔 19.4 万元