3.决策的实施
当主体对客体进行访问时,客体管理器会收集主体和客体的SID,并根据此SID对在AVC中进行查找:如果找到,则根据相应的安全决策进行处理;反之客体管理器会将主体的SID、客体的SID以及客体的类型传递给安全服务器,安全服务器会根据这些数据及相应的安全策略来计算访问向量,并将计算结果返回给客体管理器,同时将该结果存放到AVC。本节主要以文件系统为例说明文件访问时决策的检索过程,文件操作分为三步:打开、读写、关闭,下面依次从文件的打开及读写的角度从源码上对决策的检索过程进行分析。
(1)文件的打开
在Linux中,当对文件进行操作时,首先需使用open()系统调用打开该文件,在使用open()系统调用打开要访问的文件时就需要检查主体是否有权限访问指定的文件。
图2-23 打开文件时权限的检查过程调用图
如图2-23所示,其中灰色背景标注的红色文字为inode操作集中相应的文件创建时调用的钩子函数,在ext4文件系统中,该钩子函数对应的回调函数为ext4_create()。open()系统调用经过层层调用,最后会通过inode_permission()函数来检查指定inode的访问权限,该函数首先基于ACL进行权限的检查,接着检查主体是否能访问文件所在的设备,最后调用security_inode_permission()函数基于挂载到LSM的安全策略来进行权限的检查。对于inode_permission()函数,详细介绍详见“Linux自主访问控制机制模块代码分析报告”一文,这里不再赘述,下面主要针对security_inode_permission()函数进行详细分析。
security_inode_permission()函数用于通过挂载到LSM的安全策略模块来对文件的访问权限进行检查,以判断某一主体是否可以访问指定的文件,其代码如下所示:
intsecurity_inode_permission(structinode*inode, intmask)
该函数包含两个参数:inode表示要访问的文件的inode;mask表示要检查的权限掩码。对于该函数,其首先判断指定的inode是否是文件系统私有的inode,若是则函数直接返回0;反之调用LSM的钩子函数inode_permission()来进行权限的检查。在启用了SELinux的系统中,security_ops变量指向SELinux实现的相关函数,此时inode_permission()钩子即对应selinux_inode_permission()函数。selinux_inode_permission()函数用于在主体访问inode之前检查其是否具有相应的权限,成功时返回0,该函数定义在security/selinux/hooks.c中,函数头如下所示:
static intselinux_inode_permission(structinode*inode, intmask)
该函数包含两个参数,其含义和security_inode_permission()函数相同,这里不再赘述。对于该函数,其函数调用流程图如图2-24所示,下面结合源码对其主要执行步骤进行说明:
①判断待检查的权限mask是否设置了读、写、执行或追加权限,如果没有,则说明不需要进行权限的检查,此时函数直接返回0。
②调用validate_creds()函数验证当前进程的cred是否有效。若无效,则通知使用了无效的凭证。
③调用IS_PRIVATE()宏判断指定的inode是否是文件系统内部的inode,若是,则直接返回0。
④调用file_mask_to_av()函数将linux中文件的类型和权限转化为访问向量。
⑤调用cred_sid()函数获取当前进程的SID。
⑥调用avc_has_perm_noaudit()函数进行权限的检查,对于该函数,详细分析参见3.1节。
⑦调用avc_audit_required()函数计算待审计的权限及拒绝的权限,并返回待审计的权限。
⑧判断是否存在要审计的权限,如果不存在待审的权限,则函数返回。
⑨调用audit_inode_permission()函数来审计相应的权限。该函数封装了slow_avc_audit()函数,后者最终会调用common_lsm_audit()函数。common_lsm_aduit()函数是一个通用的lsm审计函数,它根据普通的安全信息设置审计缓冲区,并调用回调函数来打印LSM指定的信息。对于其详细介绍,参见“Linux安全审计机制模块代码分析报告”一文。
⑩结束并返回。
图2-24 selinux_inode_permission()函数调用流程图
(2)文件的读写
当使用open()系统调用打开文件后,此时即可使用read()/write()系统调用来对该文件进行读写操作,在使用read()/write()系统调用读写打开的文件时就需要检查主体是否有权限读写指定的文件。对于该过程,函数调用过程如图2-25所示:
图2-25文件读写时权限检查的过程调用图
如上图所示,read()/write()系统调用经过层层调用,最后均会通过security_file_permission()函数来检查指定inode的访问权限,该函数定义在security/security.c中,代码如下所示:
intsecurity_file_permission(structfile*file, intmask)
{
intret;
ret=security_ops->file_permission(file,mask);
if (ret)
returnret;
returnfsnotify_perm(file,mask);
}
该函数包含两个参数:file表示进程打开的文件;mask表示要检查的权限掩码。对于该函数,其首先调用LSM的钩子函数file_permission()在访问打开的文件之前进行权限的检查,如果安全模块授予了相应的权限,则调用fsnotify_perm()函数来通知父目录对该文件进行了相应的操作,对于该函数,详细介绍参见“Linux完整性保护机制模块代码分析报告”一文;反之直接返回。对于启用了SELinux的系统来说,LSM的file_permission()钩子对应的回调函数为selinux_file_permission(),其代码如下所示:
static intselinux_file_permission(structfile*file, intmask)
{
structinode*inode=file->f_path.dentry->d_inode;
structfile_security_struct*fsec=file->f_security;
structinode_security_struct*isec=inode->i_security;
u32sid=current_sid();
if (!mask)
return 0;
if (sid==fsec->sid&&fsec->isid==isec->sid&&
fsec->pseqno==avc_policy_seqno())
return 0;
returnselinux_revalidate_file_permission(file,mask);
}
如上所示,该函数包含两个参数,其含义和security_file_permission()函数相同,这里不再赘述。该函数首先对参数进行相应的检查,如果通过了检查,则调用selinux_revalidate_file_permission()函数,selinux_revalidate_file_permission()函数只是对file_has_perm()函数的简单封装,因此下文主要针对file_has_perm()函数进行详细的分析。
file_has_perm()函数用于检查一个进程是否可以使用打开的文件描述符以指定的方式来访问一个inode,其定义在security/selinux/hooks.c中,函数头如下所示:
static intfile_has_perm(const structcred*cred,structfile*file,u32av)
该函数包含3个参数:cred表示相应的进程的凭证;file表示要访问的文件对应的打开的文件描述符;av表示相应的访问向量。对于该函数,其函数调用流程如图2-26所示,下面结合源码对该函数的执行步骤进行说明:
①调用cred_sid()函数获取当前进程的SID。
②判断当前进程的SID是否与打开的文件描述符的SID相等,若不等,则调用avc_has_perm()函数来进行权限的检查,对于其详细的介绍,参见2.3.2.1小节。
③判断av是否为空,若不为空,则调用inode_has_perm()检查指定的进程对指定节点是否具有参数av指定的权限。
④结束并返回。
图2-26 file_has_perm()函数调用流程图