天天看點

學習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.\n");

    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\n",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\n");
                return -EIO;
            }
            printk("Demo:Init %s'f_security success!\n",pTaskStruct->comm);
        }
        else if(value == DEMO_OFF){
            err = task_free_security(pTaskStruct);
            if(err){
                printk("Demo: Free %s's f_security fail!\n",pTaskStruct->comm);
                return -EIO;
            }
            printk("Demo:Free %s'f_security success!\n",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\n");
            return -EIO;
        }
        st = (struct security_demo_task*)(pTaskStruct->f_security);
        st->ptrace_flag = value;
        printk("Demo:%s'->f_security-> ptrace_flag=%d\n",pTaskStruct->comm,st->ptrace_flag);
    }
    
    else if(type == DEMO_FILE){
        if(pTaskStruct->f_security == NULL){
            printk("Demo:Dont be inint\n");
            return -EIO;
        }
        st = (struct security_demo_task*)(pTaskStruct->f_security);
        st->file_flag = value;
        printk("Demo:%s'->f_security-> file_flag=%d\n",pTaskStruct->comm,st->file_flag);
    }
    
    else if(type == DEMO_SOCKET){
        if(pTaskStruct->f_security == NULL){
            printk("Demo:Dont be inint\n");
            return -EIO;
        }
        st = (struct security_demo_task*)(pTaskStruct->f_security);
        st->socket_flag = value;
        printk("Demo:%s'->f_security-> socket_flag=%d\n",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\n",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!\n",current->comm);
     }
    }
    return 0;

}      

  四:其它

    大緻設計就這麼些了,想要擴充還是挺友善的,寫代碼的時候的一個很坑的一點是:子產品不能動态加載!!是以呢!如果想好好寫的話,最好還是先把源碼改成可以動态加載,當然,怎麼改就自己看源碼咯!

繼續閱讀