Android P SELinux (三) 权限检查原理与调试

本文详细解析了Android P的SELinux权限机制,包括权限检测原理、avc_denied信息解读、调试方法以及如何利用宏和工具如setools优化规则。通过实例演示了权限检查流程,提供了编译和策略文件管理的技巧,适合深入理解Android权限控制。

Android P SELinux (一) 基础概念
Android P SELinux (二) 开机初始化与策略文件编译过程
Android P SELinux (三) 权限检查原理与调试
Android P SELinux (四) CTS neverallow处理总结

引子

我们在处理SELinux权限问题的时候,avc denied信息是最关键的,那么avc denied 的打印信息要怎么看、里面的内容每一个字段是什么意思?kernel log的avc denied信息是哪里打印出来的?

以前有个想法,我们系统的操作、命令都是有限的,那其实脚本写完之后需要什么权限都是确定的,那可不可能更快的加好te规则呢?

带着上面的一些疑问,接着在代码想找到是怎么检查权限的,下文会简单介绍一些目前了解到的知识:

假设有如下脚本以及te规则

  • /vendor/bin/testA.sh
#!/vendor/bin/sh

echo "hello world" > /data/vendor/test.txt
  • /android/device/xxxx/common/sepolicy/file_contexts
/vendor/bin/testA.sh     u:object_r:testA_exec:s0
  • /android/device/xxxx/common/sepolicy/testA.te
type testA, domain;
type testA_exec, exec_type, vendor_file_type, file_type;
 
init_daemon_domain(testA)
 
allow testA console_device:chr_file {
   
    write open getattr ioctl };
allow testA vendor_toolbox_exec:file {
   
    execute_no_trans };
allow testA vendor_data_file:dir {
   
    add_name };

执行脚本后,会有如下报错信息:

[ 5424.996583@0]- type=1400 audit(1577887433.668:59): avc: denied {
   
    write } for pid=4021 comm="testA.sh"
name="vendor" dev="mmcblk0p20" ino=7395 scontext=u:r:testA:s0 tcontext=u:object_r:vendor_data_file:s0 tclass=dir permissive=0

一、权限检测原理

这部分的代码集中在两个部分

  • kernel的代码,里面的security/selinux
  • /android/external/selinux

一开始,/data/vendor/test.txt是不存在的;testA.sh脚本运行起来之后,它的安全上下文是u:r:testA:s0,type为testA的进程,要拥有对type为vendor_data_file的目录(dir)的写(write)权限,才能创建一个不存在的文件

权限检测其实就是针对进程访问对象的两个权限:拥有的权限和需要的权限,将两者进行计算得出结果,即允许还是拒绝

1、拥有的权限

te规则定义的权限保存在哪里?

testA.te里面它拥有add_name权限,前面的文章有提到te策略文件的编译过程,这个规则最终会生成在二进制策略文件precompiled_sepolicy里面,而policydb这个数据结构是用来组织起所有数据的

其中te规则保存在这个成员里面

/* type enforcement access vectors and transitions */
avtab_t te_avtab;

这里面的数据结构比较复杂,里面主要需要理解几个数据结构:

  • 位图ebitmap
  • 链表
  • 哈希表hashtab
  • 符号表symtab

这里面的内容太多了,我只是简单分析了其中te规则一部分代码,有兴趣可以结合参考文章《Linux多安全策略和动态安全策 略框架模块代码分析报告》(见参考文章)来阅读源码深入了解

下面分析的一些结论,如有问题请指出:

testA这个type的进程,对type为vendor_data_file的dir(目录)所拥有的权限是用一个整数u32来表示的:

它的值是:

0x00120010

下面简单看看这个值是怎么生成的?

(allow testA_28_0 vendor_data_file_28_0 (dir (add_name)))

接着看看 /android/external/selinux/libsepol/cil/src/cil_binary.c 里面的__cil_perms_to_datum函数

