注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

linux中audit模块对系统调用监控实现的相关笔记  

2016-11-04 21:49:48|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、为什么会关注到这个问题
这个是之前遇到一个问题:通过tcpdump抓包可以看到系统中有进程向特定机器的UDP端口发送数据,只是发送的频率相对比较低,在不影响问题讨论的基础上,我们假设这个发送频率是10分钟每次。遗憾的是,tcpdump并没有办法显示发送方的进程id,而UDP连接通常又通常不能假设它是长期存在的,例如可能是一个crontab派生的进程周期性的发送数据,然后就退出(深藏功与名),这样想从系统中找到这个报文的发送进程看起来就比较麻烦。搜索了之后,有人提到了使用audit功能http://serverfault.com/questions/192893/how-i-can-identify-which-process-is-making-udp-traffic-on-linux。虽然作者认为的解决方案是使用audit,但是对于这个问题感觉使用audit并不是一个很好的方法,因为udp发送的时候,目的地址是一个结构指针,把参数打印出来并不能准确的知道sendto系统调用发送的目的地址(IP+PORT),当然这样可以将执行了这个系统调用的进程范围缩小一些。另外不出所料,当时使用的系统在编译的时候并没有使能AUDIT模块。所以audit并没有解决这个问题,但是这个模块作为一个可能的方案进入视野(之前都只是以源代码的形式在内核中瞥见过),前段时间粗略看了下这个模块的实现,现在这里简单记录下。
二、系统调用钩子的第一步判断
为了监控系统调用,首先就需要在系统调用的地方添加钩子,让系统调用感知到监控的存在,反过来说让监控知道系统调用的发生也是可以的。内核既然提供了这样的功能模块,它就需要和体系结构无关,但是对于常见的386系统来说,这个钩子的最早拦截发生在下面的汇编代码中:
linux-2.6.21\arch\i386\kernel\entry.S
ENTRY(system_call)
RING0_INT_FRAME # can't unwind into user space anyway
pushl %eax # save orig_eax
……
no_singlestep:
# system call tracing in operation / emulation
/* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */
testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry

syscall_exit_work:
testb $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT|_TIF_SINGLESTEP), %cl
jz work_pending
TRACE_IRQS_ON
ENABLE_INTERRUPTS(CLBR_ANY) # could let do_syscall_trace() call
# schedule() instead
movl %esp, %eax
movl $1, %edx
call do_syscall_trace
……
syscall_trace_entry:
movl $-ENOSYS,PT_EAX(%esp)
movl %esp, %eax
xorl %edx,%edx
call do_syscall_trace
cmpl $0, %eax
jne resume_userspace # ret != 0 -> running under PTRACE_SYSEMU,
# so must skip actual syscall
movl PT_ORIG_EAX(%esp), %eax
cmpl $(nr_syscalls), %eax
jnae syscall_call
jmp syscall_exit
END(syscall_trace_entry)

