[转载]android 8.1 安全机制 — SEAndroid & SELinux

作者 by adtxl / 2021-03-03 / 暂无评论 / 392 个足迹

版权声明:本文为CSDN博主「岁月斑驳7」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_19923217/article/details/81240027

1. SELinux背景知识

关于SELinux详细内容可参阅Google SELinux参考文档

1.1 DAC与MAC

在 SELinux 出现之前,Linux 上的安全模型叫 DAC,全称是 Discretionary Access Control,翻译为自主访问控制。

DAC 的核心思想很简单,就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以 root 用户启动 Browser,那么 Browser 就有 root 用户的权限,在 Linux 系统上能干任何事情。

显然,DAD 管理太过宽松,只要想办法在 Android 系统上获取到 root 权限就可以了。那么 SELinux 是怎么解决这个问题呢?在 DAC 之外,它设计了一种新的安全模型,叫 MAC(Mandatory Access Control),翻译为强制访问控制。

MAC 的理论也很简单,任何进程想在 SELinux 系统上干任何事情,都必须在《安全策略文件》中赋予权限,凡是没有出现在安全策略文件中的权限,就不行。

关于 DAC 和 MAC,可以总结几个知识点:

  1. Linux 系统先做 DAC 检查。如果没有通过 DAC 权限检查,则操作直接失败。通过 DAC 检查之后,再做 MAC 权限检查
  2. SELinux 有自己的一套规则来编写安全策略文件,这套规则被称之为 SELinux Policy 语言。

1.2 SEPolicy语言

Linux中有两种东西,一种死的(Inactive),一种活的(Active)。死的东西就是文件(Linux哲学,万物皆文件。注意,万不可狭义解释为File),而活的东西就是进程。此处的 死 和 活 是一种比喻,映射到软件层面的意思是:进程能发起动作,例如它能打开文件并操作它。而文件只能被进程操作。

根据 SELinux 规范,完整的 Secure Context 字符串为:user:role:type[:range]

1.2.1 进程的Secure Context

在 SELinux 中,每种东西都会被赋予一个安全属性,官方说法叫做 Security Context,Security Context 是一个字符串,主要由三个部分组成,例如 SEAndroid 中,进程的 Security Context 可通过 ps -Z 命令查看:

rk3288:/ $ ps -AZ
u:r:hal_wifi_supplicant_default:s0 wifi      1816     1   11388   6972 0                   0 S wpa_supplicant
u:r:platform_app:s0:c512,c768  u0_a14        1388   228 1612844  57396 0                   0 S android.ext.services
u:r:system_app:s0              system        1531   228 1669680 119364 0                   0 S com.android.gallery3d
u:r:kernel:s0                  root           582     2       0      0 0                   0 S [kworker/1:2]
u:r:radio:s0                   radio          594   228 1634876  89296 0                   0 S com.android.phone
u:r:system_app:s0              system         672   228 1686204 141716 0                   0 S com.android.settings
u:r:platform_app:s0:c512,c768  u0_a18         522   223 1721656 152116 0                   0 S com.android.systemui

上面的最左边的一列就是进程的 Security Context,以第一个进程 wpa_supplicant 为例

u:r:hal_wifi_supplicant_default:s0

其中

  • u 为 user 的意思,SEAndroid 中定义了一个 SELinux 用户,值为 u
  • r 为 role 的意思,role 是角色之意,它是 SELinux 中一个比较高层次,更方便的权限管理思路。简单点说,一个 u 可以属于多个 role,不同的 role 具有不同的权限。
  • hal_wifi_supplicant_default 代表该进程所属的 Domain 为 hal_wifi_supplicant_default。MAC(Mandatory Access Control)强制访问控制 的基础管理思路其实是 Type Enforcement Access Control(简称TEAC,一般用TE表示),对进程来说,Type 就是 Domain,比如 hal_wifi_supplicant_default 需要什么权限,都需要通过 allow 语句在 te 文件中进行说明。
  • s0 是 SELinux 为了满足军用和教育行业而设计的 Multi-Level Security(MLS)机制有关。简单点说,MLS 将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问

1.2.2 文件的Secure Context

文件的 Secure Context 可以通过 ls -Z 来查看,如下

