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

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 & MAY_NOT_BLOCK;
    struct inode_security_struct *isec;
    u32 sid;
    struct av_decision avd;
    int rc, rc2;
    u32 audited, denied;
 
    from_access = mask & MAY_ACCESS;
  //mask = MAY_WRITE | MAY_EXEC;
    mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
 
    /* No permission to check.  Existence test. */
    if (!mask)
        return 0;
 
    validate_creds(cred);
 
    if (unlikely(IS_PRIVATE(inode)))
        return 0;
 
  //mask = MAY_WRITE | MAY_EXEC;
  //perms = DIR__WRITE | DIR__SEARCH
  //perms = 0x00400004
    perms = file_mask_to_av(inode->i_mode, mask);
 
    sid = cred_sid(cred);
    isec = inode_security_rcu(inode, flags & MAY_NOT_BLOCK);
    if (IS_ERR(isec))
        return PTR_ERR(isec);
 
  //主要是获取av_decision信息,里面包含了权限信息,先从avc cache里面获取,如果没有就从policydb里面计算得出
    rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, &avd);
  //这个就是审计、裁决的动作了,计算拥有的权限avd和需要的权限perms,得出缺失的权限
    audited = avc_audit_required(perms, &avd, rc,
                     from_access ? FILE__AUDIT_ACCESS : 0,
                     &denied);
  //大部分情况下,如果没有权限问题,到这儿就结束了
    if (likely(!audited))
        return rc;
  //再往下就要开始收集信息,打印avc denied信息了
    rc2 = audit_inode_permission(inode, perms, audited, denied, rc, flags);
    if (rc2)
        return rc2;
    return rc;
}

这里面file_mask_to_av计算出来的perms就是我们现在操作dir所需要的权限

DIR__WRITE 、 DIR__SEARCH的定义是av_permissions.h头文件里面

不过不是这个 /android/external/selinux/libselinux/include/selinux/av_permissions.h

而是编译生成,路径在:/android/out/target/product/xxxxx/obj/KERNEL_OBJ/security/selinux/av_permissions.h

#define DIR__WRITE                                0x00000004UL
#define DIR__SEARCH                               0x00400000UL

也就是perms = 0x00400004

实际上,这里面这个权限也是每一位代表了一个权限,和上面的不同的是,这个是根据classmap.h里面定义的值来计算的

kernelcode/security/selinux/include/classmap.h

#define COMMON_FILE_SOCK_PERMS "ioctl", "read", "write", "create", \
    "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append"
 
#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \
    "rename", "execute", "quotaon", "mounton", "audit_access", \
    "open", "execmod"
 
#define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \
    "listen", "accept", "getopt", "setopt", "shutdown", "recvfrom",  \
    "sendto", "name_bind"
 
#define COMMON_IPC_PERMS "create", "destroy", "getattr", "setattr", "read", \
        "write", "associate", "unix_read", "unix_write"
 
#define COMMON_CAP_PERMS  "chown", "dac_override", "dac_read_search", \
        "fowner", "fsetid", "kill", "setgid", "setuid", "setpcap", \
        "linux_immutable", "net_bind_service", "net_broadcast", \
        "net_admin", "net_raw", "ipc_lock", "ipc_owner", "sys_module", \
        "sys_rawio", "sys_chroot", "sys_ptrace", "sys_pacct", "sys_admin", \
        "sys_boot", "sys_nice", "sys_resource", "sys_time", \
        "sys_tty_config", "mknod", "lease", "audit_write", \
        "audit_control", "setfcap"
 
#define COMMON_CAP2_PERMS  "mac_override", "mac_admin", "syslog", \
        "wake_alarm", "block_suspend", "audit_read"
 
/*
 * Note: The name for any socket class should be suffixed by "socket",
 *   and doesn't contain more than one substr of "socket".
 */
