Android中的SELinux

一、概念

SELinux(Security-Enhanced Linux)即安全增强型Linux,是一个Linux内核的安全模块,其提供了访问控制安全策略机制,包括强制访问控制。SELinux为每个进程与文件都打上一个安全上下文标签,通过该标签实现主体(进程)对客体(资源文件)的访问控制。

SELinux是适用于 Linux 操作系统的强制访问控制 (MAC) 系统。作为 MAC 系统,它与 Linux 中用户非常熟悉的自主访问控制 (DAC) 系统不同。在 DAC 系统中,存在所有权的概念,即特定资源的所有者可以控制与该资源关联的访问权限。这种系统通常比较粗放,并且容易出现无意中提权的问题。MAC 系统则会在每次收到访问请求时都先咨询核心机构,再做出决定。

SELinux 已作为 Linux 安全模块 (LSM) 框架的一部分实现,该框架可识别各种内核对象以及对这些对象执行的敏感操作。其中每项操作要执行时,系统都会调用 LSM 钩子函数,以便根据不透明安全对象中存储的关于相应操作的信息来确定是否应允许执行相应操作。SELinux 针对这些钩子以及这些安全对象的管理提供了相应的实现,该实现可结合自己的政策来决定是否允许相应访问。

SELinux 按照默认拒绝的原则运行:任何未经明确允许的行为都会被拒绝。SELinux 可按两种全局模式运行:

  • 宽容模式(permissive):权限拒绝事件会被记录下来,但不会被强制执行。
  • 强制模式(enforcing):权限拒绝事件会被记录下来强制执行。

Android 中包含 SELinux(处于强制模式)和默认适用于整个 AOSP 的相应安全政策。在强制模式下,非法操作会被阻止,并且尝试进行的所有违规行为都会被内核记录到 dmesglogcat。开发时,应该先利用这些错误信息对软件和 SELinux 政策进行优化,再对它们进行强制执行。

此外,SELinux 还支持基于域的宽容模式。在这种模式下,可将特定域(进程)设为宽容模式,同时使系统的其余部分处于全局强制模式。简单来说,域是安全政策中用于标识一个进程或一组进程的标签,安全政策会以相同的方式处理所有具有相同域标签的进程。借助基于域的宽容模式,可逐步将 SELinux 应用于系统中越来越多的部分,还可以为新服务制定政策(同时确保系统的其余部分处于强制模式)。

类型、属性和规则

Android 依靠 SELinux 的类型强制执行 (TE) 组件来实施其政策。这表示所有对象(例如文件、进程或套接字)都具有相关联的类型。例如,默认情况下,应用的类型为 untrusted_app。对于进程而言,其类型也称为域。可以使用一个或多个属性为类型添加注释。属性可用于同时指代多种类型。

对象会映射到类(例如文件、目录、符号链接、套接字),并且每个类的不同访问权限类型由表示。 例如,file 类存在权限 open。虽然类型和属性作为 Android SELinux 政策的一部分会进行定期更新,但权限和类是静态定义的,并且作为新 Linux 版本的一部分也很少进行更新。

政策规则采用以下格式:

复制代码
allow source target:class permissions;

,其中:

  • source - 规则主题的类型(或属性)。谁正在请求访问权限?
  • 目标 - 对象的类型(或属性)。对哪些内容提出了访问权限请求?
  • 类 - 要访问的对象(例如文件、套接字)的类型。
  • 权限 - 要执行的操作(或一组操作,例如读取、写入)。

规则的一个示例如下:

复制代码
allow untrusted_app app_data_file:file { read write };

这表示应用可以读取和写入带有 app_data_file 标签的文件。还有其他应用类型。例如,isolated_app 用于清单中含有 isolatedProcess=true 的应用服务。Android 对涵盖应用的所有类型使用名为 appdomain 的属性,而不是对这两种类型重复同一规则:

复制代码
# Associate the attribute appdomain with the type untrusted_app.
typeattribute untrusted_app appdomain;

# Associate the attribute appdomain with the type isolated_app.
typeattribute isolated_app appdomain;

allow appdomain app_data_file:file { read write };

当编写的规则指定了某个属性名称时,该名称会自动扩展为列出与该属性关联的所有域或类型。一些重要属性包括:

  • domain - 与所有进程类型相关联的属性
  • file_type - 与所有文件类型相关联的属性。

