嗯!如題,一個簡單的基于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;
}
四:其它
大緻設計就這麼些了,想要擴充還是挺友善的,寫代碼的時候的一個很坑的一點是:子產品不能動态加載!!是以呢!如果想好好寫的話,最好還是先把源碼改成可以動态加載,當然,怎麼改就自己看源碼咯!