]>
Commit | Line | Data |
---|---|---|
6a930a95 BS |
1 | From: John Johansen <jjohansen@suse.de> |
2 | Subject: AppArmor: Module and LSM hooks | |
3 | ||
4 | Module parameters, LSM hooks, initialization and teardown. | |
5 | ||
6 | Signed-off-by: John Johansen <jjohansen@suse.de> | |
7 | Signed-off-by: Andreas Gruenbacher <agruen@suse.de> | |
8 | ||
9 | --- | |
10 | security/apparmor/lsm.c | 895 ++++++++++++++++++++++++++++++++++++++++++++++++ | |
11 | 1 file changed, 895 insertions(+) | |
12 | ||
13 | --- /dev/null | |
14 | +++ b/security/apparmor/lsm.c | |
15 | @@ -0,0 +1,895 @@ | |
16 | +/* | |
17 | + * Copyright (C) 1998-2007 Novell/SUSE | |
18 | + * | |
19 | + * This program is free software; you can redistribute it and/or | |
20 | + * modify it under the terms of the GNU General Public License as | |
21 | + * published by the Free Software Foundation, version 2 of the | |
22 | + * License. | |
23 | + * | |
24 | + * AppArmor LSM interface | |
25 | + */ | |
26 | + | |
27 | +#include <linux/security.h> | |
28 | +#include <linux/module.h> | |
29 | +#include <linux/mm.h> | |
30 | +#include <linux/mman.h> | |
31 | +#include <linux/mount.h> | |
32 | +#include <linux/namei.h> | |
33 | +#include <linux/ctype.h> | |
34 | +#include <linux/sysctl.h> | |
35 | +#include <linux/audit.h> | |
36 | + | |
37 | +#include "apparmor.h" | |
38 | +#include "inline.h" | |
39 | + | |
40 | +/* Flag indicating whether initialization completed */ | |
41 | +int apparmor_initialized = 0; | |
42 | + | |
43 | +static int param_set_aabool(const char *val, struct kernel_param *kp); | |
44 | +static int param_get_aabool(char *buffer, struct kernel_param *kp); | |
45 | +#define param_check_aabool(name, p) __param_check(name, p, int) | |
46 | + | |
47 | +static int param_set_aauint(const char *val, struct kernel_param *kp); | |
48 | +static int param_get_aauint(char *buffer, struct kernel_param *kp); | |
49 | +#define param_check_aauint(name, p) __param_check(name, p, int) | |
50 | + | |
51 | +/* Flag values, also controllable via /sys/module/apparmor/parameters | |
52 | + * We define special types as we want to do additional mediation. | |
53 | + * | |
54 | + * Complain mode -- in complain mode access failures result in auditing only | |
55 | + * and task is allowed access. audit events are processed by userspace to | |
56 | + * generate policy. Default is 'enforce' (0). | |
57 | + * Value is also togglable per profile and referenced when global value is | |
58 | + * enforce. | |
59 | + */ | |
60 | +int apparmor_complain = 0; | |
61 | +module_param_named(complain, apparmor_complain, aabool, S_IRUSR | S_IWUSR); | |
62 | +MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode"); | |
63 | + | |
64 | +/* Debug mode */ | |
65 | +int apparmor_debug = 0; | |
66 | +module_param_named(debug, apparmor_debug, aabool, S_IRUSR | S_IWUSR); | |
67 | +MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode"); | |
68 | + | |
69 | +/* Audit mode */ | |
70 | +int apparmor_audit = 0; | |
71 | +module_param_named(audit, apparmor_audit, aabool, S_IRUSR | S_IWUSR); | |
72 | +MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode"); | |
73 | + | |
74 | +/* Syscall logging mode */ | |
75 | +int apparmor_logsyscall = 0; | |
76 | +module_param_named(logsyscall, apparmor_logsyscall, aabool, S_IRUSR | S_IWUSR); | |
77 | +MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode"); | |
78 | + | |
79 | +/* Maximum pathname length before accesses will start getting rejected */ | |
80 | +unsigned int apparmor_path_max = 2 * PATH_MAX; | |
81 | +module_param_named(path_max, apparmor_path_max, aauint, S_IRUSR | S_IWUSR); | |
82 | +MODULE_PARM_DESC(apparmor_path_max, "Maximum pathname length allowed"); | |
83 | + | |
84 | +/* Boot time disable flag */ | |
85 | +#ifdef CONFIG_SECURITY_APPARMOR_DISABLE | |
86 | +#define AA_ENABLED_PERMS 0600 | |
87 | +#else | |
88 | +#define AA_ENABLED_PERMS 0400 | |
89 | +#endif | |
90 | +static int param_set_aa_enabled(const char *val, struct kernel_param *kp); | |
91 | +unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; | |
92 | +module_param_call(enabled, param_set_aa_enabled, param_get_aauint, | |
93 | + &apparmor_enabled, AA_ENABLED_PERMS); | |
94 | +MODULE_PARM_DESC(apparmor_enabled, "Enable/Disable Apparmor on boot"); | |
95 | + | |
96 | +static int __init apparmor_enabled_setup(char *str) | |
97 | +{ | |
98 | + apparmor_enabled = simple_strtol(str, NULL, 0); | |
99 | + return 1; | |
100 | +} | |
101 | +__setup("apparmor=", apparmor_enabled_setup); | |
102 | + | |
103 | +static int param_set_aabool(const char *val, struct kernel_param *kp) | |
104 | +{ | |
105 | + if (aa_task_context(current)) | |
106 | + return -EPERM; | |
107 | + return param_set_bool(val, kp); | |
108 | +} | |
109 | + | |
110 | +static int param_get_aabool(char *buffer, struct kernel_param *kp) | |
111 | +{ | |
112 | + if (aa_task_context(current)) | |
113 | + return -EPERM; | |
114 | + return param_get_bool(buffer, kp); | |
115 | +} | |
116 | + | |
117 | +static int param_set_aauint(const char *val, struct kernel_param *kp) | |
118 | +{ | |
119 | + if (aa_task_context(current)) | |
120 | + return -EPERM; | |
121 | + return param_set_uint(val, kp); | |
122 | +} | |
123 | + | |
124 | +static int param_get_aauint(char *buffer, struct kernel_param *kp) | |
125 | +{ | |
126 | + if (aa_task_context(current)) | |
127 | + return -EPERM; | |
128 | + return param_get_uint(buffer, kp); | |
129 | +} | |
130 | + | |
131 | +/* allow run time disabling of apparmor */ | |
132 | +static int param_set_aa_enabled(const char *val, struct kernel_param *kp) | |
133 | +{ | |
134 | + char *endp; | |
135 | + unsigned long l; | |
136 | + | |
137 | + if (!apparmor_initialized) { | |
138 | + apparmor_enabled = 0; | |
139 | + return 0; | |
140 | + } | |
141 | + | |
142 | + if (aa_task_context(current)) | |
143 | + return -EPERM; | |
144 | + | |
145 | + if (!apparmor_enabled) | |
146 | + return -EINVAL; | |
147 | + | |
148 | + if (!val) | |
149 | + return -EINVAL; | |
150 | + | |
151 | + l = simple_strtoul(val, &endp, 0); | |
152 | + if (endp == val || l != 0) | |
153 | + return -EINVAL; | |
154 | + | |
155 | + apparmor_enabled = 0; | |
156 | + apparmor_disable(); | |
157 | + return 0; | |
158 | +} | |
159 | + | |
160 | +static int aa_reject_syscall(struct task_struct *task, gfp_t flags, | |
161 | + const char *name) | |
162 | +{ | |
163 | + struct aa_profile *profile = aa_get_profile(task); | |
164 | + int error = 0; | |
165 | + | |
166 | + if (profile) { | |
167 | + error = aa_audit_syscallreject(profile, flags, name); | |
168 | + aa_put_profile(profile); | |
169 | + } | |
170 | + | |
171 | + return error; | |
172 | +} | |
173 | + | |
174 | +static int apparmor_ptrace(struct task_struct *parent, | |
175 | + struct task_struct *child, unsigned int mode) | |
176 | +{ | |
177 | + struct aa_task_context *cxt; | |
178 | + int error = 0; | |
179 | + | |
180 | + /* | |
181 | + * parent can ptrace child when | |
182 | + * - parent is unconfined | |
183 | + * - parent & child are in the same namespace && | |
184 | + * - parent is in complain mode | |
185 | + * - parent and child are confined by the same profile | |
186 | + * - parent profile has CAP_SYS_PTRACE | |
187 | + */ | |
188 | + | |
189 | + rcu_read_lock(); | |
190 | + cxt = aa_task_context(parent); | |
191 | + if (cxt) { | |
192 | + if (parent->nsproxy != child->nsproxy) { | |
193 | + struct aa_audit sa; | |
194 | + memset(&sa, 0, sizeof(sa)); | |
195 | + sa.operation = "ptrace"; | |
196 | + sa.gfp_mask = GFP_ATOMIC; | |
197 | + sa.parent = parent->pid; | |
198 | + sa.task = child->pid; | |
199 | + sa.info = "different namespaces"; | |
200 | + aa_audit_reject(cxt->profile, &sa); | |
201 | + error = -EPERM; | |
202 | + } else { | |
203 | + struct aa_task_context *child_cxt = | |
204 | + aa_task_context(child); | |
205 | + | |
206 | + error = aa_may_ptrace(cxt, child_cxt ? | |
207 | + child_cxt->profile : NULL); | |
208 | + if (PROFILE_COMPLAIN(cxt->profile)) { | |
209 | + struct aa_audit sa; | |
210 | + memset(&sa, 0, sizeof(sa)); | |
211 | + sa.operation = "ptrace"; | |
212 | + sa.gfp_mask = GFP_ATOMIC; | |
213 | + sa.parent = parent->pid; | |
214 | + sa.task = child->pid; | |
215 | + aa_audit_hint(cxt->profile, &sa); | |
216 | + } | |
217 | + } | |
218 | + } | |
219 | + rcu_read_unlock(); | |
220 | + | |
221 | + return error; | |
222 | +} | |
223 | + | |
224 | +static int apparmor_capable(struct task_struct *task, int cap) | |
225 | +{ | |
226 | + int error; | |
227 | + struct aa_task_context *cxt; | |
228 | + | |
229 | + /* cap_capable returns 0 on success, else -EPERM */ | |
230 | + error = cap_capable(task, cap); | |
231 | + | |
232 | + rcu_read_lock(); | |
233 | + cxt = aa_task_context(task); | |
234 | + if (cxt && (!error || cap_raised(cxt->profile->set_caps, cap))) | |
235 | + error = aa_capability(cxt, cap); | |
236 | + rcu_read_unlock(); | |
237 | + | |
238 | + return error; | |
239 | +} | |
240 | + | |
241 | +static int apparmor_sysctl(struct ctl_table *table, int op) | |
242 | +{ | |
243 | + struct aa_profile *profile = aa_get_profile(current); | |
244 | + int error = 0; | |
245 | + | |
246 | + if (profile) { | |
247 | + char *buffer, *name; | |
248 | + int mask; | |
249 | + | |
250 | + mask = 0; | |
251 | + if (op & 4) | |
252 | + mask |= MAY_READ; | |
253 | + if (op & 2) | |
254 | + mask |= MAY_WRITE; | |
255 | + | |
256 | + error = -ENOMEM; | |
257 | + buffer = (char*)__get_free_page(GFP_KERNEL); | |
258 | + if (!buffer) | |
259 | + goto out; | |
260 | + name = sysctl_pathname(table, buffer, PAGE_SIZE); | |
261 | + if (name && name - buffer >= 5) { | |
262 | + name -= 5; | |
263 | + memcpy(name, "/proc", 5); | |
264 | + error = aa_perm_path(profile, "sysctl", name, mask, 0); | |
265 | + } | |
266 | + free_page((unsigned long)buffer); | |
267 | + } | |
268 | + | |
269 | +out: | |
270 | + aa_put_profile(profile); | |
271 | + return error; | |
272 | +} | |
273 | + | |
274 | +static int apparmor_bprm_set_security(struct linux_binprm *bprm) | |
275 | +{ | |
276 | + /* handle capability bits with setuid, etc */ | |
277 | + cap_bprm_set_security(bprm); | |
278 | + /* already set based on script name */ | |
279 | + if (bprm->sh_bang) | |
280 | + return 0; | |
281 | + return aa_register(bprm); | |
282 | +} | |
283 | + | |
284 | +static int apparmor_bprm_secureexec(struct linux_binprm *bprm) | |
285 | +{ | |
286 | + int ret = cap_bprm_secureexec(bprm); | |
287 | + | |
288 | + if (!ret && (unsigned long)bprm->security & AA_SECURE_EXEC_NEEDED) { | |
289 | + AA_DEBUG("%s: secureexec required for %s\n", | |
290 | + __FUNCTION__, bprm->filename); | |
291 | + ret = 1; | |
292 | + } | |
293 | + | |
294 | + return ret; | |
295 | +} | |
296 | + | |
297 | +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, | |
298 | + unsigned long flags, void *data) | |
299 | +{ | |
300 | + return aa_reject_syscall(current, GFP_KERNEL, "mount"); | |
301 | +} | |
302 | + | |
303 | +static int apparmor_umount(struct vfsmount *mnt, int flags) | |
304 | +{ | |
305 | + return aa_reject_syscall(current, GFP_KERNEL, "umount"); | |
306 | +} | |
307 | + | |
308 | +static int apparmor_inode_mkdir(struct inode *dir, struct dentry *dentry, | |
309 | + struct vfsmount *mnt, int mask) | |
310 | +{ | |
311 | + struct aa_profile *profile; | |
312 | + int error = 0; | |
313 | + | |
314 | + if (!mnt || !mediated_filesystem(dir)) | |
315 | + goto out; | |
316 | + | |
317 | + profile = aa_get_profile(current); | |
318 | + | |
319 | + if (profile) | |
320 | + error = aa_perm_dir(profile, "inode_mkdir", dentry, mnt, | |
321 | + MAY_WRITE); | |
322 | + | |
323 | + aa_put_profile(profile); | |
324 | + | |
325 | +out: | |
326 | + return error; | |
327 | +} | |
328 | + | |
329 | +static int apparmor_inode_rmdir(struct inode *dir, struct dentry *dentry, | |
330 | + struct vfsmount *mnt) | |
331 | +{ | |
332 | + struct aa_profile *profile; | |
333 | + int error = 0; | |
334 | + | |
335 | + if (!mnt || !mediated_filesystem(dir)) | |
336 | + goto out; | |
337 | + | |
338 | + profile = aa_get_profile(current); | |
339 | + | |
340 | + if (profile) | |
341 | + error = aa_perm_dir(profile, "inode_rmdir", dentry, mnt, | |
342 | + MAY_WRITE); | |
343 | + | |
344 | + aa_put_profile(profile); | |
345 | + | |
346 | +out: | |
347 | + return error; | |
348 | +} | |
349 | + | |
350 | +static int aa_permission(const char *operation, struct inode *inode, | |
351 | + struct dentry *dentry, struct vfsmount *mnt, | |
352 | + int mask, int check) | |
353 | +{ | |
354 | + int error = 0; | |
355 | + | |
356 | + if (mnt && mediated_filesystem(inode)) { | |
357 | + struct aa_profile *profile; | |
358 | + | |
359 | + profile = aa_get_profile(current); | |
360 | + if (profile) | |
361 | + error = aa_perm(profile, operation, dentry, mnt, mask, | |
362 | + check); | |
363 | + aa_put_profile(profile); | |
364 | + } | |
365 | + return error; | |
366 | +} | |
367 | + | |
368 | +static inline int aa_mask_permissions(int mask) | |
369 | +{ | |
370 | + if (mask & MAY_APPEND) | |
371 | + mask &= (MAY_READ | MAY_APPEND | MAY_EXEC); | |
372 | + else | |
373 | + mask &= (MAY_READ | MAY_WRITE | MAY_EXEC); | |
374 | + return mask; | |
375 | +} | |
376 | + | |
377 | +static int apparmor_inode_create(struct inode *dir, struct dentry *dentry, | |
378 | + struct vfsmount *mnt, int mask) | |
379 | +{ | |
380 | + return aa_permission("inode_create", dir, dentry, mnt, MAY_APPEND, 0); | |
381 | +} | |
382 | + | |
383 | +static int apparmor_inode_link(struct dentry *old_dentry, | |
384 | + struct vfsmount *old_mnt, struct inode *dir, | |
385 | + struct dentry *new_dentry, | |
386 | + struct vfsmount *new_mnt) | |
387 | +{ | |
388 | + int error = 0; | |
389 | + struct aa_profile *profile; | |
390 | + | |
391 | + if (!old_mnt || !new_mnt || !mediated_filesystem(dir)) | |
392 | + goto out; | |
393 | + | |
394 | + profile = aa_get_profile(current); | |
395 | + | |
396 | + if (profile) | |
397 | + error = aa_link(profile, new_dentry, new_mnt, | |
398 | + old_dentry, old_mnt); | |
399 | + | |
400 | + aa_put_profile(profile); | |
401 | + | |
402 | +out: | |
403 | + return error; | |
404 | +} | |
405 | + | |
406 | +static int apparmor_inode_unlink(struct inode *dir, struct dentry *dentry, | |
407 | + struct vfsmount *mnt) | |
408 | +{ | |
409 | + int check = 0; | |
410 | + | |
411 | + if (S_ISDIR(dentry->d_inode->i_mode)) | |
412 | + check |= AA_CHECK_DIR; | |
413 | + return aa_permission("inode_unlink", dir, dentry, mnt, MAY_WRITE, | |
414 | + check); | |
415 | +} | |
416 | + | |
417 | +static int apparmor_inode_symlink(struct inode *dir, struct dentry *dentry, | |
418 | + struct vfsmount *mnt, const char *old_name) | |
419 | +{ | |
420 | + return aa_permission("inode_symlink", dir, dentry, mnt, MAY_WRITE, 0); | |
421 | +} | |
422 | + | |
423 | +static int apparmor_inode_mknod(struct inode *dir, struct dentry *dentry, | |
424 | + struct vfsmount *mnt, int mode, dev_t dev) | |
425 | +{ | |
426 | + return aa_permission("inode_mknod", dir, dentry, mnt, MAY_WRITE, 0); | |
427 | +} | |
428 | + | |
429 | +static int apparmor_inode_rename(struct inode *old_dir, | |
430 | + struct dentry *old_dentry, | |
431 | + struct vfsmount *old_mnt, | |
432 | + struct inode *new_dir, | |
433 | + struct dentry *new_dentry, | |
434 | + struct vfsmount *new_mnt) | |
435 | +{ | |
436 | + struct aa_profile *profile; | |
437 | + int error = 0; | |
438 | + | |
439 | + if ((!old_mnt && !new_mnt) || !mediated_filesystem(old_dir)) | |
440 | + goto out; | |
441 | + | |
442 | + profile = aa_get_profile(current); | |
443 | + | |
444 | + if (profile) { | |
445 | + struct inode *inode = old_dentry->d_inode; | |
446 | + int check = 0; | |
447 | + | |
448 | + if (inode && S_ISDIR(inode->i_mode)) | |
449 | + check |= AA_CHECK_DIR; | |
450 | + if (old_mnt) | |
451 | + error = aa_perm(profile, "inode_rename", old_dentry, | |
452 | + old_mnt, MAY_READ | MAY_WRITE, check); | |
453 | + | |
454 | + if (!error && new_mnt) { | |
455 | + error = aa_perm(profile, "inode_rename", new_dentry, | |
456 | + new_mnt, MAY_WRITE, check); | |
457 | + } | |
458 | + } | |
459 | + | |
460 | + aa_put_profile(profile); | |
461 | + | |
462 | +out: | |
463 | + return error; | |
464 | +} | |
465 | + | |
466 | +static int apparmor_inode_permission(struct inode *inode, int mask, | |
467 | + struct nameidata *nd) | |
468 | +{ | |
469 | + int check = 0; | |
470 | + | |
471 | + if (!nd || nd->flags & (LOOKUP_PARENT | LOOKUP_CONTINUE)) | |
472 | + return 0; | |
473 | + mask = aa_mask_permissions(mask); | |
474 | + if (S_ISDIR(inode->i_mode)) { | |
475 | + check |= AA_CHECK_DIR; | |
476 | + /* allow traverse accesses to directories */ | |
477 | + mask &= ~MAY_EXEC; | |
478 | + } | |
479 | + return aa_permission("inode_permission", inode, nd->dentry, nd->mnt, | |
480 | + mask, check); | |
481 | +} | |
482 | + | |
483 | +static int apparmor_inode_setattr(struct dentry *dentry, struct vfsmount *mnt, | |
484 | + struct iattr *iattr) | |
485 | +{ | |
486 | + int error = 0; | |
487 | + | |
488 | + if (!mnt) | |
489 | + goto out; | |
490 | + | |
491 | + if (mediated_filesystem(dentry->d_inode)) { | |
492 | + struct aa_profile *profile; | |
493 | + | |
494 | + profile = aa_get_profile(current); | |
495 | + /* | |
496 | + * Mediate any attempt to change attributes of a file | |
497 | + * (chmod, chown, chgrp, etc) | |
498 | + */ | |
499 | + if (profile) | |
500 | + error = aa_attr(profile, dentry, mnt, iattr); | |
501 | + | |
502 | + aa_put_profile(profile); | |
503 | + } | |
504 | + | |
505 | +out: | |
506 | + return error; | |
507 | +} | |
508 | + | |
509 | +static int aa_xattr_permission(struct dentry *dentry, struct vfsmount *mnt, | |
510 | + const char *operation, int mask, | |
511 | + struct file *file) | |
512 | +{ | |
513 | + int error = 0; | |
514 | + | |
515 | + if (mnt && mediated_filesystem(dentry->d_inode)) { | |
516 | + struct aa_profile *profile = aa_get_profile(current); | |
517 | + int check = file ? AA_CHECK_FD : 0; | |
518 | + | |
519 | + if (profile) | |
520 | + error = aa_perm_xattr(profile, operation, dentry, mnt, | |
521 | + mask, check); | |
522 | + aa_put_profile(profile); | |
523 | + } | |
524 | + | |
525 | + return error; | |
526 | +} | |
527 | + | |
528 | +static int apparmor_inode_setxattr(struct dentry *dentry, struct vfsmount *mnt, | |
529 | + const char *name, const void *value, | |
530 | + size_t size, int flags, struct file *file) | |
531 | +{ | |
532 | + int error = cap_inode_setxattr(dentry, mnt, name, value, size, flags, | |
533 | + file); | |
534 | + | |
535 | + if (!error) | |
536 | + error = aa_xattr_permission(dentry, mnt, "xattr set", | |
537 | + MAY_WRITE, file); | |
538 | + return error; | |
539 | +} | |
540 | + | |
541 | +static int apparmor_inode_getxattr(struct dentry *dentry, struct vfsmount *mnt, | |
542 | + const char *name, struct file *file) | |
543 | +{ | |
544 | + return aa_xattr_permission(dentry, mnt, "xattr get", MAY_READ, file); | |
545 | +} | |
546 | + | |
547 | +static int apparmor_inode_listxattr(struct dentry *dentry, struct vfsmount *mnt, | |
548 | + struct file *file) | |
549 | +{ | |
550 | + return aa_xattr_permission(dentry, mnt, "xattr list", MAY_READ, file); | |
551 | +} | |
552 | + | |
553 | +static int apparmor_inode_removexattr(struct dentry *dentry, | |
554 | + struct vfsmount *mnt, const char *name, | |
555 | + struct file *file) | |
556 | +{ | |
557 | + return aa_xattr_permission(dentry, mnt, "xattr remove", MAY_WRITE, | |
558 | + file); | |
559 | +} | |
560 | + | |
561 | +static int aa_file_permission(const char *op, struct file *file, int mask) | |
562 | +{ | |
563 | + struct aa_profile *profile; | |
564 | + struct aa_profile *file_profile = (struct aa_profile*)file->f_security; | |
565 | + int error = 0; | |
566 | + | |
567 | + if (!file_profile) | |
568 | + goto out; | |
569 | + | |
570 | + /* | |
571 | + * If this file was opened under a different profile, we | |
572 | + * revalidate the access against the current profile. | |
573 | + */ | |
574 | + profile = aa_get_profile(current); | |
575 | + if (profile && (file_profile != profile || mask & AA_MAY_LOCK)) { | |
576 | + struct dentry *dentry = file->f_dentry; | |
577 | + struct vfsmount *mnt = file->f_vfsmnt; | |
578 | + struct inode *inode = dentry->d_inode; | |
579 | + int check = AA_CHECK_FD; | |
580 | + | |
581 | + /* | |
582 | + * FIXME: We should remember which profiles we revalidated | |
583 | + * against. | |
584 | + */ | |
585 | + if (S_ISDIR(inode->i_mode)) | |
586 | + check |= AA_CHECK_DIR; | |
587 | + error = aa_permission(op, inode, dentry, mnt, mask, check); | |
588 | + } | |
589 | + aa_put_profile(profile); | |
590 | + | |
591 | +out: | |
592 | + return error; | |
593 | +} | |
594 | + | |
595 | +static int apparmor_file_permission(struct file *file, int mask) | |
596 | +{ | |
597 | + return aa_file_permission("file_permission", file, | |
598 | + aa_mask_permissions(mask)); | |
599 | +} | |
600 | + | |
601 | +static inline int apparmor_file_lock (struct file *file, unsigned int cmd) | |
602 | +{ | |
603 | + int mask = AA_MAY_LOCK; | |
604 | + if (cmd == F_WRLCK) | |
605 | + mask |= MAY_WRITE; | |
606 | + return aa_file_permission("file_lock", file, mask); | |
607 | +} | |
608 | + | |
609 | +static int apparmor_file_alloc_security(struct file *file) | |
610 | +{ | |
611 | + struct aa_profile *profile; | |
612 | + | |
613 | + profile = aa_get_profile(current); | |
614 | + if (profile) | |
615 | + file->f_security = profile; | |
616 | + | |
617 | + return 0; | |
618 | +} | |
619 | + | |
620 | +static void apparmor_file_free_security(struct file *file) | |
621 | +{ | |
622 | + struct aa_profile *file_profile = (struct aa_profile*)file->f_security; | |
623 | + | |
624 | + aa_put_profile(file_profile); | |
625 | +} | |
626 | + | |
627 | +static inline int aa_mmap(struct file *file, const char *operation, | |
628 | + unsigned long prot, unsigned long flags) | |
629 | +{ | |
630 | + struct dentry *dentry; | |
631 | + int mask = 0; | |
632 | + | |
633 | + if (!file || !file->f_security) | |
634 | + return 0; | |
635 | + | |
636 | + if (prot & PROT_READ) | |
637 | + mask |= MAY_READ; | |
638 | + /* Private mappings don't require write perms since they don't | |
639 | + * write back to the files */ | |
640 | + if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE)) | |
641 | + mask |= MAY_WRITE; | |
642 | + if (prot & PROT_EXEC) | |
643 | + mask |= AA_EXEC_MMAP; | |
644 | + | |
645 | + dentry = file->f_dentry; | |
646 | + return aa_permission(operation, dentry->d_inode, dentry, | |
647 | + file->f_vfsmnt, mask, AA_CHECK_FD); | |
648 | +} | |
649 | + | |
650 | +static int apparmor_file_mmap(struct file *file, unsigned long reqprot, | |
651 | + unsigned long prot, unsigned long flags, | |
652 | + unsigned long addr, unsigned long addr_only) | |
653 | +{ | |
654 | + if ((addr < mmap_min_addr) && !capable(CAP_SYS_RAWIO)) { | |
655 | + struct aa_profile *profile = aa_get_profile(current); | |
656 | + if (profile) | |
657 | + /* future control check here */ | |
658 | + return -EACCES; | |
659 | + else | |
660 | + return -EACCES; | |
661 | + aa_put_profile(profile); | |
662 | + } | |
663 | + | |
664 | + return aa_mmap(file, "file_mmap", prot, flags); | |
665 | +} | |
666 | + | |
667 | +static int apparmor_file_mprotect(struct vm_area_struct *vma, | |
668 | + unsigned long reqprot, unsigned long prot) | |
669 | +{ | |
670 | + return aa_mmap(vma->vm_file, "file_mprotect", prot, | |
671 | + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); | |
672 | +} | |
673 | + | |
674 | +static int apparmor_task_alloc_security(struct task_struct *task) | |
675 | +{ | |
676 | + return aa_clone(task); | |
677 | +} | |
678 | + | |
679 | +/* | |
680 | + * Called from IRQ context from RCU callback. | |
681 | + */ | |
682 | +static void apparmor_task_free_security(struct task_struct *task) | |
683 | +{ | |
684 | + aa_release(task); | |
685 | +} | |
686 | + | |
687 | +static int apparmor_getprocattr(struct task_struct *task, char *name, | |
688 | + char **value) | |
689 | +{ | |
690 | + unsigned len; | |
691 | + int error; | |
692 | + struct aa_profile *profile; | |
693 | + | |
694 | + /* AppArmor only supports the "current" process attribute */ | |
695 | + if (strcmp(name, "current") != 0) | |
696 | + return -EINVAL; | |
697 | + | |
698 | + /* must be task querying itself or admin */ | |
699 | + if (current != task && !capable(CAP_SYS_ADMIN)) | |
700 | + return -EPERM; | |
701 | + | |
702 | + profile = aa_get_profile(task); | |
703 | + error = aa_getprocattr(profile, value, &len); | |
704 | + aa_put_profile(profile); | |
705 | + if (!error) | |
706 | + error = len; | |
707 | + | |
708 | + return error; | |
709 | +} | |
710 | + | |
711 | +static int apparmor_setprocattr(struct task_struct *task, char *name, | |
712 | + void *value, size_t size) | |
713 | +{ | |
714 | + char *command, *args; | |
715 | + int error; | |
716 | + | |
717 | + if (strcmp(name, "current") != 0 || size == 0 || size >= PAGE_SIZE) | |
718 | + return -EINVAL; | |
719 | + args = value; | |
720 | + args[size] = '\0'; | |
721 | + args = strstrip(args); | |
722 | + command = strsep(&args, " "); | |
723 | + if (!args) | |
724 | + return -EINVAL; | |
725 | + while (isspace(*args)) | |
726 | + args++; | |
727 | + if (!*args) | |
728 | + return -EINVAL; | |
729 | + | |
730 | + if (strcmp(command, "changehat") == 0) { | |
731 | + if (current != task) | |
732 | + return -EACCES; | |
733 | + error = aa_setprocattr_changehat(args); | |
734 | + } else if (strcmp(command, "changeprofile") == 0) { | |
735 | + if (current != task) | |
736 | + return -EACCES; | |
737 | + error = aa_setprocattr_changeprofile(args); | |
738 | + } else if (strcmp(command, "setprofile") == 0) { | |
739 | + struct aa_profile *profile; | |
740 | + | |
741 | + /* Only an unconfined process with admin capabilities | |
742 | + * may change the profile of another task. | |
743 | + */ | |
744 | + | |
745 | + if (!capable(CAP_SYS_ADMIN)) | |
746 | + return -EACCES; | |
747 | + | |
748 | + profile = aa_get_profile(current); | |
749 | + if (profile) { | |
750 | + struct aa_audit sa; | |
751 | + memset(&sa, 0, sizeof(sa)); | |
752 | + sa.operation = "profile_set"; | |
753 | + sa.gfp_mask = GFP_KERNEL; | |
754 | + sa.task = task->pid; | |
755 | + sa.info = "from confined process"; | |
756 | + aa_audit_reject(profile, &sa); | |
757 | + aa_put_profile(profile); | |
758 | + return -EACCES; | |
759 | + } | |
760 | + error = aa_setprocattr_setprofile(task, args); | |
761 | + } else { | |
762 | + struct aa_audit sa; | |
763 | + memset(&sa, 0, sizeof(sa)); | |
764 | + sa.operation = "setprocattr"; | |
765 | + sa.gfp_mask = GFP_KERNEL; | |
766 | + sa.info = "invalid command"; | |
767 | + sa.name = command; | |
768 | + sa.task = task->pid; | |
769 | + aa_audit_reject(NULL, &sa); | |
770 | + return -EINVAL; | |
771 | + } | |
772 | + | |
773 | + if (!error) | |
774 | + error = size; | |
775 | + return error; | |
776 | +} | |
777 | + | |
778 | +struct security_operations apparmor_ops = { | |
779 | + .ptrace = apparmor_ptrace, | |
780 | + .capget = cap_capget, | |
781 | + .capset_check = cap_capset_check, | |
782 | + .capset_set = cap_capset_set, | |
783 | + .sysctl = apparmor_sysctl, | |
784 | + .capable = apparmor_capable, | |
785 | + .syslog = cap_syslog, | |
786 | + | |
787 | + .netlink_send = cap_netlink_send, | |
788 | + .netlink_recv = cap_netlink_recv, | |
789 | + | |
790 | + .bprm_apply_creds = cap_bprm_apply_creds, | |
791 | + .bprm_set_security = apparmor_bprm_set_security, | |
792 | + .bprm_secureexec = apparmor_bprm_secureexec, | |
793 | + | |
794 | + .sb_mount = apparmor_sb_mount, | |
795 | + .sb_umount = apparmor_umount, | |
796 | + | |
797 | + .inode_mkdir = apparmor_inode_mkdir, | |
798 | + .inode_rmdir = apparmor_inode_rmdir, | |
799 | + .inode_create = apparmor_inode_create, | |
800 | + .inode_link = apparmor_inode_link, | |
801 | + .inode_unlink = apparmor_inode_unlink, | |
802 | + .inode_symlink = apparmor_inode_symlink, | |
803 | + .inode_mknod = apparmor_inode_mknod, | |
804 | + .inode_rename = apparmor_inode_rename, | |
805 | + .inode_permission = apparmor_inode_permission, | |
806 | + .inode_setattr = apparmor_inode_setattr, | |
807 | + .inode_setxattr = apparmor_inode_setxattr, | |
808 | + .inode_getxattr = apparmor_inode_getxattr, | |
809 | + .inode_listxattr = apparmor_inode_listxattr, | |
810 | + .inode_removexattr = apparmor_inode_removexattr, | |
811 | + .file_permission = apparmor_file_permission, | |
812 | + .file_alloc_security = apparmor_file_alloc_security, | |
813 | + .file_free_security = apparmor_file_free_security, | |
814 | + .file_mmap = apparmor_file_mmap, | |
815 | + .file_mprotect = apparmor_file_mprotect, | |
816 | + .file_lock = apparmor_file_lock, | |
817 | + | |
818 | + .task_alloc_security = apparmor_task_alloc_security, | |
819 | + .task_free_security = apparmor_task_free_security, | |
820 | + .task_post_setuid = cap_task_post_setuid, | |
821 | + .task_reparent_to_init = cap_task_reparent_to_init, | |
822 | + | |
823 | + .getprocattr = apparmor_getprocattr, | |
824 | + .setprocattr = apparmor_setprocattr, | |
825 | +}; | |
826 | + | |
827 | +void info_message(const char *str) | |
828 | +{ | |
829 | + struct aa_audit sa; | |
830 | + memset(&sa, 0, sizeof(sa)); | |
831 | + sa.gfp_mask = GFP_KERNEL; | |
832 | + sa.info = str; | |
833 | + printk(KERN_INFO "AppArmor: %s\n", str); | |
834 | + if (audit_enabled) | |
835 | + aa_audit_message(NULL, &sa, AUDIT_APPARMOR_STATUS); | |
836 | +} | |
837 | + | |
838 | +static int __init apparmor_init(void) | |
839 | +{ | |
840 | + int error; | |
841 | + | |
842 | + if (!apparmor_enabled) { | |
843 | + info_message("AppArmor disabled by boottime parameter\n"); | |
844 | + return 0; | |
845 | + } | |
846 | + | |
847 | + if ((error = create_apparmorfs())) { | |
848 | + AA_ERROR("Unable to activate AppArmor filesystem\n"); | |
849 | + goto createfs_out; | |
850 | + } | |
851 | + | |
852 | + if ((error = alloc_default_namespace())){ | |
853 | + AA_ERROR("Unable to allocate default profile namespace\n"); | |
854 | + goto alloc_out; | |
855 | + } | |
856 | + | |
857 | + if ((error = register_security(&apparmor_ops))) { | |
858 | + AA_ERROR("Unable to register AppArmor\n"); | |
859 | + goto register_security_out; | |
860 | + } | |
861 | + | |
862 | + /* Report that AppArmor successfully initialized */ | |
863 | + apparmor_initialized = 1; | |
864 | + if (apparmor_complain) | |
865 | + info_message("AppArmor initialized: complainmode enabled"); | |
866 | + else | |
867 | + info_message("AppArmor initialized"); | |
868 | + | |
869 | + return error; | |
870 | + | |
871 | +register_security_out: | |
872 | + free_default_namespace(); | |
873 | + | |
874 | +alloc_out: | |
875 | + destroy_apparmorfs(); | |
876 | + | |
877 | +createfs_out: | |
878 | + return error; | |
879 | + | |
880 | +} | |
881 | + | |
882 | +security_initcall(apparmor_init); | |
883 | + | |
884 | +void apparmor_disable(void) | |
885 | +{ | |
886 | + /* Remove and release all the profiles on the profile list. */ | |
887 | + mutex_lock(&aa_interface_lock); | |
888 | + aa_profile_ns_list_release(); | |
889 | + | |
890 | + /* FIXME: cleanup profiles references on files */ | |
891 | + free_default_namespace(); | |
892 | + | |
893 | + /* | |
894 | + * Delay for an rcu cycle to make sure that all active task | |
895 | + * context readers have finished, and all profiles have been | |
896 | + * freed by their rcu callbacks. | |
897 | + */ | |
898 | + synchronize_rcu(); | |
899 | + | |
900 | + destroy_apparmorfs(); | |
901 | + mutex_unlock(&aa_interface_lock); | |
902 | + | |
903 | + apparmor_initialized = 0; | |
904 | + | |
905 | + info_message("AppArmor protection removed"); | |
906 | +} | |
907 | + | |
908 | +MODULE_DESCRIPTION("AppArmor process confinement"); | |
909 | +MODULE_AUTHOR("Novell/Immunix, http://bugs.opensuse.org"); | |
910 | +MODULE_LICENSE("GPL"); |