rk3288:/vendor/lib $ ls libOMX_Core.so -Z
u:object_r:vendor_file:s0 libOMX_Core.so
  • u:同样是 user 之意,它代表创建这个文件的 SELinux user
  • object_r:文件是死的东西,它没法扮演角色,所以在 SELinux 中,死的东西都用 object_r 来表示它的 role
  • vendor_file:type,和进程的 Domain 是一个意思,它表示 libOMX_Core.so 文件所属的 Type 是 vendor_file
  • s0:MLS 的等级

1.3 TE介绍

MAC 基本管理单位是 TEAC(Type Enforcement Accesc Control),然后是高一级别的RBAC(Role Based Accesc Control)。RBAC 是基于 TE 的,而 TE 也是 SELinux 中最主要的部分。上面说的 allow 语句就是 TE 的范畴。

根据 SELinux 规范,完整的 SELinux 策略规则语句格式为:

allow domains types:classes permissions;

- Domain - 一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。
- Type - 一个对象(例如,文件、套接字)或一组对象的标签。
- Class - 要访问的对象(例如,文件、套接字)的类型。
- Permission - 要执行的操作(例如,读取、写入)。

= allow : 允许主体对客体进行操作
= neverallow :拒绝主体对客体进行操作
= dontaudit : 表示不记录某条违反规则的决策信息
= auditallow :记录某项决策信息,通常 SElinux 只记录失败的信息,应用这条规则后会记录成功的决策信息。

使用政策规则时将遵循的结构示例:

语句:
allow appdomain app_data_file:file rw_file_perms;

这表示所有应用域都可以读取和写入带有 app_data_file 标签的文件

相关实例

1. SEAndroid 中的安全策略文件 policy.conf
    # 允许 zygote 域中的进程向 init 域中的进程(Object Class 为 process)发送 sigchld 信号

    allow zygote init:process sigchld;

2. # 允许 zygote 域中的进程 search 或 getattr 类型为 appdomain 的目录。
   # 注意,多个 perm_set 可用 {} 括起来
    allow zygote appdomain:dir { getattr search };

3. # perm_set 语法比较奇特,前面有一个 ~ 号。
   # 它表示除了{entrypoint relabelto}之外,{chr_file #file}这两个object_class所拥有的其他操作 
    allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file }   \
    ~{entrypoint relabelto};

Object Class 类型

文件路径: system/sepolicy/private/security_classes

# file-related classes
class filesystem
class file  #代表普通文件
class dir   #代表目录
class fd    #代表文件描述符
class lnk_file  #代表链接文件
class chr_file  #代表字符设备文件

# network-related classes
class socket   #socket
class tcp_socket
class udp_socket

......
class binder   #Android 平台特有的 binder
class zygote   #Android 平台特有的 zygote

Perm Set类型
Perm Set 指得是某种 Object class 所拥有的权限。以 file 这种 Object class 而言,其拥有的 Perm Set 就包括 read、write、open、create、execute等。

文件路径: system/sepolicy/private/access_vectors

2. SELinux相关设置

2.1 强制执行等级

熟悉以下术语,了解如何按不同的强制执行级别实现 SELinux

  • 宽容模式(permissive) - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式(enforcing) - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

2.2 关闭SELinux

临时关闭
(1)setenforce

$ setenforce 0

setenforce 命令修改的是 /sys/fs/selinux/enforce 节点的值,是 kernel 意义上的修改 selinux 的策略。断电之后,节点值会复位

永久关闭
(2)kernel 关闭 selinux

SECURITY_SELINUX 设置为 false,重新编译 kernel

(3)设置 ro.boot.selinux=permissive 属性,并且修改在 system/core/init/Android.mk 中设置用于 user 版本下 selinux 模式为 permissive

ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
init_options += \
    -DALLOW_LOCAL_PROP_OVERRIDE=1 \
    -DALLOW_PERMISSIVE_SELINUX=1 \
    -DREBOOT_BOOTLOADER_ON_PANIC=1 \
    -DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
    -DALLOW_LOCAL_PROP_OVERRIDE=0 \
    -DALLOW_PERMISSIVE_SELINUX=1 \ // 修改为1,表示允许 selinux 为 permissive
    -DREBOOT_BOOTLOADER_ON_PANIC=0 \
    -DDUMP_ON_UMOUNT_FAILURE=0
endif

enforcing - SELinux security policy is enforced.
permissive - SELinux prints warnings instead of enforcing.
disabled - No SELinux policy is loaded.

2.7 SELinux权限不足avc-denied问题解决

