对Android 平台下SElinux的理解及遇到过的相关问题解决方法总结

笔者在工作中多次遇到和SELinux相关的问题,初次遇到时一头雾水,走了很多弯路,也耗费了很多时间精力。后来看了不少资料和博客,也研究了相关代码,对SELinux有了些认识。所以用本文来做个总结,加深理解。

本文将从下面五个方面来逐步认识和理解Android 下SELinux。

  • 什么是SELinux
  • 为什么需要SELinux
  • SElinux 工作原理
  • android 上的实现
  • 曾经遇到过的问题及解决办法

一、什么是SELinux
     
SELinux(Security-Enhanced Linux) 是美国国家安全局(NSA)对于强制访问控制的实现,是 Linux历史上最杰出的新安全子系统。NSA是在Linux社区的帮助下开发了一种访问控制体系,在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。SELinux 默认安装在 Fedora 和 Red Hat Enterprise Linux 上,也可以作为其他发行版上容易安装的包得到。
SELinux 是 2.6 版本的 Linux 内核中提供的强制访问控制(MAC)系统。对于目前可用的 Linux安全模块来说,SELinux 是功能最全面,而且测试最充分的,它是在 20 年的 MAC 研究基础上建立的。SELinux 在类型强制服务器中合并了多级安全性或一种可选的多类策略,并采用了基于角色的访问控制概念。SELinux是一种基于 域-类型 模型(domain-type)的强制访问控制(MAC)安全系统,它由NSA编写并设计成内核模块包含到内核中,相应的某些安全相关的应用也被打了SELinux的补丁,最后还有一个相应的安全策略。任何程序对其资源享有完全的控制权。
以上来之百度百科,也就是说SELinux 是一个安全系统,这个系统是要强制控制进程对系统资源的访问,并提供基于角色和多层级的安全访问控制策略。它要控制的不仅仅是用户,而是进程级别的。
二、 为什么需要SELinux
      一般的系统提供的访问控制策略(DAC)都是针对用户级别的,比如说一个文件或者目录设定的访问权限针对者都是用户,用户包括 文件的创建者owner,和创建者同在的组group用户,组外用户other,对于这三者我们都可以设定读写执行权限。可见,对于一个文件,如果owner有读写执行权限,以owner运行的所有程序都对这个文件有读写执行权限。这里就会有问题了?如果owner 不小心运行了一个攻击程序,这个程序就有可能破坏这个文件,这个是不安全的。再者比如用户以root身份登录,就有可能不小心删除掉系统文件,造成不可预料的后果。
   针对以上场景,SELinux就能派上用场了。使用SELinux,我们可以指定只有满足某种条件的进程才能访问相关的系统资源,这样即使用户运行了攻击程序或者以root身份运行,它也访问不了SElinux 不授权的文件。

三、SELinux 的工作原理
        1. 把系统实体分为主体和客体。比如进程为主体,文件、目录、文件描述符,网络套接字等为客体,进程也可以作为客体看待。
        2. 对主体定义域上下文属性,对文件定义类型上下文属性,其实就是打个标签。
       3. 在用户空间的策略文件里配置域所具有的访问某种类型属性以及域切换规则等
       4. 策略的判断和执行由内核实现。  在系统运行过程中,当某个进程要访问一个文件时,有系统内核根据用户空间的配置的策略文件来判断进程的域上下文是否有访问文件的权限。同时在需要是完成域切换等操作。
下面看下什么是域上下文和类型上下文
        用命令 ps -Z  可列出当前系统正在运行进程的域上下文:
        
      用命令 ls -Z 可列出文件和目录的类型上下文属性:
     
    可以看到域和类型的定义格式是一样的,其实可认为表达的是同一个意思,就是标签。
    格式为:用户:角色:域名(或者类型):等级
    用户: 标识哪个用户,android里面都是u
   域名(类型):用来标识一个安全上下文,不管是域还是类型,在SELinux看来都是一个属性,划分成不同的类型和域,其实是为了在策略那里可以达到统一控制。
      下面是定了2个类型集合(组)和2个域集合(组)。
# All types used for devices.
attribute dev_type;
# All types used for processes.
attribute domain;
# All types used for filesystems.
attribute fs_type;
# All domains used for apps.
attribute appdomain;
   下面的定义的 logd_debugsystem_file类型都属于file_type类型组。
    type logd_debug, file_type;
      type system_file, file_type;
    当然一个类型可以同时属于多个类型组。
    下面是init 和init_shell 进程的安全上下文域,它们都属于 domain域组。
    type init, domain;
    type init_shell, domain;
    那么就可以只通过下面一条策略来控制所有属于 domain的进程的权限,而不需要单独针对每个进程域去做处理。
   allow domain file_type:dir getattr; // 允许所有属于domain 域组的进程对所有属于 fs_type类型组的目录客体有读取属性的权限。
   当然也可以针对单个域做控制:
   allow init_shell system_file:file execute_no_trans;  // 允许所有属于init_shell 域的进程对所有属于 system_file类型的文件具有执行权限。
    角色:android 里只有两个,主体是r,客体是object_r。SELinux是同时支持基于角色的访问控制的,比如针对公司可以定义总经理角色,部门经理角色和员工角色的三个角色,在允许同样程序情况下,对有的资源,只允许总经理角色访问,有的只允许部门经理访问。可以通过角色来控制。
   级别:android 里面都是s0 。SELinux同时也支持多层次安全级别管理,在什么场景下可能会用到呢?比如说政府部门,可能设定高级别的领导是可以任意读取低级别职员资料的,但不能给低级别写资料,防止泄密;同样低级别职员只能给领导写东西,但是不能读取领导的信息。对应到系统里,就是级别高的进程可以读取比它低级别的客体,低级别的进程不能读取高级别的客体;低级别的进程可以往高级别的客体写东西,高级别进程不能往低级别客体写东西。这点使用SElinux是可以实现的。

 更多的策略配置实现在下节对照android上的实现来分析。