struct security_class_mapping secclass_map[] = {
    { "security",
      { "compute_av", "compute_create", "compute_member",
        "check_context", "load_policy", "compute_relabel",
        "compute_user", "setenforce", "setbool", "setsecparam",
        "setcheckreqprot", "read_policy", "validate_trans", NULL } },
    { "process",
      { "fork", "transition", "sigchld", "sigkill",
        "sigstop", "signull", "signal", "ptrace", "getsched", "setsched",
        "getsession", "getpgid", "setpgid", "getcap", "setcap", "share",
        "getattr", "setexec", "setfscreate", "noatsecure", "siginh",
        "setrlimit", "rlimitinh", "dyntransition", "setcurrent",
        "execmem", "execstack", "execheap", "setkeycreate",
        "setsockcreate", NULL } },
    { "system",
      { "ipc_info", "syslog_read", "syslog_mod",
        "syslog_console", "module_request", "module_load", NULL } },
    { "capability",
      { COMMON_CAP_PERMS, NULL } },
    { "filesystem",
      { "mount", "remount", "unmount", "getattr",
        "relabelfrom", "relabelto", "associate", "quotamod",
        "quotaget", NULL } },
    { "file",
      { COMMON_FILE_PERMS,
        "execute_no_trans", "entrypoint", NULL } },
    { "dir",
      { COMMON_FILE_PERMS, "add_name", "remove_name",
        "reparent", "search", "rmdir", NULL } },
    { "fd", { "use", NULL } },
    { "lnk_file",
      { COMMON_FILE_PERMS, NULL } },
    { "chr_file",
      { COMMON_FILE_PERMS, NULL } },
    { "blk_file",
      { COMMON_FILE_PERMS, NULL } },
    { "sock_file",
      { COMMON_FILE_PERMS, NULL } },
    { "fifo_file",
      { COMMON_FILE_PERMS, NULL } },
    { "socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "tcp_socket",
      { COMMON_SOCK_PERMS,
        "node_bind", "name_connect",
        NULL } },
    { "udp_socket",
      { COMMON_SOCK_PERMS,
        "node_bind", NULL } },
    { "rawip_socket",
      { COMMON_SOCK_PERMS,
        "node_bind", NULL } },
    { "node",
      { "recvfrom", "sendto", NULL } },
    { "netif",
      { "ingress", "egress", NULL } },
    { "netlink_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "packet_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "key_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "unix_stream_socket",
      { COMMON_SOCK_PERMS, "connectto", NULL } },
    { "unix_dgram_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "sem",
      { COMMON_IPC_PERMS, NULL } },
    { "msg", { "send", "receive", NULL } },
    { "msgq",
      { COMMON_IPC_PERMS, "enqueue", NULL } },
    { "shm",
      { COMMON_IPC_PERMS, "lock", NULL } },
    { "ipc",
      { COMMON_IPC_PERMS, NULL } },
    { "netlink_route_socket",
      { COMMON_SOCK_PERMS,
        "nlmsg_read", "nlmsg_write", NULL } },
    { "netlink_tcpdiag_socket",
      { COMMON_SOCK_PERMS,
        "nlmsg_read", "nlmsg_write", NULL } },
    { "netlink_nflog_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_xfrm_socket",
      { COMMON_SOCK_PERMS,
        "nlmsg_read", "nlmsg_write", NULL } },
    { "netlink_selinux_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_iscsi_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_audit_socket",
      { COMMON_SOCK_PERMS,
        "nlmsg_read", "nlmsg_write", "nlmsg_relay", "nlmsg_readpriv",
        "nlmsg_tty_audit", NULL } },
    { "netlink_fib_lookup_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_connector_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_netfilter_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_dnrt_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "association",
      { "sendto", "recvfrom", "setcontext", "polmatch", NULL } },
    { "netlink_kobject_uevent_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_generic_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_scsitransport_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_rdma_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "netlink_crypto_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "appletalk_socket",
      { COMMON_SOCK_PERMS, NULL } },
    { "packet",
      { "send", "recv", "relabelto", "forward_in", "forward_out", NULL } },
    { "key",
      { "view", "read", "write", "search", "link", "setattr", "create",
        NULL } },
    { "dccp_socket",
      { COMMON_SOCK_PERMS,
        "node_bind", "name_connect", NULL } },
    { "memprotect", { "mmap_zero", NULL } },
    { "peer", { "recv", NULL } },
    { "capability2",
      { COMMON_CAP2_PERMS, NULL } },
    { "kernel_service", { "use_as_override", "create_files_as", NULL } },
    { "tun_socket",
      { COMMON_SOCK_PERMS, "attach_queue", NULL } },
    { "binder", { "impersonate", "call", "set_context_mgr", "transfer",
              NULL } },
    { "cap_userns",
      { COMMON_CAP_PERMS, NULL } },
    { "cap2_userns",
      { COMMON_CAP2_PERMS, NULL } },
    { "bpf",
      {"map_create", "map_read", "map_write", "prog_load", "prog_run"} },
    { NULL }
  };

