學習LSM(Linux security module)之一:解讀yama


 

  最近打算寫一個基於LSM的安全模塊,發現國內現有的資料極少。因此打算自己琢磨一下。大致的學習路線如下:

  由易至難使用並閱讀兩到三個安全模塊->參照閱讀模塊自己實現一個安全模塊->在自己實現的同時閱讀LSM實現的基本源碼,由於Yama代碼量小,結構十分清晰,可以作為入門的demo進行參照。

  由於網上關於LSM的相關介紹已經爛大街了,就按自己的初步理解簡單介紹一下LSM,詳情可以自己閱讀文后的相關鏈接,本文源碼基於Linux4.8.0。

一:什么是LSM

  一種輕量級的安全訪問控制框架,主要利用Hook函數對權限進行訪問控制,並在部分對象中內置了透明的安全屬性。

二:Yama的簡單介紹和基本使用

  Yama主要是對Ptrace函數調用進行訪問控制。

  Ptrace是一個系統調用,它提供了一種方法來讓‘父’進程可以觀察和控制其它進程的執行,檢查和改變其核心映像以及寄存器。 主要用來實現斷點調試和系統調用跟蹤。利用ptrace函數,不僅可以劫持另一個進程的調用,修改系統函數調用和改變返回值,而且可以向另一個函數注入代碼,修改eip,進入自己的邏輯。這個函數廣泛用於調試和信號跟蹤工具。所以說,對ptrace函數進行訪問控制還是很有必要的。

  Yama一共分為四個等級:

#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子進程才能進行調試,YAMA_SCOPE_CAPABILITY,擁有CAP_SYS_PTRACE能力的進程才可以使用ptrace。而YAMA_SCOPE_NO_ATTACH代表沒有任何進程可以attach,而且只要設置成了3就無法降級了。

  現在,先來測試使用一下,先將等級設為0。在root權限下進行:

  

  此時,任何ptrace都能夠直接運行。

  被ptrace的demo程序如下:

//test.c 
#include<stdio.h>
int main() { while(1) { sleep(20); static int i = 0; } return 0; }

 

  得到結果如下:

  

  將等級設為一:

  

  等級設為二:

  可以通過setcap CAP_SYS_PTRACE=ep /usr/bin/strace給strace設置CAP_SYS_PTRACE權限,

  

  等級設為三:

  

 三:源碼解析

  從開頭看起

void __init yama_add_hooks(void)
{
    pr_info("Yama: becoming mindful.\n");        //打印相關信息,可以通過dmesg |  grep Yama:查看
    security_add_hooks(yama_hooks, ARRAY_SIZE(yama_hooks));   //添加安全模塊函數
    yama_init_sysctl();                                    //在中sysctl進行注冊
}

 

  先簡單解釋一下yama_init_sysctl()函數,這個函數的作用是在sysctl中進行注冊,使其能通過/proc/sys/kernel/yama/ptrace_scope進行設置參數,看具體源碼:

static void __init yama_init_sysctl(void)
{
	if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table))
		panic("Yama: sysctl registration failed.\n");
}

  其中,yama_sysctl_path用於注明在/proc/sys目錄下的具體位置,yama的定義如下:

struct ctl_path yama_sysctl_path[] = {
	{ .procname = "kernel", },
	{ .procname = "yama", },
	{ }
};

  即在/proc/sys/kernel/yama目錄下。

  yama_sysctl_table表示參數的相關信息,源碼如下:

static int zero;//自動初始化為0
static int max_scope = YAMA_SCOPE_NO_ATTACH;

 


static struct ctl_table yama_sysctl_table[] = { { .procname = "ptrace_scope", //文件名 .data = &ptrace_scope, //實際參數在內核中的數據結構 .maxlen = sizeof(int), //對超過該最大長度的字符串截掉后面超長的部分. .mode = 0644, //條目在proc文件系統下的權限 .proc_handler = yama_dointvec_minmax, //如上,對.proc_handler進行hook .extra1 = &zero, //proc_handler的參數,即范圍為0~3 .extra2 = &max_scope, }, { } };

