mirror of
https://github.com/openharmony/kernel_linux_common_modules.git
synced 2026-08-27 01:31:36 -04:00
e27f2a753a
Signed-off-by: lihehe <lihehao@huawei.com> Change-Id: I458ca1e44ac3c9342435a9c785c34186c1c635d8
341 lines
8.5 KiB
C
341 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (c) 2023 Huawei Device Co., Ltd.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/types.h>
|
|
#include <linux/compat.h>
|
|
#include "avc.h"
|
|
#include "objsec.h"
|
|
#include "dsmm_developer.h"
|
|
#include "code_sign_ioctl.h"
|
|
#include "code_sign_log.h"
|
|
|
|
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;
|
|
|
|
rc = security_sid_to_context(&selinux_state, sid, &context, &context_len);
|
|
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;
|
|
|
|
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);
|
|
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) {
|
|
code_sign_log_error("invalid path len or subject or issuer");
|
|
ret = -EINVAL;
|
|
goto copy_source_failed;
|
|
}
|
|
|
|
source->subject = kzalloc(info.signing_length, 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, 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)
|
|
{
|
|
int is_dev_mode = 0;
|
|
|
|
if (code > RELEASE_CODE_START && code < RELEASE_CODE_END)
|
|
return is_dev_mode;
|
|
|
|
// developer mode
|
|
if (get_developer_mode_state() == STATE_ON) {
|
|
code_sign_log_debug("developer mode on");
|
|
is_dev_mode = 1;
|
|
}
|
|
|
|
if (is_dev_mode && (code > DEBUG_CODE_START && code < DEBUG_CODE_END))
|
|
return is_dev_mode;
|
|
|
|
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) {
|
|
// 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) {
|
|
// 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;
|
|
}
|