Android 强烈建议 OEM 全面测试其 SELinux 实现。制造商在实现 SELinux 时,应先在一组测试设备上实施新政策。
实施新政策后,您可以通过执行 getenforce
命令来确认 SELinux 在设备上的运行模式是否正确。
该命令会输出全局 SELinux 模式:强制或宽容。如需确定每个域的 SELinux 模式,您必须检查相应的文件,或运行带有相应 (-p
) 标记的最新版 sepolicy-analyze
(位于 /platform/system/sepolicy/tools/
中)。
读取拒绝事件
检查是否有错误,错误会以事件日志的形式传给 dmesg
和 logcat
,并可在设备上从本地查看。制造商应先检查这些设备上传给 dmesg
的 SELinux 输出并优化设置,然后再在宽容模式下公开发布,最后切换到强制模式。SELinux 日志消息中包含“avc:
”字样,因此可使用 grep
轻松找到。您可以通过运行 cat /proc/kmsg
来获取当前的拒绝事件日志,也可以通过运行 cat /sys/fs/pstore/console-ramoops
来获取上次启动时的拒绝事件日志。
启动完成后,SELinux 错误消息会受到速率限制,以避免过多的日志。为了确保您可以看到所有相关消息,您可以运行 adb shell auditctl -r 0
将其停用。
根据这些输出内容,制造商可以轻松发现系统用户或组件违反 SELinux 政策的行为。然后,制造商便可更改相应软件和/或 SELinux 政策,以防范此类恶意行为。
具体来说,这些日志消息会指明在强制模式下哪些进程会失败以及失败原因。示例如下:
avc: denied { connectto } for pid=2671 comm="ping" path="/dev/socket/dnsproxyd" scontext=u:r:shell:s0 tcontext=u:r:netd:s0 tclass=unix_stream_socket
该输出的解读如下:
- 上方的
{ connectto }
表示执行的操作。根据它和末尾的tclass
(unix_stream_socket
),您可以大致了解是对什么对象执行什么操作。在此例中,是操作方正在试图连接到 UNIX 信息流套接字。 -
scontext (u:r:shell:s0)
表示发起相应操作的环境,在此例中是 shell 中运行的某个程序。 -
tcontext (u:r:netd:s0)
表示操作目标的环境,在此例中是归netd
所有的某个 unix_stream_socket。 - 顶部的
comm="ping"
可帮助您了解拒绝事件发生时正在运行的程序。在此示例中,给出的信息非常清晰明了。
我们再看看另一个示例:
adb shell su root dmesg | grep 'avc: '
输出:
<5> type=1400 audit: avc: denied { read write } for pid=177 comm="rmt_storage" name="mem" dev="tmpfs" ino=6004 scontext=u:r:rmt:s0 tcontext=u:object_r:kmem_device:s0 tclass=chr_file
以下是此拒绝事件的关键元素:
- 操作 - 试图进行的操作会使用括号突出显示:
read write
或setenforce
。 - 操作方 -
scontext
(来源环境)条目表示操作方;在此例中为rmt_storage
守护程序。 - 对象 -
tcontext
(目标环境)条目表示对哪个对象执行操作;在此例中为 kmem。 - 结果 -
tclass
(目标类别)条目表示操作对象的类型;在此例中为chr_file
(字符设备)。
转储用户和内核堆栈
在某些情况下,事件日志中包含的信息不足以查明拒绝事件的来源。通常,获取调用链(包括内核和用户空间)有助于更好地了解发生拒绝事件的原因。
最新的内核定义了一个名为 avc:selinux_audited
的跟踪点。使用 Android simpleperf
可启用此跟踪点并获取调用链。
支持的配置
- 支持 5.10 及更高版本的 Linux 内核(尤其是 Android 通用内核分支 mainline 和 android12-5.10),
也支持 android12-5.4 分支。您可以使用
simpleperf
来确定您的设备上是否定义了跟踪点:adb root && adb shell simpleperf list | grep avc:selinux_audited
。 对于其他内核版本,您可以择优挑选提交内容 dd81662 和 30969bc。 - 应该可以重现正在调试的事件。使用 simpleperf 时不支持启动时间事件;不过,您或许仍然可以重启服务以触发事件。
获取调用链
第一步是使用 simpleperf record
录制事件:
adb shell -t "cd /data/local/tmp && su root simpleperf record -a -g -e avc:selinux_audited"
接下来,应触发导致拒绝事件的事件。之后,应停止录制。在此例中应使用 Ctrl-c
获取样本:
^Csimpleperf I cmd_record.cpp:751] Samples recorded: 1. Samples lost: 0.
最后,可使用 simpleperf report
检查获取的堆栈轨迹。
例如:
adb shell -t "cd /data/local/tmp && su root simpleperf report -g --full-callgraph" [...] Children Self Command Pid Tid Shared Object Symbol 100.00% 0.00% dmesg 3318 3318 /apex/com.android.runtime/lib64/bionic/libc.so __libc_init | -- __libc_init | -- main toybox_main toy_exec_which dmesg_main klogctl entry_SYSCALL_64_after_hwframe do_syscall_64 __x64_sys_syslog do_syslog selinux_syslog slow_avc_audit common_lsm_audit avc_audit_post_callback avc_audit_post_callback
上面的调用链是一个统一的内核和用户空间调用链,可发起跟踪从用户空间直到内核中发生拒绝事件的位置,让您更好地查看代码流。如需详细了解 simpleperf
,请参阅 Simpleperf 可执行文件命令参考。
切换到宽容模式
SELinux 强制模式可以在 userdebug 或 eng build 中通过 ADB 停用。为此,请先运行 adb root
以将 ADB 切换为 root 权限。然后,如需停用 SELinux 强制模式,请运行以下命令:
adb shell setenforce 0
或在内核命令行中输入以下命令(适用于设备开发初期):
androidboot.selinux=permissive
androidboot.selinux=enforcing
或使用 Android 12 中的 bootconfig 功能:
androidboot.selinux=permissive
androidboot.selinux=enforcing
使用 audit2allow
audit2allow
工具可以获取 dmesg
拒绝事件并将其转换成相应的 SELinux 政策声明。因此,该工具有助于大幅加快 SELinux 开发速度。
如需使用该工具,请运行以下命令:
adb pull /sys/fs/selinux/policy
adb logcat -b events -d | audit2allow -p policy
不过,请务必仔细审核要添加到政策中的条目,以免出现权限过宽的情况。例如,如果将上面的 rmt_storage
拒绝事件输入到 audit2allow
中,会生成以下 SELinux 政策声明建议:
#============= shell ============== allow shell kernel:security setenforce; #============= rmt ============== allow rmt kmem_device:chr_file { read write };
这会授予 rmt
向内核内存写入内容的权限,从而形成明显的安全漏洞。通常情况下,audit2allow
给出的声明建议只是一个大致的基础。在添加这些声明后,您可能需要更改来源域和目标标签,并纳入适当的宏,才能实现良好的政策配置。有时,应对拒绝事件的合理方式不是更改政策,而是更改违规的应用。