  proc_handler代表讀寫操作函數,對/proc/sys/kernel/yama/ptrace_scope進行讀寫時調用的函數。其中

    proc_dointvec 讀寫一個包含一個或多個整數的數組

    proc_dostring 讀寫一個字符串

    proc_dointvec_minmax 寫的數組必須在min~max范圍內。

  在這個數據結構中,自己構造了一個函數,來在操作之前進行了一些操作,如下

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來對權限做出檢查,檢查是否有權對指定的資源進行操作,該函數返回0則代表無權操作
    這里對ptrace_scope進行讀寫需要write置1而且需要用戶有CAP_SYS_PTRACE權限
    */
    if (write && !capable(CAP_SYS_PTRACE))         
        return -EPERM;
    //當設置為最大值時,不再允許修改該參數
    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);
}

 

   核心函數是security_add_hooks函數,這個函數負責對ptrace進行訪問控制,如下:

  先來看一下yama_hooks:

static struct security_hook_list yama_hooks[] = {
    //上面兩個hook就是對ptrace的兩種方式進行分別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), };

 

  先看一下在內核中關於LSM_HOOK_INIT的相關定義,

  #define LSM_HOOK_INIT(HEAD, HOOK) { .head = &security_hook_heads.HEAD, .hook = { .HEAD = HOOK } }
  可見該宏的作用就是來是填充security_hook_list,security_hook_list的,相關函數定義如下:

struct security_hook_list {
       struct list_head                list;
struct list_head *head; union security_list_options hook; };

 

  在介紹ptrace_access_check和ptrace_trace前需要補充一些相關知識:

    PTRACE_TRACEME和PTRACE_ATTACH是ptrace()函數TRACE的兩種類型。這兩種方式的主要區別可以概括為:

    PTRACE_TRACEME是子進程主動申請被TRACE。

    而PTRACE_ATTACH是父進程自己要attach到子進程,

    相當於子進程是被動的trace。

  繼續,ptrace_may_access主要發生在發生在ptrace_attach,而ptrace_attch函數發生在ptrace()中。,ptrace_may_access函數的功能正如源碼注釋

    This check is used both for attaching with ptrace and for allowing access to sensitive information in /proc.

  ptrace_traceme函數發生在ptrace()調用前,主要的功能是做檢查和設置PTRACE_TRACEME位,其中PTRACE_TRACEME表示程序已被跟蹤。通過對這兩個函數進行hook,就廊括了ptrace的所有情況了。

  先來看的yama_ptrace_access_check函數,代碼如下:

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:					//進程可以跟蹤有血緣關系(后代)的進程
			rcu_read_lock();									
			if (!task_is_descendant(current, child) &&				//簡單的遍歷,查看進程是否是后代
			    !ptracer_exception_found(current, child) &&		//檢測是否已有祖先attach了
			    !ns_capable(__task_cred(child)->user_ns, CAP_SYS_PTRACE))
				rc = -EPERM;
			rcu_read_unlock();
			break;
		case YAMA_SCOPE_CAPABILITY:				//擁有CAP_SYS_PTRACE能力的進程才可以使用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:			//無法進行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)\n",
			child->pid, current->comm, current->pid);
	}

	return rc;
}

  注釋講的很清楚了,通過switch ptrace_scope的值,對每種情況分別討論。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:
        /*
            當用戶父進程有CAP_SYS_PTRACE沒有CAP_SYS_PTRACE時,返回失敗
        */
        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;
    }

 

  對於

    LSM_HOOK_INIT(task_prctl, yama_task_prctl),
    LSM_HOOK_INIT(task_free, yama_task_free),

 

  這兩個hook,主要是為了構建調試函數和被調試函數的關系,不多闡述,有興趣可以自由閱讀源碼。 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM