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