三星手机内核防护技术RKP深度剖析(七)

 

在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在上一篇文章中,我们为读者介绍了与RKP/KDP相关的一些命令,在本文中,将继续为读者呈现更多精彩内容!

 

凭证的保护机制

内核中的相关结构体

我们已经看到,struct cred和struct task_security_struct现在被分配在只读页中,因此,内核也无法对其进行修改。同时,这些结构体还增加了新的字段,这些字段将被用于数据流完整性保护。同时,每个结构体都有一个“反向指针(back-pointer)”,也就是指向所属结构体的指针:

  1. struct task_struct对应于struct cred;
  2. struct cred对应于struct task_security_struct。

实际上,struct cred也具有一个指向所属任务的PGD的反向指针,以及一个“使用计数器”,用于防止重复使用另一个task_struct的struct cred(尤其是init的凭证)。

// from include/linux/cred.h

struct cred {

// ...

atomic_t *use_cnt;

struct task_struct *bp_task;

void *bp_pgd;

unsigned long long type;

} __randomize_layout;



// from security/selinux/include/objsec.h



struct task_security_struct {

// ...

void *bp_cred;

};

对于这些值来说,都应该通过调用security_integrity_current对每个SELinux hook进行相应的验证,但在我们的研究设备中并没有实现这一功能,也许是他们忘了。因此,我们只好从另一个提供该功能的设备中获取了相关的源代码。

// from security/security.c

#define call_void_hook(FUNC, ...)\

do {\

struct security_hook_list *P;\

\

if(security_integrity_current()) break; \

list_for_each_entry(P, &security_hook_heads.FUNC, list) \

P->hook.FUNC(__VA_ARGS__);\

} while (0)



#define call_int_hook(FUNC, IRC, ...) ({\

int RC = IRC;\

do {\

struct security_hook_list *P;\

\

RC = security_integrity_current();\

if (RC != 0)\

break;\

list_for_each_entry(P, &security_hook_heads.FUNC, list) { \

RC = P->hook.FUNC(__VA_ARGS__);\

if (RC != 0)\

break;\

}\

} while (0);\

RC;\

})



// from security/selinux/hooks.c

static inline unsigned int cmp_sec_integrity(const struct cred *cred,struct mm_struct *mm)

{

return ((cred->bp_task != current) ||

(mm && (!( in_interrupt() || in_softirq())) &&

(cred->bp_pgd != swapper_pg_dir) &&

(mm->pgd != cred->bp_pgd)));

}

extern struct cred init_cred;

static inline unsigned int rkp_is_valid_cred_sp(u64 cred,u64 sp)

{

struct task_security_struct *tsec = (struct task_security_struct *)sp;



if((cred == (u64)&init_cred) &&

( sp == (u64)&init_sec)){

return 0;

}

if(!rkp_ro_page(cred)|| !rkp_ro_page(cred+sizeof(struct cred)-1)||

(!rkp_ro_page(sp)|| !rkp_ro_page(sp+sizeof(struct task_security_struct)-1))) {

return 1;

}

if((u64)tsec->bp_cred != cred) {

return 1;

}

return 0;

}

inline void rkp_print_debug(void)

{

u64 pgd;

struct cred *cred;



pgd = (u64)(current->mm?current->mm->pgd:swapper_pg_dir);

cred = (struct cred *)current_cred();



printk(KERN_ERR"\n RKP44 cred = %p bp_task = %p bp_pgd = %p pgd = %llx stat = #%d# task = %p mm = %p \n",cred,cred->bp_task,cred->bp_pgd,pgd,(int)rkp_ro_page((unsigned long long)cred),current,current->mm);



//printk(KERN_ERR"\n RKP44_1 uid = %d gid = %d euid = %degid = %d \n",(u32)cred->uid,(u32)cred->gid,(u32)cred->euid,(u32)cred->egid);

printk(KERN_ERR"\n RKP44_2 Cred %llx #%d# #%d# Sec ptr %llx #%d# #%d#\n",(u64)cred,rkp_ro_page((u64)cred),rkp_ro_page((u64)cred+sizeof(struct cred)),(u64)cred->security, rkp_ro_page((u64)cred->security),rkp_ro_page((u64)cred->security+sizeof(struct task_security_struct)));





}

/* Main function to verify cred security context of a process */

int security_integrity_current(void)

{

rcu_read_lock();

if ( rkp_cred_enable &&

(rkp_is_valid_cred_sp((u64)current_cred(),(u64)current_cred()->security)||

cmp_sec_integrity(current_cred(),current->mm)||

cmp_ns_integrity())) {

rkp_print_debug();

rcu_read_unlock();

panic("RKP CRED PROTECTION VIOLATION\n");

}

rcu_read_unlock();

return 0;

}

unsigned int rkp_get_task_sec_size(void)

{

return sizeof(struct task_security_struct);

}

unsigned int rkp_get_offset_bp_cred(void)

{

return offsetof(struct task_security_struct,bp_cred);

}

我们可以看到,security_integrity_current将执行下列操作:

  1. 检查当前的cred和task_security_struct是否属于init。
  2. 检查当前cred和task_security_struct的起始/结束地址是否位于RO页中。
  3. 检查task_security_struct的反向指针是否指向cred。
  4. 检查cred的反向指针是否指向当前的task_struct。
  5. 检查cred的PGD反向指针是不是swapper_pg_dir。
  6. 检查cred的PGD bp是否是当前mm_struct的PDG;
  7. 对挂载命名空间进行相应的检查,这一点我们将在后面看到。

修改struct cred

如前所述,内核每次要修改进程的struct cred时,都需要调用RKP。在执行内核操作之前,RKP会做一些必要的检查。而为了完成这些检查,它需要知道task_struct和cred结构体的各个字段的偏移量。

在内核端,该命令是由init/main.c中的kdp_init函数执行的。

// from init/main.c

void kdp_init(void)

