Selinux导致系统文件读取变慢

前言

最近公司的linux服务器开始准备启用selinux,有一个team在产线上独自启用了selinux,而且在QA, ATS, BTS都没有进行特别大规模的压测,导致产线上出现了大面积的crash,也是一次比较严重的事故,因为dump的位置在我们team维护的一个基础lib里,所以前几周一直在和这个team联调,中间怀疑过很多原因也做过多次的修改,最终定位到了问题的真正所在,而问题确实隐藏得挺深。

背景

这个出现crash的lib A是和机器上另一个c++的程序B一起工作的,在机器上运行的程序C调用A中的接口在linux上创建shared memory,而B也会去创建shared memory,且编译出A的代码在B中也存在,也就是说A的代码在B中也是有的。

C中会有多线程调用A中的接口,而在A中有加锁和解锁的方法,在每一个线程做操作前必须调用加锁的操作(lock),且操作完成后必须解锁(unlock),这个加锁解锁的操作在lib中是通过信号量实现的。

问题

变量及关键部分已改名。

加锁方法(Lock,在程序中使信号量-1)部分内容:

lpid = *(pid_t *)((unsigned char *)sharedMemoryAddr);//1

if (lpid != 0)
{
    if (checkproc(lpid) != 0)//2
    {
        Unlock();
    }
}

while (((ret = semop(m_sem_id, m_waitop, 1)) == -1) && (errno == EINTR));//3

在系统创建信号量时创建了一个为1的信号量,所以每次PV操作对应的值都是1或者-1。sharedMemoryAddr存的是当前获得锁的线程的进程id。

可以看到在3处,如果没有能够成功获得信号量,那么就会一直在这里做循环操作,也就是自旋的操作。但是在2处会去/proc目录下检查/proc/lpid/stat文件是否存在来判断进程是否存在(checkproc),如果存在返回0,不存在返回一个非零值。在unlock方法中,持锁进程会做两件事,1. 把sharedMemoryAddr值置为0,2. 把信号量+1。问题就出在加锁时出现了问题。

在B内部有一个线程会起一个非常短的子进程d进行发送数据到另一个模块的操作,这个进程很快就会结束掉(1ms以内),进程不会留下任何记录,我们也没有注意到。在多线程操作的情况下,A中子线程a1在1处查询到当前进程是d,然后到2处去/proc目录下做文件打开的操作,在没有enable selinux的情况下很快就结束了,所以出现问题的几率很小,考虑这种情况,d进程在运行获得锁时,a1线程在1处获得d进程的id,等到2处a1线程去检查d进程,在2处,如果没有enable selinux,那么2处很快就执行完了,在/proc目录下/proc/lpid/stat文件也确实存在,而在enable selinux的情况下,2处open /proc/lpid/stat文件速度会变慢,这时d进程结束了,进行了一次unlock的操作,而a1线程还没有完成open /proc/lpid/stat文件的操作,这时就会发现,a1线程就会多进行一次unlock的操作,这样原本为1的信号量变为了2,同时有两个进程可以去获得信号量,这里的互斥锁的功能也就丧失了。

而为什么会有a1线程帮助d进程做unlock操作的代码是因为如果d进程异常crash,那么a1会帮助d把信号量恢复。

总而言之,这里的问题有两点,第一是原来的代码设计就有问题,因为3之前的代码是没有加锁的,所以多个线程可以访问,而如果一个进程恰好在另一个进程检查/proc目录的过程中结束的情况,信号量就会异常,但是之前没有暴露是因为访问/proc目录下文件足够快,所以未出现,而第二个问题就是在enable了selinux情况下访问一些系统目录文件会变慢,这样会导致出现这个问题的可能性增加。我们后面在2前后打时间戳发现确实在enable selinux后这个函数的执行速度大大变慢了。

解决办法

lpid = *(pid_t *)((unsigned char *)sharedMemoryAddr);//1

	if (lpid != 0)
	{
		if (checkproc(lpid) != 0)//2
		{
			pid_t pidTemp = *(pid_t *)((unsigned char *)sharedMemoryAddr);//4
			if (pidTemp == lpid){//5
				MemUnlock();
			}
		}
	}

	while (((ret = semop(m_sem_id, m_waitop, 1)) == -1) && (errno == EINTR));//3

在2处检查完后如果发现进程不在了,然后再去拿一次当前持有锁的进程的id,如果进程真的crash了,那么持有锁的还是crash的进程,再去做unlock,如果发现持有锁的进程变了,那么就标识这个进程没有crash,而是刚好在查/proc目录时结束了。当然,实际上这个修改避免不了多进程多线程情况下,不同进程的不同线程同时走到4,5然后做unlock的情况,但是这种情况可能性会非常的小。

思考

selinux会使访问系统文件的速度变慢,应该是为了安全加了一些权限的控制检查之类的,需要注意。

posted @ 2019-01-03 21:15  SmallMushroom  阅读(492)  评论(0编辑  收藏  举报