Exploring Android's SELinux Kernel Policy

来源:互联网 发布:v2ex java 编辑:程序博客网 时间:2024/06/16 10:28
原文地址:https://ge0n0sis.github.io/posts/2015/12/exploring-androids-selinux-kernel-policy/

探索 Android SELinux 内核策略


简介

自android4.3版本以来,Android系统增加了SELinux来增强系统的安全性。SElinux对系统中的所有进程都进行了强制访问控制,并在Linux原生的访问控制机制基础上实现进一步限制特权进程。
本文着眼于Android系统的SELinux内核策略,将详细的分析SELinux策略编译为二进制文件的过程,分析文件格式,并引入一个作者自创的概念证明(proof-of-concept)工具--sedump。该工具可以将Android ROM中的二进制策略文件还原为SELinux策略。

创建Android安全策略文件(sepolicy)
首先从理解Android系统中的SELinux内核策略的产生开始。创建Android安全策略文件的源码可以从以下两个地方下载:
1.Android源码目录中: https://android.googlesource.com/platform/external/sepolicy
2.Security Enhancements (SE) 的Android分支:https://bitbucket.org/seandroid/external-sepolicy/
$ git clone https://android.googlesource.com/platform/external/sepolicy
$ cd sepolicy
$ ls
access_vectors          bluetoothdomain.te  dnsmasq.te            [...]
adbd.te                 bluetooth.te        domain_deprecated.te  [...]
Android.mk              bootanim.te         domain.te             [...]
[...]

与其他Android项目相同,编译规则在Android.mk文件中,查看这个文件,分析编译sepolicy的规则:
POLICYVERS ?= 29
[...]
LOCAL_MODULE := sepolicy
LOCAL_MODULE_CLASS := ETC
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT)