{

kdp_init_t cred;



cred.credSize= sizeof(struct cred);

cred.sp_size= rkp_get_task_sec_size();

cred.pgd_mm= offsetof(struct mm_struct,pgd);

cred.uid_cred= offsetof(struct cred,uid);

cred.euid_cred= offsetof(struct cred,euid);

cred.gid_cred= offsetof(struct cred,gid);

cred.egid_cred= offsetof(struct cred,egid);



cred.bp_pgd_cred= offsetof(struct cred,bp_pgd);

cred.bp_task_cred= offsetof(struct cred,bp_task);

cred.type_cred= offsetof(struct cred,type);

cred.security_cred= offsetof(struct cred,security);

cred.usage_cred= offsetof(struct cred,use_cnt);



cred.cred_task= offsetof(struct task_struct,cred);

cred.mm_task= offsetof(struct task_struct,mm);

cred.pid_task= offsetof(struct task_struct,pid);

cred.rp_task= offsetof(struct task_struct,real_parent);

cred.comm_task= offsetof(struct task_struct,comm);



cred.bp_cred_secptr= rkp_get_offset_bp_cred();



cred.verifiedbootstate = (u64)verifiedbootstate;

#ifdef CONFIG_SAMSUNG_PRODUCT_SHIP

cred.selinux.ss_initialized_va= (u64)&ss_initialized;

#endif

uh_call(UH_APP_RKP, RKP_KDP_X40, (u64)&cred, 0, 0, 0);

}



asmlinkage __visible void __init start_kernel(void)

{

// ...

if (rkp_cred_enable)

kdp_init();

cred_init();

// ...

}

在管理程序端,该命令在rkp_cmd_cred_init函数中进行处理的,该函数将调用rkp_cred_init。

int64_t rkp_cmd_cred_init(saved_regs_t* regs) {

uh_log('L', "rkp.c", 399, "KDP_KDP About to call cred_init");

rkp_cred_init(regs);

return 0;

}



void rkp_cred_init(saved_regs_t* regs) {

// ...



rkp_cred = malloc(0xF0, 0);

cred = rkp_get_pa(regs->x2);

if (cred_inited == 1) {

uh_log('L', "rkp_kdp.c", 1083, "Cannot initialized for Second Time\n");

return;

}

cred_inited = 1;

credSize = cred->credSize;

sp_size = cred->sp_size;

uid_cred = cred->uid_cred;

euid_cred = cred->euid_cred;

gid_cred = cred->gid_cred;

egid_cred = cred->egid_cred;

usage_cred = cred->usage_cred;

bp_pgd_cred = cred->bp_pgd_cred;

bp_task_cred = cred->bp_task_cred;

type_cred = cred->type_cred;

security_cred = cred->security_cred;

bp_cred_secptr = cred->bp_cred_secptr;

if (uid_cred > credSize || euid_cred > credSize || gid_cred > credSize || egid_cred > credSize ||

usage_cred > credSize || bp_pgd_cred > credSize || bp_task_cred > credSize || type_cred > credSize ||

security_cred > credSize || bp_cred_secptr > sp_size) {

uh_log('L', "rkp_kdp.c", 1102, "RKP_9a19e9ca");

return;

}

rkp_cred->CRED_SIZE = cred->credSize;

rkp_cred->SP_SIZE = sp_size;

rkp_cred->CRED_UID_OFFSET = uid_cred >> 2;

rkp_cred->CRED_EUID_OFFSET = euid_cred >> 2;

rkp_cred->CRED_GID_OFFSET = gid_cred >> 2;

rkp_cred->CRED_EGID_OFFSET = egid_cred >> 2;

rkp_cred->TASK_PID_OFFSET = cred->pid_task >> 2;

rkp_cred->TASK_CRED_OFFSET = cred->cred_task >> 3;

rkp_cred->TASK_MM_OFFSET = cred->mm_task >> 3;

rkp_cred->TASK_PARENT_OFFSET = cred->rp_task >> 3;

rkp_cred->TASK_COMM_OFFSET = cred->comm_task >> 3;

rkp_cred->CRED_SECURITY_OFFSET = security_cred >> 3;

rkp_cred->CRED_BP_PGD_OFFSET = bp_pgd_cred >> 3;

rkp_cred->CRED_BP_TASK_OFFSET = bp_task_cred >> 3;

rkp_cred->CRED_FLAGS_OFFSET = type_cred >> 3;

rkp_cred->SEC_BP_CRED_OFFSET = bp_cred_secptr >> 3;

rkp_cred->MM_PGD_OFFSET = cred->pgd_mm >> 3;

rkp_cred->CRED_USE_CNT = usage_cred >> 3;

rkp_cred->VERIFIED_BOOT_STATE = 0;

vbs_va = cred->verifiedbootstate;

if (vbs_va) {

vbs_pa = check_and_convert_kernel_input(vbs_va);

if (vbs_pa != 0) {

rkp_cred->VERIFIED_BOOT_STATE = strcmp(vbs_pa, "orange") == 0;

}

}

rkp_cred->SELINUX = rkp_get_pa(&cred->selinux);

rkp_cred->SS_INITIALIZED_VA = rkp_get_pa(cred->selinux.ss_initialized_va);

uh_log('L', "rkp_kdp.c", 1147, "RKP_4bfa8993 %lx %lx %lx %lx");

}

函数rkp_cred_init将执行下列任务:

  1. 为包含所有偏移量的结构体rkp_cred分配内存空间。
  2. 对不同的偏移量进行一些基本的安全检查。
  3. 将各种偏移量存储到这个rkp_cred结构体中。
  4. 检查设备是否被解锁(即verifiedbootstate是否为“orange”)。
  5. 保存ss_initialized的地址(它是由SELinux初始化的)。

修改PGD

当内核要更新struct task_struct的PGD时,会调用RKP来同时更新struct cred中的反向指针。在内核端,这主要发生在进程被复制的时候。

// from fs/exec.c

static int exec_mmap(struct mm_struct *mm)