把dir所对应的展开一下:

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

其实这里对应的就是权限:“write"和"search”

也就是说,这一次权限检查,要检查的权限是对type为vendor_data_file的目录(dir)的"write"和"search"权限

其他的其实也是一样的道理,kernel里面的代码规定了进程在访问某些对象的时候所需要的权限

再比如:

mkdir /data/testdir
 
[ 2336.839823@1] type=1400 audit(1230741525.976:909): avc: denied { getattr } for pid=18561 comm="mkdir"
path="/data/testdir" dev="mmcblk0p21" ino=1149 scontext=u:r:testA:s0 tcontext=u:object_r:system_data_file:s0 tclass=dir permissive=0
 
[ 2336.754088@3] [bc6e5b7c+  16][<c020e3c0>] show_stack+0x20/0x24
[ 2336.759889@3] [bc6e5b9c+  32][<c05daf0c>] dump_stack+0x90/0xac
[ 2336.765696@3] [bc6e5bf4+  88][<c055e978>] slow_avc_audit+0x108/0x128
[ 2336.772019@3] [bc6e5c74+ 128][<c055f040>] avc_has_perm+0x13c/0x170
[ 2336.778172@3] [bc6e5c8c+  24][<c0560334>] inode_has_perm+0x48/0x5c
[ 2336.784326@3] [bc6e5cb4+  40][<c0562b00>] selinux_inode_getattr+0x6c/0x74
[ 2336.791085@3] [bc6e5cd4+  32][<c055ab40>] security_inode_getattr+0x4c/0x68
[ 2336.797934@3] [bc6e5cec+  24][<c03a1ff4>] vfs_getattr+0x20/0x38
[ 2336.803826@3] [bc6e5d24+  56][<c03a20d4>] vfs_fstatat+0x70/0xb0
[ 2336.809718@3] [bc6e5d8c+ 104][<c03a2804>] SyS_fstatat64+0x24/0x40
[ 2336.815787@3] [00000000+   0][<c0208680>] ret_fast_syscall+0x0/0x48
 
inode_has_perm
kernelcode/security/selinux/hooks.c:1705
 
selinux_inode_getattr
kernelcode/security/selinux/hooks.c:3089
 
security_inode_getattr
kernelcode/security/security.c:631 (discriminator 5)
 
vfs_getattr
kernelcode/fs/stat.c:70
 
vfs_fstatat
kernelcode/fs/stat.c:110
 
SYSC_fstatat64
kernelcode/fs/stat.c:440

首先检查的需要的权限是:getattr

static int selinux_inode_getattr(const struct path *path)
{
    return path_has_perm(current_cred(), path, FILE__GETATTR);
}
 
#define FILE__GETATTR                             0x00000010UL

3、裁决

前面的分析,我们知道了需要的权限是0x00400004,拥有的权限是0x00120010

那代码是怎么来判断权限是否都ok呢?

先看看权限是怎么从policydb里面读取,然后转换的

inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,
             u16 tclass, u32 requested,
             unsigned flags,
             struct av_decision *avd)
{
    ......
    node = avc_lookup(ssid, tsid, tclass);
    if (unlikely(!node))
        node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);
    else
        memcpy(avd, &node->ae.avd, sizeof(*avd));
    ......
}

首先从avc cache里面找看看有没有缓存值,这个是为了提高运行效率的,系统里面成千上万条规则,如果每一次判断权限都要从文件里面读取然后计算结果,这将会是一个巨大的工作量

所以采用了内存里面缓存之前计算的结果,如果没有的话那就调用avc_compute_av函数计算一次

接着调用security_compute_av

kernelcode/security/selinux/ss/services.c

void security_compute_av(u32 ssid,
             u32 tsid,
             u16 orig_tclass,
             struct av_decision *avd,
             struct extended_perms *xperms)
{
    ......
    context_struct_compute_av(scontext, tcontext, tclass, avd, xperms);
    map_decision(orig_tclass, avd, policydb.allow_unknown);
  ......
}

函数比较长,这里只看最重要的两句

context_struct_compute_av是从policydb里面读取出来的权限值,在这里就是0x00120010

map_decision会将权限值做一个映射关系,如果认真对比classmap.h和access_vectors里面dir权限的顺序,会发现其实不是完全一样的