目前所有的 SELinux 权限检测失败,在 Kernel Log 或者 Android Log 中都有对应的 avc-denied Log 与之对应。反过来,有 avc-denied Log,并非就会直接失败,还需要确认当时 SELinux 的模式, 是 Enforcing 还是 Permissve。

如果是 Enforcing 模式,就要检测对应的进程访问权限资源是否正常?是否有必要添加? 如果有必要添加,可以按下面的方式生成需要的 sepolicy 规则并添加到对应 te 文件。

使用 audit2allow 简化方法

  1. 从 logcat 或串口中提取相应的 avc-denied log,下面的语句为提取所有的 avc- denied log

    $ adb shell "cat /proc/kmsg | grep avc" > avc_log.txt 
  2. 使用 audit2allow 工具生成对应的 policy 规则

    // audio2allow 使用必须先 source build/envsetup.sh,导入环境变量
    $ audit2allow -i avc_log.txt -p $OUT/vendor/etc/selinux/precompiled_sepolicy
  3. 将对应的policy 添加到 te 文件中

    一般添加在 /device/<company>/common/sepolicy 或者 /device/<company>/$DEVICE/sepolicy 目录下

    BOARD_SEPOLICY_DIRS += device/$SoC/common/sepolicy 通过这个命令添加厂家自定义的 sepolicy 规则

3. SEAndroid安全机制框架

SELinux 系统比起通常的 Linux 系统来,安全性能要高的多,它通过对于用户,进程权限的最小化,即使受到攻击,进程或者用户权限被夺去,也不会对整个系统造成重大影响。

我们知道,Android 系统是基于 Linux 内核实现,针对 Linux 系统,NSA 开发了一套安全机制 SELinux,用来加强安全性。然而,由于 Android 系统有着独特的用户空间运行时,因此 SELinux 不能完全适用于 Android 系统。为此,NSA 针对 Android 系统,在 SELinux 基础上开发了 SEAndroid。

SEAndroid 安全机制所要保护的对象是系统中的资源,这些资源分布在各个子系统中,例如我们经常接触的文件就是分布文件子系统中的。实际上,系统中需要保护的资源非常多,除了前面说的文件之外,还有进程、socket 和 IPC 等等。对于 Android 系统来说,由于使用了与传统 Linux 系统不一样的用户空间运行时,即应用程序运行时框架,因此它在用户空间有一些特有的资源是需要特别保护的,例如系统属性的设置等。

3.1 SEAndroid框架流程

SEAndroid 安全机制的整体框架,可以使用下图来概括:
image.png

以 SELinux 文件系统接口为边界,SEAndroid 安全机制包含内核空间和用户空间两部分支持。

1. 内核空间的 SELinux LSM 模块负责内核资源的安全访问控制
2. 用户空间的 SEAndroid Policy 描述的是资源安全访问策略。
   系统在启动的时候,用户空间的 Security Server 需要将这些安全访问策略加载内核空间的 SELinux LSM 模块中去。
   这是通过SELinux文件系统接口实现的
3. 用户空间的 Security Context 描述的是资源安全上下文。
   SEAndroid 的安全访问策略就是在资源的安全上下文基础上实现的
4. 用户空间的 Security Server 一方面需要到用户空间的 Security Context 去检索对象的安全上下文,
   另一方面也需要到内核空间去操作对象的安全上下文
5. 用户空间的 libselinux 库封装了对 SELinux 文件系统接口的读写操作。
   用户空间的 Security Server 访问内核空间的 SELinux LSM 模块时,都是间接地通过 libselinux进行的。
   这样可以将对 SELinux 文件系统接口的读写操作封装成更有意义的函数调用。
6. 用户空间的 Security Server 到用户空间的 Security Context 去检索对象的安全上下文时,同样也是通过 selinux 库来进行的

3.1.1 内核空间

在内核空间,存在一个 SELinux LSM(Linux Secrity Moudle)模块,(用 MAC 强制访问控制)负责资源的安全访问控制。

LSM 模块中包含一个访问向量缓冲(Access Vector Cache)和一个安全服务(Security Server)。Security Server 负责安全访问控制逻辑,即由它来决定一个主体访问一个客体是否是合法的,这个主体一般是指进程,而客体主要指资源,例如文件。