int __cil_perms_to_datum(struct cil_list *perms, class_datum_t *sepol_class, uint32_t *datum)
{
   
   
    int rc = SEPOL_ERR;
    char *key = NULL;
    struct cil_list_item *curr_perm;
    struct cil_perm *cil_perm;
    uint32_t data = 0;
 
    cil_list_for_each(curr_perm, perms) {
   
   
        cil_perm = curr_perm->data;
        key = cil_perm->datum.fqn;
 
        rc = __perm_str_to_datum(key, sepol_class, &data);
        if (rc != SEPOL_OK) {
   
   
            goto exit;
        }
    }
 
    *datum = data;
 
    return SEPOL_OK;
 
exit:
    return rc;
}
 
 
int __perm_str_to_datum(char *perm_str, class_datum_t *sepol_class, uint32_t *datum)
{
   
   
    int rc;
    perm_datum_t *sepol_perm;
    common_datum_t *sepol_common;
 
    sepol_perm = hashtab_search(sepol_class->permissions.table, perm_str);
    if (sepol_perm == NULL) {
   
   
        sepol_common = sepol_class->comdatum;
        sepol_perm = hashtab_search(sepol_common->permissions.table, perm_str);
        if (sepol_perm == NULL) {
   
   
            cil_log(CIL_ERR, "Failed to find datum for perm %s\n", perm_str);
            rc = SEPOL_ERR;
            goto exit;
        }
    }
    *datum |= 1 << (sepol_perm->s.value - 1);
 
    return SEPOL_OK;
 
exit:
    return rc;
}

datum 里面保存的值就是0x00120010

看最关键的一句话:

*datum |= 1 << (sepol_perm->s.value - 1);

上面的操作其实就是将对应的位置1,代表它拥有的权限,那每一个位对应的权限是什么呢?
这个要看:

/android/system/sepolicy/private/access_vectors

common file
{
   
   
    ioctl
    read
    write
    create
    getattr
    setattr
    lock
    relabelfrom
    relabelto
    append
    map
    unlink
    link
    rename
    execute
    quotaon
    mounton
}
 
class dir
inherits file
{
   
   
    add_name
    remove_name
    reparent
    search
    rmdir
    open
    audit_access
    execmod
}

sepol_perm->s.value可以理解成是解析过程中某个权限在access_vectors定义的class里面的index值,它会用符号表和对应的字符串关联起来,比如add_name在集合里面的是第18个,对应的值是0x00020000

我们这里的是dir,将其合并下:

{
   
   
    ioctl,read,write,create,getattr,setattr,lock,relabelfrom,relabelto,append,map,unlink,
    link,rename,execute,quotaon,mounton,add_name,remove_name,reparent,search,rmdir,open,
    audit_access,execmod
}

上面那句话的操作就是将datum的第18位置成1

那么最后计算出来的0x00120010就是:
在这里插入图片描述
翻译过来,是这个规则

allow testA vendor_data_file:dir {
   
    search add_name getattr };

奇怪的是,这里怎么会多出来两个权限呢,我都没有加上去

原因是来自这条规则:
这个在domain.te里面

allow domain vendor_data_file:dir {
   
    getattr search };

2、需要的权限

要分析操作vendor_data_file:dir所需要的权限,借助打印堆栈信息来分析:

[ 5424.899991@1]d CPU: 1 PID: 4119 Comm: testA.sh Tainted: P           O    4.9.113 #14
[ 5424.907561@1]d Hardware name: Generic DT based system
[ 5424.912595@1]d [bc3c7a84+  16][<c020dc10>] show_stack+0x20/0x24
[ 5424.918447@1]d [bc3c7aa4+  32][<c05443ec>] dump_stack+0x90/0xac
[ 5424.924311@1]d [bc3c7aec+  72][<c04d5954>] slow_avc_audit+0x88/0xa8
[ 5424.930515@1]d [bc3c7b34+  72][<c04da498>] audit_inode_permission+0x80/0x88
[ 5424.937417@1]d [bc3c7bac+ 120][<c04daf20>] selinux_inode_permission+0x2d0/0x314
[ 5424.944664@1]d [bc3c7bcc+  32][<c04d1f44>] security_inode_permission+0x4c/0x68
[ 5424.951824@1]d [bc3c7bec+  32][<c03a5bc8>] __inode_permission2+0x50/0xf0
[ 5424.958455@1]d [bc3c7bfc+  16][<c03a5cb0>] inode_permission2+0x20/0x54
[ 5424.964930@1]d [bc3c7c84+ 136][<c03a9048>] path_openat+0xb30/0x118c
[ 5424.971137@1]d [bc3c7d2c+ 168][<c03aabe4>] do_filp_open+0x7c/0xe0
[ 5424.977179@1]d [bc3c7d7c+  80][<c0397b34>] do_sys_open+0x124/0x238
[ 5424.983303@1]d [bc3c7d8c+  16][<c0397c90>] SyS_openat+0x1c/0x20
[ 5424.989165@1]d [00000000+   0][<c02085c0>] ret_fast_syscall+0x0/0x48
 
