// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (c) 2023-2024 Huawei Device Co., Ltd. */ #include #include #include #include #include #include #include "avc.h" #include "objsec.h" #include "code_sign_ioctl.h" #include "code_sign_log.h" #define MAX_SIGNING_LENGTH 2048 DEFINE_SPINLOCK(cert_chain_tree_lock); struct rb_root cert_chain_tree = RB_ROOT; struct rb_root dev_cert_chain_tree = RB_ROOT; struct cert_source *matched_cert_search(struct rb_root *root, const char *subject, const char *issuer) { struct rb_node **cur_node = &(root->rb_node); while (*cur_node) { struct cert_source *cur_cert = container_of(*cur_node, struct cert_source, node); int result = strcmp(subject, cur_cert->subject); if (result < 0) { cur_node = &((*cur_node)->rb_left); } else if (result > 0) { cur_node = &((*cur_node)->rb_right); } else { result = strcmp(issuer, cur_cert->issuer); if (result < 0) { cur_node = &((*cur_node)->rb_left); } else if (result > 0) { cur_node = &((*cur_node)->rb_right); } else { code_sign_log_info("cert found"); return cur_cert; } } } code_sign_log_error("cert not found"); return NULL; } struct cert_source *cert_chain_search(struct rb_root *root, const char *subject, const char *issuer, bool has_locked) { if (has_locked) return matched_cert_search(root, subject, issuer); else { spin_lock(&cert_chain_tree_lock); struct cert_source *matched_cert = matched_cert_search(root, subject, issuer); spin_unlock(&cert_chain_tree_lock); return matched_cert; } } struct cert_source *find_match(const char *subject, const char *issuer, bool is_dev) { if (is_dev) return cert_chain_search(&dev_cert_chain_tree, subject, issuer, false); else return cert_chain_search(&cert_chain_tree, subject, issuer, false); } int code_sign_check_caller(char *caller) { u32 sid = current_sid(), context_len; char *context = NULL; int rc; #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) rc = security_sid_to_context(&selinux_state, sid, &context, &context_len); #else rc = security_sid_to_context(sid, &context, &context_len); #endif if (rc) return -EINVAL; code_sign_log_debug("sid=%d, context=%s", sid, context); if (!strncmp(caller, context, strlen(caller))) return 0; return -EPERM; } int cert_chain_insert(struct rb_root *root, struct cert_source *cert) { int ret = code_sign_check_caller(KEY_ENABLE_CTX); if (ret == -EINVAL) { code_sign_log_error("load SELinux context failed"); return -EINVAL; } else if (ret == -EPERM) { // procs except key_enable are only allowed to insert developer_code if (!(cert->path_type == RELEASE_DEVELOPER_CODE || cert->path_type == DEBUG_DEVELOPER_CODE)) { code_sign_log_error("no permission to insert code %d", cert->path_type); return -EPERM; } } spin_lock(&cert_chain_tree_lock); struct rb_node **new = &(root->rb_node), *parent = NULL; while (*new) { struct cert_source *this = container_of(*new, struct cert_source, node); int result = strcmp(cert->subject, this->subject); parent = *new; if (result < 0) { new = &((*new)->rb_left); } else if (result > 0) { new = &((*new)->rb_right); } else { result = strcmp(cert->issuer, this->issuer); if (result < 0) { new = &((*new)->rb_left); } else if (result > 0) { new = &((*new)->rb_right); } else { this->cnt++; code_sign_log_info("cert already exist in trust sources"); goto out; } } } // add new node cert->cnt++; rb_link_node(&cert->node, parent, new); rb_insert_color(&cert->node, root); code_sign_log_info("add trusted cert: subject = '%s', issuer = '%s', max_path_depth = %d", cert->subject, cert->issuer, cert->max_path_depth); out: spin_unlock(&cert_chain_tree_lock); return 0; } int cert_chain_remove(struct rb_root *root, struct cert_source *cert) { spin_lock(&cert_chain_tree_lock); struct cert_source *matched_cert = cert_chain_search(root, cert->subject, cert->issuer, true); int ret = 0; if (!matched_cert) { ret = -EINVAL; goto out; } if (matched_cert->path_type == RELEASE_DEVELOPER_CODE || matched_cert->path_type == DEBUG_DEVELOPER_CODE) { --matched_cert->cnt; if (matched_cert->cnt > 0) goto out; rb_erase(&matched_cert->node, root); code_sign_log_info("remove trusted cert: subject = '%s', issuer = '%s', max_path_depth = %d", cert->subject, cert->issuer, cert->max_path_depth); goto out; } code_sign_log_error("can not remove cert type %x", cert->path_type); ret = -EKEYREJECTED; out: spin_unlock(&cert_chain_tree_lock); return ret; } int code_sign_open(struct inode *inode, struct file *filp) { return 0; } int code_sign_release(struct inode *inode, struct file *filp) { return 0; } int code_sign_avc_has_perm(u16 tclass, u32 requested) { struct av_decision avd; u32 sid = current_sid(); int rc, rc2; #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) rc = avc_has_perm_noaudit(&selinux_state, sid, sid, tclass, requested, AVC_STRICT, &avd); rc2 = avc_audit(&selinux_state, sid, sid, tclass, requested, &avd, rc, NULL, AVC_STRICT); #else rc = avc_has_perm_noaudit(sid, sid, tclass, requested, AVC_STRICT, &avd); rc2 = avc_audit(sid, sid, tclass, requested, &avd, rc, NULL); #endif if (rc2) return rc2; return rc; } int parse_cert_source(unsigned long args, struct cert_source **_source) { int ret = 0; struct cert_source *source = kzalloc(sizeof(struct cert_source), GFP_KERNEL); if (!source) return -ENOMEM; struct cert_chain_info info; if (copy_from_user(&info, args, sizeof(struct cert_chain_info))) { code_sign_log_error("cmd copy_from_user failed"); ret = -ENOMEM; goto copy_source_failed; } if (info.path_len > CERT_CHAIN_PATH_LEN_MAX || info.issuer_length == 0 || info.signing_length == 0 || info.issuer_length > MAX_SIGNING_LENGTH || info.signing_length > MAX_SIGNING_LENGTH) { code_sign_log_error("invalid path len or subject or issuer"); ret = -EINVAL; goto copy_source_failed; } source->subject = kzalloc(info.signing_length + 1, GFP_KERNEL); if (!source->subject) { ret = -ENOMEM; goto copy_source_failed; } if (copy_from_user(source->subject, u64_to_user_ptr(info.signing_ptr), info.signing_length)) { code_sign_log_error("copy_from_user get signing failed"); ret = -EFAULT; goto copy_subject_failed; } source->issuer = kzalloc(info.issuer_length + 1, GFP_KERNEL); if (!source->issuer) { ret = -ENOMEM; goto copy_subject_failed; } ret = copy_from_user(source->issuer, u64_to_user_ptr(info.issuer_ptr), info.issuer_length); if (ret) { code_sign_log_error("copy_from_user get issuer failed"); ret = -EFAULT; goto copy_issuer_failed; } source->max_path_depth = info.path_len; source->path_type = info.cert_type; *_source = source; return ret; copy_issuer_failed: kfree(source->issuer); copy_subject_failed: kfree(source->subject); copy_source_failed: kfree(source); return ret; } int code_sign_check_code(int code) { if (code > RELEASE_CODE_START && code < RELEASE_CODE_END) return 0; if (code > DEBUG_CODE_START && code < DEBUG_CODE_END) return 1; code_sign_log_error("cert type %x is invalid", code); return -EINVAL; } long code_sign_ioctl(struct file *filp, unsigned int cmd, unsigned long args) { int ret = 0; struct cert_source *source; switch (cmd) { case ADD_CERT_CHAIN: if (code_sign_avc_has_perm(SECCLASS_CODE_SIGN, CODE_SIGN__ADD_CERT_CHAIN)) { code_sign_log_error("selinux check failed, no permission to add cert chain"); return -EPERM; } ret = parse_cert_source(args, &source); if (ret) return ret; // insert rb_tree ret = code_sign_check_code(source->path_type); if (ret < 0) return ret; if (ret == 1) { // developer cert code_sign_log_debug("add developer cert"); ret = cert_chain_insert(&dev_cert_chain_tree, source); } else { code_sign_log_debug("add release cert"); ret = cert_chain_insert(&cert_chain_tree, source); } break; case REMOVE_CERT_CHAIN: if (code_sign_avc_has_perm(SECCLASS_CODE_SIGN, CODE_SIGN__REMOVE_CERT_CHAIN)) { code_sign_log_error("selinux check failed, no permission to remove cert chain"); return -EPERM; } ret = parse_cert_source(args, &source); if (ret) return ret; // delete rb_tree ret = code_sign_check_code(source->path_type); if (ret < 0) return ret; if (ret == 1) { // developer cert code_sign_log_debug("remove developer cert"); ret = cert_chain_remove(&dev_cert_chain_tree, source); } else { code_sign_log_debug("remove release cert"); ret = cert_chain_remove(&cert_chain_tree, source); } if (ret) { code_sign_log_error("remove cert failed."); } break; default: code_sign_log_error("code_sign cmd error, cmd: %d", cmd); ret = -EINVAL; break; } return ret; }