static void map_decision(u16 tclass, struct av_decision *avd,
             int allow_unknown)
{
    if (tclass < current_mapping_size) {
        unsigned i, n = current_mapping[tclass].num_perms;
        u32 result;
 
        for (i = 0, result = 0; i < n; i++) {
            if (avd->allowed & current_mapping[tclass].perms[i])
                result |= 1<<i;
            if (allow_unknown && !current_mapping[tclass].perms[i])
                result |= 1<<i;
        }
        avd->allowed = result;
 
        ......
    }
}
 
//current_mapping 初始化的时候,是从classmap.h的secclass_map读取的
rc = selinux_set_mapping(&policydb, secclass_map,
                     ¤t_mapping,
                     ¤t_mapping_size);

这里会将0x00120010转成0x00480010

和上面一样的道理:

0x00480010
 
二进制表示:
0000 0000 0100 1000 0000 0000 0001 0000
 
这里的每一个位代表着不同的权限,1是有权限,0是没有权限

那么0x00480010对应的权限:
在这里插入图片描述
和前面的权限是一致的,这里没有搞清楚为什么要有一个映射关系,感觉直接搞成一样的顺序不是更方便吗?

那接下来要比较的权限就是0x00480010(拥有的权限)和0x00400004(需要的权限)

找到0x00400004(需要的权限)为1 但是 0x00480010(拥有的权限)不为 1的那一位,就是缺少权限的

计算方法可以看看函数:

static inline u32 avc_audit_required(u32 requested,
                  struct av_decision *avd,
                  int result,
                  u32 auditdeny,
                  u32 *deniedp)
{
    u32 denied, audited;
  //最重要的就是这个计算
    denied = requested & ~avd->allowed;
    ......
}

那么最后计算的结果就是

0x00400004 & ~0x00480010 = 0x00000004

根据前面的描述,这个权限就是 “write”

所以会看到avc denied信息里面会提示你缺少write权限

[ 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

上面提到的只是一个简单的例子,其他的权限也可以用类似的方式分析

我们用一张图来总结下:
在这里插入图片描述

4、其他

下面聊聊servicemanager里面一些权限的检查,这个是Android才有的

前面的流程可以先看看这篇文章:Binder系列3—启动ServiceManager

我们在这直接看到svcmgr_handler

比较常看见的两个权限问题,find和add

case SVC_MGR_CHECK_SERVICE:
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }
    handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
    if (!handle)
        break;
    bio_put_ref(reply, handle);
    return 0;
 
case SVC_MGR_ADD_SERVICE:
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }
    handle = bio_get_ref(msg);
    allow_isolated = bio_get_uint32(msg) ? 1 : 0;
    dumpsys_priority = bio_get_uint32(msg);
    if (do_add_service(bs, s, len, handle, txn->sender_euid, allow_isolated, dumpsys_priority,
                       txn->sender_pid))
        return -1;
    break;

这里面的调用栈都会跑到kernel里面,和前面的分析都差不多的

do_add_service -> svc_can_register -> check_mac_perms_from_lookup -> check_mac_perms ->
selinux_check_access -> avc_has_perm -> avc_has_perm_noaudit

重点的这里的权限需要哪些,这个是在service_manager.c里面指定的

比如add 和 find :

static int svc_can_find(const uint16_t *name, size_t name_len, pid_t spid, uid_t uid)
{
    const char *perm = "find";
    return check_mac_perms_from_lookup(spid, uid, perm, str8(name, name_len)) ? 1 : 0;
}
 
static int svc_can_register(const uint16_t *name, size_t name_len, pid_t spid, uid_t uid)
{
    const char *perm = "add";
 
    if (multiuser_get_app_id(uid) >= AID_APP) {
        return 0; /* Don't allow apps to register services */
    }
 
    return check_mac_perms_from_lookup(spid, uid, perm, str8(name, name_len)) ? 1 : 0;
}

不过这里的打印不再是kernel log里面的了,而是logcat信息里面,那它的打印是怎么来的?

06-21 13:12:58.631  2832  2832 E SELinux : avc:  denied  { find } for service=activity pid=4138 uid=0 scontext=u:r:testA:s0 tcontext=u:object_r:activity_service:s0 tclass=service_manager permissive=1

在前面提到的avc_has_perm函数里面,还有一句

rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata, 0);

/android/external/selinux/libselinux/src/avc.c