{

// ...

if(rkp_cred_enable){

uh_call(UH_APP_RKP, RKP_KDP_X43,(u64)current_cred(), (u64)mm->pgd, 0, 0);

}

// ...

}



// from kernel/fork.c

void rkp_assign_pgd(struct task_struct *p)

{

u64 pgd;

pgd = (u64)(p->mm ? p->mm->pgd :swapper_pg_dir);



uh_call(UH_APP_RKP, RKP_KDP_X43, (u64)p->cred, (u64)pgd, 0, 0);

}



static __latent_entropy struct task_struct *copy_process(

unsigned long clone_flags,

unsigned long stack_start,

unsigned long stack_size,

int __user *child_tidptr,

struct pid *pid,

int trace,

unsigned long tls,

int node)

{

// ...

if(rkp_cred_enable)

rkp_assign_pgd(p);

// ...

}



函数rkp_cmd_pgd_assign将会调用rkp_pgd_assign函数。



int64_t rkp_cmd_pgd_assign(saved_regs_t* regs) {

rkp_pgd_assign(regs);

return 0;

}



int64_t rkp_phys_map_verify_cred(uint64_t cred) {

// ...



if (!cred)

return 1;

if (cred != cred / CRED_BUFF_SIZE * CRED_BUFF_SIZE)

return 1;

rkp_phys_map_lock(cred);

if (is_phys_map_cred(cred)) {

uh_log('L', "rkp_kdp.c", 127, "physmap verification failed !!!!! %lx %lx %lx", cred, cred, cred);

rkp_phys_map_unlock(cred);

return 1;

}

rkp_phys_map_unlock(cred);

return 0;

}



void rkp_pgd_assign(saved_regs_t* regs) {

// ...



cred = rkp_get_pa(regs->x2);

pgd = regs->x3;

if (rkp_phys_map_verify_cred(cred)) {

uh_log('L', "rkp_kdp.c", 146, "rkp_pgd_assign !!%lx %lx %lx", cred, regs->x2, pgd);

return;

}

*(cred + 8 * rkp_cred->CRED_BP_PGD_OFFSET) = pgd;

}

函数rkp_pgd_assign在更新struct cred的bp_pgd字段之前,会检查是否需要将physmap中的struct cred标记为CRED。

修改security字段

与上一条命令类似,当内核需要修改struct cred的security字段时,它也会调用RKP。这发生在struct cred被释放的时候。

// from security/selinux/hooks.c

int rkp_from_tsec_jar(unsigned long addr)

{

static void *objp;

static struct kmem_cache *s;

static struct page *page;



objp = (void *)addr;



if(!objp)

return 0;



page = virt_to_head_page(objp);

s = page->slab_cache;

if(s && s->name) {

if(!strcmp(s->name,"tsec_jar")) {

return 1;

}

}

return 0;

}

int chk_invalid_kern_ptr(u64 tsec)

{

return (((u64)tsec >> 36) != (u64)0xFFFFFFC);

}

void rkp_free_security(unsigned long tsec)

{

if(!tsec ||

chk_invalid_kern_ptr(tsec))

return;



if(rkp_ro_page(tsec) &&

rkp_from_tsec_jar(tsec)){

kmem_cache_free(tsec_jar,(void *)tsec);

}

else {

kfree((void *)tsec);

}

}



static void selinux_cred_free(struct cred *cred)

{

// ...

if (rkp_ro_page((unsigned long)cred)) {

uh_call(UH_APP_RKP, RKP_KDP_X45, (u64) &cred->security, 7, 0, 0);

}

// ...

rkp_free_security((unsigned long)tsec);

// ...

}



之后,函数rkp_cmd_cred_set_security将调用rkp_cred_set_security函数。



int64_t rkp_cmd_cred_set_security(saved_regs_t* regs) {

rkp_cred_set_security(regs);

return 0;

}



int64_t* rkp_cred_set_security(saved_regs_t* regs) {

// ...



cred = rkp_get_pa(regs->x2 - 8 * rkp_cred->CRED_SECURITY_OFFSET);

if (is_phys_map_cred(cred))

return uh_log('L', "rkp_kdp.c", 146, "invalidate_security: invalid cred !!!!! %lx %lx %lx", regs->x2,

regs->x2 - 8 * CRED_SECURITY_OFFSET, CRED_SECURITY_OFFSET);

security = rkp_get_pa(regs->x2);

*security = 7;

return security;

}

rkp_cred_set_security函数在将struct cred的security字段设置为7(毒药值)之前,会检查传给它的地址是否确实是struct cred(在physmap中标记为CRED)。

进程的标记

当进程进行execve系统调用时,将执行额外的检查。

// from fs/exec.c

#define rkp_is_nonroot(x) ((x->cred->type)>>1 & 1)

#define rkp_is_lod(x) ((x->cred->type)>>3 & 1)



#define CHECK_ROOT_UID(x) (x->cred->uid.val == 0 || x->cred->gid.val == 0 || \

x->cred->euid.val == 0 || x->cred->egid.val == 0 || \

x->cred->suid.val == 0 || x->cred->sgid.val == 0)



static int rkp_restrict_fork(struct filename *path)

{

struct cred *shellcred;



if (!strcmp(path->name, "/system/bin/patchoat") ||

!strcmp(path->name, "/system/bin/idmap2")) {

return 0;

}

/* If the Process is from Linux on Dex,

then no need to reduce privilege */

#ifdef CONFIG_LOD_SEC

if(rkp_is_lod(current)){

return 0;

}

#endif

if(rkp_is_nonroot(current)){

shellcred = prepare_creds();

if (!shellcred) {

return 1;

}

shellcred->uid.val = 2000;

shellcred->gid.val = 2000;

shellcred->euid.val = 2000;

shellcred->egid.val = 2000;



commit_creds(shellcred);

}

return 0;

}



SYSCALL_DEFINE3(execve,

const char __user *, filename,

const char __user *const __user *, argv,

const char __user *const __user *, envp)

