• 学习LSM(Linux security module)之四:一个基于LSM的简单沙箱的设计与实现


      嗯!如题,一个简单的基于LSM的沙箱设计。环境是Linux v4.4.28。一个比较新的版本,所以在实现过程中很难找到资料,而且还有各种坑逼,所以大部分的时间都是在看源码,虽然写的很烂,但是感觉收获还是挺大的。

      具体思路很简单,每个进程都有对应一个task_struct。可以使用task_struct来对进程的行为进行监测和限制,换句话来说,沙箱是基于进程实现的。

      正如官方文档所说的,LSM在Linux关键数据结构中添加了透明的安全域,具体实现应该是这样的

      

    task_struct{
        ...
        void *security;
        ...
    }

      然而,task_struct 的安全透明域在2.6.28以后的版本就没有了(后来才发现放到cred里面去了)刚发现这一点的时候表示很懵逼,所以,为了不去重新设计实现思路或者换版本实现,所以在task_struct里面添加了void * f_security。配套的安全域初始化hook可以换个思路来进行实现,所以不多添加钩子了。

      1:设计一下安全域的数据结构。

      先来看一下SELinux的设计:

     struct task_security_struct {
             u32 osid;               /* SID prior to last execve */
             u32 sid;                /* current SID */
             u32 exec_sid;           /* exec SID */
             u32 create_sid;         /* fscreate SID */
             u32 keycreate_sid;      /* keycreate SID */
             u32 sockcreate_sid;     /* fscreate SID */
      };
      

      简单一点就设计我这样:

        struct security_demo_task{
        int demo_sid;
         u32 task_sid;   u32 ptrace_sid;   u32 socket_sid;   u32 file_sid;
      /*
      u32 cap_sid;   u32 inode_sid;   u32 ipc_sid;   u32 msg_sid;   u32 task_sid;   u32 dev_sid;   u32 audit_sid; */ };

      可以根据实现的具体情况对各个sid的值进行宏定义,我的宏定义为:

    #define DEMO_ON 1
    #define DEMO_OFF 0
    #define DEMO_PTRACE_ON 1
    #define DEMO_PTRACE_OFF 0
    #define DEMO_FILE_ON 1
    #define DEMO_FILE_OFF 0
    #define DEMO_SOCKET_ON 1
    #define DEMO_SOCKET_OFF 0
    #define DEMO_SCOPE_DISABLED    0
    #define DEMO_SCOPE_LEARNING    1
    #define DEMO_SCOPE_ENABLE    2
    #define DEMO 0 

    #define DEMO_PTRACE 1

    #define DEMO_FILE 2

    #define DEMO_SOCKET 3

      对安全域的初始化,在SELinux,在钩子里队每一个task自动初始化,SElinux因为对每个task都设计了安全域,为了提高内存利用效率,还用了SLAB。而由于我的沙箱只是针对具体进程,所以干脆直接用了kmalloc和kfree进行分配并添加了一个系统调用。代码对比如下:

    //SLinux
    static
    void cred_init_security(void) { struct cred *cred = (struct cred *) current->real_cred; struct task_security_struct *tsec; tsec = kzalloc(sizeof(struct task_security_struct), GFP_KERNEL); if (!tsec) panic("SELinux: Failed to initialize initial task. "); tsec->osid = tsec->sid = SECINITSID_KERNEL; cred->security = tsec; }

      而我的:

    static int task_alloc_security(struct task_struct *task){
        struct security_demo_task *tsec;
        if(task->f_security != NULL){
        printk("Demo:error!Aready Inint!");
        return -EIO;
        }
    
        tsec = kmalloc(sizeof(struct security_demo_task),GFP_KERNEL);
        if (!tsec){
        printk("Demo:error!kmalloc fail!");
            return -ENOMEM;
        }
        tsec->demo_flag = DEMO_ON;
        tsec->ptrace_flag = DEMO_PTRACE_ON;
        tsec->file_flag = DEMO_FILE_ON;
        tsec->socket_flag = DEMO_SOCKET_ON;
        task->f_security = tsec;
    
        return 0;
    }

       二:对安全域的操作

      SELinux采用了AVC对其进行操作,具体的原理网上烂大街了,也不多做阐述。对于这种玩具型的LSM模块来说,可以自己直接添加一个系统调用就OK了。

    SYSCALL_DEFINE3(task_security,pid_t,pid,int,type,int,value){
        struct task_struct* pTaskStruct;
           struct pid* p;
        int err;
        struct security_demo_task* st;
        
        //get task_struct by pid with rcu
        rcu_read_lock();
        p = find_vpid(pid);
        pTaskStruct = pid_task(p,PIDTYPE_PID);
        //printk("Test:name %s
    ",pTaskStruct->comm);    
        rcu_read_unlock();

    //下面代码用于检测各个属性域的sid,每个sid初步设置为open和close,其中的错误检测,就偷懒写成-EIO,具体的宏定义可以自己区看type.h
    if(type == DEMO){
            if(value == DEMO_ON){
         
                err = task_alloc_security(pTaskStruct);
                if(err){
                    printk("Demo:error! Init task_struct_security fail
    ");
                    return -EIO;
                }
                printk("Demo:Init %s'f_security success!
    ",pTaskStruct->comm);
            }
            else if(value == DEMO_OFF){
                err = task_free_security(pTaskStruct);
                if(err){
                    printk("Demo: Free %s's f_security fail!
    ",pTaskStruct->comm);
                    return -EIO;
                }
                printk("Demo:Free %s'f_security success!
    ",pTaskStruct->comm);
            }
            else{
                printk("Demo:Invid Argument!Please check it,and try again");
                return -EIO;
            }
        }
        
        else if(type == DEMO_PTRACE){
            if(pTaskStruct->f_security == NULL){
                printk("Demo:Dont be inint
    ");
                return -EIO;
            }
            st = (struct security_demo_task*)(pTaskStruct->f_security);
            st->ptrace_flag = value;
            printk("Demo:%s'->f_security-> ptrace_flag=%d
    ",pTaskStruct->comm,st->ptrace_flag);
        }
        
        else if(type == DEMO_FILE){
            if(pTaskStruct->f_security == NULL){
                printk("Demo:Dont be inint
    ");
                return -EIO;
            }
            st = (struct security_demo_task*)(pTaskStruct->f_security);
            st->file_flag = value;
            printk("Demo:%s'->f_security-> file_flag=%d
    ",pTaskStruct->comm,st->file_flag);
        }
        
        else if(type == DEMO_SOCKET){
            if(pTaskStruct->f_security == NULL){
                printk("Demo:Dont be inint
    ");
                return -EIO;
            }
            st = (struct security_demo_task*)(pTaskStruct->f_security);
            st->socket_flag = value;
            printk("Demo:%s'->f_security-> socket_flag=%d
    ",pTaskStruct->comm,st->socket_flag);
        }
        
        else{
                printk("Demo:Invid Argument!Please check it,and try again");
                return -EIO;
            }    
        //st = (struct security_demo_task*)(pTaskStruct->f_security);
        //printk("Test:Switch %d
    ",st->demo_flag);
        return 0;
    }

      三:具体设计

        每一个内核里的每个LSM模块(SELinux,Apparmor等)都分为了几个等级(Enable,Learning,Disable)。在这个简单的demo里我也是这样设计的,可以利用sysctl来注册一个变量,这里可以参考yama的具体实现或者直接看系列(二)的文章。至于Disable级,所有的函数Hook都被禁用,而学习模式,则将目标进程的活动全部进行记录,这里可以专门将一个ASCII码文件作为输出log,也可以省事点直接Printk出来。而在Enable级中,则是根据security_demo_task的具体sid来对进程的活动进行限制,所以说,安全域的安全属性越全,则沙箱的功能越强大:

        这里展示一下对创建进程的函数进行hook:

     

    int demo_task_create(unsigned long clone_flags)
    {
        struct security_demo_task* sdt;
    
        if(demo_scope == DEMO_SCOPE_ENABLE && current->f_security != NULL){
          sdt = (struct security_demo_task*)(current->f_security);
          if (sdt->task_flag ==  DEMO_TASK_ON){
              printk("Demo:Forbid create task!");
          
    return -EIO;    }   else if(sdt->task_flag == DEMO_TASK_OFF){    printk("Demo:OK!You can fork!");   } } if(demo_scope == DEMO_SCOPE_LEARNING && current->f_security != NULL){   sdt = (struct security_demo_task*)(current->f_security);   if (sdt->task_flag == DEMO_TASK_ON){    get_time();   printk("%s try to fork a new task that forbid! ",current->comm);     } } return 0; }

      四:其它

        大致设计就这么些了,想要扩展还是挺方便的,写代码的时候的一个很坑的一点是:模块不能动态加载!!所以呢!如果想好好写的话,最好还是先把源码改成可以动态加载,当然,怎么改就自己看源码咯!

      

     

  • 相关阅读:
    多线程的创建方式
    ArrayList 初探
    java创建对象的几种方式
    select2动态查询及多选
    RabbitMQ 消息发送、消息监听
    tr命令
    集群,分布式,微服务概念和区别理解
    xargs命令
    shell中的EOF用法
    字段分隔符IFS
  • 原文地址:https://www.cnblogs.com/0xJDchen/p/6137214.html
Copyright © 2020-2023  润新知