sheldon_blogs

Android : SELinux 简析&修改

一 SELinux背景知识

SELinux出现之前,Linux上的安全模型叫DAC,全称是Discretionary Access Control,翻译为自主访问控制。DAC的核心思想很简单,就是:

  • 进程理论上所拥有的权限与执行它的用户的权限相同。比如,以root用户启动Browser,那么Browser就有root用户的权限,在Linux系统上能干任何事情。

显然,DAC太过宽松了,所以各路高手想方设法都要在Android系统上搞到root权限。那么SELinux如何解决这个问题呢?原来,它在DAC之外,设计了一个新的安全模型,叫MAC(Mandatory Access Control),翻译为强制访问控制。MAC的处世哲学非常简单:即任何进程想在SELinux系统中干任何事情,都必须先在安全策略配置文件中赋予权限。凡是没有出现在安全策略配置文件中的权限,进程就没有该权限。来看一个SEAndroid中设置权限的例子:

/* 示例:

  from external/sepolicy/netd.te

 下面这条SELinux语句表示 允许(allow )netd域(domain)中的进程  ”写(write)“ 

 类型为proc的文件

 注意,SELinux中安全策略文件有自己的一套语法格式,下文我们将详细介绍它

*/

allow netd proc:file write

如果没有在netd.te中使用上例中的权限配置allow语句,则netd就无法往/proc目录下得任何文件中写数据,即使netd具有root权限。

这条语句的语法为:

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

根据SELinux规范,完整的allow相关的语句格式为:rule_name source_type target_type : class perm_set

 

二、 SELinux Policy语言介绍

SELinux中,每种东西都会被赋予一个安全属性,官方说法叫Security Context。Security Context(以后用SContext表示)是一个字符串,主要由三部分组成。例如SEAndroid中,进程的SContext可通过ps -Z命令查看,如图1所示:

以第一个进程/init的SContext为例,其值为u:r:init:s0,其中:

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

再来看文件的SContext,读者可通过ls -Z来查看,如图2所示:

以第一行acct目录为例,其信息为u:object_r:cgroup:s0 :

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

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

user:role:type[:range]

注意,方括号中的内容表示可选项。s0属于range中的一部分,MAC基本管理单位是TEAC(Type Enforcement Accesc Control),然后是高一级别的Role Based Accesc Control。RBAC是基于TE的,而TE也是SELinux中最主要的部分。

 

三、Security Labeling:

Android系统启动后(其他Linux发行版类似),init进程会将一个编译完的安全策略文件传递给kernel以初始化kernel中的SELinux相关模块(姑且用Linux Security Module:LSM来表示它把),然后LSM可根据其中的信息给相关Object打标签。

LSM初始化时所需要的信息以及SContext信息保存在两个特殊的文件中,以Android为例,它们分别是:

  • initial_sids:定义了LSM初始化时相关的信息。SID是SELinux中一个概念,全称是Security Identifier。SID其实类似SContext的key值。因为在实际运行时,如果老是去比较字符串(还记得吗,SContext是字符串)会严重影响效率。所以SELinux会用SID来匹配某个SContext。
  • initial_sid_context:为这些SID设置最初的SContext信息。

 

四、SEAndroid源码分析

  本节主要看Google是如何在Android平台定制SELinux的。如前文所示,Android平台中的SELinux叫SEAndroid。

1.  编译sepolicy

Android平台中:(注:Android7.0开始目录为 system/sepolicy/)

  • external/sepolicy:提供了Android平台中的安全策略源文件。同时,该目录下的tools还提供了诸如m4,checkpolicy等编译安全策略文件的工具。注意,这些工具运行于主机(即不是提供给Android系统使用的)
  • external/libselinux:提供了Android平台中的libselinux,供Android系统使用。
  • external/libsepol:提供了供安全策略文件编译时使用的一个工具checkcon。

对我们而言,最重要的还是external/sepolicy。通过如下make命令查看执行情况: mmm external/sepolicy  --just-print

  • sepolicy的重头工作是编译sepolicy安全策略文件。这个文件来源于众多的te文件,初始化相关的文件(initial_sid,initial_sid_context,users,roles,fs_context等)。
  • file_context:该文件记载了不同目录的初始化SContext,所以它和死货打标签有关。
  • seapp_context:和Android中的应用程序打标签有关。
  • property_contexts:和Android系统中的属性服务(property_service)有关,它为各种不同的属性打标签。