特别是对于文件访问权限,有很多种权限需要考虑。例如,read 权限不足以打开相应文件或对其调用 stat。为了简化规则定义,Android 提供了一组宏来处理最常见的情况。例如,若要添加 open 等缺少的权限,可以将上述规则改写为:

复制代码
allow appdomain app_data_file:file rw_file_perms;

请尽可能使用宏,以降低因相关权限被拒而导致失败的可能性。

定义类型后,需要将其与所代表的文件或进程相关联。

安全上下文和类别

调试 SELinux 政策或为文件添加标签时(通过 file_contexts 或运行 ls -Z),可能会遇到安全上下文(也称为标签)。例如 u:r:untrusted_app:s0:c15,c256,c513,c768。安全上下文的格式为:user:role:type:sensitivity[:categories]。通常可以忽略上下文的 userrolesensitivity 字段。上一部分介绍了 type 字段。categories 是 SELinux 中多级安全(MLS)支持的一部分。在 Android 12 及更高版本中,类别用于:

  • 分隔应用数据,使其不被其他应用访问。
  • 分隔不同实际用户的应用数据。

明确性

Android 并不会使用 SELinux 提供的所有功能。阅读外部文档时,请记住以下几点:

  • AOSP 中的大部分政策都是使用内核政策语言定义的。在使用通用中间语言 (CIL) 时,会存在一些例外情况。
  • 不使用 SELinux 用户。唯一定义的用户是 u。必要时,系统会使用安全上下文的类别字段表示实际用户。
  • 不使用 SELinux 角色和基于角色的访问控制 (RBAC)。定义并使用了两个默认角色:r(适用于主题)和 object_r(适用于对象)。
  • 不使用 SELinux 敏感度。已始终设置好默认的 s0 敏感度。
  • 不使用 SELinux 布尔值。为设备构建政策后,政策将不依赖于设备状态。这简化了政策的审核和调试过程。

二、相关文件

政策文件

*.te 结尾的文件是 SELinux 政策源代码文件,用于定义域及其标签。可能需要在 /device/manufacturer/device-name/sepolicy 中创建新的政策文件,但应尽可能尝试更新现有文件。

上下文的描述文件

可以在上下文的描述文件中为对象指定标签。

  • file_contexts 用于为文件分配标签,并且可供多种用户空间组件使用。在创建新政策时,请创建或更新该文件,以便为文件分配新标签。如需应用新的 file_contexts,请重新构建文件系统映像,或对要重新添加标签的文件运行 restorecon。在升级时,对 file_contexts 所做的更改会在升级过程中自动应用于系统和用户数据分区。此外,还可以通过以下方式使这些更改在升级过程中自动应用于其他分区:在以允许读写的方式装载相应分区后,将 restorecon_recursive 调用添加到 init.board.rc 文件中。
  • genfs_contexts 用于为不支持扩展属性的文件系统(例如 procvfat)分配标签。此配置会作为内核政策的一部分进行加载,但更改可能对内核 inode 无效。要全面应用更改,需要重新启动设备,或卸载后重新装载文件系统。 此外,通过使用 context=mount 选项,还可以为装载的特定系统文件(例如 vfat)分配特定标签。
  • property_contexts 用于为 Android 系统属性分配标签,以便控制哪些进程可以设置这些属性。在启动期间,init 进程会读取此配置。
  • service_contexts 用于为 Android binder 服务分配标签,以便控制哪些进程可以为相应服务添加(注册)和查找(查询)binder 引用。在启动期间,servicemanager 进程会读取此配置。
  • seapp_contexts 用于为应用进程和 /data/data 目录分配标签。在每次应用启动时,zygote 进程都会读取此配置;在启动期间,installd 会读取此配置。
  • mac_permissions.xml 用于根据应用签名和应用软件包名称(后者可选)为应用分配 seinfo 标记。随后,分配的 seinfo 标记可在 seapp_contexts 文件中用作密钥,以便为带有该 seinfo 标记的所有应用分配特定标签。在启动期间,system_server 会读取此配置。
  • keystore2_key_contexts 用于为密钥库 2.0 命名空间分配标签。 这些命名空间由 keystore2 守护程序强制执行。密钥库始终都提供基于 UID/AID 的命名空间。密钥库 2.0 还会强制执行 sepolicy 定义的命名空间。

