• 学习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.
    ");        //打印相关信息,可以通过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.
    ");
    }
    

      其中,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)
    ",
    			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,主要是为了构建调试函数和被调试函数的关系,不多阐述,有兴趣可以自由阅读源码。 

      

  • 相关阅读:
    自动关联
    如何提高测试效率
    检查点
    windows server 2008 安装vs2008 的问题
    【转】xampp mysql 忘记密码的解决方案
    zencart 目录产品显示控制
    静态html文件执行php语句的方法
    UNIX主机访问PHP程序提示“Internal Server Error”的处理办法
    【转】javascript 点击 <a> 链接
    u880刷机
  • 原文地址:https://www.cnblogs.com/0xJDchen/p/6033167.html
Copyright © 2020-2023  润新知