include $(BUILD_SYSTEM)/base_rules.mk
sepolicy_policy.conf := $(intermediates)/policy.conf
[...]
$(LOCAL_BUILT_MODULE): $(sepolicy_policy.conf) $(HOST_OUT_EXECUTABLES)/checkpolicy
    @mkdir -p $(dir $@)
    $(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o $@ $<
    $(hide) $(HOST_OUT_EXECUTABLES)/checkpolicy -M -c $(POLICYVERS) -o \
        $(dir $<)/$(notdir $@).dontaudit $<.dontaudit

运行Android.mk后,sepolicy将在 $(intermediates) 文件夹(该文件夹的位置由Android编译系统决定)中输出两个文件:sepolicy和sepolicy.dontaudit。这两个文件分别以 $(sepolicy_policy.conf)和$(sepolicy_policy.conf).dontaudit为资源,通过checkpolicy命令生成。
MLS_SENS=1
MLS_CATS=1024
[...]
sepolicy_policy.conf := $(intermediates)/policy.conf
$(sepolicy_policy.conf): PRIVATE_MLS_SENS := $(MLS_SENS)
$(sepolicy_policy.conf): PRIVATE_MLS_CATS := $(MLS_CATS)
$(sepolicy_policy.conf): PRIVATE_ADDITIONAL_M4DEFS := $(LOCAL_ADDITIONAL_M4DEFS)
$(sepolicy_policy.conf): $(call build_policy, $(sepolicy_build_files))
    @mkdir -p $(dir $@)
    $(hide) m4 $(PRIVATE_ADDITIONAL_M4DEFS) \
        -D mls_num_sens=$(PRIVATE_MLS_SENS) -D mls_num_cats=$(PRIVATE_MLS_CATS) \
        -D target_build_variant=$(TARGET_BUILD_VARIANT) \
        -s $^ > $@
    $(hide) sed '/dontaudit/d' $@ > $@.dontaudit

sepolicy_policy.conf将输出两个文件: $(intermediates)/policy.conf 和 $(intermediates)/policy.conf.dontaudit 。宏处理器M4将扩展变量$(sepolicy_build_files)中列出的文件来生成policy.conf和它的stripped off版本policy.conf.dontaudit 。变量$(sepolicy_build_files)中列出了所有需要编译到Android SELinux内核策略的文件:
sepolicy_build_files := security_classes initial_sids access_vectors \
    global_macros neverallow_macros mls_macros mls policy_capabilities \
    te_macros attributes ioctl_macros *.te roles users initial_sid_contexts \
    fs_use genfs_contexts port_contexts

请注意:如果我们执行命令 $(call build_policy, $(sepolicy_build_files)) ,那么变量$(sepolicy_build_files)所包括的文件列表将会被改变。这一改变通常会在定义了BOARD_SEPOLICY_*变量后产生。
依靠以上命令,可以轻松的编译出一个seplicy策略文件。checkpolicy并不是android特有的工具,所以,常见的Linux系统提供的setools(https://github.com/TresysTechnology/setools3)工具就够用了。
$ sudo apt-get install m4 setools
$ m4 -D mls_num_sens=1 -D mls_num_cats=1024 -D target_build_variant=user \
     -s security_classes initial_sids access_vectors global_macros \
        neverallow_macros mls_macros mls policy_capabilities te_macros \
        attributes ioctl_macros *.te roles users initial_sid_contexts \
        fs_use genfs_contexts port_contexts > policy.conf
$ checkpolicy -h
usage:  checkpolicy [-b] [-d] [-U handle_unknown (allow,deny,reject)] \
                    [-M][-c policyvers (15-29)] [-o output_file]      \
                    [-t target_platform (selinux,xen)] [input_file]
$ checkpolicy -M -c 29 -o sepolicy policy.conf
checkpolicy:  loading policy configuration from policy.conf
checkpolicy:  policy configuration loaded
checkpolicy:  writing binary representation (version 29) to sepolicy
$ file sepolicy
sepolicy: SE Linux policy v29 MLS 8 symbols 7 ocons

checkpolicy支持很多的选项。 -M 选项表示支持多级别安全。-C表示策略版本。

理解SELnux内核策略文件格式
SELinux内核策略文件格式深受策略版本的影响,所以,格式比较复杂。
checkpolicy(http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/)项目的main函数入口在checkpolicy.c(http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/checkpolicy.c)文件中。简单地说,SELinux策略在内存中表示为一个policydb_t类型的数据。它通过函数policydb_init()初始化(http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/checkpolicy.c#571)。通过函数 read_source_policy()(http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/checkpolicy.c#583)中的LEX和YACC来解析SELinux指令并为policydb_t类型的数据设置成员值。解析完成后,checkpolicy向命令行中指定的地址输出二进制的SELinux内核策略文件。函数 policydb_write() (http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/checkpolicy.c#632)将policydb_t类型文件写入到硬盘当中。
在此,我们跳过解析SELinux指令的过程,并假设读取策略已经完成,policydb_t类型的数据已准备好写入硬盘当中。以下的这些handles将把二进制的策略写入到硬盘当中:
struct policy_file pf;
[...]
if (outfile) {
    outfp = fopen(outfile, "w");
    [...]
    if (!cil) {
        printf("%s:  writing binary representation (version %d) to %s\n",
               argv[0], policyvers, outfile);
        policydb.policy_type = POLICY_KERN;

        policy_file_init(&pf);
        pf.type = PF_USE_STDIO;
        pf.fp = outfp;
        ret = policydb_write(&policydb, &pf);
        [...]
    } else {
        [...]
    }
    fclose(outfp);
}

函数policydb_write()需要两个参数:一个policydb_t 类型的数据和一个struct policy_file。后者是一个可能的输入或输出格式(memory-mapped memory or basic I/O)的抽象层。它的定义如下(external/libsepol/include/sepol/policydb/policydb.h):
/* A policy "file" may be a memory region referenced by a (data, len) pair
   or a file referenced by a FILE pointer. */
typedef struct policy_file {
#define PF_USE_MEMORY  0
#define PF_USE_STDIO   1
#define PF_LEN         2 /* total up length in len field */
    unsigned type;
    char *data;
    size_t len;
    size_t size;
    FILE *fp;
    struct sepol_handle *handle;
} policy_file_t;

查看函数policydb_write()来理解二进制文件的格式(external/libsepol/src/write.c)。SELinux策略可以通过SELinux内核策略或SELinux模式策略来定义,不过我们只关心SELinux内核策略,所以,我们只要关注源码中满足p->policy_type == POLICY_KERN部分的代码就可以了。
在函数policydb_write()中,SELinux内核策略二进制文件的开头是一个magic数,这个数取决于策略的类型。
if (p->policy_type == POLICY_KERN) {
        buf[items++] = cpu_to_le32(POLICYDB_MAGIC);
        len = strlen(policydb_target_strings[p->target_platform]);
        policydb_str = policydb_target_strings[p->target_platform];
    } else {
        buf[items++] = cpu_to_le32(POLICYDB_MOD_MAGIC);
        len = strlen(POLICYDB_MOD_STRING);
        policydb_str = POLICYDB_MOD_STRING;
    }
    buf[items++] = cpu_to_le32(len);
    items2 = put_entry(buf, sizeof(uint32_t), items, fp);
    if (items != items2)
        return POLICYDB_ERROR;
    items = put_entry(policydb_str, 1, len, fp);
    if (items != len)
        return POLICYDB_ERROR;
然后文件中写入了当前策略相关的信息:策略标识符的长度,标识符内容(例如:"SE Linux" or "XenFlask" or "SELinux Module"这些字符串定义在/external/libsepol/include/sepol/policydb/policydb.h),策略版本号,configuration (例如:MLS policy or not),symbol array 和 object context array尺寸。(上面只截取了写入策略标识符的长度和标识符内容的代码,其余可以查看源文件。)
为了阐明文件格式,作者写了一个不完全的010编辑模板来解析SELinux内核策略头部。
$ cat SELinux.bt
typedef enum <uint> {
    MLS    = 1
} CONFIG;

typedef struct {
    uint magic <format=hex>; // 0xf97cff8c (SELINUX_MAGIC) or 0xf97cff8d (SELINUX_MOD_MAGIC)
    uint target_len;
    if (target_len > 0)
        uchar target[target_len]; // "SE Linux" or "XenFlask" or "SELinux Module"
    uint version;
    CONFIG config;
    uint sym_num;
    uint ocon_num;
} SELinuxPolicyHeader;

typedef struct {
    SELinuxPolicyHeader header;
} SELinuxPolicy;

LittleEndian();
while(!FEof())
{
    SELinuxPolicy policy;
    Warning("Incomplete template, stopped." );
    return -1;
}

Python format parser (pfp) (https://github.com/d0c-s4vage/pfp)使用上方的模板可以解析sepolicy头部。
接着,我们将二进制策略文件的头部还原成了文件:
$ cat pfp-parse.py
import sys, pfp

dom = pfp.parse(data_file=sys.argv[1], template_file=sys.argv[2])
print(dom._pfp__show(include_offset=True))
$ python fpf-parse.py sepolicy SELinuxPolicy.bt
0000 struct {
    0000 policy     = 0000 struct {
        0000 header     = 0000 struct {
            0000 magic      = UInt(4185718668 [f97cff8c])
            0004 target_len = UInt(8 [00000008])
            0008 target     = UChar[8] ('SE Linux')
            0010 version    = UInt(29 [0000001d])
            0014 config     = Enum<UInt>(1 [00000001])(MLS)
            0018 sym_num    = UInt(8 [00000008])
            001c ocon_num   = UInt(7 [00000007])
        }
    }
}
$ file sepolicy
sepolicy: SE Linux policy v29 MLS 8 symbols 7 ocons


SELinux内核策略头部后面跟着两个ebitmap_t类型的数据(http://androidxref.com/6.0.0_r1/xref/external/selinux/libsepol/src/ebitmap.c):一个polcap,一个permissive。
使用模板来输出这些bitmaps:
$ cat SELinuxPolicy.bt
[...]
} SELinuxPolicyHeader;

typedef struct {
    uint   start;
    uint64 bits;
} BITMAP;

typedef struct {
    uint size;
    uint highbit;
    uint count;
    BITMAP node[count];
} SELinuxPolicyEBitmap;

typedef struct {
    SELinuxPolicyHeader  header;
    if (header.version >= 22)
        SELinuxPolicyEBitmap polcap;
    if (header.version >= 23)
        SELinuxPolicyEBitmap permissive;
} SELinuxPolicy;
[...]
$ python fpf-parse.py sepolicy SELinuxPolicy.bt
0000 struct {
    0000 policy     = 0000 struct {
        [...]
        0020 polcap     = 0020 struct {
            0020 size       = UInt(64 [00000040])
            0024 highbit    = UInt(64 [00000040])
            0028 count      = UInt(1 [00000001])
            002c node       = BITMAP[1]
                002c node[0] = 002c struct {
                        002c start      = UInt(0 [00000000])
                        0030 bits       = UInt64(3 [0000000000000003])
                    }
        }
        0038 permissive = 0038 struct {
            0038 size       = UInt(64 [00000040])
            003c highbit    = UInt(0 [00000000])
            0040 count      = UInt(0 [00000000])
            0044 node       = BITMAP[0]
        }
    }
}

在AOSP中,policycap 指令定义在policy_capabilities(https://android.googlesource.com/platform/external/sepolicy/+/master/policy_capabilities)。定义了两个策略属性:network_peer_controls 和 open_perms,他们与上方显示的bitmap相一致,其内容的含义定义在libsepol/polcaps.c当中(http://androidxref.com/6.0.0_r1/xref/external/selinux/libsepol/src/polcaps.c#16)。此外, AOSP SELinux配置中没有定义任何的许可类型,这可能解释了为什么permissive的bitmap为空。
悲桑的是,SELinux内核策略中剩下的部分是又长又繁琐的数据结构:policydb_write()输出了标识符说明(common,types,attributes之类的),许可向量规则(allow,deny,dontaudit之类的)。下面来看看共有许可集。
共有许可集(Common permission sets)存在policydb_t数据结构的symtable[0]的table域中(看代码,其实是symtab[0]的table域)。table域是一个hash table,以Common permission set标识符为key,以一个common_datum_t类型的引用为value。common_datum_t数据类型由一个datum_t(比如index)和一个hash table组成,这个hash table列出了关联在该common identifier上的所有许可。以上这些数据结构都定义在libsepol/sepol/policydb/policydb.h(http://androidxref.com/6.0.0_r1/xref/external/selinux/libsepol/include/sepol/policydb/policydb.h#106)当中。

在libsepol中,hashtab_t hash tables都以相同的方式连接。这个连接结构包括一个nprim数,hash table的一些键值对,和nelem。nelem表示存在hash table中的元素的数量。这些成员可以在symtable[0].table 和common_datum_t中找到。至于strings idenetifiers,只是简单的string+string的长度。以下是解析common permission组的模板:
$ cat SELinuxPolicy.bt
[...]
} SELinuxPolicyEBitmap;

typedef struct {
    uint len;
    uint datum;
    uchar identifier[len];
} PERMISSION;

typedef struct {
    uint len;
    uint datum;
    uint perm_nprim;
    uint perm_nelem;
    uchar identifier[len];
    PERMISSION permission[perm_nelem];
} COMMON;

typedef struct {
    uint nprim;
    uint nelem;
    switch (i) {
        case 0: // common statements
            COMMON common[nelem];
        default: // not handled yet
            return -1;
    }
} SYMBOL;

typedef struct {
    SELinuxPolicyHeader  header;
    if (header.version >= 22)
        SELinuxPolicyEBitmap polcap;
    if (header.version >= 23)
        SELinuxPolicyEBitmap permissive;
    for (local int i = 0; i < header.sym_num; i++) {
        SYMBOL symbol;
    }
} SELinuxPolicy;
[...]
$ python fpf-parse.py sepolicy SELinuxPolicy.bt
0000 struct {
    0000 policy     = 0000 struct {
        [...]
        0044 symbol     = 0044 struct {
            0044 nprim      = UInt(3 [00000003])
            0048 nelem      = UInt(3 [00000003])
            004c common     = COMMON[3]
                004c common[0] = 004c struct {
                        004c len        = UInt(6 [00000006])
                        0050 datum      = UInt(2 [00000002])
                        0054 perm_nprim = UInt(22 [00000016])
                        0058 perm_nelem = UInt(22 [00000016])
                        005c identifier = UChar[6] ('socket')
                        0062 permission = PERMISSION[22]
                            0062 permission[0] = 0062 struct {
                                    0062 len        = UInt(6 [00000006])
                                    0066 datum      = UInt(10 [0000000a])
                                    006a identifier = UChar[6] ('append')
                                }
                            0070 permission[1] = 0070 struct {
                                    0070 len        = UInt(4 [00000004])
                                    0074 datum      = UInt(11 [0000000b])
                                    0078 identifier = UChar[4] ('bind')
                                }
                            007c permission[2] = 007c struct {
                                    007c len        = UInt(7 [00000007])
                                    0080 datum      = UInt(12 [0000000c])
                                    0084 identifier = UChar[7] ('connect')
                                }
        [...]



把sepolicy还原成policy.conf
到目前为止,假设我们有编译sepolicy的源码。不过,人生并没有那么easy阿骚年。我们能拿来分析的Android系统中的策略文件,是一个二进制的SELinux内核策略文件。而且,这个策略文件还不仅仅是AOSP提供的,各大厂商也会添加一些自己的策略来保护自己的增加在系统中的服务。
为了审核SELinux,通常我们用 setools3 utilities(apol, sesearch, seinfo, sediff, etc.)(地址:https://github.com/TresysTechnology/setools3)之类的工具来获取二进制策略文件的信息。这项任务超无聊又繁琐,因为工具只能提供一些策略文件的片段,我们还要把他们组装起来。
据我所知,目前尚无工具可以将而建值的sepolicy策略文件还原成policy.conf文件。不过,基于我们前面所讲的内容,SELinux内核策略文件就是解析policy.conf文件,然后连接成一些policydb_t结构。此外,checkpolicy可以从编译好的内核策略文件中生成一个语义等价的二进制内核策略(看 -b 选项)。因此,还是有可能把一个二进制的sepolicy文件还原成与原本的policy.conf文件内容等价的文件的。
$ checkpolicy -b -M -c 29 -o sepolicy.new sepolicy
checkpolicy:  loading policy configuration from sepolicy
libsepol.policydb_index_others: security:  1 users, 2 roles, 534 types, 0 bools
libsepol.policydb_index_others: security:  1 sens, 1024 cats
libsepol.policydb_index_others: security:  55 classes, 4473 rules, 0 cond rules
checkpolicy:  policy configuration loaded
checkpolicy:  writing binary representation (version 29) to sepolicy.new
$ sediff -q --stats sepolicy/sepolicy \; sepolicy/sepolicy.new
$ echo $?
0

作者几周前写了个proof-of-concept工具,叫sedump(https://github.com/ge0n0sis/sedump/),其中用到了setools4的python bindings(https://github.com/TresysTechnology/setools)。不过,因为setool4还在alpha版本,而且还和setools3冲突,所以,建议各位用docker来运行它。
$ sudo apt-get install docker-engine python-pip
$ sudo pip install docker-compose
$ git clone https://github.com/ge0n0sis/sedump
$ cd sedump/docker
$ docker-compose build master
$ docker-compose up -d master
$ docker-compose run master

目前,这个工具使用AOSP编译出来的sepolicy二进制文件和Samsung stock ROMs里的sepolicy二进制文件。到目前为止,有条件的许可向量的二进制策略还没搞定,作者会继续加油的。
docker@5534108629ba:~$ cd setools
docker@5534108629ba:~/setools$ python setup.py develop
docker@5534108629ba:~/setools$ python sedump sepolicy -o policy.conf

在docker外,我们可以用dif工具(比如sediff)来检测policy.conf和原本的策略文件是否语义等价:
$ checkpolicy -M -c 29 -o sepolicy.new policy.conf
checkpolicy:  loading policy configuration from policy.conf
checkpolicy:  policy configuration loaded
checkpolicy:  writing binary representation (version 29) to sepolicy.new
$ sediff -q --stats sepolicy \; sepolicy.new
$ echo $?
0

【1】 dispol(http://androidxref.com/6.0.0_r1/xref/external/selinux/checkpolicy/test/dispol.c)目前只支持许可向量和有条件的许可向量规则。

结论
Android系统的安全增强工具,还有其他普通的SELinux,真的很复杂。在本文中,作者只涉及到了整个安全增强解决方案的皮毛,而这个解决方案从Android4.3开始就已经是一个完整的Android安全模式。
首先,Android的编译系统了解如何生成SELinux内核策略二进制文件。整个编译过程和SELinux在PC终端上的编译过程没啥大区别。都使用m4和checkpolicy把一对SELinux命令扩展和编译成一个庞大的策略文件。使用正规的m4和checkpolicy就可以在Android环境之外编译出sepolicy来。
然后,作者在分析checkpolicy源码的过程中,进一步挖掘策略编译过程。引入内存中存储这些指令的数据结构并且简单的介绍了SELinux内核策略的文件格式。二进制的sepolicy文件以一连串的policydb_t数据结构,由解析policy.conf得来。
最后,作者介绍了它发明的小工具sedump,这个工具可以把二进制的sepolicy还原成文本格式的policy.conf,目前,这种工具仅此一种。解析后,我们甚至可以对策略文件进行一些修改。
请注意,sedump工具还在alpha版本:请毫不犹豫的在github上给作者反馈或者发崩溃记录过来。目前已知的限制是,本工具无法处理带条件的许可向量,if/else语句之类的。在把它merge到setools4的主分支之前,还有很多的事情要做呢。

0 0
原创粉丝点击