自定义 SELinux

集成基本级别的 SELinux 功能并全面分析结果后,您可以添加自己的政策设置,以便涵盖对 Android 操作系统所做的自定义。这些政策必须仍然满足 Android 兼容性计划的要求,并且不得移除默认的 SELinux 设置。

制造商不得移除现有的 SELinux 政策,否则可能会破坏 Android SELinux 的实施方式及其管控的应用。这包括可能需要改进以遵守政策并正常运行的第三方应用。应用必须无需任何修改即可继续在启用了 SELinux 的设备上正常运行。

当开始自定义 SELinux 时,请注意:

  • 为所有新的守护进程编写 SELinux 政策
  • 尽可能使用预定义的域
  • 为作为 init 服务衍生的所有进程分配域
  • 在编写政策之前先熟悉相关的宏
  • 向 AOSP 提交对核心政策进行的更改

同时,谨记下列禁忌:

  • 不得创建不兼容的政策
  • 不得允许对最终用户政策进行自定义
  • 不得允许对移动设备管理 (MDM) 政策进行自定义
  • 不得恐吓违反政策的用户
  • 不得添加后门程序

要查看具体要求,请参阅 Android 兼容性定义文档中的“内核安全功能”部分。

SELinux 采用白名单方法,这意味着只能授予政策中明确允许的访问权限。由于 Android 的默认 SELinux 政策已经支持 Android 开放源代码项目,因此您无需以任何方式修改 SELinux 设置。如果您要自定义 SELinux 设置,则应格外谨慎,以免破坏现有应用。要开始使用,请按下列步骤操作:

  1. 使用最新的 Android 内核
  2. 采用最小权限原则
  3. 仅针对您向 Android 添加的内容调整 SELinux 政策。默认政策能够自动适用于 Android 开源项目代码库。
  4. 将各个软件组件拆分成多个负责执行单项任务的模块。
  5. 创建用于将这些任务与无关功能隔离开来的 SELinux 政策。
  6. 将这些政策放在 /device/manufacturer/device-name/sepolicy 目录下的 *.te 文件(SELinux 政策源代码文件的扩展)中,然后使用 BOARD_SEPOLICY 变量将其纳入到您的细分版本。
  7. 先将新域设为宽容域。为此,可以在该域的 .te 文件中使用宽容声明。
  8. 分析结果并优化域定义。
  9. 当 userdebug 版本中不再出现拒绝事件时,移除宽容声明。

完成 SELinux 政策更改的集成工作后,请在开发工作流程中添加一个步骤,以确保向前兼容 SELinux。在理想的软件开发过程中,SELinux 政策只会在软件模型发生变化时才需要更改,而不会在实际的实施方式变化时更改。

当您开始自定义 SELinux 时,首先要审核自己向 Android 添加的内容。如果添加的是执行新功能的组件,则在开启强制模式之前,您需要先确认该组件是否符合 Android 的安全政策,以及原始设备制造商 (OEM) 制定的所有相关政策。

为了防止出现不必要的问题,与其过度限制和不兼容,不如过度宽泛和过度兼容,因为前者会导致设备功能损坏。不过,如果您的更改能够惠及其他人,则应将这些更改作为补丁程序提交至默认 SELinux 政策。如果相应补丁程序已应用于默认安全政策,您将不需要针对每个新的 Android 版本进行此项更改。

政策声明示例

SELinux 基于 M4 计算机语言,因此支持多种有助于节省时间的宏。

在以下示例中,所有域都被授予向 /dev/null 读写数据的权限以及从 /dev/zero 读取数据的权限。

# Allow read / write access to /dev/null
allow domain null_device:chr_file { getattr open read ioctl lock append write};

# Allow read-only access to /dev/zero
allow domain zero_device:chr_file { getattr open read ioctl lock };

此声明也可以通过 SELinux *_file_perms 宏编写(简短版):

# Allow read / write access to /dev/null
allow domain null_device:chr_file rw_file_perms;

# Allow read-only access to /dev/zero
allow domain zero_device:chr_file r_file_perms;

政策示例

以下是一个完整的 DHCP 政策示例,我们将在下文中对其进行分析:

type dhcp, domain;
permissive dhcp;
type dhcp_exec, exec_type, file_type;
type dhcp_data_file, file_type, data_file_type;

init_daemon_domain(dhcp)
net_domain(dhcp)