用addr2line看看对应的代码调用逻辑
1  show_stack c020dc10
show_stack
kernelcode/arch/arm/kernel/traps.c:263
 
2  dump_stack c05443ec
dump_stack
kernelcode/lib/dump_stack.c:53
 
3  slow_avc_audit c04d5954
slow_avc_audit
kernelcode/security/selinux/avc.c:775
 
4  audit_inode_permission c04da498
audit_inode_permission
kernelcode/security/selinux/hooks.c:3005
 
5  selinux_inode_permission c04daf20
selinux_inode_permission
kernelcode/security/selinux/hooks.c:3058
 
6  security_inode_permission c04d1f44
security_inode_permission
kernelcode/security/security.c:611 (discriminator 5)
 
7  __inode_permission2 c03a5bc8
__inode_permission2
kernelcode/fs/namei.c:436
 
8  inode_permission2 c03a5cb0
inode_permission2
kernelcode/fs/namei.c:486
 
9  path_openat c03a9048
may_o_create
kernelcode/fs/namei.c:3018
 
10  do_filp_open c03aabe4
do_filp_open
kernelcode/fs/namei.c:3569
 
11  do_sys_open c0397b34
do_sys_open
kernelcode/fs/open.c:1073
 
12  SyS_openat c0397c90
SyS_openat
kernelcode/fs/open.c:1093
 
13  ret_fast_syscall c02085c0
ret_fast_syscall
kernelcode/arch/arm/kernel/entry-common.S:37

堆栈打印添加在kernel代码里面:

kernelcode$ git diff security/selinux/avc.c

diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index e60c79d..79cea20 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -771,7 +771,7 @@ noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
        sad.result = result;
  
        a->selinux_audit_data = &sad;
-
+       dump_stack();
        common_lsm_audit(a, avc_audit_pre_callback, avc_audit_post_callback);
        return 0;
 }

跟着堆栈简单看一遍代码,不难发现,需要的权限是从这里来的

may_o_create

kernelcode/fs/namei.c:3018

static int may_o_create(const struct path *dir, struct dentry *dentry, umode_t mode)
{
   
   
    struct user_namespace *s_user_ns;
    int error = security_path_mknod(dir, dentry, mode, 0);
    if (error)
        return error;
 
    s_user_ns = dir->dentry->d_sb->s_user_ns;
    if (!kuid_has_mapping(s_user_ns, current_fsuid()) ||
        !kgid_has_mapping(s_user_ns, current_fsgid()))
        return -EOVERFLOW;
 
    error = inode_permission2(dir->mnt, dir->dentry->d_inode, MAY_WRITE | MAY_EXEC);
    if (error)
        return error;
 
    return security_inode_create(dir->dentry->d_inode, dentry, mode);
}

inode_permission2的第三个参数 MAY_WRITE | MAY_EXEC

会一直传递到 kernelcode/security/selinux/hooks.c

selinux_inode_permission这个函数的第二参数mask

static int selinux_inode_permission(struct inode *inode, int mask)
{
   
   
    const struct cred *cred = current_cred();
    u32 perms;
    bool from_access;
    unsigned flags = mask &</
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值