SELinux、LSM 和内核中的子系统是如何交互的呢?首先,SELinux 会在 LSM 中注册相应的回调函数。其次,LSM 会在相应的内核对象子系统中会加入一些 Hook 代码。例如,我们调用系统接口 read 函数来读取一个文件的时候,就会进入到内核的文件子系统中。在文件子系统中负责读取文件函数 vfs_read 就会调用 LSM 加入的 Hook 代码。这些 Hook 代码就会调用之前 SELinux 注册进来的回调函数,以便后者可以进行安全检查。

SELinux 在进行安全检查的时候,首先是看一下自己的 Access Vector Cache 是否已经有结果。如果有的话,就直接将结果返回给相应的内核子系统就可以了。如果没有的话,就需要到 Security Server 中去进行检查。检查出来的结果在返回给相应的内核子系统的同时,也会保存在自己的 Access Vector Cache 中,以便下次可以快速地得到检查结果

上面概述的安全访问控制流程,可以使用下图来总结:
image76d34b3ae9f72d50.png

1. 一般性错误检查,例如访问的对象是否存在、访问参数是否正确等
2. DAC 检查,即基于 Linux UID/GID 的安全检查
3. SELinux 检查,即基于安全上下文和安全策略的安全检查

3.1.2 用户空间

在用户空间,SEAndorid 主要包含三个模块,分别是安全上下文(Security Context)、安全策略(SEAndroid Policy)和安全服务(Security Server)。

(1)安全上下文
前面已经描述过了,SEAndroid 是一种基于安全策略的 MAC 安全机制。这种安全策略又是建立在对象的安全上下文的基础上的,SEAndroid 中的对象分为主体(Subject)和客体(Object),主体通常是指进程,而客体是指进程所要访问的资源,如文件、系统属性等

安全上下文实际上就是一个附加在对象上的标签(Tag)。这个标签实际上就是一个字符串,它由四部分内容组成,分别是 SELinux 用户、SELinux 角色、类型、安全级别,每一个部分都通过一个冒号来分隔,格式为

"user:role:type:sensitivity"

用 ps -Z 来查看主体的安全上下文,而用 ls -Z 来查看客体的安全上下文。

安全上下文<类型>说明
通常将用来标注文件的安全上下文中的类型称为 file_type,用来标注进程的安全上下文的类型称为 domain。

将两个类型相关联可以通过 type 语句实现,例如用来描述 init 进程安全策略文件 /system/sepolicy/public/init.te 文件中,使用 type 语句将 init 与 domain 相关联(等价)

type init domain;
// 这样就可以表明 init 描述的类型是用来描述进程的安全上下文的

Android 系统中对象的安全上下文定义
系统中各种类型对象(包含主体和客体)的安全上下文是在 system/sepolicy 工程中定义的,我们讨论四种类型对象的安全上下文,分别是 app 进程、app 数据文件、系统文件和系统属性。这四种类型对象的安全上下文通过四个文件来描述:mac_permissions.xml、seapp_contexts、file_contexts 和 property_contexts。

mac_permissions 文件给不同签名的 app 分配不同的 seinfo 字符串,例如,在 AOSP 源码环境下编译并且使用平台签名的 App 获得的 seinfo 为 “platform”,使用第三方签名安装的 App 获得的 seinfo 签名为”default”。

<!-- Platform dev key in AOSP -->
<signer signature="@PLATFORM" >
  <seinfo value="platform" />
</signer>

<!-- Media key in AOSP -->
<signer signature="@MEDIA" >
  <seinfo value="media" />
</signer>

这里的 seinfo 描述的并不是安全上下文的对象类型,它用来在 seapp_contexts 中查找对应的对象类型。在 seapp_contexts 对 seinfo 也就是平台签名为”platform”的 app 如下定义:

user=_app seinfo=platform domain=platform_app type=app_data_file levelFrom=user

也就是说明使用"platform"平台签名的 app 所运行的进程 domain 为"platform_app",
并且他的数据文件类型为"app_data_file"。****

接下来看一下系统文件的安全上下文是如何定义的:

###########################################
# Root
/                   u:object_r:rootfs:s0

# Data files
/adb_keys           u:object_r:adb_keys_file:s0
/build\.prop        u:object_r:rootfs:s0
/default\.prop      u:object_r:rootfs:s0
/fstab\..*          u:object_r:rootfs:s0
/init\..*           u:object_r:rootfs:s0
/res(/.*)?          u:object_r:rootfs:s0
/selinux_version    u:object_r:rootfs:s0
/ueventd\..*        u:object_r:rootfs:s0
/verity_key         u:object_r:rootfs:s0

