Commit | Line | Data |
---|---|---|
63e2b423 JJ |
1 | /* |
2 | * AppArmor security module | |
3 | * | |
4 | * This file contains AppArmor /sys/kernel/security/apparmor interface functions | |
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 | ||
0d259f04 | 15 | #include <linux/ctype.h> |
63e2b423 JJ |
16 | #include <linux/security.h> |
17 | #include <linux/vmalloc.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/seq_file.h> | |
20 | #include <linux/uaccess.h> | |
a71ada30 | 21 | #include <linux/mount.h> |
63e2b423 | 22 | #include <linux/namei.h> |
e74abcf3 | 23 | #include <linux/capability.h> |
29b3822f | 24 | #include <linux/rcupdate.h> |
a71ada30 JJ |
25 | #include <uapi/linux/major.h> |
26 | #include <linux/fs.h> | |
63e2b423 JJ |
27 | |
28 | #include "include/apparmor.h" | |
29 | #include "include/apparmorfs.h" | |
30 | #include "include/audit.h" | |
31 | #include "include/context.h" | |
f8eb8a13 | 32 | #include "include/crypto.h" |
63e2b423 | 33 | #include "include/policy.h" |
cff281f6 | 34 | #include "include/policy_ns.h" |
d384b0a1 | 35 | #include "include/resource.h" |
63e2b423 | 36 | |
0d259f04 JJ |
37 | /** |
38 | * aa_mangle_name - mangle a profile name to std profile layout form | |
39 | * @name: profile name to mangle (NOT NULL) | |
40 | * @target: buffer to store mangled name, same length as @name (MAYBE NULL) | |
41 | * | |
42 | * Returns: length of mangled name | |
43 | */ | |
bbe4a7c8 | 44 | static int mangle_name(const char *name, char *target) |
0d259f04 JJ |
45 | { |
46 | char *t = target; | |
47 | ||
48 | while (*name == '/' || *name == '.') | |
49 | name++; | |
50 | ||
51 | if (target) { | |
52 | for (; *name; name++) { | |
53 | if (*name == '/') | |
54 | *(t)++ = '.'; | |
55 | else if (isspace(*name)) | |
56 | *(t)++ = '_'; | |
57 | else if (isalnum(*name) || strchr("._-", *name)) | |
58 | *(t)++ = *name; | |
59 | } | |
60 | ||
61 | *t = 0; | |
62 | } else { | |
63 | int len = 0; | |
64 | for (; *name; name++) { | |
65 | if (isalnum(*name) || isspace(*name) || | |
66 | strchr("/._-", *name)) | |
67 | len++; | |
68 | } | |
69 | ||
70 | return len; | |
71 | } | |
72 | ||
73 | return t - target; | |
74 | } | |
75 | ||
63e2b423 JJ |
76 | /** |
77 | * aa_simple_write_to_buffer - common routine for getting policy from user | |
78 | * @op: operation doing the user buffer copy | |
79 | * @userbuf: user buffer to copy data from (NOT NULL) | |
3ed02ada | 80 | * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) |
63e2b423 JJ |
81 | * @copy_size: size of data to copy from user buffer |
82 | * @pos: position write is at in the file (NOT NULL) | |
83 | * | |
84 | * Returns: kernel buffer containing copy of user buffer data or an | |
85 | * ERR_PTR on failure. | |
86 | */ | |
87 | static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, | |
88 | size_t alloc_size, size_t copy_size, | |
89 | loff_t *pos) | |
90 | { | |
91 | char *data; | |
92 | ||
3ed02ada JJ |
93 | BUG_ON(copy_size > alloc_size); |
94 | ||
63e2b423 JJ |
95 | if (*pos != 0) |
96 | /* only writes from pos 0, that is complete writes */ | |
97 | return ERR_PTR(-ESPIPE); | |
98 | ||
99 | /* | |
100 | * Don't allow profile load/replace/remove from profiles that don't | |
101 | * have CAP_MAC_ADMIN | |
102 | */ | |
103 | if (!aa_may_manage_policy(op)) | |
104 | return ERR_PTR(-EACCES); | |
105 | ||
106 | /* freed by caller to simple_write_to_buffer */ | |
107 | data = kvmalloc(alloc_size); | |
108 | if (data == NULL) | |
109 | return ERR_PTR(-ENOMEM); | |
110 | ||
111 | if (copy_from_user(data, userbuf, copy_size)) { | |
112 | kvfree(data); | |
113 | return ERR_PTR(-EFAULT); | |
114 | } | |
115 | ||
116 | return data; | |
117 | } | |
118 | ||
119 | ||
120 | /* .load file hook fn to load policy */ | |
121 | static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | |
122 | loff_t *pos) | |
123 | { | |
124 | char *data; | |
125 | ssize_t error; | |
126 | ||
127 | data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); | |
128 | ||
129 | error = PTR_ERR(data); | |
130 | if (!IS_ERR(data)) { | |
73688d1e JJ |
131 | error = aa_replace_profiles(__aa_current_profile()->ns, data, |
132 | size, PROF_ADD); | |
63e2b423 JJ |
133 | kvfree(data); |
134 | } | |
135 | ||
136 | return error; | |
137 | } | |
138 | ||
139 | static const struct file_operations aa_fs_profile_load = { | |
6038f373 AB |
140 | .write = profile_load, |
141 | .llseek = default_llseek, | |
63e2b423 JJ |
142 | }; |
143 | ||
144 | /* .replace file hook fn to load and/or replace policy */ | |
145 | static ssize_t profile_replace(struct file *f, const char __user *buf, | |
146 | size_t size, loff_t *pos) | |
147 | { | |
148 | char *data; | |
149 | ssize_t error; | |
150 | ||
151 | data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); | |
152 | error = PTR_ERR(data); | |
153 | if (!IS_ERR(data)) { | |
73688d1e JJ |
154 | error = aa_replace_profiles(__aa_current_profile()->ns, data, |
155 | size, PROF_REPLACE); | |
63e2b423 JJ |
156 | kvfree(data); |
157 | } | |
158 | ||
159 | return error; | |
160 | } | |
161 | ||
162 | static const struct file_operations aa_fs_profile_replace = { | |
6038f373 AB |
163 | .write = profile_replace, |
164 | .llseek = default_llseek, | |
63e2b423 JJ |
165 | }; |
166 | ||
167 | /* .remove file hook fn to remove loaded policy */ | |
168 | static ssize_t profile_remove(struct file *f, const char __user *buf, | |
169 | size_t size, loff_t *pos) | |
170 | { | |
171 | char *data; | |
172 | ssize_t error; | |
173 | ||
174 | /* | |
175 | * aa_remove_profile needs a null terminated string so 1 extra | |
176 | * byte is allocated and the copied data is null terminated. | |
177 | */ | |
178 | data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); | |
179 | ||
180 | error = PTR_ERR(data); | |
181 | if (!IS_ERR(data)) { | |
182 | data[size] = 0; | |
b79473f2 JJ |
183 | error = aa_remove_profiles(__aa_current_profile()->ns, data, |
184 | size); | |
63e2b423 JJ |
185 | kvfree(data); |
186 | } | |
187 | ||
188 | return error; | |
189 | } | |
190 | ||
191 | static const struct file_operations aa_fs_profile_remove = { | |
6038f373 AB |
192 | .write = profile_remove, |
193 | .llseek = default_llseek, | |
63e2b423 JJ |
194 | }; |
195 | ||
e74abcf3 KC |
196 | static int aa_fs_seq_show(struct seq_file *seq, void *v) |
197 | { | |
198 | struct aa_fs_entry *fs_file = seq->private; | |
199 | ||
200 | if (!fs_file) | |
201 | return 0; | |
202 | ||
203 | switch (fs_file->v_type) { | |
204 | case AA_FS_TYPE_BOOLEAN: | |
205 | seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); | |
206 | break; | |
a9bf8e9f KC |
207 | case AA_FS_TYPE_STRING: |
208 | seq_printf(seq, "%s\n", fs_file->v.string); | |
209 | break; | |
e74abcf3 KC |
210 | case AA_FS_TYPE_U64: |
211 | seq_printf(seq, "%#08lx\n", fs_file->v.u64); | |
212 | break; | |
213 | default: | |
214 | /* Ignore unpritable entry types. */ | |
215 | break; | |
216 | } | |
217 | ||
218 | return 0; | |
219 | } | |
220 | ||
221 | static int aa_fs_seq_open(struct inode *inode, struct file *file) | |
222 | { | |
223 | return single_open(file, aa_fs_seq_show, inode->i_private); | |
224 | } | |
225 | ||
226 | const struct file_operations aa_fs_seq_file_ops = { | |
227 | .owner = THIS_MODULE, | |
228 | .open = aa_fs_seq_open, | |
229 | .read = seq_read, | |
230 | .llseek = seq_lseek, | |
231 | .release = single_release, | |
232 | }; | |
233 | ||
0d259f04 JJ |
234 | static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, |
235 | int (*show)(struct seq_file *, void *)) | |
236 | { | |
8399588a JJ |
237 | struct aa_proxy *proxy = aa_get_proxy(inode->i_private); |
238 | int error = single_open(file, show, proxy); | |
0d259f04 JJ |
239 | |
240 | if (error) { | |
241 | file->private_data = NULL; | |
8399588a | 242 | aa_put_proxy(proxy); |
0d259f04 JJ |
243 | } |
244 | ||
245 | return error; | |
246 | } | |
247 | ||
248 | static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) | |
249 | { | |
250 | struct seq_file *seq = (struct seq_file *) file->private_data; | |
251 | if (seq) | |
8399588a | 252 | aa_put_proxy(seq->private); |
0d259f04 JJ |
253 | return single_release(inode, file); |
254 | } | |
255 | ||
256 | static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) | |
257 | { | |
8399588a JJ |
258 | struct aa_proxy *proxy = seq->private; |
259 | struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); | |
0d259f04 JJ |
260 | seq_printf(seq, "%s\n", profile->base.name); |
261 | aa_put_profile(profile); | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
266 | static int aa_fs_seq_profname_open(struct inode *inode, struct file *file) | |
267 | { | |
268 | return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show); | |
269 | } | |
270 | ||
271 | static const struct file_operations aa_fs_profname_fops = { | |
272 | .owner = THIS_MODULE, | |
273 | .open = aa_fs_seq_profname_open, | |
274 | .read = seq_read, | |
275 | .llseek = seq_lseek, | |
276 | .release = aa_fs_seq_profile_release, | |
277 | }; | |
278 | ||
279 | static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) | |
280 | { | |
8399588a JJ |
281 | struct aa_proxy *proxy = seq->private; |
282 | struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); | |
0d259f04 JJ |
283 | seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); |
284 | aa_put_profile(profile); | |
285 | ||
286 | return 0; | |
287 | } | |
288 | ||
289 | static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file) | |
290 | { | |
291 | return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show); | |
292 | } | |
293 | ||
294 | static const struct file_operations aa_fs_profmode_fops = { | |
295 | .owner = THIS_MODULE, | |
296 | .open = aa_fs_seq_profmode_open, | |
297 | .read = seq_read, | |
298 | .llseek = seq_lseek, | |
299 | .release = aa_fs_seq_profile_release, | |
300 | }; | |
301 | ||
556d0be7 JJ |
302 | static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) |
303 | { | |
8399588a JJ |
304 | struct aa_proxy *proxy = seq->private; |
305 | struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); | |
556d0be7 JJ |
306 | if (profile->attach) |
307 | seq_printf(seq, "%s\n", profile->attach); | |
308 | else if (profile->xmatch) | |
309 | seq_puts(seq, "<unknown>\n"); | |
310 | else | |
311 | seq_printf(seq, "%s\n", profile->base.name); | |
312 | aa_put_profile(profile); | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
317 | static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file) | |
318 | { | |
319 | return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show); | |
320 | } | |
321 | ||
322 | static const struct file_operations aa_fs_profattach_fops = { | |
323 | .owner = THIS_MODULE, | |
324 | .open = aa_fs_seq_profattach_open, | |
325 | .read = seq_read, | |
326 | .llseek = seq_lseek, | |
327 | .release = aa_fs_seq_profile_release, | |
328 | }; | |
329 | ||
f8eb8a13 JJ |
330 | static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) |
331 | { | |
8399588a JJ |
332 | struct aa_proxy *proxy = seq->private; |
333 | struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); | |
f8eb8a13 JJ |
334 | unsigned int i, size = aa_hash_size(); |
335 | ||
336 | if (profile->hash) { | |
337 | for (i = 0; i < size; i++) | |
338 | seq_printf(seq, "%.2x", profile->hash[i]); | |
339 | seq_puts(seq, "\n"); | |
340 | } | |
0b938a2e | 341 | aa_put_profile(profile); |
f8eb8a13 JJ |
342 | |
343 | return 0; | |
344 | } | |
345 | ||
346 | static int aa_fs_seq_hash_open(struct inode *inode, struct file *file) | |
347 | { | |
348 | return single_open(file, aa_fs_seq_hash_show, inode->i_private); | |
349 | } | |
350 | ||
351 | static const struct file_operations aa_fs_seq_hash_fops = { | |
352 | .owner = THIS_MODULE, | |
353 | .open = aa_fs_seq_hash_open, | |
354 | .read = seq_read, | |
355 | .llseek = seq_lseek, | |
356 | .release = single_release, | |
357 | }; | |
358 | ||
a71ada30 JJ |
359 | static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) |
360 | { | |
361 | struct aa_ns *ns = aa_current_profile()->ns; | |
362 | ||
363 | seq_printf(seq, "%d\n", ns->level); | |
364 | ||
365 | return 0; | |
366 | } | |
367 | ||
368 | static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file) | |
369 | { | |
370 | return single_open(file, aa_fs_seq_show_ns_level, inode->i_private); | |
371 | } | |
372 | ||
373 | static const struct file_operations aa_fs_ns_level = { | |
374 | .owner = THIS_MODULE, | |
375 | .open = aa_fs_seq_open_ns_level, | |
376 | .read = seq_read, | |
377 | .llseek = seq_lseek, | |
378 | .release = single_release, | |
379 | }; | |
380 | ||
0d259f04 JJ |
381 | /** fns to setup dynamic per profile/namespace files **/ |
382 | void __aa_fs_profile_rmdir(struct aa_profile *profile) | |
383 | { | |
384 | struct aa_profile *child; | |
385 | int i; | |
386 | ||
387 | if (!profile) | |
388 | return; | |
389 | ||
390 | list_for_each_entry(child, &profile->base.profiles, base.list) | |
391 | __aa_fs_profile_rmdir(child); | |
392 | ||
393 | for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { | |
8399588a | 394 | struct aa_proxy *proxy; |
0d259f04 JJ |
395 | if (!profile->dents[i]) |
396 | continue; | |
397 | ||
8399588a | 398 | proxy = d_inode(profile->dents[i])->i_private; |
0d259f04 | 399 | securityfs_remove(profile->dents[i]); |
8399588a | 400 | aa_put_proxy(proxy); |
0d259f04 JJ |
401 | profile->dents[i] = NULL; |
402 | } | |
403 | } | |
404 | ||
405 | void __aa_fs_profile_migrate_dents(struct aa_profile *old, | |
406 | struct aa_profile *new) | |
407 | { | |
408 | int i; | |
409 | ||
410 | for (i = 0; i < AAFS_PROF_SIZEOF; i++) { | |
411 | new->dents[i] = old->dents[i]; | |
d671e890 | 412 | if (new->dents[i]) |
078cd827 | 413 | new->dents[i]->d_inode->i_mtime = current_time(new->dents[i]->d_inode); |
0d259f04 JJ |
414 | old->dents[i] = NULL; |
415 | } | |
416 | } | |
417 | ||
418 | static struct dentry *create_profile_file(struct dentry *dir, const char *name, | |
419 | struct aa_profile *profile, | |
420 | const struct file_operations *fops) | |
421 | { | |
8399588a | 422 | struct aa_proxy *proxy = aa_get_proxy(profile->proxy); |
0d259f04 JJ |
423 | struct dentry *dent; |
424 | ||
8399588a | 425 | dent = securityfs_create_file(name, S_IFREG | 0444, dir, proxy, fops); |
0d259f04 | 426 | if (IS_ERR(dent)) |
8399588a | 427 | aa_put_proxy(proxy); |
0d259f04 JJ |
428 | |
429 | return dent; | |
430 | } | |
431 | ||
432 | /* requires lock be held */ | |
433 | int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) | |
434 | { | |
435 | struct aa_profile *child; | |
436 | struct dentry *dent = NULL, *dir; | |
437 | int error; | |
438 | ||
439 | if (!parent) { | |
440 | struct aa_profile *p; | |
441 | p = aa_deref_parent(profile); | |
442 | dent = prof_dir(p); | |
443 | /* adding to parent that previously didn't have children */ | |
444 | dent = securityfs_create_dir("profiles", dent); | |
445 | if (IS_ERR(dent)) | |
446 | goto fail; | |
447 | prof_child_dir(p) = parent = dent; | |
448 | } | |
449 | ||
450 | if (!profile->dirname) { | |
451 | int len, id_len; | |
452 | len = mangle_name(profile->base.name, NULL); | |
453 | id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); | |
454 | ||
455 | profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); | |
456 | if (!profile->dirname) | |
457 | goto fail; | |
458 | ||
459 | mangle_name(profile->base.name, profile->dirname); | |
460 | sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); | |
461 | } | |
462 | ||
463 | dent = securityfs_create_dir(profile->dirname, parent); | |
464 | if (IS_ERR(dent)) | |
465 | goto fail; | |
466 | prof_dir(profile) = dir = dent; | |
467 | ||
468 | dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops); | |
469 | if (IS_ERR(dent)) | |
470 | goto fail; | |
471 | profile->dents[AAFS_PROF_NAME] = dent; | |
472 | ||
473 | dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops); | |
474 | if (IS_ERR(dent)) | |
475 | goto fail; | |
476 | profile->dents[AAFS_PROF_MODE] = dent; | |
477 | ||
556d0be7 JJ |
478 | dent = create_profile_file(dir, "attach", profile, |
479 | &aa_fs_profattach_fops); | |
480 | if (IS_ERR(dent)) | |
481 | goto fail; | |
482 | profile->dents[AAFS_PROF_ATTACH] = dent; | |
483 | ||
f8eb8a13 JJ |
484 | if (profile->hash) { |
485 | dent = create_profile_file(dir, "sha1", profile, | |
486 | &aa_fs_seq_hash_fops); | |
487 | if (IS_ERR(dent)) | |
488 | goto fail; | |
489 | profile->dents[AAFS_PROF_HASH] = dent; | |
490 | } | |
491 | ||
0d259f04 JJ |
492 | list_for_each_entry(child, &profile->base.profiles, base.list) { |
493 | error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); | |
494 | if (error) | |
495 | goto fail2; | |
496 | } | |
497 | ||
498 | return 0; | |
499 | ||
500 | fail: | |
501 | error = PTR_ERR(dent); | |
502 | ||
503 | fail2: | |
504 | __aa_fs_profile_rmdir(profile); | |
505 | ||
506 | return error; | |
507 | } | |
508 | ||
98849dff | 509 | void __aa_fs_ns_rmdir(struct aa_ns *ns) |
0d259f04 | 510 | { |
98849dff | 511 | struct aa_ns *sub; |
0d259f04 JJ |
512 | struct aa_profile *child; |
513 | int i; | |
514 | ||
515 | if (!ns) | |
516 | return; | |
517 | ||
518 | list_for_each_entry(child, &ns->base.profiles, base.list) | |
519 | __aa_fs_profile_rmdir(child); | |
520 | ||
521 | list_for_each_entry(sub, &ns->sub_ns, base.list) { | |
522 | mutex_lock(&sub->lock); | |
98849dff | 523 | __aa_fs_ns_rmdir(sub); |
0d259f04 JJ |
524 | mutex_unlock(&sub->lock); |
525 | } | |
526 | ||
527 | for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { | |
528 | securityfs_remove(ns->dents[i]); | |
529 | ns->dents[i] = NULL; | |
530 | } | |
531 | } | |
532 | ||
98849dff | 533 | int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) |
0d259f04 | 534 | { |
98849dff | 535 | struct aa_ns *sub; |
0d259f04 JJ |
536 | struct aa_profile *child; |
537 | struct dentry *dent, *dir; | |
538 | int error; | |
539 | ||
540 | if (!name) | |
541 | name = ns->base.name; | |
542 | ||
543 | dent = securityfs_create_dir(name, parent); | |
544 | if (IS_ERR(dent)) | |
545 | goto fail; | |
546 | ns_dir(ns) = dir = dent; | |
547 | ||
548 | dent = securityfs_create_dir("profiles", dir); | |
549 | if (IS_ERR(dent)) | |
550 | goto fail; | |
551 | ns_subprofs_dir(ns) = dent; | |
63e2b423 | 552 | |
0d259f04 JJ |
553 | dent = securityfs_create_dir("namespaces", dir); |
554 | if (IS_ERR(dent)) | |
555 | goto fail; | |
556 | ns_subns_dir(ns) = dent; | |
557 | ||
558 | list_for_each_entry(child, &ns->base.profiles, base.list) { | |
559 | error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); | |
560 | if (error) | |
561 | goto fail2; | |
562 | } | |
563 | ||
564 | list_for_each_entry(sub, &ns->sub_ns, base.list) { | |
565 | mutex_lock(&sub->lock); | |
98849dff | 566 | error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); |
0d259f04 JJ |
567 | mutex_unlock(&sub->lock); |
568 | if (error) | |
569 | goto fail2; | |
570 | } | |
571 | ||
572 | return 0; | |
573 | ||
574 | fail: | |
575 | error = PTR_ERR(dent); | |
576 | ||
577 | fail2: | |
98849dff | 578 | __aa_fs_ns_rmdir(ns); |
0d259f04 JJ |
579 | |
580 | return error; | |
581 | } | |
582 | ||
583 | ||
29b3822f JJ |
584 | #define list_entry_is_head(pos, head, member) (&pos->member == (head)) |
585 | ||
586 | /** | |
98849dff | 587 | * __next_ns - find the next namespace to list |
29b3822f JJ |
588 | * @root: root namespace to stop search at (NOT NULL) |
589 | * @ns: current ns position (NOT NULL) | |
590 | * | |
591 | * Find the next namespace from @ns under @root and handle all locking needed | |
592 | * while switching current namespace. | |
593 | * | |
594 | * Returns: next namespace or NULL if at last namespace under @root | |
595 | * Requires: ns->parent->lock to be held | |
596 | * NOTE: will not unlock root->lock | |
597 | */ | |
98849dff | 598 | static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) |
29b3822f | 599 | { |
98849dff | 600 | struct aa_ns *parent, *next; |
29b3822f JJ |
601 | |
602 | /* is next namespace a child */ | |
603 | if (!list_empty(&ns->sub_ns)) { | |
604 | next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); | |
605 | mutex_lock(&next->lock); | |
606 | return next; | |
607 | } | |
608 | ||
609 | /* check if the next ns is a sibling, parent, gp, .. */ | |
610 | parent = ns->parent; | |
ed2c7da3 | 611 | while (ns != root) { |
29b3822f | 612 | mutex_unlock(&ns->lock); |
38dbd7d8 | 613 | next = list_next_entry(ns, base.list); |
29b3822f JJ |
614 | if (!list_entry_is_head(next, &parent->sub_ns, base.list)) { |
615 | mutex_lock(&next->lock); | |
616 | return next; | |
617 | } | |
29b3822f JJ |
618 | ns = parent; |
619 | parent = parent->parent; | |
620 | } | |
621 | ||
622 | return NULL; | |
623 | } | |
624 | ||
625 | /** | |
626 | * __first_profile - find the first profile in a namespace | |
627 | * @root: namespace that is root of profiles being displayed (NOT NULL) | |
628 | * @ns: namespace to start in (NOT NULL) | |
629 | * | |
630 | * Returns: unrefcounted profile or NULL if no profile | |
631 | * Requires: profile->ns.lock to be held | |
632 | */ | |
98849dff JJ |
633 | static struct aa_profile *__first_profile(struct aa_ns *root, |
634 | struct aa_ns *ns) | |
29b3822f | 635 | { |
98849dff | 636 | for (; ns; ns = __next_ns(root, ns)) { |
29b3822f JJ |
637 | if (!list_empty(&ns->base.profiles)) |
638 | return list_first_entry(&ns->base.profiles, | |
639 | struct aa_profile, base.list); | |
640 | } | |
641 | return NULL; | |
642 | } | |
643 | ||
644 | /** | |
645 | * __next_profile - step to the next profile in a profile tree | |
646 | * @profile: current profile in tree (NOT NULL) | |
647 | * | |
648 | * Perform a depth first traversal on the profile tree in a namespace | |
649 | * | |
650 | * Returns: next profile or NULL if done | |
651 | * Requires: profile->ns.lock to be held | |
652 | */ | |
653 | static struct aa_profile *__next_profile(struct aa_profile *p) | |
654 | { | |
655 | struct aa_profile *parent; | |
98849dff | 656 | struct aa_ns *ns = p->ns; |
29b3822f JJ |
657 | |
658 | /* is next profile a child */ | |
659 | if (!list_empty(&p->base.profiles)) | |
660 | return list_first_entry(&p->base.profiles, typeof(*p), | |
661 | base.list); | |
662 | ||
663 | /* is next profile a sibling, parent sibling, gp, sibling, .. */ | |
664 | parent = rcu_dereference_protected(p->parent, | |
665 | mutex_is_locked(&p->ns->lock)); | |
666 | while (parent) { | |
38dbd7d8 | 667 | p = list_next_entry(p, base.list); |
29b3822f JJ |
668 | if (!list_entry_is_head(p, &parent->base.profiles, base.list)) |
669 | return p; | |
670 | p = parent; | |
671 | parent = rcu_dereference_protected(parent->parent, | |
672 | mutex_is_locked(&parent->ns->lock)); | |
673 | } | |
674 | ||
675 | /* is next another profile in the namespace */ | |
38dbd7d8 | 676 | p = list_next_entry(p, base.list); |
29b3822f JJ |
677 | if (!list_entry_is_head(p, &ns->base.profiles, base.list)) |
678 | return p; | |
679 | ||
680 | return NULL; | |
681 | } | |
682 | ||
683 | /** | |
684 | * next_profile - step to the next profile in where ever it may be | |
685 | * @root: root namespace (NOT NULL) | |
686 | * @profile: current profile (NOT NULL) | |
687 | * | |
688 | * Returns: next profile or NULL if there isn't one | |
689 | */ | |
98849dff | 690 | static struct aa_profile *next_profile(struct aa_ns *root, |
29b3822f JJ |
691 | struct aa_profile *profile) |
692 | { | |
693 | struct aa_profile *next = __next_profile(profile); | |
694 | if (next) | |
695 | return next; | |
696 | ||
697 | /* finished all profiles in namespace move to next namespace */ | |
98849dff | 698 | return __first_profile(root, __next_ns(root, profile->ns)); |
29b3822f JJ |
699 | } |
700 | ||
701 | /** | |
702 | * p_start - start a depth first traversal of profile tree | |
703 | * @f: seq_file to fill | |
704 | * @pos: current position | |
705 | * | |
706 | * Returns: first profile under current namespace or NULL if none found | |
707 | * | |
708 | * acquires first ns->lock | |
709 | */ | |
710 | static void *p_start(struct seq_file *f, loff_t *pos) | |
711 | { | |
712 | struct aa_profile *profile = NULL; | |
98849dff | 713 | struct aa_ns *root = aa_current_profile()->ns; |
29b3822f | 714 | loff_t l = *pos; |
98849dff | 715 | f->private = aa_get_ns(root); |
29b3822f JJ |
716 | |
717 | ||
718 | /* find the first profile */ | |
719 | mutex_lock(&root->lock); | |
720 | profile = __first_profile(root, root); | |
721 | ||
722 | /* skip to position */ | |
723 | for (; profile && l > 0; l--) | |
724 | profile = next_profile(root, profile); | |
725 | ||
726 | return profile; | |
727 | } | |
728 | ||
729 | /** | |
730 | * p_next - read the next profile entry | |
731 | * @f: seq_file to fill | |
732 | * @p: profile previously returned | |
733 | * @pos: current position | |
734 | * | |
735 | * Returns: next profile after @p or NULL if none | |
736 | * | |
737 | * may acquire/release locks in namespace tree as necessary | |
738 | */ | |
739 | static void *p_next(struct seq_file *f, void *p, loff_t *pos) | |
740 | { | |
741 | struct aa_profile *profile = p; | |
98849dff | 742 | struct aa_ns *ns = f->private; |
29b3822f JJ |
743 | (*pos)++; |
744 | ||
745 | return next_profile(ns, profile); | |
746 | } | |
747 | ||
748 | /** | |
749 | * p_stop - stop depth first traversal | |
750 | * @f: seq_file we are filling | |
751 | * @p: the last profile writen | |
752 | * | |
753 | * Release all locking done by p_start/p_next on namespace tree | |
754 | */ | |
755 | static void p_stop(struct seq_file *f, void *p) | |
756 | { | |
757 | struct aa_profile *profile = p; | |
98849dff | 758 | struct aa_ns *root = f->private, *ns; |
29b3822f JJ |
759 | |
760 | if (profile) { | |
761 | for (ns = profile->ns; ns && ns != root; ns = ns->parent) | |
762 | mutex_unlock(&ns->lock); | |
763 | } | |
764 | mutex_unlock(&root->lock); | |
98849dff | 765 | aa_put_ns(root); |
29b3822f JJ |
766 | } |
767 | ||
768 | /** | |
769 | * seq_show_profile - show a profile entry | |
770 | * @f: seq_file to file | |
771 | * @p: current position (profile) (NOT NULL) | |
772 | * | |
773 | * Returns: error on failure | |
774 | */ | |
775 | static int seq_show_profile(struct seq_file *f, void *p) | |
776 | { | |
777 | struct aa_profile *profile = (struct aa_profile *)p; | |
98849dff | 778 | struct aa_ns *root = f->private; |
29b3822f JJ |
779 | |
780 | if (profile->ns != root) | |
92b6d8ef | 781 | seq_printf(f, ":%s://", aa_ns_name(root, profile->ns, true)); |
29b3822f JJ |
782 | seq_printf(f, "%s (%s)\n", profile->base.hname, |
783 | aa_profile_mode_names[profile->mode]); | |
784 | ||
785 | return 0; | |
786 | } | |
787 | ||
788 | static const struct seq_operations aa_fs_profiles_op = { | |
789 | .start = p_start, | |
790 | .next = p_next, | |
791 | .stop = p_stop, | |
792 | .show = seq_show_profile, | |
793 | }; | |
794 | ||
795 | static int profiles_open(struct inode *inode, struct file *file) | |
796 | { | |
797 | return seq_open(file, &aa_fs_profiles_op); | |
798 | } | |
799 | ||
800 | static int profiles_release(struct inode *inode, struct file *file) | |
801 | { | |
802 | return seq_release(inode, file); | |
803 | } | |
804 | ||
805 | static const struct file_operations aa_fs_profiles_fops = { | |
806 | .open = profiles_open, | |
807 | .read = seq_read, | |
808 | .llseek = seq_lseek, | |
809 | .release = profiles_release, | |
810 | }; | |
811 | ||
812 | ||
0d259f04 | 813 | /** Base file system setup **/ |
a9bf8e9f KC |
814 | static struct aa_fs_entry aa_fs_entry_file[] = { |
815 | AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \ | |
816 | "link lock"), | |
817 | { } | |
818 | }; | |
819 | ||
e74abcf3 KC |
820 | static struct aa_fs_entry aa_fs_entry_domain[] = { |
821 | AA_FS_FILE_BOOLEAN("change_hat", 1), | |
822 | AA_FS_FILE_BOOLEAN("change_hatv", 1), | |
823 | AA_FS_FILE_BOOLEAN("change_onexec", 1), | |
824 | AA_FS_FILE_BOOLEAN("change_profile", 1), | |
34c426ac | 825 | AA_FS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), |
e74abcf3 KC |
826 | { } |
827 | }; | |
828 | ||
474d6b75 JJ |
829 | static struct aa_fs_entry aa_fs_entry_versions[] = { |
830 | AA_FS_FILE_BOOLEAN("v5", 1), | |
831 | { } | |
832 | }; | |
833 | ||
9d910a3b | 834 | static struct aa_fs_entry aa_fs_entry_policy[] = { |
474d6b75 JJ |
835 | AA_FS_DIR("versions", aa_fs_entry_versions), |
836 | AA_FS_FILE_BOOLEAN("set_load", 1), | |
837 | { } | |
9d910a3b JJ |
838 | }; |
839 | ||
e74abcf3 | 840 | static struct aa_fs_entry aa_fs_entry_features[] = { |
9d910a3b | 841 | AA_FS_DIR("policy", aa_fs_entry_policy), |
e74abcf3 | 842 | AA_FS_DIR("domain", aa_fs_entry_domain), |
a9bf8e9f | 843 | AA_FS_DIR("file", aa_fs_entry_file), |
e74abcf3 | 844 | AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), |
d384b0a1 | 845 | AA_FS_DIR("rlimit", aa_fs_entry_rlimit), |
84f1f787 | 846 | AA_FS_DIR("caps", aa_fs_entry_caps), |
e74abcf3 KC |
847 | { } |
848 | }; | |
849 | ||
9acd494b KC |
850 | static struct aa_fs_entry aa_fs_entry_apparmor[] = { |
851 | AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), | |
852 | AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), | |
853 | AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), | |
a71ada30 | 854 | AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level), |
29b3822f | 855 | AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), |
e74abcf3 | 856 | AA_FS_DIR("features", aa_fs_entry_features), |
9acd494b KC |
857 | { } |
858 | }; | |
63e2b423 | 859 | |
9acd494b KC |
860 | static struct aa_fs_entry aa_fs_entry = |
861 | AA_FS_DIR("apparmor", aa_fs_entry_apparmor); | |
63e2b423 | 862 | |
9acd494b KC |
863 | /** |
864 | * aafs_create_file - create a file entry in the apparmor securityfs | |
865 | * @fs_file: aa_fs_entry to build an entry for (NOT NULL) | |
866 | * @parent: the parent dentry in the securityfs | |
867 | * | |
868 | * Use aafs_remove_file to remove entries created with this fn. | |
869 | */ | |
870 | static int __init aafs_create_file(struct aa_fs_entry *fs_file, | |
871 | struct dentry *parent) | |
872 | { | |
873 | int error = 0; | |
874 | ||
875 | fs_file->dentry = securityfs_create_file(fs_file->name, | |
876 | S_IFREG | fs_file->mode, | |
877 | parent, fs_file, | |
878 | fs_file->file_ops); | |
879 | if (IS_ERR(fs_file->dentry)) { | |
880 | error = PTR_ERR(fs_file->dentry); | |
881 | fs_file->dentry = NULL; | |
63e2b423 | 882 | } |
9acd494b | 883 | return error; |
63e2b423 JJ |
884 | } |
885 | ||
0d259f04 | 886 | static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir); |
63e2b423 | 887 | /** |
9acd494b KC |
888 | * aafs_create_dir - recursively create a directory entry in the securityfs |
889 | * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) | |
890 | * @parent: the parent dentry in the securityfs | |
63e2b423 | 891 | * |
9acd494b | 892 | * Use aafs_remove_dir to remove entries created with this fn. |
63e2b423 | 893 | */ |
9acd494b KC |
894 | static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, |
895 | struct dentry *parent) | |
63e2b423 | 896 | { |
9acd494b | 897 | struct aa_fs_entry *fs_file; |
0d259f04 JJ |
898 | struct dentry *dir; |
899 | int error; | |
63e2b423 | 900 | |
0d259f04 JJ |
901 | dir = securityfs_create_dir(fs_dir->name, parent); |
902 | if (IS_ERR(dir)) | |
903 | return PTR_ERR(dir); | |
904 | fs_dir->dentry = dir; | |
63e2b423 | 905 | |
0d259f04 | 906 | for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { |
9acd494b KC |
907 | if (fs_file->v_type == AA_FS_TYPE_DIR) |
908 | error = aafs_create_dir(fs_file, fs_dir->dentry); | |
909 | else | |
910 | error = aafs_create_file(fs_file, fs_dir->dentry); | |
911 | if (error) | |
912 | goto failed; | |
913 | } | |
914 | ||
915 | return 0; | |
916 | ||
917 | failed: | |
0d259f04 JJ |
918 | aafs_remove_dir(fs_dir); |
919 | ||
9acd494b KC |
920 | return error; |
921 | } | |
922 | ||
923 | /** | |
924 | * aafs_remove_file - drop a single file entry in the apparmor securityfs | |
925 | * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL) | |
926 | */ | |
927 | static void __init aafs_remove_file(struct aa_fs_entry *fs_file) | |
928 | { | |
929 | if (!fs_file->dentry) | |
930 | return; | |
931 | ||
932 | securityfs_remove(fs_file->dentry); | |
933 | fs_file->dentry = NULL; | |
934 | } | |
935 | ||
936 | /** | |
937 | * aafs_remove_dir - recursively drop a directory entry from the securityfs | |
938 | * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) | |
939 | */ | |
940 | static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) | |
941 | { | |
942 | struct aa_fs_entry *fs_file; | |
943 | ||
0d259f04 | 944 | for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { |
9acd494b KC |
945 | if (fs_file->v_type == AA_FS_TYPE_DIR) |
946 | aafs_remove_dir(fs_file); | |
947 | else | |
948 | aafs_remove_file(fs_file); | |
949 | } | |
950 | ||
951 | aafs_remove_file(fs_dir); | |
63e2b423 JJ |
952 | } |
953 | ||
954 | /** | |
955 | * aa_destroy_aafs - cleanup and free aafs | |
956 | * | |
957 | * releases dentries allocated by aa_create_aafs | |
958 | */ | |
959 | void __init aa_destroy_aafs(void) | |
960 | { | |
9acd494b | 961 | aafs_remove_dir(&aa_fs_entry); |
63e2b423 JJ |
962 | } |
963 | ||
a71ada30 JJ |
964 | |
965 | #define NULL_FILE_NAME ".null" | |
966 | struct path aa_null; | |
967 | ||
968 | static int aa_mk_null_file(struct dentry *parent) | |
969 | { | |
970 | struct vfsmount *mount = NULL; | |
971 | struct dentry *dentry; | |
972 | struct inode *inode; | |
973 | int count = 0; | |
974 | int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count); | |
975 | ||
976 | if (error) | |
977 | return error; | |
978 | ||
979 | inode_lock(d_inode(parent)); | |
980 | dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME)); | |
981 | if (IS_ERR(dentry)) { | |
982 | error = PTR_ERR(dentry); | |
983 | goto out; | |
984 | } | |
985 | inode = new_inode(parent->d_inode->i_sb); | |
986 | if (!inode) { | |
987 | error = -ENOMEM; | |
988 | goto out1; | |
989 | } | |
990 | ||
991 | inode->i_ino = get_next_ino(); | |
992 | inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO; | |
993 | inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; | |
994 | init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, | |
995 | MKDEV(MEM_MAJOR, 3)); | |
996 | d_instantiate(dentry, inode); | |
997 | aa_null.dentry = dget(dentry); | |
998 | aa_null.mnt = mntget(mount); | |
999 | ||
1000 | error = 0; | |
1001 | ||
1002 | out1: | |
1003 | dput(dentry); | |
1004 | out: | |
1005 | inode_unlock(d_inode(parent)); | |
1006 | simple_release_fs(&mount, &count); | |
1007 | return error; | |
1008 | } | |
1009 | ||
63e2b423 JJ |
1010 | /** |
1011 | * aa_create_aafs - create the apparmor security filesystem | |
1012 | * | |
1013 | * dentries created here are released by aa_destroy_aafs | |
1014 | * | |
1015 | * Returns: error on failure | |
1016 | */ | |
3417d8d5 | 1017 | static int __init aa_create_aafs(void) |
63e2b423 JJ |
1018 | { |
1019 | int error; | |
1020 | ||
1021 | if (!apparmor_initialized) | |
1022 | return 0; | |
1023 | ||
9acd494b | 1024 | if (aa_fs_entry.dentry) { |
63e2b423 JJ |
1025 | AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); |
1026 | return -EEXIST; | |
1027 | } | |
1028 | ||
9acd494b KC |
1029 | /* Populate fs tree. */ |
1030 | error = aafs_create_dir(&aa_fs_entry, NULL); | |
63e2b423 JJ |
1031 | if (error) |
1032 | goto error; | |
1033 | ||
98849dff | 1034 | error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); |
0d259f04 JJ |
1035 | if (error) |
1036 | goto error; | |
1037 | ||
a71ada30 JJ |
1038 | error = aa_mk_null_file(aa_fs_entry.dentry); |
1039 | if (error) | |
1040 | goto error; | |
1041 | ||
1042 | /* TODO: add default profile to apparmorfs */ | |
63e2b423 JJ |
1043 | |
1044 | /* Report that AppArmor fs is enabled */ | |
1045 | aa_info_message("AppArmor Filesystem Enabled"); | |
1046 | return 0; | |
1047 | ||
1048 | error: | |
1049 | aa_destroy_aafs(); | |
1050 | AA_ERROR("Error creating AppArmor securityfs\n"); | |
1051 | return error; | |
1052 | } | |
1053 | ||
1054 | fs_initcall(aa_create_aafs); |