谈谈Android 安全策略SElinux

不积跬步无以至千里,补全自己的短板,完善体系,站在巨人的肩膀上,看到的更远,写这篇文章也算是对这个知识点的总结。

一,背景

SElinux出现之前,Linux上的安全模型叫DAC(Discretionary Access Control 自主访问控制),它的核心思想就是:进程拥有的权限与执行它的用户权限相同,比如,以root用户启动camera, 那么camera就拥有root的用户权限,我们知道root的权限是很大的,可以说为所欲为。

所以DAC方式管理太过宽松,只要应用获得了root权限,可以在后台做很多我们不知道的事情,所以在4.4之前都建议不要root破解,我记得5.0之前还有很多应用提示“如果想要运行应用,需要root”,现在想想,不知道该应用做了哪些不可告人的事情。

后来,研究过AOSP的应该知道Android5.0算是一次大的跨度升级,在5.0上google启用了新的权限管理,增加了系统安全性,

SElinux是一种新的安全模型,称为MAC(Mandatory Access Control 强制访问控制),他的思想就是:任何进程想要在SElinux系统中干任何事, 都必须在安全策略配置文件中赋予权限,凡是没有出现在安全策略配置文件中的权限,进程没有权限操作任何事。

我们来看个例子

 下面这条SELinux语句表示 允许(allow )hal_nfc_default域(domain)中的进程  ”创建“类型为nfc_vendor_data_file的文件

allow hal_nfc_default nfc_vendor_data_file:file { create rw_file_perms };

如果没有在hal_nfc.te中使用上例中的权限配置allow语句,则hal_nfc_default就无法在data目录创建文件,即使hal_nfc_default具有root权限

DAC和MAC对比总结:

1)Android 系统先做DAC检查, 如果没有通过DAC检查,操作直接失败,通过DAC后,再做MAC权限检查。

2)SELinux中也有用户的概念,但和Linux中原有的user概念不同, Linux中的超级用户root在SELinux中可能就是一个没权限,没地位打打酱油的“路人甲”。

写这篇文章的目的是跟着前辈的经验,能够看懂现有的策略文件,能够编写新添加的权限文件或语句。

二,SELinux Policy 语言介绍

Linux有种思想,即万事万物皆文件,普通文件是文件,目录是文件,设备是文件。

SELinux中,每种东西都会被赋予一个安全属性,官方说法叫Security Context。Security Context(以后用SContext表示)是一个字符串,主要由三部分组成,分为进程和文件两种。

SEAndroid中,进程的SContext可通过ps -ZA命令查看,

最左边的LABEL列值进程的SContext,以第一个u:r:init:s0为例,说明代表含义

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

文件的Security Context,可通过ls -lZ命令查看,

上图中倒数第二列为手机内根目录下文件和目录的SContext信息,同样以第一行u:object_r:vendor_file:s0为例,说明代表含义

  • u:同样是user之意,它代表创建这个文件的SELinux user。
  • object_r:文件是死的东西,它没法扮演角色,所以在SELinux中,死的东西都用object_r来表示它的role。
  • vendor_file:死的东西的Type,和进程的Domain其实是一个意思,它表示odm目录对应的Type是vendor_file。
  • s0:MLS的级别。

根据SELinux规范,完整的SContext字符串为:

user:role:type[:range]

注意,方括号中的内容表示可选项,s0属于range中的一部分。

三,TE介绍

MAC(Mandatory Access Control 强制访问控制)基本管理单位是TEAC(Type Enforcement Access Control), 然后是高一级别的Role Based Accesc Control。RBAC是基于TE的,而TE也是SELinux中最主要的部分。我们常添加的allow就是TE的范畴。

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

allow domains types:classes permissions;

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

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

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

语句:
allow hal_nfc_default nfc_vendor_data_file:file { create rw_file_perms };
  • allow:TE的allow语句,表示授权。除了allow之外,还有allowaudit、dontaudit、neverallow等。
  • hal_nfc_default:source type,也叫subject,domain。
  • nfc_vendor_data_file:target type,代表其后的file所对应的Type.
  • file:代表Object Class,它代表能够给subject操作的一类东西。例如File、Dir、socket等。在Android系统中,有一个其他Linux系统没有的Object Class,那就是Binder。
  • create rw_file_perms:在该类Object Class中所定义的操作

下面根据几个实例了解权限的写法

#允许zygote域中的进程search或getattr类型为appdomain的目录。注意,多个perm_set

#可用{}括起来

allow zygote appdomain:dir { getattr search };

#来个复杂点的:

#source_type为unconfineddomain target_type为一组type,由
#{ fs_type dev_type file_type }构成。object_class也包含两个,为{ chr_file file }

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

allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file }   \

 ~{entrypoint relabelto};

#特殊符号除了~外,还有-号和*号,其中:

# 1):-号表示去除某项内容。

# 2):*号表示所有内容。

#下面这条语句中,source_type为属于appdomain,但不属于unconfinedomain的进程。

#而 *表示所有和capability2相关的权限

#neverallow:表示绝不允许。

neverallow { appdomain -unconfineddomain } self:capability2 *;

 特别注意,前面曾提到说权限必须显示声明,没有声明的话默认就没有权限。那neverallow语句就没必要存在了。因为”无权限“是不需要声明的。确实如此,neverallow语句的作用只是在生成安全策略文件时进行检查,判断是否有违反neverallow语句的allow语句。