{

struct filename *path = getname(filename);

int error = PTR_ERR(path);



if(IS_ERR(path))

return error;



if(rkp_cred_enable){

uh_call(UH_APP_RKP, RKP_KDP_X4B, (u64)path->name, 0, 0, 0);

}



if(CHECK_ROOT_UID(current) && rkp_cred_enable) {

if(rkp_restrict_fork(path)){

pr_warn("RKP_KDP Restricted making process. PID = %d(%s) "

"PPID = %d(%s)\n",

current->pid, current->comm,

current->parent->pid, current->parent->comm);

putname(path);

return -EACCES;

}

}

putname(path);

return do_execve(getname(filename), argv, envp);

}

之后,内核会调用RKP,将可执行文件的路径传给它,并通过设置struct cred的type字段来标记该进程。如果当前任务以root身份执行,那么内核也会调用rkp_restrict_fork函数。

rkp_restrict_fork函数将执行以下操作:

  1. 如果运行的二进制文件是patchoat或idmap2,则放行。
  2. 如果进程来自DeX上的Linux,则放行。
  3. 如果当前进程被标记为非root进程,则为其分配shell用户的凭证。

在管理程序端,rkp_cmd_mark_ppt函数将调用rkp_mark_ppt函数。

int64_t rkp_cmd_mark_ppt(saved_regs_t* regs) {

rkp_mark_ppt(regs);

return 0;

}



void rkp_mark_ppt(saved_regs_t* regs) {

// ...



current_va = rkp_ns_get_current();

current_pa = rkp_get_pa(current_va);

current_cred = rkp_get_pa(*(current_pa + 8 * rkp_cred->TASK_CRED_OFFSET));

name_va = regs->x2;

name_pa = rkp_get_pa(name_va);

if (!current_cred || !name_pa || rkp_phys_map_verify_cred(current_cred)) {

uh_log('L', "rkp_kdp.c", 551, "rkp_mark_ppt NULL Cred OR filename %lx %lx %lx", current_cred, 0, 0);

}

if (!strcmp(name_pa, "/system/bin/adbd") || !strcmp(name_pa, "/system/bin/app_process32") ||

!strcmp(name_pa, "/system/bin/app_process64")) {

*(current_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET) |= CRED_FLAG_MARK_PPT;

}

if (!strcmp(name_pa, "/system/bin/nst"))

*(current_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET) |= CRED_FLAG_LOD;

if (!strcmp(name_pa, "/system/bin/idmap2"))

*(current_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET) &= ~CRED_FLAG_CHILD_PPT;

if (!strcmp(name_pa, "/system/bin/patchoat"))

*(current_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET) &= ~CRED_FLAG_CHILD_PPT;

}

函数rkp_mark_ppt将执行以下操作:

  1. 对参数进行合理性检查。
  2. 如果可执行文件是adbd、app_process32或app_process64;
  3. 则设置标志CRED_FLAG_MARK_PPT (4)。
  4. 如果可执行文件是nst;
  5. 则设置标志CRED_FLAG_LOD(8,由rkp_is_lod进行相关的检查)。
  6. 如果可执行文件是idmap2或patchoat;
  7. 则取消标志CRED_FLAG_CHILD_PPT(2,由rkp_is_nonroot进行相关的检查)。

凭证的分配

该命令是KDP的核心。当内核需要给struct task_struct分配一个struct cred并进行大量检查以检测特权提升时,将调用该方法。

在内核端,copy_creds、commit_creds和override_creds将调用prepare_ro_creds进行相应的处理,而不是直接设置struct task_struct的cred字段。

函数prepare_ro_creds将执行以下操作:

  1. 为一个只读类型的struct cred分配内存空间;
  2. 为一个供“使用计数器”使用的结构体分配内存空间;
  3. 为一个只读类型的struct task_security_struct分配内存空间;
  4. 创建内核要分配给RKP的上述3个结构体以及一个读写类型的结构体;
  5. RKP将验证相关的值,并将数据从RW struct cred复制到RO struct cred;
  6. prepare_ro_creds返回赋值后的RO struct cred。
// from include/linux/cred.h

typedef struct cred_param{

struct cred *cred;

struct cred *cred_ro;

void *use_cnt_ptr;

void *sec_ptr;

unsigned long type;

union {

void *task_ptr;

u64 use_cnt;

};

}cred_param_t;



enum {

RKP_CMD_COPY_CREDS = 0,

RKP_CMD_CMMIT_CREDS,

RKP_CMD_OVRD_CREDS,

};

#define override_creds(x) rkp_override_creds(&x)



#define rkp_cred_fill_params(crd,crd_ro,uptr,tsec,rkp_cmd_type,rkp_use_cnt) \

do {\

cred_param.cred = crd;\

cred_param.cred_ro = crd_ro;\

cred_param.use_cnt_ptr = uptr;\

cred_param.sec_ptr= tsec;\

cred_param.type = rkp_cmd_type;\

cred_param.use_cnt = (u64)rkp_use_cnt;\

} while(0)



// from kernel/cred.c

static struct cred *prepare_ro_creds(struct cred *old, int kdp_cmd, u64 p)

