5

疑难杂症:Linux下杀毒软件CPU占用率为何持续升高

 3 years ago
source link: https://blog.csdn.net/BEYONDMA/article/details/113031660
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

疑难杂症:Linux下杀毒软件CPU占用率为何持续升高

beyondma 2021-01-23 10:42:12 2844

最近笔者遇到这样一个相对比较疑难的事件,某个在Linux下运行的杀毒软件启动后在,某些情况下CPU占用率会持续升高,而且在交易量较高的情况下极易复现。而奇怪的是我们之前已经对于杀毒软件的CPU使用率进行了上限限定,但是出现这样异常事件表明杀毒软件并没有执行之前设定的资源占用控制策略,CPU使用率始终持续异常偏高。

分析下来这个事件还是很有借鉴意义的,由于此事件涉及一些敏感信息,因此具体不便公开的细节也就不透露了,仅把可以公开的情况梳理一下,供各位读者参考。首先我们先明确一下钩子(hook)函数的概念,简单来讲这就是一类改变其它函数行为的函数,举个简单的例子,我每次进入会议室的时候都是直接推开门然后进入的,但是现在我在进入门之前要先向向会议室主持人申请,得到许可才能进入,那么向主持人申请的动作就被attatch到了进入会议室这个动作上了,整个过程就可以简单的理解为hook。

我们知道在Linux下想改变系统的行为,需要代码运行的内核态,比如kprobe,fsnotify等机制,提供了root用户hook到内核代码的权限,并最终将自己的代码段attach到内核调用中。

CPU使用率过高的原因分析

经确认这款杀毒软件的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占用率持续升高的案例。那么针对这样的机制具体的解决方案如下:

  1. 对于内核态代码执行,加入全局并发数限制,对于所有执行在内核态的扫描线程,进行全局并发锁限制,具体并发数的设置还需要进行进一步测试后得出结果,在鲲鹏4C/8G的平台上测试最大并发数设置为4基本不会对系统正常调用产生影响,建议先将系统CPU个数设定为最大并发数,进行测试。
  2. 对于对于内核态代码执行加入每秒执行次数限制,对于所有执行在内核态的扫描线程,进行全局的执行次数限制,加入执行令牌,每秒执行次数不应该大于最大IO数量的10%,在此方案下也可避免对于系统正常调用的影响。
  3. 加入扫描任务调度机制:避免在内核态执行耗时的扫描任务,只是快速收到系统的open调用指令后,将相关的扫描任务加入调试队列,就立刻返回,在用户态统一执行扫描任务,也可避免由于代码长时间运行于内核态造成的问题。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK