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
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。两者看起来都是花括号集合,但不能交换位置。
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
dontaudit su domain:ipc_class_set *;10. 验证方法
10.1 递归展开
最小 m4 实验能显示嵌套关系。注意输出仍是嵌套集合,这可以反向验证“m4 不负责去重或扁平化”。
# 从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'关键输出形态如下:
{ 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,确认后者不是自动全集。
# 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,却看不到宏名。
# 将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
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/getattr | ioctl、watch、map |
| mmap 只读配置 | open/read/getattr/map | ioctl、write |
| 已有 fd 上读取 | r_without_open_file_perms 或显式权限 | path open |
| 创建数据文件 | 父目录 search/add_name + 新对象 create/write | execute/no_trans |
| TCP 客户端 | socket create/connect + port name_connect | listen/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
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_macros | 56 行宏定义 |
| 反向 permission set | system/sepolicy/public/neverallow_macros | no_*_perms |
| class 是否继承某权限 | system/sepolicy/private/access_vectors | common 与 class inherits |
| 当前有哪些 class | system/sepolicy/private/security_classes | class 声明顺序 |
| 文件 operation 如何转成 AV | kernel/common/security/selinux/hooks.c | file_to_av、open_file_to_av |
| ioctl/map/watch 谁检查 | kernel/common/security/selinux/hooks.c | ioctl_has_perm、mmap、fsnotify hook |
| 创建为何要目录权限 | kernel/common/security/selinux/hooks.c | may_create |
| Socket生命周期谁检查 | kernel/common/security/selinux/hooks.c | socket create/bind/connect/listen/accept |
| Capability class 如何选择 | kernel/common/security/selinux/hooks.c | cred_has_capability |
| 最终规则如何被测试查询 | system/sepolicy/tests/policy.py | TERule、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。