2.  SEAndroid修改:

----------【例子1】:通过修改shell的权限,使其无法设置属性:

先来看shell的te,如下所示:

[external/sepolicy/shell.te]

# Domain for shell processes spawned by ADB

type shell, domain;

type shell_exec, file_type;

#shell属于unconfined_domain,unconfined即是不受限制的意思

unconfined_domain(shell)

# Run app_process.

# XXX Split into its own domain?

app_domain(shell)

unconfied_domain是一个宏,它将shell和如下两个attribute相关联:

[external/sepolicy/te_macros]

#####################################

# unconfined_domain(domain)

# Allow the specified domain to do anything.

#

define(`unconfined_domain', `

typeattribute $1 mlstrustedsubject; #这个和MLS有关

typeattribute $1 unconfineddomain;

')

unconfineddomain权限很多,它的allow语句定义在unconfined.te中:

[external/sepolicy/unconfined.te]

......

allow unconfineddomain property_type:property_service set;

从上面可以看出,shell所关联的unconfineddomain有权限设置属性。所以,我们把它改成:

allow {unconfineddomain -shell} property_type:property_service set;

通过一个“-”号,将shell的权限排除。

然后:

  • 我们mmm external/sepolicy,得到sepolicy文件。
  • 将其push到/data/security/current/sepolicy目录下
  • 接着调用setprop selinux.reload_policy 1,使得init重新加载sepolicy,由于/data目录下有了sepolicy,所以它将使用这个新的。

示为整个测试的例子:

  • 重新加载sepolicy之前,笔者可通过"setprop wlan.driver.status test_ok"设置该属性的值为test_ok。
  • 当通过 setprop selinux.reload_policy 1 的命令后,init重新加载了sepolicy。
  • 笔者setprop wlan.driver.status 都不能修改该属性的值。

示为dmesg输出,可以看出,当selinux使用了data目录下这个新的sepolicy后,shell的setprop权限就被否了!

提示:前面曾提到过audit,日志一类的事情。日志由kernel输出,可借助诸如audit2allow等host上的工具查看哪些地方有违反权限的地方。

 

----------【例子2】:Nerverallow 实例分析

1 问题背景介绍

  项目中想要添加与 audio 相关的功能,添加完后,编译版本,在关闭 selinux 的情况下,功能正常,打开 selinux 即有问题,且
在 log 中有 selinux 相关的 log,所以判断此问题为 selinux 问题,根据关闭 selinux 的 log 我们解析出两句 allow 语句:
allow hal_audio_default default_prop:property_service set;
allow audioserver default_prop:property_service set;

2 编译问题

  根据上文生成的.te 语句,我们在代码中创建对应的.te 文件(没有则创建),编译代码,发现代码编译不过,log 如下:

  libsepol.report_failure: neverallow on line 447 of system/sepolicy/public/domain.te (or line 8935 of policy.conf) violated by allow hal_audio_default default_prop:property_service { set };
libsepol.report_failure: neverallow on line 447 of system/sepolicy/public/domain.te (or line 8935 of policy.conf) violated by allow audioserver default_prop:property_service { set }; libsepol.check_assertions: 2 neverallow failures occurred

  分析可知:我 们 添 加 的 代 码 与system/sepolicy/public/domain.te 中的如下代码有冲突:
    Nerverallow { domain -init } default_prop:property_service set;

  我们来对比一下:
  allow hal_audio_default default_prop:property_service set;
  allow audioserver default_prop:property_service set;
  结果很显然,是因为我们的主体对 default_prop 这个 type 进行了 set 操作,所以我们需要绕过去。

3 查清源头

  因为 default_prop 是一个很宏观的概念,所以我们需要查清我们的主体到底是对什么属性进行了 set 操作,所以需要查代码,看代码中具体做了什么操作,代码如下:

  property_set("pmc.audio.spkoff_w_bths_status", "true");

  property_set("pmc.audio.spkoff_w_bths_status", "false");

  由此便得知,我们的主体是对 pmc.audio.这个属性进行了 set操作,所以我们需要给 pmc.audio.重新打一个标记,给属性打标记需要在 property_contexts 中添加,代码如下:

  pmc.audio. u:object_r:audio_prop:s0

4 修改对应的.te 语句

  属性是比较特殊的我们可以直接通过 set_prop 来添加权限,代码如下:

  set_prop(audioserver, audio_prop);

  set_prop(hal_audio_default, audio_prop);

 

 

五、修改方法简结:

1.  编译、提取sepolicy相关信息

1.1 提取所有的avc LOG. 如 adb shell “cat /proc/kmsg | grep avc” > avc_log.txt 
1.2 使用 audit2allow tool 直接生成policy:  audit2allow -i avc_log.txt
1.3 将对应的policy 添加到selinux policy 规则中,对应MTK 厂家目录如: device/mediatek/common/sepolicy :

  allow zygote resource_cache_data_file:dir rw_dir_perms;
  allow zygote resource_cache_data_file:file create_file_perms;
  ===> device/mediatek/common/sepolicy/zygote.te (L)

注意audit2allow 它自动机械的帮您将LOG 转换成policy, 而无法知道你操作的真实意图,有可能出现权限放大问题,经常出现policy 无法编译通过的情况。

2). 按需确认方法