可以看到使用正则表达式描述了系统文件的安全上下文。

在 Android 系统中有一种比较特殊的权限——属性,app 能够读写他们获取相应的系统信息以及控制系统的行为。因此不同与 SELinux,SEAndroid 中对系统属性也进行了保护,这意味着 Android 系统的属性也需要关联安全上下文。这是在 property_contexts 文件中描述的

##########################
# property service keys
#
#
net.rmnet               u:object_r:net_radio_prop:s0
net.gprs                u:object_r:net_radio_prop:s0
net.ppp                 u:object_r:net_radio_prop:s0
net.qmi                 u:object_r:net_radio_prop:s0
net.lte                 u:object_r:net_radio_prop:s0
net.cdma                u:object_r:net_radio_prop:s0
net.dns                 u:object_r:net_dns_prop:s0
sys.usb.config          u:object_r:system_radio_prop:s0
ril.                    u:object_r:radio_prop:s0
ro.ril.                 u:object_r:radio_prop:s0
gsm.                    u:object_r:radio_prop:s0
persist.radio           u:object_r:radio_prop:s0

这边的 net.rmnet 行类型为 net_radio_prop,意味着只有有权限访问 net_radio_prop 的进程才可以访问这个属性。

(2) 安全策略
SEAndroid 安全机制中的安全策略是在安全上下文的基础上进行描述的,也就是说,它通过主体和客体的安全上下文,定义主体是否有权限访问客体。