allow dhcp self:capability { setgid setuid net_admin net_raw net_bind_service
};
allow dhcp self:packet_socket create_socket_perms;
allow dhcp self:netlink_route_socket { create_socket_perms nlmsg_write };
allow dhcp shell_exec:file rx_file_perms;
allow dhcp system_file:file rx_file_perms;
# For /proc/sys/net/ipv4/conf/*/promote_secondaries
allow dhcp proc_net:file write;
allow dhcp system_prop:property_service set ;
unix_socket_connect(dhcp, property, init)

type_transition dhcp system_data_file:{ dir file } dhcp_data_file;
allow dhcp dhcp_data_file:dir create_dir_perms;
allow dhcp dhcp_data_file:file create_file_perms;

allow dhcp netd:fd use;
allow dhcp netd:fifo_file rw_file_perms;
allow dhcp netd:{ dgram_socket_class_set unix_stream_socket } { read write };
allow dhcp netd:{ netlink_kobject_uevent_socket netlink_route_socket
netlink_nflog_socket } { read write };

下面我们来分析一下该示例:

在第一行(即类型声明)中,该政策声明 DHCP 守护进程将沿用基本的安全政策 (domain)。在前面的声明示例中,DHCP 可以向 /dev/null 读写数据。

在第二行中,DHCP 被声明为宽容域。

init_daemon_domain(dhcp) 这一行中,该政策声明 DHCP 是从 init 衍生而来的,并且可以与其通信。

net_domain(dhcp) 这一行中,该政策允许 DHCP 使用 net 域中的常用网络功能,例如读取和写入 TCP 数据包、通过套接字进行通信,以及执行 DNS 请求。

allow dhcp proc_net:file write; 这一行中,该政策声明 DHCP 可以向 /proc 中的特定文件写入数据。这一行显示了 SELinux 的详细文件标签。它使用 proc_net 标签来限定 DHCP 仅对 /proc/sys/net 中的文件具有写入权限。

该示例的最后一部分以 allow dhcp netd:fd use; 开头,描述了允许应用之间如何交互。该政策声明 DHCP 和 netd 之间可通过文件描述符、FIFO 文件、数据报套接字以及 UNIX 信息流套接字进行通信。DHCP 只能向数据报套接字和 UNIX 信息流套接字中读写数据,但不能创建或打开此类套接字。

可用控件

权限
文件

ioctl read write create getattr setattr lock relabelfrom relabelto append
unlink link rename execute swapon quotaon mounton
目录

add_name remove_name reparent search rmdir open audit_access execmod
套接字

ioctl read write create getattr setattr lock relabelfrom relabelto append bind
connect listen accept getopt setopt shutdown recvfrom sendto recv_msg send_msg
name_bind
文件系统

mount remount unmount getattr relabelfrom relabelto transition associate
quotamod quotaget
进程

fork transition sigchld sigkill sigstop signull signal ptrace getsched setsched
getsession getpgid setpgid getcap setcap share getattr setexec setfscreate
noatsecure siginh setrlimit rlimitinh dyntransition setcurrent execmem
execstack execheap setkeycreate setsockcreate
安全

compute_av compute_create compute_member check_context load_policy
compute_relabel compute_user setenforce setbool setsecparam setcheckreqprot
read_policy
权能

chown dac_override dac_read_search fowner fsetid kill setgid setuid setpcap
linux_immutable net_bind_service net_broadcast net_admin net_raw ipc_lock
ipc_owner sys_module sys_rawio sys_chroot sys_ptrace sys_pacct sys_admin
sys_boot sys_nice sys_resource sys_time sys_tty_config mknod lease audit_write
audit_control setfcap

更多

还有更多

neverallow 规则

SELinux neverallow 规则用于禁止在任何情况下都不应该发生的行为。通过兼容性测试,现在会在各种设备上强制执行 SELinux neverallow 规则。

以下准则旨在协助制造商在自定义过程中避免出现与 neverallow 规则相关的错误。此处使用的规则编号与 Android 5.1 中使用的编号一致,并且会因版本而异。

规则 48:neverallow { domain -debuggerd -vold -dumpstate -system_server } self:capability sys_ptrace;
请参阅 ptrace 的帮助页面。sys_ptrace 权能用于授予对任何进程执行 ptrace 命令的权限,拥有该权限的组件能够对其他进程进行广泛的控制。只有该规则中列出的指定系统组件才能享有该权限。如果需要该权能,则通常表明存在的某些内容不适用于面向用户的版本或存在不需要的功能。请移除不必要的组件。

