Android安全模型简介之SEAndroid

Android平台的基础是Linux内核,android每个应用都运行在自己的沙盒中,在Android4.3之前的版本中,android会给每一个应用分配一个独一无二的ID,所以每个应用都有自己的权限边界,从Android4.3开始,android引入了SELinux,并加以适配,构成了SEAndroid,进一步定义Android应用沙盒的边界,运行在单独的进程中,所以每个应用都有自己的权限边界,系统负责管理Android应用对资源的访问权限。作为Android安全模型的一部分,Android使用SELinux的强制访问控制(MAC) 来管理所有的进程,即使是进程具有root(超级用户权限)的能力,SELinux通过创建安全策略(sepolicy)来限制特权进程来增强Android的安全性。从Android4.4开始Android打开了SELinux的Enforcing模式,使其工作在默认的AOSP代码库定义的安全策略(sepolicy)下。在Enforcing模式下,违反SELinux安全策略的的行为都会被阻止,所有不合法的访问都会记录在dmesg和logcat中。

1 访问控制机制

1.1 DAC

全称是Discretionary Access Control,翻译为自主访问控制。DAC的核心思想很简单,其原理就是:进程理论上所拥有的权限与执行它的用户的权限相同。比如,以root用户启动Browser,那么Browser就有root用户的权限,在Linux系统上能干任何事情。DAC是传统的Linux的访问控制方式,DAC可以对文件、文件夹、共享资源等进行访问控制。 在DAC这种模型中,文件客体的所有者(或者管理员)负责管理访问控制。DAC使用了ACL(Access Control List,访问控制列表)来给非管理者用户提供不同的权限,而root用户对文件系统有完全自由的控制权。

1.2 MAC

由于DAC的管理太过宽松,所以NSA设计了一种新的安全模型,叫MAC(Mandatory Access Control),翻译为强制访问控制。MAC的理论也很简单,MAC核心思想:即任何进程想在SELinux系统中干任何事情,都必须先在安全策略配置文件中赋予权限。凡是没有出现在安全策略配置文件中的权限,进程就没有该权限。这个机制相当于一个白名单,这个白名单上配置了所有进程的权限,进程只能做白名单上权限内的事情,一旦它想做一个不属于它权限的操作就会被拒绝。

MAC不再像DAC一样简单的把进程分为root others等,而是每个进程(Subject,主体)和文件(Object,客体)都配置了一个类型(Type),当一个进程去操控(读写等)一个文件时,系统会检测该进程类型是否有对该文件类型的操作权限。

通过命令ps -Z 可以查看进程的安全label,比如进程安全上下文中的com.mediatek.ims进程,他的type为radio,下面的进程的type为platform_app。(其实进程的type也被称为Domain )

主体输出.png

图1  ps -Z 输出

ls -Z 可以看到文件的label(安全上下文),文件安全上下文中的bugreports的type为rootfs,

客体输出.png

图2  ls -Z 输出

1.3 总结

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

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

2 SEPolicy语言

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

3 安全策略

3.1 SecurityContext

SEAndroid通过type(Security Context)来决定应用的访问权限,Security Context格式:

User:Role:Type:SecurityLevel
比如
u:r:system_app:s0              system        2656   501 0 20:59:02 ?     00:00:00 com.mediatek.providers.drm
  • User

    参考以上案例,u为user的意思,SEAndroid中定义了一个SELinux用户,值为u。

  • Role

    r为role的意思,role是角色之意,它是SELinux 中一个比较高层次,更方便的权限管理思路。简单点说,一个u可以属于多个role,不同的role具有不同的权限。Android中也只有一个role,值为r。

  • Type

    代表该进程所属的Domain为system_app。MAC(Mandatory Access Control)强制访问控制的基础管理思路其实是Type Enforcement Access Control(简称TEAC,一般用TE表示),对进程来说,Type就是Domain,比如system_app需要什么权限,都需要通过allow语句在te文件中进行说明。

  • SecurityLevel

    s0是SELinux为了满足军用和教育行业而设计的Multi-Level Security(MLS)机制有关。简单点说,MLS将系统的进程和文件进行了分级,不同级别的资源需要对应级别的进程才能访问。Android下默认通常为s0。

3.2 te