这里可以看到的是,在系统调用发生的时候,通过判断线程标志位中的(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),如果有任何一个标志位置位,则需要执行可能的系统调用跟踪代码,对于进入系统调用事件,先跳转到syscall_trace_entry然后执行do_syscall_trace,对于系统退出则直接调用do_syscall_trace函数。
三、do_syscall_trace
对于386系统而言,这个函数定义在文件linux-2.6.21\arch\i386\kernel\ptrace.c中:
__attribute__((regparm(3)))
int do_syscall_trace(struct pt_regs *regs, int entryexit)
{
……
if (unlikely(current->audit_context)) {
if (entryexit)
audit_syscall_exit(AUDITSC_RESULT(regs->eax),
regs->eax);
/* Debug traps, when using PTRACE_SINGLESTEP, must be sent only
* on the syscall exit path. Normally, when TIF_SYSCALL_AUDIT is
* not used, entry.S will call us only on syscall exit, not
* entry; so when TIF_SYSCALL_AUDIT is used we must avoid
* calling send_sigtrap() on syscall entry.
*
* Note that when PTRACE_SYSEMU_SINGLESTEP is used,
* is_singlestep is false, despite his name, so we will still do
* the correct thing.
*/
else if (is_singlestep)
goto out;
}
……
out:
if (unlikely(current->audit_context) && !entryexit)
audit_syscall_entry(AUDIT_ARCH_I386, regs->orig_eax,
   regs->ebx, regs->ecx, regs->edx, regs->esi);
if (ret == 0)
return 0;
……
}
这里对于是否进入审计统计函数audit_syscall_exit又加了一层current->audit_context非空的判断。
四、_TIF_SYSCALL_AUDIT标志位的设置
linux-2.6.21\kernel\auditsc.c
int audit_alloc(struct task_struct *tsk)
{
struct audit_context *context;
enum audit_state     state;

if (likely(!audit_enabled))
return 0; /* Return if not auditing. */

state = audit_filter_task(tsk);
if (likely(state == AUDIT_DISABLED))
return 0;

if (!(context = audit_alloc_context(state))) {
audit_log_lost("out of memory in audit_alloc");
return -ENOMEM;
}

/* Preserve login uid */
context->loginuid = -1;
if (current->audit_context)
context->loginuid = current->audit_context->loginuid;

tsk->audit_context  = context;
set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT);
return 0;
}
这个函数调用的地方比较唯一,就是在进程创建的时候被调用do_fork===>>>copy_process===>>>audit_alloc,在进程创建的时候设置线程中的TIF_SYSCALL_AUDIT标志位。
五、问题
在do_syscall_trace函数的时候可以看到,要进入audit_syscall_entry函数还要求current->audit_context非空,而这个值是否为空是由进程创建的当时上下文决定,从代码中看,之后使能audit的指令并不会保证此前没有分配该结构的进程再次分配这个结构。也即是说,从这个实现上来看在禁止掉系统审计之后,启动一个任务,然后再打开审计并开始监控之前启动的任务是不能成功的,这个结果比较令人扫兴,所以在虚拟机中实验了下:
首先在"终端1"禁掉系统的audit功能
tsecer@harry: auditctl -e 0
AUDIT_STATUS: enabled=0 flag=1 pid=1624 rate_limit=0 backlog_limit=320 lost=0 backlog=0
tsecer@harry: auditctl -l
No rules
tsecer@harry: 
然后在另一"终端2"中启动一个定时执行的watch命令,启动之后在"终端1"中开始审计该进程
tsecer@harry: ps aux | grep watch | grep /home/tsecer/
root      2697  0.0  0.0   4524  1096 pts/1    S+   13:48   0:00 watch ls /home/tsecer/
tsecer@harry: auditctl -a always,exit  -F pid=2697 -S write -k tsecer.audit
tsecer@harry: ausearch -k tsecer.audit
<no matches>
tsecer@harry: 
可以看到并没有输出,迄今为止和之前的推测类似。接下来再使能系统审计:
tsecer@harry: auditctl -e 1
AUDIT_STATUS: enabled=1 flag=1 pid=1624 rate_limit=0 backlog_limit=320 lost=0 backlog=0
tsecer@harry: ausearch -k tsecer.audit
----
time->Fri Nov  4 13:51:46 2016
type=SYSCALL msg=audit(1478292706.573:17): arch=40000003 syscall=4 success=yes exit=16 a0=1 a1=8696320 a2=10 a3=10 items=0 ppid=2656 pid=2697 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=1 comm="watch" exe="/usr/bin/watch" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="tsecer.audit"
----
time->Fri Nov  4 13:51:44 2016
type=SYSCALL msg=audit(1478292704.566:16): arch=40000003 syscall=4 success=yes exit=16 a0=1 a1=8696320 a2=10 a3=10 items=0 ppid=2656 pid=2697 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=1 comm="watch" exe="/usr/bin/watch" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="tsecer.audit"
tsecer@harry: 
可以看到,系统审计马上生效,并不是之前预料的行为。后来我注意到我当前看的内核代码和测试环境上的内核版本不同,所以看了下和测试环境版本相近的内核:
tsecer@harry: uname -a
Linux localhost.localdomain 2.6.32-504.el6.i686 #1 SMP Tue Sep 16 01:56:19 EDT 2014 i686 i686 i386 GNU/Linux
tsecer@harry: 
看新的2.6.32的内核代码:
int audit_alloc(struct task_struct *tsk)
{
……
if (likely(!audit_ever_enabled))
return 0; /* Return if not auditing. */
……
set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT);
return 0;
}
从这个名字就可以看出,只要系统中曾经是能过这个审计系统,那么进程创建的时候就一定会分配并设置这个字段,看起来这个代价还是很高的。而日志没有输出是由于在audit_syscall_entry函数中对audit_enabled作了判断 if (!audit_enabled) return;
六、其它说明
1、audit_enabled使能数值的意义
下面是man auditctl中对于使能数值的判断,其中比较有意思的是数值2,修改该值之后,相当于不留后路,也就是该值为2表示该值被锁定,之后用户态无法通过任何手段修改该值。
       -e [0..2]
              Set enabled flag. When 0 is passed, this can  be  used  to  tem-
              porarily  disable  auditing. When 1 is passed as an argument, it
              will enable auditing. To lock the audit configuration so that it
              can’t be changed, pass a 2 as the argument. Locking the configu-
              ration is intended to be the last  command  in  audit.rules  for
              anyone  wishing this feature to be active. Any attempt to change
              the configuration in this mode will be audited and  denied.  The
              configuration can only be changed by rebooting the machine.
2、do_syscall_trace函数
该函数是发部分系统调用跟踪的实现基础,从2.6.32版本可以看到,这里有TIF_SYSCALL_TRACEPOINT、TIF_SYSCALL_TRACE,对于TIF_SYSCALL_TRACEPOINT的作用我还没看,但是TIF_SYSCALL_TRACE是一个可以通过ptrace设置的字段,也是trace的实现基础。
linux-2.6.32.60\kernel\ptrace.c
static int ptrace_resume(struct task_struct *child, long request, long data)
{
if (!valid_signal(data))
return -EIO;

if (request == PTRACE_SYSCALL)
set_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
else
clear_tsk_thread_flag(child, TIF_SYSCALL_TRACE);
……
3、audit和strace的对比
在可以确定pid集合的情况下,audit没有优势,因为strace做的事情更加精细,以开始遇到的sendto调用为例,strace可以解析出sockaddr参数的内容而audit不能。而且从前面的实现来看,audit对于系统有较大的额外开销,而strace是临时功能,对系统压力更小。audit就好像在代码中自己加日志,而strace更类似于gdb调试的方式。但是,如果不知道pid,例如有定时程序或者CGI进程,那么audit的意义就体现出来,它是系统级功能,对系统中所有(在audit_enabled被只能过之后)进程均生效。
  评论这张
 
阅读(87)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017