作者:wzt
原文鏈接:https://mp.weixin.qq.com/s/-U71_2jMvboCj-y7CHDBAA
1 背景
1.1 freebsd audit 簡介
Freebsd audit子系統是由TrustBSD項目從Apple的XNU內核移植過來的,在freebsd6.2系統中發布。XNU內核中的audit子系統最初是由McAfee公司給apple設計的,它遵循的是solaris發明的BSM框架。
1.2 freebsd 與 linux audit 對比
同樣作為audit審計功能,freebsd的設計理念跟linux的有所不同。
-
1) 對于審計的對象,
linux是每個系統調用,而freebsd定義的則是event事件,多個類似的event事件歸屬同一個class組。對于linux,在用戶空間定義規則時就要指定某個具體的系統調用,freebsd則是指定的class組。 -
2)
linux提供了更精確的rule規則列表,只針對在某種特定條件下才記錄日志,它有一個規則匹配引擎,而freebsd沒有提供這項功能,只是純粹的記錄日志。比如兩個系統都能監控socket系統調用,freebsd會把所有的socket調用都記錄,而linux可以做到只記錄第一個參數domain為AF_INET,第二個參數type為SOCK_STREAM,第三個參數為IPPROTO_TCP的某次socket調用。當然linux的規則引擎也不完備,不能處理指針和結構體。規則數目較多時,系統會感到明顯的卡頓,在敏感的系統調用路徑里,規則審計應該做到越快越好,從這一點上來說,freebsd的做法似乎更純粹一些。 -
3) 對于與用戶層的通訊接口,
linux使用的是netlink socket,而freebsd則是增加了若干系統調用以及/dev/audit和/dev/audit_pipe來做通訊。 -
4) 對于審計的入口,
freebsd只在系統調用入口處處理,而linux還可以從進程fork以及文件系統等路徑進行處理。 -
5)
Freebsd沒有對全部的系統調用進行審計,而linux則是全部都要審計。 -
6) 對于系統調用參數的記錄是比較困難的, 因為不同的系統調用參數個數不同,每個參數的類型也不同,類型還可能包括指針和數據結構嵌套, 目前業界沒有一個較好的算法能捕獲這些參數。所以
freebsd的做法是在內核大部分模塊中都加入了hook,才可以保證系統調用參數的獲取,而linux對這種支持很少。 -
7)
Freebsd對MAC強制訪問控制系統是不做審計的, 而linux對MAC甚至是secomp都做了審計操作。 -
8)
Freebsd的日志格式采用的是工業界的標準BSM(basic security model),linux采用的是自定義的格式。
2 實現
2.1 與用戶層通訊接口
Freebsd 增加了以下幾個系統調用,用于從用戶層與內核層的通訊,這些系統調用包括audit功能開啟,參數配置等等。
sys_audit// 向內核傳遞用戶層自定義的日志內容
sys_auditon// 用于參數和規則控制
sys_getauid// 獲取audit session id
sys_setauid// 設置audit session id
sys_getaudit// 獲取audit狀態信息
sys_setaudit// 設置audit狀態信息
sys_getaudit_addr// 獲取audit狀態信息, 包含一些額外信息
sys_setaudit_addr// 設置audit狀態信息, 包含一些額外信息
sys_auditctl// 建立一個新的audit日志文件
這幾個系統調用的實現邏輯都比較簡單,筆者不在本文進行講解,讀者朋友可以自己嘗試閱讀下源碼。
2.2 審計實現
我們同樣以x86體系為例,看下freebsd audit子系統的入口是如何進入的。
amd64/amd64/exception.S:
IDTVEC(fast_syscall)
call amd64_syscall
amd64/amd64/trap.c:
amd64_syscall()->syscallenter
syscallenter(struct thread *td)
{
AUDIT_SYSCALL_ENTER(sa->code, td);[1]
error = (sa->callp->sy_call)(td, sa->args);[2]
AUDIT_SYSCALL_EXIT(error, td);[3]
}
在執行具體的系統調用[2]之前,需要在[1] 處執行審計的預處理:
security/audit/audit.c:
void
audit_syscall_enter(unsigned short code, struct thread *td)
{
event = td->td_proc->p_sysent->sv_table[code].sy_auevent;[4]
auid = td->td_ucred->cr_audit.ai_auid;[5]
if (auid == AU_DEFAUDITID)
aumask = &audit_nae_mask;
else
aumask = &td->td_ucred->cr_audit.ai_mask;
class = au_event_class(event);[6]
if (au_preselect(event, class, aumask, AU_PRS_BOTH)) {[7]
record_needed = 1;
} else if (audit_pipe_preselect(auid, event, class, AU_PRS_BOTH, 0)) {[8]
record_needed = 1;
} else {
record_needed = 0;
}
if (record_needed) {
td->td_ar = audit_new(event, td);[9]
}
freebsd在每個進程結構體里都保存一個系統調用數組指針struct sysentvec,它包含一個成員struct sysent:
struct sysent { /* system call table */
int sy_narg; /* number of arguments */
sy_call_t *sy_call; /* implementing function */
au_event_t sy_auevent; /* audit event associated with syscall */
systrace_args_func_t sy_systrace_args_func;
/* optional argument conversion function. */
u_int32_t sy_entry; /* DTrace entry ID for systrace. */
u_int32_t sy_return; /* DTrace return ID for systrace. */
u_int32_t sy_flags; /* General flags for system calls. */
u_int32_t sy_thrcnt;
};
Sy_call 保存的是具體的系統調用函數指針。
前面講過freebsd audit是基于event事件來驅動的,sy_auevent保存的就是event事件號。每個系統調用只有一個或沒有event事件。如果沒有event事件,那么在audit審計的時候就會被忽略。這一點與linux不同, linux是所有的系統調用都要被審計。我們可以看下freebsd的init進程的struct sysent的初始化表:
kern/init_sysent.c:
struct sysent sysent[] = {
{ 0, (sy_call_t *)nosys, AUE_NULL, NULL, 0, 0, 0, SY_THR_STATIC },
{ AS(sys_exit_args), (sy_call_t *)sys_sys_exit, AUE_EXIT, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC },
{ 0, (sy_call_t *)sys_fork, AUE_FORK, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC },
{ AS(break_args), (sy_call_t *)sys_break, AUE_NULL, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC },
{ compat(AS(ogetkerninfo_args),getkerninfo), AUE_NULL, NULL, 0, 0, 0, SY_THR_STATIC }, /* 63 = old getkerninfo */
{ compat(0,getpagesize), AUE_NULL, NULL, 0, 0, SYF_CAPENABLED, SY_THR_STATIC }, /* 64 = old getpagesize */
}
這里還是有很多空event事件的,那么這些系統調用都不會被audit審計到。Freebsd內核開發者應該是認為某些系統調用沒有危險性,所以暫時不需要被審計到。
在[5]處獲取當前的會話session id,來判斷是否使用內核的class mask還是進程的class mask。[6]處開始將event事件號,轉化為對應的class組,前面提到freebsd將類似的event事件歸并入一個class組。Event事件和class組是通過哈希表來管理的, audit子系統在初始化的時候把上述init進程的sysent數組中event號進行提取,然后歸檔到哈希表中。后續應用進程也可以通過auditon來進行動態添加。[7]處的au_preselect對class mark進行匹配,來判斷是否需要進行本地審計。[9]處是否需要使用/dev/audit_pipe來與用戶層進行實時交互。[9]處如果需要記錄就通過audit_new動態分配一個struct kaudit_record數據結構。Linux的audit數據結構是在進程fork時就提前生成,筆者認為這樣做的效率會高些。
當[2]處具體的系統調用執行完畢后, 在[3]處開始做記錄日志操作。
void
audit_syscall_exit(int error, struct thread *td)
{
audit_commit(td->td_ar, error, retval);
}
void
audit_commit(struct kaudit_record *ar, int error, int retval)
{
while (audit_q_len >= audit_qctrl.aq_hiwater)
cv_wait(&audit_watermark_cv, &audit_mtx);
TAILQ_INSERT_TAIL(&audit_q, ar, k_q);
audit_q_len++;
audit_pre_q_len--;
cv_signal(&audit_worker_cv);
}
與linux不同, feebsd的系統調用日志記錄操作邏輯很清晰簡單,因為沒有linux的規則匹配引擎。Linux在進入系統調用之前只有一些簡單的初始化操作,真正的判斷是在系統調用返回時通過規則引擎來識別的,這是它們的不同之處。
Freebsd是在進入系統調用之前就已經預判此次系統調用是否需要被審計,后續的audit_commit只管往日志隊列里寫數據,當隊列長度超過高水位線時就進行休眠,否則將一個節點插入到隊列里,并喚醒等待的audit worker進程。
Audit worker進程是在audit子系統初始化被建立的:
static void
audit_worker(void *arg)
{
struct kaudit_queue ar_worklist;
struct kaudit_record *ar;
int lowater_signal;
TAILQ_INIT(&ar_worklist);[1]
while (1) {
mtx_assert(&audit_mtx, MA_OWNED);
while (TAILQ_EMPTY(&audit_q))[2]
cv_wait(&audit_worker_cv, &audit_mtx);
lowater_signal = 0;
while ((ar = TAILQ_FIRST(&audit_q))) {[3]
TAILQ_REMOVE(&audit_q, ar, k_q);
audit_q_len--;
if (audit_q_len == audit_qctrl.aq_lowater)
lowater_signal++;
TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q);
}
if (lowater_signal)[4]
cv_broadcast(&audit_watermark_cv);
mtx_unlock(&audit_mtx);
while ((ar = TAILQ_FIRST(&ar_worklist))) {[5]
TAILQ_REMOVE(&ar_worklist, ar, k_q);
audit_worker_process_record(ar);[6]
audit_free(ar);
}
mtx_lock(&audit_mtx);
}
}
[1]處初始化一個臨時的日志隊列,[2]處判斷audit日志隊列是否為空,為空時就進入休眠狀態,當再次被喚醒后,如果audit日志隊列不為空,就將節點一個個取下來插入到臨時隊列里,同時判斷audit日志隊列長度在低水位線時,就要在[4]處通知audit_commit進行日志的補充。Linux的這部分操作沒有使用臨時隊列,而是在持有鎖的情況下進行隊列節點的處理,而freebsd則是將節點插入臨時隊列后,馬上釋放鎖,這樣做做效率會更高些。
[6]處的audit_worker_process_record首先將日志轉化為BSM格式后,通過調用audit_record_write將日志寫入到磁盤文件里,然后調用audit_send_trigger,將日志信息同步到一個隊列里, 這個隊列是由/dev/audit進行操作,這樣用戶態程序可以通過讀取/dev/audit獲取到本次系統調用的日志內容。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1612/
暫無評論