BoardConfig.mk makefile

修改或添加政策文件和上下文的描述文件后,请更新 /device/manufacturer/device-name/BoardConfig.mk makefile 以引用 sepolicy 子目录和每个新的政策文件。 如需详细了解 BOARD_SEPOLICY 变量,请参阅 system/sepolicy/README 文件

复制代码
BOARD_SEPOLICY_DIRS += \
        <root>/device/manufacturer/device-name/sepolicy

BOARD_SEPOLICY_UNION += \
        genfs_contexts \
        file_contexts \
        sepolicy.te

重新进行构建后,设备会启用 SELinux。

BoardConfig.mk在不同设备中路径不同,例如在高通的某些项目中,路径为device/qcom/kalama/BoardConfig.mk

三、查看/切换SELinux模式

查看SELinux模式

可通过命令或AVC log查看当前的SELinux模式。

1.执行如下命令查看SELinux模式:

复制代码
adb shell getenforce

2.通过查看AVC log结尾的permissive 取值来查看SELinux模式。

  • permissive=1 ,当前SELinux为permissive模式。
  • permissive=0 ,当前SELinux为enforcing模式。

切换SELinux模式

Enforcing模式默认打开。可通过执行命令、修改配置文件或修改init代码切换至Permissive模式。

1.执行如下命令切换为Permissive模式:

复制代码
adb root
adb shell setenforce 0

执行命令切换模式的方式仅适用于Android userdebug版本,系统重启后修改失效。

2.修改配置文件

可通过修改配置文件BoardConfig.mk中的BOARD_KERNEL_CMDLINE参数切换SELinux模式。

  1. BOARD_KERNEL_CMDLINE 参数中存在androidboot.selinux=enforcing 字段,则将androidboot.selinux=enforcing 修改为androidboot.selinux=permissive 即可切换至permissive模式。
  2. BOARD_KERNEL_CMDLINE 参数中不存在androidboot.selinux=enforcing 字段,则在BOARD_KERNEL_CMDLINE 参数中增加androidboot.selinux=permissive 字段即可切换至permissive模式。

修改完成后,需将修改后的配置文件重新编译并烧录至模块,使配置生效。

修改配置文件切换模式的方式仅适用于Android userdebug版本,系统重启后修改失效。

3.修改init代码

修改system/core/init/selinux.cpp 文件里的IsEnforcing() 函数,将该函数返回值改为false,如下所示:

复制代码
bool IsEnforcing() {
return false;

......
}

修改完成后,需将修改后的配置文件重新编译并烧录至模块,使配置生效。

修改init代码切换模式的方式适用于Android user和userdebug版本。系统重启后修改失效。

四、示例

下面以原生VHAL的SELinux相关配置作为范例,其它的SELinux配置可以参考。

1.rc文件中配置好二进制文件/hardware/interfaces/automotive/vehicle/2.0/default/android.hardware.automotive.vehicle@2.0-default-service.rc

复制代码
service vendor.vehicle-hal-2.0 /vendor/bin/hw/android.hardware.automotive.vehicle@2.0-default-service
    class early_hal
    user vehicle_network
    group system inet

2.配置服务的te文件

/VENDOR/system/sepolicy/vendor/hal_vehicle_default.te

复制代码
# vehicle subsystem
type hal_vehicle_default, domain;
# 通过将HAL服务的默认执行类型(如hal_vehicle_default)与halserverdomain属性关联,明确该服务作为HAL服务器的安全边界‌
hal_server_domain(hal_vehicle_default, hal_vehicle)
 
# may be started by init
type hal_vehicle_default_exec, exec_type, vendor_file_type, file_type;
# init_daemon_domain宏的功能是:init进程执行一个type为hal_vehicle_default_exec的文件,
#创建一个子进程,子进程的domain是hal_vehicle_default。
init_daemon_domain(hal_vehicle_default)
 
# communication with CAN bus HAL
# 允许hal_vehicle_default访问hal_can_bus
hal_client_domain(hal_vehicle_default, hal_can_bus)
 
# communicate with servicemanager
#允许双向IPC通信‌
binder_call(hal_vehicle_server, servicemanager)

3.赋予可执行文件安全上下文

/VENDOR/system/sepolicy/vendor/file_contexts