void avc_audit(security_id_t ssid, security_id_t tsid,
           security_class_t tclass, access_vector_t requested,
           struct av_decision *avd, int result, void *a)
{
    access_vector_t denied, audited;
 
    denied = requested & ~avd->allowed;
    if (denied)
        audited = denied & avd->auditdeny;
    else if (!requested || result)
        audited = denied = requested;
    else
        audited = requested & avd->auditallow;
    if (!audited)
        return;
#if 0
    if (!check_avc_ratelimit())
        return;
#endif
    /* prevent overlapping buffer writes */
    avc_get_lock(avc_log_lock);
    snprintf(avc_audit_buf, AVC_AUDIT_BUFSIZE,
         "%s:  %s ", avc_prefix, (denied || !requested) ? "denied" : "granted");
    avc_dump_av(tclass, audited);
    log_append(avc_audit_buf, " for ");
 
    /* get any extra information printed by the callback */
    avc_suppl_audit(a, tclass, avc_audit_buf + strlen(avc_audit_buf),
            AVC_AUDIT_BUFSIZE - strlen(avc_audit_buf));
 
    log_append(avc_audit_buf, " ");
    avc_dump_query(ssid, tsid, tclass);
 
    if (denied)
        log_append(avc_audit_buf, " permissive=%u", result ? 0 : 1);
 
    log_append(avc_audit_buf, "\n");
    avc_log(SELINUX_AVC, "%s", avc_audit_buf);
 
    avc_release_lock(avc_log_lock);
}

最后调用的是avc_log打印的,而这个其实这里是回调service_manager.c设置的callback函数

cb.func_audit = audit_callback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
cb.func_log = selinux_log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
 
 
int selinux_log_callback(int type, const char *fmt, ...)
{
    va_list ap;
    int priority;
    char *strp;
 
    switch(type) {
    case SELINUX_WARNING:
        priority = ANDROID_LOG_WARN;
        break;
    case SELINUX_INFO:
        priority = ANDROID_LOG_INFO;
        break;
    default:
        priority = ANDROID_LOG_ERROR;
        break;
    }
 
    va_start(ap, fmt);
    if (vasprintf(&strp, fmt, ap) != -1) {
        LOG_PRI(priority, "SELinux", "%s", strp);
        LOG_EVENT_STRING(AUDITD_LOG_TAG, strp);
        free(strp);
    }
    va_end(ap);
    return 0;
}

关于Android里面的属性,也有相应的权限检查机制,有兴趣的可以看看相关代码~

二、avc denied信息分析

kernel log中的avc denied来自于common_lsm_audit,还有函数avc_audit_pre_callback、avc_audit_post_callback,把avc denied的每一段信息拼接起来

最后是用printk打印到kernel log里面

kernelcode/security/selinux/avc.c

/* This is the slow part of avc audit with big stack footprint */
noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass,
        u32 requested, u32 audited, u32 denied, int result,
        struct common_audit_data *a,
        unsigned flags)
{
    ......
    common_lsm_audit(a, avc_audit_pre_callback, avc_audit_post_callback);
    return 0;
}

下面针对几个关键的信息分析下

  • { write }
    缺失的权限,这个是本文讨论的核心部分

打印来自于函数 avc_dump_av (kernelcode/security/selinux/avc.c)

/**
 * avc_dump_av - Display an access vector in human-readable form.
 * @tclass: target security class
 * @av: access vector
 */
static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)
{
    const char **perms;
    int i, perm;
 
    if (av == 0) {
        audit_log_format(ab, " null");
        return;
    }
 
    BUG_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map));
    perms = secclass_map[tclass-1].perms;
 
    audit_log_format(ab, " {");
    i = 0;
    perm = 1;
    while (i < (sizeof(av) * 8)) {
        if ((perm & av) && perms[i]) {
            audit_log_format(ab, " %s", perms[i]);
            av &= ~perm;
        }
        i++;
        perm <<= 1;
    }
 
    if (av)
        audit_log_format(ab, " 0x%x", av);
 
    audit_log_format(ab, " }");
}

分析avc_dump_av代码逻辑,其实就是在secclass_map数组里面找到权限对应的字符串,打印出来方便分析

和前面部分提到的内容是相关的

  • for pid=4021
    进程ID

  • comm=“testA.sh”
    进程的名称,但是这个字符串设计的是有长度限制的,所以经常看见一些apk的包名这里会打印不完整