{

u64 pgd =(u64)(current->mm?current->mm->pgd:swapper_pg_dir);

struct cred *new_ro;

void *use_cnt_ptr = NULL;

void *rcu_ptr = NULL;

void *tsec = NULL;

cred_param_t cred_param;

new_ro = kmem_cache_alloc(cred_jar_ro, GFP_KERNEL);

if (!new_ro)

panic("[%d] : kmem_cache_alloc() failed", kdp_cmd);



use_cnt_ptr = kmem_cache_alloc(usecnt_jar,GFP_KERNEL);

if (!use_cnt_ptr)

panic("[%d] : Unable to allocate usage pointer\n", kdp_cmd);



rcu_ptr = get_usecnt_rcu(use_cnt_ptr);

((struct ro_rcu_head*)rcu_ptr)->bp_cred = (void *)new_ro;



tsec = kmem_cache_alloc(tsec_jar, GFP_KERNEL);

if (!tsec)

panic("[%d] : Unable to allocate security pointer\n", kdp_cmd);



rkp_cred_fill_params(old,new_ro,use_cnt_ptr,tsec,kdp_cmd,p);

uh_call(UH_APP_RKP, RKP_KDP_X46, (u64)&cred_param, 0, 0, 0);

if (kdp_cmd == RKP_CMD_COPY_CREDS) {

if ((new_ro->bp_task != (void *)p)

|| new_ro->security != tsec

|| new_ro->use_cnt != use_cnt_ptr) {

panic("[%d]: RKP Call failed task=#%p:%p#, sec=#%p:%p#, usecnt=#%p:%p#", kdp_cmd, new_ro->bp_task,(void *)p,new_ro->security,tsec,new_ro->use_cnt,use_cnt_ptr);

}

}

else {

if ((new_ro->bp_task != current)||

(current->mm

&& new_ro->bp_pgd != (void *)pgd) ||

(new_ro->security != tsec) ||

(new_ro->use_cnt != use_cnt_ptr)) {

panic("[%d]: RKP Call failed task=#%p:%p#, sec=#%p:%p#, usecnt=#%p:%p#, pgd=#%p:%p#", kdp_cmd, new_ro->bp_task,current,new_ro->security,tsec,new_ro->use_cnt,use_cnt_ptr,new_ro->bp_pgd,(void *)pgd);

}

}



rocred_uc_set(new_ro, 2);



set_cred_subscribers(new_ro, 0);

get_group_info(new_ro->group_info);

get_uid(new_ro->user);

get_user_ns(new_ro->user_ns);



#ifdef CONFIG_KEYS

key_get(new_ro->session_keyring);

key_get(new_ro->process_keyring);

key_get(new_ro->thread_keyring);

key_get(new_ro->request_key_auth);

#endif



validate_creds(new_ro);

return new_ro;

}



int copy_creds(struct task_struct *p, unsigned long clone_flags)

{

// ...

/*

* Disabling cred sharing among the same thread group. This

* is needed because we only added one back pointer in cred.

*

* This should NOT in any way change kernel logic, if we think about what

* happens when a thread needs to change its credentials: it will just

* create a new one, while all other threads in the same thread group still

* reference the old one, whose reference counter decreases by 2.

*/

if(!rkp_cred_enable){

// ...

}

// ...

if(rkp_cred_enable){

p->cred = p->real_cred = prepare_ro_creds(new, RKP_CMD_COPY_CREDS, (u64)p);

put_cred(new);

}

else {

p->cred = p->real_cred = get_cred(new);

alter_cred_subscribers(new, 2);

validate_creds(new);

}

// ...

}



int commit_creds(struct cred *new)

{



if (rkp_ro_page((unsigned long)new))

BUG_ON((rocred_uc_read(new)) < 1);

else

// ...

if(rkp_cred_enable) {

struct cred *new_ro;



new_ro = prepare_ro_creds(new, RKP_CMD_CMMIT_CREDS, 0);



rcu_assign_pointer(task->real_cred, new_ro);

rcu_assign_pointer(task->cred, new_ro);

}

else {

rcu_assign_pointer(task->real_cred, new);

rcu_assign_pointer(task->cred, new);

}

// ...

if (rkp_cred_enable){

put_cred(new);

put_cred(new);

}

// ...

}



const struct cred *rkp_override_creds(struct cred **cnew)

{

// ...

struct cred *new = *cnew;

// ...

if(rkp_cred_enable) {

volatile unsigned int rkp_use_count = rkp_get_usecount(new);

struct cred *new_ro;



new_ro = prepare_ro_creds(new, RKP_CMD_OVRD_CREDS, rkp_use_count);

*cnew = new_ro;

rcu_assign_pointer(current->cred, new_ro);

put_cred(new);

}

else {

get_cred(new);

alter_cred_subscribers(new, 1);

rcu_assign_pointer(current->cred, new);

}

// ...

}

在管理程序端,rkp_cmd_assign_creds函数将调用rkp_assign_creds函数。

int64_t rkp_cmd_assign_creds(saved_regs_t* regs) {

rkp_assign_creds(regs);

return 0;

}



uint64_t rkp_ns_get_current() {

if (get_sp_sel())

return get_sp_el0();

return get_sp();

}



int64_t check_pe_id(uint32_t targ_id, uint32_t curr_id) {

return curr_id > 1000 && targ_id <= 1000;

}



bool rkp_check_pe(int64_t targ_cred, int64_t curr_cred) {

// ..



curr_uid = *(curr_cred + 4 * rkp_cred->CRED_UID_OFFSET);

targ_uid = *(targ_cred + 4 * rkp_cred->CRED_UID_OFFSET);

if (check_pe_id(targ_uid, curr_uid))

return 1;

curr_gid = *(curr_cred + 4 * rkp_cred->CRED_GID_OFFSET);

targ_gid = *(targ_cred + 4 * rkp_cred->CRED_GID_OFFSET);

if (check_pe_id(targ_gid, curr_gid))

return 1;

curr_ueid = *(curr_cred + 4 * rkp_cred->CRED_EUID_OFFSET);

targ_euid = *(targ_cred + 4 * rkp_cred->CRED_EUID_OFFSET);

if (targ_euid < curr_uid && check_pe_id(targ_euid, curr_euid))

return 1;

curr_egid = *(curr_cred + 4 * rkp_cred->CRED_EGID_OFFSET);

targ_egid = *(targ_cred + 4 * rkp_cred->CRED_EGID_OFFSET);

if (targ_egid < curr_gid && check_pe_id(targ_egid, curr_egid))

return 1;

return 0;

}



