1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
4 */
5
6 #include <linux/fs.h>
7 #include <linux/slab.h>
8 #include <linux/spinlock.h>
9 #include <linux/types.h>
10 #include <linux/compat.h>
11 #include <linux/version.h>
12 #include "avc.h"
13 #include "objsec.h"
14 #include "code_sign_ioctl.h"
15 #include "code_sign_log.h"
16 #define MAX_SIGNING_LENGTH 2048
17
18 DEFINE_SPINLOCK(cert_chain_tree_lock);
19 struct rb_root cert_chain_tree = RB_ROOT;
20 struct rb_root dev_cert_chain_tree = RB_ROOT;
21
matched_cert_search(struct rb_root * root,const char * subject,const char * issuer)22 struct cert_source *matched_cert_search(struct rb_root *root, const char *subject, const char *issuer)
23 {
24 struct rb_node **cur_node = &(root->rb_node);
25
26 while (*cur_node) {
27 struct cert_source *cur_cert = container_of(*cur_node, struct cert_source, node);
28 int result = strcmp(subject, cur_cert->subject);
29
30 if (result < 0) {
31 cur_node = &((*cur_node)->rb_left);
32 } else if (result > 0) {
33 cur_node = &((*cur_node)->rb_right);
34 } else {
35 result = strcmp(issuer, cur_cert->issuer);
36 if (result < 0) {
37 cur_node = &((*cur_node)->rb_left);
38 } else if (result > 0) {
39 cur_node = &((*cur_node)->rb_right);
40 } else {
41 code_sign_log_info("cert found");
42 return cur_cert;
43 }
44 }
45 }
46 code_sign_log_error("cert not found");
47 return NULL;
48 }
49
cert_chain_search(struct rb_root * root,const char * subject,const char * issuer,bool has_locked)50 struct cert_source *cert_chain_search(struct rb_root *root, const char *subject, const char *issuer, bool has_locked)
51 {
52 if (has_locked)
53 return matched_cert_search(root, subject, issuer);
54 else {
55 spin_lock(&cert_chain_tree_lock);
56 struct cert_source *matched_cert = matched_cert_search(root, subject, issuer);
57 spin_unlock(&cert_chain_tree_lock);
58 return matched_cert;
59 }
60 }
61
find_match(const char * subject,const char * issuer,bool is_dev)62 struct cert_source *find_match(const char *subject, const char *issuer, bool is_dev)
63 {
64 if (is_dev)
65 return cert_chain_search(&dev_cert_chain_tree, subject, issuer, false);
66 else
67 return cert_chain_search(&cert_chain_tree, subject, issuer, false);
68 }
69
code_sign_check_caller(char * caller)70 int code_sign_check_caller(char *caller)
71 {
72 u32 sid = current_sid(), context_len;
73 char *context = NULL;
74 int rc;
75 #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
76 rc = security_sid_to_context(&selinux_state, sid, &context, &context_len);
77 #else
78 rc = security_sid_to_context(sid, &context, &context_len);
79 #endif
80 if (rc)
81 return -EINVAL;
82
83 code_sign_log_debug("sid=%d, context=%s", sid, context);
84 if (!strncmp(caller, context, strlen(caller)))
85 return 0;
86
87 return -EPERM;
88 }
89
cert_chain_insert(struct rb_root * root,struct cert_source * cert)90 int cert_chain_insert(struct rb_root *root, struct cert_source *cert)
91 {
92 int ret = code_sign_check_caller(KEY_ENABLE_CTX);
93 if (ret == -EINVAL) {
94 code_sign_log_error("load SELinux context failed");
95 return -EINVAL;
96 } else if (ret == -EPERM) {
97 // procs except key_enable are only allowed to insert developer_code
98 if (!(cert->path_type == RELEASE_DEVELOPER_CODE
99 || cert->path_type == DEBUG_DEVELOPER_CODE)) {
100 code_sign_log_error("no permission to insert code %d", cert->path_type);
101 return -EPERM;
102 }
103 }
104
105 spin_lock(&cert_chain_tree_lock);
106 struct rb_node **new = &(root->rb_node), *parent = NULL;
107
108 while (*new) {
109 struct cert_source *this = container_of(*new, struct cert_source, node);
110 int result = strcmp(cert->subject, this->subject);
111
112 parent = *new;
113 if (result < 0) {
114 new = &((*new)->rb_left);
115 } else if (result > 0) {
116 new = &((*new)->rb_right);
117 } else {
118 result = strcmp(cert->issuer, this->issuer);
119 if (result < 0) {
120 new = &((*new)->rb_left);
121 } else if (result > 0) {
122 new = &((*new)->rb_right);
123 } else {
124 this->cnt++;
125 code_sign_log_info("cert already exist in trust sources");
126 goto out;
127 }
128 }
129 }
130
131 // add new node
132 cert->cnt++;
133 rb_link_node(&cert->node, parent, new);
134 rb_insert_color(&cert->node, root);
135
136 code_sign_log_info("add trusted cert: subject = '%s', issuer = '%s', max_path_depth = %d",
137 cert->subject, cert->issuer, cert->max_path_depth);
138 out:
139 spin_unlock(&cert_chain_tree_lock);
140 return 0;
141 }
142
cert_chain_remove(struct rb_root * root,struct cert_source * cert)143 int cert_chain_remove(struct rb_root *root, struct cert_source *cert)
144 {
145 spin_lock(&cert_chain_tree_lock);
146 struct cert_source *matched_cert = cert_chain_search(root, cert->subject, cert->issuer, true);
147
148 int ret = 0;
149 if (!matched_cert) {
150 ret = -EINVAL;
151 goto out;
152 }
153
154 if (matched_cert->path_type == RELEASE_DEVELOPER_CODE
155 || matched_cert->path_type == DEBUG_DEVELOPER_CODE) {
156 --matched_cert->cnt;
157 if (matched_cert->cnt > 0)
158 goto out;
159 rb_erase(&matched_cert->node, root);
160 code_sign_log_info("remove trusted cert: subject = '%s', issuer = '%s', max_path_depth = %d",
161 cert->subject, cert->issuer, cert->max_path_depth);
162 goto out;
163 }
164
165 code_sign_log_error("can not remove cert type %x", cert->path_type);
166 ret = -EKEYREJECTED;
167 out:
168 spin_unlock(&cert_chain_tree_lock);
169 return ret;
170 }
171
code_sign_open(struct inode * inode,struct file * filp)172 int code_sign_open(struct inode *inode, struct file *filp)
173 {
174 return 0;
175 }
176
code_sign_release(struct inode * inode,struct file * filp)177 int code_sign_release(struct inode *inode, struct file *filp)
178 {
179 return 0;
180 }
181
code_sign_avc_has_perm(u16 tclass,u32 requested)182 int code_sign_avc_has_perm(u16 tclass, u32 requested)
183 {
184 struct av_decision avd;
185 u32 sid = current_sid();
186 int rc, rc2;
187 #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
188 rc = avc_has_perm_noaudit(&selinux_state, sid, sid, tclass, requested,
189 AVC_STRICT, &avd);
190 rc2 = avc_audit(&selinux_state, sid, sid, tclass, requested, &avd, rc,
191 NULL, AVC_STRICT);
192 #else
193 rc = avc_has_perm_noaudit(sid, sid, tclass, requested,
194 AVC_STRICT, &avd);
195 rc2 = avc_audit(sid, sid, tclass, requested, &avd, rc,
196 NULL);
197 #endif
198 if (rc2)
199 return rc2;
200
201 return rc;
202 }
203
parse_cert_source(unsigned long args,struct cert_source ** _source)204 int parse_cert_source(unsigned long args, struct cert_source **_source)
205 {
206 int ret = 0;
207 struct cert_source *source = kzalloc(sizeof(struct cert_source), GFP_KERNEL);
208
209 if (!source)
210 return -ENOMEM;
211
212 struct cert_chain_info info;
213
214 if (copy_from_user(&info, args, sizeof(struct cert_chain_info))) {
215 code_sign_log_error("cmd copy_from_user failed");
216 ret = -ENOMEM;
217 goto copy_source_failed;
218 }
219
220 if (info.path_len > CERT_CHAIN_PATH_LEN_MAX || info.issuer_length == 0 || info.signing_length == 0
221 || info.issuer_length > MAX_SIGNING_LENGTH || info.signing_length > MAX_SIGNING_LENGTH) {
222 code_sign_log_error("invalid path len or subject or issuer");
223 ret = -EINVAL;
224 goto copy_source_failed;
225 }
226
227 source->subject = kzalloc(info.signing_length + 1, GFP_KERNEL);
228 if (!source->subject) {
229 ret = -ENOMEM;
230 goto copy_source_failed;
231 }
232
233 if (copy_from_user(source->subject, u64_to_user_ptr(info.signing_ptr), info.signing_length)) {
234 code_sign_log_error("copy_from_user get signing failed");
235 ret = -EFAULT;
236 goto copy_subject_failed;
237 }
238
239 source->issuer = kzalloc(info.issuer_length + 1, GFP_KERNEL);
240 if (!source->issuer) {
241 ret = -ENOMEM;
242 goto copy_subject_failed;
243 }
244
245 ret = copy_from_user(source->issuer, u64_to_user_ptr(info.issuer_ptr), info.issuer_length);
246 if (ret) {
247 code_sign_log_error("copy_from_user get issuer failed");
248 ret = -EFAULT;
249 goto copy_issuer_failed;
250 }
251
252 source->max_path_depth = info.path_len;
253 source->path_type = info.cert_type;
254
255 *_source = source;
256 return ret;
257
258 copy_issuer_failed:
259 kfree(source->issuer);
260 copy_subject_failed:
261 kfree(source->subject);
262 copy_source_failed:
263 kfree(source);
264 return ret;
265 }
266
code_sign_check_code(int code)267 int code_sign_check_code(int code)
268 {
269 if (code > RELEASE_CODE_START && code < RELEASE_CODE_END)
270 return 0;
271
272 if (code > DEBUG_CODE_START && code < DEBUG_CODE_END)
273 return 1;
274
275 code_sign_log_error("cert type %x is invalid", code);
276 return -EINVAL;
277 }
278
code_sign_ioctl(struct file * filp,unsigned int cmd,unsigned long args)279 long code_sign_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
280 {
281 int ret = 0;
282 struct cert_source *source;
283
284 switch (cmd) {
285 case ADD_CERT_CHAIN:
286 if (code_sign_avc_has_perm(SECCLASS_CODE_SIGN, CODE_SIGN__ADD_CERT_CHAIN)) {
287 code_sign_log_error("selinux check failed, no permission to add cert chain");
288 return -EPERM;
289 }
290
291 ret = parse_cert_source(args, &source);
292 if (ret)
293 return ret;
294
295 // insert rb_tree
296 ret = code_sign_check_code(source->path_type);
297 if (ret < 0)
298 return ret;
299
300 if (ret == 1) {
301 // developer cert
302 code_sign_log_debug("add developer cert");
303 ret = cert_chain_insert(&dev_cert_chain_tree, source);
304 } else {
305 code_sign_log_debug("add release cert");
306 ret = cert_chain_insert(&cert_chain_tree, source);
307 }
308 break;
309 case REMOVE_CERT_CHAIN:
310 if (code_sign_avc_has_perm(SECCLASS_CODE_SIGN, CODE_SIGN__REMOVE_CERT_CHAIN)) {
311 code_sign_log_error("selinux check failed, no permission to remove cert chain");
312 return -EPERM;
313 }
314
315 ret = parse_cert_source(args, &source);
316 if (ret)
317 return ret;
318
319 // delete rb_tree
320 ret = code_sign_check_code(source->path_type);
321 if (ret < 0)
322 return ret;
323
324 if (ret == 1) {
325 // developer cert
326 code_sign_log_debug("remove developer cert");
327 ret = cert_chain_remove(&dev_cert_chain_tree, source);
328 } else {
329 code_sign_log_debug("remove release cert");
330 ret = cert_chain_remove(&cert_chain_tree, source);
331 }
332 if (ret) {
333 code_sign_log_error("remove cert failed.");
334 }
335 break;
336 default:
337 code_sign_log_error("code_sign cmd error, cmd: %d", cmd);
338 ret = -EINVAL;
339 break;
340 }
341
342 return ret;
343 }
344