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