此方法需要工程人员对SELinux 基本原理,以及SELinux Policy Language 有了解. 
2.1 确认是哪个进程访问哪个资源,具体需要哪些访问权限,read ? write ? exec ? create ? search ? 
2.2 当前进程是否已经创建了policy 文件? 通常是process 的执行档.te,如果没有,并且它的父进程即source context 无须访问对应的资源,则创建新的te 文件. 
在L 版本上, Google 要求维护关键 security context 的唯一性, 比如严禁zygote, netd, installd, vold, ueventd 等关键process 与其它process 共享同一个security context. 
2.3 创建文件后,关联它的执行档,在file_contexts 中, 关联相关的执行档. 
比如 /system/bin/idmap 则是 /system/bin/idmap u:object_r:idmap_exec:s0 
2.4 填写policy 到相关的te 文件中 
如果沿用原来父进程的te 文件,则直接添加. 
如果是新的文件,那么首先添加:

  #==============================================
  # Type Declaration
  #==============================================
  type idmap, domain;
  type idmap_exec, exec_type, file_type;

  #==============================================
  # Android Policy Rule
  #==============================================
  #permissive idmap;
  domain_auto_trans(zygote, idmap_exec, idmap);

然后添加新的policy:

  # new policy
  allow idmap resource_cache_data_file:dir rw_dir_perms;
  allow idmap resource_cache_data_file:file create_file_perms;

3). 权限放大情况处理

如果直接按照avc: denied 的LOG 转换出SELinux Policy, 往往会产生权限放大问题. 比如因为要访问某个device, 在这个device 没有细化SELinux Label 的情况下, 可能出现:

  <7>[11281.586780] avc:  denied { read write } for pid=1217 
comm="mediaserver" name="tfa9897" dev="tmpfs" ino=4385 
scontext=u:r:mediaserver:s0 tcontext=u:object_r:device:s0 
tclass=chr_file permissive=0

如果直接按照此LOG 转换出SELinux Policy: allow mediaserver device:chr_file {read write}; 那么就会放开mediaserver 读写所有device 的权限. 而Google 为了防止这样的情况, 使用了neverallow 语句来约束, 这样你编译sepolicy 时就无法编译通过. 
为了规避这种权限放大情况, 我们需要细化访问目标(Object) 的SELinux Label, 做到按需申请. 通常会由三步构成 
3.1 定义相关的SELinux type. 
比如上述案例, 在 device/mediatek/common/sepolicy/device.te 添加

   type tfa9897_device, dev_type;

3.2 绑定文件与SELinux type. 
比如上述案例, 在 device/mediatek/common/sepolicy/file_contexts 添加

/dev/tfa9897(/.*)? u:object_r:tfa9897_device:s0

3.3 添加对应process/domain 的访问权限. 
比如上述案例, 在 device/mediatek/common/sepolicy/mediaserver.te 添加

allow mediaserver tfa9897_device:chr_file { open read write };

