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

《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;
};

在这里插入图片描述

类型名称含义
u16source_type源SID
u16target_type目标SID
u16target_classCLASS ID
u16specified规则类型

这里着重需要解析下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查找逻辑中一起讲解。

  • 35
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值