SELinux使用类型强制来改进强制访问控制。所有的主体(程序进程)对客体(文件/socket等资源)的访问都有一条TE规则来许可。在TE中,所有的东西都被抽象成类型。进程抽象成类型;资源抽象成类型。属性是类型的集合。所以TE规则中的最小单位就是类型。当程序访问一个资源的时候,系统会搜索所有的TE规则集,并根据结果进行处理。这个规则集是由访问向量规则(AV,Access Vector)来描述的。

3.2.1 语法

SELinux语法如下图所示,下图的语句表示允许mediaserver进程对app_data_file类型的文件(file)进行rw_file_perms操作。

selinux语法.png

图3  selinux语法

  1. 属性和类型

    声明类型用type关键字:

    type 类型名称 [alias 别名集] [,属性集];
    type XXXXX;
    type alias {aa,bb,cc} XX,YY;          属性用逗号分开
    

    我们还可以为类型添加别名:

    # 这两条语句等同于   
    type mozilla_t, domain;   
    typealias mozilla_t alias netscape_t;   
    # 下面这一条语句   
    type mozilla_t alias netscape_t, domain; 
    

    声明属性用,android属性定义文件在android/external/sepolicy/attributes文件中,这里定义了系统所需的几乎全部的属性。

    # All types used for processes.
    attribute domain;
    
    # All types used for files that can exist on a labeled fs.
    # Do not use for pseudo file types.
    attribute file_type;
    
    # All types used for domain entry points.
    attribute exec_type;
    
    # All types used for /data files.
    attribute data_file_type;
    
    # All domains used for apps.
    attribute appdomain;
    
    # All domains used for apps with network access.
    attribute netdomain;
    
  2. 关联属性和类型

    有两种方法可以将某个类型跟某个属性关联起来。第一,在使用type声明类型的时候就关联已经定义的属性;第二,使用typeattribute进行属性关联。

    # /data/data subdirectories - app sandboxes
    type app_data_file, file_type, data_file_type;
    
    # /data/data subdirectory for system UID apps.
    type system_app_data_file, file_type, data_file_type, mlstrustedobject;
    
    # Compatibility with type name used in Android 4.3 and 4.4.
    typealias app_data_file alias platform_app_data_file;
    typealias app_data_file alias download_file;
    
    type keystore, domain;
    type keystore_exec, exec_type, file_type;
    init_daemon_domain(keystore)
    ////////////////////////////////////////
    typeattribute keystore mlstrustedsubject;
    
  3. 访问向量(AV)规则
    • allow

      表示允许主体对客体执行许可的操作。

    • neverallow

      表示不允许主体对客体执行制定的操作。

    • dontaudit

      表示允许操作并记录访问决策信息。

    • auditallow

      表示不记录违反规则的决策信息,切违反规则不影响运行。

3.2.2 案例1

例如在***.te中书写主体的SecurityContext规则,其具体格式如下:

type subject, attributes;
allow/neverallow/dontaudit/auditallow domains types:classes permissions;
比如
allow appdomain app_data_file:file rw_file_perms;
这表示域为appdomain的进程都可以读取和写入带有app_data_file标签的文件
  • domain

    一个进程或一组进程的标签。也称为域类型,因为它只是指进程的类型。

  • type

    一个对象(例如,文件、套接字)或一组对象的标签。

  • class

    要访问的对象(例如,文件、套接字)的类型。

  • permissions

    要执行的操作(例如,读取、写入)。

3.2.3 案例2

以下是init进程的策略配置文件,其中详细描述了init的权限范围。

# 将init关联到domain,即将domain设置为init类型的属性
type init, domain;

# 允许init类型对unlabeled类型的filesystem进行mount的操作
allow init unlabeled:filesystem mount;

# 允许init类型对fotad类型的unix_stream_socket 进行bind和create的操作
allow init fotad:unix_stream_socket { bind create };

# appdomain是定义在te_macros里面的一个宏,很多的app规则会使用类似app_domain(shell)的命令将其添加进去
# 允许app去对anr_data_file类型的目录进行查找的操作
allow appdomain anr_data_file:dir search;

# 允许app对anr_data_file类型的file进行打开和添加操作
allow appdomain anr_data_file:file { open append };