int64_t from_zyg_adbd(int64_t curr_task, int64_t curr_cred) {

// ...



curr_flags = *(curr_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET);

if ((curr_flags & 2) != 0)

return 1;

task = curr_task;

while (1) {

task_pid = *(task + 4 * rkp_cred->TASK_PID_OFFSET);

if (!task_pid)

return 0;

task_comm = task + 8 * rkp_cred->TASK_COMM_OFFSET;

memcpy(comm, task_comm, sizeof(comm));

if (!strcmp(comm, "zygote") || !strcmp(comm, "zygote64") || !strcmp(comm, "adbd"))

return 1;

parent_va = *(task + 8 * rkp_cred->TASK_PARENT_OFFSET);

task = parent_pa = rkp_get_pa(parent_va);

}

}



int64_t rkp_privilege_escalation(int64_t targ_cred, int64_t curr_cred, int64_t flag) {

uh_log('L', "rkp_kdp.c", 461, "Priv Escalation - Current %lx %lx %lx", *(curr_cred + 4 * rkp_cred->CRED_UID_OFFSET),

*(curr_cred + 4 * rkp_cred->CRED_GID_OFFSET), *(curr_cred + 4 * rkp_cred->CRED_EGID_OFFSET));

uh_log('L', "rkp_kdp.c", 462, "Priv Escalation - Passed %lx %lx %lx", *(targ_cred + 4 * rkp_cred->CRED_UID_OFFSET),

*(targ_cred + 4 * rkp_cred->CRED_GID_OFFSET), *(targ_cred + 4 * rkp_cred->CRED_EGID_OFFSET));

return rkp_policy_violation("KDP Privilege Escalation %lx %lx %lx", targ_cred, curr_cred, flag);

}



bool check_privilege_escalation(int32_t targ_id, int32_t curr_id) {

return ((curr_id - 0x61A80000) <= 0xFFFF && (targ_id - 0x61A80000) > 0xFFFF && targ_id != -1);

}



int64_t chk_invalid_sec_ptr(uint64_t sec_ptr) {

rkp_phys_map_lock(sec_ptr);

if (!sec_ptr || !is_phys_map_sec_ptr(sec_ptr) || !is_phys_map_sec_ptr(sec_ptr + rkp_cred->SP_SIZE - 1) ||

sec_ptr != sec_ptr / rkp_cred->SP_BUFF_SIZE * rkp_cred->SP_BUFF_SIZE) {

uh_log('L', "rkp_kdp.c", 186, "Invalid Sec Pointer %lx %lx %lx", is_phys_map_sec_ptr(sec_ptr), sec_ptr,

sec_ptr - sec_ptr / rkp_cred->SP_BUFF_SIZE * rkp_cred->SP_BUFF_SIZE);

rkp_phys_map_unlock(sec_ptr);

return 1;

}

rkp_phys_map_unlock(sec_ptr);

return 0;

}