3.2 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

3.3 Permissions

指的是某种class 所拥有的权限,以file这种object class而言,拥有的permission如下

查看路径:system/sepolicy/private/access_vectors

#
# Define common prefixes for access vectors
#
# common common_name { permission_name ... }


#
# Define a common prefix for file access vectors.
#

common file
{
    ioctl
    read
    write
    create
    getattr
    setattr
    lock
    relabelfrom
    relabelto
    append
    map
    unlink
    link
    rename
    execute
    quotaon
    mounton
    audit_access
    open
    execmod
    watch
    watch_mount
    watch_sb
    watch_with_perm
    watch_reads
}

从上面例子可以看出SELinux定义perm set方式使用common命令,实际使用中我们可以参考该文件的定义

#除了common外,还有一种class命令也可定义perm set,如下面的例子:

#class命令的完整格式是:

#class class_name [ inherits common_name ] { permission_name ... }

#inherits表示继承了某个common定义的权限  注意,class命令定义的权限其实针对得就是

#某个object class。它不能被其他class继承

class dir inherits file {

   add_name  remove_name reparent search rmdir open audit_access execmod

}

#来看SEAndroid中的binder和property_service这两个Object class定义了哪些操作权限

class binder {

      impersonate  call set_context_mgr transfer }

class property_service { set }

3.4 type

type命令的完整格式为:type type_id [alias alias_id,] [attribute_id]

其中,方括号中的内容为可选。alias指定了type的别名,可以指定多个别名。

四,SELinux应用

上面理论讲述了一堆,估计你已经有点不耐烦,下面我们以实际使用解决问题来说说,

SELinux分两种模式:enforceing mode(限制访问) 和permissive mode(只审查权限,不限制)

4.1怎么关闭这个权限限制呢?

4.1.1 临时关闭,当你的手机是debug模式时可以通过adb shell设置

setenfroce 0

setenforce 命令修改的是 /sys/fs/selinux/enforce 节点的值,0 表示permissive,1 表示 enforcing,是 kernel 意义上的修改 selinux 的策略。系统重启后,节点值会复位

4.1.2永久关闭

SECURITY_SELINUX 设置为 false,重新编译 kernel

make bootimage 重新编译boot,刷机验证

4.1.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

4.2 系统运行权限不够,avc异常处理

在系统开发过程中,经常遇到的一个问题就是没有权限操作某个动作,直观的就是功能未生效。出现此类问题我们可以通过adb logcat 抓取系统log,过滤log中avc子串,看看是否有报对应进程的权限问题。或者关掉权限看看功能是否正常。

案例1,我们抓到一份异常

可以看到INxpNfc没有find权限,分析如下:

谁缺少权限:scontext=u:r:hal_nfc_default:s0  ---->hal_nfc_default

对什么缺少权限:tcontext=u:object_r:default_android_hwservice:s0 ---> default_android_hwservice

什么类型的权限:tclass=hwservice_manager ---> hwservice_manager

缺少什么权限:denied{find} --->find

找到关于nfc的te文件按照套路添加定义

allow scontext_type tcontext_type:tclass_type { denied }

故 allow hal_nfc_default default_android_hwservice:hwservice_manager {find}

也可以使用audit2allow工具生成这个sepolicy规则,路径:AOSP/external/selinux/prebuilts/bin

注意:使用这个工具,需要先 souce build/envsetup.sh -->lunch 之后,audit2allow -i avc_log.txt 会在终端输出,如果想输出到指定文件,使用-p 后面跟路径名

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

新添加的这个权限有可能会引起neverallowed,出现问题可以重新定义相同类型的type,以规避权限检查

案例2

SELinux : avc:  denied  { find } for interface=android.hardware.secure_element::ISecureElement sid=u:r:nfc:s0 pid=21355 scontext=u:r:nfc:s0 tcontext=u:object_r:hal_secure_element_hwservice:s0 tclass=hwservice_manager permissive=0

添加权限allow nfc hal_secure_element_hwservice:hwservice_manager find;

会发现编译报错

libsepol.report_failure: neverallow on line 5 of system/sepolicy/public/hal_secure_element.te (or line 19894 of policy.conf) violated by allow nfc hal_secure_element_hwservice:hwservice_manager { find };
libsepol.check_assertions: 1 neverallow failures occurred
Error while expanding policy

解决方案添加hal_client_domain(nfc, hal_secure_element)

4.3 SELinux Android 源码架构

- 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.3.1 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 文件

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();
}

4.3.2 SEAndroid 编译

audit2allow -i avc_log.txt 即可自动输出生成的policy 

source build/envsetup.sh

lunch

mma system/sepolicy/
adb push out/target/product/xx/vendor/etc/selinux/ vendor/etc/selinux/
adb push out/target/product/xx/system/etc/selinux/ system/etc/selinux/

参考:

深入理解SELinux/SEAndroid(第一部分) - 邓凡平的个人页面 - OSCHINA - 中文开源技术交流社区

android 8.1 安全机制 — SEAndroid & SELinux_岁月斑驳7的博客-CSDN博客_android selinux

你可能感兴趣的:(Android,android,linux)