#绝不允许app(除了有unconfineddomain属性的app)对kmem_device类型的字符设备进行读写的操作
neverallow { appdomain -unconfineddomain } kmem_device:chr_file { read write };

# 绝不允许除了unconfineddomain以外的app对self类型的capability2进行任何的操作
neverallow { appdomain -unconfineddomain } self:capability2 *;

# 声明一个httpd_user_content_t的类型,具有file_type和httpdcontent的属性
type httpd_user_content_t, file_type, httpdcontent;

# 声明一个httpd_user_content_t的类型
type httpd_user_content_t;

# 定义httpd_user_content_t具有file_type, httpdcontent的属性
typeattribute httpd_user_content_t file_type, httpdcontent;

# 允许所有具有app属性的内容可以去对self属性的rawip_socket进行create的操作
allow appdomain self:rawip_socket create_socket_perms;

# 允许user_t和domain属性的类对bin_t, file_type, sbin_t类型的file进行可执行的操作
allow {user_t domain} {bin_t file_type sbin_t}:file execute ;

# 这两条语句的表述其实是一致的,其实self指的是目标的类型和发起人的类型是一致的
allow user_t user_t:process signal;
allow user_t self:process signal;

# 允许user_t对bin_t类型的file进行除了write setattr ioctl相关的操作
allow user_t bin_t:file ~{ write setattr ioctl };

4 SELinux的工作模式

SELinux提供了3种工作模式:Disabled、Permissive和Enforcing,而每种模式都为Linux系统安全提供了不同的好处。

4.1 Disabled

在Disable模式中,SELinux被关闭,默认的DAC访问控制方式被使用。对于那些不需要增强安全性的环境来说,该模式是非常有用的。

4.2 Permissive

在Permissive模式中,SELinux被启用,但安全策略规则并没有被强制执行。当安全策略规则应该拒绝访问时,访问仍然被允许。但是此时会向日志文件发送一条消息,表示该访问应该被拒绝。SELinux Permissive 模式主要用于以下几种情况:审核当前的SELinux策略规则、测试新应用程序,看看将SELinux策略规则应用到这些程序时会有什么效果、调试解决某一特定服务或应用程序在SELinux下不再正常工作的故障。

4.3 Enforcing

从此模式的名称就可以看出,在Enforcing模式中,SELinux被启动,并强制执行所有的安全策略规则,开始限制domain/type 了。

5 如何快速生成策略文件

  1. 设置SELinux为宽容模式

    在init.rc中会去检查判断kernel_cmdline里面是否有androidboot.seliux变量,当androidboot.seliux值为enforcing为打开,permissive为关上。所以我们在Boardconfig.mk里面对BOARD_KERNEL_CMDLINE进行添加androidboot.selinux=permissive即可将selinux设置为permissive模式用于调试。

    static selinux_enforcing_status selinux_status_from_cmdline() {
      selinux_enforcing_status status = SELINUX_ENFORCING;
      import_kernel_cmdline(false, [&](const std::string& key, const std::string& value, bool in_qemu) {
        if (key == "androidboot.selinux" && value == "permissive") {
          status = SELINUX_PERMISSIVE;
        }
      });
    return status;
    }
    
  2. 使用audit2allow自动生成

    在permissive模式下,会在logcat以及kmsg中打印avc信息。通过adb shell dmesg | grep avc | grep "pid=***" > avc.txt可以抓取avc信息。

    03-25 22:56:15.009   499   499 I httpshell: type=1400 audit(0.0:134): avc: denied { net_raw } for capability=13 scontext=u:r:shell:s0 tcontext=u:r:shell:s0 tclass=capability permissive=1
    03-25 22:56:20.869  2833  2833 I re-initialized>: type=1400 audit(0.0:135): avc: denied { read } for name="device.json" dev="dm-0" ino=1785859 scontext=u:r:radio:s0 tcontext=u:object_r:shell_data_file:s0 tclass=file permissive=1
    03-25 22:56:20.869  2833  2833 I re-initialized>: type=1400 audit(0.0:136): avc: denied { open } for path="/data/local/tmp/device.json" dev="dm-0" ino=1785859 scontext=u:r:radio:s0 tcontext=u:object_r:shell_data_file:s0 tclass=file permissive=1
    

    然后通过audit2allow指定日志文件自动生成te代码。

    audit2allow -i avc.txt > avc.te