返回列表 发帖

FreeBSD 5.0中强制访问控制机制的使用与源代码分析 - 第二部分

本文主要讲述FreeBSD 5.0操作系统中新增的重要安全机制,即强制访问控制机制(MAC)的使用与源代码分析,主要包括强制访问控制框架及多级安全(MLS)策略两部分内容。这一部分较系统地对MAC框架及MLS策略的源代码进行分析。 2 MAC框架与MLS策略源代码分析 与本文相关的源代码文件主要有两个,即 /usr/src/sys/kern/kern_mac.c 和 /usr/src/sys/security/mac_mls/mac_mls.c 。另外还有一些头文件如mac.h、mac_policy.h等。 2.1 MAC框架整体结构 下面是 MAC 框架的示意性结构图,当用户控制台或用户程序通过系统调用对内核对象进行访问的时候,由于内核代码中相应的位置插入了MAC框架的检查函数,于是内核就会调用MAC框架的相应检查函数来做安全性检查。MAC框架会依次调用每个挂接在MAC框架上的安全策略,以决定访问是否安全。另外,其它可能涉及到安全问题的系统事件,如初始化各种安全标记、初始化各种内核对象等,也会通知MAC框架,由它做出相应的处理。 从图中我们也可以看到,安全策略作为一个独立的KLD模块,可以独立于内核进行编译,再在使用的时候挂接到MAC框架上。要判断一次访问是否安全,MAC框架会调用所有的安全策略,只有当所有的安全策略均表示同意,MAC框架才会授权这次访问。 2.2 安全标记 安全标记是由MAC框架和各个安全策略定义的一组数据,用于描述主体或客体的安全信息,安全标记与内核描述主客体的其它数据一起存储在内核中。要实现强制访问控制,首先必须为主客体定义安全标记。不同的策略由于判断的依据不一样,可能定义的标记也不相同。作为MAC框架,当安全策略向它注册的时候,它必须把该策略使用的安全标记附加到各个内核对象上去,这样当需要调用该策略做安全性检查的时候,才能为策略提供它们自己定义和理解的安全标记。我们先给出MAC框架与MLS策略定义的安全标记,再对之作进一步的解释。 MAC框架中安全标记的定义是这样的: struct label { int l_flags; union { void *l_ptr; long l_long; } l_perpolicy[MAC_MAX_POLICIES]; }; 其中l_flags是一个标志,被MAC框架用来判断是否初始化了整个标记数据结构。l_perpolicy数组为每个策略定义了一个联合,这样当策略向MAC框架注册时,它们既可以用一个long类的整数作为它们自己的安全标记,也可以使用联合中的指针指向一个它们自己定义的标记数据结构。MLS策略选择了后者。 MLS策略定义的安全标记是这们的: struct mac_mls_element { u_short mme_type; u_short mme_level; u_char mme_compartments[MAC_MLS_MAX_COMPARTMENTS >> 3]; }; struct mac_mls { int mm_flags; struct mac_mls_element mm_single; struct mac_mls_element mm_rangelow, mm_rangehigh; }; 在mac_mls结构中,定义了一个单一标记(mm_single)和一个标记范围(mm_rangelow,mm_rangehigh),主客体既可以使用单一标记来标识单一安全级,也可以使用标记范围来标识一个安全级范围,还可以同时使用二者。如第1章中我们使用getfmac得到的输出"mls/high"表明该文件使用的是值为"high"的单一安全标记。再比如我们使用getpmac得到的输出"mls/low(low-high)"表明进程同时使用了单一标记和标记范围。至于究竟使用的是哪种标记,由mm_flags标识。 mac_mls_element定义一个标记,它定义的标记功能很强大,既支持安全类型(mme_type变量,值为LOW、HIGH、EQUAL和UNDEFINE),也支持多达256个级别的安全级(当mme_type的值为LEVEL时,mme_level变量有效,由它定义安全级),同时还使用mme_compartments数组支持域(field),后续章节将具体讲述MLS策略是怎样使用它所定义的标记的。 当MLS策略向MAC框架注册时,它会使用MAC标记所定义的l_perpolicy数组中的一项,然后把这一项的l_ptr指针指向自己定义的mac_mls结构,这样就把自己定义的标记挂接到每个内核对象上去了。 2.3 MAC框架的实现 MAC 框架首先必须要维护一个链表,以记录所有挂接在它上面的安全策略,这个链表由下列代码定义,其意义不再详细解释,请参见kern_mac.c: static LIST_HEAD(, mac_policy_conf) mac_policy_list; 上面的代码定义一个mac_policy_conf类型的链表mac_policy_list,其类型定义如下: struct mac_policy_conf { char *mpc_name; /* policy name */ char *mpc_fullname; /* policy full name */ struct mac_policy_ops *mpc_ops; /* policy operations */ int mpc_loadtime_flags; /* flags */ int *mpc_field_off; /* security field */ int mpc_runtime_flags; /* flags */ LIST_ENTRY(mac_policy_conf) mpc_list; /* global list */ }; 其中mpc_list成员变量是一组指针,用于形成链表。mac_policy_conf结构中最重要的一个成员是mpc_ops,我们讲过,每当有访问安全性检查时,MAC框架会把所有的检查事件如创建inode、访问inode等传递给安全策略作检查,这就是通过mpc_ops这个成员变量来进行的。也就是说,mpc_ops这个结构中记录了每个策略对各种MAC事件的处理函数。例如下面是一个来自kern_mac.c的函数: int mac_check_vnode_open(struct ucred *cred, struct vnode *vp, int acc_mode) { int error; ASSERT_VOP_LOCKED(vp, "mac_check_vnode_open"); if (!mac_enforce_fs) return (0); MAC_CHECK(check_vnode_open, cred, vp, &vp->v_label, acc_mode); return (error); } 当用户试图打开一个文件时,内核就会试图打开一个vnode,在打开之前会调用这个函数,用于检查主体是否有权限打开这个vnode。MAC_CHECK宏定义如下: #define MAC_CHECK(check, args...) do { \ struct mac_policy_conf *mpc; \ \ error = 0; \ MAC_POLICY_LIST_BUSY(); \ LIST_FOREACH(mpc, &mac_policy_list, mpc_list) { \ if (mpc->mpc_ops->mpo_ ## check != NULL) \ error = error_select( \ mpc->mpc_ops->mpo_ ## check (args), \ error); \ } \ MAC_POLICY_LIST_UNBUSY(); \ } while (0) 我们可以看到,代码中使用LIST_FOREACH宏来遍历安全策略链表中的每个策略,然后调用其在mpc_ops中注册的检查函数mpo_##check。mac_policy_ops这个结构中定义了很多事件处理函数,进行细粒度的访问控制,限于篇幅,在此不一一列出。这些函数共分四类:第一类是策略本身的初始化和销毁函数;第二类是对安全标记的操作函数,包括各种内核对象的安全标记的初始化与销毁函数,以及获取与设置安全标记的接口函数;第三类是对文件系统、网络系统及进程对象的标记进行操作的事件处理函数;第四类是检查对各个内核对象的访问是否安全的检查函数。详见mac_policy.h。 2.4 安全策略的注册 分析MLS策略,我们可以发现,安全策略向MAC框架注册是一件很简单的事情。mac_mls.c文件中花了大量篇幅定义了所有的事件处理函数,如前所述,这些函数是安全策略的核心,用于判断访问的安全性等。定义完这些函数后,在源代码的最后,把这些函数的地址填入mac_policy_ops结构中,再用一个MAC_POLICY_SET宏把mac_policy_ops结构挂接到上面提到的mac_policy_list链表中去就可以了,如下: MAC_POLICY_SET(&mac_mls_ops, trustedbsd_mac_mls, "TrustedBSD MAC/MLS", MPC_LOADTIME_FLAG_NOTLATE, &mac_mls_slot); 其中MPC_LOADTIME_FLAG_NOTLATE标志告诉系统这个KLD模块只能在系统初始化之前加载,这是因为MLS必须在初始化系统内核对象的时候为每个对象附加上自己定义的安全标记。具体地,系统初始化所有的内核对象之前,都会调用mac_policy_ops结构中定义的mpo_init_XXX_label函数,MLS就是在这个函数中在该内核对象的标记里附加上自己的标记的。下面我们分析一下MAC_POLICY_SET宏。 #define MAC_POLICY_SET(mpops, mpname, mpfullname, mpflags, privdata_wanted) \ static struct mac_policy_conf mpname##_mac_policy_conf = { \ #mpname, \ mpfullname, \ mpops, \ mpflags, \ privdata_wanted, \ 0, \ }; \ static moduledata_t mpname##_mod = { \ #mpname, \ mac_policy_modevent, \ &mpname##_mac_policy_conf \ }; \ MODULE_DEPEND(mpname, kernel_mac_support, 1, 1, 1); \ DECLARE_MODULE(mpname, mpname##_mod, SI_SUB_MAC_POLICY, \ SI_ORDER_MIDDLE) 逻辑也相当简单,我们来走一遍。首先定义一个mac_policy_conf结构,用于描述MLS策略,后续的操作会把这个结构加入到MAC框架所定义的mac_policy_list列表中的。由于MLS策略本身是一个KLD模块,所以必须定义一个moduledata_t结构用于向系统注册模块,在这个结构中指明了由mac_policy_modevent函数处理模块事件,当有模块事件产生的时候将把上面定义的mac_policy_conf结构的地址传给处理函数。 mac_policy_modevent函数的核心代码如下: case MOD_LOAD: if (mpc->mpc_loadtime_flags & MPC_LOADTIME_FLAG_NOTLATE && mac_late) { printf("mac_policy_modevent: can't load %s policy " "after booting\n", mpc->mpc_name); error = EBUSY; break; } error = mac_policy_register(mpc); break; case MOD_UNLOAD: /* Don't unregister the module if it was never registered. */ if ((mpc->mpc_runtime_flags & MPC_RUNTIME_FLAG_REGISTERED) != 0) error = mac_policy_unregister(mpc); else error = 0; break; 当模块被LOAD的时候,会调用 mac_policy_register函数,它首先检查该模块是否已注册,如果没有,就会执行下面的代码: LIST_INSERT_HEAD(&mac_policy_list, mpc, mpc_list); 其中mpc为我们上面提到的mac_policy_conf结构的地址,这样到此为止,就把MLS策略添加到MAC框架的策略列表中去了。 2.5 MLS策略源代码分析 根据MLS策略,高安全级别的主体不能写低安全级别的客体,而低安全级别的主体则不能读高安全级别的客体,只有主客体的安全级别相同,才能既读又写。MLS定义了三种标记比较函数: mac_mls_dominate_single(a,b):用于检查a的单一标记的安全级是否高于b的单一标记的安全级。这个函数主要用于对内核变量的读、写、查询等事件进行安全性检查。 mac_mls_single_in_range(a,b):用于检查a的单一标记的安全级是否界于b的标记范围的安全级范围之内。这个函数主要用于对改变内核对象的标记进行安全性检查,当主体改变一个客体的安全标记时,要求新旧标记的安全级别处于主体的安全级别范围之内。 mac_mls_range_in_range(a,b):用于检查a的安全级范围是否落于b的安全级范围之内。这个函数主要用于改变主体安全级时,要求主体新的安全级必须落于主体当前安全级范围之内。 mac_mls_equal_single(a,b):用于检查a、b的安全级是否相同。这个函数主要用于网络和设备操作。 以上函数都是调用mac_mls_dominate_element()函数来具体比较的,下面我们就分析一下这个函数,其源代码如下: static int mac_mls_dominate_element(struct mac_mls_element *a, struct mac_mls_element *b) { int bit; switch (a->mme_type) { case MAC_MLS_TYPE_EQUAL: case MAC_MLS_TYPE_HIGH: return (1); case MAC_MLS_TYPE_LOW: switch (b->mme_type) { case MAC_MLS_TYPE_LEVEL: case MAC_MLS_TYPE_HIGH: return (0); case MAC_MLS_TYPE_EQUAL: case MAC_MLS_TYPE_LOW: return (1); default: panic("mac_mls_dominate_element: b->mme_type invalid"); } case MAC_MLS_TYPE_LEVEL: switch (b->mme_type) { case MAC_MLS_TYPE_EQUAL: case MAC_MLS_TYPE_LOW: return (1); case MAC_MLS_TYPE_HIGH: return (0); case MAC_MLS_TYPE_LEVEL: for (bit = 1; bit <= MAC_MLS_MAX_COMPARTMENTS; bit++) if (!MAC_MLS_BIT_TEST(bit, a->mme_compartments) && MAC_MLS_BIT_TEST(bit, b->mme_compartments)) return (0); return (a->mme_level >= b->mme_level); default: panic("mac_mls_dominate_element: b->mme_type invalid"); } default: panic("mac_mls_dominate_element: a->mme_type invalid"); } return (0); } 我们只解释一下最后一个case,即当a、b的类型均为MAC_MLS_TYPE_LEVEL时,也就是它们都使用一个数(0到254)标识它们的安全级时,判断是如何进行的。首先: #define MAC_MLS_BIT_TEST(b, w) ((w)[(((b) - 1) >> 3)] & (1 << (((b) - 1) & 7))) 我们可以看到,在比较二者安全级之前,对它们的mme_compartments进行了位测试。mme_compartments是一个256bit的数据(由uchar[32]数组定义),每个bit都可以设置为1来表示该对象属于某一个安全域,当然也可以设置多个bit表示这个对象属于多个安全域。MLS要求如果a可以访问b的话,b所属的安全域必须要是a的子集,否则访问被拒绝。对mme_compartments进行测试的目的也即是要判断b中设置了1的那些bit在a中有否设置。 2.6 对MAC框架及MLS策略的扩充 最后我们对MAC框架作一点改进。前面提过,MAC框架目前并不支持对用户的安全级进行设置,也没有提供存储用户安全级信息的地方,我们想对之做改进,以使其更实用。我们改进的思想是,拦截系统的setuid系统调用,在系统作setuid的时候(用户登录、运行su命令等),读入用户安全级别信息并作相应设置。我们做了如下工作: 在mac_policy.h文件中的mac_policy_ops结构中增加一个新函数mpo_get_cred_from_file(),其参数和返回值与mpo_create_cred()完全一样。 在mac_mls.c文件中增加一个新函数mac_mls_get_cred_from_file(),其参数和返回值与mac_mls_create_cred()完全一样。在这个函数里,增加在内核中读写用户安全级配置文件的语句,从而把用户的安全级在这里读到标记中。然后在对mac_policy_ops结构的设置语句中,增加一条语句:.mpo_get_cred_from_file = mac_mls_get_cred_from_file。 在kern_prot.c文件中的setuid()函数里crcopy()函数替换为我们自己的mac_crcopy()函数,并在调用这个函数之前加一条语句:newcred->cr_uid = uid;。 mac_crcopy()函数的代码如下: void mac_crcopy(struct ucred *dest,struct ucred *src) { uid_t uid = dest->cr_uid; KASSERT(crshared(dest) == 0,("crcopy of shared ucred")); bcopy(&src->cr_startcopy,&dest->cr_startcopy, (unsigned)((caddr_t)&src->cr_endcopy - (caddr_t)&src->cr_startcopy)); uihold(dest->cr_uidinfo); uihold(dest->cr_ruidinfo); if(jailed(dest)) prison_hold(dest->cr_prison); #ifdef MAC dest->cr_uid = uid; mac_get_cred(src,dest); dest->cr_uid = src->cr_uid; #endif } 修改相应的头文件,并重新编译内核。 2.7 小结 上面分析了MAC框架与MLS策略的主要内容,限于篇幅,我们只给出主要逻辑,如果您想要了解更为详细的技术细节,请阅读其源代码或联系我。另外,MAC框架和MLS策略还用到了一些很重要的技术,如系统控制(sysctl)机制、锁等,我们也没有提到,如有兴趣的读者可以继续钻研。

返回列表 回复 发帖