[ 5786.420602@3] type=1400 audit(1624264582.595:426): avc: denied { open } for pid=4686 comm="testA.launcher3" path="/proc/vmstat" dev="proc" ino=4026532002 scontext=u:r:untrusted_app:s0:c46,c256,c512,c768 tcontext=u:object_r:proc_vmstat:s0 tclass=file permissive=1
 
实际上进程名是com.testA.launcher3
  • scontext=u:r:testA:s0
    主体的安全上下文,这里就是脚本所在的进程testA.sh

  • tcontext=u:object_r:vendor_data_file:s0
    被操作对象的安全上下文,这里是/data/vendor/目录的安全上下文,默认在这个目录下面创建的文件的安全上下文,都会继承这个父目录的安全上下文

  • tclass=dir
    种类,dir是目录,file文件,这个也比较好理解

所以后面写te规则的时候,其实就是

allow scontext tcontext:tclass perms;
 
也就是:
 
allow testA vendor_data_file:dir { write };

有个工具叫audit2allow,可以将报错信息自动生成te规则,其实讲实话我并不建议使用

首先,格式有要求,动不动转失败,还有转漏的;

其次,你都不清楚需要的权限是什么,就一顿操作,把所有报错信息都加上去了,可能加的一些权限和问题没有关联,结果还违反neverallow规则,改完以后根本追溯不了

我建议直接看log信息添加权限信息,结合SELinux的知识,添加的权限有理有据,也可以提升自己的能力

三、调试

1、快速编译和替换

1.1 编译和替换

修改的内容编译命令替换方式
*.temake precompiled_sepolicy
或者
make selinux_policy
/vendor/etc/selinux/precompiled_sepolicy
或者
/odm/etc/selinux/precompiled_sepolicy
property_contexts可以直接板子上验证直接到板子上改
/vendor/etc/selinux/vendor_property_contexts、
/system/etc/selinux/plat_property_contexts
file_contexts可以直接板子上验证直接到板子上改
/vendor/etc/selinux/vendor_file_contexts、
/system/etc/selinux/plat_file_contexts
service_contexts可以直接板子上验证直接到板子上改
/vendor/etc/selinux/vendor_hwservice_contexts、
/system/etc/selinux/plat_hwservice_contexts、
/system/etc/selinux/plat_service_contexts

有很多文章提到在Android 9.0上,编译策略文件要用make sepolicy然后替换/vendor/etc/selinux /system/etc/selinux 两个目录,其实这里是有问题的

1、make sepolicy之后,/vendor/etc/selinux /system/etc/selinux 目录下的产物是不会发生变化,因此替换整个目录是没有作用的(如果lunch的时候target发生变化,那也只是第一次会重新生成,后面再修改te也不会变)

2、system/sepolicy目录下, mma -j编译后,替换是可以的,因为这个相当于把android/system/sepolicy/Android.mk 的所有module都编译了一遍

讲讲由来:

从Android 8.0之后,因为Google project treble的影响,二进制策略文件不再是根目录的 /sepolicy (也就是make sepolicy 编译生成的产物 root/sepolicy)

而是 vendor/etc/selinux/precompiled_sepolicy (或者是odm/etc/selinux/precompiled_sepolicy)

相应的,编译的命令也有变化,修改了*.te,更正确的应该是使用make precompiled_sepolicy

生成 vendor/etc/selinux/precompiled_sepolicy 替换到板子即可 (如果存在odm分区,生成在 odm/etc/selinux/precompiled_sepolicy,替换到板子的路径也是对应的)

那make sepolicy和make precompiled_sepolicy 有什么区别?

编译出来的产物其实内容是一样的,但是生成路径不一样

之所以说有误解,是因为我们没有搞清楚真正要替换的是什么

make selinux_policy 我比较常使用,因为这个编译范围把需要的都编译了,在对应目录下的文件都会更新

这里还需要注意一下,修改/vendor 、 /system下面文件的安全上下文(在/vendor/etc/selinux/vendor_file_contexts、/system/etc/selinux/plat_file_contexts)时,

可能出现修改了没有生效的问题,要注意用restorecon命令恢复一下安全上下文,这些只读分区的文件是一开始编译的时候确定下来的,

所以会发现有些时候没有改成功,最关键的就是出现这些问题的时候,多用ls -Z、getprop -Z看看安全上下文

比如:

console:/ # ls -lZ /system/xbin/testB
-rwxr-xr-x 1 root shell u:object_r:system_file:s0 20380 2009-01-01 00:00 /system/xbin/testB
 
