频繁设置CGroup触发linux内核bug导致CGroup running task不调度
1. 说明1> 本篇是实际工作中linux上碰到的一个问题,一个使用了CGroup的进程处于R状态但不执行,也不退出,还不能kill,经过深入挖掘才发现是Cgroup的内核bug2>发现该bug后,去年给RedHat提交过漏洞,但可惜并未通过,不知道为什么,这里就发我博客公开了3> 前面的2个帖子《极简cfs公平调度算法》《极简组调度-CGroup如何限制cpu》是为了了解本篇这个内核bug而写的,需要linux内核进程调度和CGroup控制的基本原理才能够比较清晰的了解这个内核bug的来龙去脉4> 本文所用的内核调试工具是crash,大家可以到官网上去查看crash命令的使用,这里就不多介绍了https://crash-utility.github.io/help.html 2. 问题2.1 触发bug code(code较长,请展开代码)2.1.1 code#include #include #include #include #include #include #include #include #include #include #include using namespace std;std::string sub_cgroup_dir("/sys/fs/cgroup/cpu/test");// common libbool is_dir(const std::string& path){ struct stat statbuf; if (stat(path.c_str(), &statbuf) == 0 ) { if (0 != S_ISDIR(statbuf.st_mode)) { return true; } } return false;}bool write_file(const std::string& file_path, int num){ FILE* fp = fopen(file_path.c_str(), "w"); if (fp = NULL) { return false; } std::string write_data = to_string(num); fputs(write_data.c_str(), fp); fclose(fp); return true;}// mslong get_ms_timestamp(){ timeval tv; gettimeofday(&tv, NULL); return (tv.tv_sec * 1000 + tv.tv_usec / 1000);}// cgroupbool create_cgroup(){ if (is_dir(sub_cgroup_dir) == false) { if (mkdir(sub_cgroup_dir.c_str(), S_IRWXU | S_IRGRP) != 0) { cout timer_active为1,直接return,而不再将period_timer激活 3.3 搜索__start_cfs_bandwidth()的调用,发现时钟中断中会调用update_curr()函数,其最终会调用assign_cfs_rq_runtime()检查cgroup cpu配额使用情况,决定是否需要throttle,这里在cfs_b->timer_active = 0时,也会调用__start_cfs_bandwidth(),即执行上面B线程的代码,从而和设置CGroup的线程A发生线程竞争,导致timer失效。1> 完整代码执行流程图2> 当定时器失效后,由于3.2中线程B将cfs_b->timer_active = 1,所以即使下次时钟中断执行到assign_cfs_rq_runtime()中时,由于误判timer是active的,也不会调用__start_cfs_bandwidth()再次激活timer,这样被throttle的group se永远不会被unthrottle投入rq调度了 3.4 总结频繁设置CGroup配置,会和时钟中断中检查group quota的线程在__start_cfs_bandwidth()上发生线程竞争,导致period_timer被cancel后不再激活,然后CGroup控制的task不能分配cpu quota,导致不再被调度 3.5 恢复方法知道了漏洞成因,我们也看到tg_set_cfs_quota()会调用__start_cfs_bandwidth() cancel掉timer,然后重新激活timer,这样就能在timer回调中unthrottle了,所以只要手动设置下这个CGroup的cpu.cfs_period_us或cpu.cfs_quota_us,就能恢复运行。 4. 修复3.10.0-693以上的版本并不会出现这个问题,通过和2.6.32版本(下图右边)的代码对比,可知3.10.0-693版的代码(下图左边)将hrtimer_cancel()该为hrtimer_try_to_cancel(),并将其和cfs_b->timer_active的判定都放在自旋锁中保护,这样就不会cfs_b->timer_active被置1后,仍然还会去cancel period_timer的问题了,但看这个bug fix的邮件组讨论,是为了修另一个问题顺便把这个问题也修了,痛失给linux提patch的机会- -<img>ref : https://gfiber.googlesource.com/kernel/bruno/+/09dc4ab03936df5c5aa711d27c81283c6d09f495 5. 漏洞利用1> 在国内,仍有大量的公司在使用CentOS6和CentOS7.0~7.5,这些系统都存在这个漏洞,使用了CGroup限制cpu就有可能触发这个bug导致业务中断,且还不一定能重启恢复2> 一旦触发这个bug,由于task本身已经是running状态了,即使去kill,由于task得不到调度,是无法kill掉的,因此可以通过这种方法攻击任意软件程序(如杀毒软件),让其不能执行又不能重启(很多程序为了保证不双开,都会只保证只有一个进程存在),即使他们不用CGroup,也可以给他建一个对其进行攻击3> 该bug由于是linux内核bug,一旦触发还不易排查和感知,因为看进程状态都是running,直觉上认为进程仍然在正常执行的
来源:https://www.cnblogs.com/organic/p/17321523.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]