规则 76:neverallow { domain -appdomain -dumpstate -shell -system_server -zygote } { file_type -system_file -exec_type }:file execute;
该规则旨在防止执行系统中的任意代码。具体来说,该规则声明仅执行 /system 中的代码,以便通过验证启动等机制实现安全保证。通常,在遇到与 neverallow 规则相关的问题时,最好的解决办法是将违规代码移到 /system 分区。

在 Android 8.0 及更高版本中自定义 SEPolicy

此部分的指南适用于 Android 8.0 及更高版本中的供应商 SELinux 政策,包括有关 Android 开源项目 (AOSP) SEPolicy 和 SEPolicy 扩展的详细信息。要详细了解 SELinux 政策如何在各分区和 Android 版本中保持兼容,请参阅兼容性

政策的存放位置

在 Android 7.0 及更低版本中,设备制造商可以将政策添加到 BOARD_SEPOLICY_DIRS,包括用来在不同设备类型之间增强 AOSP 政策的政策。在 Android 8.0 及更高版本中,将政策添加到 BOARD_SEPOLICY_DIRS 会将该政策仅存放在供应商映像中。

在 Android 8.0 及更高版本中,政策位于 AOSP 中的以下位置:

  • system/sepolicy/public。其中包括所导出的用于供应商特定政策的政策。所有内容都会纳入 Android 8.0 兼容性基础架构。公共政策会保留在不同版本上,因此您可以在自定义政策的 /public 中添加任何内容。正因如此,可存放在 /public 中的政策类型的限制性更强。将此目录视为相应平台的已导出政策 API:处理 /system/vendor 之间的接口的所有内容都位于这里。
  • system/sepolicy/private。包括系统映像正常运行所必需(但供应商映像政策应该不知道)的政策。
  • system/sepolicy/vendor。包括位于 /vendor 但存在于核心平台树(非设备特定目录)中的组件的相关政策。这是编译系统区分设备和全局组件的软件工件;从概念上讲,这是下述设备专用政策的一部分。
  • device/manufacturer/device-name/sepolicy。包含设备专用政策,以及对政策进行的设备自定义(在 Android 8.0 及更高版本中,该政策对应于供应商映像组件的相关政策)。

支持的政策场景

在搭载 Android 8.0 及更高版本的设备上,供应商映像必须使用 OEM 系统映像和 Google 提供的参考 AOSP 系统映像(并在此参考映像上传递 CTS),这样可确保框架与供应商代码完全分离开来。此类设备支持以下场景。

仅含供应商映像的扩展

示例:从支持相关进程的供应商映像向 vndservicemanager 添加新服务。

与搭载之前 Android 版本的设备一样,请在 device/manufacturer/device-name/sepolicy 中添加专门针对特定设备的自定义配置。用于管控供应商组件如何与其他供应商组件(仅限这些组件)交互的新政策应涉及仅存在于 device/manufacturer/device-name/sepolicy 中的类型。此处编写的政策允许运行供应商的代码,不会在仅针对框架的 OTA 期间更新,并且将存在于具有参考 AOSP 系统映像的设备上的组合政策中。

支持使用 AOSP 的供应商映像

示例:添加用于实现 AOSP 定义的 HAL 的新进程(通过供应商映像中的 hwservicemanager 注册)。

与搭载之前 Android 版本的设备一样,请在 device/manufacturer/device-name/sepolicy 中执行专门针对特定设备的自定义配置。作为 system/sepolicy/public/ 的一部分导出的政策可供使用,并且包含在供应商政策中。公共政策中的类型和属性可以用在新规则中,指示与供应商特有新位的互动,但要遵守所提供的 neverallow 限制。与 vendor-only 情形一样,此处的新政策不会在仅针对框架的 OTA 期间更新,并且将存在于具有参考 AOSP 系统映像的设备上的组合政策中。

仅含系统映像的扩展

示例:添加一种仅供系统映像中的其他进程访问的新服务(通过 servicemanager 注册)。

将此政策添加到 system/sepolicy/private。您可以添加额外的进程或对象以在合作伙伴系统映像中启用功能,前提是这些新位不需要与供应商映像上的新组件互动(具体而言,即使没有供应商映像中的政策,此类进程或对象也必须能够完全正常运行)。system/sepolicy/public 导出的政策在此处的提供方式与仅含供应商映像的扩展相同。此政策包含在系统映像中,可以在仅针对框架的 OTA 中进行更新,但在使用参考 AOSP 系统映像时不会存在。

