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