学习LSM(Linux security module)之一:解读yama
最近打算寫(xiě)一個(gè)基于LSM的安全模塊,發(fā)現(xiàn)國(guó)內(nèi)現(xiàn)有的資料極少。因此打算自己琢磨一下。大致的學(xué)習(xí)路線如下:
由易至難使用并閱讀兩到三個(gè)安全模塊->參照閱讀模塊自己實(shí)現(xiàn)一個(gè)安全模塊->在自己實(shí)現(xiàn)的同時(shí)閱讀LSM實(shí)現(xiàn)的基本源碼,由于Yama代碼量小,結(jié)構(gòu)十分清晰,可以作為入門(mén)的demo進(jìn)行參照。
由于網(wǎng)上關(guān)于LSM的相關(guān)介紹已經(jīng)爛大街了,就按自己的初步理解簡(jiǎn)單介紹一下LSM,詳情可以自己閱讀文后的相關(guān)鏈接,本文源碼基于Linux4.8.0。
一:什么是LSM
一種輕量級(jí)的安全訪問(wèn)控制框架,主要利用Hook函數(shù)對(duì)權(quán)限進(jìn)行訪問(wèn)控制,并在部分對(duì)象中內(nèi)置了透明的安全屬性。
二:Yama的簡(jiǎn)單介紹和基本使用
Yama主要是對(duì)Ptrace函數(shù)調(diào)用進(jìn)行訪問(wèn)控制。
Ptrace是一個(gè)系統(tǒng)調(diào)用,它提供了一種方法來(lái)讓‘父’進(jìn)程可以觀察和控制其它進(jìn)程的執(zhí)行,檢查和改變其核心映像以及寄存器。 主要用來(lái)實(shí)現(xiàn)斷點(diǎn)調(diào)試和系統(tǒng)調(diào)用跟蹤。利用ptrace函數(shù),不僅可以劫持另一個(gè)進(jìn)程的調(diào)用,修改系統(tǒng)函數(shù)調(diào)用和改變返回值,而且可以向另一個(gè)函數(shù)注入代碼,修改eip,進(jìn)入自己的邏輯。這個(gè)函數(shù)廣泛用于調(diào)試和信號(hào)跟蹤工具。所以說(shuō),對(duì)ptrace函數(shù)進(jìn)行訪問(wèn)控制還是很有必要的。
Yama一共分為四個(gè)等級(jí):
#define YAMA_SCOPE_DISABLED 0 #define YAMA_SCOPE_RELATIONAL 1 #define YAMA_SCOPE_CAPABILITY 2 #define YAMA_SCOPE_NO_ATTACH 3
其中YAMA_SCOPE_DISABLED代表yama并不起任何作用,YAMA_SCOPE_RELATIONAL代表只能ptarce子進(jìn)程才能進(jìn)行調(diào)試,YAMA_SCOPE_CAPABILITY,擁有CAP_SYS_PTRACE能力的進(jìn)程才可以使用ptrace。而YAMA_SCOPE_NO_ATTACH代表沒(méi)有任何進(jìn)程可以attach,而且只要設(shè)置成了3就無(wú)法降級(jí)了。
現(xiàn)在,先來(lái)測(cè)試使用一下,先將等級(jí)設(shè)為0。在root權(quán)限下進(jìn)行:
此時(shí),任何ptrace都能夠直接運(yùn)行。
被ptrace的demo程序如下:
//test.c
#include<stdio.h> int main() { while(1) { sleep(20); static int i = 0; } return 0; }
得到結(jié)果如下:
將等級(jí)設(shè)為一:
等級(jí)設(shè)為二:
可以通過(guò)setcap CAP_SYS_PTRACE=ep /usr/bin/strace給strace設(shè)置CAP_SYS_PTRACE權(quán)限,
等級(jí)設(shè)為三:
三:源碼解析
從開(kāi)頭看起
void __init yama_add_hooks(void)
{
pr_info("Yama: becoming mindful.
"); //打印相關(guān)信息,可以通過(guò)dmesg | grep Yama:查看
security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks)); //添加安全模塊函數(shù)
yama_init_sysctl(); //在中sysctl進(jìn)行注冊(cè)
}
先簡(jiǎn)單解釋一下yama_init_sysctl()函數(shù),這個(gè)函數(shù)的作用是在sysctl中進(jìn)行注冊(cè),使其能通過(guò)/proc/sys/kernel/yama/ptrace_scope進(jìn)行設(shè)置參數(shù),看具體源碼:
static void __init yama_init_sysctl(void)
{
if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table))
panic("Yama: sysctl registration failed.
");
}
其中,yama_sysctl_path用于注明在/proc/sys目錄下的具體位置,yama的定義如下:
struct ctl_path yama_sysctl_path[] = {
{ .procname = "kernel", },
{ .procname = "yama", },
{ }
};
即在/proc/sys/kernel/yama目錄下。
yama_sysctl_table表示參數(shù)的相關(guān)信息,源碼如下:
static int zero;//自動(dòng)初始化為0 static int max_scope = YAMA_SCOPE_NO_ATTACH;
static struct ctl_table yama_sysctl_table[] = { { .procname = "ptrace_scope", //文件名 .data = &ptrace_scope, //實(shí)際參數(shù)在內(nèi)核中的數(shù)據(jù)結(jié)構(gòu) .maxlen = sizeof(int), //對(duì)超過(guò)該最大長(zhǎng)度的字符串截掉后面超長(zhǎng)的部分. .mode = 0644, //條目在proc文件系統(tǒng)下的權(quán)限 .proc_handler = yama_dointvec_minmax, //如上,對(duì).proc_handler進(jìn)行hook .extra1 = &zero, //proc_handler的參數(shù),即范圍為0~3 .extra2 = &max_scope, }, { } };
proc_handler代表讀寫(xiě)操作函數(shù),對(duì)/proc/sys/kernel/yama/ptrace_scope進(jìn)行讀寫(xiě)時(shí)調(diào)用的函數(shù)。其中
proc_dointvec 讀寫(xiě)一個(gè)包含一個(gè)或多個(gè)整數(shù)的數(shù)組
proc_dostring 讀寫(xiě)一個(gè)字符串
proc_dointvec_minmax 寫(xiě)的數(shù)組必須在min~max范圍內(nèi)。
在這個(gè)數(shù)據(jù)結(jié)構(gòu)中,自己構(gòu)造了一個(gè)函數(shù),來(lái)在操作之前進(jìn)行了一些操作,如下
static int yama_dointvec_minmax(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
struct ctl_table table_copy;
/*
capable來(lái)對(duì)權(quán)限做出檢查,檢查是否有權(quán)對(duì)指定的資源進(jìn)行操作,該函數(shù)返回0則代表無(wú)權(quán)操作
這里對(duì)ptrace_scope進(jìn)行讀寫(xiě)需要write置1而且需要用戶(hù)有CAP_SYS_PTRACE權(quán)限
*/
if (write && !capable(CAP_SYS_PTRACE))
return -EPERM;
//當(dāng)設(shè)置為最大值時(shí),不再允許修改該參數(shù)
table_copy = *table;
if (*(int *)table_copy.data == *(int *)table_copy.extra2)
table_copy.extra1 = table_copy.extra2;
return proc_dointvec_minmax(&table_copy, write, buffer, lenp, ppos);
}
核心函數(shù)是security_add_hooks函數(shù),這個(gè)函數(shù)負(fù)責(zé)對(duì)ptrace進(jìn)行訪問(wèn)控制,如下:
先來(lái)看一下yama_hooks:
static struct security_hook_list yama_hooks[] = {
//上面兩個(gè)hook就是對(duì)ptrace的兩種方式進(jìn)行分別check
LSM_HOOK_INIT(ptrace_access_check, yama_ptrace_access_check),
LSM_HOOK_INIT(ptrace_traceme, yama_ptrace_traceme),
LSM_HOOK_INIT(task_prctl, yama_task_prctl),
LSM_HOOK_INIT(task_free, yama_task_free),
};
先看一下在內(nèi)核中關(guān)于LSM_HOOK_INIT的相關(guān)定義,
#define LSM_HOOK_INIT(HEAD, HOOK) { .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
可見(jiàn)該宏的作用就是來(lái)是填充security_hook_list,security_hook_list的,相關(guān)函數(shù)定義如下:
struct security_hook_list {
struct list_head list;
struct list_head *head;
union security_list_options hook;
};
在介紹ptrace_access_check和ptrace_trace前需要補(bǔ)充一些相關(guān)知識(shí):
PTRACE_TRACEME和PTRACE_ATTACH是ptrace()函數(shù)TRACE的兩種類(lèi)型。這兩種方式的主要區(qū)別可以概括為:
PTRACE_TRACEME是子進(jìn)程主動(dòng)申請(qǐng)被TRACE。
而PTRACE_ATTACH是父進(jìn)程自己要attach到子進(jìn)程,
相當(dāng)于子進(jìn)程是被動(dòng)的trace。
繼續(xù),ptrace_may_access主要發(fā)生在發(fā)生在ptrace_attach,而ptrace_attch函數(shù)發(fā)生在ptrace()中。,ptrace_may_access函數(shù)的功能正如源碼注釋
This check is used both for attaching with ptrace and for allowing access to sensitive information in /proc.
ptrace_traceme函數(shù)發(fā)生在ptrace()調(diào)用前,主要的功能是做檢查和設(shè)置PTRACE_TRACEME位,其中PTRACE_TRACEME表示程序已被跟蹤。通過(guò)對(duì)這兩個(gè)函數(shù)進(jìn)行hook,就廊括了ptrace的所有情況了。
先來(lái)看的yama_ptrace_access_check函數(shù),代碼如下:
static int yama_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
int rc = 0;
/* require ptrace target be a child of ptracer on attach */
if (mode & PTRACE_MODE_ATTACH) {
switch (ptrace_scope) {
case YAMA_SCOPE_DISABLED:
/* No additional restrictions. */
break;
case YAMA_SCOPE_RELATIONAL: //進(jìn)程可以跟蹤有血緣關(guān)系(后代)的進(jìn)程
rcu_read_lock();
if (!task_is_descendant(current, child) && //簡(jiǎn)單的遍歷,查看進(jìn)程是否是后代
!ptracer_exception_found(current, child) && //檢測(cè)是否已有祖先attach了
!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
rc = -EPERM;
rcu_read_unlock();
break;
case YAMA_SCOPE_CAPABILITY: //擁有CAP_SYS_PTRACE能力的進(jìn)程才可以使用ptrace
rcu_read_lock();
if (!ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
rc = -EPERM;
rcu_read_unlock();
break;
case YAMA_SCOPE_NO_ATTACH: //無(wú)法進(jìn)行ptrace
default:
rc = -EPERM;
break;
}
}
if (rc && (mode & PTRACE_MODE_NOAUDIT) == 0) {
printk_ratelimited(KERN_NOTICE
"ptrace of pid %d was attempted by: %s (pid %d)
",
child->pid, current->comm, current->pid);
}
return rc;
}
注釋講的很清楚了,通過(guò)switch ptrace_scope的值,對(duì)每種情況分別討論。yama_ptrace_traceme 基本如下:
int yama_ptrace_traceme(struct task_struct *parent)
{
int rc = 0;
/* Only disallow PTRACE_TRACEME on more aggressive settings. */
switch (ptrace_scope) {
case YAMA_SCOPE_CAPABILITY:
/*
當(dāng)用戶(hù)父進(jìn)程有CAP_SYS_PTRACE沒(méi)有CAP_SYS_PTRACE時(shí),返回失敗
*/
if (!has_ns_capability(parent, current_user_ns(), CAP_SYS_PTRACE))
rc = -EPERM;
break;
/*
如果YAMA_SCOPE_NO_ATTACH,直接返回失敗
*/
case YAMA_SCOPE_NO_ATTACH:
rc = -EPERM;
break;
}
對(duì)于
LSM_HOOK_INIT(task_prctl, yama_task_prctl),
LSM_HOOK_INIT(task_free, yama_task_free),
這兩個(gè)hook,主要是為了構(gòu)建調(diào)試函數(shù)和被調(diào)試函數(shù)的關(guān)系,不多闡述,有興趣可以自由閱讀源碼。
總結(jié)
以上是生活随笔為你收集整理的学习LSM(Linux security module)之一:解读yama的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 彩钢板价格分析
- 下一篇: 线性代数笔记32——线性变换及对应矩阵