四、 android 上的实现
    1.  在init.c main函数里面初始化
int main(int argc, char **argv){
..........
union selinux_callback cb;
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
selinux_initialize();
.......
}
selinux_initialize()这个函数里面先判断SELinux 是否启用,启用则载入编译出来的策略文件,并在用户空间用mmap方式与内核共享策略文件,这样内核之后就按照拿到的策略来控制访问,然后设置SELinux启动模式。(./external/libselinux/src)
   SELinux 启动模式有两种enforcing 和permissive。 enforcing 是强制安全检查,不符合安全策略时不允许执行;permissive 模式也进行安全检测,但是遇到不符合安全策略时仅仅打印警告信息,依然运行程序执行。
   可以在编译文件里面kernel cmdline 里面加入启动模式如androidboot.selinux=permissive。 一般eng版本使用的是permissive,user和user-debug版本启动的是enforcing。当然如果对安全性要求不高,完全可以不启用SELinux。在android5.1 之前默认并没有真正意义上的使用。
 2. 策略文件在哪配置
    android模式的策略配置文件放在 external/sepolicy目录下,一般不提倡直接修改这下面的文件,而是针对平台或者项目在device或者vendor目录下添加。比如:
  自己策略文件所在目录:
    BOARD_SEPOLICY_DIRS += \
                    device/xxx/sepolicy

  所添加的策略文件
BOARD_SEPOLICY_UNION := \
        bluetooth.te \
         file.te 
  这样添加的策略就参与编译了。
3. 编译出来的策略文件在哪?
    编译出来的策略文件在out\...\root目录下
   root\file_contexts
   root\sepolicy
   root\property_contexts
   root\seapp_contexts
   root\service_contexts
   所以修改完只需要编译bootimage,但是烧录boot 就可以验证了。
4. 如何配置策略
   需要先了解系统针对客体和主体提供了哪些设置属性和操作。
    external/sepolicy/security_classes 定义了客体的基本类型,比如下面的
 # for userspace object managers
class security
class process
class system
class capability
# file-related classes
class filesystem
class file
class dir
class fd
class lnk_file
class chr_file
class blk_file
class sock_file

      external/sepolicy/ access_vectors 文件列出来各种客体类型所拥有的属性,比如下面的file类型和dir 类型,类型间可以有集成关系。
