文章目录
前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
经过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
映射关系
struct policydb {
......
struct flex_array *type_attr_map_array;
......
}
其也是一个柔性数组。其整体逻辑如下
柔性数组下标为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
void security_compute_av(struct selinux_state *state,
u32 ssid,
u32 tsid,
u16 orig_tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
struct policydb *policydb;
struct sidtab *sidtab;
u16 tclass;
struct context *scontext = NULL, *tcontext = NULL;
read_lock(&state->ss->policy_rwlock);
avd_init(state, avd);
xperms->len = 0;
if (!state->initialized)
goto allow;
policydb = &state->ss->policydb;
sidtab = &state->ss->sidtab;
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
pr_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) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tsid);
goto out;
}
tclass = unmap_class(&state->ss->map, orig_tclass);
if (unlikely(orig_tclass && !tclass)) {
if (policydb->allow_unknown)
goto allow;
goto out;
}
context_struct_compute_av(policydb, scontext, tcontext, tclass, avd,
xperms);
map_decision(&state->ss->map, orig_tclass, avd,
policydb->allow_unknown);
out:
read_unlock(&state->ss->policy_rwlock);
return;
allow:
avd->allowed = 0xffffffff;
goto out;
}
我们一段一段来分解
首先,获取数据库的读写锁。如果SELinux未初始化完成,则直接放行
read_lock(&state->ss->policy_rwlock);
avd_init(state, avd);
xperms->len = 0;
if (!state->initialized)
goto allow;
接着通过ssid
获取其源安全上下文。然后判断是否源安全上下文的type
是否设置了permissive
标志,如果设置了将会给avd
打上AVD_FLAGS_PERMISSIVE
,将在权限检查的最后的avc_denied
阶段忽略拒绝权限直接放行。
奇怪了。为什么这里不直接返回0呢,还要继续往下走。(应该是为了缓存此查询结果)
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
pr_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;
然后是获取目标的安全上下文。接着通过传入的class id
来查询其真实的class id
。如果未找到合法的class id
而且 规则中允许未知权限,则直接放行
tcontext = sidtab_search(sidtab, tsid);
if (!tcontext) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tsid);
goto out;
}
tclass = unmap_class(&state->ss->map, orig_tclass);
if (unlikely(orig_tclass && !tclass)) {
if (policydb->allow_unknown)
goto allow;
goto out;
}
接着将通过源安全上下文、目标安全上下文、目标class id
来获取此组合的许可权限permission bit
。
然后通过map_decision
来将许可的permission bit
转换为permission id
。并返回
context_struct_compute_av(policydb, scontext, tcontext, tclass, avd,
xperms);
map_decision(&state->ss->map, orig_tclass, avd,
policydb->allow_unknown);
out:
read_unlock(&state->ss->policy_rwlock);
return;
context_struct_compute_av
到重头戏了,所以context_struct_compute_av
才是真正查询规则库的函数。
static void context_struct_compute_av(struct policydb *policydb,
struct context *scontext,
struct context *tcontext,
u16 tclass,
struct av_decision *avd,
struct extended_perms *xperms)
{
struct constraint_node *constraint;
struct role_allow *ra;
struct avtab_key avkey;
struct avtab_node *node;
struct class_datum *tclass_datum;
struct ebitmap *sattr, *tattr;
struct ebitmap_node *snode, *tnode;
unsigned int i, j;
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
if (xperms) {
memset(&xperms->drivers, 0, sizeof(xperms->drivers));
xperms->len = 0;
}
if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) {
if (printk_ratelimit())
pr_warn("SELinux: Invalid class %hu\n", tclass);
return;
}
tclass_datum = policydb->class_val_to_struct[tclass - 1];
/*
* If a specific type enforcement rule was defined for
* this permission check, then use it.
*/
avkey.target_class = tclass;
avkey.specified = AVTAB_AV | AVTAB_XPERMS;
sattr = flex_array_get(policydb->type_attr_map_array,
scontext->type - 1);
BUG_ON(!sattr);
tattr = flex_array_get(policydb->type_attr_map_array,
tcontext->type - 1);
BUG_ON(!tattr);
ebitmap_for_each_positive_bit(sattr, snode, i) {
ebitmap_for_each_positive_bit(tattr, tnode, j) {
avkey.source_type = i + 1;
avkey.target_type = j + 1;
for (node = avtab_search_node(&policydb->te_avtab,
&avkey);
node;
node = avtab_search_node_next(node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
avd->allowed |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITALLOW)
avd->auditallow |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITDENY)
avd->auditdeny &= node->datum.u.data;
else if (xperms && (node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(xperms, node);
}
/* Check conditional av table for additional permissions */
cond_compute_av(&policydb->te_cond_avtab, &avkey,
avd, xperms);
}
}
/*
* Remove any permissions prohibited by a constraint (this includes
* the MLS policy).
*/
constraint = tclass_datum->constraints;
while (constraint) {
if ((constraint->permissions & (avd->allowed)) &&
!constraint_expr_eval(policydb, scontext, tcontext, NULL,
constraint->expr)) {
avd->allowed &= ~(constraint->permissions);
}
constraint = constraint->next;
}
/*
* If checking process transition permission and the
* role is changing, then check the (current_role, new_role)
* pair.
*/
if (tclass == policydb->process_class &&
(avd->allowed & policydb->process_trans_perms) &&
scontext->role != tcontext->role) {
for (ra = policydb->role_allow; ra; ra = ra->next) {
if (scontext->role == ra->role &&
tcontext->role == ra->new_role)
break;
}
if (!ra)
avd->allowed &= ~policydb->process_trans_perms;
}
/*
* If the given source and target types have boundary
* constraint, lazy checks have to mask any violated
* permission and notice it to userspace via audit.
*/
type_attribute_bounds_av(policydb, scontext, tcontext,
tclass, avd);
}
相信对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的检查
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
if (xperms) {
memset(&xperms->drivers, 0, sizeof(xperms->drivers));
xperms->len = 0;
}
if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) {
if (printk_ratelimit())
pr_warn("SELinux: Invalid class %hu\n", tclass);
return;
}
接着初始化查询条件,包括:
- 通过
class id
拿到 存放class
信息的结构体 - 通过
type_attr_map_array
拿到scontext
和tcontext
属于的attribute
信息
tclass_datum = policydb->class_val_to_struct[tclass - 1];
avkey.target_class = tclass;
avkey.specified = AVTAB_AV | AVTAB_XPERMS;
sattr = flex_array_get(policydb->type_attr_map_array,
scontext->type - 1);
BUG_ON(!sattr);
tattr = flex_array_get(policydb->type_attr_map_array,
tcontext->type - 1);
BUG_ON(!tattr);
遍历源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内的文件格式有详细的了解后才能解答,这里作为一个遗留问题。
ebitmap_for_each_positive_bit(sattr, snode, i) {
ebitmap_for_each_positive_bit(tattr, tnode, j) {
avkey.source_type = i + 1;
avkey.target_type = j + 1;
for (node = avtab_search_node(&policydb->te_avtab, &avkey);
node;
node = avtab_search_node_next(node, avkey.specified)) {
if (node->key.specified == AVTAB_ALLOWED)
avd->allowed |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITALLOW)
avd->auditallow |= node->datum.u.data;
else if (node->key.specified == AVTAB_AUDITDENY)
avd->auditdeny &= node->datum.u.data;
else if (xperms && (node->key.specified & AVTAB_XPERMS))
services_compute_xperms_drivers(xperms, node);
}
/* Check conditional av table for additional permissions */
cond_compute_av(&policydb->te_cond_avtab, &avkey,
avd, xperms);
}
}
然后查询约束表
如果avd->allow
中有不允许权限的约束,则剥离此权限结果
constraint = tclass_datum->constraints;
while (constraint) {
if ((constraint->permissions & (avd->allowed)) &&
!constraint_expr_eval(policydb, scontext, tcontext, NULL,
constraint->expr)) {
avd->allowed &= ~(constraint->permissions);
}
constraint = constraint->next;
}
如果当前正在检查的是进程类(policydb->process_class
),并且计算出的允许权限(avd->allowed
)中包含了进程转换权限(policydb->process_trans_perms
),同时源上下文(scontext)和目标上下文(tcontext)的角色不同,那么就需要进一步检查角色转换是否允许。
通过遍历策略数据库中的 role_allow 链表,查找是否存在一个角色允许条目,其源角色(role)等于当前源上下文的角色,且新角色(new_role)等于目标上下文的角色。
如果找到这样的条目,说明avd->allowed
中允许含有的角色转换权限;
如果没有找到,则需要从avd->allowed
中移除进程转换权限。
if (tclass == policydb->process_class &&
(avd->allowed & policydb->process_trans_perms) &&
scontext->role != tcontext->role) {
for (ra = policydb->role_allow; ra; ra = ra->next) {
if (scontext->role == ra->role &&
tcontext->role == ra->new_role)
break;
}
if (!ra)
avd->allowed &= ~policydb->process_trans_perms;
}
最近,检查源目上下文中的bounds字段是否有不满足的权限,如果有则剥离
bounds表示其允许切换至的sid
type_attribute_bounds_av(policydb, scontext, tcontext,
tclass, avd);
static void type_attribute_bounds_av(struct policydb *policydb,
struct context *scontext,
struct context *tcontext,
u16 tclass,
struct av_decision *avd)
{
struct context lo_scontext;
struct context lo_tcontext, *tcontextp = tcontext;
struct av_decision lo_avd;
struct type_datum *source;
struct type_datum *target;
u32 masked = 0;
//获取源安全上下文
source = flex_array_get_ptr(policydb->type_val_to_struct_array,
scontext->type - 1);
BUG_ON(!source);
// 如果源上下文没有转换则退出
if (!source->bounds)
return;
//获取目标安全上下文
target = flex_array_get_ptr(policydb->type_val_to_struct_array,
tcontext->type - 1);
BUG_ON(!target);
memset(&lo_avd, 0, sizeof(lo_avd));
memcpy(&lo_scontext, scontext, sizeof(lo_scontext));
lo_scontext.type = source->bounds;
if (target->bounds) {
memcpy(&lo_tcontext, tcontext, sizeof(lo_tcontext));
lo_tcontext.type = target->bounds;
tcontextp = &lo_tcontext;
}
//查询源转换后的sid(source->bounds)对target->bounds(如果有)或者tcontext 的sid的权限
// 注意这里是递归查询(context_struct_compute_av 函数被递归)
context_struct_compute_av(policydb, &lo_scontext,
tcontextp,
tclass,
&lo_avd,
NULL);
// 获取转换前有的权限而转换后没有的权限
masked = ~lo_avd.allowed & avd->allowed;
// 如果没有则说明转换前后权限一致,跳出
if (likely(!masked))
return; /* no masked permission */
// 如果存在转换前拥有,而转换后没有的权限,则在转换前的权限avd中剥离掉转换后没有的权限并打印审计日志
/* mask violated permissions */
avd->allowed &= ~masked;
/* audit masked permissions */
security_dump_masked_av(policydb, scontext, tcontext,
tclass, masked, "bounds");
}
转换的权限检查只检查同层权限
比如某个进程访问某个文件的时候,其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中,
static void map_decision(struct selinux_map *map,
u16 tclass, struct av_decision *avd,
int allow_unknown)
{
if (tclass < map->size) {
struct selinux_mapping *mapping = &map->mapping[tclass];
unsigned int i, n = mapping->num_perms;
u32 result;
// 通过遍历比较class内的perms与avd中的每一位,如果一致则说明当前的索引号为permission的索引id。然后将1偏移索引id的长度,将结果存放给avd中,
for (i = 0, result = 0; i < n; i++) {
if (avd->allowed & mapping->perms[i])
result |= 1<<i;
if (allow_unknown && !mapping->perms[i])
result |= 1<<i;
}
avd->allowed = result;
for (i = 0, result = 0; i < n; i++)
if (avd->auditallow & mapping->perms[i])
result |= 1<<i;
avd->auditallow = result;
for (i = 0, result = 0; i < n; i++) {
if (avd->auditdeny & mapping->perms[i])
result |= 1<<i;
if (!allow_unknown && !mapping->perms[i])
result |= 1<<i;
}
/*
* In case the kernel has a bug and requests a permission
* between num_perms and the maximum permission number, we
* should audit that denial
*/
for (; i < (sizeof(u32)*8); i++)
result |= 1<<i;
avd->auditdeny = result;
}
}
这样 SElinux就完成了基于规则文件的权限查询了。
附录1 type_attr_map_array检查的内容在core_dump中的表示
首先,我找到了一个保存在symtab[SYM_TYPES]中的attribute
:sysctl_type
crash> p * (struct hashtab_node *) 0xffff8e91282018a0
$281 = {
key = 0xffff8e9128202380,
datum = 0xffff8e91282024d0,
next = 0x0
}
crash> p (char *) 0xffff8e9128202380
$283 = 0xffff8e9128202380 "sysctl_type"
crash> p * (struct type_datum *) 0xffff8e91282024d0
$282 = {
value = 361,
bounds = 0,
primary = 1 '\001',
attribute = 1 '\001'
}
然后,我检查了其type_attr_map_array[361]
对于的attribute
索引id的内容
crash> p *(struct ebitmap*)&selinux_ss.policydb.type_attr_map_array.parts[1].elements[(360-256)*16]
$320 = {
node = 0xffff8e9132308780,
highbit = 384
}
crash> p *(struct ebitmap_node * ) 0xffff8e9132308780
$321 = {
next = 0x0,
maps = {0, 0, 0, 0, 0, 1099511627776}, //1099511627776 = 00010000000000000000000000000000000000000000 = 5*64+41 =361
startbit = 0
}
所以这里可以发现,对于attribute
来说,其attribute
位图中 保留了其自身的索引id
接着,我在selinux-policy
的规则文件中,找到了一个属于sysctl_type
的type
:sysctl_irq_t
cat kerntl.te
......
# /proc/sys directory, base directory of sysctls
type sysctl_t, sysctl_type;
......
然后找到这个sysctl_irq_t的信息
crash> search -c sysctl_irq_t
ffff8e9128f23f10: sysctl_irq_t............................................
crash> search ffff8e9128f23f10
ffff8e912642d978: ffff8e9128f23f10
ffff8e912e29b7b8: ffff8e9128f23f10
crash> p * (struct hashtab_node *) 0xffff8e912642d978
$1 = {
key = 0xffff8e9128f23f10,
datum = 0xffff8e9128f233c0,
next = 0xffff8e912642d438
}
crash> p * (struct type_datum *) 0xffff8e9128f233c0
$2 = {
value = 1272,
bounds = 0,
primary = 1 '\001',
attribute = 0 '\000'
}
发现sysctl_irq_t
的type id
为1272
然后查看type
对于的attributex
信息
p *(struct ebitmap*)&selinux_ss.policydb.type_attr_map_array.parts[4].elements[(1271-4*256)*16]
$3 = {
node = 0xffff8e913233ec40,
highbit = 2304
}
crash> p *(struct ebitmap_node * ) 0xffff8e913233ec40
$4 = {
next = 0xffff8e913233ea80,
maps = {0, 0, 0, 0, 0, 1099511627776},
startbit = 0
}
这里的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
信息,也能得到同样的结果。
# /proc/sys/kernel directory and files
type sysctl_kernel_t, sysctl_type;
crash> search -c sysctl_kernel_t
ffff8e912664eac0: sysctl_kernel_t.8...............................nova_api
crash> search ffff8e912664eac0
ffff8e912664f720: ffff8e912664eac0
ffff8e912e29b7c8: ffff8e912664eac0
crash> p * (struct hashtab_node *) 0xffff8e912664f720
$5 = {
key = 0xffff8e912664eac0,
datum = 0xffff8e912664e8d0,
next = 0xffff8e912664ffa8
}
crash> p * (struct type_datum *) 0xffff8e912664e8d0
$6 = {
value = 1274,
bounds = 0,
primary = 1 '\001',
attribute = 0 '\000'
}
crash> p (char *) 0xffff8e912664eac0
$7 = 0xffff8e912664eac0 "sysctl_kernel_t"
crash> p *(struct ebitmap*)&selinux_ss.policydb.type_attr_map_array.parts[4].elements[(1273-4*256)*16]
$8 = {
node = 0xffff8e913233e700,
highbit = 2304
}
crash> p *(struct ebitmap_node * ) 0xffff8e913233e700
$9 = {
next = 0xffff8e913233ea00,
maps = {0, 0, 0, 0, 0, 1099511627776},
startbit = 0
}
根据上面core_dump的信息如下结论:
type 索引 id
与attribute 索引 id
都保存在avtab
中,使用字段type_datum->attribute
来区分type
与attribute
- SELinux在查询avtab中的规则的时候,本质是查询
attribute
对attribute
的权限。