Genfs Contexts
本文面向已经读过 安全上下文、File Contexts 和 M4预处理 的读者。File Contexts 依靠用户空间路径数据库和 security.selinux xattr;Genfs Contexts 则进入编译后的 SELinux policy,由内核使用文件系统类型与挂载内路径为 inode 计算 SID。
“proc/sysfs 不支持 xattr,所以挂载时由 genfscon 统一标注”只是粗略描述。Android 17 的真实路径更复杂:内核先为 superblock 选择 SECURITY_FS_USE_* 行为;proc、binder、bpf 等文件系统设置 genfs flag;inode 首次初始化时才构造挂载内路径并查询 policy;sysfs/cgroup 等还能以 genfs SID 作为默认值,再由内核内建 xattr 覆盖。缺少匹配时 inode 可能变成 unlabeled,而不是自动继承最近的 file_contexts。
Android 17 还引入了 genfs label 版本协商。Vendor 分区记录自己接受的 genfs labels 版本,init 选择 /system/etc/selinux/plat_sepolicy_genfs_<version>.cil 加入 split policy 编译,避免新 system 标签改变旧 vendor 对 proc/sysfs 节点的访问假设。本文会把构建、版本、内核消费、失败路径和测试证据串成一条可复查主线。
1. 标签模型
1.1 Xattr行为
ext4、f2fs、erofs 等在 fs_use 中声明 fs_use_xattr。这类文件系统的 inode SID 通常来自持久化 security.selinux,没有 xattr 时使用 default SID。
源码文件:system/sepolicy/private/fs_use
fs_use_xattr ext4 u:object_r:labeledfs:s0;
fs_use_xattr f2fs u:object_r:labeledfs:s0;
fs_use_xattr overlay u:object_r:labeledfs:s0;
fs_use_xattr erofs u:object_r:labeledfs:s0;
fs_use_xattr virtiofs u:object_r:labeledfs:s0;1.2 Task与Transition
pipefs/sockfs 使用 task SID;tmpfs/devtmpfs 等使用 task、filesystem 和 class 的 type transition。它们也不是 genfs 路径查找。
源码文件:system/sepolicy/private/fs_use
fs_use_task pipefs u:object_r:pipefs:s0;
fs_use_task sockfs u:object_r:sockfs:s0;
fs_use_trans devpts u:object_r:devpts:s0;
fs_use_trans tmpfs u:object_r:tmpfs:s0;
fs_use_trans devtmpfs u:object_r:device:s0;
fs_use_trans shm u:object_r:shm:s0;1.3 Genfs行为
如果 fs_use 没有为某个 filesystem type 指定行为,内核尝试用该文件系统根路径 / 查询 genfs。查询成功时将 superblock behavior 设为 SECURITY_FS_USE_GENFS;查询失败则设为 SECURITY_FS_USE_NONE。
源码文件:kernel/common/security/selinux/ss/services.c
c = policydb->ocontexts[OCON_FSUSE];
while (c) {
if (strcmp(fstype, c->u.name) == 0)
break;
c = c->next;
}
if (c) {
sbsec->behavior = c->v.behavior;
rc = ocontext_to_sid(sidtab, c, 0, &sbsec->sid);
} else {
rc = __security_genfs_sid(policy, fstype, "/",
SECCLASS_DIR, &sbsec->sid);
if (rc) {
sbsec->behavior = SECURITY_FS_USE_NONE;
rc = 0;
} else {
sbsec->behavior = SECURITY_FS_USE_GENFS;
}
}因此是否走 genfs 由“文件系统类型是否在 policy 中有根规则”以及内核特殊 flag 共同决定,不是用户空间看到路径以 /proc 或 /sys 开头就直接决定。
2. 规则语法
2.1 四个字段
传统文本形式由 genfscon、filesystem type、挂载内路径和完整 Context 组成。路径不是 Android 全局路径:proc 的 /meminfo 对应挂载后的 /proc/meminfo,sysfs 的 /class/net 对应 /sys/class/net。
源码文件:system/sepolicy/private/genfs_contexts
genfscon proc /meminfo u:object_r:proc_meminfo:s0
genfscon proc /net u:object_r:proc_net:s0
genfscon proc /net/tcp u:object_r:proc_net_tcp_udp:s0
genfscon sysfs /class/net u:object_r:sysfs_net:s0
genfscon binder /vndbinder u:object_r:vndbinder_device:s0
genfscon bpf /loader u:object_r:fs_bpf_loader:s02.2 文件系统名
第一个 selector 来自 sb->s_type->name,必须与内核文件系统类型完全一致。Binderfs 在 policy 中使用 binder,并不使用 mountpoint 名 /dev/binderfs;BPF 文件系统使用 bpf。
2.3 路径前缀
当前 Android policy 没有启用 genfs_seclabel_wildcard capability,所以内核使用字符串前缀匹配。Policy loader 会把同一 filesystem 的路径按长度从长到短保存,查询时第一个匹配生效,由此实现最长前缀语义。
源码文件:kernel/common/security/selinux/ss/policydb.c
for (l = NULL, c = genfs->head; c; l = c, c = c->next) {
if (!strcmp(newc->u.name, c->u.name) &&
(!c->v.sclass || !newc->v.sclass ||
newc->v.sclass == c->v.sclass)) {
pr_err("SELinux: dup genfs entry (%s,%s)\n",
genfs->fstype, c->u.name);
goto out;
}
len = strlen(newc->u.name);
len2 = strlen(c->u.name);
if (len > len2)
break;
}
newc->next = c;
if (l)
l->next = newc;
else
genfs->head = newc;2.4 Class约束
二进制 policy 的 genfs entry 可以带 object class;内核只接受未限制 class 或与 inode class 相同的 entry。Android 文本中的常见规则不显式写 class,但查询函数仍保留该维度。
3. 当前规则集
3.1 文件系统规模
Android 17 platform private/genfs_contexts 包含 352 条 genfscon 规则。按当前源码统计,数量最多的是 proc、tracefs 与 sysfs;这反映了内核动态节点需要细粒度区分,并不意味着这些文件系统都允许 App 访问。
| Filesystem | 当前条目数 |
|---|---|
proc | 115 |
tracefs | 103 |
sysfs | 76 |
bpf | 14 |
binder | 13 |
debugfs | 12 |
3.2 Proc示例
源码文件:system/sepolicy/private/genfs_contexts
genfscon proc / u:object_r:proc:s0
genfscon proc /kallsyms u:object_r:proc_kallsyms:s0
genfscon proc /meminfo u:object_r:proc_meminfo:s0
genfscon proc /net u:object_r:proc_net:s0
genfscon proc /net/tcp u:object_r:proc_net_tcp_udp:s0
genfscon proc /pressure/memory u:object_r:proc_pressure_mem:s0
genfscon proc /sys/kernel/hostname u:object_r:proc_hostname:s0
genfscon proc /sys/vm/drop_caches u:object_r:proc_drop_caches:s03.3 Sysfs示例
源码文件:system/sepolicy/private/genfs_contexts
genfscon sysfs / u:object_r:sysfs:s0
genfscon sysfs /class/block u:object_r:sysfs_block:s0
genfscon sysfs /class/net u:object_r:sysfs_net:s0
genfscon sysfs /devices/system/cpu u:object_r:sysfs_devices_system_cpu:s0
genfscon sysfs /devices/virtual/block/dm- u:object_r:sysfs_dm:s0
genfscon sysfs /power/state u:object_r:sysfs_power:s03.4 Binder与BPF
源码文件:system/sepolicy/private/genfs_contexts
genfscon binder / u:object_r:binderfs:s0
genfscon binder /binder u:object_r:binder_device:s0
genfscon binder /hwbinder u:object_r:hwbinder_device:s0
genfscon binder /vndbinder u:object_r:vndbinder_device:s0
genfscon binder /binder_logs/proc u:object_r:binderfs_logs_proc:s0
genfscon bpf / u:object_r:fs_bpf:s0
genfscon bpf /loader u:object_r:fs_bpf_loader:s0
genfscon bpf /net_private u:object_r:fs_bpf_net_private:s0
genfscon bpf /vendor u:object_r:fs_bpf_vendor:s04. Policy构建
4.1 Policy输入
genfs_contexts 不是独立安装的运行时文本文件。它和 security_classes、*.te、fs_use、port_contexts 一起进入 se_build_files,随后参与 policy.conf、CIL 和二进制 policy 的构建。
源码文件:system/sepolicy/Android.bp
se_build_files {
name: "se_build_files",
srcs: [
"security_classes",
"initial_sids",
"access_vectors",
"global_macros",
"te_macros",
"attributes",
"*.te",
"roles_decl",
"roles",
"users",
"initial_sid_contexts",
"fs_use",
"genfs_contexts",
"port_contexts",
],
}因此设备上不能像 file_contexts 那样简单替换一个 genfs 文本文件并运行 restorecon;必须重新编译并加载 SELinux policy,已有 inode 还涉及重新初始化或重新挂载。
4.2 M4与Board API
se_policy_conf 为策略输入传入 target_board_api_level 和 release flags。历史 API 快照中的 genfs_contexts 使用 starting_at_board_api() 控制新增标签;Android 17 当前分支把部分跨版本新增标签拆为额外 genfs CIL。
源码文件:system/sepolicy/build/soong/selinux.go
func boardApiLevelToM4Macro(ctx android.ModuleContext,
boardApiLevel *string) string {
level := proptools.StringDefault(boardApiLevel, "current")
if level == "current" {
level = ctx.Config().ReleaseBoardApiLevel()
}
return "-D target_board_api_level=" + level
}4.3 CIL表示
文本规则进入 CIL 后变成结构化 genfscon 表达式。例如:
源码文件:system/sepolicy/compat/plat_sepolicy_genfs_202604.cil
(genfscon sysfs "/class/udc" (u object_r sysfs_udc ((s0) (s0))))
(genfscon sysfs "/power/mem_sleep" (u object_r sysfs_mem_sleep ((s0) (s0))))
(genfscon sysfs "/class/typec" (u object_r sysfs_typec ((s0) (s0))))
(genfscon proc "/sys/kernel/firmware_config"
(u object_r proc_firmware_config ((s0) (s0))))这些文件不是完整 genfs 表,而是某个兼容版本允许平台追加的标签集合。
4.4 二进制Policy
checkpolicy/secilc 将 genfs 条目写入 policydb 的 genfs 列表。内核加载二进制 policy 时,genfs_read() 检查重复 filesystem/path/class,并按 filesystem 名和路径长度建立有序链表。
5. 版本协商
5.1 Vendor版本文件
Vendor 构建安装 /vendor/etc/selinux/genfs_labels_version.txt。默认值来自 BOARD_GENFS_LABELS_VERSION,未显式设置时等于 BOARD_API_LEVEL;构建系统禁止 genfs labels version 小于 board API level。
源码文件:build/make/core/config.mk
BOARD_GENFS_LABELS_VERSION ?= $(BOARD_API_LEVEL)
ifeq ($(call math_gt,$(BOARD_API_LEVEL),$(BOARD_GENFS_LABELS_VERSION)),true)
$(error BOARD_GENFS_LABELS_VERSION ($(BOARD_GENFS_LABELS_VERSION)) \
must be greater than or equal to BOARD_API_LEVEL ($(BOARD_API_LEVEL)))
endif源码文件:system/sepolicy/Android.bp
genrule {
name: "genfs_labels_version.txt.gen",
out: ["genfs_labels_version.txt"],
cmd: select(soong_config_variable(
"ANDROID", "BOARD_GENFS_LABELS_VERSION"), {
any @ value: "echo " + value + " > $(out)",
default: "echo > $(out)",
}),
}5.2 读取回退
libgenfslabelsversion 读取 vendor 文件并解析整数。文件不存在或内容非法时回退到 202404,保证旧 vendor 不需要提供新文件。
源码文件:system/sepolicy/compat/libgenfslabelsversion/src/lib.rs
const GENFS_LABELS_VERSION_TXT_PATH: &str =
"/vendor/etc/selinux/genfs_labels_version.txt";
const DEFAULT_GENFS_LABELS_VERSION: i32 = 202404;
#[no_mangle]
pub extern "C" fn get_genfs_labels_version() -> i32 {
match fs::read_to_string(GENFS_LABELS_VERSION_TXT_PATH) {
Ok(contents) => match contents.trim().parse::<i32>() {
Ok(version) => version,
Err(_) => DEFAULT_GENFS_LABELS_VERSION,
},
Err(_) => DEFAULT_GENFS_LABELS_VERSION,
}
}5.3 Init选择CIL
Split policy 编译时,init 根据 vendor 版本拼出平台附加 CIL 路径。版本大于等于 202504 且文件缺失时直接失败;旧版本允许没有额外文件。
源码文件:system/core/init/selinux.cpp
int vendor_genfs_version = get_genfs_labels_version();
std::string genfs_cil_file = std::format(
"/system/etc/selinux/plat_sepolicy_genfs_{}.cil",
vendor_genfs_version);
if (access(genfs_cil_file.c_str(), F_OK) != 0) {
if (vendor_genfs_version >= 202504) {
LOG(ERROR) << "Missing " << genfs_cil_file;
return false;
}
LOG(INFO) << "Missing " << genfs_cil_file << "; skipping";
genfs_cil_file.clear();
} else {
LOG(INFO) << "Using " << genfs_cil_file
<< " for genfs labels";
}5.4 编译顺序
平台、system_ext、product、vendor、odm CIL 都加入 secilc 参数后,选中的 genfs CIL 最后追加。这让兼容 genfs 标签成为最终组合 policy 的一部分。
源码文件:system/core/init/selinux.cpp
if (!vendor_policy_cil_file.empty()) {
compile_args.push_back(vendor_policy_cil_file.c_str());
}
if (!odm_policy_cil_file.empty()) {
compile_args.push_back(odm_policy_cil_file.c_str());
}
if (!genfs_cil_file.empty()) {
compile_args.push_back(genfs_cil_file.c_str());
}
compile_args.push_back(nullptr);5.5 兼容动机
新 platform 若立即给旧 sysfs/proc 节点换成更具体 type,旧 vendor policy 可能只允许原有通用 type,导致 OTA 后访问中断。版本文件让 vendor 声明它理解到哪个 genfs label 集合,system 只加入相应兼容 CIL。
6. Policydb匹配
6.1 Filesystem查找
内核 policydb 按 fstype 字典序保存 genfs group。__security_genfs_sid() 遍历到第一个大于等于目标 fstype 的节点;没有精确 filesystem 名时返回 -ENOENT。
源码文件:kernel/common/security/selinux/ss/services.c
for (genfs = policydb->genfs; genfs; genfs = genfs->next) {
cmp = strcmp(fstype, genfs->fstype);
if (cmp <= 0)
break;
}
if (!genfs || cmp)
return -ENOENT;6.2 Path查找
查询前会折叠多余的开头 /。当前无 wildcard capability 时,对每条候选执行 strncmp(rule_path, path, rule_len),第一个前缀匹配即结束。
源码文件:kernel/common/security/selinux/ss/services.c
while (path[0] == '/' && path[1] == '/')
path++;
sclass = unmap_class(&policy->map, orig_sclass);
*sid = SECINITSID_UNLABELED;
for (c = genfs->head; c; c = c->next) {
if (!c->v.sclass || sclass == c->v.sclass) {
if (wildcard) {
if (match_wildcard(c->u.name, path))
break;
} else {
size_t len = strlen(c->u.name);
if (strncmp(c->u.name, path, len) == 0)
break;
}
}
}
if (!c)
return -ENOENT;
return ocontext_to_sid(sidtab, c, 0, sid);6.3 最长前缀来源
最长前缀不是 query 函数现场比较所有长度,而是 policy load 时已将长 path 插到短 path 前面。因此调试时既要看 query,也要看 genfs_read() 的排序不变量。
6.4 边界字符
普通前缀匹配不检查下一个字符是否为 /。规则 /devices/virtual/block/dm- 有意覆盖 dm-0、dm-1;规则设计者必须自己决定是否保留尾斜杠以限制目录边界。
7. Superblock初始化
7.1 Genfs Flags
SELinux mount hook 对若干已知 pseudo filesystem 设置 SE_SBGENFS。proc 还设置 SE_SBPROC;sysfs/cgroup/cgroup2 设置 SE_SBGENFS_XATTR,表示先用 genfs 计算默认 SID,再尝试内核 xattr 覆盖。
源码文件:kernel/common/security/selinux/hooks.c
if (strcmp(sb->s_type->name, "proc") == 0)
sbsec->flags |= SE_SBPROC | SE_SBGENFS;
if (!strcmp(sb->s_type->name, "debugfs") ||
!strcmp(sb->s_type->name, "tracefs") ||
!strcmp(sb->s_type->name, "binder") ||
!strcmp(sb->s_type->name, "bpf") ||
!strcmp(sb->s_type->name, "pstore") ||
!strcmp(sb->s_type->name, "securityfs") ||
(selinux_policycap_functionfs_seclabel() &&
!strcmp(sb->s_type->name, "functionfs")))
sbsec->flags |= SE_SBGENFS;
if (!strcmp(sb->s_type->name, "sysfs") ||
!strcmp(sb->s_type->name, "cgroup") ||
!strcmp(sb->s_type->name, "cgroup2"))
sbsec->flags |= SE_SBGENFS | SE_SBGENFS_XATTR;7.2 Behavior选择
flags 描述 inode 是否需要 genfs 路径处理,behavior 描述 filesystem 默认标签策略。Mount option 没有预先指定 behavior 时,hook 调用 security_fs_use(sb)。
源码文件:kernel/common/security/selinux/hooks.c
if (!sbsec->behavior) {
rc = security_fs_use(sb);
if (rc) {
pr_warn("%s: security_fs_use(%s) returned %d\n",
__func__, sb->s_type->name, rc);
goto out;
}
}7.3 Xattr回退
一个声明为 xattr 的 filesystem 若根 inode 没有 xattr handler,内核尝试查询 genfs root。存在 root rule 时切换到 SECURITY_FS_USE_GENFS,否则 mount 初始化返回 -EOPNOTSUPP。
源码文件:kernel/common/security/selinux/hooks.c
fallback:
rc = security_genfs_sid(sb->s_type->name, "/",
SECCLASS_DIR, &sid);
if (rc)
return -EOPNOTSUPP;
pr_warn("SELinux: (dev %s, type %s) falling back to genfs\n",
sb->s_id, sb->s_type->name);
sbsec->behavior = SECURITY_FS_USE_GENFS;
sbsec->sid = sid;
return 0;7.4 延迟初始化
Policy 加载前创建的 inode 被挂到 sbsec->isec_head。Superblock 完成初始化后,内核先标根 inode,再遍历待处理 inode;没有 dentry 的 inode 暂时标为 invalid,后续出现 dentry 时重试。
源码文件:kernel/common/security/selinux/hooks.c
rc = inode_doinit_with_dentry(root_inode, root);
spin_lock(&sbsec->isec_lock);
while (!list_empty(&sbsec->isec_head)) {
struct inode_security_struct *isec =
list_first_entry(&sbsec->isec_head,
struct inode_security_struct, list);
struct inode *inode = isec->inode;
list_del_init(&isec->list);
spin_unlock(&sbsec->isec_lock);
inode = igrab(inode);
if (inode) {
if (!IS_PRIVATE(inode))
inode_doinit_with_dentry(inode, NULL);
iput(inode);
}
spin_lock(&sbsec->isec_lock);
}
spin_unlock(&sbsec->isec_lock);8. Inode SID
8.1 路径构造
selinux_genfs_get_sid() 使用 dentry_path_raw() 得到相对当前 mount root 的路径,并把 superblock filesystem name 与 inode security class 一起传给 security_genfs_sid()。
源码文件:kernel/common/security/selinux/hooks.c
buffer = (char *)__get_free_page(GFP_KERNEL);
if (!buffer)
return -ENOMEM;
path = dentry_path_raw(dentry, buffer, PAGE_SIZE);
if (IS_ERR(path)) {
rc = PTR_ERR(path);
} else {
rc = security_genfs_sid(sb->s_type->name,
path, tclass, sid);
if (rc == -ENOENT) {
*sid = SECINITSID_UNLABELED;
rc = 0;
}
}路径超出一个 page 或无法连接到 dentry 时会失败。Genfs query 没有匹配则明确返回 unlabeled SID,之后访问通常触发针对 unlabeled 的 denial。
8.2 Proc PID归一化
proc 每个进程都有 /proc/<pid>/...。Policy 不为每个 PID 生成规则;内核在 SE_SBPROC 下把开头数字段剥离。例如 /1/net/rpc/nfs 转成 /net/rpc/nfs 再 lookup。
源码文件:kernel/common/security/selinux/hooks.c
if (flags & SE_SBPROC) {
/* /proc/1/net/rpc/nfs -> /net/rpc/nfs */
while (path[1] >= '0' && path[1] <= '9') {
path[1] = '/';
path++;
}
}只有开头 PID 段被规范化;/proc/sys/...、/proc/net/... 保持原路径。
8.3 Inode初始化分支
inode_doinit_with_dentry() 先按 behavior 处理 xattr/task/transition。其他 behavior 默认使用 superblock SID;若设置 SE_SBGENFS 且 inode 不是被 policycap 排除的 symlink,再按 dentry 查 genfs SID。
源码文件:kernel/common/security/selinux/hooks.c
default:
sid = sbsec->sid;
if ((sbsec->flags & SE_SBGENFS) &&
(!S_ISLNK(inode->i_mode) ||
selinux_policycap_genfs_seclabel_symlinks())) {
if (opt_dentry) {
dentry = dget(opt_dentry);
} else {
dentry = d_find_alias(inode);
if (!dentry)
dentry = d_find_any_alias(inode);
}
if (!dentry)
goto out_invalid;
rc = selinux_genfs_get_sid(dentry, sclass,
sbsec->flags, &sid);
if (rc) {
dput(dentry);
goto out;
}8.4 Sysfs Xattr覆盖
sysfs/cgroup 设置 SE_SBGENFS_XATTR。Genfs 先计算默认 SID;如果 inode 提供 xattr handler,再读取 in-core xattr,并以 genfs SID 作为无 xattr或无效值时的 default。
源码文件:kernel/common/security/selinux/hooks.c
if ((sbsec->flags & SE_SBGENFS_XATTR) &&
(inode->i_opflags & IOP_XATTR)) {
rc = inode_doinit_use_xattr(inode, dentry,
sid, &sid);
if (rc) {
dput(dentry);
goto out;
}
}这说明“sysfs 标签全部来自 genfscon”并不严格:genfs 是基础选择,内核 xattr 可能覆盖具体 inode。
8.5 提交到Blob
SID 计算完成后,在持有 inode security lock 时把 isec->sid 和初始化状态提交。后续 AVC 访问检查读取的是这个 SID,不会每次 open 都重新扫描 genfs 表。
源码文件:kernel/common/security/selinux/hooks.c
spin_lock(&isec->lock);
if (isec->initialized == LABEL_PENDING) {
if (rc) {
isec->initialized = LABEL_INVALID;
goto out_unlock;
}
isec->initialized = LABEL_INITIALIZED;
isec->sid = sid;
}
spin_unlock(&isec->lock);9. 文件系统案例
9.1 Binderfs
Binderfs 的 filesystem name 是 binder。根目录、三个 binder driver 和 debug/log 节点通过同一 fstype 下的路径前缀区分。
源码文件:system/sepolicy/private/genfs_contexts
genfscon binder / u:object_r:binderfs:s0
genfscon binder /binder u:object_r:binder_device:s0
genfscon binder /hwbinder u:object_r:hwbinder_device:s0
genfscon binder /vndbinder u:object_r:vndbinder_device:s0
genfscon binder /binder_logs u:object_r:binderfs_logs:s0
genfscon binder /binder_logs/proc u:object_r:binderfs_logs_proc:s0
genfscon binder /features u:object_r:binderfs_features:s0这条链解释了三个 ServiceManager 系列为何能在同一 binderfs mount 中获得不同 device type,而不是靠 ueventd 的 file_contexts restorecon。
9.2 BPF FS
BPF pin 路径需要按 owner/用途拆分。Platform 测试要求所有 bpf genfs target 都具有 bpffs_type,避免普通 fs type 混入 BPF policy。
源码文件:system/sepolicy/tests/sepolicy_tests.py
def TestBpffsTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"bpf", "bpffs_type")9.3 Selinuxfs
selinuxfs 自己创建 boolean inode 时直接调用 selinux_policy_genfs_sid(newpolicy, "selinuxfs", path, class, &sid)。查询失败会回退到 security initial SID。
源码文件:kernel/common/security/selinux/selinuxfs.c
ret = selinux_policy_genfs_sid(newpolicy, "selinuxfs", page,
SECCLASS_FILE, &sid);
if (ret) {
pr_warn_ratelimited(
"SELinux: no sid found, defaulting to security isid for %s\n",
page);
sid = SECINITSID_SECURITY;
}
isec->sid = sid;
isec->initialized = LABEL_INITIALIZED;9.4 Tracefs与Debugfs
Android 把大量 tracing event path 标为 debugfs_tracing。Platform tests 还要求 tracefs target 具有 tracefs_type、debugfs target 具有 debugfs_type,并检查 /sys/kernel/debug 与 /sys/kernel/tracing 的物理路径边界。
10. Vendor扩展
10.1 设备规则
Vendor 可以在 device sepolicy 的 genfs_contexts 增加硬件特有 proc/sysfs 路径。规则进入 vendor policy CIL,最终与 platform policy 一起编译到内核加载的二进制 policy。
源码文件:device/google/cuttlefish/shared/sepolicy/vendor/genfs_contexts
# crosvm (x86)
cf_pci_gpu_device(/devices/pci0000:00)
genfscon sysfs /devices/platform/rtc_cmos/rtc u:object_r:sysfs_rtc:s0
genfscon sysfs /devices/virtual/mac80211_hwsim/hwsim0/net u:object_r:sysfs_net:s0
genfscon sysfs /devices/pci0000:00/0000:00:01.1/virtio1/net u:object_r:sysfs_net:s0
# common on all platforms / vm managers
genfscon sysfs /bus/iio/devices u:object_r:sysfs_iio_devices:s0新增规则时不应复制假想路径。先在设备上确认真实 mount filesystem type 和挂载内路径,再回到对应 device policy 文件。
10.2 Type属性
Proc target 应带 proc_type,sysfs target 应带 sysfs_type,BPF target 应带 bpffs_type。Platform test 会从编译 policy 枚举 genfs entries 并与 attribute 集合求差。
源码文件:system/sepolicy/tests/policy.py
def AssertGenfsFilesystemTypesHaveAttr(self, Filesystem, Attr):
TypesPol = self.QueryTypeAttribute(Attr, True)
TypesGenfs = set([
Type for _, _, Type in self.__GenfsDict[Filesystem]
])
violators = TypesGenfs.difference(TypesPol)
ret = ""
if len(violators) > 0:
ret += "The following types in " + Filesystem
ret += " must be associated with the "
ret += '"' + Attr + '": '
ret += " ".join(str(x) for x in sorted(violators)) + "\n"
return ret10.3 路径稳定性
Sysfs 路径中设备枚举号、bus topology 和 driver 目录可能随 kernel/device tree 变化。优先使用稳定 class、module parameter 或明确平台 ABI;若只能匹配动态前缀,应评估是否过宽地标注相邻节点。
10.4 平台兼容
Vendor 自己新增 type 和 rule 不等同于平台 genfs labels version。版本协商主要控制 system 提供的增量标签,防止新 system 在 OTA 后改变旧 vendor 可见标签。设备规则仍要与 vendor policy 同步发布。
11. 失败路径
11.1 Filesystem不匹配
规则写 genfscon sysfs ...,但目标实际位于 configfs、proc 或另一个 bind mount 时,不会命中。先用 stat -f -c %T、mountinfo 或内核源码确认 superblock type,不要只看 Android 全局路径前缀。
11.2 Path不匹配
Genfs path 从 mount root 开始。把 /sys/class/net 原样写成 genfscon sysfs /sys/class/net 会多一层 /sys,永远无法命中。Proc PID path 还会被内核特殊归一化。
11.3 缺少根规则
一个没有 fs_use 声明、也没有 genfscon fstype / 根规则的 filesystem 会得到 SECURITY_FS_USE_NONE。仅添加深层 path 而没有 root rule是否足以让根查询成功,取决于 policy 是否存在 / entry;Android platform 通常为每个 genfs filesystem提供根标签。
11.4 Unlabeled结果
Superblock 已启用 genfs,但具体 inode path 没有任何前缀匹配时,selinux_genfs_get_sid() 把 SID 设为 SECINITSID_UNLABELED 并返回成功。日志中的 target unlabeled 是缺少 genfs rule 的强信号。
11.5 Xattr覆盖
Sysfs path rule正确但设备标签与预期不同,应检查该 inode 是否有内核 xattr label。SE_SBGENFS_XATTR 允许 xattr 覆盖 genfs default;restorecon 的普通 file_contexts lookup仍不是来源。
11.6 版本CIL缺失
Vendor 声明 genfs labels version 202604,但 system 不安装 plat_sepolicy_genfs_202604.cil 时,init split policy 编译直接失败。Target-files merge 检查也会把该文件加入组合 sepolicy 命令。
11.7 已有Inode状态
Policy reload 后,并非所有已存在 inode 立刻有可用 dentry 重算 SID。内核会处理 superblock pending list,无 dentry 的 inode 暂时 invalid,之后在访问/revalidate 路径重新初始化。测试新 genfs rule 时,重新挂载或重启能减少旧 inode cache 干扰。
12. 测试证据
12.1 Attribute测试
Platform sepolicy_tests.py 对 bpf/proc/sysfs/debugfs/tracefs 分别检查 target attribute。它读取的是编译 policy 中的 genfs 表,不依赖文本文件顺序。
源码文件:system/sepolicy/tests/sepolicy_tests.py
def TestBpffsTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"bpf", "bpffs_type")
def TestProcTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"proc", "proc_type")
def TestSysfsTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"sysfs", "sysfs_type")
def TestDebugfsTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"debugfs", "debugfs_type")
def TestTracefsTypeViolations(pol):
return pol.AssertGenfsFilesystemTypesHaveAttr(
"tracefs", "tracefs_type")测试证明 type membership,不证明某个设备路径实际存在,也不验证运行时 xattr 覆盖。
12.2 Policy搜索
AOSP searchpolicy 可从二进制 policy 枚举 genfs entries,并按 filesystem 或 target type 过滤。
源码文件:system/sepolicy/tests/searchpolicy.py
if args.genfs:
for fs, path, context, _ in pol.QueryGenfs(
fs=args.fs, target=args.target):
print("genfscon " + fs + " " + path + " " + context)12.3 源码实验
# 按filesystem统计当前平台文本规则数量。
awk '$1 == "genfscon" { count[$2]++ }
END { for (fs in count) print fs, count[fs] }' \
system/sepolicy/private/genfs_contexts | sort
# 比较两个平台API快照,查看202604相对202504新增的genfs条目。
comm -13 \
<(rg '^\(genfscon' \
system/sepolicy/prebuilts/api/202504/202504_plat_sepolicy.cil | sort) \
<(rg '^\(genfscon' \
system/sepolicy/prebuilts/api/202604/202604_plat_sepolicy.cil | sort)第一条命令的关键断言是 proc=115、tracefs=103、sysfs=76;第二条应出现 binder hashed logs、BPF、ublk、Type-C 等新增规则。它们证明固定源码快照的规则集合发生变化,不证明目标设备最终选择了哪一个版本 CIL。
12.4 构建命令
# 选择真实产品,构建最终policy和属性测试。
source build/envsetup.sh
lunch PRODUCT-userdebug
m precompiled_sepolicy sepolicy_test \
genfs_labels_version.txt \
plat_sepolicy_genfs_202504.cil \
plat_sepolicy_genfs_202604.cil12.5 版本合并测试
Releasetools 单元测试构造 vendor version 202604 和 system CIL,断言组合 secilc 命令包含 plat_sepolicy_genfs_202604.cil。它证明 OTA/target-files merge 使用与 init 相同的附加文件,不证明具体条目权限。
源码文件:build/make/tools/releasetools/merge/test_merge_compatibility_checks.py
write_temp_file(
'vendor/etc/selinux/genfs_labels_version.txt', '202604')
write_temp_file(
'system/etc/selinux/plat_sepolicy_genfs_202604.cil')
self.assertIn(
'plat_sepolicy_genfs_202604.cil',
' '.join(cmds[0]))12.6 设备命令
# 查看真实 filesystem type 与挂载边界。
adb shell cat /proc/self/mountinfo | \
grep -E ' /proc | /sys |binder|bpf|tracefs'
# 查看当前 inode Context。
adb shell ls -Zd /proc/meminfo /proc/net/tcp
adb shell ls -Zd /sys/class/net /dev/binderfs/vndbinder
# 查看 vendor 声明的 genfs 标签版本。
adb shell cat /vendor/etc/selinux/genfs_labels_version.txt
# 从可访问的二进制policy枚举genfs,工具路径因产品而异。
adb shell su 0 cat /sys/fs/selinux/policy > /data/local/tmp/sepolicyls -Z 只能观察当前 inode SID;它不能告诉你是 genfs、xattr 还是 mount context 覆盖。必须结合 filesystem behavior 和 policy source 判断。
13. 源码导航
| 问题 | 首选源码 | 关键符号 |
|---|---|---|
| 平台规则在哪里 | system/sepolicy/private/genfs_contexts | proc/sysfs/binder/bpf条目 |
| 与fs_use如何分工 | system/sepolicy/private/fs_use | xattr/task/trans行为 |
| genfs如何进入policy | system/sepolicy/Android.bp | se_build_files |
| Board API如何进入M4 | system/sepolicy/build/soong/selinux.go | boardApiLevelToM4Macro |
| Vendor版本如何生成 | system/sepolicy/Android.bp | genfs_labels_version.txt |
| 版本如何读取 | system/sepolicy/compat/libgenfslabelsversion/src/lib.rs | get_genfs_labels_version |
| Init如何选择附加CIL | system/core/init/selinux.cpp | plat_sepolicy_genfs_*.cil |
| 兼容标签有哪些 | system/sepolicy/compat/ | plat_sepolicy_genfs_202504.cil 等 |
| Policydb如何排序 | kernel/common/security/selinux/ss/policydb.c | genfs_read |
| Fstype/path如何匹配 | kernel/common/security/selinux/ss/services.c | __security_genfs_sid |
| Superblock行为如何选 | 同上 | security_fs_use |
| Mount如何设置genfs flag | kernel/common/security/selinux/hooks.c | selinux_set_mnt_opts |
| Inode SID如何提交 | 同上 | selinux_genfs_get_sid、inode_doinit_with_dentry |
| Selinuxfs如何直接查询 | kernel/common/security/selinux/selinuxfs.c | selinux_policy_genfs_sid |
| Type属性如何测试 | system/sepolicy/tests/sepolicy_tests.py | Test*TypeViolations |
| 如何枚举编译结果 | system/sepolicy/tests/searchpolicy.py | --genfs |
以 /proc/net/tcp 为例复述完整路径时,应包括:proc superblock 设置 SE_SBPROC|SE_SBGENFS;security_fs_use() 通过 genfscon proc / 选择 GENFS behavior;inode 初始化构造 /net/tcp,policydb 中路径已按长度降序,所以 /net/tcp 先于 /net 与 / 命中;Context 转为 proc_net_tcp_udp SID并写入 inode security blob;后续 read/open 使用该 SID 做 AVC。若 vendor 依赖的平台新增标签受版本控制,还必须确认选中的 plat_sepolicy_genfs_<version>.cil 已进入 split policy 编译。这样才能从规则文本走到实际访问,而不是停留在 proc/sysfs 路径清单。