提供扩展 AOSP 组件的供应商映像扩展

示例:供同样存在于 AOSP 系统映像中的扩展客户端(例如扩展的 system_server)使用且新增的非 AOSP HAL。

系统与供应商之间的交互政策必须纳入供应商分区上的 device/manufacturer/device-name/sepolicy 目录中。这类似于上述添加供应商映像支持以使用参考 AOSP 映像的情景,不同之处在于修改后的 AOSP 组件可能还需要其他政策才能正确使用系统分区的其余部分(只要这些组件仍具有公开 AOSP 类型标签就可以)。

用于控制公开 AOSP 组件与仅含系统映像的扩展之间的互动的政策应该位于 system/sepolicy/private 中。

仅访问 AOSP 接口的系统映像扩展

示例:新的非 AOSP 系统进程必须访问 AOSP 所依赖的 HAL。

这与仅含系统映像的扩展示例类似,不同之处在于新的系统组件可能在 system/vendor 接口进行交互。新系统组件的相关政策必须位于 system/sepolicy/private,只要它是通过 AOSP 已在 system/sepolicy/public 中建立的接口发挥作用就可以接受(即,该目录中包含功能正常运行所需的类型和属性)。虽然可以在设备专用政策中添加政策,但无法在仅针对框架的更新中使用其他 system/sepolicy/private 类型或进行更改(以任何影响政策的方式)。此政策可以在仅针对框架的 OTA 中更改,但在使用 AOSP 系统映像时,此政策将不出现(也不会有新的系统组件)。

提供新系统组件的供应商映像扩展

示例:添加新的非 AOSP HAL 以供无需 AOSP 模拟的客户端进程使用(因此,该进程需要自己的域)。

AOSP 扩展示例类似,系统与供应商之间的互动政策必须位于供应商分区上的 device/manufacturer/device-name/sepolicy 目录中(以确保系统政策不知道与特定供应商有关的详细信息)。您可以在 system/sepolicy/public 中添加新的用于扩展该政策的公共类型;只能在现有 AOSP 政策的基础上进行添加,即不要移除 AOSP 公共政策。新添加的公共类型随后可用于 system/sepolicy/privatedevice/manufacturer/device-name/sepolicy 中的政策。

请注意,每次向 system/sepolicy/public 添加内容都会增加复杂程度,因为这会增加必须在映射文件中跟踪的新兼容性保证(会受到其他限制)。只有新类型和相关项允许在 system/sepolicy/public 中添加规则;属性和其他政策声明不受支持。此外,新的公共类型不能用于直接为 /vendor 政策中的对象添加标签。

不受支持的政策场景

搭载 Android 8.0 及更高版本的设备不支持以下政策场景和示例。

系统映像的其他扩展,这些扩展需要在仅支持框架的 OTA 之后获得新供应商映像组件的权限

示例:在下一个 Android 版本中添加新的非 AOSP 系统进程(需要自己的域),该进程需要访问新的非 AOSP HAL。

新(非 AOSP)系统和供应商组件之间的交互类似,不同之处在于新的系统类型是在仅针对框架的 OTA 中引入的。虽然这个新类型可以添加到 system/sepolicy/public 中的政策,但现有的供应商政策不知道这个新类型,因为它仅跟踪 Android 8.0 系统公共政策。AOSP 可通过某个属性(例如 hal_foo 属性)要求取得供应商提供的资源,进而处理此情况,但由于属性合作伙伴扩展在 system/sepolicy/public 中不受支持,因此供应商政策无法使用此方法。访问权限必须由之前存在的公共类型提供。

示例:对系统进程(AOSP 或非 AOSP)的更改必须更改它与新的非 AOSP 供应商组件进行互动的方式。

在编写系统映像上的政策时必须对供应商独有的自定义不知情。因此,系统会通过 system/sepolicy/public 中的属性公开 AOSP 中涉及特定接口的政策,以便供应商政策可以选择启用将来使用这些属性的系统政策。不过,system/sepolicy/public 中的属性扩展不受支持,因此指明系统组件与新供应商组件如何交互的所有政策(这些政策不通过 AOSP system/sepolicy/public 中已存在的属性进行处理)都必须位于 device/manufacturer/device-name/sepolicy 中。这意味着系统类型无法在仅针对框架的 OTA 中更改已为供应商类型授予的访问权限。