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 编译和替换
修改的内容 | 编译命令 | 替换方式 |
---|---|---|
*.te | make 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强制访问控制机制模块代码分析报告》
- https://www.sohu.com/a/135967369_467784
- https://www.sohu.com/a/136011893_467784
- https://www.sohu.com/a/136288365_467784
- https://www.sohu.com/a/136482060_467784
- https://www.sohu.com/a/136639046_467784
- http://www.safebase.cn/article-231024-1.html
- https://www.sohu.com/a/137347468_467784
- https://www.sohu.com/a/137512129_467784
- https://www.sohu.com/a/137888523_467784
- https://www.sohu.com/a/137888531_467784
- https://www.sohu.com/a/138185447_467784
《Linux多安全策略和动态安全策 略框架模块代码分析报告》
- https://www.sohu.com/a/138580970_467784
- http://www.safebase.cn/article-231245-1.html
- https://www.sohu.com/a/138804853_467784
- https://www.sohu.com/a/138976820_467784
- http://www.voidcn.com/article/p-sfquuwtm-wk.html
- https://www.sohu.com/a/139413489_467784
- https://www.sohu.com/a/139709740_467784
- https://www.sohu.com/a/139973785_467784
- http://www.voidcn.com/article/p-xhablags-wk.html
- http://www.voidcn.com/article/p-uwdtubpm-wk.html
- https://www.sohu.com/a/140884205_467784
- http://www.voidcn.com/article/p-tkroeamc-wk.html
- https://www.sohu.com/a/141414625_467784
- https://www.sohu.com/a/141709117_467784
- https://m.sohu.com/a/142034082_467784/
- http://www.voidcn.com/article/p-ksphumbr-wk.html
- https://www.sohu.com/a/142438688_467784
- http://www.voidcn.com/article/p-blaxfzwg-wk.html
- http://www.voidcn.com/article/p-gfifderx-wk.html
- https://m.sohu.com/a/143323077_467784/
- http://www.voidcn.com/article/p-eoijaouz-wk.html
- https://www.sohu.com/a/143971000_467784
- http://www.voidcn.com/article/p-zfzucopl-wk.html
fs_use_xattr
其他