那么哪些访问对象通常会遇到此类呢?(以L 版本为例) 

  • File 类型: 
    – 类型定义: external/sepolicy/file.te;
    – 绑定类型: external/sepolicy/file_contexts;

  •  device 
    – 类型定义: external/sepolicy/device.te;
    – 类型绑定: external/sepolicy/file_contexts;
  • 虚拟File 类型: 
    – 类型定义: external/sepolicy/file.te;
    – 绑定类型: external/sepolicy/genfs_contexts;

  • Service 类型: 
    – 类型定义: external/sepolicy/service.te;
    – 绑定类型: external/sepolicyservice_contexts;

  • Property 类型: 
    – 类型定义: external/sepolicy/property.te;
    – 绑定类型: external/sepolicy/property_contexts;

  • 通常我们强烈反对更新google default 的policy, 大家可以更新device目录下对应OEM厂商的policy.

 

 

六、新版本遇到的问题

 1.Android 8.1 以上,非系统进程设置系统域属性问题:

  权限异常的log:

[   24.371278@2] type=1400 audit(1514764828.528:304): avc: denied { read } for pid=3126 comm="HwBinder:3126_1" name="u:object_r:default_prop:s0" dev="tmpfs" ino=12511 scontext=u:r:hal_audio_default:s0 tcontext=u:object_r:default_prop:s0 tclass=file permissive=0 duplicate messages suppressed

  查找代码源头是设置了如下属性:property_set("persist.audio.debug.search", "");

  但是Android 8.1 及以上版本系统添加了权限限制,不允许普通进程设置系统属性,所以按照之前的方法添加会遇到如下编译错误:

22:33:00 FAILED: out/target/product/marconi/obj/ETC/precompiled_sepolicy_intermediates/precompiled_sepolicy 
22:33:00 /bin/bash -c "out/host/linux-x86/bin/secilc -m -M true -G -c 30          out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil out/target/product/marconi/obj/ETC/28.0.cil_intermediates/28.0.cil out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil -o out/target/product/marconi/obj/ETC/precompiled_sepolicy_intermediates/precompiled_sepolicy -f /dev/null"
22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil:7039
22:33:00   (neverallow base_typeattr_262_28_0 base_typeattr_271_28_0 (file (ioctl read write create setattr lock relabelfrom append unlink link rename open)))
22:33:00     <root>
22:33:00     allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1514
22:33:00       (allow hal_audio_default audio_prop_28_0 (file (ioctl read getattr lock map open)))
22:33:00 
22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_pub_versioned.cil_intermediates/plat_pub_versioned.cil:7029
22:33:00   (neverallow base_typeattr_262_28_0 base_typeattr_263_28_0 (property_service (set)))
22:33:00     <root>
22:33:00     allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1513
22:33:00       (allow hal_audio_default audio_prop_28_0 (property_service (set)))
22:33:00 
22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil:9866 from system/sepolicy/public/property.te:155
22:33:00   (neverallow base_typeattr_262 base_typeattr_271 (file (ioctl read write create setattr lock relabelfrom append unlink link rename open)))
22:33:00     <root>
22:33:00     allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1514
22:33:00       (allow hal_audio_default audio_prop_28_0 (file (ioctl read getattr lock map open)))
22:33:00 
22:33:00 neverallow check failed at out/target/product/marconi/obj/ETC/plat_sepolicy.cil_intermediates/plat_sepolicy.cil:9824 from system/sepolicy/public/property.te:155
22:33:00   (neverallow base_typeattr_262 base_typeattr_263 (property_service (set)))
22:33:00     <root>
22:33:00     allow at out/target/product/marconi/obj/ETC/vendor_sepolicy.cil_intermediates/vendor_sepolicy.cil:1513
22:33:00       (allow hal_audio_default audio_prop_28_0 (property_service (set)))
解决方案 1:

允许 hal_audio_default 进程设置 persist.audio.debug.xxx属性

(1)property_contexts 添加:

persist.audio.debug.          u:object_r:audio_debug_prop:s0

(2)hal_audio_default.te 添加:

set_prop(hal_audio_default, audio_debug_prop)   

(3)property.te 添加:

type audio_debug_prop, property_type, mlstrustedsubject; #这一attribute包含了所有能越过MLS检查的主体domain。
解决方案 2:

