Skip to content

Global Macros

从 Android class 集合、permission 集合、内核 file/socket/capability hook 与策略实例解释 global_macros 的真实授权边界。

基于android-17.0.0_r1
AndroidSELinuxglobal_macrosPermissionSetObjectClassAVC源码阅读

Global Macros ​

本文面向已经读过 客体类别与权限、TE规则语法 和 TE宏库 的读者。上一篇说明 te_macros 如何把 domain、service 与 HAL 等语义参数展开成完整规则;本篇向下一层追踪:global_macros 中的 class 集合与 permission 集合究竟覆盖哪些权限位,这些权限由哪些内核 hook 消费,以及宏名为什么不能代替 operation 分析。

Android 17 的 public/global_macros 只有 56 行,但它被大量平台与 vendor 策略递归引用。本文不会把 56 行重新排版成速查表,而是回答更容易导致错误授权的问题:r_file_perms 为什么包含 ioctl/map/watch,create_file_perms 为什么仍不能在目录中新建文件,create_stream_socket_perms 为什么不包含端口 name_bind/name_connect,class set 为什么不会自动随 security_classes 增长,以及 capability class set 如何区分 init user namespace。

1. 文件边界 ​

1.1 两类集合 ​

global_macros 只定义两类文本集合:object class set 与 permission set。它不接收 domain 参数,不生成 allow/neverallow,也不拥有运行时状态。

源码文件:system/sepolicy/public/global_macros

