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 | ||
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) { | |
ef88a7ac JJ |
129 | DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); |
130 | ||
131 | aad(&sa)->info = str; | |
cdff2642 JJ |
132 | aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); |
133 | } | |
134 | printk(KERN_INFO "AppArmor: %s\n", str); | |
135 | } | |
136 | ||
a1bd627b JJ |
137 | __counted char *aa_str_alloc(int size, gfp_t gfp) |
138 | { | |
139 | struct counted_str *str; | |
140 | ||
141 | str = kmalloc(sizeof(struct counted_str) + size, gfp); | |
142 | if (!str) | |
143 | return NULL; | |
144 | ||
145 | kref_init(&str->count); | |
146 | return str->name; | |
147 | } | |
148 | ||
149 | void aa_str_kref(struct kref *kref) | |
150 | { | |
151 | kfree(container_of(kref, struct counted_str, count)); | |
152 | } | |
153 | ||
154 | ||
e53cfe6c JJ |
155 | const char aa_file_perm_chrs[] = "xwracd km l "; |
156 | const char *aa_file_perm_names[] = { | |
157 | "exec", | |
158 | "write", | |
159 | "read", | |
160 | "append", | |
161 | ||
162 | "create", | |
163 | "delete", | |
164 | "open", | |
165 | "rename", | |
166 | ||
167 | "setattr", | |
168 | "getattr", | |
169 | "setcred", | |
170 | "getcred", | |
171 | ||
172 | "chmod", | |
173 | "chown", | |
174 | "chgrp", | |
175 | "lock", | |
176 | ||
177 | "mmap", | |
178 | "mprot", | |
179 | "link", | |
180 | "snapshot", | |
181 | ||
182 | "unknown", | |
183 | "unknown", | |
184 | "unknown", | |
185 | "unknown", | |
186 | ||
187 | "unknown", | |
188 | "unknown", | |
189 | "unknown", | |
190 | "unknown", | |
191 | ||
192 | "stack", | |
193 | "change_onexec", | |
194 | "change_profile", | |
195 | "change_hat", | |
196 | }; | |
197 | ||
198 | /** | |
199 | * aa_perm_mask_to_str - convert a perm mask to its short string | |
200 | * @str: character buffer to store string in (at least 10 characters) | |
201 | * @mask: permission mask to convert | |
202 | */ | |
203 | void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) | |
204 | { | |
205 | unsigned int i, perm = 1; | |
206 | ||
207 | for (i = 0; i < 32; perm <<= 1, i++) { | |
208 | if (mask & perm) | |
209 | *str++ = chrs[i]; | |
210 | } | |
211 | *str = '\0'; | |
212 | } | |
213 | ||
aa9aeea8 JJ |
214 | void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask) |
215 | { | |
216 | const char *fmt = "%s"; | |
217 | unsigned int i, perm = 1; | |
218 | bool prev = false; | |
219 | ||
220 | for (i = 0; i < 32; perm <<= 1, i++) { | |
221 | if (mask & perm) { | |
222 | audit_log_format(ab, fmt, names[i]); | |
223 | if (!prev) { | |
224 | prev = true; | |
225 | fmt = " %s"; | |
226 | } | |
227 | } | |
228 | } | |
229 | } | |
230 | ||
231 | void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, | |
232 | u32 chrsmask, const char **names, u32 namesmask) | |
233 | { | |
234 | char str[33]; | |
235 | ||
236 | audit_log_format(ab, "\""); | |
237 | if ((mask & chrsmask) && chrs) { | |
238 | aa_perm_mask_to_str(str, chrs, mask & chrsmask); | |
239 | mask &= ~chrsmask; | |
240 | audit_log_format(ab, "%s", str); | |
241 | if (mask & namesmask) | |
242 | audit_log_format(ab, " "); | |
243 | } | |
244 | if ((mask & namesmask) && names) | |
245 | aa_audit_perm_names(ab, names, mask & namesmask); | |
246 | audit_log_format(ab, "\""); | |
247 | } | |
248 | ||
637f688d JJ |
249 | /** |
250 | * aa_audit_perms_cb - generic callback fn for auditing perms | |
251 | * @ab: audit buffer (NOT NULL) | |
252 | * @va: audit struct to audit values of (NOT NULL) | |
253 | */ | |
254 | static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) | |
255 | { | |
256 | struct common_audit_data *sa = va; | |
257 | ||
258 | if (aad(sa)->request) { | |
259 | audit_log_format(ab, " requested_mask="); | |
260 | aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs, | |
261 | PERMS_CHRS_MASK, aa_file_perm_names, | |
262 | PERMS_NAMES_MASK); | |
263 | } | |
264 | if (aad(sa)->denied) { | |
265 | audit_log_format(ab, "denied_mask="); | |
266 | aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs, | |
267 | PERMS_CHRS_MASK, aa_file_perm_names, | |
268 | PERMS_NAMES_MASK); | |
269 | } | |
270 | audit_log_format(ab, " peer="); | |
271 | aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, | |
272 | FLAGS_NONE, GFP_ATOMIC); | |
273 | } | |
274 | ||
aa9aeea8 JJ |
275 | /** |
276 | * aa_apply_modes_to_perms - apply namespace and profile flags to perms | |
277 | * @profile: that perms where computed from | |
278 | * @perms: perms to apply mode modifiers to | |
279 | * | |
280 | * TODO: split into profile and ns based flags for when accumulating perms | |
281 | */ | |
282 | void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) | |
283 | { | |
284 | switch (AUDIT_MODE(profile)) { | |
285 | case AUDIT_ALL: | |
286 | perms->audit = ALL_PERMS_MASK; | |
287 | /* fall through */ | |
288 | case AUDIT_NOQUIET: | |
289 | perms->quiet = 0; | |
290 | break; | |
291 | case AUDIT_QUIET: | |
292 | perms->audit = 0; | |
293 | /* fall through */ | |
294 | case AUDIT_QUIET_DENIED: | |
295 | perms->quiet = ALL_PERMS_MASK; | |
296 | break; | |
297 | } | |
298 | ||
299 | if (KILL_MODE(profile)) | |
300 | perms->kill = ALL_PERMS_MASK; | |
301 | else if (COMPLAIN_MODE(profile)) | |
302 | perms->complain = ALL_PERMS_MASK; | |
303 | /* | |
304 | * TODO: | |
305 | * else if (PROMPT_MODE(profile)) | |
306 | * perms->prompt = ALL_PERMS_MASK; | |
307 | */ | |
308 | } | |
309 | ||
310 | static u32 map_other(u32 x) | |
311 | { | |
312 | return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ | |
313 | ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ | |
314 | ((x & 0x60) << 19); /* SETOPT/GETOPT */ | |
315 | } | |
316 | ||
317 | void aa_compute_perms(struct aa_dfa *dfa, unsigned int state, | |
318 | struct aa_perms *perms) | |
319 | { | |
320 | perms->deny = 0; | |
321 | perms->kill = perms->stop = 0; | |
322 | perms->complain = perms->cond = 0; | |
323 | perms->hide = 0; | |
324 | perms->prompt = 0; | |
325 | perms->allow = dfa_user_allow(dfa, state); | |
326 | perms->audit = dfa_user_audit(dfa, state); | |
327 | perms->quiet = dfa_user_quiet(dfa, state); | |
328 | ||
329 | /* for v5 perm mapping in the policydb, the other set is used | |
330 | * to extend the general perm set | |
331 | */ | |
332 | perms->allow |= map_other(dfa_other_allow(dfa, state)); | |
333 | perms->audit |= map_other(dfa_other_audit(dfa, state)); | |
334 | perms->quiet |= map_other(dfa_other_quiet(dfa, state)); | |
335 | // perms->xindex = dfa_user_xindex(dfa, state); | |
336 | } | |
337 | ||
637f688d JJ |
338 | /** |
339 | * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms | |
340 | * @accum - perms struct to accumulate into | |
341 | * @addend - perms struct to add to @accum | |
342 | */ | |
343 | void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) | |
344 | { | |
345 | accum->deny |= addend->deny; | |
346 | accum->allow &= addend->allow & ~addend->deny; | |
347 | accum->audit |= addend->audit & addend->allow; | |
348 | accum->quiet &= addend->quiet & ~addend->allow; | |
349 | accum->kill |= addend->kill & ~addend->allow; | |
350 | accum->stop |= addend->stop & ~addend->allow; | |
351 | accum->complain |= addend->complain & ~addend->allow & ~addend->deny; | |
352 | accum->cond |= addend->cond & ~addend->allow & ~addend->deny; | |
353 | accum->hide &= addend->hide & ~addend->allow; | |
354 | accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny; | |
355 | } | |
356 | ||
357 | /** | |
358 | * aa_perms_accum - accumulate perms, masking off overlapping perms | |
359 | * @accum - perms struct to accumulate into | |
360 | * @addend - perms struct to add to @accum | |
361 | */ | |
362 | void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) | |
363 | { | |
364 | accum->deny |= addend->deny; | |
365 | accum->allow &= addend->allow & ~accum->deny; | |
366 | accum->audit |= addend->audit & accum->allow; | |
367 | accum->quiet &= addend->quiet & ~accum->allow; | |
368 | accum->kill |= addend->kill & ~accum->allow; | |
369 | accum->stop |= addend->stop & ~accum->allow; | |
370 | accum->complain |= addend->complain & ~accum->allow & ~accum->deny; | |
371 | accum->cond |= addend->cond & ~accum->allow & ~accum->deny; | |
372 | accum->hide &= addend->hide & ~accum->allow; | |
373 | accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny; | |
374 | } | |
375 | ||
376 | void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label, | |
377 | int type, u32 request, struct aa_perms *perms) | |
378 | { | |
379 | /* TODO: doesn't yet handle extended types */ | |
380 | unsigned int state; | |
381 | ||
382 | state = aa_dfa_next(profile->policy.dfa, | |
383 | profile->policy.start[AA_CLASS_LABEL], | |
384 | type); | |
385 | aa_label_match(profile, label, state, false, request, perms); | |
386 | } | |
387 | ||
388 | ||
389 | /* currently unused */ | |
390 | int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, | |
391 | u32 request, int type, u32 *deny, | |
392 | struct common_audit_data *sa) | |
393 | { | |
394 | struct aa_perms perms; | |
395 | ||
396 | aad(sa)->label = &profile->label; | |
397 | aad(sa)->peer = &target->label; | |
398 | aad(sa)->request = request; | |
399 | ||
400 | aa_profile_match_label(profile, &target->label, type, request, &perms); | |
401 | aa_apply_modes_to_perms(profile, &perms); | |
402 | *deny |= request & perms.deny; | |
403 | return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb); | |
404 | } | |
405 | ||
406 | /** | |
407 | * aa_check_perms - do audit mode selection based on perms set | |
408 | * @profile: profile being checked | |
409 | * @perms: perms computed for the request | |
410 | * @request: requested perms | |
411 | * @deny: Returns: explicit deny set | |
412 | * @sa: initialized audit structure (MAY BE NULL if not auditing) | |
413 | * @cb: callback fn for tpye specific fields (MAY BE NULL) | |
414 | * | |
415 | * Returns: 0 if permission else error code | |
416 | * | |
417 | * Note: profile audit modes need to be set before calling by setting the | |
418 | * perm masks appropriately. | |
419 | * | |
420 | * If not auditing then complain mode is not enabled and the | |
421 | * error code will indicate whether there was an explicit deny | |
422 | * with a positive value. | |
423 | */ | |
424 | int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, | |
425 | u32 request, struct common_audit_data *sa, | |
426 | void (*cb)(struct audit_buffer *, void *)) | |
427 | { | |
428 | int type, error; | |
429 | bool stop = false; | |
430 | u32 denied = request & (~perms->allow | perms->deny); | |
431 | ||
432 | if (likely(!denied)) { | |
433 | /* mask off perms that are not being force audited */ | |
434 | request &= perms->audit; | |
435 | if (!request || !sa) | |
436 | return 0; | |
437 | ||
438 | type = AUDIT_APPARMOR_AUDIT; | |
439 | error = 0; | |
440 | } else { | |
441 | error = -EACCES; | |
442 | ||
443 | if (denied & perms->kill) | |
444 | type = AUDIT_APPARMOR_KILL; | |
445 | else if (denied == (denied & perms->complain)) | |
446 | type = AUDIT_APPARMOR_ALLOWED; | |
447 | else | |
448 | type = AUDIT_APPARMOR_DENIED; | |
449 | ||
450 | if (denied & perms->stop) | |
451 | stop = true; | |
452 | if (denied == (denied & perms->hide)) | |
453 | error = -ENOENT; | |
454 | ||
455 | denied &= ~perms->quiet; | |
456 | if (!sa || !denied) | |
457 | return error; | |
458 | } | |
459 | ||
460 | if (sa) { | |
461 | aad(sa)->label = &profile->label; | |
462 | aad(sa)->request = request; | |
463 | aad(sa)->denied = denied; | |
464 | aad(sa)->error = error; | |
465 | aa_audit_msg(type, sa, cb); | |
466 | } | |
467 | ||
468 | if (type == AUDIT_APPARMOR_ALLOWED) | |
469 | error = 0; | |
470 | ||
471 | return error; | |
472 | } | |
473 | ||
474 | ||
fe6bb31f JJ |
475 | /** |
476 | * aa_policy_init - initialize a policy structure | |
477 | * @policy: policy to initialize (NOT NULL) | |
478 | * @prefix: prefix name if any is required. (MAYBE NULL) | |
479 | * @name: name of the policy, init will make a copy of it (NOT NULL) | |
a1bd627b | 480 | * @gfp: allocation mode |
fe6bb31f JJ |
481 | * |
482 | * Note: this fn creates a copy of strings passed in | |
483 | * | |
484 | * Returns: true if policy init successful | |
485 | */ | |
486 | bool aa_policy_init(struct aa_policy *policy, const char *prefix, | |
d102d895 | 487 | const char *name, gfp_t gfp) |
fe6bb31f | 488 | { |
a1bd627b JJ |
489 | char *hname; |
490 | ||
fe6bb31f JJ |
491 | /* freed by policy_free */ |
492 | if (prefix) { | |
a1bd627b JJ |
493 | hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); |
494 | if (hname) | |
495 | sprintf(hname, "%s//%s", prefix, name); | |
496 | } else { | |
497 | hname = aa_str_alloc(strlen(name) + 1, gfp); | |
498 | if (hname) | |
499 | strcpy(hname, name); | |
500 | } | |
501 | if (!hname) | |
b9c42ac7 | 502 | return false; |
a1bd627b | 503 | policy->hname = hname; |
fe6bb31f | 504 | /* base.name is a substring of fqname */ |
d102d895 | 505 | policy->name = basename(policy->hname); |
fe6bb31f JJ |
506 | INIT_LIST_HEAD(&policy->list); |
507 | INIT_LIST_HEAD(&policy->profiles); | |
508 | ||
b9c42ac7 | 509 | return true; |
fe6bb31f JJ |
510 | } |
511 | ||
512 | /** | |
513 | * aa_policy_destroy - free the elements referenced by @policy | |
514 | * @policy: policy that is to have its elements freed (NOT NULL) | |
515 | */ | |
516 | void aa_policy_destroy(struct aa_policy *policy) | |
517 | { | |
5fd1b95f JJ |
518 | AA_BUG(on_list_rcu(&policy->profiles)); |
519 | AA_BUG(on_list_rcu(&policy->list)); | |
fe6bb31f JJ |
520 | |
521 | /* don't free name as its a subset of hname */ | |
a1bd627b | 522 | aa_put_str(policy->hname); |
fe6bb31f | 523 | } |