void rkp_assign_creds(saved_regs_t* regs) {

// ...



cred_param = rkp_get_pa(regs->x2);

if (!cred_param) {

uh_log('L', "rkp_kdp.c", 662, "NULL pData");

return;

}

curr_task_va = rkp_ns_get_current();

curr_task = rkp_get_pa(curr_task_va);

curr_cred_va = *(curr_task + 8 * rkp_cred->TASK_CRED_OFFSET);

curr_cred = rkp_get_pa(curr_cred_va);

targ_cred = rkp_get_pa(cred_param->cred);

targ_cred_ro = rkp_get_pa(cred_param->cred_ro);

curr_secptr_va = *(curr_cred + 8 * rkp_cred->CRED_SECURITY_OFFSET);

curr_secptr = rkp_get_pa(curr_secptr_va);

if (!curr_cred) {

uh_log('L', "rkp_kdp.c", 489, "\nCurrent Cred is NULL %lx %lx %lx\n ", curr_task, curr_task_va, 0);

return rkp_policy_violation("Data Protection Violation %lx %lx %lx", curr_task_va, curr_task, 0);

}

if (!curr_secptr && rkp_deferred_inited) {

uh_log('L', "rkp_kdp.c", 495, "\nCurrent sec_ptr is NULL%lx %lx %lx\n ", curr_task, curr_task_va, curr_cred);

return rkp_policy_violation("Data Protection Violation %lx %lx %lx", curr_task_va, curr_cred, 0);

}

bp_cred_va = *(curr_secptr + 8 * rkp_cred->SEC_BP_CRED_OFFSET);

bp_task_va = *(curr_cred + 8 * rkp_cred->CRED_BP_TASK_OFFSET);

if (bp_cred_va != curr_cred_va || bp_task_va != curr_task_va) {

uh_log('L', "rkp_kdp.c", 502, "\n Integrity Check failed_1%lx %lx %lx\n ", bp_cred_va, curr_cred_va, curr_cred);

uh_log('L', "rkp_kdp.c", 503, "\n Integrity Check failed_2 %lx %lx %lx\n ", bp_task_va, curr_task_va, curr_task);

rkp_policy_violation("KDP Privilege Escalation %lx %lx %lx", bp_cred_va, curr_cred_va, curr_secptr);

return;

}

if (!targ_cred || !targ_cred_ro || rkp_phys_map_verify_cred(targ_cred_ro)) {

uh_log('L', "rkp_kdp.c", 699, "rkp_assign_creds !! %lx %lx", targ_cred_ro, targ_cred);

return;

}

skip_checks = 0;

curr_flags = *(curr_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET);

if ((curr_flags & 8) == 0) {

curr_uid = *(curr_cred + 4 * rkp_cred->CRED_UID_OFFSET);

curr_euid = *(curr_cred + 4 * rkp_cred->CRED_EUID_OFFSET);

curr_gid = *(curr_cred + 4 * rkp_cred->CRED_GID_OFFSET);

curr_egid = *(curr_cred + 4 * rkp_cred->CRED_EGID_OFFSET);

if ((curr_uid & 0xFFFF0000) != 0x61A80000 && (curr_euid & 0xFFFF0000) != 0x61A80000 &&

(curr_gid & 0xFFFF0000) != 0x61A80000 && (curr_egid & 0xFFFF0000) != 0x61A80000) {

if (!rkp_cred->VERIFIED_BOOT_STATE) {

if (rkp_check_pe(targ_cred, curr_cred) && from_zyg_adbd(curr_task, curr_cred)) {

uh_log('L', "rkp_kdp.c", 717, "Priv Escalation! %lx %lx %lx", targ_cred,

*(targ_cred + 8 * rkp_cred->CRED_EUID_OFFSET), *(curr_cred + 8 * rkp_cred->CRED_EUID_OFFSET));

return rkp_privilege_escalation(targ_cred, cred_pa, 1);

}

}

skip_checks = 1;

} else {

*(curr_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET) = curr_flags | CRED_FLAG_LOD;

}

}

if (!skip_checks) {

targ_uid = *(targ_cred + rkp_cred->CRED_UID_OFFSET);

priv_esc = 0;

if (targ_uid != 3003) {

curr_uid = *(cred_pa + 4 * rkp_cred->CRED_UID_OFFSET);

priv_esc = 0;

if (check_privilege_escalation(targ_uid, curr_uid)) {

uh_log('L', "rkp_kdp.c", 382, "\n LOD: uid privilege escalation curr_uid = %ld targ_uid = %ld \n", curr_uid,

targ_uid);

priv_esc = 1;

}

}

targ_euid = *(targ_cred + rkp_cred->CRED_EUID_OFFSET);

if (targ_euid != 3003) {

curr_euid = *(cred_pa + 4 * rkp_cred->CRED_EUID_OFFSET);

if (check_privilege_escalation(targ_euid, curr_euid)) {

uh_log('L', "rkp_kdp.c", 387, "\n LOD: euid privilege escalation curr_euid = %ld targ_euid = %ld \n", curr_euid,

targ_euid);

priv_esc = 1;

}

}

targ_gid = *(targ_cred + rkp_cred->CRED_GID_OFFSET);

if (targ_gid != 3003) {

curr_gid = *(cred_pa + 4 * rkp_cred->CRED_GID_OFFSET);

if (check_privilege_escalation(targ_gid, curr_gid)) {

uh_log('L', "rkp_kdp.c", 392, "\n LOD: Gid privilege escalation curr_gid = %ld targ_gid = %ld \n", curr_gid,

targ_gid);

priv_esc = 1;

}

}

targ_egid = *(targ_cred + rkp_cred->CRED_EGID_OFFSET);

if (targ_egid != 3003) {

curr_egid = *(cred_pa + 4 * rkp_cred->CRED_EGID_OFFSET);

if (check_privilege_escalation(targ_egid, curr_egid)) {

uh_log('L', "rkp_kdp.c", 397, "\n LOD: egid privilege escalation curr_egid = %ld targ_egid = %ld \n", curr_egid,

targ_egid);

priv_esc = 1;

}

}

if (priv_esc) {

uh_log('L', "rkp_kdp.c", 705, "Linux on Dex Priv Escalation! %lx", targ_cred);

if (curr_task) {

curr_comm = curr_task + 8 * rkp_cred->TASK_COMM_OFFSET;

uh_log('L', "rkp_kdp.c", 707, curr_comm);

}

return rkp_privilege_escalation(param_cred_pa, cred_pa, 1);

}

}

memcpy(targ_cred_ro, targ_cred, rkp_cred->CRED_SIZE);

cmd_type = cred_param->type;

*(targ_cred_ro + 8 * rkp_cred->CRED_USE_CNT) = cred_param->use_cnt_ptr;

if (cmd_type != RKP_CMD_COPY_CREDS) {

curr_mm_va = *(current_pa + 8 * rkp_cred->TASK_MM_OFFSET);

if (curr_mm_va) {

curr_mm = rkp_get_pa(curr_mm_va);

curr_pgd_va = *(curr_mm + 8 * rkp_cred->MM_PGD_OFFSET);

} else {

curr_pgd_va = rkp_get_va(get_ttbr1_el1() & 0xFFFFFFFFC000);

}

*(targ_cred_ro + 8 * rkp_cred->CRED_BP_PGD_OFFSET) = curr_pgd_va;

*(targ_cred_ro + 8 * rkp_cred->CRED_BP_TASK_OFFSET) = curr_task_va;

if (cmd_type == RKP_CMD_OVRD_CREDS) {

if (cred_param->use_cnt <= 1)

*(targ_cred_ro + 8 * rkp_cred->CRED_FLAGS_OFFSET) &= ~CRED_FLAG_ORPHAN;

else

*(targ_cred_ro + 8 * rkp_cred->CRED_FLAGS_OFFSET) |= CRED_FLAG_ORPHAN;

}

} else {

*(targ_cred_ro + 8 * rkp_cred->CRED_BP_TASK_OFFSET) = cred_param->task_ptr;

}

sec_ptr_va = cred_param->sec_ptr;

cred_ro_va = cred_param->cred_ro;

if (sec_ptr_va) {

sec_ptr = rkp_get_pa(sec_ptr_va);

targ_secptr_va = *(targ_cred + 8 * rkp_cred->CRED_SECURITY_OFFSET);

targ_secptr = rkp_get_pa(targ_secptr);

if (chk_invalid_sec_ptr(sec_ptr) || !targ_secptr || !curr_secptr) {

uh_log('L', "rkp_kdp.c", 594, "Invalid sec pointer [assign_secptr] %lx %lx %lx", sec_ptr_va, sec_ptr,

targ_secptr);

rkp_policy_violation("Data Protection Violation %lx %lx %lx", sec_ptr_va, targ_secptr, curr_secptr);

} else {

curr_sid = *(curr_secptr + 4);

targ_sid = *(targ_secptr + 4);

if (rkp_deferred_inited && targ_sid <= 19 && curr_sid >= 21) {

uh_log('L', "rkp_kdp.c", 607, "Selinux Priv Escalation !! [assign_secptr] %lx %lx ", targ_sid, curr_sid);

rkp_policy_violation("Data Protection Violation %lx %lx %lx", targ_sid, curr_sid, 0);

} else {

memcpy(sec_ptr, targ_secptr, rkp_cred->SP_SIZE);

*(targ_cred_ro + 8 * rkp_cred->CRED_SECURITY_OFFSET) = sec_ptr_va;

*(sec_ptr + 8 * rkp_cred->SEC_BP_CRED_OFFSET) = cred_ro_va;

}

}

} else {

uh_log('L', "rkp_kdp.c", 583, "Security Pointer is NULL [assign_secptr] %lx", 0);

rkp_policy_violation("Data Protection Violation", 0, 0, 0);

}

if (rkp_cred->VERIFIED_BOOT_STATE)

return;

targ_flags = *(targ_cred_ro + 8 * rkp_creds->CRED_FLAGS_OFFSET);

if ((targ_flags & CRED_FLAG_MARK_PPT) != 0) {

parent_task_va = *(curr_task + 8 * rkp_cred->TASK_PARENT_OFFSET);

parent_task = rkp_get_pa(parent_task_va);

parent_cred_va = *(parent_task + 8 * rkp_cred->TASK_CRED_OFFSET);

parent_cred = rkp_get_pa(parent_cred_va);

parent_flags = *(parent_cred + 8 * rkp_cred->CRED_FLAGS_OFFSET);

if ((parent_flags & CRED_FLAG_MARK_PPT) != 0)

*(targ_cred_ro + 8 * rkp_cred->CRED_FLAGS_OFFSET) |= CRED_FLAG_CHILD_PPT;

}

}

