gen2linux的blog

不要問我gentoo linux的東西, 玩一玩就忘記了....

星期日, 8月 20, 2006

 

Audit Subsystem Trace心得

written by gen2linux 2006/8/20
自由轉載但請註明出處

What is Audit Subsystem?
Audit subsystem是Linux kernel 2.6新增的功能,此子系統能記錄下將各行程的系統呼叫參數,檔案的操作等,以供事後稽核,SELinux subsystem也依賴Audit subsystem提供AVC。下圖為執行Audit subsystem所記錄下的數個系統呼叫。



What is Not Audit Subsystem?
Audit Subsystem並不是一個像Netfilter的hook機制,我們並不能利用這個Audit subsystem來對各系統呼叫做一些「hook」的動作,我們僅能觀察Audit Subsystem所產生的記錄。

Audit Subsystem Trace
我trace的版本是 2.6.17.8,不同的版本可能會有很大的差別。底下的描述也以系統呼叫稽核(syscall audit)為主,不討論檔案操作稽核,也不討論audit tool/audit lib。

在kernel 2.6.17,Audit subsystem的實作主要有三個檔案,分別是在kernel目錄下的audit.c、auditsc.c、auditfilter.c;其中audit.c是Audit subsystem主要架構,包括了audit logging、utility、kernel thread,和Netlink相關的函式;auditsc.c為對syscall驗證的相關實作;auditfilter.c則是對audit filter rule的解析與維護。

就算是將Audit subsystem編入核心中,也並不是每個行程都會受到Audit subsystem的驗證,因為還要受到audit filter的篩選,使用者可以自定filter rule來決定條件,只有符合條件的行程才會被Audit Subsystem驗證記錄。

在 fork時期Audit subsystem會替task(child process)準備好被驗證所需的相關資料程序—fork()替task填入 Audit subsystem的bookkeeping structure—struct audit_context,此結構記錄了該task的audit狀態,系統呼叫的參數及回傳值,該task的pid/uid等資訊。

in kernel/fork.c
static task_t *copy_process(){ /* copy_process()被 do_fork()呼叫*/

if ((retval = audit_alloc(p)))…
}



在建立struct audit_context前先經過audit filter的rule檢查,若成立才建立,否則立刻return表示當前的task不受Audit Subsystem的驗證。

in kernel/audit.c
int audit_alloc(struct task_struct *tsk){
state = audit_filter_task(tsk); /* 跟據 filter決定是否要audit此task*/
if (!(context = audit_alloc_context(state))) {…

/* 將此 task標示為TIF_SYSCALL_AUDIT,表示此task往後的系統呼叫都受到Audit subsystem的驗證*/
set_tsk_thread_flag(tsk, TIF_SYSCALL_AUDIT);
}


現在我們假設有一A process已經利用了auditctl tool設定為要audit的行程,並且A process呼叫了某一個syscall:


in arch/i386/kernel/entry.S:

ENTRY(system_call) //系統呼叫handler進入點
pushl %eax # save orig_eax
SAVE_ALL
GET_THREAD_INFO(%ebp)
testl $TF_MASK,EFLAGS(%esp)
jz no_singlestep
orl $_TIF_SINGLESTEP,TI_flags(%ebp)

no_singlestep:

//看看此task是否有標記成_TIF_SYSCALL_AUDIT(from thread info. struct)
testw $( ………_TIF_SYSCALL_AUDIT),TI_flags(%ebp)
jnz syscall_trace_entry //有,跳到syscall_track_entry
cmpl $(nr_syscalls), %eax
jae syscall_badsys

syscall_call:
call *sys_call_table(,%eax,4) //呼叫 syscall
movl %eax,EAX(%esp) # store the return value

syscall_exit:
cli # make sure we don't miss an interrupt…(skip)
movl TI_flags(%ebp), %ecx
testw $_TIF_ALLWORK_MASK, %cx # current->work
jne syscall_exit_work //有,跳到syscall_exit_work


syscall_exit_work:
testb $(…_TIF_SYSCALL_AUDIT), %cl
jz work_pending
sti # could let do_syscall_trace() call
# schedule() instead
movl %esp, %eax //eax是do_syscall_trace()第一個參數
movl $1, %edx //edx是do_syscall_trace()第二個參數
call do_syscall_trace //do_syscall_trace()有gcc的regparm(3)修飾字,故
//是以register傳遞參數
jmp resume_userspace



syscall_trace_entry:
movl $-ENOSYS,EAX(%esp)
movl %esp, %eax
xorl %edx,%edx
call do_syscall_trace // 呼叫do_syscall_trace()
cmpl $0, %eax
jne resume_userspace


do_syscall_trace()的第一個參數 pt_regs含有行程呼叫syscall時的參數,entryexit則表示是將進入syscall或是將退出syscall。


in 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);
}