common file
{
ioctl
read
write
create
getattr
setattr
lock
relabelfrom
relabelto
append
unlink
link
rename
execute
swapon
quotaon
mounton
}
class dir
inherits file
{
add_name
remove_name
reparent
search
rmdir
open
audit_access
execmod
}
external/sepolicy/global_macros全局 宏变量定义文件,也就是用一个宏变量同时表示几个属性。如下例子
define(`x_file_perms', `{ getattr execute execute_no_trans }')
define(`r_file_perms', `{ getattr open read ioctl lock }')
define(`w_file_perms', `{ open append write }')
define(`rx_file_perms', `{ r_file_perms x_file_perms }')
define(`ra_file_perms', `{ r_file_perms append }')
define(`rw_file_perms', `{ r_file_perms w_file_perms }')
define(`rwx_file_perms', `{ rw_file_perms x_file_perms }')
define(`link_file_perms', `{ getattr link unlink rename }')
define(`create_file_perms', `{ create setattr rw_file_perms link_file_perms }')
external/sepolicy/te_macros 宏规则变量定义文件,也就是用一个变量来表示连续执行几条规则。如下例子
define(`domain_trans', `
# Old domain may exec the file and transition to the new domain.
allow $1 $2:file { getattr open read execute };
allow $1 $3:process transition;
# New domain is entered by executing the file.
allow $3 $2:file { entrypoint open read execute getattr };
# New domain can send SIGCHLD to its caller.
allow $3 $1:process sigchld;
# Enable AT_SECURE, i.e. libc secure mode.
dontaudit $1 $3:process noatsecure;
# XXX dontaudit candidate but requires further study.
allow $1 $3:process { siginh rlimitinh };
')
external/sepolicy/file_contexts  文件安全上下文定义文件。就是说对于一个文件或者目录,它的安全上下文应该在这里定义,如果没有定义,那么默认和父目录的安全上下文一样。
比如说我们在data目录下建了一个log目录存放系统运行时的log,那么我们可能希望这个log目录的的安全上下文是u:object_r:data_log_file:s0
那么就应该在 file_contexts文件里做如下定义:/data/log(/.*)?u:object_r:data_log_file:s0

external/sepolicy/property_contexts 属性安全上下文定义文件。比如说新加了一个persist.log. * 开头的属性,那么就需要给它添加对应的安全上下文,如果不添加或者添加的上下文不对,那么在的某个进程里写入时就写不进去。
比如做了如下定义,那么是具有 system_prop域安全上下文,只能在system进程里修改值,在shell里是不能访问的。
persist.log.        u:object_r:system_prop:s0
如果希望在shell里能修改值,就应该这么写persist.log.        u:object_r:shell_prop:s0 ,当然如果通过修改相关*.te 文件也能实现,但是不是很规范。

external/sepolicy/property.te 文件允许我们定义自己的prop 类型域,还有seapp_contexts、service_contexts 文件等,都是类似的,都可以根据需要修改。

下面几个是常用到的规则操作
allow:赋予某项权限。
allowaudit:audit含义就是记录某项操作。默认情况下是SELinux只记录那些权限检查失败的操作。allowaudit则使得权限检查成功的操作也被记录。注意,allowaudit只是允许记录,它和赋予权限没关系。赋予权限必须且只能使用allow语句。
dontaudit:对那些权限检查失败的操作不做记录。
neverallow:前面讲过,用来检查安全策略文件中是否有违反该项规则的allow语句
规则命令格式:规则名 主体域 客体域:类型 权限属性 ;       或者    规则名 主体域 客体域:类型 {权限属性集合} ;
比如:
allow domain system_data_file:dir { search getattr };
allow domain system_file:file r_file_perms;

规则都定义在以te为后缀的文件里面,比如见到的init.te  installd.te system_app.te platform_app.te shell.te 等等。init.te 是针对init进程定义的规则,system_app.te 针对的是app进程是system的应用定义的规则,shell.te 针对的是shell进程定义的规则,以此类推。如果我们对某种类型的进程定义了新的规则,那么我们应该把新规则添加到它对应的te文件里面。 比如我们在data目录下加了一个log目录,并且它的DAC权限为777,安全上下文是 u:object_r:data_log_file:s0, 我们希望系统运行过程通过shell命令查看log,那么我们就可以往shell.te 文件添加如下策略:
allow shell data_log_file:dir r_dir_perms;
allow shell data_log_file:file r_file_perms;
如果我们新启了一个进程,并且希望它运行在自己定义的安全上下文里面,那么我们应该给它添加对应的te规则文件。

五、 曾经遇到过的问题和解决办法

   1. 在init.rc 文件里启动一个log服务,一直属于restarting 状态,无法启动。系统输出如下warning log。

W/init    ( 9795): type=1400 audit(0.0:284): avc: denied { execute_no_trans } for path="/system/bin/start_log.sh" dev="dm-0" ino=430 scontext=u:r:init:s0 tcontext=u:object_r:logsvc_exec:s0 tclass=file permissive=0

    主体是:init进程,对应域是u:r:init:s0 

    客体是:system/bin/start_log.sh 对应域是u:object_r:logsvc_exec:s0

    这个log的意思就是init进程在执行system/bin/start_log.sh 时没有 execute_no_trans权限。那我们就给它init加权限。

    找到init.te 文件添加如下到末尾:

    allow init logsvc_exec:file execute_no_trans;


   2. 在开发一个自动压力测试程序,其中有一项是测试休眠唤醒相关的,会通过读取/d/suspend_stats 设备节点来判断是否有休眠失败情况。但是什么也读取不到这个值。打log发现如下warning。

    09-07 12:22:52.480 W/AutoStressTools(32594): type=1400 audit(0.0:11): avc: denied { read } for name="suspend_stats" dev="debugfs" ino=10246 scontext=u:r:system_app:s0 tcontext=u:object_r:debugfs:s0 tclass=file permissive=0

    主体是:AutoStressTools应用,属于system app进程,对应域是u:r:system_app:s0

    客体是:/d/suspend_stats 对应域是u:object_r:debugfs:s0

    这个log的意思就是system读取 /d/suspend_stats 时没有 read权限。那我们就给它system app加权限。

    找到system_app.te 文件添加如下到末尾:

    allow system_app debugfs:file r_file_perms;


   3. CTS认证相关的,在init.rc 添加一个服务后,CTS 测试failed掉。

    

-- testInitDomain
junit.framework.AssertionFailedError: Expected 1 process in SELinux domain "u:r:init:s0" Found "[pid: "1" proctitle: "/init" label: "u:r:init:s0" vsize: 4161536, pid: "211" proctitle: "/system/bin/memsicp" label: "u:r:init:s0" vsize: 3977216]" expected:<1> but was:<2> at junit.framework.Assert.fail(Assert.java:50)
  也就是说使用了init域,这个在系统看来是不安全的,init域权限比较大。

  那么我们就另外给这个服务添加域就可以,并且添加对应的规则te文件。


你可能感兴趣的:(Android)