文章目录
- 前言
- type和attribute映射关系的实现
- type和attribute映射关系保存
- 基于规则库的权限结果查询
- security_compute_av
- context_struct_compute_av
- map_decision
- 附录1 type_attr_map_array检查的内容在core_dump中的表示
前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
经过class的实现、sid的实现、avc的实现、规则库的实现、条件规则库5篇文章的解析。我们基本理解了SELinux的核心数据结构的设计与使用。
老实讲虽然经过了分解成5章的内容,但是本文的内容还是有点绕。头疼
不过我们还需要点亮最后一个技能点<type
和attribute
的实现>后,就可以开始看看SELinux基于规则文件的权限查询是如何实现了。
type和attribute映射关系的实现
首先回顾下type
和attribute
的定义
Type(类型)
- 定义:type 是用来标记文件、进程、socket等系统对象的一个标签,表示这些对象属于哪种安全类别。每个对象至少有一个类型,这决定了它可以与哪些其他类型的对象交互。例如,一个网页文件可能被标记为httpd_sys_content_t类型,表明它是HTTP服务器可以访问的内容。
- 作用:类型系统帮助隔离不同功能领域的资源,确保例如网络服务、用户数据、系统配置等保持在各自的安全边界内,减少了潜在的安全风险。
Attribute(属性)
- 定义:attribute 是附加在类型上的元数据,为类型提供额外的标签或特性。它不是直接定义对象的类别,而是提供了额外的上下文信息,使得策略制定者能够基于更细致的特征来定制访问控制规则。例如,一个文件可能同时被标记为“加密”或“敏感”属性。
- 作用:属性使得SELinux策略更加灵活和强大,因为它允许基于不仅仅是对象的类型,还有其属性来决定访问权限。这为安全策略增加了另一个维度,例如,可以设定规则,使得只有具有特定属性的进程才能访问带有相应属性的文件。
关系与作用
- 类型(Type)是基础,决定了对象的基本访问控制范畴。
- 属性(Attribute)是附加在类型上的,提供了额外的筛选条件,使得策略能够基于对象的更深层次特征来定义访问控制规则。属性可以关联到一个或多个 type 上。这意味着,一个 attribute 可以跨多个 type 应用,为这些类型提供共享的特征或访问控制特性。因此,更准确地说,attribute 是一种机制,它允许策略制定者根据安全策略的需求,将具有相似访问控制需求或特点的 type 组织在一起,而无需直接定义这些类型的一个静态集合。
type和attribute映射关系保存
SELinux使用policydb->type_attr_map_array
来保存type
和attribute
映射关系
其也是一个柔性数组。其整体逻辑如下
柔性数组下标为type id
-1。 指向的结构体struct ebitmap_node
内保存的为type
和attribute
映射关系的位图(bitmap),
当bitmap
某位被置为1时,则此位的编号对应的id
则为attribute
的索引id。比如 某个bitmap
的值为000001000101
则表示此attribute
下,此type
拥有attribute 索引id
为 1、3、7的attribute
考虑到type
和attribute
的长度并非固定的,所以也是使用的可扩展的struct ebitmap_node
来保存位图。
同时,SELinux也封装了对这个位图的快速操作:
ebitmap_for_each_positive_bit(e, n, bit)
- 快速遍历
e
的所有被置为1的位,并将此位的编号保证在i
中返回。其是ebitmap_start_positive
与ebitmap_next_positive
的封装
ebitmap_get_bit(struct ebitmap *e, unsigned long bit)
- 快速获取位图
e
的bit
位的结果,其是ebitmap_node_get_bit
的封装
ebitmap_set_bit(struct ebitmap *e, unsigned long bit)
- 快速设置位图
e
的bit
位的结果,其是ebitmap_node_set_bit
的封装
这样,SElinux就可以使用type id
作为type_attr_map_array
的下标,快速获取其所属的所有attribute
基于规则库的权限结果查询
还是回头看看SELinux的核心函数avc_has_perm
,其在avc_has_perm_noaudit
函数中首先尝试从avc缓存查询结果,失败后则使用函数avc_compute_av
尝试从规则文件中获取结果。avc_compute_av
将会通过security_compute_av
查询结果,然后使用insert_avc
将结果插入到缓存中
security_compute_av
我们一段一段来分解
首先,获取数据库的读写锁。如果SELinux未初始化完成,则直接放行
接着通过ssid
获取其源安全上下文。然后判断是否源安全上下文的type
是否设置了permissive
标志,如果设置了将会给avd
打上AVD_FLAGS_PERMISSIVE
,将在权限检查的最后的avc_denied
阶段忽略拒绝权限直接放行。
奇怪了。为什么这里不直接返回0呢,还要继续往下走。(应该是为了缓存此查询结果)
然后是获取目标的安全上下文。接着通过传入的class id
来查询其真实的class id
。如果未找到合法的class id
而且 规则中允许未知权限,则直接放行
接着将通过源安全上下文、目标安全上下文、目标class id
来获取此组合的许可权限permission bit
。
然后通过map_decision
来将许可的permission bit
转换为permission id
。并返回
context_struct_compute_av
到重头戏了,所以context_struct_compute_av
才是真正查询规则库的函数。
相信对avtab_node
比较熟悉的同学这里会有点疑问:
avtab_node
中,查询的key是avtab_key
,其保存的源目信息是格式为 u16 的id。
但是查询函数context_struct_compute_av
的入参却是类型为struct context
的安全上下文。所以SElinux需要将入参struct context
安全上下文转换为avtab_key
以便于对avtab的查询。这个中间变量即struct avtab_key avkey;
我们还是来一段段分析
首先是初始化以及class的检查
接着初始化查询条件,包括:
- 通过
class id
拿到 存放class
信息的结构体 - 通过
type_attr_map_array
拿到scontext
和tcontext
属于的attribute
信息
遍历源attribute
和目标attribute
,找到其下的所有的attribute id
,
这里返回的是type id以0开始,所以后续使用需要+1
然后两两做积运算,得到source type id
和target type id
之间的所有映射。
然后查询规则库内这些映射关系的结果,并将结果通过|
运算集中到avd
中
这里的实现逻辑困扰了我很久,我也查询不少资料。但是始终无法理解这里的查询逻辑.为什么要先获取目标
type
的attribute
,然后查询attributed
们间的权限。除非说,avtab
的规则是基于attribute
而非type
不过我确实尝试在core_dump
的实际运行内存中查看了type_attr_map_array
的实现,其结果确实是保存的attribute
的索引id。也就是说,在规则检查的时候,avtab
内保存的本质是attribute
对attribute
的访问权限,具体检查逻辑我附录在了最后附录1中。
这部分的疑问,可能需要看完selinux-policy的编译过程,对avtab内的文件格式有详细的了解后才能解答,这里作为一个遗留问题。
然后查询约束表
如果avd->allow
中有不允许权限的约束,则剥离此权限结果
如果当前正在检查的是进程类(policydb->process_class
),并且计算出的允许权限(avd->allowed
)中包含了进程转换权限(policydb->process_trans_perms
),同时源上下文(scontext)和目标上下文(tcontext)的角色不同,那么就需要进一步检查角色转换是否允许。
通过遍历策略数据库中的 role_allow 链表,查找是否存在一个角色允许条目,其源角色(role)等于当前源上下文的角色,且新角色(new_role)等于目标上下文的角色。
如果找到这样的条目,说明avd->allowed
中允许含有的角色转换权限;
如果没有找到,则需要从avd->allowed
中移除进程转换权限。
最近,检查源目上下文中的bounds字段是否有不满足的权限,如果有则剥离
bounds表示其允许切换至的sid
转换的权限检查只检查同层权限
比如某个进程访问某个文件的时候,其sid将会从10->20,然后目标文件的sid将会从30->40.
那么,SElinux将会先在avtab中检查10对30的权限,然后在type_attribute_bounds_av 检查20对40的权限,并不会检查 10对40的权限
同时,多层嵌套的时候以source是否转换了为准,如果source没有转换则不检查。
比如某个进程访问某个文件的时候,其sid将会从10->20->60,然后目标文件的sid将会从30->40.
那么,SElinux将会先在avtab中检查10对30的权限,然后在type_attribute_bounds_av 检查20对40的权限, 然后递归调用继续在type_attribute_bounds_av 检查60对40的权限
但是某个进程访问某个文件的时候,其sid不变一直为10,然后目标文件的sid将会从30->40.
则SElinux将会先在avtab中检查10对30的权限,然后在type_attribute_bounds_av 退出。因为sid没有变化。
哎。。。这逻辑
到这里 SELinux通过context_struct_compute_av
获取到了安全上下文之间的avd结果。
我们应当知道 avd中的allowed保存的是允许的权限(permission)的索引id。
但是class中的permissions在内核中的表示可能结果并不是固定的。不同的规则可能生成的不同的class和permissions之间的映射。
所以将通过map_decision
来将结果转换为满足当前内核中 class的permissions索引id的结果。
map_decision
其实现原理也很简单,就是通过查询当前class和permissions之间的映射,即查询selinux_mapping
来转换avd中保存的结果
通过遍历比较class内的perms与avd中的每一位,如果一致则说明当前的索引号为permission的索引id。然后将1偏移索引id的长度,将结果存放给avd中,
这样 SElinux就完成了基于规则文件的权限查询了。
附录1 type_attr_map_array检查的内容在core_dump中的表示
首先,我找到了一个保存在symtab[SYM_TYPES]中的attribute
:sysctl_type
然后,我检查了其type_attr_map_array[361]
对于的attribute
索引id的内容
所以这里可以发现,对于attribute
来说,其attribute
位图中 保留了其自身的索引id
接着,我在selinux-policy
的规则文件中,找到了一个属于sysctl_type
的type
:sysctl_irq_t
然后找到这个sysctl_irq_t的信息
发现sysctl_irq_t
的type id
为1272
然后查看type
对于的attributex
信息
这里的1099511627776 用二进制表示为0001 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
其第41位为1,而maps数组的每个元素的长度为64bit,所以这里第41位在位图中的位为 5 * 64 + 41 = 361 = sysctl_type 的attribute 索引id
同理,我们找下名称为sysctl_kernel_t
的type
信息,也能得到同样的结果。
根据上面core_dump的信息如下结论:
-
type 索引 id
与attribute 索引 id
都保存在avtab
中,使用字段type_datum->attribute
来区分type
与attribute
- SELinux在查询avtab中的规则的时候,本质是查询
attribute
对attribute
的权限。