修改了file_context
 
console:/ # restorecon -R /system/xbin/testB
SELinux: Loaded file_contexts
console:/ #
console:/ # ls -lZ /system/xbin/testB
-rwxr-xr-x 1 root shell u:object_r:testB_exec:s0 20380 2009-01-01 00:00 /system/xbin/testB

1.2 load_policy

上面的方式在替换之后需要重启,开机会重新load新的二进制策略文件

还有一个命令也很方便:

load_policy

我们可以直接加载二进制策略文件

load_policy  /storage/xxxx/precompiled_sepolicy

不过只在本次开机才有效,重启就没有效果了,所以如果是开机过程的SELinux权限问题,还是得乖乖的替换文件

2、尽量使用宏

对于一些文件和目录的操作,我们可以尽量使用宏

比如:

allow testC vendor_data_file:dir { write add_name create getattr read setattr open remove_name rmdir };
allow testC vendor_data_file:file { create read write open ioctl getattr lock append unlink setattr };

这里脚本的目的是要创建目录和文件

如果按我们现在的方式,等着系统一条一条的报权限问题,那这个过程会发现你得反复添加编译再替换到板子,很浪费时间

但其实可以直接添加下面的权限:

allow testC vendor_data_file:dir create_dir_perms;
allow testC vendor_data_file:file create_file_perms;

create_dir_perms和create_file_perms的定义在/android/system/sepolicy/public/global_macros

如果只是读取,那就直接用r_file_perms、r_dir_perms

使用宏会比较快的解决文件和目录相关的操作,这个也是遇到比较多的一种

还有一个宏定义的地方:

/android/system/sepolicy/public/te_macros

对于属性,通常用的是这两个

get_prop(testC, system_prop)
set_prop(testC, system_prop)

这里是有包含关系的,set_prop宏里面是包含了get_prop的,所以其实写了set_prop就不用写get_prop了

对于binder,这两个用的比较多

binder_use
binder_call

遇到一些权限问题的时候,可以先看看te_macros里面的,搜一搜,看看注释,会有很大帮助的

当然,还可以自定义宏,可以少写很多重复的规则,比如下面这个:

