1 Efuse and Dm-verity
1.1 Efuse - 对bootloader签名
熔丝文件:sec.dat
1)烧写signed bootloader
2)将密匙文件sec.dat烧写入efuse
3)重启设备,设备的bootROM会读取efuse中的密匙pubk验证bootloader
4)bootloader验证通过,启动,开始AVB验证流程
1.2 编译时关闭AVB
@ BoardConfig.mk
BOARD_AVB_ENABLE := false
BOARD_BUILD_DISABLED_VBMETAIMAGE := true
AndroidO之后没有独立的recovery.img,boot.img根据cmdline参数来决定mount哪个ramdisk。
如果有skip_initramfs参数,那么mount打包在system.img中的normal ramdisk;否则mount打包在boot.img中的recovery ramdisk。
参数BOARD_BUILD_SYSTEM_ROOT_IMAGE的配置决定是将normal ramdisk打包到boot.img中还是打包到system.img中。
打包到boot.img中:
BOARD_BUILD_SYSTEM_ROOT_IMAGE := false
打包到system.img中:
BOARD_BUILD_SYSTEM_ROOT_IMAGE := true
1.3 AVB验证流程
需要提前挂载分区的fstab来自于commandline的androidboot.android_dt_dir
1)bootloader使用内置的OEM pubk验证vbmeta.img,验证通过后用vbmeta.img中的boot pubk验证boot.img,如果验证通过就启动boot.img
2)init启动后,init/fs_mgr使用vbmeta.img中的vendor pubk、system pubk、odm pubk验证vendor.img、system.img、odm.img,验证通过就mount,否则不会mount
Figure 1-1 Dm-verity的工作流程
编译生成Metadata流程:
@ build/core/Makefile
->
@ build/tools/releasetools/build_image.py
->
BuildVerityTree() - 用来生成dm_verity需要的签名数据
BuildVerityMetadata() - 生成Metadata数据
->
@ system/extras/verity/build_verity_metadata.py
->
build_verity_metadata()
1.4 Disable System Dm-verity
@ device/{ro.boot.hardware}/{ro.board.platform}/fstab.{ro.boot.hardware}
Change
/dev/block/bootdevice/by-name/system /system ext4 ro,barrier=1,discard wait, avb
to
/dev/block/bootdevice/by-name/system /system ext4 ro,barrier=1,discard wait
可以使用内核配置 CONFIG_DM_VERITY_HASH_PREFETCH_MIN_SIZE(默认大小为 128)来启用 dm-verity 哈希预提取大小。该修改可提升启动速度。
1.5 AndroidO userdebug版本运行时禁止dm-verity
AndroidO dm-verity disable flag存在于vbmeta.img(keystore分区)中;而老版本是放置在system.img分区的dm-verity metadata中。
1)在设置中打开OEM unlocking选项
2)在设置中打开USB debugging选项
3)adb reboot bootloader
4)fastboot flashing unlock和fastboot oem unlock
5)fastboot reboot
6)adb root
7)adb disable-verity
8)adb reboot
9)adb root
10)adb remount
1.6 AndroidO userdebug版本刷机时禁止dm-verity
AndroidO dm-verity disable flag存在于vbmeta.img(keystore分区)中;而老版本是放置在system.img分区的dm-verity metadata中。
1)在设置中打开OEM unlocking选项
2)在设置中打开USB debugging选项
3)adb reboot bootloader
4)fastboot flashing unlock和fastboot oem unlock
5)fastboot --disable-verity --disable-verification flash vbmeta vbmeta.img
6)fastboot reboot
7)adb root
8)adb remount
2 Android selinux How-to
2.1 LSM(Linux Security Module)的五种实现
SELinux:Security Enhanced Linux,基于inode,Android当前使用的就是这种
SMACK:Simple Mandatory Access Control Kernel,基于inode
Tomoyo:日本女人名“智代”,日本人实现的代码,基于path
AppArmor:应用盔甲,AppArmor is installed by default in Ubuntu
Yama:来自梵文,中文名为“阎罗”,只处理ptrace和文件链接
2.2 注释
avc - Access Vector Cache
avd - Access Vector Decision,访问向量决策
ssid - 主体的安全标识
tsid - 客体的安全标识
tclass - 客体安全类型
requested - 申请检查的权限
auditdata - 附加的审计数据,主要产生审计日志,供内核kauditd和用户auditd保存日志到磁盘中,当权限检查失败时,内核会打印审计不通过的log,参考另一篇文章Linux auditd
2.3 编译时强制打开selinux
BOARD_KERNEL_CMDLINE += androidboot.selinux=enforcing
2.4 查看设备节点的sepolicy权限
ls -alZ /dev/kmsg
2.5 not_full_treble
如果遇到execute_no_trans无法通过编译neverallow检查,就需要用到这个宏
2.6 根据avc log自动生成Android Selinux策略
2.6.1 生成policy文本文件
1)提取所有的avc log
adb shell "cat /proc/kmsg | grep avc" > avc_log.txt
or
adb shell
dmesg | grep avc > /dev/avc_log.txt
adb pull /dev/avc_log.txt .
2)使用audit2allow直接生成policy
sudo apt-get install policycoreutils
audit2allow -i avc_log.txt -o output_pol.te
vi output_pol.te
2.6.2 直接插入到sepolicy文件中
adb shell
dmesg > /dev/kern_msg.txt
adb pull /dev/kern_msg.txt .
cat kern_msg.log | audit2allow -p out/target/product/<device>/root/sepolicy
2.7 打开关闭Sepolicy
需要android版本是usereng/eng
adb root
adb shell
关闭:
# setenforce 0
打开:
# setenforce 1
2.8 添加到启动脚本中禁止security
on nonencrypted
# A/B update verifier that marks a successful boot.
exec - root cache -- /system/bin/update_verifier nonencrypted
class_start main
class_start late_start
in late_start script, add [setenforce 0]
2.9 查找用户空间支持的所有权限字符串
用户空间宏定义
@ sepolicy/global_macros
内核空间权限字符串
@ security/selinux/selinuxfs.c
security_load_policy()
->
@ security/selinux/include/classmap.h
struct security_class_mapping secclass_map[]; // 对应tclass,权限bitmap(每个权限占用一个32bit整数的1bit)
2.10 Dump sepolicy DB
@ security/selinux/ss/services.c
#include <linux/moduleparam.h>
static int oem_sepolicy_db_show(char *buffer, const struct kernel_param *kp)
{
unsigned i, j;
struct sidtab_node *cur;
char *context_name = NULL;
u32 length;
i = 0;
while (secclass_map[i].name) i++;
printk(KERN_DEBUG "(tclass_cnt, current_mapping_size, SIDTAB_SIZE) = "
"(%d, %d, %d)\n\n",
i, current_mapping_size, SIDTAB_SIZE);
// step1
if (!current_mapping) {
goto out;
}
for (i = 0; i < current_mapping_size; i++) {
printk("%d (value, num_perms, permission_bitmap) = (%d, %d",
i, current_mapping[i].value, current_mapping[i].num_perms);
if (!current_mapping[i].num_perms) {
printk(")\n");
continue;
} else {
printk(" - ");
}
for (j = 0; j < current_mapping[i].num_perms; j++) {
if (j == (current_mapping[i].num_perms - 1)) {
printk("%x", current_mapping[i].perms[j]);
} else {
printk("%x ", current_mapping[i].perms[j]);
}
}
printk(")\n");
}
// step2
printk("\n");
for (i = 0; i < SIDTAB_SIZE; i++) {
cur = sidtab.htable[i];
while (cur) {
if (context_struct_to_string(&(cur->context),
&context_name, &length) < 0)
continue;
printk("(%d %s)", cur->sid, context_name);
kfree(context_name);
cur = cur->next;
if (cur) {
printk(" ### ");
}
}
printk("\n");
}
out:
// no output log to userspace
return 0;
}
static struct kernel_param_ops oem_sepolicy_db_ops = {
.get = oem_sepolicy_db_show,
};
module_param_cb(oem_sepolicy_db, &oem_sepolicy_db_ops, NULL, 0600);
MODULE_PARM_DESC(oem_sepolicy_db, "show sepolicy db");
3 Abbreviations
avb:Android Verified Boot,用dm-verify验证system分区的完整性,用在Android 8.0之后的fstab中
AVC:Access Vector Cache
DAC:Discretionary Access Control,自主访问控制
FRP:Factory Reset Protection
LSM:Linux Security Module
Appendix
audit2allow python demo for OEM
#
# audit2allow, translate avc log to .te file
# Author: George Tso
#
# usage
# dmesg | grep "avc" > /dev/avc.log
# adb pull /dev/avc.log .
# audit2allow avc.log avc.te
import re
import string
import sys
def write_outfile(outfile, hashmap):
for (hm_key, hm_value) in hashmap.items():
#{
allow_value = ''
hm_value.sort()
if (len(hm_value) == 1):
allow_line = hm_key + ' ' + hm_value[0] + ';' + '\n'
else:
for value in hm_value:
allow_value += ' ' + value
allow_line = hm_key + ' {' + allow_value + ' };' + '\n'
outfile.writelines(allow_line)
#}
def has_proc(line, proc):
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
if (proc == sub[2]):
return True
else:
return False
#}
return False
def _generate_te(proc_list):
src = ''
tgt = ''
tclass = ''
got_tclass = False
hashmap = {}
got_key = False
repeat = False
outfile = open(sys.argv[2], 'w')
for proc in proc_list:
#{
outfile.writelines('\n\n===============' + proc + '================\n')
file = open(sys.argv[1], 'r')
for line in file.readlines():
#{
line = line.strip()
if not len(line) or line.startswith('#'):
continue
if (has_proc(line, proc) == False):
# not this process, continue
continue
# regular expression to extract {}
perm = re.findall(r'[{](.*?)[}]', line)
#print perm[0].strip()
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
src = sub[2]
elif (e.find('tcontext') > -1):
sub = e.split(':')
tgt = sub[2]
elif (e.find('tclass') > -1):
sub = e.split('=')
tclass = sub[1]
got_tclass = True
if (got_tclass == True):
got_tclass = False
allow_key = 'allow' + ' ' + src + ' ' + tgt + ':' + tclass.strip()
allow_value = perm[0].lstrip().rstrip()
hm_key = ''
hm_value = []
for (hm_key, hm_value) in hashmap.items():
#{
if (hm_key == allow_key):
got_key = True
break;
#}
if (got_key == True):
got_key = False
for value in hm_value:
#{
if (value == allow_value):
repeat = True
break;
#}
if (repeat == False):
hm_value.append(allow_value)
hashmap[allow_key] = hm_value
repeat = False
else:
hm_value = []
hm_value.append(allow_value)
hashmap[allow_key] = hm_value
#} end of for e in e_list
#} end of for line in file.readlines()
write_outfile(outfile, hashmap)
hashmap.clear()
file.close()
#} for proc in proc_list
outfile.close()
def generate_te():
# STEP 1 - FIND ALL THE PROCESSES
proc_list = []
repeat = False
file = open(sys.argv[1], 'r')
for line in file.readlines():
#{
line = line.strip()
if not len(line) or line.startswith('#'):
continue
e_list = line.split()
for e in e_list:
#{
if (e.find('scontext') > -1):
sub = e.split(':')
for proc in proc_list:
#{
if (proc == sub[2]):
repeat = True;
break;
#}
if (repeat == False):
proc_list.append(sub[2])
repeat = False
break
#}
#}
file.close()
proc_list.sort()
print proc_list
# STEP 2 - GENERATE OUTPUT FILE
_generate_te(proc_list)
if __name__ == '__main__':
if (len(sys.argv) < 3):
print(sys.argv[0] + ' ' + '<input_file.log>' + ' ' + '<output_file.te>')
exit(0)
generate_te()