非系统域的属性设置则没有如上限制,可以将 audio 属性修改为vender 域的属性如: persist.vendor.audio.

 

 2.Android9.0为系统服务添加SELinux权限

  从Android 4.4到Android 7.0的SELinux策略构建方式合并了所有sepolicy片段(平台和非平台),然后在根目录生成单一文件,而Android 8.0开始关于selinux架构也类似于HIDL想把系统平台的selinux策略和厂商自己维护的策略剥离开来, 允许合作伙伴单独自己的策略,构建他们的镜像(.img)引导,这样便可以独立于平台更新这些.img,反之亦然(即:在不更新合作伙伴镜像的情况下执行平台更新)。

  关于8.0 selinux架构介绍官方文档(SELinux_Treble.pdf): https://pan.baidu.com/s/161_OpZRqx7PvOmcQ4G-CwA

  1、例如:xxx service权限异常有如下log:

324 E SELinux : avc: denied { add } for service=xxx pid=933 uid=1000 scontext=u:r:system_server:s0 tcontext=u:object_r:default_android_service:s0 tclass=service_manager permissive=0

  则需要对selinux进行权限配置:(参考公式:allow SourceContext TargetContext:TargetClass Permission)

  allow system_server default_android_service:service_manager { add };

  2、以下部分是对selinux权限进行定义(实际需根据SDK的版本修改对应目录):

(1)./system/sepolicy/prebuilts/api/26.0/nonplat_sepolicy.cil

(typeattribute xxx_service_26_0)
(roletype object_r xxx_service_26_0)

(2)./system/sepolicy/prebuilts/api/27.0/nonplat_sepolicy.cil

(typeattribute xxx_service_27_0)
(roletype object_r xxx_service_27_0)

(3)./system/sepolicy/prebuilts/api/28.0/private/compat/26.0/26.0.cil

(typeattributeset xxx_service_26_0 (xxx_service))

(4)./system/sepolicy/prebuilts/api/28.0/private/compat/27.0/27.0.cil

(typeattributeset xxx_service_27_0 (xxx_service))

(5)./system/sepolicy/prebuilts/api/28.0/private/service_contexts

xxx u:object_r:xxx_service:s0

(6)./system/sepolicy/prebuilts/api/28.0/public/service.te

type xxx_service, system_api_service, system_server_service, service_manager_type;

(7)./system/sepolicy/private/compat/26.0/26.0.cil

(typeattributeset xxx_service_26_0 (xxx_service))

(8)./system/sepolicy/private/compat/27.0/27.0.cil

(typeattributeset xxx_service_27_0 (xxx_service))

(9)./system/sepolicy/private/service_contexts

xxx  u:object_r:xxx_service:s0

(10)./system/sepolicy/public/service.te

type xxx_service, system_api_service, system_server_service, service_manager_type;

 

  3、使用修改selinux权限的系统服务:

// 1.定义aidl文件:------------------------------------
package com.xxx.aidl;
interface ISecurityServer {
    void startLockAppSevice();

}

//2.实现aidl接口:------------------------------------
package com.xxx.aidl;
public class SecurityServer extends ISecurityServer.Stub{
    public void startLockAppSevice() {

    }

}

//3.提供对外接口类:----------------------------------
package com.xxx.security;
public class SecurityManager {
    private final ISecurityServer mService;
    public SecurityManager(ISecurityServer service) {
        mService = service;
    }
    public void startLockAppSevice(){
        try {
            mService.startLockAppSevice();
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

//4.注册服务:---------------------------------------
SystemServiceRegistry.java 添加 

        registerService("xxx", com.xxx.SecurityManager.class,
             new CachedServiceFetcher<com.xxx.SecurityManager>() {
            @Override
            public com.xxx.SecurityManager createService(ContextImpl ctx) {                
                IBinder b = ServiceManager.getService("xxx");
                return new com.xxx.SecurityManager(com.xxx.aidl.ISecurityServer.Stub.asInterface(b));
            }

        });    

//5. SystemServer.java 将服务添加进ServiceManager -------------
        try {
            // 
            com.xxx.aidl.SecurityServer Security = new com.xxx.aidl.SecurityServer(mContext);
            ServiceManager.addService("xxx", Security);
        } catch (Throwable e) {
            Log.e(TAG, "Failure starting olc_service_security", e);

        }

//6. 服务调用:-------------------------------------------------
SecurityManager securityManager = (SecurityManager)getSystemService("xxx");

 

posted on 2017-09-13 15:05  sheldon_blogs  阅读(15967)  评论(0编辑  收藏  举报

导航