此处,原生的VHAL因为兼容HIDL VHAL和AIDL VHAL,所以有2个可执行文件。我们自己赋予可执行文件安全上下文时,需根据可执行文件的实际情况进行配置。

复制代码
/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@2\.0-((default|emulator)-)*(service|protocan-service)  u:object_r:hal_vehicle_default_exec:s0
/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@V1-(default|emulator)-service u:object_r:hal_vehicle_default_exec:s0

五、常见问题处理

1.AVC报错

报错日志

  • 'add ', 'u:object_r:default_android_service:s0', '01-01 00:00:25.425 334 334 E SELinux : avc: denied { add } for pid=1709 uid=1000 name=xxxxxxx_2nd_manager scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=1'

  • 'find ', 'u:object_r:default_android_service:s0', '01-01 00:00:25.825 334 334 E SELinux : avc: denied { find } for pid=1709 uid=1000 name=CxxxxxService scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=1'

  • 'find ', 'u:object_r:xx_powermoding_hwservice:s0', '09-29 18:34:16.760 335 335 E SELinux : avc: denied { find } for interface=vendor.xx.powermode::IPowerModing sid=u:r:system_server:s0 pid=1709 scontext=u:r:system_server:s0 tcontext=u:object_r:xx_powermoding_hwservice:s0 tclass=hwservice_manager permissive=1'

上面报错的处理方法:

在.te文件(这里是system_server.te,一般都能找到同名的te文件)中添加

allow system_server default_android_service:service_manager { add find };

allow system_server xx_powermoding_hwservice:hwservice_manager find;

*注意上面语句中,scontext,tcontext和tclass的位置。

如果scontext和tcontext相同,tcontext可以用self代替

如allow xxx self:yyy { zzz };

编译完成后,推包验证。如果使用的是make selinux_policy,system和vendor目录都有产物。产物目录里,除了.cil文件以外,还有很多contexts结尾的文件,需要把整个文件夹push进去验证。

SELinux的目录,下面以原生的logd为例。修改logd的SELinux权限,需要修改/system/sepolicy目录下public/logd.te以及(如果是安卓12,对应API 32)/prebuilts/api/32.0/public/logd.te

调试SELinux时,可以setenforce 0 切换SELinux模式,这样只会有SELinux的权限报错,但实际没有拦截,不影响实际功能。此时报错的结尾permissive=1。而SELinux权限拦截生效时,结尾permissive=0.

2.Neverallow问题

1)问题现象

在添加如下代码进行编译时,编译失败并报neverallow错误。

复制代码
allow system_app sysfs:file { write };

2)问题分析

原因是谷歌不允许应用进程写sysfs类型的文件,其代码如下:

复制代码
neverallow { appdomain -bluetooth -nfc } sysfs:dir_file_class_set write;

3)解决方法

Attention : 第一时间应该检查和neverallow冲突的语句,是否真的需要添加。因为添加SELinux权限后出现neverallow编译报错,是新添加的策略违反了谷歌的总策略原则。

若确认需要添加,可以采取下面的方法:

可通过自定义type,并更改客体的安全上下文解决该问题。以VENDOR侧为例,步骤如下:

  • 1: 参考原有的sysfs类型,在*/VENDOR/device/qcom/sepolicy_vndr/generic/vendor/c ommon/file.te*文件中定义一种新type(type名可自定义)。示例如下:

    type sysfs_xxx, fs_type, sysfs_type;

  • 2: 使用新定义的type,在*/VENDOR/device/qcom/sepolicy_vndr/generic/vendor/com mon/genfs_contexts*文件中添加如下代码,为目标文件配置安全上下文。示例如下:

    genfscon sysfs /xxx/xxx(目标文件路径) u:object_r:sysfs_xxx:s0

  • 3: 在*/VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/system_app.te*文件中添加如下代码,重新赋予主体进程访问新类型文件的权限。示例如下:

    allow system_app sysfs_xxx:file { write };

和neverallow冲突的策略若强行添加,有CTS测试失败的风险。解决neverallow报错的最佳方法是调整添加的权限策略以避免和neverallow规则冲突。

更多内容可参考安卓官网的SELinux部分(需自行解决网络问题才能访问)

https://source.android.com/docs/security/features/selinux?hl=zh-cn