1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * AppArmor security module |
4 | * |
5 | * This file contains basic common functions used in AppArmor |
6 | * |
7 | * Copyright (C) 1998-2008 Novell/SUSE |
8 | * Copyright 2009-2010 Canonical Ltd. |
9 | */ |
10 | |
11 | #include <linux/ctype.h> |
12 | #include <linux/mm.h> |
13 | #include <linux/slab.h> |
14 | #include <linux/string.h> |
15 | #include <linux/vmalloc.h> |
16 | |
17 | #include "include/audit.h" |
18 | #include "include/apparmor.h" |
19 | #include "include/lib.h" |
20 | #include "include/perms.h" |
21 | #include "include/policy.h" |
22 | |
23 | struct aa_perms nullperms; |
24 | struct aa_perms allperms = { .allow = ALL_PERMS_MASK, |
25 | .quiet = ALL_PERMS_MASK, |
26 | .hide = ALL_PERMS_MASK }; |
27 | |
28 | /** |
29 | * aa_free_str_table - free entries str table |
30 | * @t: the string table to free (MAYBE NULL) |
31 | */ |
32 | void aa_free_str_table(struct aa_str_table *t) |
33 | { |
34 | int i; |
35 | |
36 | if (t) { |
37 | if (!t->table) |
38 | return; |
39 | |
40 | for (i = 0; i < t->size; i++) |
41 | kfree_sensitive(objp: t->table[i]); |
42 | kfree_sensitive(objp: t->table); |
43 | t->table = NULL; |
44 | } |
45 | } |
46 | |
47 | /** |
48 | * aa_split_fqname - split a fqname into a profile and namespace name |
49 | * @fqname: a full qualified name in namespace profile format (NOT NULL) |
50 | * @ns_name: pointer to portion of the string containing the ns name (NOT NULL) |
51 | * |
52 | * Returns: profile name or NULL if one is not specified |
53 | * |
54 | * Split a namespace name from a profile name (see policy.c for naming |
55 | * description). If a portion of the name is missing it returns NULL for |
56 | * that portion. |
57 | * |
58 | * NOTE: may modify the @fqname string. The pointers returned point |
59 | * into the @fqname string. |
60 | */ |
61 | char *aa_split_fqname(char *fqname, char **ns_name) |
62 | { |
63 | char *name = strim(fqname); |
64 | |
65 | *ns_name = NULL; |
66 | if (name[0] == ':') { |
67 | char *split = strchr(&name[1], ':'); |
68 | *ns_name = skip_spaces(&name[1]); |
69 | if (split) { |
70 | /* overwrite ':' with \0 */ |
71 | *split++ = 0; |
72 | if (strncmp(split, "//" , 2) == 0) |
73 | split += 2; |
74 | name = skip_spaces(split); |
75 | } else |
76 | /* a ns name without a following profile is allowed */ |
77 | name = NULL; |
78 | } |
79 | if (name && *name == 0) |
80 | name = NULL; |
81 | |
82 | return name; |
83 | } |
84 | |
85 | /** |
86 | * skipn_spaces - Removes leading whitespace from @str. |
87 | * @str: The string to be stripped. |
88 | * @n: length of str to parse, will stop at \0 if encountered before n |
89 | * |
90 | * Returns a pointer to the first non-whitespace character in @str. |
91 | * if all whitespace will return NULL |
92 | */ |
93 | |
94 | const char *skipn_spaces(const char *str, size_t n) |
95 | { |
96 | for (; n && isspace(*str); --n) |
97 | ++str; |
98 | if (n) |
99 | return (char *)str; |
100 | return NULL; |
101 | } |
102 | |
103 | const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, |
104 | size_t *ns_len) |
105 | { |
106 | const char *end = fqname + n; |
107 | const char *name = skipn_spaces(str: fqname, n); |
108 | |
109 | *ns_name = NULL; |
110 | *ns_len = 0; |
111 | |
112 | if (!name) |
113 | return NULL; |
114 | |
115 | if (name[0] == ':') { |
116 | char *split = strnchr(&name[1], end - &name[1], ':'); |
117 | *ns_name = skipn_spaces(str: &name[1], n: end - &name[1]); |
118 | if (!*ns_name) |
119 | return NULL; |
120 | if (split) { |
121 | *ns_len = split - *ns_name; |
122 | if (*ns_len == 0) |
123 | *ns_name = NULL; |
124 | split++; |
125 | if (end - split > 1 && strncmp(split, "//" , 2) == 0) |
126 | split += 2; |
127 | name = skipn_spaces(str: split, n: end - split); |
128 | } else { |
129 | /* a ns name without a following profile is allowed */ |
130 | name = NULL; |
131 | *ns_len = end - *ns_name; |
132 | } |
133 | } |
134 | if (name && *name == 0) |
135 | name = NULL; |
136 | |
137 | return name; |
138 | } |
139 | |
140 | /** |
141 | * aa_info_message - log a none profile related status message |
142 | * @str: message to log |
143 | */ |
144 | void aa_info_message(const char *str) |
145 | { |
146 | if (audit_enabled) { |
147 | DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); |
148 | |
149 | ad.info = str; |
150 | aa_audit_msg(type: AUDIT_APPARMOR_STATUS, ad: &ad, NULL); |
151 | } |
152 | printk(KERN_INFO "AppArmor: %s\n" , str); |
153 | } |
154 | |
155 | __counted char *aa_str_alloc(int size, gfp_t gfp) |
156 | { |
157 | struct counted_str *str; |
158 | |
159 | str = kmalloc(struct_size(str, name, size), flags: gfp); |
160 | if (!str) |
161 | return NULL; |
162 | |
163 | kref_init(kref: &str->count); |
164 | return str->name; |
165 | } |
166 | |
167 | void aa_str_kref(struct kref *kref) |
168 | { |
169 | kfree(container_of(kref, struct counted_str, count)); |
170 | } |
171 | |
172 | |
173 | const char aa_file_perm_chrs[] = "xwracd km l " ; |
174 | const char *aa_file_perm_names[] = { |
175 | "exec" , |
176 | "write" , |
177 | "read" , |
178 | "append" , |
179 | |
180 | "create" , |
181 | "delete" , |
182 | "open" , |
183 | "rename" , |
184 | |
185 | "setattr" , |
186 | "getattr" , |
187 | "setcred" , |
188 | "getcred" , |
189 | |
190 | "chmod" , |
191 | "chown" , |
192 | "chgrp" , |
193 | "lock" , |
194 | |
195 | "mmap" , |
196 | "mprot" , |
197 | "link" , |
198 | "snapshot" , |
199 | |
200 | "unknown" , |
201 | "unknown" , |
202 | "unknown" , |
203 | "unknown" , |
204 | |
205 | "unknown" , |
206 | "unknown" , |
207 | "unknown" , |
208 | "unknown" , |
209 | |
210 | "stack" , |
211 | "change_onexec" , |
212 | "change_profile" , |
213 | "change_hat" , |
214 | }; |
215 | |
216 | /** |
217 | * aa_perm_mask_to_str - convert a perm mask to its short string |
218 | * @str: character buffer to store string in (at least 10 characters) |
219 | * @str_size: size of the @str buffer |
220 | * @chrs: NUL-terminated character buffer of permission characters |
221 | * @mask: permission mask to convert |
222 | */ |
223 | void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs, u32 mask) |
224 | { |
225 | unsigned int i, perm = 1; |
226 | size_t num_chrs = strlen(chrs); |
227 | |
228 | for (i = 0; i < num_chrs; perm <<= 1, i++) { |
229 | if (mask & perm) { |
230 | /* Ensure that one byte is left for NUL-termination */ |
231 | if (WARN_ON_ONCE(str_size <= 1)) |
232 | break; |
233 | |
234 | *str++ = chrs[i]; |
235 | str_size--; |
236 | } |
237 | } |
238 | *str = '\0'; |
239 | } |
240 | |
241 | void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, |
242 | u32 mask) |
243 | { |
244 | const char *fmt = "%s" ; |
245 | unsigned int i, perm = 1; |
246 | bool prev = false; |
247 | |
248 | for (i = 0; i < 32; perm <<= 1, i++) { |
249 | if (mask & perm) { |
250 | audit_log_format(ab, fmt, names[i]); |
251 | if (!prev) { |
252 | prev = true; |
253 | fmt = " %s" ; |
254 | } |
255 | } |
256 | } |
257 | } |
258 | |
259 | void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, |
260 | u32 chrsmask, const char * const *names, u32 namesmask) |
261 | { |
262 | char str[33]; |
263 | |
264 | audit_log_format(ab, fmt: "\"" ); |
265 | if ((mask & chrsmask) && chrs) { |
266 | aa_perm_mask_to_str(str, str_size: sizeof(str), chrs, mask: mask & chrsmask); |
267 | mask &= ~chrsmask; |
268 | audit_log_format(ab, fmt: "%s" , str); |
269 | if (mask & namesmask) |
270 | audit_log_format(ab, fmt: " " ); |
271 | } |
272 | if ((mask & namesmask) && names) |
273 | aa_audit_perm_names(ab, names, mask: mask & namesmask); |
274 | audit_log_format(ab, fmt: "\"" ); |
275 | } |
276 | |
277 | /** |
278 | * aa_audit_perms_cb - generic callback fn for auditing perms |
279 | * @ab: audit buffer (NOT NULL) |
280 | * @va: audit struct to audit values of (NOT NULL) |
281 | */ |
282 | static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) |
283 | { |
284 | struct common_audit_data *sa = va; |
285 | struct apparmor_audit_data *ad = aad(sa); |
286 | |
287 | if (ad->request) { |
288 | audit_log_format(ab, fmt: " requested_mask=" ); |
289 | aa_audit_perm_mask(ab, mask: ad->request, chrs: aa_file_perm_chrs, |
290 | PERMS_CHRS_MASK, names: aa_file_perm_names, |
291 | PERMS_NAMES_MASK); |
292 | } |
293 | if (ad->denied) { |
294 | audit_log_format(ab, fmt: "denied_mask=" ); |
295 | aa_audit_perm_mask(ab, mask: ad->denied, chrs: aa_file_perm_chrs, |
296 | PERMS_CHRS_MASK, names: aa_file_perm_names, |
297 | PERMS_NAMES_MASK); |
298 | } |
299 | audit_log_format(ab, fmt: " peer=" ); |
300 | aa_label_xaudit(ab, labels_ns(ad->subj_label), label: ad->peer, |
301 | FLAGS_NONE, GFP_ATOMIC); |
302 | } |
303 | |
304 | /** |
305 | * aa_apply_modes_to_perms - apply namespace and profile flags to perms |
306 | * @profile: that perms where computed from |
307 | * @perms: perms to apply mode modifiers to |
308 | * |
309 | * TODO: split into profile and ns based flags for when accumulating perms |
310 | */ |
311 | void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) |
312 | { |
313 | switch (AUDIT_MODE(profile)) { |
314 | case AUDIT_ALL: |
315 | perms->audit = ALL_PERMS_MASK; |
316 | fallthrough; |
317 | case AUDIT_NOQUIET: |
318 | perms->quiet = 0; |
319 | break; |
320 | case AUDIT_QUIET: |
321 | perms->audit = 0; |
322 | fallthrough; |
323 | case AUDIT_QUIET_DENIED: |
324 | perms->quiet = ALL_PERMS_MASK; |
325 | break; |
326 | } |
327 | |
328 | if (KILL_MODE(profile)) |
329 | perms->kill = ALL_PERMS_MASK; |
330 | else if (COMPLAIN_MODE(profile)) |
331 | perms->complain = ALL_PERMS_MASK; |
332 | else if (USER_MODE(profile)) |
333 | perms->prompt = ALL_PERMS_MASK; |
334 | } |
335 | |
336 | void aa_profile_match_label(struct aa_profile *profile, |
337 | struct aa_ruleset *rules, |
338 | struct aa_label *label, |
339 | int type, u32 request, struct aa_perms *perms) |
340 | { |
341 | /* TODO: doesn't yet handle extended types */ |
342 | aa_state_t state; |
343 | |
344 | state = aa_dfa_next(dfa: rules->policy->dfa, |
345 | state: rules->policy->start[AA_CLASS_LABEL], |
346 | c: type); |
347 | aa_label_match(profile, rules, label, state, subns: false, request, perms); |
348 | } |
349 | |
350 | |
351 | /* currently unused */ |
352 | int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, |
353 | u32 request, int type, u32 *deny, |
354 | struct apparmor_audit_data *ad) |
355 | { |
356 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
357 | typeof(*rules), list); |
358 | struct aa_perms perms; |
359 | |
360 | ad->peer = &target->label; |
361 | ad->request = request; |
362 | |
363 | aa_profile_match_label(profile, rules, label: &target->label, type, request, |
364 | perms: &perms); |
365 | aa_apply_modes_to_perms(profile, perms: &perms); |
366 | *deny |= request & perms.deny; |
367 | return aa_check_perms(profile, perms: &perms, request, ad, cb: aa_audit_perms_cb); |
368 | } |
369 | |
370 | /** |
371 | * aa_check_perms - do audit mode selection based on perms set |
372 | * @profile: profile being checked |
373 | * @perms: perms computed for the request |
374 | * @request: requested perms |
375 | * @ad: initialized audit structure (MAY BE NULL if not auditing) |
376 | * @cb: callback fn for type specific fields (MAY BE NULL) |
377 | * |
378 | * Returns: 0 if permission else error code |
379 | * |
380 | * Note: profile audit modes need to be set before calling by setting the |
381 | * perm masks appropriately. |
382 | * |
383 | * If not auditing then complain mode is not enabled and the |
384 | * error code will indicate whether there was an explicit deny |
385 | * with a positive value. |
386 | */ |
387 | int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, |
388 | u32 request, struct apparmor_audit_data *ad, |
389 | void (*cb)(struct audit_buffer *, void *)) |
390 | { |
391 | int type, error; |
392 | u32 denied = request & (~perms->allow | perms->deny); |
393 | |
394 | if (likely(!denied)) { |
395 | /* mask off perms that are not being force audited */ |
396 | request &= perms->audit; |
397 | if (!request || !ad) |
398 | return 0; |
399 | |
400 | type = AUDIT_APPARMOR_AUDIT; |
401 | error = 0; |
402 | } else { |
403 | error = -EACCES; |
404 | |
405 | if (denied & perms->kill) |
406 | type = AUDIT_APPARMOR_KILL; |
407 | else if (denied == (denied & perms->complain)) |
408 | type = AUDIT_APPARMOR_ALLOWED; |
409 | else |
410 | type = AUDIT_APPARMOR_DENIED; |
411 | |
412 | if (denied == (denied & perms->hide)) |
413 | error = -ENOENT; |
414 | |
415 | denied &= ~perms->quiet; |
416 | if (!ad || !denied) |
417 | return error; |
418 | } |
419 | |
420 | if (ad) { |
421 | ad->subj_label = &profile->label; |
422 | ad->request = request; |
423 | ad->denied = denied; |
424 | ad->error = error; |
425 | aa_audit_msg(type, ad, cb); |
426 | } |
427 | |
428 | if (type == AUDIT_APPARMOR_ALLOWED) |
429 | error = 0; |
430 | |
431 | return error; |
432 | } |
433 | |
434 | |
435 | /** |
436 | * aa_policy_init - initialize a policy structure |
437 | * @policy: policy to initialize (NOT NULL) |
438 | * @prefix: prefix name if any is required. (MAYBE NULL) |
439 | * @name: name of the policy, init will make a copy of it (NOT NULL) |
440 | * @gfp: allocation mode |
441 | * |
442 | * Note: this fn creates a copy of strings passed in |
443 | * |
444 | * Returns: true if policy init successful |
445 | */ |
446 | bool aa_policy_init(struct aa_policy *policy, const char *prefix, |
447 | const char *name, gfp_t gfp) |
448 | { |
449 | char *hname; |
450 | |
451 | /* freed by policy_free */ |
452 | if (prefix) { |
453 | hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); |
454 | if (hname) |
455 | sprintf(buf: hname, fmt: "%s//%s" , prefix, name); |
456 | } else { |
457 | hname = aa_str_alloc(strlen(name) + 1, gfp); |
458 | if (hname) |
459 | strcpy(p: hname, q: name); |
460 | } |
461 | if (!hname) |
462 | return false; |
463 | policy->hname = hname; |
464 | /* base.name is a substring of fqname */ |
465 | policy->name = basename(hname: policy->hname); |
466 | INIT_LIST_HEAD(list: &policy->list); |
467 | INIT_LIST_HEAD(list: &policy->profiles); |
468 | |
469 | return true; |
470 | } |
471 | |
472 | /** |
473 | * aa_policy_destroy - free the elements referenced by @policy |
474 | * @policy: policy that is to have its elements freed (NOT NULL) |
475 | */ |
476 | void aa_policy_destroy(struct aa_policy *policy) |
477 | { |
478 | AA_BUG(on_list_rcu(&policy->profiles)); |
479 | AA_BUG(on_list_rcu(&policy->list)); |
480 | |
481 | /* don't free name as its a subset of hname */ |
482 | aa_put_str(str: policy->hname); |
483 | } |
484 | |