函数rkp_assign_creds将完成下列任务:

  1. 通过SP_EL0获取当前的task_struct。
  2. 这就是内核在使用current时的做法。
  3. (参见arch/arm64/include/asm/current.h)。
  4. 对参数进行一些安全检查。
  5. 检查当前struct task_struct的反向指针是否正确。
  6. 检查当前struct task_security_struct的反向指针是否正确。
  7. 检查目标RO struct cred在physmap中是否被标记为CRED。
  8. 如果当前struct cred没有设置任何标志。
  9. 如果当前UID/EUID/GID/EGID中的任何一个是0x61A8xxxx;
  10. 则设置标志CRED_FLAG_LOD(8)。
  11. 如果VERIFIED_BOOT_STATE为0(设备解锁),则跳过下一步检查。
  12. 否则调用rkp_check_pe和from_zyg_adbd。
  13. rkp_check_pe(检查权限提升)检查当前ID是否>AID_SYSTEM,目标ID是否<=AID_SYSTEM。同时,它还会检查UID、GID、EUID和EGID。
  14. 如果当前任务被标记,或者它的父任务是zygote、zygote64或adbd(意味着它是这些任务的子任务),那么from_zyg_adbd将会返回。
  15. 如果这两个函数都返回true,则调用rkp_privilege_escalation来触发违规。
  16. 否则,在设备未锁定的情况下,它将跳过下一步检查。
  17. 它开始对UID/EUID/GID/EGID进行一系列检查。
  18. 对于每一对ID(当前/目标ID),调用check_privilege_escalation;
  19. 如果当前ID属于LOD,而目标ID不属于LOD,该函数将返回。
  20. 如果任何一个调用返回true,则调用rkp_privilege_escalation。
  21. 现在检查已经完成,它将RW struct cred复制到RO struct cred中。
  22. 它将RO struct cred的use_cnt字段设置为参数给定的值,如果命令类型是RKP_privilege_escalation,则调用rkp_privilege_escalation。
  23. 如果命令类型是RKP_CMD_CREDS(即从copy_creds中调用的),则设置RO结构cred的后指针。
  24. 则将RO struct cred的反向指针设置为struct task_struct。
  25. 否则,如果命令类型是RKP_CMD_CMMIT_CREDS(即从commit_creds中调用)或RKP_CMD_OVRD_CREDS(即从override_creds中调用);
  26. 获取当前struct task_struct的mm_struct的PGD,如果是NULL,则从TTBR1_EL1获取PGD。
  27. 如果是NULL,则从TTBR1_EL1中获取PGD。
  28. 设置RO struct cred的PGD反向指针。
  29. 将RO struct cred的PGD反向指针设置为当前任务的反向指针。
  30. 如果命令类型为RKP_CMD_OVRD_CREDS。
  31. 如果“使用计数器”的值为0或1,则取消标志CRED_FLAG_ORPHAN(1)。
  32. 否则,设置标志CRED_FLAG_ORPHAN(1)。
  33. 调用chk_invalid_sec_ptr对以参数形式给出的struct task_security_struct进行检查。
  34. 这个函数检查其地址是否在physmap中被标记为SEC_PTR。
  35. 以及它是否具有预期的大小(SP_BUFF_SIZE的倍数)。
  36. 检查安全上下文ID是否允许权限升级。
  37. 如果我们是defer inited的,并且当前SID >= SECINITSID_SYSCTL_NET_UNIX 而目标SID <= SECINITSID_SYSCTL_KERNEL,那么这就是权限升级。
  38. (SECINITSID_xxx是在security/selinux/include/flask.h中定义的)。
  39. 它将目标struct task_security_struct复制到对应的RO结构体中。
  40. 它将RO struct cred的security字段设置为RO的。
  41. 它将RO struct cred的bp_cred字段设置为RO的。
  42. 如果设备被解锁,则提前返回。
  43. 否则,如果目标没有标志CRED_FLAG_MARK_PPT。
  44. 当前任务的父任务的标志是否为CRED_FLAG_MARK_PPT:
  45. 是否将目标标记为CRED_FLAG_CHILD_PPT。

 

小结

在本系列文章中,我们将为读者深入讲解三星手机的内核防护技术。在本文中,我们为读者详细介绍了RKP内核保护机制是如何处理安全凭证的,在后续的文章中,会有更多精彩内容呈现给大家,敬请期待!

(未完待续)

(完)