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