文章目录
  • 什么规则库,SELinux如何保存规则文件
  • 规则库
  • 规则库的组成
  • 验证
  • 验证标签转换规则是否正确
  • 规则库的初始化 avtab_alloc
  • 规则库的插入
  • 规则库的删除
  • 规则库的查询

在《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;
	...
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

规则库

规则库的组成

规则库类型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;       /* 计算哈希值使用 */
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

柔性数组的每个节点为哈希表中某个哈希链表的头节点,每个节点的的内容为strcut avtab_node

SElinux内核态的实现-权限检查-规则库avtab篇_#define

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;
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

SElinux内核态的实现-权限检查-规则库avtab篇_#define_02

类型

名称

含义

u16

source_type

源SID

u16

target_type

目标SID

u16

target_class

CLASS ID

u16

specified

规则类型

这里着重需要解析下specifiedspecified用于备注此规则的用途与限制。

#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 */
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

主要提供了以下标志:

  • 本规则的datum保存的是权限检查结果:
#define AVTAB_ALLOWED		0x0001
#define AVTAB_AUDITALLOW	0x0002
#define AVTAB_AUDITDENY		0x0004
#define AVTAB_AV		(AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY)
  • 1.
  • 2.
  • 3.
  • 4.
  • 本规则的datum保存的是type转换的结果:
#define AVTAB_TRANSITION	0x0010
#define AVTAB_MEMBER		0x0020
#define AVTAB_CHANGE		0x0040
#define AVTAB_TYPE		(AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE)
  • 1.
  • 2.
  • 3.
  • 4.
  • 本规则的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)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 是否启用当前规则结果,主要用于条件规则库:
#define AVTAB_ENABLED_OLD   0x80000000 /* reserved for used in cond_avtab */
#define AVTAB_ENABLED		0x8000 /* reserved for used in cond_avtab */
  • 1.
  • 2.

在这里我们可以知晓,规则库中除了表示权限规则外,还保存了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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

继续往下找到了一个同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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

可以发现其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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

翻译过来就是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", 
	...
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

然后看看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"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

然后看看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"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

然后看看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"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

所以 *(struct avtab_node *) 0xffff8e91266e8570翻译过来就是

type_transition ifconfig_t var_run_t:file  ifconfig_var_run_t
  • 1.

我们找下selinux-policy规则文件内是否有这条语句。
经过多次接口调用files_pid_filetrans -> filetrans_pattern -> filetrans_pattern 发现规则文件中确实生成了语句

type_transition ifconfig_t var_run_t:file  ifconfig_var_run_t;
  • 1.

SElinux内核态的实现-权限检查-规则库avtab篇_安全_03

SElinux内核态的实现-权限检查-规则库avtab篇_规则库_04

SElinux内核态的实现-权限检查-规则库avtab篇_安全_05

规则库的初始化 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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.

输入参数:

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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

这部分代码比较简单,查询是否已有数据(不匹配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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

所以可以通过

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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

在查找的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;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.

规则库的查询

规则库查询逻辑需要与type的查找逻辑一起,所以后面在type查找逻辑中一起讲解。