text
define(`devfile_class_set', `{ chr_file blk_file }')
define(`notdevfile_class_set', `{ file lnk_file sock_file fifo_file }')
define(`file_class_set', `{ devfile_class_set notdevfile_class_set }')
define(`dir_file_class_set', `{ dir file_class_set }')

define(`ipc_class_set', `{ sem msgq shm ipc }')

class set 放在 TE 规则冒号后面,扩展 target class;permission set 放在最后一个字段,扩展 operation。两者看起来都是花括号集合,但不能交换位置。

text
allow source target:dir_file_class_set r_file_perms;

这条调用先把 dir_file_class_set 展开成多个 class,再为每个 class 应用 r_file_perms。只有当集合中的每个 class 都定义了这些 permission 时,checkpolicy 才能接受规则。

1.2 正反分离 ​

no_w_file_perms、no_rw_file_perms 等反向集合不属于 global_macros,而位于独立的 neverallow_macros。正向“允许写”与反向“禁止任何写入路径”并非互补的同一集合。

源码文件:system/sepolicy/public/neverallow_macros

text
define(`no_w_file_perms', `{ append create link unlink relabelfrom rename setattr write }')
define(`no_rw_file_perms', `{ no_w_file_perms open read ioctl lock watch watch_mount watch_sb watch_with_perm watch_reads }')
define(`no_x_file_perms', `{ execute execute_no_trans }')
define(`no_w_dir_perms',  `{ add_name create link relabelfrom remove_name rename reparent rmdir setattr write }')

例如 w_file_perms 没有 create/unlink/link/rename,因为它描述操作已存在文件;no_w_file_perms 必须覆盖这些改变对象生命周期或目录引用关系的写路径,才能用于安全断言。neverallow 的完整编译与测试机制由后续专题展开,本篇只划清文件归属与集合语义。

1.3 构建产物 ​

和上一篇的高层宏一样,global macro 只存在于 m4 阶段。运行时 policydb 保存展开后的 class 与 permission bit,不保存宏名。

2. Class集合 ​

2.1 文件层次 ​

文件 class set 通过递归组合形成四层范围:设备文件、非设备文件、所有非目录文件、目录加所有文件。

选择 class set 会扩大规则覆盖面。例如 system_data_file:notdevfile_class_set create_file_perms 同时作用于普通文件、符号链接、Unix socket 路径与 FIFO,而不是只允许普通文件。

源码文件:system/sepolicy/private/system_server.te

text
allow system_server system_data_file:dir create_dir_perms;
allow system_server system_data_file:notdevfile_class_set create_file_perms;
allow system_server packages_list_file:file create_file_perms;
allow system_server user_list_data_file:file create_file_perms;

第一条单独处理目录,第二条处理四种非设备对象。create_file_perms 不能应用到 dir 来替代第一条,因为目录的 class-specific permission 与生命周期不同。

2.2 Class契约 ​

permission set 能跨多个 class 使用,依赖 access vector 的继承关系。dir、file、chr_file、sock_file 等都继承 common file,所以共享 read/write/open/ioctl/map/watch 等权限。

源码文件:system/sepolicy/private/access_vectors

text
common file
{
	ioctl
	read
	write
	create
	getattr
	setattr
	lock
	relabelfrom
	relabelto
	append
	map
	unlink
	link
	rename
	execute
	quotaon
	mounton
	audit_access
	open
	execmod
	watch
	watch_mount
	watch_sb
	watch_with_perm
	watch_reads
}

源码文件:system/sepolicy/private/access_vectors

text
class dir
inherits file
{
	add_name
	remove_name
	reparent
	search
	rmdir
}

class file
inherits file
{
	execute_no_trans
	entrypoint
}

class lnk_file
inherits file

class chr_file
inherits file
{
	execute_no_trans
	entrypoint
}

因此 r_file_perms 可以用于 chr_file,但带 execute_no_trans 的 x_file_perms 不能任意应用于所有 file_class_set 成员:lnk_file 只继承 common file,没有声明 execute_no_trans。宏定义本身不验证这种兼容性,错误在 checkpolicy 解析展开结果时出现。

2.3 手工集合 ​

socket_class_set 是手工维护的枚举,不是对 security_classes 的动态查询。Android 17 的集合以 xdp_socket 结束。

源码文件:system/sepolicy/public/global_macros

text
define(`socket_class_set', `{ socket tcp_socket udp_socket rawip_socket netlink_socket packet_socket key_socket unix_stream_socket unix_dgram_socket appletalk_socket netlink_route_socket netlink_tcpdiag_socket netlink_nflog_socket netlink_xfrm_socket netlink_selinux_socket netlink_audit_socket netlink_dnrt_socket netlink_kobject_uevent_socket tun_socket netlink_iscsi_socket netlink_fib_lookup_socket netlink_connector_socket netlink_netfilter_socket netlink_generic_socket netlink_scsitransport_socket netlink_rdma_socket netlink_crypto_socket sctp_socket icmp_socket ax25_socket ipx_socket netrom_socket atmpvc_socket x25_socket rose_socket decnet_socket atmsvc_socket rds_socket irda_socket pppox_socket llc_socket can_socket tipc_socket bluetooth_socket iucv_socket rxrpc_socket isdn_socket phonet_socket ieee802154_socket caif_socket alg_socket nfc_socket vsock_socket kcm_socket qipcrtr_socket smc_socket xdp_socket }')

同一版本的 security_classes 在 xdp_socket 后还有 mctp_socket。

源码文件:system/sepolicy/private/security_classes

text
class qipcrtr_socket
class smc_socket
class xdp_socket
class mctp_socket

class process2
class bpf
class perf_event
class io_uring

所以不能把 socket_class_set 解释成“当前版本全部 socket class”。新增 class 是否应加入集合,需要策略维护者显式决定;现有集合上的 neverallow/dontaudit 也不会自动覆盖遗漏 class。

2.4 功能子集 ​

源码文件:system/sepolicy/public/global_macros

text
define(`dgram_socket_class_set', `{ udp_socket unix_dgram_socket }')
define(`stream_socket_class_set', `{ tcp_socket unix_stream_socket sctp_socket }')
define(`unpriv_socket_class_set', `{ tcp_socket udp_socket unix_stream_socket unix_dgram_socket sctp_socket }')
define(`network_socket_class_set', `{ icmp_socket rawip_socket tcp_socket udp_socket }')

这些名字按策略用途而不是严格协议分类组织。例如 network_socket_class_set 没有包含 SCTP;dgram_socket_class_set 也没有把所有具有数据报行为的协议族 class 收入。阅读调用点时应以实际成员为准,不能从宏名推导未列出的 class。

3. 文件读权限 ​

3.1 集合内容 ​

r_file_perms 不等于 { open read }。它还允许 metadata、ioctl、锁、内存映射和文件监控。

源码文件:system/sepolicy/public/global_macros

text
define(`r_file_perms', `{ getattr open read ioctl lock map watch watch_reads }')
define(`r_without_open_file_perms', `{ getattr read ioctl lock map watch watch_reads }')
define(`w_file_perms', `{ open append write lock map }')
define(`rw_without_open_file_perms', `{ getattr read ioctl lock map watch watch_reads write append }')

without_open 变体用于已经通过其他路径获得 fd、但不希望授予按路径重新 open 的场景。它仍包含实际 read/write、map、lock 等对象操作。

3.2 Open与Read ​

SELinux 会在 open 时根据 file mode 计算 read/write/append,并在启用 open permission capability 时追加 open。

源码文件:kernel/common/security/selinux/hooks.c

c
static inline u32 file_to_av(const struct file *file)
{
	u32 av = 0;

	if (file->f_mode & FMODE_READ)
		av |= FILE__READ;
	if (file->f_mode & FMODE_WRITE) {
		if (file->f_flags & O_APPEND)
			av |= FILE__APPEND;
		else
			av |= FILE__WRITE;
	}
	if (!av) {
		/*
		 * Special file opened with flags 3 for ioctl-only use.
		 */
		av = FILE__IOCTL;
	}

	return av;
}

static inline u32 open_file_to_av(struct file *file)
{
	u32 av = file_to_av(file);
	struct inode *inode = file_inode(file);

	if (selinux_policycap_openperm() &&
	    inode->i_sb->s_magic != SOCKFS_MAGIC)
		av |= FILE__OPEN;

	return av;
}

文件被打开后,SELinux 保存 inode SID 与 policy sequence。后续 read/write 通常复用 open 时结果;若 task SID、inode SID 或 policy seqno 变化,再重新校验。

源码文件:kernel/common/security/selinux/hooks.c

c
static int selinux_file_permission(struct file *file, int mask)
{
	struct inode *inode = file_inode(file);
	struct file_security_struct *fsec = selinux_file(file);
	struct inode_security_struct *isec;
	u32 sid = current_sid();

	if (!mask)
		return 0;

	isec = inode_security(inode);
	if (sid == fsec->sid && fsec->isid == isec->sid &&
	    fsec->pseqno == avc_policy_seqno())
		return 0;

	return selinux_revalidate_file_permission(file, mask);
}

3.3 Ioctl并非只读 ​

r_file_perms 包含基础 ioctl bit。实际 ioctl 还会进入 extended permission 查询,按 command 的 driver byte 与低八位检查 allowxperm/neverallowxperm。

源码文件:kernel/common/security/selinux/hooks.c

c
static int ioctl_has_perm(const struct cred *cred, struct file *file,
		u32 requested, u16 cmd)
{
	struct common_audit_data ad;
	struct file_security_struct *fsec = selinux_file(file);
	struct inode *inode = file_inode(file);
	struct inode_security_struct *isec;
	u32 ssid = cred_sid(cred);
	int rc;
	u8 driver = cmd >> 8;
	u8 xperm = cmd & 0xff;

	ad.type = LSM_AUDIT_DATA_IOCTL_OP;
	ad.u.op = &ioctl;
	ad.u.op->cmd = cmd;
	ad.u.op->path = file->f_path;

	if (ssid != fsec->sid) {
		rc = avc_has_perm(ssid, fsec->sid,
				  SECCLASS_FD,
				  FD__USE,
				  &ad);
		if (rc)
			goto out;
	}

	if (unlikely(IS_PRIVATE(inode)))
		return 0;

	isec = inode_security(inode);
	rc = avc_has_extended_perms(ssid, isec->sid, isec->sclass, requested,
				    driver, AVC_EXT_IOCTL, xperm, &ad);
out:
	return rc;
}

因此用 r_file_perms 修复单纯 read denial,会同时授予通用 ioctl 入口。敏感设备通常需要更窄的显式 permission,并配合 allowxperm 约束 command。Android 策略也提供 rw_no_ioctl_file_perms 与 socket 的 *_no_ioctl 变体来避免无意扩大 ioctl 面。

3.4 Map与Watch ​

map 由 mmap hook 单独消费。它先检查 FILE__MAP,随后根据映射是否可写、可执行再检查 read/write/execute。

源码文件:kernel/common/security/selinux/hooks.c

c
static int selinux_mmap_file(struct file *file,
			     unsigned long reqprot __always_unused,
			     unsigned long prot, unsigned long flags)
{
	struct common_audit_data ad;
	int rc;

	if (file) {
		ad.type = LSM_AUDIT_DATA_FILE;
		ad.u.file = file;
		rc = inode_has_perm(current_cred(), file_inode(file),
				    FILE__MAP, &ad);
		if (rc)
			return rc;
	}

	return file_map_prot_check(file, prot,
				   (flags & MAP_TYPE) == MAP_SHARED);
}

watch 与 watch_reads 来自 fsnotify/fanotify 标记。读取类事件需要 watch_reads,阻塞 permission event 还需要 watch_with_perm,后者并不在 r_file_perms 中。

源码文件:kernel/common/security/selinux/hooks.c

c
switch (obj_type) {
case FSNOTIFY_OBJ_TYPE_VFSMOUNT:
	perm = FILE__WATCH_MOUNT;
	break;
case FSNOTIFY_OBJ_TYPE_SB:
	perm = FILE__WATCH_SB;
	ret = superblock_has_perm(current_cred(), path->dentry->d_sb,
				 FILESYSTEM__WATCH, &ad);
	if (ret)
		return ret;
	break;
case FSNOTIFY_OBJ_TYPE_INODE:
	perm = FILE__WATCH;
	break;
case FSNOTIFY_OBJ_TYPE_MNTNS:
	perm = FILE__WATCH_MOUNTNS;
	break;
default:
	return -EINVAL;
}

if (mask & (ALL_FSNOTIFY_PERM_EVENTS))
	perm |= FILE__WATCH_WITH_PERM;

if (mask & (FS_ACCESS | FS_ACCESS_PERM | FS_PRE_ACCESS |
	    FS_CLOSE_NOWRITE))
	perm |= FILE__WATCH_READS;

这说明 r_file_perms 的“read”语义是 Android 常用读取工作流的便利集合,不是最小的 read syscall 权限。

4. 文件写权限 ​

4.1 递归组合 ​

文件权限通过集合并递归组合。m4 只做文本替换,直接查看输出时仍能看到嵌套花括号;最终由策略编译器按权限集合处理。

源码文件:system/sepolicy/public/global_macros

text
define(`rx_file_perms', `{ r_file_perms x_file_perms }')
define(`ra_file_perms', `{ r_file_perms append }')
define(`rw_file_perms', `{ r_file_perms w_file_perms }')
define(`rw_no_ioctl_file_perms', `{ getattr open read lock map watch watch_reads w_file_perms }')
define(`rwx_file_perms', `{ rw_file_perms x_file_perms }')
define(`create_file_perms', `{ create rename setattr unlink rw_file_perms }')

rw_file_perms 中 open/lock/map 会由 read 与 write 两边重复带入。m4 不负责去重;TE permission field 本身是集合,编译后的 access vector bit 只有一份。

4.2 只管新对象 ​

create_file_perms 授予 source 对“结果 file type”的权限,不授予 source 修改父目录的权限。创建路径先检查父目录 search/add_name,再检查新对象 create。

源码文件:kernel/common/security/selinux/hooks.c

c
rc = avc_has_perm(sid, dsec->sid, SECCLASS_DIR,
			  DIR__ADD_NAME | DIR__SEARCH,
			  &ad);
if (rc)
	return rc;

rc = selinux_determine_inode_label(crsec, dir, &dentry->d_name, tclass,
					   &newsid);
if (rc)
	return rc;

rc = avc_has_perm(sid, newsid, tclass, FILE__CREATE, &ad);
if (rc)
	return rc;

策略通常必须成对授予目录与文件权限。Hostapd 的数据目录就是直接案例。

源码文件:system/sepolicy/vendor/hal_wifi_hostapd_default.te

text
allow hal_wifi_hostapd_default hostapd_data_file:dir create_dir_perms;
allow hal_wifi_hostapd_default hostapd_data_file:file create_file_perms;
allow hal_wifi_hostapd_default hostapd_data_file:sock_file create_file_perms;

第一条让 domain 管理目录对象及目录项,后两条让它创建和管理普通文件、socket path。只保留后两条时,可能在 add_name/search 阶段失败;只保留目录规则时,又会在新对象 create 阶段失败。

4.3 Rename边界 ​

create_file_perms 中的 rename 是被移动对象自身的 permission。跨目录 rename 还会检查旧/新目录的 remove_name/add_name/search,目录对象移动还需要 reparent。因此拥有 file rename 不等于可以在任意目录间移动它。

源码文件:kernel/common/security/selinux/hooks.c

c
ad.u.dentry = new_dentry;
av = DIR__ADD_NAME | DIR__SEARCH;
if (d_is_positive(new_dentry))
	av |= DIR__REMOVE_NAME;
rc = avc_has_perm(sid, new_dsec->sid, SECCLASS_DIR, av, &ad);
if (rc)
	return rc;
if (d_is_positive(new_dentry)) {
	new_isec = backing_inode_security(new_dentry);
	new_is_dir = d_is_dir(new_dentry);
	rc = avc_has_perm(sid, new_isec->sid,
			  new_isec->sclass,
			  (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad);
	if (rc)
		return rc;
}

5. 目录权限 ​

5.1 Read与Search ​

目录的 read 与 search 是不同 operation:read 用于枚举目录项,search 用于路径遍历。只知道完整文件路径时,进程可能只需要沿途目录 search;要列目录内容才需要 read/open/getattr 等。

源码文件:system/sepolicy/public/global_macros

text
define(`r_dir_perms', `{ open getattr read search ioctl lock watch watch_reads }')
define(`w_dir_perms', `{ open search write add_name remove_name lock }')
define(`ra_dir_perms', `{ r_dir_perms add_name write }')
define(`rw_dir_perms', `{ r_dir_perms w_dir_perms }')
define(`create_dir_perms', `{ create reparent rename rmdir setattr rw_dir_perms }')

w_dir_perms 自带 search,因为向目录添加或删除名字前需要解析该目录。ra_dir_perms 则是在完整读权限上增加 add_name/write,但不包含 remove_name。

5.2 两个Create ​

目录规则中存在两个容易混淆的“创建”:

权限target含义
dir add_name父目录 SID在父目录增加一个名字
dir create新目录 SID创建一个 class 为 dir 的新对象

create_dir_perms 应用于新目录 type;父目录若是不同 type,还需要单独的父目录规则。高层 file_type_trans 宏正是把父目录与结果 type 分成两个参数。

5.3 Reparent ​

reparent 只属于 dir class-specific permission,用于目录跨父级移动。普通 file rename 不会检查 reparent。这也是 create_dir_perms 不能简单替换成 create_file_perms 的原因之一。

6. 执行权限 ​

6.1 执行集合 ​

x_file_perms 只有 getattr、execute、execute_no_trans 与 map;它不包含 open/read。

源码文件:system/sepolicy/public/global_macros

text
define(`x_file_perms', `{ getattr execute execute_no_trans map }')
define(`rx_file_perms', `{ r_file_perms x_file_perms }')

需要装载共享库、读取脚本或显式读取 executable 内容时,通常使用 rx_file_perms 或按实际 operation 写权限。x_file_perms 不是“读取并执行”的缩写。

6.2 NoTrans风险 ​

execute_no_trans 允许进程执行文件后保持原 domain。对于要求每个 executable 进入专用 domain 的服务,这个 bit 会破坏隔离,因此许多 domain 明确 neverallow 它。

源码文件:system/sepolicy/private/mediatuner.te

text
neverallow mediatuner { file_type fs_type }:file execute_no_trans;

如果只需要 domain transition,不应为了方便给 source 对 executable 使用 x_file_perms;transition 宏会分别授予 source execute 与 new domain entrypoint,并让 exec hook检查 SID 是否变化。

6.3 映射检查 ​

可执行映射同时涉及 map、read 与 execute。mmap hook先检查 FILE__MAP,随后 file_map_prot_check() 根据 PROT_EXEC/PROT_WRITE 追加 operation。

源码文件:kernel/common/security/selinux/hooks.c

c
if (file) {
	/* read access is always possible with a mapping */
	u32 av = FILE__READ;

	/* write access only matters if the mapping is shared */
	if (shared && (prot & PROT_WRITE))
		av |= FILE__WRITE;

	if (prot & PROT_EXEC)
		av |= FILE__EXECUTE;

	return file_has_perm(cred, file, av);
}

因此只有 map 不足以建立可执行映射,只有 execute 也不足以通过前置 FILE__MAP 检查。

7. Socket权限 ​

7.1 四层集合 ​

Socket 权限宏把通用读写、创建与 stream server 操作分层,并提供去掉 ioctl 的变体。

源码文件:system/sepolicy/public/global_macros

text
define(`rw_socket_perms', `{ ioctl read getattr write setattr lock append bind connect getopt setopt shutdown map }')
define(`rw_socket_perms_no_ioctl', `{ read getattr write setattr lock append bind connect getopt setopt shutdown map }')
define(`create_socket_perms', `{ create rw_socket_perms }')
define(`create_socket_perms_no_ioctl', `{ create rw_socket_perms_no_ioctl }')
define(`rw_stream_socket_perms', `{ rw_socket_perms listen accept }')
define(`create_stream_socket_perms', `{ create rw_stream_socket_perms }')

注意 bind/connect 属于 socket 自身 operation,name_bind/name_connect 属于端口标签 operation,不在这些通用集合中。

7.2 生命周期 ​

内核在 socket 创建时检查 create,bind 时先检查 bind,listen/accept 分别在对应 hook 检查。TCP/SCTP connect 还会对目标端口 type 检查 class-specific name_connect。

源码文件:kernel/common/security/selinux/hooks.c

c
static int selinux_socket_create(int family, int type,
				 int protocol, int kern)
{
	const struct cred_security_struct *crsec = selinux_cred(current_cred());
	u32 newsid;
	u16 secclass;
	int rc;

	if (kern)
		return 0;

	secclass = socket_type_to_security_class(family, type, protocol);
	rc = socket_sockcreate_sid(crsec, secclass, &newsid);
	if (rc)
		return rc;

	return avc_has_perm(crsec->sid, newsid, secclass, SOCKET__CREATE, NULL);
}

源码文件:kernel/common/security/selinux/hooks.c

c
static int selinux_socket_listen(struct socket *sock, int backlog)
{
	return sock_has_perm(sock->sk, SOCKET__LISTEN);
}

static int selinux_socket_accept(struct socket *sock, struct socket *newsock)
{
	int err;

	err = sock_has_perm(sock->sk, SOCKET__ACCEPT);
	if (err)
		return err;

	/* 新socket继承监听socket的class与SID,后续赋值代码省略。 */
	return 0;
}

上面的 accept 摘录明确省略了新 inode security blob 的赋值,只保留 permission 检查。调用方若只需要客户端 connect,不应无条件授予 listen/accept。

7.3 端口检查 ​

TCP connect 先消费 SOCKET__CONNECT,再根据端口查询 port SID,消费 TCP_SOCKET__NAME_CONNECT。create_stream_socket_perms 只解决第一层。

源码文件:kernel/common/security/selinux/hooks.c

c
err = sock_has_perm(sk, SOCKET__CONNECT);
if (err)
	return err;

if (sksec->sclass == SECCLASS_TCP_SOCKET ||
    sksec->sclass == SECCLASS_SCTP_SOCKET) {
	/* 地址解析与端口SID查询省略。 */
	switch (sksec->sclass) {
	case SECCLASS_TCP_SOCKET:
		perm = TCP_SOCKET__NAME_CONNECT;
		break;
	case SECCLASS_SCTP_SOCKET:
		perm = SCTP_SOCKET__NAME_CONNECT;
		break;
	}

	err = avc_has_perm(sksec->sid, sid, sksec->sclass, perm, &ad);
	if (err)
		return err;
}

7.4 Android实例 ​

平台网络基础策略对 TCP self socket 使用 create_stream_socket_perms,对 ICMP/UDP/raw IP 使用 create_socket_perms。

源码文件:system/sepolicy/private/net.te

text
allow { netdomain -pcc_component } self:tcp_socket create_stream_socket_perms;
allow { netdomain -pcc_component } self:{ icmp_socket udp_socket rawip_socket } create_socket_perms;

Netlink 策略大量使用 create_socket_perms_no_ioctl,因为 socket ioctl 往往需要单独审视。

源码文件:system/sepolicy/private/system_server.te

text
allow system_server self:netlink_netfilter_socket create_socket_perms_no_ioctl;
allow system_server self:netlink_tcpdiag_socket create_socket_perms_no_ioctl;
allow system_server self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl;

状态图中的 operation 分散在多个 hook。宏只是一次性授予常用集合,不会改变 socket 的运行时状态机。

8. Capability集合 ​

8.1 Namespace范围 ​

源码文件:system/sepolicy/public/global_macros

text
define(`capability_class_set', `{ capability capability2 cap_userns cap2_userns }')
define(`global_capability_class_set', `{ capability cap_userns }')
define(`global_capability2_class_set', `{ capability2 cap2_userns }')

名字中的 global 指 capability 编号所在的第一组或第二组,并同时覆盖 init user namespace 与非 init user namespace class;它不是“只允许全局 namespace”。完整 capability_class_set 同时包含两组编号。

8.2 内核选择 ​

内核根据 capability 编号是否小于 32 选择第一/第二 class,再根据目标 user namespace 是否为 init namespace 选择 capability/capability2 或 cap_userns/cap2_userns。

源码文件:kernel/common/security/selinux/hooks.c

c
switch (CAP_TO_INDEX(cap)) {
case 0:
	sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS;
	break;
case 1:
	sclass = initns ? SECCLASS_CAPABILITY2 : SECCLASS_CAP2_USERNS;
	break;
default:
	pr_err("SELinux:  out of range capability %d\n", cap);
	BUG();
	return -EINVAL;
}

rc = avc_has_perm_noaudit(sid, sid, sclass, av, 0, &avd);

global_capability_class_set sys_admin 会对 capability 与 cap_userns 两个 class 生成规则。若只想约束 init user namespace,需要显式选择 capability class,而不是使用该集合。

8.3 真实调用 ​

System Server 对第一组与第二组 capability 分开授权。

源码文件:system/sepolicy/private/system_server.te

text
allow system_server self:global_capability_class_set {
    ipc_lock
    kill
    net_admin
    net_bind_service
    net_broadcast
    net_raw
    sys_boot
    sys_nice
    sys_ptrace
    sys_time
    sys_tty_config
};

allow system_server self:global_capability2_class_set mac_admin;
allow system_server self:global_capability2_class_set wake_alarm;

capability 是否实际可用还受 Linux credentials、bounding set、ambient/inheritable 与 seccomp 等机制影响;SELinux allow 只是 capable hook 的一层判断。

9. IPC集合 ​

9.1 四种Class ​

System V IPC 集合覆盖 semaphore、message queue、shared memory 与通用 ipc class,并提供读写/创建权限组合。

源码文件:system/sepolicy/public/global_macros

text
define(`ipc_class_set', `{ sem msgq shm ipc }')

define(`r_ipc_perms', `{ getattr read associate unix_read }')
define(`w_ipc_perms', `{ write unix_write }')
define(`rw_ipc_perms', `{ r_ipc_perms w_ipc_perms }')
define(`create_ipc_perms', `{ create setattr destroy rw_ipc_perms }')

集合不含 msg class,因为单条 message 与 queue 的 permission 不同。把所有 SysV IPC 粗略理解成 Binder 的替代也会遗漏 associate、destroy、enqueue/send/receive 等对象关系。

9.2 Runtime检查 ​

共享内存创建时检查 SHM__CREATE,attach 时根据只读 flag 检查 read 或 read+write。

源码文件:kernel/common/security/selinux/hooks.c

c
static int selinux_shm_alloc_security(struct kern_ipc_perm *shp)
{
	struct ipc_security_struct *isec;
	struct common_audit_data ad;
	u32 sid = current_sid();

	isec = selinux_ipc(shp);
	ipc_init_security(isec, SECCLASS_SHM);

	ad.type = LSM_AUDIT_DATA_IPC;
	ad.u.ipc_id = shp->key;

	return avc_has_perm(sid, isec->sid, SECCLASS_SHM,
			    SHM__CREATE, &ad);
}

static int selinux_shm_shmat(struct kern_ipc_perm *shp,
			     char __user *shmaddr, int shmflg)
{
	u32 perms;

	if (shmflg & SHM_RDONLY)
		perms = SHM__READ;
	else
		perms = SHM__READ | SHM__WRITE;

	return ipc_has_perm(shp, perms);
}

Android 平台很少直接使用正向 IPC permission macros;su 的调试策略用 ipc_class_set 做 dontaudit,说明 class set 也常用于抑制或约束,而不只用于 allow。

源码文件:system/sepolicy/private/su.te

text
dontaudit su domain:ipc_class_set *;

10. 验证方法 ​

10.1 递归展开 ​

最小 m4 实验能显示嵌套关系。注意输出仍是嵌套集合,这可以反向验证“m4 不负责去重或扁平化”。

bash
# 从AOSP根目录执行,逐行展开几个权限集合。
printf '%s\n' \
  'r_file_perms' \
  'rw_file_perms' \
  'create_file_perms' \
  'create_dir_perms' \
  'create_stream_socket_perms' | \
  m4 system/sepolicy/public/global_macros - | \
  sed '/^[[:space:]]*$/d'

关键输出形态如下:

text
{ getattr open read ioctl lock map watch watch_reads }
{ { getattr open read ioctl lock map watch watch_reads } { open append write lock map } }
{ create rename setattr unlink { { getattr open read ioctl lock map watch watch_reads } { open append write lock map } } }

实验输入只有 global macro 文件与宏名。断言是 rw_file_perms 仍包含两层子集合,create_file_perms 再包一层生命周期权限。它验证 m4 文本替换,不验证 class/permission 是否兼容,也不生成完整策略。

10.2 集合覆盖 ​

可以对比 security_classes 与 socket_class_set,确认后者不是自动全集。

bash
# security_classes中存在mctp_socket。
rg -n '^class .*socket' system/sepolicy/private/security_classes | tail -20

# global_macros的socket集合中没有mctp_socket。
rg -n '\bmctp_socket\b' system/sepolicy/public/global_macros || \
  printf '%s\n' 'mctp_socket is not in socket_class_set'

输入是同一 release 的两个源码文件。断言是 class 声明存在,而宏成员缺失。它说明集合是人工 API,不说明缺失一定是缺陷;是否加入要结合兼容性与既有规则影响评估。

10.3 最终策略 ​

最终 binary policy 查询能看到宏展开后的原子 permission,却看不到宏名。

bash
# 将PRODUCT替换为实际产品名。
ANDROID_SEPOLICY='out/target/product/PRODUCT/root/sepolicy'

# r_file_perms应展开为多个file permission。
sesearch -A -s vendor_modprobe -t proc_modules \
  -c file "$ANDROID_SEPOLICY"

# system_server属于netdomain,其TCP self规则来自net.te。
sesearch -A -s system_server -t system_server \
  -c tcp_socket "$ANDROID_SEPOLICY"

第一项断言至少包含 getattr/open/read/ioctl/lock/map/watch/watch_reads;第二项需要结合 attribute 展开查看具体 domain。查询只能说明 permission 进入最终策略,不证明运行时对象标签、端口 type 或文件系统路径符合规则。

10.4 测试查询模型 ​

Android sepolicy 测试工具把每条展开规则的 permission 字段解析为 Python set,再按 source、target、class 与 permission 查询。这与读者排错时应采用的四元组一致。

源码文件:system/sepolicy/tests/policy.py

python
class TERule:
    def __init__(self, rule):
        data = rule.split(',')
        self.flavor = data[0]
        self.sctx = data[1]
        self.tctx = data[2]
        self.tclass = data[3]
        self.perms = set((data[4].strip()).split(' '))
        self.rule = rule

测试输入已经是 libsepol wrapper 导出的展开规则,不含 r_file_perms 等 m4 名字。它证明测试消费者按原子 permission 判断策略结果,但不证明 m4 源调用选择了最小权限集合。

11. 选择边界 ​

11.1 先列Operation ​

选择 permission macro 前,应先从真实 syscall/hook 列出需要的 operation,再判断是否有恰好覆盖的集合。不要从“读取文件”“创建 socket”这种自然语言直接跳到最大宏。

需求常见最小方向不应默认添加
按路径读取普通文件目录 search + file open/read/getattrioctl、watch、map
mmap 只读配置open/read/getattr/mapioctl、write
已有 fd 上读取r_without_open_file_perms 或显式权限path open
创建数据文件父目录 search/add_name + 新对象 create/writeexecute/no_trans
TCP 客户端socket create/connect + port name_connectlisten/accept
TCP 服务端create/bind/listen/accept + port name_bind任意 ioctl

宏是稳定、经过审阅的常用集合,优先使用它们通常比重复裸权限更易维护;但当集合明显超出 operation,Android 策略也大量使用显式 permission 或 no-ioctl 变体。

11.2 读权限过宽 ​

r_file_perms 的风险不是允许写,而是把 read 周边能力一起开放。设备节点、可攻击 ioctl 面或不需要 mmap/watch 的敏感文件,应考虑显式 { getattr open read }、rw_no_ioctl_file_perms 或 allowxperm。

System Server 对 tun device 就把普通 file permission 与 ioctl command 分开。

源码文件:system/sepolicy/private/system_server.te

text
allow system_server tun_device:chr_file rw_file_perms;
allowxperm system_server tun_device:chr_file ioctl { TUNGETIFF TUNSETIFF TUNSETLINK TUNSETCARRIER };

这段仍先授予通用 ioctl bit,再用 xperm 约束允许的 command;若完全不需要 ioctl,应选择不含 ioctl 的集合或显式权限。

11.3 Class范围过宽 ​

dir_file_class_set、socket_class_set 常用于 neverallow/dontaudit,因为目标是覆盖一组对象;正向 allow 若使用过宽 class set,则每个新增成员都可能扩大权限面。尤其是手工维护的 socket 集合,版本升级时应检查新增 class 是否进入集合、进入后会影响哪些通配规则。

11.4 故障决策 ​

12. 源码导航 ​

问题源码入口关键对象
class/permission set 定义system/sepolicy/public/global_macros56 行宏定义
反向 permission setsystem/sepolicy/public/neverallow_macrosno_*_perms
class 是否继承某权限system/sepolicy/private/access_vectorscommon 与 class inherits
当前有哪些 classsystem/sepolicy/private/security_classesclass 声明顺序
文件 operation 如何转成 AVkernel/common/security/selinux/hooks.cfile_to_av、open_file_to_av
ioctl/map/watch 谁检查kernel/common/security/selinux/hooks.cioctl_has_perm、mmap、fsnotify hook
创建为何要目录权限kernel/common/security/selinux/hooks.cmay_create
Socket生命周期谁检查kernel/common/security/selinux/hooks.csocket create/bind/connect/listen/accept
Capability class 如何选择kernel/common/security/selinux/hooks.ccred_has_capability
最终规则如何被测试查询system/sepolicy/tests/policy.pyTERule、QueryTERule

读一条 allow domain type:file r_file_perms; 时,完整解释不应停在“允许读文件”。它允许 getattr、open、read、ioctl、lock、map、watch 与 watch_reads;open/read 由文件访问路径消费,ioctl 继续进入 command 级 extended permission,map 和 watch 各有独立 hook。若目标只是一次普通 read,这个集合是否合适取决于对象敏感度与项目约定,而不是宏名。

同样,create_file_perms 只描述新对象,父目录仍需 search/add_name;create_stream_socket_perms 只描述 socket 自身,端口仍需 name_bind/name_connect;global_capability_class_set 同时覆盖 init 与非 init user namespace class。能够从宏成员复述到这些运行时消费者和缺失边界,才算掌握 Global Macros。