===================================================
作者:ietf AT doit.com.cn
所有源文件来自于linux kernel 2.6.20
请在GNU Library General Public License下参考。
引用请注明出处。
===================================================
selinux的出现着实扰乱了文件系统的进度,不过送算慢慢搞清楚了其中的来龙去脉。下面将通过2.6.20内核中的security代码进行一番简单的分析。该版本的security系统在capability和rootplug之间还存在一些问题,有很多新的补丁程序,具体可以参考下列邮件列表。
http://lists.jammed.com/linux-security-module/2005/08/
初次接触LSM时,其间复杂的模块加载方式和顺序很是让人头疼。光其中的ops操作就有security_ops, capability_ops, secondary_ops, selinux_ops, rootplug_ops, selinux_ops, original_ops,再有init函数security_init, capability_init, rootplug_init, selinux_init中的注册关系register_secrity反反复复的来回赋值,最后究竟ops操作都是什么,已经成了一团乱麻。为此,不得不从系统的启动过程开始,寻找其间的因果顺序,事情的经过是这样的:
一、LSM的初始化
LSM系统的初始化发生在系统内核初始化阶段,在src/init/main.c的start_kernel()里,其位置如下所示:
fork_init(num_physpages);
proc_caches_init();
buffer_init();
unnamed_dev_init();
key_init();
security_init();
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
在unnamed_dev_init(), key_init()之后,vfs_caches_init之前,其在内核中的位置层次也基本上如此。
security_init()的具体实现在/src/security/security.c中,
/**
* security_init - initializes the security framework
*
* This should be called early in the kernel initialization sequence.
*/
int __init security_init(void)
{
printk(KERN_INFO "Security Framework v" SECURITY_FRAMEWORK_VERSION
" initialized\n");
if (verify(&dummy_security_ops)) {
printk(KERN_ERR "%s could not verify "
"dummy_security_ops structure.\n", __FUNCTION__);
return -EIO;
}
security_ops = &dummy_security_ops;
do_security_initcalls();
return 0;
}
在此,很有必要了解一下dummy_security_ops的定义,在src/security/dummy.c中,给出了其定义
struct security_operations dummy_security_ops;
定义后的dummy_security_ops并没有初始化,也就是说,它是security_operations的一个结构,该结构里是一系列的指针,每个指针都指向一个函数,而这些函数,就是security框架所能覆盖的领域,通过修改函数指针,可以达到为原先的系统通过钩子增加一个安全过滤层的目的。即,系统的这些调用,先通过你设计的函数过滤相关操作,再有你的函数调用原先的实现,实现增加一层的目的,这和NT中注册操作的回调函数相似。
struct security_operations {.......
dummy_security_ops的初始化是在上面给出的security_init中verify(&dummy_security_ops)函数实现的,它将参数XXX_ops中的所有空指针的函数,初始化为dummy.c中定义的dummy_XXX类型对应函数体,每个函数体只提供一个表示成功的返回值,而不执行任何操作。具体参见src/security/dummy.c。
dummy_security_ops初始化完成后,通过security_ops=&dummy_security_ops,使得security_ops指向一个不做任何操作的过滤层。再通过do_security_initcalls()加载具体配置的安全过滤模块。
在来看看do_security_initcalls()函数是怎么实现的:
static void __init do_security_initcalls(void)
{
initcall_t *call;
call = __security_initcall_start;
while (call < __security_initcall_end) {
(*call) ();
call++;
}
}
在系统中,security_initcall.init的函数总共有三个,分别为:capability_init(), rootplug_init()和selinux_init()。其中,selinux只能第一个注册(即作为security_ops上的第一个过滤层),否则注册会失败。selinux注册后,original_ops=secondary_ops=dummy_ops; securuty_ops=selinux_ops。前两者的注册方式完全一致,并且,在当前代码实现中,两者只能注册一个,另外一个在第一个注册成功后,即使以模块方式加载也会失败。例如,capability_init()注册后,secondary_ops=capability_ops; original_ops=dummy_ops; security_ops=selinux_ops。此时如果再调用rootplug_init(),将因为security_ops!=dummy_security_ops而调用register_security失败,然后因为secondary_ops != original_ops而在调用security_ops->register_security即selinux_register_security函数中返回出错,原因为几经存在了一个第二层模块。
具体代码不在这里列出,有兴趣者可以到src/security/目录下分别查看capability.c, root_plug.c和selinux/hooks.c。
因此可见,主要得代码实现都是在selinux中实现的。以我所感兴趣的selinux_mount和selinux_umount为例,其代码如下:
static int selinux_mount(char * dev_name,
struct nameidata *nd,
char * type,
unsigned long flags,
void * data)
{
int rc;
rc = secondary_ops->sb_mount(dev_name, nd, type, flags, data);
if (rc)
return rc;
if (flags & MS_REMOUNT)
return superblock_has_perm(current, nd->mnt->mnt_sb,
FILESYSTEM__REMOUNT, NULL);
else
return dentry_has_perm(current, nd->mnt, nd->dentry,
FILE__MOUNTON);
}
static int selinux_umount(struct vfsmount *mnt, int flags)
{
int rc;
rc = secondary_ops->sb_umount(mnt, flags);
if (rc)
return rc;
return superblock_has_perm(current,mnt->mnt_sb,
FILESYSTEM__UNMOUNT,NULL);
}
由上面的ops赋值关系可知,secondary_ops->sb_mount和secondary_ops->sb_umount无论capability和root_plug是否加载,因为其在这两者中都没有定义实现,所以,为dummy_XXX函数,相关的代码如下:
static int dummy_sb_mount (char *dev_name, struct nameidata *nd, char *type,
unsigned long flags, void *data)
{
return 0;
}
static int dummy_sb_check_sb (struct vfsmount *mnt, struct nameidata *nd)
{
return 0;
}
static int dummy_sb_umount (struct vfsmount *mnt, int flags)
{
return 0;
}
static void dummy_sb_umount_close (struct vfsmount *mnt)
{
return;
}
static void dummy_sb_umount_busy (struct vfsmount *mnt)
{
return;
}
static void dummy_sb_post_remount (struct vfsmount *mnt, unsigned long flags,
void *data)
{
return;
}
static void dummy_sb_post_mountroot (void)
{
return;
}
然后给出一定的pemmision的判断。permmision判断通过查找avc的hash表实现,它是以数据库的形式在内核维护的。在selinux中,ss目录下的文件负责维护security所有安全检查需要的数据库,对于数据库的详细操作和保存和加载,请参考src/security/selinux/ss/policydb.c中的相关函数。