在《SElinux内核态的实现-avc、avd的设计篇》 ,我们深入了解了SELinux如何使用AVC缓存机制来支撑高效权限检查,包括AVC缓存的的初始化、插入、查找和回收的整个生命周期管理。
本文将着重讲解缓存没有命中的情况下SELinux的处理逻辑,即基于规则文件的权限查找与计算。
同样的,由于avc_has_perm
内涉及内容过于庞大,本文将仅仅介绍规则库实现部分
什么规则库,SELinux如何保存规则文件
既然有SELinux是管控工具,则其管控肯定是基于一定规则放行或者拒绝的。存放放行和拒绝的规则的内存,我称为规则库,而基于条件的规则库,则称为条件规则库(也就是可以通过bool来打开和关闭的规则)。
SELinux的规则库存放在policydb->te_avtab
,条件规则库存放在policydb->te_cond_avtab
struct policydb {
...
struct avtab te_avtab;
struct avtab te_cond_avtab;
...
规则库
规则库的组成
规则库类型avtab
是一个柔性数组,柔性数组可以简单理解为一个长度可变的数组,用于存放总量不固定的数据。
内核提供了
flex_array_put_ptr(fa, nr, src, gfp)
将src的地址保存在柔性数组中的的下标为nr的位置#define flex_array_put_ptr(fa, nr, src, gfp) flex_array_put(fa, nr, (void *)&(src), gfp)
同时提供
flex_array_get_ptr(fa, nr)
来获取柔性数组中的的下标为nr的位保存的地址。
struct avtab {
struct flex_array *htable;
u32 nel; /* 柔性数组内元素长度 */
u32 nslot; /* 柔性数组内哈希链表数量 */
u32 mask; /* 计算哈希值使用 */
};
柔性数组的每个节点为哈希表中某个哈希链表的头节点,每个节点的的内容为strcut avtab_node
strcut avtab_node
以及其内部元素avtab_key
avtab_datum
定义如下:
struct avtab_node {
struct avtab_key key;
struct avtab_datum datum;
struct avtab_node *next;
};
struct avtab_key {
u16 source_type; /* source type */
u16 target_type; /* target type */
u16 target_class; /* target object class */
u16 specified; /* what field is specified */
};
struct avtab_datum {
union {
u32 data; /* access vector or type value */
struct avtab_extended_perms *xperms;
} u;
};
类型 | 名称 | 含义 |
---|---|---|
u16 | source_type | 源SID |
u16 | target_type | 目标SID |
u16 | target_class | CLASS ID |
u16 | specified | 规则类型 |
这里着重需要解析下specified
specified
用于备注此规则的用途与限制。
#define AVTAB_ALLOWED 0x0001
#define AVTAB_AUDITALLOW 0x0002
#define AVTAB_AUDITDENY 0x0004
#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY)
#define AVTAB_TRANSITION 0x0010
#define AVTAB_MEMBER 0x0020
#define AVTAB_CHANGE 0x0040
#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE)
/* extended permissions */
#define AVTAB_XPERMS_ALLOWED 0x0100
#define AVTAB_XPERMS_AUDITALLOW 0x0200
#define AVTAB_XPERMS_DONTAUDIT 0x0400
#define AVTAB_XPERMS (AVTAB_XPERMS_ALLOWED | \
AVTAB_XPERMS_AUDITALLOW | \
AVTAB_XPERMS_DONTAUDIT)
#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */
主要提供了以下标志:
- 本规则的datum保存的是权限检查结果:
#define AVTAB_ALLOWED 0x0001
#define AVTAB_AUDITALLOW 0x0002
#define AVTAB_AUDITDENY 0x0004
#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY)
- 本规则的datum保存的是type转换的结果:
#define AVTAB_TRANSITION 0x0010
#define AVTAB_MEMBER 0x0020
#define AVTAB_CHANGE 0x0040
#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE)
- 本规则的datum保存的是扩展权限检查结果:
/* extended permissions */
#define AVTAB_XPERMS_ALLOWED 0x0100
#define AVTAB_XPERMS_AUDITALLOW 0x0200
#define AVTAB_XPERMS_DONTAUDIT 0x0400
#define AVTAB_XPERMS (AVTAB_XPERMS_ALLOWED | \
AVTAB_XPERMS_AUDITALLOW | \
AVTAB_XPERMS_DONTAUDIT)
- 是否启用当前规则结果,主要用于条件规则库:
#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */
#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */
在这里我们可以知晓,规则库中除了表示权限规则外,还保存了type转换的规则。
验证
来看是不是胡扯,我们直接按照上述规则来分析core_dump内存的某个avtab_node信息,并查看是否有实际的配置规则。
这里随便找个avtab
crash> p **(struct avtab_node **)&(selinux_ss.policydb.te_avtab.htable.parts.elements)
$1 = {
key = {
source_type = 2081,
target_type = 4301,
target_class = 2,
specified = 16
},
datum = {
u = {
data = 2482,
xperms = 0x9b2
}
},
next = 0xffff8e91275e5270
}
继续往下找到了一个同sid 、tid、class id的规则
crash> p *(struct avtab_node *)0xffff8e91275e5270
$2 = {
key = {
source_type = 4122,
target_type = 3281,
target_class = 12,
specified = 1
},
datum = {
u = {
data = 262676,
xperms = 0x40214
}
},
next = 0xffff8e91266e8570
}
crash> p *(struct avtab_node *) 0xffff8e91266e8570
$3 = {
key = {
source_type = 4644,
target_type = 1358,
target_class = 7,
specified = 16
},
datum = {
u = {
data = 4646,
xperms = 0x1226
}
},
next = 0xffff8e91266e8798
}
可以发现其sid= 4644 ,tid=1358,class id =7,有两条avtab规则。
区别为一个specified为1,表示为AVTAB_ALLOWED
即allow规则。
一个specified为16,即AVTAB_TRANSITION
表示为标签转换
验证标签转换规则是否正确
首先看标签转换的规则
crash> p *(struct avtab_node *) 0xffff8e91266e8570
$3 = {
key = {
source_type = 4644,
target_type = 1358,
target_class = 7,
specified = 16
},
datum = {
u = {
data = 4646,
xperms = 0x1226
}
},
next = 0xffff8e91266e8798
}
翻译过来就是id=4644的标签 对 id=1358 的标签 的 class id = 7 操作时候将会切换到 id=4646 的标签
首先看看id 为 7 的class是file
crash> p *selinux_ss.policydb.class_val_to_struct[6]
$43 = {
...
value = 7,
comkey = 0xffff8e91331dc780 "file",
...
}
然后看看id 为 4644的 type为 ifconfig_t
crash> p **(struct type_datum** )&selinux_ss.policydb.type_val_to_struct_array.parts[9].elements[35*8]
$93 = {
value = 4644,
bounds = 0,
primary = 1 '\001',
attribute = 0 '\000'
}
4644 -> ifconfig_t
crash> p *(char**)&selinux_ss.policydb.sym_val_to_name[3].parts[9].elements[35*8]
$96 = 0xffff8e91285d72e0 "ifconfig_t"
然后看看id 为1358的 type为 var_run_t
crash> p **(struct type_datum** )&selinux_ss.policydb.type_val_to_struct_array.parts[1358/512].elements[1358%512*8-8]
$101 = {
value = 1358,
...
}
crash> p *(char**)&selinux_ss.policydb.sym_val_to_name[3].parts[1358/512].elements[1358%512*8-8]
$102 = 0xffff8e912ad59f70 "var_run_t"
然后看看id 为4646的 type为 ifconfig_var_run_t
crash> p **(struct type_datum** )&selinux_ss.policydb.type_val_to_struct_array.parts[9].elements[37*8]
$104 = {
value = 4646,
......
}
crash> p *(char**)&selinux_ss.policydb.sym_val_to_name[3].parts[9].elements[37*8]
$105 = 0xffff8e912ad5b760 "ifconfig_var_run_t"
所以 *(struct avtab_node *) 0xffff8e91266e8570
翻译过来就是
type_transition ifconfig_t var_run_t:file ifconfig_var_run_t
我们找下selinux-policy规则文件内是否有这条语句。
经过多次接口调用files_pid_filetrans
-> filetrans_pattern
-> filetrans_pattern
发现规则文件中确实生成了语句
type_transition ifconfig_t var_run_t:file ifconfig_var_run_t;
规则库的初始化 avtab_alloc
int avtab_alloc(struct avtab *h, u32 nrules)
{
u32 mask = 0;
u32 shift = 0;
u32 work = nrules;
u32 nslot = 0;
if (nrules == 0)
goto avtab_alloc_out;
while (work) {
work = work >> 1;
shift++;
}
if (shift > 2)
shift = shift - 2;
nslot = 1 << shift;
if (nslot > MAX_AVTAB_HASH_BUCKETS)
nslot = MAX_AVTAB_HASH_BUCKETS;
mask = nslot - 1;
h->htable = flex_array_alloc(sizeof(struct avtab_node *), nslot,
GFP_KERNEL | __GFP_ZERO);
if (!h->htable)
return -ENOMEM;
avtab_alloc_out:
h->nel = 0;
h->nslot = nslot;
h->mask = mask;
pr_debug("SELinux: %d avtab hash slots, %d rules.\n",
h->nslot, nrules);
return 0;
}
输入参数:
struct avtab *h
: 指向AVTable结构的指针,该结构需要被分配内存。
u32 nrules
: 需要存储的检查结果数量。
计算哈希桶数量:
首先,通过移位操作确定需要多少位来表示nrules
(结果保存在shift变量),然后在使用shift
来通过(1<< shift - 2)的方式得到哈希表中哈希链表(nslot)的数量并。确保nslot
不超过预定义的最大值MAX_AVTAB_HASH_BUCKETS
,以防止过度分配。
最后计算mask,用于快速定位哈希链表头节点,它是nslot减1的结果。
分配内存与初始化:
使用flex_array_alloc动态分配内存,成功分配内存后,
初始化AVTable结构的一些成员:
- nel(当前已有的条目数)设为0,
- nslot和mask分别设置为之前计算的哈希链表数量和掩码。
规则库的插入
static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum)
{
int hvalue;
struct avtab_node *prev, *cur, *newnode;
u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD);
if (!h || !h->htable)
return -EINVAL;
hvalue = avtab_hash(key, h->mask);
for (prev = NULL, cur = flex_array_get_ptr(h->htable, hvalue);
cur;
prev = cur, cur = cur->next) {
if (key->source_type == cur->key.source_type &&
key->target_type == cur->key.target_type &&
key->target_class == cur->key.target_class &&
(specified & cur->key.specified)) {
/* extended perms may not be unique */
if (specified & AVTAB_XPERMS)
break;
return -EEXIST;
}
if (key->source_type < cur->key.source_type)
break;
if (key->source_type == cur->key.source_type &&
key->target_type < cur->key.target_type)
break;
if (key->source_type == cur->key.source_type &&
key->target_type == cur->key.target_type &&
key->target_class < cur->key.target_class)
break;
}
newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum);
if (!newnode)
return -ENOMEM;
return 0;
}
这部分代码比较简单,查询是否已有数据(不匹配AVTAB_ENABLED|AVTAB_ENABLED_OLD
因为这两个值运行中可能会变动,导致与原始数据不一致),如果找到则报错,没有则新建.
只是其匹配过程进行了查询的优化,由于规则库内数据是按照ID递增的,比如下面这个权限规则链表可以发现source id是递增的
crash> p **(struct avtab_node **)&(selinux_ss.policydb.te_avtab.htable.parts[1].elements)
$170 = {
key = {
source_type = 1859,
target_type = 1858,
},
......
next = 0xffff8e912814b6f0
}
crash> p *(struct avtab_node * ) 0xffff8e912814b6f0
$171 = {
key = {
source_type = 2424,
target_type = 580,
},
......
next = 0xffff8e9126667ea0
}
crash> p *(struct avtab_node * ) 0xffff8e9126667ea0
$172 = {
key = {
source_type = 2475,
target_type = 2488,
}
......
next = 0x0
}
所以可以通过
if (key->source_type < cur->key.source_type)
break;
if (key->source_type == cur->key.source_type &&
key->target_type < cur->key.target_type)
break;
if (key->source_type == cur->key.source_type &&
key->target_type == cur->key.target_type &&
key->target_class < cur->key.target_class)
break;
在查找的id小于规则库内的id的时候快速跳出,找到其按照递增规则处于的最高的位置,便于avtab_insert_node
插入。
规则库的删除
这里代码也很简单,遍历链表并删除所有节点。
void avtab_destroy(struct avtab *h)
{
int i;
struct avtab_node *cur, *temp;
if (!h || !h->htable)
return;
for (i = 0; i < h->nslot; i++) {
cur = flex_array_get_ptr(h->htable, i);
while (cur) {
temp = cur;
cur = cur->next;
if (temp->key.specified & AVTAB_XPERMS)
kmem_cache_free(avtab_xperms_cachep,
temp->datum.u.xperms);
kmem_cache_free(avtab_node_cachep, temp);
}
}
flex_array_free(h->htable);
h->htable = NULL;
h->nslot = 0;
h->mask = 0;
}
规则库的查询
规则库查询逻辑需要与type的查找逻辑一起,所以后面在type查找逻辑中一起讲解。