* client_hash_auth.c
*
* function for CA code hash auth
*
* Copyright (c) 2012-2022 Huawei Technologies Co., Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 <linux/string.h>
#include <linux/mutex.h>
#include <linux/types.h>
#include <linux/rwsem.h>
#ifdef CONFIG_AUTH_SUPPORT_UNAME
#include <linux/fs.h>
#endif
#ifdef CONFIG_CLIENT_AUTH
#include <linux/mm.h>
#include <linux/dcache.h>
#include <linux/mm_types.h>
#include <linux/highmem.h>
#include <linux/cred.h>
#include <linux/slab.h>
#include <linux/sched/mm.h>
#endif
#ifdef CONFIG_AUTH_HASH
#include <crypto/hash.h>
#endif
#include <securec.h>
#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
#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", "libteec.huawei.so"};
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) {
#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;
}
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;
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;
}
cur_struct = current;
ret = proc_calc_hash(dev_file->kernel_api, session, cur_struct, dev_file->pub_key_len);
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 (ZERO_OR_NULL_PTR((unsigned long)(uintptr_t)(sdesc)))
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
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;
}
static int32_t parse_uname(uint32_t uid, char *username, int buffer_len)
{
char *str = username;
char *token = strsep(&str, ":");
char *temp_name = token;
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;
while(buf[i++] != '\n' && i < ret);
if (i < ret)
*offset -= (loff_t)(ret - i);
if (i < buf_len)
buf[i] = '\0';
return 0;
}
* 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) {
int32_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