selinux(security enhance linux)是由美国国家安全局开发的,
用来进行强制访问控制,增强linux系统安全性。
传统的linux访问控制采用的是DAC(Discretionary Access Control)模型,
这里面主要用的是uid控制。
在linux系统里,对于文件的操作,只有「所有者」,「所有组」,「其他」这3类的划分。
文件所有者对文件有所有权限,而且可以授予其他用户的访问权限,
但是之后文件的访问就可能不受所有者控制,同时这种模型中没有区分用户和进程,
一个用户下所有进程权限都是等同的。
而selinux采用MAC(Mandatory Access Control),强制访问控制使用的"最小权限集"的方式,
每个进程需要赋予特定的权限,才能进行访问,这样一个用户的不同进程就具有了不同的权限。
例如,可以设定某个网络应用进程net只有socket操作权限,而没有读写proc文件的权限。
这种方式可以对访问操作中的主体和客体进行更精准的权限控制。
使用MAC的关键是主体跟客体都要设定自己的安全上下文,
当访问主体(一般是进程)对访问客体(一般为文件)进行访问时,
需要先通过权限检查,通过之后才能进行下面的访问。
安全上下文实际上就是一个附加在对象上的标签(label)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是SELinux用户、SELinux 角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:rank”。
例如执行 ls -Z
dr-xr-xr-x root root u:object_r:proc:s0 proc
在安全上下文中,有用的是类型,用户,角色,安全级别用处不大,一般都设置为u跟object_r,s0
安全级别一般用在多级安全级别环境中,例如军事教育中。
在进行安全检查时,实际上是两个安全上下文的比较。
接下来看下selinux在内核代码中的实现。
用来进行强制访问控制,增强linux系统安全性。
传统的linux访问控制采用的是DAC(Discretionary Access Control)模型,
这里面主要用的是uid控制。
在linux系统里,对于文件的操作,只有「所有者」,「所有组」,「其他」这3类的划分。
文件所有者对文件有所有权限,而且可以授予其他用户的访问权限,
但是之后文件的访问就可能不受所有者控制,同时这种模型中没有区分用户和进程,
一个用户下所有进程权限都是等同的。
而selinux采用MAC(Mandatory Access Control),强制访问控制使用的"最小权限集"的方式,
每个进程需要赋予特定的权限,才能进行访问,这样一个用户的不同进程就具有了不同的权限。
例如,可以设定某个网络应用进程net只有socket操作权限,而没有读写proc文件的权限。
这种方式可以对访问操作中的主体和客体进行更精准的权限控制。
使用MAC的关键是主体跟客体都要设定自己的安全上下文,
当访问主体(一般是进程)对访问客体(一般为文件)进行访问时,
需要先通过权限检查,通过之后才能进行下面的访问。
安全上下文实际上就是一个附加在对象上的标签(label)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是SELinux用户、SELinux 角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为“user:role:type:rank”。
例如执行 ls -Z
dr-xr-xr-x root root u:object_r:proc:s0 proc
在安全上下文中,有用的是类型,用户,角色,安全级别用处不大,一般都设置为u跟object_r,s0
安全级别一般用在多级安全级别环境中,例如军事教育中。
在进行安全检查时,实际上是两个安全上下文的比较。
接下来看下selinux在内核代码中的实现。
使用 SELinux 的强制访问控制系统概述如图:
在内核中定义了一个LSM(linux security module)框架,这个框架定义了安全检查的通用接口,
定义在kernel/security/security.c中,具体安全策略的实现由用户可选,目前linux支持的安全
策略有selinux,yama,tomoyo,smack几种。
对于selinux来说,在hooks.c中会注册selinux 具体安全接口到LSM中,
当主体打算访问客体时,selinux先从AVC(access vector cache)中查找是否有匹配安全上下文的节点,
有的话,直接比较node的安全上下文能否允许访问。
如果在AVC中查找不到,需要从sepolicy数据库中去查找,找到后顺便将安全上下文加入AVC,便于以后的查找。
sepolicy数据库是用户编辑的安全策略文件,android中te文件就是用户定义的安全上下文,最终编译后生成一个sepolicy文件,
android init进程启动时将其加载到内存,最终通过security_load_policy加载到内核中。
我们以mount过程为例看下kernel代码流程。
mount->do_mount->security_sb_mount
int security_sb_mount(const char *dev_name, struct path *path,
const char *type, unsigned long flags, void *data)
{
return security_ops->sb_mount(dev_name, path, type, flags, data);
}
//接下来调用selinux注册的selinux_mount
static int selinux_mount(const char *dev_name,
struct path *path,
const char *type,
unsigned long flags,
void *data)
{
const struct cred *cred = current_cred();
if (flags & MS_REMOUNT)
return superblock_has_perm(cred, path->dentry->d_sb,
FILESYSTEM__REMOUNT, NULL);
else
return path_has_perm(cred, path, FILE__MOUNTON);
}
static inline int path_has_perm(const struct cred *cred,
struct path *path,
u32 av)
{
struct inode *inode = path->dentry->d_inode;
struct common_audit_data ad;
ad.type = LSM_AUDIT_DATA_PATH;
ad.u.path = *path;
return inode_has_perm(cred, inode, av, &ad, 0);
}
//权限检查
int avc_has_perm_flags(u32 ssid, u32 tsid, u16 tclass,
u32 requested, struct common_audit_data *auditdata,
unsigned flags)
{
struct av_decision avd;
int rc, rc2;
//用于在不执行审计的情况下检查访问向量缓存来确定是否授予SID对(ssid,tsid)以请求的权限
rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd);
//avc 审计检查,
rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata,
flags);
if (rc2)
return rc2;
return rc;
}
//先从AVC中查找,找不到就去sepolicy数据库中查找,
//最终的关键还是计算allowed值
inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
unsigned flags,
struct av_decision *avd)
{
struct avc_node *node;
struct avc_xperms_node xp_node;
int rc = 0;
u32 denied;
BUG_ON(!requested);
rcu_read_lock();
//从avc hash list中查找是否有匹配的node
node = avc_lookup(ssid, tsid, tclass);
if (unlikely(!node))//hash list查找node失败
node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
else
memcpy(avd, &node->ae.avd, sizeof(*avd));
denied = requested & ~(avd->allowed);//上面检查关键还是确定allowed值
if (unlikely(denied))
rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);
rcu_read_unlock();
return rc;
}
static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid,
u16 tclass, struct av_decision *avd,
struct avc_xperms_node *xp_node)
{
rcu_read_unlock();
INIT_LIST_HEAD(&xp_node->xpd_head);
//这个函数主要是查找sepolicy数据库,确定安全上下文是否匹配
security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp);
rcu_read_lock();
return avc_insert(ssid, tsid, tclass, avd, xp_node);//将ssid/tsid对应的node插入avc 缓冲区
}
//根据source,target安全上下文计算最终av_decision
void security_compute_av(u32 ssid,
u32 tsid,
u16 orig_tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
u16 tclass;
struct context *scontext = NULL, *tcontext = NULL;
read_lock(&policy_rwlock);
avd_init(avd);
xperms->len = 0;
if (!ss_initialized)
goto allow;
scontext = sidtab_search(&sidtab, ssid);//从全局sidtable中查找安全上下文
if (!scontext) {
printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n",
__func__, ssid);
goto out;
}
/* permissive domain? */
if (ebitmap_get_bit(&policydb.permissive_map, scontext->type))
avd->flags |= AVD_FLAGS_PERMISSIVE;
tcontext = sidtab_search(&sidtab, tsid);
if (!tcontext) {
printk(KERN_ERR "SELinux: %s: unrecognized SID %d\n",
__func__, tsid);
goto out;
}
tclass = unmap_class(orig_tclass);
if (unlikely(orig_tclass && !tclass)) {
if (policydb.allow_unknown)
goto allow;
goto out;
}
//比较source/dst 安全上下文
context_struct_compute_av(scontext, tcontext, tclass, avd, xperms);
//用来计算avd 各项值
map_decision(orig_tclass, avd, policydb.allow_unknown);
out:
read_unlock(&policy_rwlock);
return;
allow:
avd->allowed = 0xffffffff;
goto out;
}
//将ssid/tsid对应的node插入avc 缓冲区
static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass,
struct av_decision *avd,
struct avc_xperms_node *xp_node)
{
struct avc_node *pos, *node = NULL;
int hvalue;
unsigned long flag;
if (avc_latest_notif_update(avd->seqno, 1))
goto out;
node = avc_alloc_node();//从slub高速缓存申请一个avc_node
if (node) {
struct hlist_head *head;
spinlock_t *lock;
int rc = 0;
hvalue = avc_hash(ssid, tsid, tclass);//计算hash值
avc_node_populate(node, ssid, tsid, tclass, avd);//给node赋值
rc = avc_xperms_populate(node, xp_node);//填充node扩展属性
if (rc) {
kmem_cache_free(avc_node_cachep, node);
return NULL;
}
head = &avc_cache.slots[hvalue];
lock = &avc_cache.slots_lock[hvalue];
spin_lock_irqsave(lock, flag);
hlist_for_each_entry(pos, head, list) {//遍历hlist
if (pos->ae.ssid == ssid &&
pos->ae.tsid == tsid &&
pos->ae.tclass == tclass) {
avc_node_replace(node, pos);
goto found;
}
}
hlist_add_head_rcu(&node->list, head);//node加入hlist最前
found:
spin_unlock_irqrestore(lock, flag);
}
out:
return node;
}
//审计授予及拒绝的权限,
//所谓审计就是将权限打印到终端给用户检查
static inline int avc_audit(u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct av_decision *avd,
int result,
struct common_audit_data *a, unsigned flags)
{
u32 audited, denied;
//计算待审计及拒绝的权限,并返回待审计的权限
audited = avc_audit_required(requested, avd, result, 0, &denied);
if (likely(!audited))
return 0;
return slow_avc_audit(ssid, tsid, tclass,
requested, audited, denied, result,
a, flags);
}
对于android安全策略配置,可以参考下面的文章:
http://www.jianshu.com/p/a3572eee341c