define(`testD_common_file_dir_r_perms', `
allow testD $1:file r_file_perms;
allow testD $1:dir r_dir_perms;
')
 
然后使用:
testD_common_file_dir_r_perms(init)
testD_common_file_dir_r_perms(kernel)
testD_common_file_dir_r_perms(logd)
testD_common_file_dir_r_perms(vold)
testD_common_file_dir_r_perms(audioserver)
testD_common_file_dir_r_perms(lmkd)
testD_common_file_dir_r_perms(tee)
testD_common_file_dir_r_perms(surfaceflinger)
testD_common_file_dir_r_perms(hdmicecd)
testD_common_file_dir_r_perms(hwservicemanager)
testD_common_file_dir_r_perms(netd)
testD_common_file_dir_r_perms(sdcardfs)
testD_common_file_dir_r_perms(servicemanager)

3、使用属性

如果出现下面这种一直报的同一类型和权限的
比如service_manager { find }

allow testD activity_service:service_manager { find };
allow testD package_service:service_manager { find };
allow testD window_service:service_manager { find };
allow testD meminfo_service:service_manager { find };
allow testD cpuinfo_service:service_manager { find };
allow testD mount_service:service_manager { find };
allow testD surfaceflinger_service:service_manager { find };
allow testD audioserver_service:service_manager { find };
allow testD secure_element_service:service_manager { find };
allow testD contexthub_service:service_manager { find };
allow testD netd_listener_service:service_manager { find };
allow testD connmetrics_service:service_manager { find };
allow testD bluetooth_manager_service:service_manager { find };
allow testD imms_service:service_manager { find };
allow testD cameraproxy_service:service_manager { find };
allow testD slice_service:service_manager { find };
allow testD media_projection_service:service_manager { find };
allow testD crossprofileapps_service:service_manager { find };
allow testD launcherapps_service:service_manager { find };
allow testD shortcut_service:service_manager { find };
allow testD media_router_service:service_manager { find };
allow testD hdmi_control_service:service_manager { find };
allow testD media_session_service:service_manager { find };
allow testD restrictions_service:service_manager { find };
allow testD graphicsstats_service:service_manager { find };
allow testD dreams_service:service_manager { find };
allow testD commontime_management_service:service_manager { find };
allow testD network_time_update_service:service_manager { find };
allow testD diskstats_service:service_manager { find };
allow testD voiceinteraction_service:service_manager { find };
allow testD appwidget_service:service_manager { find };
allow testD backup_service:service_manager { find };
allow testD trust_service:service_manager { find };
allow testD voiceinteraction_service:service_manager { find };
allow testD jobscheduler_service:service_manager { find };
allow testD hardware_properties_service:service_manager { find };
allow testD serial_service:service_manager { find };
allow testD usb_service:service_manager { find };
allow testD DockObserver_service:service_manager { find };
allow testD audio_service:service_manager { find };
allow testD wallpaper_service:service_manager { find };
allow testD search_service:service_manager { find };
allow testD country_detector_service:service_manager { find };
allow testD location_service:service_manager { find };
allow testD devicestoragemonitor_service:service_manager { find };
allow testD notification_service:service_manager { find };
allow testD updatelock_service:service_manager { find };
allow testD system_update_service:service_manager { find };
allow testD servicediscovery_service:service_manager { find };
allow testD connectivity_service:service_manager { find };
allow testD ethernet_service:service_manager { find };
allow testD wifip2p_service:service_manager { find };
allow testD wifiscanner_service:service_manager { find };
allow testD wifi_service:service_manager { find };
allow testD netpolicy_service:service_manager { find };
allow testD netstats_service:service_manager { find };
allow testD network_score_service:service_manager { find };
allow testD textclassification_service:service_manager { find };
allow testD textservices_service:service_manager { find };
allow testD ipsec_service:service_manager { find };
allow testD network_management_service:service_manager { find };
allow testD clipboard_service:service_manager { find };
allow testD statusbar_service:service_manager { find };
allow testD device_policy_service:service_manager { find };
allow testD deviceidle_service:service_manager { find };
allow testD uimode_service:service_manager { find };
allow testD lock_settings_service:service_manager { find };
allow testD storagestats_service:service_manager { find };
allow testD accessibility_service:service_manager { find };
allow testD input_method_service:service_manager { find };
allow testD pinner_service:service_manager { find };
allow testD network_watchlist_service:service_manager { find };
allow testD vr_manager_service:service_manager { find };
allow testD input_service:service_manager { find };

可以找到这些type的共同点,也就是attribute

上面的可以简化为下面的,减去的哪些type是因为违反了neverallow规则的

allow testD { service_manager_type -stats_service -incident_service -vold_service -netd_service -installd_service -dumpstate_service }:service_manager { find };

4、setools工具

这个工具挺好用的,可以分析二进制策略文件,比如用于某些情况下判断权限是否加上了

GitHub上有:setools3 工具

$ sesearch -A precompiled_sepolicy | grep kernel
allow kernel app_data_file:file read;
allow kernel asec_image_file:file read;
allow kernel binder_device:chr_file { append getattr ioctl lock map open read write };
allow kernel debugfs_mali:dir search;
allow kernel device:blk_file { create getattr setattr unlink };
allow kernel device:chr_file { create getattr setattr unlink };
allow kernel device:dir { add_name append create getattr ioctl lock map open read remove_name rmdir search write };
allow kernel file_contexts_file:file { getattr ioctl lock map open read };
allow kernel hwbinder_device:chr_file { append getattr ioctl lock map open read write };
allow kernel init:process { rlimitinh share siginh transition };
allow kernel init_exec:file { execute getattr map open read relabelto };
allow kernel kernel:cap_userns { sys_boot sys_nice sys_resource };
allow kernel kernel:capability { mknod sys_boot sys_nice sys_resource };
allow kernel kernel:dir { getattr ioctl lock open read search };
allow kernel kernel:fd use;
allow kernel kernel:fifo_file { append getattr ioctl lock map open read write };
allow kernel kernel:file { append getattr ioctl lock map open read write };
allow kernel kernel:lnk_file { getattr ioctl lock map open read };
allow kernel kernel:process { fork getattr getcap getpgid getsched getsession setcap setpgid setrlimit setsched sigchld sigkill signal signull sigstop };
allow kernel kernel:security setcheckreqprot;
......

四、参考文章

《Linux强制访问控制机制模块代码分析报告》

《Linux多安全策略和动态安全策 略框架模块代码分析报告》

fs_use_xattr

其他

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值