if (unlikely(current->audit_context) && !entryexit)
audit_syscall_entry(AUDIT_ARCH_I386, regs->orig_eax, regs->ebx, regs->ecx, regs->edx, regs->esi);

}



上述的entry.S與do_syscall_trace(),最主要只是要說明Audit subsystem在呼叫真正的sys_xxx之前,以及呼叫完sys_xxx之後作用,概念上如下圖。



在呼叫sys_xxx之前Audit subsystem的audit_syscall_entry()先呼叫audit filter函式以決定此次的syscall是否該被驗證,若是則替該task的struct audit_context結構完成初始化,以便記錄此次的syscall。之後流程返回真正執行syscall routine,syscall routine會使用來自user space的系統呼叫參數,當成是input或output,且結束前會將retval設定給eax。

syscall routine預結束返回resume_userspaces之前,Audit Subsystem的audit_syscall_exit()會將struct audit_context中的內容以文字方式輸出到audit log中。

所以audit_syscall_entry()跟audit_syscall_exit()是兩個所謂的syscall的hook點,使得Audit subsystem能夠有機會觀察syscall routine的各參數和retval。

audit_syscall_exit()是Audit subsystem將syscall資訊寫入log檔的時機,所以可知觀察audit log紀錄僅能得到「呼叫syscall後的參數」,但是無法看到「呼叫syscall前的參數」。大部份的情況下觀察「呼叫syscall後的參數」是比較有意義的,但是某些時候觀察「呼叫syscall前的參數」是比較有用的,例如execve()。

整個的寫入audit log動作,由audit_syscall_exit()呼叫audit_log_exit()開始,如下圖所示。



整個系統中會有一個audit_skb_queue佇列存放要寫入log檔的資料,所有的process所要輸出的audit log都存放在此處。這個queue的consumer是一個kernel thread—kauditd,負責將此queue中的skb利用netlink函式送到u-space。如果queue中沒有skb的話kauditd就schedule()進入睡眠,如果kauditd被喚醒的話表示有某個audit_log_end()將skb丟進queue中了,kauditd便可以趕快將skb處理掉,並且嘗試wake up所有因queue滿而進入睡眠的行程,因為queue可能即將有空位。這樣的整體架構好處是可以射後不理,將log資料交給另一個執行體(a kernel thread)去做真正的傳輸到u-space的工作。

audit_log_start()會先觀察queue是不是滿了,如果滿了則會將process睡一段時間(schedule_timeout())後自行醒來再次檢查queue是否滿了,直到queue有空位後,audit_log_start()會new出一個ab(audit_buffer)來代表將要寫入log的資料描述子,且每個ab中含有一個skb存放資料,用skb來存放的原因是便可以將其丟入queue中。

audit_log_start()會回傳 ab描述子,往後的audit log相關函式都需持著ab進行log動作,如輸出格式化字串audit_log_format()、輸出十六進位字串audit_log_hex()、輸出目前行程資料audit_log_task(),最後呼叫audit_log_end()。 audit_log_end()會將此ab描述子中的skb丟入queue中,並且叫醒kauditd該處理queue了。

About Netlink
傳統上處在user space的應用程式要跟kernel space雙向傳遞非常態性的資料時有數種管道:/proc、sysctl(2)、sysfs、ioctl(2)以及Netlink,Audit subsystem選擇以Netlink的原因是因為Netlink適合大量的傳輸,由其是Log的產生常常相當龐大。相較於其他的方式,Netlink介面的使用也較為直覺:Application利用傳統的BSD Socket介面接收傳送來自kernel space的資料,而核心則利用Netlink數組函式來接收和傳遞來自user space的資料,稍有不同的是Netlink的函式傳輸是以”skb”為單位,而非BSD Socket中的”資料流”概念。有關Netlink可以參考RFC3549。

Comments: 張貼留言



<< Home

Archives

八月 2006   九月 2006   十月 2006   十一月 2006   三月 2007  

This page is powered by Blogger. Isn't yours?