到目前为止,我们已经在dmesg
中看到了 AVC 记录或 SELinux 拒绝消息,但是dmesg
是一个循环内存缓冲区,会根据内核的详细程度频繁翻转。通过使用审核内核子系统,我们可以将这些消息路由到用户空间,并将它们记录到磁盘上。在桌面上,执行此操作的守护进程称为auditd
。国家安全局的分支机构保留了一个最小的auditd
港口,但是它还没有正式并入 AOSP。我们将使用美国国家安全局分支机构的auditd
版本,因为我们正在开发安卓 4.3。截至 2014 年 4 月 7 日的正式合并版本可在https://android-review.googlesource.com/#/c/89645/找到。在logd
内实施,在https://android-review.googlesource.com/#/c/83526/合并。
在本章中,我们将:
- 用快节奏的 SE 为安卓开源社区 ( AOSP )更新我们的系统
- 调查审计子系统如何工作
- 学习阅读 SELinux 审计日志并开始编写策略
- 查看与日志相关的上下文
所有物流服务经理应将他们的信息记录到审核子系统中。然后,审核子系统可以使用printk
将消息路由到内核循环缓冲区,或者如果存在审核守护程序,则路由到用户空间中的审核守护程序。内核和用户空间日志守护程序使用AUDIT_NETLINK
套接字进行通信。我们将在本章中进一步剖析这个接口。
最后,审计子系统能够在发生策略违规时打印全面的记录。虽然您不需要该功能来启用和使用 SELinux,但它可以让您的生活更加轻松。要启用这个系统,必须使用auditd
,因为logd
目前没有这个支持。您需要用CONFIG_AUDITSYSCALL=y
构建您的内核,并在/data/misc/audit/
中放置一个audit.rules
文件。按照以下说明修补树后,阅读system/core/auditd/README
。
遗憾的是,UDOO 内核 3.0.35 版本不支持CONFIG_AUDITSYSCALL
。补丁位于https://git . kernel . org/cgit/Linux/kernel/git/stable/Linux-stable . git/commit/?id = 29ef 73 b7a 823 b77 a7 CD 0 BDD 7d 7 cded 3 FB 6 c 2587 b应启用支持。然而,在 UDOO 上,它导致了一个我们无法追踪的死锁。
虽然从谷歌发布的安卓 4.3 有针对安卓支持的 SE,但仍然有限,尤其是在审计领域。让它变得更可用的最简单的方法之一是从美国国家安全局的安卓 4.3 部门获得一些项目的补丁。在这里,社区已经部署了许多在 4.3 版本中没有合并的更高级的特性。
美国国家安全局在 https://bitbucket.org/seandroid/(T2)维持储存库。有许多项目,因此弄清楚使用哪个和哪个分支可能会令人望而生畏。找到它们的一种方法是遍历每个项目,找到带有`SEAndroid-4.3`分支的项目。你不需要深入设备树,因为我们不是在建造 AOSP 设备。这类项目的清单如下:
- https://bitbucket.org/seandroid/system-core
- https://bitbucket.org/seandroid/frameworks-base
- https://bit bucket . org/seandroid/external-libselinux
- https://bitbucket.org/seandroid/build
- https://bitbucket.org/seandroid/frameworks-native
我们也可以安全地跳过sepolicy
,因为我们已经将其更新到出血边缘,但是内核有点棘手。我们需要从内核普通(https://bitbucket.org/seandroid/kernel-common)和绑定补丁(https://android-review.googlesource.com/#/c/45984/的变化,可以达到如下:
$ mkdir ~/sepatches
$ cd ~/sepatches
$ git clone https://bitbucket.org/seandroid/system-core.git
$ git clone https://bitbucket.org/seandroid/frameworks-base.git
$ git clone https://bitbucket.org/seandroid/external-libselinux.git
$ git clone https://bitbucket.org/seandroid/build.git
$ git clone https://bitbucket.org/seandroid/frameworks-native.git
我们可以通过查看build/core/build_id.mk
文件,并使用网页https://source.android.com/source/build-numbers.html进行查找,来找出需要修补的确切版本。
文件显示BUILD_ID
是JSS15J
,查找显示我们正在为 UDOO 使用android-4.3_r2.1
版本。
对于到目前为止下载的每个项目,通过运行命令git checkout origin/seandroid-4.3_r2
来生成补丁。最后执行git format-patch origin/jb-mr2.0-release
。由于没有4.3._r2.1
分公司,我们使用r2
。
对于这些补丁中的每一个,你将需要从它们对应的udoo/<project>
文件夹在树中应用它们。从0001*
补丁开始,到0002*
等等,按照数字顺序为每个项目应用补丁是很重要的。作为一个如何为项目应用特定补丁的例子,我们来看看system-core
需要的第一个补丁。请注意,这些 Git 存储库使用连字符代替源树中的斜线;所以frameworks-base
与frameworks/base
相关。
首先,生成补丁:
$ cd sepatches/system-core
$ git checkout origin/seandroid-4.3_r2
$ git format-patch origin/jb-mr2.0-release
应用第一个补丁,如下所示:
$ cd <udoo_root>/system/core
$ patch -p1 < ~/sepatches/system-core/0001-Add-writable-data-space-for-radio.patch
patching file rootdir/init.rc
Reversed (or previously applied) patch detected! Assume -R? [n]
注意,对于 UDOO,重要的是不要在frameworks/base
中应用高于0005
的补丁号。对于其他项目,您应该应用所有的补丁。
请注意错误。只要点击 Ctrl + C 就可以了,看到这个就退出打补丁过程。Git 树并不十分完美,正因为如此,一些补丁已经在 UDOO 源代码中了。补丁命令会让我们知道,当警告时,我们可以通过 Ctrl + C 取消它们来跳过它们。继续浏览补丁,取消已经应用的补丁,修复任何故障。修补用户空间后,强烈建议您构建*,以确保没有任何损坏。*
*一旦用户空间被完全修补,我们需要修补内核。首先用git clone https://bitbucket.org/seandroid/kernel-common.git
命令从 Bitbucket 克隆内核通用项目。除了 binder 补丁之外,我们将使用与其他项目相同的方法来修补内核。通过查看所述活页夹补丁的链接,https://android-review.googlesource.com/#/c/45984/,我们发现 Git SHA 哈希是a3c9991b560cf0a8dec1622fcc0edca5d0ced936
,如下图截图中的补丁集 4 参考字段所示:
然后我们可以为这个 SHA 哈希生成补丁:
$ git format-patch -1 a3c9991b560cf0a8dec1622fcc0edca5d0ced936
0001-Add-security-hooks-to-binder-and-implement-the-hooks.patch
然后,像之前一样,使用 patch 命令应用该修补程序。该修补程序包含头文件失败的块;就像其他人一样使用拒绝文件来修复它。当您构建时,您会在内核中得到这个错误:
security/selinux/hooks.c:1846:9: error: variable 'sad' has initializer but incomplete type
security/selinux/hooks.c:1846:28: error: storage size of 'sad' isn't known
继续,删除这一行和所有引用。这是 3.0 内核中的一个变化:
struct selinux_audit_data sad = {0,};
ad.selinux_audit_data = &sad;
我们通过查看原始的 3.0 补丁解决了这个问题,这些补丁可以在以下链接中找到:
大家记得,UDOO 使用了一个自定义init.rc
。我们需要将对init.rc
的任何更改添加到 UDOO 实际使用的更改中。所有可以修改init.rc
的补丁都在系统核心项目中,具体如下:
0003-Auditd-initial-commit.patch
0007-Handle-policy-reloads-within-ueventd-rather-than-res.patch
0009-Allow-system-UID-to-set-enforcing-and-booleans.patch
继续并在这些补丁中找到对init.rc
的更改,并使用相同的补丁技术将它们应用于device/fsl/imx6/etc/init.rc
。
在前一节,我们做了大量的打补丁;其目的是在 Android 及其依赖项上实现审计集成工作。这些补丁还修复了代码中的一些错误,非常重要的是,启用了 SELinux/LSM 绑定器挂钩和策略控制。
Linux 中的审计系统被 LSMs 用来打印拒绝记录以及收集非常彻底和完整的事件记录。无论如何,当 LSM 打印一条消息时,它会被传播到审核子系统并被打印。但是,如果审计子系统已经启用,那么您将获得更多与拒绝相关的上下文。审计子系统甚至支持加载规则来观察这一点。例如,您可以观察所有不是由系统 UID 完成的对/system
的写入。
auditd
守护进程或服务在用户空间中运行,并通过 NETLINK 套接字监听审计子系统。守护进程注册自己来接收内核消息,还可以通过这个套接字加载审计规则。注册后,auditd
守护程序接收所有审计事件。auditd
守护进程被最小化移植,有人试图将其引入安卓系统,但后来被拒绝。然而,auditd
已经被各种原始设备制造商(如三星)和美国国家安全局的 4.3 部门使用。将记录放入 logcat 的另一种方法后来被合并到安卓系统中(更多信息,请参考https://android-review.googlesource.com/89645)。
之前我们在dmesg
看到了 SELinux 发来的 AVC 拒绝消息。这样做的问题是,当你有很多拒绝或者一个健谈的内核时,循环内存日志很容易翻车。通过auditd
,所有的消息都会到达守护进程并被写入/data/misc/audit/audit.log
文件。此日志文件,在此称为audit.log
,可能存在于设备引导中,并被旋转到/data/misc/audit/audit.old
文件中,称为audit.old
。守护进程继续记录到一个新的audit.log
文件。当超过大小阈值AUDITD_MAX_LOG_FILE_SIZEKB
(在编译期间在system/core/auditd/Android.mk
文件中设置)时,该旋转事件发生。该阈值通常为 1000 千字节,但可以在设备的makefile
中更改。此外,用kill
发送SIGHUP
将导致旋转,如下例所示。
验证守护进程正在运行,并获取其 PID:
root@udoo:/ # ps -Z | grep audit
u:r:auditd:s0 audit 2281 1 /system/bin/auditd
u:r:kernel:s0 root 2293 2 kauditd
验证仅存在一个日志:
root@udoo:/ # ls -la /data/misc/audit/
-rw-r----- audit system 79173 1970-01-02 00:19 audit.log
旋转原木:
root@udoo:/ # kill -SIGHUP 2281
验证audit.old
:
root@udoo:/ # ls -la /data/misc/audit/
-rw-r----- audit system 319 1970-01-02 00:20 audit.log
-rw-r----- audit system 79173 1970-01-02 00:19 audit.old
由于的auditd
和libaudit
代码来自 Linux 桌面有一个 GPL 许可证,所以对 Android 进行了重写,在 Apache 许可证下发布。重写是最小的,因此您将只找到支持守护进程所需的函数。但是功能接口和头接口应该保持一致。
auditd
守护进程在system/core/auditd.c
的main()
开始生活。它迅速将其权限从 UID 根更改为特殊的auditd
UID。当它这样做时,它保留CAPSYS_AUDIT
,这是使用AUDIT
NETLINK 插座所需的数模转换器能力检查。它通过调用drop_privileges_or_die()
来实现这一点。从那里,它用getopt()
进行一些选项解析,我们最终得到特定于审计的调用,第一个调用使用audit_open()
打开 NETLINK 套接字。这个函数只是调用socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT)
,打开一个文件描述符到 NETLINK 套接字。打开套接字后,守护进程打开audit.log
的句柄,并调用audit_log_open(const char *logfile, const char *rotatefile, size_t threshold)
。该功能检查audit.log
文件是否存在,如果存在,将其重命名为audit.old
。然后,它会创建一个新的空日志文件来记录数据。
下一步是向审核子系统注册守护程序,以便它知道向谁发送消息。通过设置守护进程的 PID,您可以确保只有这个守护进程会获得消息。因为 NETLINK 可以支持很多读者,所以你不希望一个“流氓auditd
”去阅读消息。这样,守护进程调用audit_set_pid(audit_fd, getpid(), WAIT_YES)
,其中audit_fd
是来自audit_open()
的 NETLINK 套接字,getpid()
返回守护进程的 PID,WAIT_YES
导致守护进程阻塞,直到操作完成。接下来,守护程序通过调用audit_set_enabled(audit_fd, 1)
启用审计子系统的高级功能,并通过audit_rules_read_and_add(audit_fd, AUDITD_RULES_FILE)
向审计子系统添加规则。该函数从该文件中读取规则,格式化一些结构,并将这些结构发送给内核。
audit_set_enabled()
和audit_rules_read_and_add()
只有在内核是用CONFIG_AUDITSYSCALL
构建的情况下才有效果。此后,守护进程检查是否指定了-k
选项。-k
选项告诉auditd
在dmesg
中查找任何遗漏的审计记录。这样做是因为在循环缓冲区溢出之前捕获审计记录和用户空间启动许多服务、生成审计事件和违反策略之间存在竞争。本质上,这有助于将早期引导的审核事件合并到相同的日志文件中。
此后,守护进程进入一个循环,从 NETLINK 套接字读取消息,格式化消息,并将其写入日志文件。它通过使用poll()
在 NETLINK 套接字上等待 IO 来启动该循环。如果poll()
出错退出,循环继续检查quit
变量。如果EINTR
被激活,在信号处理程序中循环保护quit
被设置为true
,守护进程退出。如果poll()
是 NETLINK 上的数据,守护进程调用audit_get_reply(audit_fd, &rep, GET_REPLY_BLOCKING, 0)
,用rep
参数返回一个audit_reply
结构。然后用audit_log_write(alog, "type=%d msg=%.*s\n", rep.type, rep.len, rep.msg.data)
将audit_reply
结构(带格式)写入audit.log
文件。它一直这样做,直到EINTR
被引发,此时守护进程退出。
守护进程退出时,会清除在内核中注册的 PID(audit_set_pid(audit_fd, 0)
),通过audit_close()
(实际上只是 syscall,close(audit_fd)
)关闭审核套接字,用audit_log_close()
关闭audit.log
。audit_log_*
系列功能不是要审计的 GPLed 接口的一部分,而是自定义编写的。
当谷歌将auditd
移植到安卓的logd
基础设施时,它使用了守护进程的main()
使用的相同函数和库代码,并将其打包到logd
中。但是,谷歌并没有带audit_set_enabled()
和audit_rules_read_and_add()
功能。
SELinux 拒绝被路由到内核审计子系统、auditd
,最后到达audit.log
和audit.old
。随着日志驻留在audit.log
中,让我们把这个文件拉到adb
上,仔细看看。
在启用adb
的情况下,从主机运行以下命令:
$ adb pull /data/misc/audit/audit.log
现在,让我们跟踪该文件并查找以下几行:
$ tail audit.log
...
type=1400 msg=audit(88526.980:312): avc: denied { getattr } for pid=3083 comm="adbd" path="/data/misc/audit/audit.log" dev=mmcblk0p4 ino=42 scontext=u:r:adbd:s0 tcontext=u:object_r:audit_log:s0 tclass=file
type=1400 msg=audit(88527.030:313): avc: denied { read } for pid=3083 comm="adbd" name="audit.log" dev=mmcblk0p4 ino=42 scontext=u:r:adbd:s0 tcontext=u:object_r:audit_log:s0 tclass=file
type=1400 msg=audit(88527.030:314): avc: denied { open } for pid=3083 comm="adbd" name="audit.log" dev=mmcblk0p4 ino=42 scontext=u:r:adbd:s0 tcontext=u:object_r:audit_log:s0 tclass=file
这里的记录包括两个主要部分:type
和msg
。type
字段指示它是什么类型的消息。类型为 1400 的消息是 AVC 消息,这是 SELinux 拒绝消息(也有其他类型)。前面政策的msg
(短消息)部分包含了我们要分析的部分。
我们执行的最后一个命令是adb pull /data/misc/audit/aduit.log
,如您所见,我们在audit.log
文件的尾部有一些adb
政策违规。让我们从这个事件开始:
type=1400 msg=audit(88526.980:312): avc: denied { getattr } for pid=3083 comm="adbd" path="/data/misc/audit/audit.log" dev=mmcblk0p4 ino=42 scontext=u:r:adbd:s0 tcontext=u:object_r:audit_log:s0 tclass=file
我们可以看到comm
字段是adbd
。然而,信任这个值是不明智的,因为它可以使用prctl()
界面从用户空间进行控制。只能看作是一种暗示。最好的验证方法是使用ps -Z
检查 PID:
# ps -Z | grep adbd
u:r:adbd:s0 root 3083 1 /sbin/adbd
通过验证守护程序,我们现在可以更详细地检查消息了。该消息由以下字段组成(可选字段由*
标识):
avc: denied
:这部分会一直发生,表示是否认记录。{ permission }
:这是被拒绝的权限,在本例中为getattr
。for
:这将始终被打印,并使输出可读。Path*
:这是可选字段,包含问题对象的路径。它只对拒绝文件系统访问有意义。dev*
:这是可选字段,用于标识已装载文件系统的数据块设备。它只对拒绝文件系统访问有意义。ino*
:这是文件的可选索引节点。只有 Linux 中的匿名文件会打印索引节点。它只对拒绝文件系统访问有意义。tclass
:这是对象的目标类,在我们的例子中是file
。
在这个点上,我们需要理解否认记录的msg
部分在非常精确的水平上告诉我们什么。据说安卓调试桥守护程序希望能够在我们的策略文件上调用getattr
。几个事件下来,我们会看到它还想要read
和open
。这就是跑步的副作用adb pull
。getattr
权限拒绝来自stat()
系统调用,read/open
来自read()
和open()
系统调用。如果您希望在策略中允许这种情况,这将是基于您的威胁模型的安全决策,您应该添加:
allow adbd audit_log:file { getattr read open };
或者,使用global_macros
中定义的宏集:
allow adbd audit_log:file r_file_perms;
大多数时候,文件权限访问应该使用global_macros
中定义的宏。通常,一个接一个地添加它们是非常耗时和乏味的。宏在类似于读取、写入和执行数模转换器权限的上下文中对权限进行分组。例如,如果你给它open
和read
,在某个时候源域很有可能需要统计这个文件。所以r_file_perms
宏已经有这些权限了。
你应该把这条规则加到external/sepolicy/adbd.te
上。.te
文件(也称为type enforcement
文件)是由源上下文组织的,因此请确保将其添加到正确的文件中。我们不建议添加此允许规则——没有合法的理由adbd
需要访问审核日志——我们可以放心地使用dontaudit
规则忽略这些:
dontaudit adbd audit_log:file r_file_perms;
dontaudit
规则是一项政策声明,表示不要审核(打印)符合该规则的否认。
如果你不确定该怎么做,最好的建议是利用邮件列表来支持安卓、SELinux 和审计。只需保持邮件适合特定的邮件列表主题。
有一个叫audit2allow
的工具,可以帮你写策略允许规则。然而,它只是一个工具,可能被滥用。它将策略文件转换为策略的允许规则:
$ cat audit.log | audit2allow
#============= adbd ==============
allow adbd audit_log:file { read getattr open };
如果您真的想将此允许规则添加到策略文件中,则audit2allow
工具不是宏感知的或感知的。只有政策制定者才能做出这个决定。
还有一个启用r_file_*
宏映射的工具叫做fixup.py
。你可以在https://bitbucket.org/billcroberts/fixup/overview拿到工具。下载后,使其可执行,并将其放在可执行路径中的某个位置:
$ chmod a+x fixup.py
$ cat audit.log | audit2allow | fixup.py
#============= adbd ==============
allow adbd audit_log:file r_file_perms;
在最简单的意义上,编写策略只是识别策略违规并将适当的允许规则添加到策略文件中的活动。然而,为了使 SELinux 有效,源和目标上下文必须正确。如果不是,则允许规则毫无意义。
您可能会遇到的第一件事是目标类型未标记的拒绝。在这种情况下,需要设置合适的目标标签(参见第十一章、标签属性)。此外,过程标签可能是错误的。多个进程可以属于一个域,除非通过策略显式完成,否则父进程的子进程将继承父进程的域。然而,在安卓系统中,有多个进程的域相当有限。在init
、system_server
、adbd
、auditd
、debuggerd
、dhcp
、servicemanager
、vold
、netd
、surfaceflinger
、drmserver
、mediaserver
、installd
、keystore
、sdcardd
、wpa
和zygote
域中永远看不到多个进程。
可以在以下领域看到多个流程:
system_app
untrusted_app
platform_app
shared_app
media_app
release_app
isolated_app
shell
在发布的设备上,任何东西都不应该在su
、recovery
和init_shell
域中运行。下表提供了域到预期可执行文件和基数的完整映射:
领域
|
可执行文件
|
基数(N)
|
| --- | --- | --- |
| u:r:init:s0"
| /init
| N == 1
|
| u:r:ueventd:s0
| /sbin/ueventd
| N == 1
|
| u:r:healthd:s0
| /sbin/healthd
| N == 1
|
| u:r:servicemanager:s0
| /system/bin/servicemanager
| N == 1
|
| u:r:vold:s0
| /system/bin/vold
| N == 1
|
| u:r:netd:s0
| /system/bin/netd
| N == 1
|
| u:r:debuggerd:s0
| /system/bin/debuggerd, /system/bin/debuggerd64
| N == 1
|
| u:r:surfaceflinger:s0
| /system/bin/surfaceflinger
| N == 1
|
| u:r:zygote:s0
| zygote, zygote64
| N == 1
|
| u:r:drmserver:s0
| /system/bin/drmserver
| N == 1
|
| u:r:mediaserver:s0
| /system/bin/mediaserver
| N >= 1
|
| u:r:installd:s0
| /system/bin/installd
| N == 1
|
| u:r:keystore:s0
| /system/bin/keystore
| N == 1
|
| u:r:system_server:s0
| system_server
| N ==1
|
| u:r:sdcardd:s0
| /system/bin/sdcard
| N >=1
|
| u:r:watchdogd:s0
| /sbin/watchdogd
| N >=0 && N < 2
|
| u:r:wpa:s0
| /system/bin/wpa_supplicant
| N >=0 && N < 2
|
| u:r:init_shell:s0
| null
| N == 0
|
| u:r:recovery:s0
| null
| N == 0
|
| u:r:su:s0
| null
| N == 0
|
几个 兼容性测试套件 ( CTS )测试已经围绕此编写完成,并在https://android-review.googlesource.com/#/c/82861/提交给 AOSP。
基于这些关于好政策应该是什么样子的一般性断言,让我们来评估一下我们的政策。
首先,我们将检查未标记的对象。从主机上,使用您通过adb pull
获得的audit.log
文件:
$ cat audit.log | grep unlabeled
...
type=1400 msg=audit(86527.670:341): avc: denied { rename } for pid=3206 comm="pool-1-thread-1" name="com.android.settings_preferences.xml" dev=mmcblk0p4 ino=129664 scontext=u:r:system_app:s0 tcontext=u:object_r:unlabeled:s0 tclass=file
...
看起来我们有一些文件和其他东西没有正确标记;我们将在第 11 章、标签属性中解决这些问题。现在,让我们检查应该有多个进程的域,并在这些域中找到不合适的二进制文件(有关完整的映射,请参考前面的表。)
初始化:
$ adb shell ps -Z | grep u:r:init:s0
u:r:init:s0 root 1 0 /init
u:r:init:s0 root 2267 1 /sbin/watchdogd
合子:
$ adb shell ps -Z | grep u:r:zygote:s0
u:r:zygote:s0 root 2285 1 zygote
$ adb shell ps -Z | grep u:r:init_shell
u:r:init_shell:s0 root 2278 1 /system/bin/sh
… through all domains
这样做之后,我们发现了问题,因为有东西在init_shell
域中运行,watchdogd
在init
域中。这些必须纠正。
写sepolicy
相对容易,写好政策是一门艺术,需要政策作者理解allow
规则的体系和含义。策略本身是一种元编程语言,这种语言控制用户空间和内核如何相处,并且与任何程序一样,策略可以被设计为特定的用途。政策可能漏洞太多(基本上没有用),或者非常严格,很难在不破坏已经起作用的部分的情况下改变。
一个好的政策需要保持系统预期的正常功能,所以对安卓系统中所有系统的彻底测试是必不可少的。CTS 对锻炼安卓有很大帮助,但往往不能涵盖所有情况;建议进行用户测试。在下一章中,我们将介绍文件系统和文件系统对象如何获得它们的安全标签,以及我们如何更改它们。稍后,我们将讨论如何使用 CTS 作为工具来测试系统并为预期行为生成策略违规。*