/* * client_hash_auth.c * * function for CA code hash auth * * Copyright (C) 2022 Huawei Technologies Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "client_hash_auth.h" #include #include #include #include #ifdef CONFIG_AUTH_SUPPORT_UNAME #include #endif #ifdef CONFIG_CLIENT_AUTH #include #include #include #include #include #include #include #endif #ifdef CONFIG_AUTH_HASH #include #endif #include #include "tc_ns_log.h" #include "auth_base_impl.h" #ifdef CONFIG_AUTH_HASH #define SHA256_DIGEST_LENGTH 32 #define FIXED_PKG_NAME_LENGTH 256 struct sdesc_hash { struct shash_desc shash; char ctx[]; }; #endif #if defined (CONFIG_ANDROID_HIDL) || defined (CONFIG_MDC_HAL_AUTH) static int check_proc_state(bool is_hidl, struct task_struct **hidl_struct, const struct tc_ns_client_context *context) { bool check_value = false; if (is_hidl) { rcu_read_lock(); *hidl_struct = pid_task(find_vpid(context->calling_pid), PIDTYPE_PID); check_value = !*hidl_struct || (*hidl_struct)->state == TASK_DEAD; if (check_value) { tloge("task is dead\n"); rcu_read_unlock(); return -EFAULT; } get_task_struct(*hidl_struct); rcu_read_unlock(); return EOK; } return EOK; } static int get_hidl_client_task(bool is_hidl_task, struct tc_ns_client_context *context, struct task_struct **cur_struct) { int ret; struct task_struct *hidl_struct = NULL; ret = check_proc_state(is_hidl_task, &hidl_struct, context); if (ret) return ret; if (hidl_struct) *cur_struct = hidl_struct; else *cur_struct = current; return EOK; } #endif #ifdef CONFIG_CLIENT_AUTH #define LIBTEEC_CODE_PAGE_SIZE 8 #define DEFAULT_TEXT_OFF 0 #define LIBTEEC_NAME_MAX_LEN 50 const char g_libso[KIND_OF_SO][LIBTEEC_NAME_MAX_LEN] = { "libteec_vendor.so", #ifndef CONFIG_CMS_CAHASH_AUTH #ifndef CONFIG_CADAEMON_AUTH "libteec.huawei.so", #else "libteec.so", #endif #endif }; static int find_lib_code_area(struct mm_struct *mm, struct vm_area_struct **lib_code_area, int so_index) { struct vm_area_struct *vma = NULL; bool is_valid_vma = false; bool is_so_exists = false; bool param_check = (!mm || !mm->mmap || !lib_code_area || so_index >= KIND_OF_SO); if (param_check) { tloge("illegal input params\n"); return -EFAULT; } for (vma = mm->mmap; vma; vma = vma->vm_next) { is_valid_vma = (vma->vm_file && vma->vm_file->f_path.dentry && vma->vm_file->f_path.dentry->d_name.name); if (is_valid_vma) { is_so_exists = !strcmp(g_libso[so_index], vma->vm_file->f_path.dentry->d_name.name); if (is_so_exists && (vma->vm_flags & VM_EXEC)) { *lib_code_area = vma; tlogd("so name is %s\n", vma->vm_file->f_path.dentry->d_name.name); return EOK; } } } return -EFAULT; } struct get_code_info { unsigned long code_start; unsigned long code_end; unsigned long code_size; }; static int update_so_hash(struct mm_struct *mm, struct task_struct *cur_struct, struct shash_desc *shash, int so_index) { struct vm_area_struct *vma = NULL; int rc = -EFAULT; struct get_code_info code_info; unsigned long in_size; struct page *ptr_page = NULL; void *ptr_base = NULL; if (find_lib_code_area(mm, &vma, so_index)) { tlogd("get lib code vma area failed\n"); return -EFAULT; } code_info.code_start = vma->vm_start; code_info.code_end = vma->vm_end; code_info.code_size = code_info.code_end - code_info.code_start; while (code_info.code_start < code_info.code_end) { // Get a handle of the page we want to read #if (KERNEL_VERSION(5, 10, 0) <= LINUX_VERSION_CODE) rc = get_user_pages_remote(mm, code_info.code_start, 1, FOLL_FORCE, &ptr_page, NULL, NULL); #else rc = get_user_pages_remote(cur_struct, mm, code_info.code_start, 1, FOLL_FORCE, &ptr_page, NULL, NULL); #endif if (rc != 1) { tloge("get user pages locked error[0x%x]\n", rc); rc = -EFAULT; break; } ptr_base = kmap_atomic(ptr_page); if (!ptr_base) { rc = -EFAULT; put_page(ptr_page); break; } in_size = (code_info.code_size > PAGE_SIZE) ? PAGE_SIZE : code_info.code_size; rc = crypto_shash_update(shash, ptr_base, in_size); if (rc) { kunmap_atomic(ptr_base); put_page(ptr_page); break; } kunmap_atomic(ptr_base); put_page(ptr_page); code_info.code_start += in_size; code_info.code_size = code_info.code_end - code_info.code_start; } return rc; } /* Calculate the SHA256 library digest */ static int calc_task_so_hash(unsigned char *digest, uint32_t dig_len, struct task_struct *cur_struct, int so_index) { struct mm_struct *mm = NULL; int rc; size_t size; size_t shash_size; struct sdesc *desc = NULL; if (!digest || dig_len != SHA256_DIGEST_LENTH) { tloge("tee hash: digest is NULL\n"); return -EFAULT; } shash_size = crypto_shash_descsize(get_shash_handle()); size = sizeof(desc->shash) + shash_size; if (size < sizeof(desc->shash) || size < shash_size) { tloge("size overflow\n"); return -ENOMEM; } desc = kzalloc(size, GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)desc)) { tloge("alloc desc failed\n"); return -ENOMEM; } desc->shash.tfm = get_shash_handle(); if (crypto_shash_init(&desc->shash)) { kfree(desc); return -EFAULT; } mm = get_task_mm(cur_struct); if (!mm) { tloge("so does not have mm struct\n"); if (memset_s(digest, MAX_SHA_256_SZ, 0, dig_len)) tloge("memset digest failed\n"); kfree(desc); return -EFAULT; } down_read(&mm_sem_lock(mm)); rc = update_so_hash(mm, cur_struct, &desc->shash, so_index); up_read(&mm_sem_lock(mm)); mmput(mm); if (!rc) rc = crypto_shash_final(&desc->shash, digest); kfree(desc); return rc; } static int proc_calc_hash(uint8_t kernel_api, struct tc_ns_session *session, struct task_struct *cur_struct, uint32_t pub_key_len) { int rc, i; int so_found = 0; mutex_crypto_hash_lock(); if (kernel_api == TEE_REQ_FROM_USER_MODE) { for (i = 0; so_found < NUM_OF_SO && i < KIND_OF_SO; i++) { rc = calc_task_so_hash(session->auth_hash_buf + MAX_SHA_256_SZ * so_found, (uint32_t)SHA256_DIGEST_LENTH, cur_struct, i); if (!rc) so_found++; } if (so_found != NUM_OF_SO) tlogd("so library found: %d\n", so_found); } else { tlogd("request from kernel\n"); } #ifdef CONFIG_ASAN_DEBUG tloge("so auth disabled for ASAN debug\n"); uint32_t so_hash_len = MAX_SHA_256_SZ * NUM_OF_SO; errno_t sret = memset_s(session->auth_hash_buf, so_hash_len, 0, so_hash_len); if (sret) { mutex_crypto_hash_unlock(); tloge("memset so hash failed\n"); return -EFAULT; } #endif rc = calc_task_hash(session->auth_hash_buf + MAX_SHA_256_SZ * NUM_OF_SO, (uint32_t)SHA256_DIGEST_LENTH, cur_struct, pub_key_len); if (rc) { mutex_crypto_hash_unlock(); tloge("tee calc ca hash failed\n"); return -EFAULT; } mutex_crypto_hash_unlock(); return EOK; } int calc_client_auth_hash(struct tc_ns_dev_file *dev_file, struct tc_ns_client_context *context, struct tc_ns_session *session) { int ret; struct task_struct *cur_struct = NULL; bool check = false; #if defined(CONFIG_ANDROID_HIDL) || defined(CONFIG_MDC_HAL_AUTH) bool is_hidl_srvc = false; #endif check = (!dev_file || !context || !session); if (check) { tloge("bad params\n"); return -EFAULT; } if (tee_init_shash_handle("sha256")) { tloge("init code hash error\n"); return -EFAULT; } #if defined(CONFIG_ANDROID_HIDL) || defined(CONFIG_MDC_HAL_AUTH) if(!current->mm) { tlogd("kernel thread need not check\n"); ret = ENTER_BYPASS_CHANNEL; } else { #ifdef CONFIG_CADAEMON_AUTH /* for OH */ ret = check_cadaemon_auth(); #else /* for HO and MDC/DC */ ret = check_hidl_auth(); #endif } if (ret != CHECK_ACCESS_SUCC) { if (ret != ENTER_BYPASS_CHANNEL) { tloge("hidl service may be exploited ret 0x%x\n", ret); return -EACCES; } /* native\kernel ca task this branch */ } else { /* android hidl\mdc secmgr(libteec\kms) task this branch */ is_hidl_srvc = true; } ret = get_hidl_client_task(is_hidl_srvc, context, &cur_struct); if (ret) return -EFAULT; #else cur_struct = current; #endif ret = proc_calc_hash(dev_file->kernel_api, session, cur_struct, dev_file->pub_key_len); #if defined(CONFIG_ANDROID_HIDL) || defined(CONFIG_MDC_HAL_AUTH) if (is_hidl_srvc) put_task_struct(cur_struct); #endif return ret; } #endif #ifdef CONFIG_AUTH_HASH #define UID_LEN 16 static int construct_hashdata(struct tc_ns_dev_file *dev_file, uint8_t *buf, uint32_t buf_len) { int ret; ret = memcpy_s(buf, buf_len, dev_file->pkg_name, dev_file->pkg_name_len); if (ret) { tloge("memcpy_s failed\n"); goto error; } buf += dev_file->pkg_name_len; buf_len -= dev_file->pkg_name_len; ret = memcpy_s(buf, buf_len, dev_file->pub_key, dev_file->pub_key_len); if (ret) { tloge("memcpy_s failed\n"); goto error; } return 0; error: return -EFAULT; } static struct sdesc_hash *init_sdesc(struct crypto_shash *alg) { struct sdesc_hash *sdesc; size_t size; size = sizeof(struct shash_desc) + crypto_shash_descsize(alg); sdesc = kmalloc(size, GFP_KERNEL); if (sdesc == NULL) return ERR_PTR(-ENOMEM); sdesc->shash.tfm = alg; return sdesc; } static int calc_hash(struct crypto_shash *alg, const unsigned char *data, unsigned int datalen, unsigned char *digest) { struct sdesc_hash *sdesc; int ret; sdesc = init_sdesc(alg); if (IS_ERR(sdesc)) { pr_info("can't alloc sdesc\n"); return PTR_ERR(sdesc); } ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest); kfree(sdesc); return ret; } static int do_sha256(const unsigned char *data, uint32_t datalen, unsigned char *out_digest, uint8_t digest_len) { int ret; struct crypto_shash *alg; const char *hash_alg_name = "sha256"; if (digest_len != SHA256_DIGEST_LENGTH) { tloge("error digest_len\n"); return -1; } alg = crypto_alloc_shash(hash_alg_name, 0, 0); if(IS_ERR_OR_NULL(alg)) { tloge("can't alloc alg %s, PTR_ERR alg is %ld\n", hash_alg_name, PTR_ERR(alg)); return PTR_ERR(alg); } ret = calc_hash(alg, data, datalen, out_digest); if (ret != 0) { tloge("calc hash failed\n"); crypto_free_shash(alg); alg = NULL; return -1; } crypto_free_shash(alg); alg = NULL; return 0; } int set_login_information_hash(struct tc_ns_dev_file *hash_dev_file) { int ret = 0; uint8_t *indata = NULL; if (hash_dev_file == NULL) { tloge("wrong caller info, cal hash stopped\n"); return -1; } mutex_lock(&hash_dev_file->cainfo_hash_setup_lock); if (!(hash_dev_file->cainfo_hash_setup)) { unsigned char digest[SHA256_DIGEST_LENGTH] = {0}; uint8_t digest_len = sizeof(digest); uint32_t indata_len; #ifdef CONFIG_AUTH_SUPPORT_UNAME /* username using fixed length to cal hash */ if (hash_dev_file->pub_key_len >= FIXED_PKG_NAME_LENGTH) { tloge("username is too loog\n"); ret = -1; goto error; } indata_len = hash_dev_file->pkg_name_len + FIXED_PKG_NAME_LENGTH; #else indata_len = hash_dev_file->pkg_name_len + hash_dev_file->pub_key_len; #endif indata = kzalloc(indata_len, GFP_KERNEL); if (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)indata)) { tloge("indata kmalloc fail\n"); ret = -1; goto error; } ret = construct_hashdata(hash_dev_file, indata, indata_len); if (ret != 0) { tloge("construct hashdata failed\n"); goto error; } ret = do_sha256((unsigned char *)indata, indata_len, digest, digest_len); if (ret != 0) { tloge("do sha256 failed\n"); goto error; } ret = memcpy_s(hash_dev_file->pkg_name, MAX_PACKAGE_NAME_LEN, digest, digest_len); if (ret != 0) { tloge("memcpy_s failed\n"); goto error; } hash_dev_file->pkg_name_len = SHA256_DIGEST_LENGTH; hash_dev_file->cainfo_hash_setup = true; } error: if (!ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)indata)) kfree(indata); mutex_unlock(&hash_dev_file->cainfo_hash_setup_lock); return ret; } #endif #ifdef CONFIG_AUTH_SUPPORT_UNAME #define PASSWD_FILE "/etc/passwd" #define UID_POS 2U #define DECIMAL 10 static int uid_compare(uint32_t uid, const char* uid_str, uint32_t uid_len) { uint32_t uid_num = 0; for (uint32_t i = 0; i < uid_len; i++) { bool is_number = uid_str[i] >= '0' && uid_str[i] <= '9'; if (!is_number) { tloge("passwd info wrong format: uid missing\n"); return -1; } uid_num = DECIMAL * uid_num + (uid_str[i] - '0'); } return (uid_num == uid) ? 0 : -1; } /* "username:[encrypted password]:uid:gid:[comments]:home directory:login shell" */ static uint32_t parse_uname(uint32_t uid, char *username, int buffer_len) { char *str = username; char *token = strsep(&str, ":"); char *temp_name = token; // first tokon is username, need to check uid int index = 0; while(token != NULL && index < UID_POS) { token = strsep(&str, ":"); index++; } if (token == NULL) return -1; if (uid_compare(uid, token, strlen(token)) != 0) return -1; if (strcpy_s(username, buffer_len, temp_name) != EOK) return -1; return strlen(temp_name); } static int read_line(char *buf, int buf_len, struct file *fp, loff_t *offset) { if (offset == NULL) { tloge("offset is null while read file\n"); return -1; } ssize_t ret = kernel_read(fp, buf, buf_len, offset); if (ret < 0) return -1; ssize_t i = 0; /* read buf_len, need to find first '\n' */ while (i < ret) { if (i >= buf_len) break; if (buf[i] == '\n') break; i++; } if (i < ret) *offset -= (loff_t)(ret - i); if (i < buf_len) buf[i] = '\0'; return 0; } /* get username by uid, * on linux, user info is stored in system file "/etc/passwd", * each line represents a user, fields are separated by ':', * formatted as such: "username:[encrypted password]:uid:gid:[comments]:home directory:login shell" */ int tc_ns_get_uname(uint32_t uid, char *username, int buffer_len, uint32_t *out_len) { if (username == NULL || out_len == NULL || buffer_len != FIXED_PKG_NAME_LENGTH) { tloge("params is null\n"); return -1; } struct file *f = NULL; loff_t offset = 0; f = filp_open(PASSWD_FILE, O_RDONLY, 0); if (IS_ERR(f)) { tloge("kernel open passwd file failed\n"); return -1; } while (read_line(username, buffer_len, f, &offset) == 0) { uint32_t ret = parse_uname(uid, username, buffer_len); if (ret >= 0) { *out_len = ret; filp_close(f, NULL); return 0; } } filp_close(f, NULL); return -1; } #endif