Type Enforcement
SEAndroid 安全机制主要是使用对象安全上下文中的类型来定义安全策略,这种安全策略就称 Type Enforcement,简称TE。在 system/sepolicy 目录和其他所有客制化 te 目录(通常在 device//common,用 BOARD_SEPOLICY_DIRS 添加),所有以 .te 为后缀的文件经过编译之后,就会生成一个 sepolicy 文件。这个 sepolicy 文件会打包在ROM中,并且保存在设备上的根目录下。
一个 Type 所具有的权限是通过allow语句来描述的

allow unconfineddomain domain:binder { call transfer set_context_mgr };

表明 domain 为 unconfineddomain 的进程可以与其它进程进行 binder ipc 通信(call),并且能够向这些进程传递 Binder 对象(transfer),以及将自己设置为 Binder 上下文管理器(set_context_mgr)。

注意,SEAndroid 使用的是最小权限原则,也就是说,只有通过 allow 语句声明的权限才是允许的,而其它没有通过 allow 语句声明的权限都是禁止,这样就可以最大限度地保护系统中的资源

前面我们提到,SEAndroid 安全机制的安全策略经过编译之后会得到一个 sepolicy 文件,并且最终保存在设备上的根目录上。这个 sepolicy 文件中的安全策略是不会自动加载的到内核空间的 SELinux LSM 模块中去的,它需要我们在系统启动的时候进行加载。

(3)Security Server
Security Server 是一个比较笼统的概念,主要是用来保护用户空间资源的,以及用来操作内核空间对象的安全上下文的,它由应用程序安装服务 PackageManagerService、应用程序安装守护进程 installd、应用程序进程孵化器 Zygote 进程以及 init 进程组成。其中,PackageManagerService 和 installd 负责创建 App 数据目录的安全上下文,Zygote 进程负责创建 App 进程的安全上下文,而 init 进程负责控制系统属性的安全访问。

PackageManagerService & installed —— app 数据目录的安全上下文
PackageManagerService 在启动的时候,会找到我们前面分析的 mac_permissions.xml 文件,然后对它进行解析,得到 App 签名或者包名与 seinfo 的对应关系。当 PackageManagerService 安装 App 的时候,它就会根据其签名或者包名查找到对应的 seinfo,并且将这个 seinfo 传递给另外一个守护进程 installed。

守护进程 installd 负责创建 App 数据目录。在创建 App 数据目录的时候,需要给它设置安全上下文,使得 SEAndroid 安全机制可以对它进行安全访问控制。Installd 根据 PackageManagerService 传递过来的 seinfo,并且调用 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 seapp_contexts 文件中查找到对应的 Type。有了这个 Type 之后,installd 就可以给正在安装的 App 的数据目录设置安全上下文了,这是通过调用 libselinux 库提供的 lsetfilecon 函数来实现的。

Zygote —— 设置进程的安全上下文
在 Android 系统中,Zygote 进程负责创建应用程序进程。应用程序进程是 SEAndroid 安全机制中的主体,因此它们也需要设置安全上下文,这是由 Zygote 进程来设置的。

ActivityManagerService 在请求 Zygote 进程创建应用程序进程之前,会到 PackageManagerService 中去查询对应的 seinfo,并且将这个 seinfo 传递到 Zygote 进程。于是,Zygote 进程在 fork 一个应用程序进程之后,就会使用 ActivityManagerService 传递过来的 seinfo,并且调用 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 seapp_contexts 文件中查找到对应的 Domain。有了这个 Domain 之后,Zygote 进程就可以给刚才创建的应用程序进程设置安全上下文了,这是通过调用 libselinux 库提供的 lsetcon 函数来实现的。

init —— 系统属性的安全上下文
init 进程在启动的时候会创建一块内存区域来维护系统中的属性,接着还会创建一个 Property 服务系统,这个服务系统通过 socket 提供接口给其他进程访问 android 系统中的属性。

其他进程通过 socket 和属性系统通信请求访问某项系统属性的值,属性服务系统可以通过 libselinux 库提供的 selabel_lookup 函数到前面我们分析的 property_contexts 中查找要访问的属性的安全上下文了。有了该进程的安全上下文和要访问属性的安全上下文之后,属性系统就能决定是否允许一个进程访问它所指定的服务了。

4. SEAndroid源码分析

4.1 SEAndroid源码架构

- externel/selinux:包含编译 sepolicy 策略文件的一些实用构建工具
    - external/selinux/libselinux:提供了帮助用户进程使用 SELinux 的一些函数
    - external/selinux/libsepol:提供了供安全策略文件编译时使用的一个工具 checkcon
- system/sepolicy:包含 Android SELinux 核心安全策略(te 文件),编译生成 sepolicy 文件
    - file_contexts: 系统中所有文件的安全上下文
    - property_contexts: 系统中所有属性的安全上下文
    - seapp_contexts:定义用户、seinfo和域之间的关系,用于确定用户进程的安全上下文
    - sepolicy:二进制文件,保存系统安全策略,系统初始化时会把它设置到内核中
SELinux 虚拟文件系统在 sys/fs/selinux 下,该目录下的文件是 SELinux 内核和用户进程进行通信的接口,libselinux 就是利用这边的接口进行操作

4.2 init进程SEAndroid启动源码分析

文件路径:/system/core/init/init.cpp

int main(int argc, char** argv) {
    ...
    if (is_first_stage) {
        ... 

        // Set up SELinux, loading the SELinux policy.
        selinux_initialize(true); 

        // We're in the kernel domain, so re-exec init to transition to the init domain now
        // that the SELinux policy has been loaded.
        if (selinux_android_restorecon("/init", 0) == -1) {
            PLOG(ERROR) << "restorecon failed";
            security_failure();
        }
        ...
    }
}

可以看到 SEAndroid 的启动设置在 init 进程内核态执行(first_stage)过程中,selinux_initialize 函数就是主要的初始化过程,包含加载 sepolicy 策略文件到内核的 LSM 模块中。

文件路径:/system/core/init/init.cpp

static void selinux_initialize(bool in_kernel_domain) {
    Timer t;

    selinux_callback cb;
    cb.func_log = selinux_klog_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);

    // 标识 init 进程的内核态执行和用户态执行
    if (in_kernel_domain) {
        LOG(INFO) << "Loading SELinux policy";
        if (!selinux_load_policy()) {
            panic();
        }

        bool kernel_enforcing = (security_getenforce() == 1);
        bool is_enforcing = selinux_is_enforcing();
        if (kernel_enforcing != is_enforcing) {
            if (security_setenforce(is_enforcing)) {
                PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false");
                security_failure();
            }
        }

        std::string err;
        if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) {
            LOG(ERROR) << err;
            security_failure();
        }

        // init's first stage can't set properties, so pass the time to the second stage.
        setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1);
    } else {
        selinux_init_all_handles();
    }
}

selinux_set_callback
selinux_set_callback 用来向 libselinux 设置 SEAndroid 日志和审计回调函数

selinux_load_policy
这个函数用来加载 sepolicy 策略文件,并通过 mmap 映射的方式将 sepolicy 的安全策略加载到 SELinux LSM 模块中去。
下面具体分析一下流程:

文件路径:system/core/init/init.cpp

