给linux安全模块LSM添加可链式调用模块(一)
前些日子接了個外包的活,了解了一下Linux安全模塊,發現了安全模塊中的一些問題。
關于linux安全模塊LSM在此就不多說了,大家google下就明白了。
這里主要介紹的是如何修改這個模塊,使它可鏈棧化。
關于LSM,舊版本的提供了register_security/mod_reg_security接口用于注冊用戶的安全模塊,register_security注冊接口只支持一個的安全模塊存在,mod_reg_security 支持注冊多個安全模塊,不過模塊之間的調用需要用戶自己維護(也就是不提供多個安全模塊并存的機制)。更不幸的是,2.6.19(據說這個版本)之后內核取消了mod_reg_security注冊接口(鬼知道什么原因,據說是出于安全考慮,反正給我制造了不少的麻煩)。現在的內核模塊只留下了register_security注冊接口了。
問題來了,我是要幫人家寫個安全模塊的,現在內核不讓我插入安全模塊,這戲就沒法玩了。
什么,還有一個register_security接口,呵呵,linux的安全模塊selinux(紅帽,centos發行版自帶的安全模塊)或者apparmor(ubuntu自帶的安全模塊)還有linux的capability模塊都占著了它了,總不能讓用戶關了自帶的安全模塊用你的來玩吧(除非你比NSA還牛)。我寫的安全模塊主要是提供用戶定制的一些安全機制,簡單的說,就是需要第三方的規則。
好吧,廢話不多說了,問題現在很明顯了,我們的主題也就出來了。
簡單介紹一下LSM模塊吧,這里不得不佩服Linux的設計者,LSM模塊提供的接口很簡單,它提供了一個類似文件系統的抽象層一樣,用戶只要實現它提供的操作結構(security_operations結構),用它提供的安全模塊注冊接口注冊到LSM模塊下面就可以實現它自己的安全功能了。注冊接口代碼如下,其主要的工作為檢查用戶的security_operations結構實例,對一些未定義的接口,修改為默認行為,并把security_ops地址指針指向這個結構,這樣就完成了安全模塊的注冊工作。
1 int __init register_security(struct security_operations *ops)
2 {
3 if (verify(ops)) {
4 printk(KERN_DEBUG "%s could not verify "
5 "security_operations structure.
", __func__);
6 return -EINVAL;
7 }
8
9 if (security_ops != &default_security_ops)
10 return -EAGAIN;
11
12 security_ops = ops;
13
14 return 0;
15 }
了解完這些之后,我們來分析下一步該如何改造LSM吧:
LSM提供一個靜態指針security_ops做為指向訪問模塊的渠道,安全模塊如selinux等在系統啟動之后就把這個指針修改為它們的模塊結構地址了。
首先,我們想到的就是能不能找到這個指針,把這個指針引到我們的安全模塊這邊來。嘿嘿,那樣我們的安全模塊就活起來了。
其次,我們不能只管自己的模塊,不管理系統的死活,至少你不能影響原來系統的安全機制吧。所以,也就是我們在讓自己的安全模塊活的同時,也讓原來的安全模塊一直存活著。
好了,思路分析清楚了,動手查查能不能做吧!
我們來找找security_ops在內核中的地址:
太好了,lsm中的靜態指針地址就在內核符號表中列著,這就說明我們可以通過修改這個地址,去引用我們的安全模塊。
這里給出內核探測引用這個指針的代碼
/**
* probe_kernel_read - Wrapper for kernel_read().
*
* @file: Pointer to "struct file".
* @offset: Starting position.
* @addr: Buffer.
* @count: Size of @addr.
*
* Returns return value from kernel_read().
*/
static int __init probe_kernel_read(struct file *file, unsigned long offset,
char *addr, unsigned long count)
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 8)
/*
* I can't use kernel_read() because seq_read() returns -EPIPE
* if &pos != &file->f_pos .
*/
mm_segment_t old_fs;
unsigned long pos = file->f_pos;
int result;
file->f_pos = offset;
old_fs = get_fs();
set_fs(get_ds());
result = vfs_read(file, (void __user *)addr, count, &file->f_pos);
set_fs(old_fs);
file->f_pos = pos;
return result;
#else
return kernel_read(file, offset, addr, count);
#endif
}
/**
* probe_find_symbol - Find function's address from /proc/kallsyms .
*
* @keyline: Function to find.
*
* Returns address of specified function on success, NULL otherwise.
*/
void *__init probe_find_symbol(const char *keyline)
{
struct file *file = NULL;
char *buf;
unsigned long entry = 0;
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 18)
struct file_system_type *fstype = get_fs_type("proc");
struct vfsmount *mnt = vfs_kern_mount(fstype, 0, "proc", NULL);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 8)
struct file_system_type *fstype = NULL;
struct vfsmount *mnt = do_kern_mount("proc", 0, "proc", NULL);
#else
struct file_system_type *fstype = get_fs_type("proc");
struct vfsmount *mnt = kern_mount(fstype);
#endif
struct dentry *root;
struct dentry *dentry;
/*
* We embed put_filesystem() here because it is not exported.
*/
if (fstype)
module_put(fstype->owner);
if (IS_ERR(mnt))
goto out;
root = dget(mnt->mnt_root);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 16)
mutex_lock(&root->d_inode->i_mutex);
dentry = lookup_one_len("kallsyms", root, 8);
mutex_unlock(&root->d_inode->i_mutex);
#else
down(&root->d_inode->i_sem);
dentry = lookup_one_len("kallsyms", root, 8);
up(&root->d_inode->i_sem);
#endif
dput(root);
if (IS_ERR(dentry))
mntput(mnt);
else {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
struct path path = { mnt, dentry };
file = dentry_open(&path, O_RDONLY, current_cred());
#else
file = dentry_open(dentry, mnt, O_RDONLY
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 29)
, current_cred()
#endif
);
#endif
}
}
if (IS_ERR(file) || !file)
goto out;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (buf) {
int len;
int offset = 0;
while ((len = probe_kernel_read(file, offset, buf,
PAGE_SIZE - 1)) > 0) {
char *cp;
buf[len] = '';
cp = strrchr(buf, '
');
if (!cp)
break;
*(cp + 1) = '';
offset += strlen(buf);
cp = strstr(buf, keyline);
if (!cp)
continue;
*cp = '';
while (cp > buf && *(cp - 1) != '
')
cp--;
entry = simple_strtoul(cp, NULL, 16);
break;
}
kfree(buf);
}
filp_close(file, NULL);
out:
return (void *) entry;
}
代碼很簡單,就是打開/proc/kallsyms文件,去找符號對應的地址,當然,這里考慮了內核版本的問題,使用#if/#endif等,代碼寫得比較亂。
下一步是設計一個合理的鏈式調用,使安全模塊之前能鏈式調用,這個留著下遍再說吧。
總結
以上是生活随笔為你收集整理的给linux安全模块LSM添加可链式调用模块(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Entrust
- 下一篇: 贷款审批时,会重点看征信报告哪些内容?