5、SELinux基础概念:TE模型、安全上下文与策略规则

好,咱们今天来聊聊SELinux最核心的几个概念。说实话,我刚接触SELinux那会儿,也被这些术语搞得头大。什么TE模型、安全上下文、allow规则……感觉像在看天书。但后来我发现,只要抓住几个关键点,这东西其实没那么玄乎。

咱们先搭个框架,看看今天要讲的内容之间的关系:

SELinux 核心概念体系 SELinux 安全机制 TE 模型 安全上下文 策略规则 主体(Subject) → 客体(Object) 类型强制访问控制 user:role:type:range 每个进程/文件都有标签 allow / neverallow / auditallow 决定谁能访问谁 三者结合:TE模型定义框架 → 安全上下文标记对象 → 策略规则控制访问

5.1 TE模型——SELinux的骨架

TE,全称是Type Enforcement,中文叫「类型强制」。说白了,它就是SELinux的核心思想:所有东西都被贴上类型标签,然后根据标签决定能不能访问

我打个比方你就明白了。想象一下你公司的大楼:

  • 每个人(进程)都挂着一个工牌,上面写着「研发部」、「HR」、「保安」——这就是主体(Subject)的类型
  • 每个房间(文件、设备)门口也有牌子,写着「研发办公室」、「档案室」、「机房」——这就是客体(Object)的类型
  • 保安能不能进研发办公室?看规则怎么说——这就是策略规则

在TE模型里,主体通常是进程客体是文件、目录、套接字、设备等。每个主体和客体都有一个安全上下文,里面包含了它们的类型信息。

核心思想:TE模型不关心你是谁(root还是普通用户),只关心你是什么类型(type)。一个root进程如果类型不对,照样访问不了文件。

我记得有一次帮客户排查问题,他们死活想不通:为什么root用户启动的服务,连自己的配置文件都读不了?我一看,进程的type是untrusted_app,文件的type是system_file,中间没有allow规则——那当然不行。root在SELinux面前,有时候真不好使。

5.2 安全上下文——对象的身份证

安全上下文(Security Context),你可以把它理解成每个进程和文件上的「身份证」。在Android系统里,你用ls -Z就能看到文件的身份证,用ps -Z能看到进程的身份证。

一个完整的安全上下文长这样:

u:r:init:s0
u:object_r:system_file:s0
u:r:untrusted_app:s0:c512,c768

它由四个字段组成,用冒号隔开:

字段 含义 示例 说明
user 用户 u Android里基本都是 u,表示SELinux用户
role 角色 r / object_r r 用于进程,object_r 用于文件
type 类型 init, system_file 最关键的字段,决定访问权限
range 安全级别 s0, s0:c512,c768 MLS多级安全,Android常用s0

嗯,这里要注意:type字段是TE模型的核心。策略规则里写的allow init system_file:file read,意思就是「允许类型为init的进程,读取类型为system_file的文件」。

小技巧:我习惯用 ls -Z /system/bin/ps -Z 来快速查看系统里各种对象的类型。遇到权限问题,第一步就是看类型对不对得上。

5.3 策略规则语法——allow、neverallow、auditallow

有了TE模型和安全上下文,接下来就是制定规则了。SELinux的策略规则,说白了就是回答一个问题:谁(主体类型)可以对什么(客体类型)做什么(操作)?

5.3.1 allow——允许访问

这是最常用的规则。语法如下:

allow 主体类型 客体类型:客体类别 操作集合;

举个例子:

# 允许 init 进程读取 system_file 类型的文件
allow init system_file:file read;

# 允许 init 进程对 device 类型的目录进行搜索和读取
allow init device:dir { search read };

# 允许 untrusted_app 访问网络
allow untrusted_app self:netlink_socket create_socket_perms;

这里有几个关键点:

  • 客体类别(class):比如 file(文件)、dir(目录)、socket(套接字)、binder(Android的Binder通信)等
  • 操作集合(permissions):比如 read、write、execute、create、open 等
  • 多个操作可以用花括号括起来,用空格隔开

我在项目中遇到过最典型的场景:给第三方应用加权限。厂商说「我的app需要读某个系统文件」,结果一查,app的type是untrusted_app,目标文件是system_data_file,中间没有allow规则。加一条规则就解决了:

allow untrusted_app system_data_file:file read;

但要注意——别随便加allow规则。加得越多,安全风险越大。

5.3.2 neverallow——绝对禁止

neverallow 是SELinux里的「红线」。它表示:任何策略都不能允许这个操作。如果编译时发现有人写了allow规则违反了neverallow,编译直接报错。

# 绝对不允许普通应用访问内核文件
neverallow untrusted_app kernel_file:file read;

# 绝对不允许任何域(domain)对安全策略文件进行写操作
neverallow domain security_file:file write;

为什么要用neverallow?说白了,就是防止有人「手滑」或者「故意」开了后门。比如系统核心的安全策略文件,谁都不能改。你写个neverallow,编译阶段就能拦住。

避坑指南:我曾经见过一个团队,为了临时解决问题,在neverallow规则上打了补丁。结果编译是过了,但安全审计直接被否决。记住:neverallow是底线,不要试图绕过它。如果确实需要,先跟安全团队确认,再修改neverallow本身。

5.3.3 auditallow——记录审计日志

auditallow 的作用是:允许某个操作,并且记录到审计日志里。它和allow的区别在于:allow只允许,不记录;auditallow既允许又记录。

# 允许 init 执行某个文件,并记录日志
auditallow init system_file:file execute;

你可能会问:为什么要用auditallow?

  • 调试阶段:想知道某个操作是否真的发生了,可以用auditallow来跟踪
  • 安全审计:某些敏感操作(比如提权、访问用户数据),需要留下日志记录
  • 合规要求:某些行业标准要求记录所有特权操作

我个人习惯在调试新规则时,先用auditallow代替allow。这样既能验证功能,又能看到日志输出。确认没问题后,再改成allow。

5.4 三个规则的关系

咱们用一张表总结一下:

规则 作用 是否记录日志 使用场景
allow 允许访问 正常授权
neverallow 绝对禁止 编译时检查 安全红线
auditallow 允许并记录 调试/审计

嗯,这里再补充一点:neverallow是编译时检查,不是运行时。如果你在运行时违反了neverallow,系统不会报错——因为根本编译不过。所以写策略的时候,一定要跑一遍完整的编译检查。

5.5 实战小例子

咱们来看一个完整的例子。假设我有一个自定义服务,类型叫my_service,它需要读取/data/my_config.conf这个文件,文件的类型是my_config_file

第一步,定义类型(在my_service.te文件里):

type my_service, domain;
type my_config_file, file_type, data_file_type;

第二步,写allow规则:

# 允许 my_service 读取 my_config_file 类型的文件
allow my_service my_config_file:file read;

# 允许 my_service 搜索 /data 目录(因为文件在 /data 下)
allow my_service data_file:dir search;

第三步,加上审计(调试阶段):

# 记录读取操作,方便排查问题
auditallow my_service my_config_file:file read;

第四步,检查有没有违反neverallow:

# 确保 my_service 不是 untrusted_app 的子类型
# 否则会违反 neverallow untrusted_app data_file:file read

你看,一套流程下来,思路很清晰。我在实际项目中就是这么干的,从来没出过问题。

总结一下:TE模型是骨架,安全上下文是标签,策略规则是法律。三者缺一不可。写规则的时候,记住「最小权限原则」——只给必要的权限,多一条都不要给。


公众号:蓝海资料掘金营,微信deep3321