static bool selinux_load_policy() {
    return selinux_is_split_policy_device() ? selinux_load_split_policy()
                                            : selinux_load_monolithic_policy();
}

这里区分了从哪里加载安全策略文件,第一个是从 /vendor/etc/selinux/precompiled_sepolicy 读取,第二个是直接从根目录 /sepolicy
中读取,最终调用的方法都是 selinux_android_load_policy_from_fd

int selinux_android_load_policy_from_fd(int fd, const char *description)
{
    int rc;
    struct stat sb;
    void *map = NULL;
    static int load_successful = 0;

    /*
     * Since updating policy at runtime has been abolished
     * we just check whether a policy has been loaded before
     * and return if this is the case.
     * There is no point in reloading policy.
     */
    if (load_successful){
      selinux_log(SELINUX_WARNING, "SELinux: Attempted reload of SELinux policy!/n");
      return 0;
    }

    set_selinuxmnt(SELINUXMNT);
    if (fstat(fd, &sb) < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not stat %s:  %s\n",
                description, strerror(errno));
        return -1;
    }
    map = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not map %s:  %s\n",
                description, strerror(errno));
        return -1;
    }

    rc = security_load_policy(map, sb.st_size);
    if (rc < 0) {
        selinux_log(SELINUX_ERROR, "SELinux:  Could not load policy:  %s\n",
                strerror(errno));
        munmap(map, sb.st_size);
        return -1;
    }

    munmap(map, sb.st_size);
    selinux_log(SELINUX_INFO, "SELinux: Loaded policy from %s\n", description);
    load_successful = 1;
    return 0;
}

(1)set_selinuxmnt 函数设置内核 SELinux 文件系统路径,这里的值为 /sys/fs/selinux,SELinux 文件系统用来与内核空间 SELinux LSM 模块空间。

(2)通过 fstat 获取 sepolicy 文件(fd 值)的状态信息,通过 mmap 函数将文件内容映射到内存中,起始地址为 map

(3)security_load_policy 调用另一个 security_load_policy 函数将已经映射到内存中的 SEAndroid 的安全策略加载到内核空间的 SELinux LSM 模块中去。

int security_load_policy(void *data, size_t len)
{
    char path[PATH_MAX];
    int fd, ret;

    if (!selinux_mnt) {
        errno = ENOENT;
        return -1;
    }

    snprintf(path, sizeof path, "%s/load", selinux_mnt);
    fd = open(path, O_RDWR | O_CLOEXEC);
    if (fd < 0)
        return -1;

    ret = write(fd, data, len);
    close(fd);
    if (ret < 0)
        return -1;
    return 0;
}

函数 security_load_policy 的实现很简单,它首先打开 /sys/fs/selinux/load 文件,然后将参数 data 所描述的安全策略写入到这个文件中去。由于 /sys/fs/selinux 是由内核空间的 SELinux LSM 模块导出来的文件系统接口,因此当我们将安全策略写入到位于该文件系统中的 load 文件时,就相当于是将安全策略从用户空间加载到 SELinux LSM 模块中去了。

以后 SELinux LSM 模块中的 Security Server 就可以通过它来进行安全检查

(4)加载完成,释放 sepolicy 文件占用的内存,并且关闭 sepolicy 文件
security_setenforce
前面已经提过,selinux 有两种工作模式:

  • 宽容模式(permissive) - 仅记录但不强制执行 SELinux 安全政策。
  • 强制模式(enforcing) - 强制执行并记录安全政策。如果失败,则显示为 EPERM 错误。

这个函数用来设置 kernel SELinux 的模式,实际上都是去操作 /sys/fs/selinux/enforce 文件, 0 表示permissive,1 表示 enforcing

selinux_init_all_handles
在 init 进程的用户态启动过程中会调用这个函数初始化 file_context、 property_context 相关内容 handler,根据前面的描述,init 进程给一些文件或者系统属性进行安全上下文检查时会使用 libselinux 的 API,查询文件根目录下 file_context、file_context 的安全上下文内容。所以需要先得到这些文件的 handler,以便可以用来查询系统文件和系统属性的安全上下文。

static void selinux_init_all_handles(void)
{
    sehandle = selinux_android_file_context_handle();
    selinux_android_set_sehandle(sehandle);
    sehandle_prop = selinux_android_prop_context_handle();
}

待看:
https://www.jianshu.com/p/4780e22b5446?from=timeline&isappinstalled=0

独特见解