文章目录
  • 前言
  • 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章的内容,但是本文的内容还是有点绕。头疼

不过我们还需要点亮最后一个技能点<typeattribute的实现>后,就可以开始看看SELinux基于规则文件的权限查询是如何实现了。

type和attribute映射关系的实现

首先回顾下typeattribute的定义
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来保存typeattribute映射关系

struct policydb {
......
	struct flex_array *type_attr_map_array;
......
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

其也是一个柔性数组。其整体逻辑如下

SElinux内核态的实现-type和attribute映射关系的实现以及基于规则库的权限检查_映射关系


柔性数组下标为type id -1。 指向的结构体struct ebitmap_node内保存的为typeattribute映射关系的位图(bitmap),

bitmap某位被置为1时,则此位的编号对应的id则为attribute的索引id。比如 某个bitmap的值为000001000101则表示此attribute下,此type拥有attribute 索引id 为 1、3、7的attribute

考虑到typeattribute的长度并非固定的,所以也是使用的可扩展的struct ebitmap_node来保存位图。

同时,SELinux也封装了对这个位图的快速操作:

  • ebitmap_for_each_positive_bit(e, n, bit)
  • 快速遍历 e 的所有被置为1的位,并将此位的编号保证在i中返回。其是ebitmap_start_positiveebitmap_next_positive的封装
  • ebitmap_get_bit(struct ebitmap *e, unsigned long bit)
  • 快速获取位图ebit位的结果,其是ebitmap_node_get_bit的封装
  • ebitmap_set_bit(struct ebitmap *e, unsigned long bit)
  • 快速设置位图ebit位的结果,其是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;
}
  • 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.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.

我们一段一段来分解

首先,获取数据库的读写锁。如果SELinux未初始化完成,则直接放行

read_lock(&state->ss->policy_rwlock);
	avd_init(state, avd);
	xperms->len = 0;
	if (!state->initialized)
		goto allow;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

接着通过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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

然后是获取目标的安全上下文。接着通过传入的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;
	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

接着将通过源安全上下文、目标安全上下文、目标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;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

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);
}
  • 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.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.

相信对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;
	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

接着初始化查询条件,包括:

  • 通过class id 拿到 存放class信息的结构体
  • 通过type_attr_map_array 拿到 scontexttcontext属于的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);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

遍历源attribute目标attribute,找到其下的所有的attribute id

这里返回的是type id以0开始,所以后续使用需要+1

然后两两做积运算,得到source type idtarget type id之间的所有映射。

然后查询规则库内这些映射关系的结果,并将结果通过|运算集中到avd

这里的实现逻辑困扰了我很久,我也查询不少资料。但是始终无法理解这里的查询逻辑.为什么要先获取目标typeattribute,然后查询attributed们间的权限。除非说,avtab的规则是基于attribute而非type
不过我确实尝试在core_dump的实际运行内存中查看了type_attr_map_array的实现,其结果确实是保存的attribute的索引id。也就是说,在规则检查的时候,avtab内保存的本质是attributeattribute的访问权限,具体检查逻辑我附录在了最后附录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);

		}
	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

然后查询约束表

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

如果当前正在检查的是进程类(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;
	}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

最近,检查源目上下文中的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");
}
  • 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.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.

转换的权限检查只检查同层权限

比如某个进程访问某个文件的时候,其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;
	}
}
  • 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.

这样 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'
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

然后,我检查了其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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

所以这里可以发现,对于attribute来说,其attribute位图中 保留了其自身的索引id

接着,我在selinux-policy的规则文件中,找到了一个属于sysctl_typetypesysctl_irq_t

cat kerntl.te
......
# /proc/sys directory, base directory of sysctls
type sysctl_t, sysctl_type;
......
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

然后找到这个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'
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

发现sysctl_irq_ttype 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
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

这里的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_ttype信息,也能得到同样的结果。

# /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
}
  • 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.
  • 40.

根据上面core_dump的信息如下结论:

  • type 索引 idattribute 索引 id都保存在 avtab 中,使用字段type_datum->attribute来区分 typeattribute
  • SELinux在查询avtab中的规则的时候,本质是查询attributeattribute的权限。