
一、概念SELinuxSecurity-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 的相应安全政策。在强制模式下非法操作会被阻止并且尝试进行的所有违规行为都会被内核记录到dmesg和logcat。开发时应该先利用这些错误信息对软件和 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用于清单中含有isolatedProcesstrue的应用服务。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]。通常可以忽略上下文的user、role和sensitivity字段。上一部分介绍了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用于为不支持扩展属性的文件系统例如proc或vfat分配标签。此配置会作为内核政策的一部分进行加载但更改可能对内核 inode 无效。要全面应用更改需要重新启动设备或卸载后重新装载文件系统。 此外通过使用contextmount选项还可以为装载的特定系统文件例如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.mkmakefile 以引用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 getenforce2.通过查看AVC log结尾的permissive取值来查看SELinux模式。若permissive1当前SELinux为permissive模式。若permissive0当前SELinux为enforcing模式。切换SELinux模式Enforcing模式默认打开。可通过执行命令、修改配置文件或修改init代码切换至Permissive模式。1.执行如下命令切换为Permissive模式adb root adb shell setenforce 0执行命令切换模式的方式仅适用于Android userdebug版本系统重启后修改失效。2.修改配置文件可通过修改配置文件BoardConfig.mk中的BOARD_KERNEL_CMDLINE参数切换SELinux模式。若BOARD_KERNEL_CMDLINE参数中存在androidboot.selinuxenforcing字段则将androidboot.selinuxenforcing修改为androidboot.selinuxpermissive即可切换至permissive模式。若BOARD_KERNEL_CMDLINE参数中不存在androidboot.selinuxenforcing字段则在BOARD_KERNEL_CMDLINE参数中增加androidboot.selinuxpermissive字段即可切换至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.vehicle2.0-default-service.rcservice vendor.vehicle-hal-2.0 /vendor/bin/hw/android.hardware.automotive.vehicle2.0-default-service class early_hal user vehicle_network group system inet2.配置服务的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\.vehicle2\.0-((default|emulator)-)*(service|protocan-service) u:object_r:hal_vehicle_default_exec:s0 /(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicleV1-(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 pid1709 uid1000 namexxxxxxx_2nd_manager scontextu:r:system_server:s0 tcontextu:object_r:default_android_service:s0 tclassservice_manager permissive1]- [find , u:object_r:default_android_service:s0, 01-01 00:00:25.825 334 334 E SELinux : avc: denied { find } for pid1709 uid1000 nameCxxxxxService scontextu:r:system_server:s0 tcontextu:object_r:default_android_service:s0 tclassservice_manager permissive1]- [find , u:object_r:xx_powermoding_hwservice:s0, 09-29 18:34:16.760 335 335 E SELinux : avc: denied { find } for interfacevendor.xx.powermode::IPowerModing sidu:r:system_server:s0 pid1709 scontextu:r:system_server:s0 tcontextu:object_r:xx_powermoding_hwservice:s0 tclasshwservice_manager permissive1]上面报错的处理方法在.te文件这里是system_server.te一般都能找到同名的te文件中添加allow system_server default_android_service:service_manager { add find };allow system_server xx_powermoding_hwservice:hwservice_manager find;*注意上面语句中scontexttcontext和tclass的位置。如果scontext和tcontext相同tcontext可以用self代替如allow xxx self:yyy { zzz };编译完成后推包验证。如果使用的是make selinux_policysystem和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的权限报错但实际没有拦截不影响实际功能。此时报错的结尾permissive1。而SELinux权限拦截生效时结尾permissive0.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/common/file.te文件中定义一种新typetype名可自定义。示例如下type sysfs_xxx, fs_type, sysfs_type;2使用新定义的type在/VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/genfs_contexts文件中添加如下代码为目标文件配置安全上下文。示例如下genfscon sysfs /xxx/xxx目标文件路径 u:object_r:sysfs_xxx:s03在/VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/system_app.te文件中添加如下代码重新赋予主体进程访问新类型文件的权限。示例如下allow system_app sysfs_xxx:file { write };和neverallow冲突的策略若强行添加有CTS测试失败的风险。解决neverallow报错的最佳方法是调整添加的权限策略以避免和neverallow规则冲突。方案二直接修改neverallow语句。这个方法目前实测可以解决编译问题但是后续功能和CTS测试等是否有影响还需观察。我们会有这种neverallow报错错误信息具体到冲突的某一行neverallow check failed at out/soong/.intermediates/system/sepolicy/plat_sepolicy.cil/android_common/plat_sepolicy.cil:8521 from system/sepolicy/public/domain.te:864像上面这个错误我们就可以找到domain.te这个文件在报错的这一行“neverallow { domain”后面减去我们的type。这时可能遇到报错显示undefined type。因为我们添加的type一般不放在VENDOR/system/sepolicy/public/这种目录下例如我之前添加的定义在VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/这个目录。此时可以将声明语句type xxx, yyy;单独移到VENDOR/system/sepolicy/public/目录而其他语句例如宏和allow语句还是留在VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/目录。这样就能在VENDOR/system/sepolicy/public/中使用自己定义的type又能使用VENDOR/device/qcom/sepolicy_vndr/generic/vendor/common/中的一些宏。修改VENDOR/system/sepolicy/public/和VENDOR/system/sepolicy/private/目录下的文件时注意要分别同步修改到VENDOR/system/sepolicy/prebuilts/api/最新的api版本/public/以及private/否则会编译报错显示hash值校验错误。如果VENDOR/system/sepolicy/private/中也要用到自定义的type目前实测只需要在public目录下添加了定义就能正常使用不需要在private下再定义一次。3.推包验证整编后刷机自然是可行的但这样比较耗时间。实测adb push推包system/etc/selinux和vendor/etc/selinux这2个文件夹即可。更多内容可参考安卓官网的SELinux部分需自行解决网络问题才能访问https://source.android.com/docs/security/features/selinux?hlzh-cn