文章目录
前言
本文适合对SElinux已经有一定了解,能够按需实现.te、.if、.fc文件,对内核中SElinux相关机制实现感兴趣的同事。
此外,本文实现基于4.19内核版本,且本人工作中未使用mls。也将跳过mls代码解析的实现。
SELinux内核态简介
SELinux在内核中分为检查部分以及数据库部分。
检查部分也就是我们熟悉的lsm hook机制
Linux Security Modules (LSM) 是Linux内核中的一种框架,用于为操作系统添加可插拔的安全策略。这一机制允许开发者和系统管理员通过插入自定义的安全模块来增强系统的安全性,而不必直接修改内核代码。LSM主要关注访问控制,以防范未授权的活动。
LSM的工作原理围绕“钩子”(hooks)展开。这些钩子是内核中预定义的插入点,分布在各种敏感操作上,比如文件访问、进程创建和信号处理等。每当内核执行这些操作时,都会暂停去调用LSM注册的钩子函数,让安全模块有机会根据其策略来决定是否放行或拒绝请求。
安全模块通过实现这些钩子接口,可以在操作执行前后介入,执行额外的权限验证或审计。例如,在文件打开操作期间,模块可以通过钩子检查请求者是否有权访问目标文件。如果模块判断访问不应被允许,它可以返回一个错误,从而阻止操作。
值得注意的是,LSM是静态编入内核的,意味着要在系统中使用特定的LSM,必须在编译内核时将其包含进去,且同一时间系统只能激活一个LSM模块。这保证了安全策略的统一性和效率,但要求对安全需求有明确的预先规划。
数据库部分则保存的是我们检查过程中需要使用的数据,比如selinux标签对应的编号,class对应的权限类等。
本文章对检查部分涉及较少,有以下两个原因:
一来检查部分除了一个初始化宏较难理解外,其余部分代码部分读懂难度不大。主要难点是在为什么在这里设置检查点以及检查点检查的内容是什么。而这两点更多考察的是对内核各个功能模块实现机制的了解,和安全模块本地检查功能关系不大。
二是如果要细讲,需要将前置技能点《linux内核功能的实现》一一点亮,这与SElinux本身功能实现关联较小。
所以检查部分这块不作为本文重点。本文还是以SElinux数据库部分实现为主。
SELinux数据库的实现
SElinux数据库的核心结构体是selinux_state
与 policydb
。前者主要保存的是sid与安全上下文的映射关系、检查结果的缓存,policydb主要保存的是检查规则。这些概念看不懂没关系,后续会详细解释这些内容。
不过在此之前,我们需要先了解SELinux数据库结构设计中保存各个数据的最小结构单元:安全上下文、class、
安全上下文
我们应当知道,SELinux的管控是基于扩展属性的标签实现的。其标签格式如下
/var/tinydns/env(/.*)? all files system_u:object_r:svc_conf_t:s0
这4个字段分别表示user、role、type以及mls级别,而这些元素的组合一起就是安全上下文,所以user_u:role_r:type_t:s0
就是一个安全上下文。
SElinux使用结构体为strcut context
保存安全上下文,定义如下
/*
* A security context consists of an authenticated user
* identity, a role, a type and a MLS range.
*/
struct context {
u32 user;
u32 role;
u32 type;
u32 len; /* length of string in bytes */
struct mls_range range;
char *str; /* string representation if context cannot be mapped. */
};
这里其中char str表示这个安全上下文的,也就是上文中的user_u:role_r:type_t:s0
,而len很明显是这个字符串的长度。
在context_struct_to_string
我们可以了解SELinux对context->str
的用法
static int context_struct_to_string(struct policydb *p,
struct context *context,
char **scontext, u32 *scontext_len)
{
char *scontextp;
if (scontext)
*scontext = NULL;
*scontext_len = 0;
// 如果安全上下文有设置长度,则直接返回context->str
if (context->len) {
*scontext_len = context->len;
if (scontext) {
*scontext = kstrdup(context->str, GFP_ATOMIC);
if (!(*scontext))
return -ENOMEM;
}
return 0;
}
// 否则则拼接各个字符串合成完整上下文
...
}
其次,我们观察struct context
可以发现role
,type
,user
,len
的类型是u32,所以我们可以合理推测两点
- SELinux保存
role
,type
,user
,len
是通过id保存的。 - 用户空间拿到规则te文件后,编译阶段会给这些
role
,type
,user
,len
来分配id。
sid
既然role
,type
,user
,len
是通过id来保存在内核中的,那么对于role
,type
,user
,len
,即安全上下文是不是也可以通过id来保存的呢。
结果是必然的。这个id在SELinux中被称为sid。这里以邮政编码,假设role是湖北省、type是宜昌市、user是夷陵区,那么那么实际的上下文对于邮编地址就是湖北省宜昌市夷陵区,就是sid对应的就是身份证前6位:420505
role:type:user:mls --> sid
湖北省宜昌市夷陵区 --> 420505(身份证号前6位)
这个sid与context上下文映射保存在selinux_ss->sidtab->sidtab哈希表中。此外,为了快速查询,SElinux还会将查询结果缓存,这份缓存保存在selinux_ss->sidtab->cache中。
根据SELinux生成sid部分的代码(sidtab_context_to_sid
函数),可以发现这个sid在系统每次运行时候是不一样的。也就是说,sid与安全上下文的映射关系是实时生成的,当某个安全上下文首次需要被检查时候,SELinux将会给其分配一个sid,然后将这个映射关系保存在sidtab->sidtab哈希表中,接着更新sidtab->cache缓存表。
同理,查询的时候也是先查询sidtab->cache,然后查询sidtab->sidtab,如果还找不到,则给这个安全上下文分配一个新的sid。
具体查询实现代码如下
int sidtab_context_to_sid(struct sidtab *s,
struct context *context,
u32 *out_sid)
{
u32 sid;
int ret = 0;
unsigned long flags;
*out_sid = SECSID_NULL;
// 从sidtab->cache中查询context的sid
sid = sidtab_search_cache(s, context);
if (!sid)
// 从sidtab->sidtab中查询context的sid
sid = sidtab_search_context(s, context);
if (!sid) {
spin_lock_irqsave(&s->lock, flags);
/* Rescan now that we hold the lock. */
sid = sidtab_search_context(s, context);
if (sid)
goto unlock_out;
/* No SID exists for the context. Allocate a new one. */
if (s->next_sid == UINT_MAX || s->shutdown) {
ret = -ENOMEM;
goto unlock_out;
}
// 分配新sid
sid = s->next_sid++;
if (context->len)
pr_info("SELinux: Context %s is not valid (left unmapped).\n",
context->str);
ret = sidtab_insert(s, sid, context);
if (ret)
s->next_sid--;
unlock_out:
spin_unlock_irqrestore(&s->lock, flags);
}
if (ret)
return ret;
*out_sid = sid;
return 0;
}
sid与安全上下文映射关系的保存
sid与上下文的映射关系保存在struct sidtab
(&state->ss->sidtab
)中.
struct selinux_ss {
struct sidtab sidtab;
struct policydb policydb;
rwlock_t policy_rwlock;
u32 latest_granting;
struct selinux_map map;
struct page *status_page;
struct mutex status_lock;
};
class与permission
到这里,我们已经了解sid的实现,也就是说,内核的SELinux部分这时候已经能获得源目文件的标签(假设是文件访问文件的权限检查),并成功的将标签转换为了一个唯一的sid。
现在SELinux如果要进行源目权限检查的话,还缺少一个源对目的所执行的操作(permission)。
这句话怎么具体解释呢,比如我现在知晓了进程A的需要访问文件B,而且已经知晓了进程A和文件B的sid。那么我还需要知晓进程A具体需要对文件B做什么操作。是写入还是读取还是ioctl还是mmap等。
而这里的“写入”还是“读取”或者 ioctl
亦或 mmap
这个动作在SElinux中被称为permission(操作),而一系列的操作(permission)的集合,在SELinux中被称为class(类)。
为什么会有集合这个概念呢:
- 逻辑分组:
class
将相似的permissions组织在一起,形成逻辑上的分类。这样做可以让安全策略更加清晰和易于理解。例如,所有与文件操作相关的权限(如读、写、执行)被归类在file
class下,网络操作的权限可能归在另一个class下。这样,管理员在编写或审查策略时,能更快地把握权限分配的逻辑结构。- 策略一致性与复用: 通过class,可以确保相同类型的操作在不同上下文中具有一致的处理方式。当需要调整或扩展权限时,只需修改或增加class内的permissions,而不需要逐个修改涉及这些权限的规则,提高了策略的一致性和复用性。
- 简化表达和解析: 在SELinux策略规则中,使用
class
作为permissions的载体,使得规则表达更为简洁。规则通常采用allow source target:class permission
的形式,这样的结构便于解析器快速识别并处理权限请求,也便于人类阅读和理解。
当源目上下文加上class,我们拿到了一个完整的规则检查需求明细:
源(安全上下文) -> 目的(安全上下文):class(类) 的某些具体permission(操作)。
现在我们可以来对这个明细做检查了:在前面我们已经知晓了如何将安全上下文是转换为sid。现在我们需要知晓如何将class转换为对应的id。
至于怎么检查的,权限检查章节会详细讲解。
这里我们只需要知晓:对于SELinux来说,权限的检查的入参全是一个个id即可。
class的实现
前面我们已经了解,“class” 代表了一类资源或操作(permissions)的集。
在SELinux中,class
的存储被分为了两部分:
- 被多个 class 公用的 class 保存在 symtab[SYM_COMMONS] 中
- class 自己私有权限 class保存在 symtab[SYM_CLASSES] 中。
而这个 symtab[] 数组,则保存在 policydb->symtab[SYM_NUM] 中,同时 SELinux 提供了两个宏来指向此 symtab[SYM_COMMONS] 和symtab[SYM_CLASSES]
SELinux总共提供了SYM_NUM个symtab,每个sytab的含义可以通过如下的宏定义来窥探一二
我这里只关心p_commons 与 p_classes 至于这个组内的其他内容,将在需要讲解的时候分析/* symbol tables */ struct symtab symtab[SYM_NUM]; #define p_commons symtab[SYM_COMMONS] #define p_classes symtab[SYM_CLASSES] #define p_roles symtab[SYM_ROLES] #define p_types symtab[SYM_TYPES] #define p_users symtab[SYM_USERS] #define p_bools symtab[SYM_BOOLS] #define p_levels symtab[SYM_LEVELS] #define p_cats symtab[SYM_CATS] #define p_commons symtab[SYM_COMMONS] #define p_classes symtab[SYM_CLASSES]
首先, symtab 内包含的是一个哈希表+节点数量,
struct symtab {
struct hashtab *table; /* hash table (keyed on a string) */
u32 nprim; /* number of primary names in table */
};
struct hashtab {
struct hashtab_node **htable; /* hash table 哈希表*/
u32 size; /* number of slots in hash table 哈希表内slots数量,即哈希链表数量 */
u32 nel; /* number of elements in hash table 哈希表内元素数量 */
u32 (*hash_value)(struct hashtab *h, const void *key);
/* hash function */
int (*keycmp)(struct hashtab *h, const void *key1, const void *key2);
/* key comparison function */
};
哈希表内存放的元素(内容)为hashtab_node
struct hashtab_node {
void *key;
void *datum;
struct hashtab_node *next;
};
symtab的结构如下
而在不同的symtab
中,key
和datum
的含义均不一样。 当讲解具体symtab
的时候再来详细分解其key
和datum
的含义
公用class
在 p_commons
(公用class)中,这个哈希表的key
和datum
含义如下:
- key等于公共class的名字,是一个字符串,比如“socket”
- datum 内保存的一个指向结构体
struct common_datum
的指针
struct common_datum
的结构如下:
/* Attributes of a common prefix for access vectors */
struct common_datum {
u32 value; /* internal common value */
struct symtab permissions; /* common permissions */
};
u32 value
表示给此class
的idpermissions
表示此class
含有的所有操作(permission),
这里的变量permissions可以发现其类型是struct symtab
,所以permissions变量内保存的又是一个哈希表,这也与一个class
内包含多个permission
的特性相符
datum->permissions这个哈希表中元素中的key
和datum
的含义如下
- key等于操作(permissions)的名字,是一个字符串
datum
是结构体struct perm_datum
,里面内容很简单,就是一个u32的二进制。表示此操作(permissions)在其class的编号id
/* Permission attributes */
struct perm_datum {
u32 value; /* permission bit + 1 */
};
其结构如下
这样SELinux通过结构体struct common_datum
与结构体struct perm_datum
就将class与操作(permissions)结合在一起。其中公用class以下内容:
一级名称 | 二级名称 | 变量类型 |
---|---|---|
公用class名称 | - | 字符串 |
公用class的id | - | u32 |
此class包含的所有permissions(操作) | permission的名称 | 字符串 |
- | permission在此class的id | u32 |
公用class的完整结构体布局如下
私有class
在 p_classes(私有class)中,这个哈希表的key
和datum
含义如下:
- key等于私有class的名字,是一个字符串,比如“ioctl”
- datum 内保存的一个指向结构体
struct class_datum
的指针
struct class_datum
的结构如下:
/* Class attributes */
struct class_datum {
u32 value; /* class value */
char *comkey; /* common name */
struct common_datum *comdatum; /* common datum */
struct symtab permissions; /* class-specific permission symbol table */
struct constraint_node *constraints; /* constraints on class permissions */
struct constraint_node *validatetrans; /* special transition rules */
/* Options how a new object user, role, and type should be decided */
#define DEFAULT_SOURCE 1
#define DEFAULT_TARGET 2
char default_user;
char default_role;
char default_type;
/* Options how a new object range should be decided */
#define DEFAULT_SOURCE_LOW 1
#define DEFAULT_SOURCE_HIGH 2
#define DEFAULT_SOURCE_LOW_HIGH 3
#define DEFAULT_TARGET_LOW 4
#define DEFAULT_TARGET_HIGH 5
#define DEFAULT_TARGET_LOW_HIGH 6
char default_range;
};
u32 value
表示给此class
的idcomkey
表示此class
依赖的公共class的名称- comdatum 指针指向结构体
common_datum
的地址,此地址内保存了依赖的公用class实际内容。 - permissions 保存了除了依赖的
公共class
外的其他的permission
(操作) - constraint_node 保存了此class的约束信息
- validatetrans 是此class转换的约束信息
- default_user、default_role、default_type、default_range 各个类型默认id
私有class包含了以下内容:
一级名称 | 二级名称 | 变量类型 |
---|---|---|
class名称 | - | 字符串 |
class id | - | u32 |
依赖的公用class名称 | - | 字符串 |
存放依赖公用class内容的结构体地址 | 指针 | |
此class包含的所有permissions(操作) | permission的名称 | 字符串 |
- | permission在此class的id | u32 |
class约束信息、转换约束信息 | 指针 | |
class默认role、user、type、mls | u32 |
好的,现在通过p_common
以及p_class
,我们可以拿到某个class含有的所有permissions的id了。
但是很明显,这种通过哈希表保存的数据更适合查询的是字符串和id的映射关系,那么如果想快速查询某个class下的所有permission应该怎么做呢。
class与permission的关联–selinux_mapping
SELinux采用struct selinux_mapping
来保存class id与permission id的映射关系
/* Mapping for a single class */
struct selinux_mapping {
u16 value; /* policy value for class */
unsigned int num_perms; /* number of permissions in class */
u32 perms[sizeof(u32) * 8]; /* policy values for permissions */
};
- value : class的id
- num_perms : class拥有的permission数量
- perms[] : permission 的id ,class 最多有32个permission
而这些selinux_mapping
又被集中保存在selinux_map
中,
/* Map for all of the classes, with array size */
struct selinux_map {
struct selinux_mapping *mapping; /* indexed by class */
u16 size; /* array size of mapping */
};
- *mapping :一个指针数组,长度为
array_size(secclass_map[x].name)
- size :class的总数
selinux_mapping的初始化
SElinux对selinux_map的初始化函数为selinux_set_mapping
在初始化阶段SELinux将通过依次遍历字典 struct security_class_mapping secclass_map[]
/*
* Note: The name for any socket class should be suffixed by "socket",
* and doesn't contain more than one substr of "socket".
*/
struct security_class_mapping secclass_map[] = {
{ "security",
{ "compute_av", "compute_create", "compute_member",
"check_context", "load_policy", "compute_relabel",
"compute_user", "setenforce", "setbool", "setsecparam",
"setcheckreqprot", "read_policy", "validate_trans", NULL } },
{ "process",
{ "fork", "transition", "sigchld", "sigkill",
"sigstop", "signull", "signal", "ptrace", "getsched", "setsched",
"getsession", "getpgid", "setpgid", "getcap", "setcap", "share",
"getattr", "setexec", "setfscreate", "noatsecure", "siginh",
"setrlimit", "rlimitinh", "dyntransition", "setcurrent",
"execmem", "execstack", "execheap", "setkeycreate",
"setsockcreate", "getrlimit", NULL } },
...
}
获得每一个class的所有permission的名称,然后将permission的名称作为入参查询p_common
与p_classes
得到permission的id,将id保存在通过位移的方式转换为位图,保存在selinux_mapping->perm[k]中,有点绕没关系,这里以实际某次code_dump的内存为例。来用实际内存分配的地址来解析
首先我们梳理下SElinux初始化class和permission的流程:
-
首先,SELinux将从用户空间读取policy规则文件,生成p_common公共class与p_classes私有class(如何生成的将起来有点枯燥,我放在本文的最后部分class的加载中解析)
-
然后,SELinux将解析字典secclass_map[],获取每个class的名称以及其包含的所有的permissions的名称。
-
接着,SELinux将class名称以及其permission的名称作为入参,查询p_common、p_classes,获得每个class的id与permission的id,
-
最后保存class id 与 permission id的映射关系
- 将class的id存放在
selinux_mapping
->value
中。 - 然后将生成一个
1U << permission的id
的二进制,保存在selinux_mapping
->perms[k]
中。k为secclass_map中某个class的permission的序号。
- 将class的id存放在
这里以class socket为实际的例子。我们来看看内核是如何存储 class socket的。
同时 SELinux也提供了用户空间的查询class信息的方式
[root@localhost ~]# ls /sys/fs/selinux/class/
alg_socket chr_file fifo_file llc_socket netlink_netfilter_socket perf_event socket x_drawable
appletalk_socket context file lnk_file netlink_nflog_socket phonet_socket sock_file x_event
association db_blob filesystem memprotect netlink_rdma_socket pppox_socket system x_extension
atmpvc_socket db_column ib_socket mpls_socket netlink_route_socket process tcp_socket x_font
atmsvc_socket db_database icmp_socket msg netlink_scsitransport_socket process2 tipc_socket x_gc
ax25_socket db_language ieee802154_socket msgq netlink_selinux_socket proxy tun_socket x_keyboard
binder db_procedure infiniband_endport netif netlink_socket qipcrtr_socket udp_socket x_pointer
blk_file db_schema infiniband_pkey netlink_audit_socket netlink_tcpdiag_socket rawip_socket unix_dgram_socket x_property
bluetooth_socket db_sequence ipc netlink_connector_socket netlink_xfrm_socket rds_socket unix_stream_socket x_resource
bpf db_table ipx_socket netlink_crypto_socket netrom_socket rose_socket vsock_socket x_screen
bridge_socket db_tuple irda_socket netlink_dnrt_socket nfc_socket rxrpc_socket x25_socket x_selection
caif_socket dbus isdn_socket netlink_fib_lookup_socket node sctp_socket x_application_data x_server
can_socket db_view iucv_socket netlink_firewall_socket nscd security x_client x_synthetic_event
cap2_userns dccp_socket kcm_socket netlink_generic_socket packet sem x_colormap
capability decnet_socket kernel_service netlink_ip6fw_socket packet_socket service x_cursor
capability2 dir key netlink_iscsi_socket passwd shm x_device
cap_userns fd key_socket netlink_kobject_uevent_socket peer smc_socket xdp_socket
可以直接访问查看class的id以及其perm的id
[root@localhost ~]# cat /sys/fs/selinux/class/file/index
6[root@localhost ~]# ls /sys/fs/selinux/class/file/perms/
append entrypoint execute_no_trans link mounton read rename unlink
audit_access execmod getattr lock open relabelfrom setattr write
create execute ioctl map quotaon relabelto swapon
[root@localhost ~]# cat /sys/fs/selinux/class/file/perms/append
10[root@localhost ~]#
class查询的优化
除了查询class所含有的permissions外,SElinux还会经常访问指定class id的信息,这种场景下,查询的结果一般有两种:
- 一是基于class id ,查询其结构体信息,即其结构体class_datum内存地址。
- 二是基于class id, 查询class名称。
同样selinux页提供了这两种场景下的快捷查询方式。
基于class id 查询其结构体class_datum内存地址
SELinux提供了一个指针数组 class_val_to_struct
来协助在知晓class的id的情况下,快速查找存放其信息的结构体的内存地址的场景。
其中,class 的id为指针数组 class_val_to_struct
的下标。以 class socket的id为5,那么*class_val_to_struct
[4] 指向的即是保存socket这个class信息的结构体内存地址
基于class id 查询 class的名字
selinux提供了一个struct flex_array sym_val_to_name[]
方便通过tclass,也就是class的name找到class结构体的内存地址。
sym_val_to_name[SYM_COMMONS]是用于存放公共class的快速查询。
sym_val_to_name[SYM_CLASSES]是用于存放私有class的快速查询。
sym_val_to_name是一个柔性数组,可以简单理解为一个长度可变的数组,用于存放总量不固定的数据。
内核提供了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的位保存的地址。
在flex_array_get_ptr
的基础上,SELinux封装了sym_name
函数来快速获取指定内容的指定的下标指向的地址
static inline char *sym_name(struct policydb *p, unsigned int sym_num, unsigned int element_nr)
{
struct flex_array *fa = p->sym_val_to_name[sym_num];
return flex_array_get_ptr(fa, element_nr);
}
class的加载
SELinux将从用户空间的policy文件中读取已经编译好的class信息。实现的函数为policydb_read
, 这里我们只关心p_common公共class与p_classes私有class
/*
* Read the configuration data from a policy database binary
* representation file into a policy database structure.
*/
int policydb_read(struct policydb *p, void *fp)
{
...
info = policydb_lookup_compat(p->policyvers); // 通过版本号拿到此版本的数据结构信息
...
for (i = 0; i < info->sym_num; i++) { // 通过版本号拿到此版本symtab数量,这里我们只关心SYM_COMMONS和SYM_CLASSES,也就是p_common与p_classes
rc = next_entry(buf, fp, sizeof(u32)*2);
if (rc)
goto bad;
nprim = le32_to_cpu(buf[0]); // 此symtab中的内容长度,对于p_common与p_classes,表示的是含有的class数量
nel = le32_to_cpu(buf[1]); // 对于p_common与p_classes 此值为1
for (j = 0; j < nel; j++) {
rc = read_f[i](p, p->symtab[i].table, fp); // 使用read_f[i],这个函数指针数组的第i个函数要处理read请求
if (rc)
goto bad;
}
p->symtab[i].nprim = nprim;
}
...
}
read_f
加载函数指针数组如下
static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) =
{
common_read,
class_read,
role_read,
type_read,
user_read,
cond_read_bool,
sens_read,
cat_read,
};
填充 common(公共class)
static int common_read(struct policydb *p, struct hashtab *h, void *fp)
{
char *key = NULL;
struct common_datum *comdatum;
__le32 buf[4];
u32 len, nel;
int i, rc;
comdatum = kzalloc(sizeof(*comdatum), GFP_KERNEL);
if (!comdatum)
return -ENOMEM;
//读取4个32位长度
rc = next_entry(buf, fp, sizeof buf);
if (rc)
goto bad;
//
len = le32_to_cpu(buf[0]);
// class的名称
comdatum->value = le32_to_cpu(buf[1]);
rc = symtab_init(&comdatum->permissions, PERM_SYMTAB_SIZE);
if (rc)
goto bad;
// class含有的permissions的权限条目数量
comdatum->permissions.nprim = le32_to_cpu(buf[2]);
nel = le32_to_cpu(buf[3]);
// 读取len+1的长度 ,将fp移动到len+1位置,然后将结果赋予key
rc = str_read(&key, GFP_KERNEL, fp, len);
if (rc)
goto bad;
for (i = 0; i < nel; i++) {
// 从fp读取permissions
// table内由n个xx组成, xx第一32bit是字符串长度len,第二位32bit是value值,然后是一个len长度的字符串。填充一个struct hashtab_node 然后通过hashtab_insert插入到comdatum->permissions.table
rc = perm_read(p, comdatum->permissions.table, fp);
if (rc)
goto bad;
}
rc = hashtab_insert(h, key, comdatum);
if (rc)
goto bad;
return 0;
bad:
common_destroy(key, comdatum, NULL);
return rc;
}
填充class表(私有class)
static int class_read(struct policydb *p, struct hashtab *h, void *fp)
{
char *key = NULL;
struct class_datum *cladatum;
__le32 buf[6];
u32 len, len2, ncons, nel;
int i, rc;
cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL);
if (!cladatum)
return -ENOMEM;
// 读取头 6个32bit
rc = next_entry(buf, fp, sizeof(u32)*6);
if (rc)
goto bad;
// class名字的长度
len = le32_to_cpu(buf[0]);
// comkey的名字长度
len2 = le32_to_cpu(buf[1]);
// class的编号
cladatum->value = le32_to_cpu(buf[2]);
rc = symtab_init(&cladatum->permissions, PERM_SYMTAB_SIZE);
if (rc)
goto bad;
// class的permission的长度
cladatum->permissions.nprim = le32_to_cpu(buf[3]);
nel = le32_to_cpu(buf[4]);
// calss的约束
ncons = le32_to_cpu(buf[5]);
// 读取class的名称
rc = str_read(&key, GFP_KERNEL, fp, len);
if (rc)
goto bad;
// 是否有公共class
if (len2) {
// 读取公共class的名字
rc = str_read(&cladatum->comkey, GFP_KERNEL, fp, len2);
if (rc)
goto bad;
rc = -EINVAL;
// 找到通过公共class名称找到公共class地址并保存
cladatum->comdatum = hashtab_search(p->p_commons.table, cladatum->comkey);
if (!cladatum->comdatum) {
pr_err("SELinux: unknown common %s\n", cladatum->comkey);
goto bad;
}
}
// 获取私有class的perm 并填充到 permission->table中
for (i = 0; i < nel; i++) {
rc = perm_read(p, cladatum->permissions.table, fp);
if (rc)
goto bad;
}
// 读取class的约束
rc = read_cons_helper(p, &cladatum->constraints, ncons, 0, fp);
if (rc)
goto bad;
// 是否支持validatetrans 支持则载入
if (p->policyvers >= POLICYDB_VERSION_VALIDATETRANS) {
/* grab the validatetrans rules */
rc = next_entry(buf, fp, sizeof(u32));
if (rc)
goto bad;
ncons = le32_to_cpu(buf[0]);
rc = read_cons_helper(p, &cladatum->validatetrans,
ncons, 1, fp);
if (rc)
goto bad;
}
// 是否支持 default object
if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) {
rc = next_entry(buf, fp, sizeof(u32) * 3);
if (rc)
goto bad;
cladatum->default_user = le32_to_cpu(buf[0]);
cladatum->default_role = le32_to_cpu(buf[1]);
cladatum->default_range = le32_to_cpu(buf[2]);
}
// 是否支持 default_type
if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) {
rc = next_entry(buf, fp, sizeof(u32) * 1);
if (rc)
goto bad;
cladatum->default_type = le32_to_cpu(buf[0]);
}
// 插入到
rc = hashtab_insert(h, key, cladatum);
if (rc)
goto bad;
return 0;
bad:
cls_destroy(key, cladatum, NULL);
return rc;
}