diff options
| author | NeilBrown <neilb@suse.de> | 2010-08-09 20:02:33 -0400 |
|---|---|---|
| committer | NeilBrown <neilb@suse.de> | 2010-08-09 20:02:33 -0400 |
| commit | fd8aa2c1811bf60ccb2d5de0579c6f62aec1772d (patch) | |
| tree | 311567d03758afc3a93b4273fe172836e89bb01d /security/apparmor | |
| parent | 6e17b0276452912cb13445e5ea552b599984675f (diff) | |
| parent | 2144381da478cc4aa3a29ee29b0c5e6ddaaced14 (diff) | |
Merge git://git.infradead.org/users/dwmw2/libraid-2.6 into for-linus
Diffstat (limited to 'security/apparmor')
34 files changed, 7469 insertions, 0 deletions
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore new file mode 100644 index 000000000000..0a0a99f3b083 --- /dev/null +++ b/security/apparmor/.gitignore | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | # | ||
| 2 | # Generated include files | ||
| 3 | # | ||
| 4 | af_names.h | ||
| 5 | capability_names.h | ||
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig new file mode 100644 index 000000000000..9b9013b2e321 --- /dev/null +++ b/security/apparmor/Kconfig | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | config SECURITY_APPARMOR | ||
| 2 | bool "AppArmor support" | ||
| 3 | depends on SECURITY && NET | ||
| 4 | select AUDIT | ||
| 5 | select SECURITY_PATH | ||
| 6 | select SECURITYFS | ||
| 7 | select SECURITY_NETWORK | ||
| 8 | default n | ||
| 9 | help | ||
| 10 | This enables the AppArmor security module. | ||
| 11 | Required userspace tools (if they are not included in your | ||
| 12 | distribution) and further information may be found at | ||
| 13 | http://apparmor.wiki.kernel.org | ||
| 14 | |||
| 15 | If you are unsure how to answer this question, answer N. | ||
| 16 | |||
| 17 | config SECURITY_APPARMOR_BOOTPARAM_VALUE | ||
| 18 | int "AppArmor boot parameter default value" | ||
| 19 | depends on SECURITY_APPARMOR | ||
| 20 | range 0 1 | ||
| 21 | default 1 | ||
| 22 | help | ||
| 23 | This option sets the default value for the kernel parameter | ||
| 24 | 'apparmor', which allows AppArmor to be enabled or disabled | ||
| 25 | at boot. If this option is set to 0 (zero), the AppArmor | ||
| 26 | kernel parameter will default to 0, disabling AppArmor at | ||
| 27 | boot. If this option is set to 1 (one), the AppArmor | ||
| 28 | kernel parameter will default to 1, enabling AppArmor at | ||
| 29 | boot. | ||
| 30 | |||
| 31 | If you are unsure how to answer this question, answer 1. | ||
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile new file mode 100644 index 000000000000..f204869399ea --- /dev/null +++ b/security/apparmor/Makefile | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | # Makefile for AppArmor Linux Security Module | ||
| 2 | # | ||
| 3 | obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o | ||
| 4 | |||
| 5 | apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ | ||
| 6 | path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ | ||
| 7 | resource.o sid.o file.o | ||
| 8 | |||
| 9 | clean-files: capability_names.h af_names.h | ||
| 10 | |||
| 11 | quiet_cmd_make-caps = GEN $@ | ||
| 12 | cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; sed -n -e "/CAP_FS_MASK/d" -e "s/^\#define[ \\t]\\+CAP_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ | ||
| 13 | |||
| 14 | quiet_cmd_make-rlim = GEN $@ | ||
| 15 | cmd_make-rlim = echo "static const char *rlim_names[] = {" > $@ ; sed -n --e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+RLIMIT_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ ; echo "static const int rlim_map[] = {" >> $@ ; sed -n -e "/AF_MAX/d" -e "s/^\# \\?define[ \\t]\\+\\(RLIMIT_[A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/\\1,/p" $< >> $@ ; echo "};" >> $@ | ||
| 16 | |||
| 17 | $(obj)/capability.o : $(obj)/capability_names.h | ||
| 18 | $(obj)/resource.o : $(obj)/rlim_names.h | ||
| 19 | $(obj)/capability_names.h : $(srctree)/include/linux/capability.h | ||
| 20 | $(call cmd,make-caps) | ||
| 21 | $(obj)/af_names.h : $(srctree)/include/linux/socket.h | ||
| 22 | $(call cmd,make-af) | ||
| 23 | $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h | ||
| 24 | $(call cmd,make-rlim) | ||
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c new file mode 100644 index 000000000000..7320331b44ab --- /dev/null +++ b/security/apparmor/apparmorfs.c | |||
| @@ -0,0 +1,239 @@ | |||
| 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 | |||
| 15 | #include <linux/security.h> | ||
| 16 | #include <linux/vmalloc.h> | ||
| 17 | #include <linux/module.h> | ||
| 18 | #include <linux/seq_file.h> | ||
| 19 | #include <linux/uaccess.h> | ||
| 20 | #include <linux/namei.h> | ||
| 21 | |||
| 22 | #include "include/apparmor.h" | ||
| 23 | #include "include/apparmorfs.h" | ||
| 24 | #include "include/audit.h" | ||
| 25 | #include "include/context.h" | ||
| 26 | #include "include/policy.h" | ||
| 27 | |||
| 28 | /** | ||
| 29 | * aa_simple_write_to_buffer - common routine for getting policy from user | ||
| 30 | * @op: operation doing the user buffer copy | ||
| 31 | * @userbuf: user buffer to copy data from (NOT NULL) | ||
| 32 | * @alloc_size: size of user buffer | ||
| 33 | * @copy_size: size of data to copy from user buffer | ||
| 34 | * @pos: position write is at in the file (NOT NULL) | ||
| 35 | * | ||
| 36 | * Returns: kernel buffer containing copy of user buffer data or an | ||
| 37 | * ERR_PTR on failure. | ||
| 38 | */ | ||
| 39 | static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, | ||
| 40 | size_t alloc_size, size_t copy_size, | ||
| 41 | loff_t *pos) | ||
| 42 | { | ||
| 43 | char *data; | ||
| 44 | |||
| 45 | if (*pos != 0) | ||
| 46 | /* only writes from pos 0, that is complete writes */ | ||
| 47 | return ERR_PTR(-ESPIPE); | ||
| 48 | |||
| 49 | /* | ||
| 50 | * Don't allow profile load/replace/remove from profiles that don't | ||
| 51 | * have CAP_MAC_ADMIN | ||
| 52 | */ | ||
| 53 | if (!aa_may_manage_policy(op)) | ||
| 54 | return ERR_PTR(-EACCES); | ||
| 55 | |||
| 56 | /* freed by caller to simple_write_to_buffer */ | ||
| 57 | data = kvmalloc(alloc_size); | ||
| 58 | if (data == NULL) | ||
| 59 | return ERR_PTR(-ENOMEM); | ||
| 60 | |||
| 61 | if (copy_from_user(data, userbuf, copy_size)) { | ||
| 62 | kvfree(data); | ||
| 63 | return ERR_PTR(-EFAULT); | ||
| 64 | } | ||
| 65 | |||
| 66 | return data; | ||
| 67 | } | ||
| 68 | |||
| 69 | |||
| 70 | /* .load file hook fn to load policy */ | ||
| 71 | static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, | ||
| 72 | loff_t *pos) | ||
| 73 | { | ||
| 74 | char *data; | ||
| 75 | ssize_t error; | ||
| 76 | |||
| 77 | data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); | ||
| 78 | |||
| 79 | error = PTR_ERR(data); | ||
| 80 | if (!IS_ERR(data)) { | ||
| 81 | error = aa_replace_profiles(data, size, PROF_ADD); | ||
| 82 | kvfree(data); | ||
| 83 | } | ||
| 84 | |||
| 85 | return error; | ||
| 86 | } | ||
| 87 | |||
| 88 | static const struct file_operations aa_fs_profile_load = { | ||
| 89 | .write = profile_load | ||
| 90 | }; | ||
| 91 | |||
| 92 | /* .replace file hook fn to load and/or replace policy */ | ||
| 93 | static ssize_t profile_replace(struct file *f, const char __user *buf, | ||
| 94 | size_t size, loff_t *pos) | ||
| 95 | { | ||
| 96 | char *data; | ||
| 97 | ssize_t error; | ||
| 98 | |||
| 99 | data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); | ||
| 100 | error = PTR_ERR(data); | ||
| 101 | if (!IS_ERR(data)) { | ||
| 102 | error = aa_replace_profiles(data, size, PROF_REPLACE); | ||
| 103 | kvfree(data); | ||
| 104 | } | ||
| 105 | |||
| 106 | return error; | ||
| 107 | } | ||
| 108 | |||
| 109 | static const struct file_operations aa_fs_profile_replace = { | ||
| 110 | .write = profile_replace | ||
| 111 | }; | ||
| 112 | |||
| 113 | /* .remove file hook fn to remove loaded policy */ | ||
| 114 | static ssize_t profile_remove(struct file *f, const char __user *buf, | ||
| 115 | size_t size, loff_t *pos) | ||
| 116 | { | ||
| 117 | char *data; | ||
| 118 | ssize_t error; | ||
| 119 | |||
| 120 | /* | ||
| 121 | * aa_remove_profile needs a null terminated string so 1 extra | ||
| 122 | * byte is allocated and the copied data is null terminated. | ||
| 123 | */ | ||
| 124 | data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); | ||
| 125 | |||
| 126 | error = PTR_ERR(data); | ||
| 127 | if (!IS_ERR(data)) { | ||
| 128 | data[size] = 0; | ||
| 129 | error = aa_remove_profiles(data, size); | ||
| 130 | kvfree(data); | ||
| 131 | } | ||
| 132 | |||
| 133 | return error; | ||
| 134 | } | ||
| 135 | |||
| 136 | static const struct file_operations aa_fs_profile_remove = { | ||
| 137 | .write = profile_remove | ||
| 138 | }; | ||
| 139 | |||
| 140 | /** Base file system setup **/ | ||
| 141 | |||
| 142 | static struct dentry *aa_fs_dentry __initdata; | ||
| 143 | |||
| 144 | static void __init aafs_remove(const char *name) | ||
| 145 | { | ||
| 146 | struct dentry *dentry; | ||
| 147 | |||
| 148 | dentry = lookup_one_len(name, aa_fs_dentry, strlen(name)); | ||
| 149 | if (!IS_ERR(dentry)) { | ||
| 150 | securityfs_remove(dentry); | ||
| 151 | dput(dentry); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | /** | ||
| 156 | * aafs_create - create an entry in the apparmor filesystem | ||
| 157 | * @name: name of the entry (NOT NULL) | ||
| 158 | * @mask: file permission mask of the file | ||
| 159 | * @fops: file operations for the file (NOT NULL) | ||
| 160 | * | ||
| 161 | * Used aafs_remove to remove entries created with this fn. | ||
| 162 | */ | ||
| 163 | static int __init aafs_create(const char *name, int mask, | ||
| 164 | const struct file_operations *fops) | ||
| 165 | { | ||
| 166 | struct dentry *dentry; | ||
| 167 | |||
| 168 | dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry, | ||
| 169 | NULL, fops); | ||
| 170 | |||
| 171 | return IS_ERR(dentry) ? PTR_ERR(dentry) : 0; | ||
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * aa_destroy_aafs - cleanup and free aafs | ||
| 176 | * | ||
| 177 | * releases dentries allocated by aa_create_aafs | ||
| 178 | */ | ||
| 179 | void __init aa_destroy_aafs(void) | ||
| 180 | { | ||
| 181 | if (aa_fs_dentry) { | ||
| 182 | aafs_remove(".remove"); | ||
| 183 | aafs_remove(".replace"); | ||
| 184 | aafs_remove(".load"); | ||
| 185 | |||
| 186 | securityfs_remove(aa_fs_dentry); | ||
| 187 | aa_fs_dentry = NULL; | ||
| 188 | } | ||
| 189 | } | ||
| 190 | |||
| 191 | /** | ||
| 192 | * aa_create_aafs - create the apparmor security filesystem | ||
| 193 | * | ||
| 194 | * dentries created here are released by aa_destroy_aafs | ||
| 195 | * | ||
| 196 | * Returns: error on failure | ||
| 197 | */ | ||
| 198 | int __init aa_create_aafs(void) | ||
| 199 | { | ||
| 200 | int error; | ||
| 201 | |||
| 202 | if (!apparmor_initialized) | ||
| 203 | return 0; | ||
| 204 | |||
| 205 | if (aa_fs_dentry) { | ||
| 206 | AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); | ||
| 207 | return -EEXIST; | ||
| 208 | } | ||
| 209 | |||
| 210 | aa_fs_dentry = securityfs_create_dir("apparmor", NULL); | ||
| 211 | if (IS_ERR(aa_fs_dentry)) { | ||
| 212 | error = PTR_ERR(aa_fs_dentry); | ||
| 213 | aa_fs_dentry = NULL; | ||
| 214 | goto error; | ||
| 215 | } | ||
| 216 | |||
| 217 | error = aafs_create(".load", 0640, &aa_fs_profile_load); | ||
| 218 | if (error) | ||
| 219 | goto error; | ||
| 220 | error = aafs_create(".replace", 0640, &aa_fs_profile_replace); | ||
| 221 | if (error) | ||
| 222 | goto error; | ||
| 223 | error = aafs_create(".remove", 0640, &aa_fs_profile_remove); | ||
| 224 | if (error) | ||
| 225 | goto error; | ||
| 226 | |||
| 227 | /* TODO: add support for apparmorfs_null and apparmorfs_mnt */ | ||
| 228 | |||
| 229 | /* Report that AppArmor fs is enabled */ | ||
| 230 | aa_info_message("AppArmor Filesystem Enabled"); | ||
| 231 | return 0; | ||
| 232 | |||
| 233 | error: | ||
| 234 | aa_destroy_aafs(); | ||
| 235 | AA_ERROR("Error creating AppArmor securityfs\n"); | ||
| 236 | return error; | ||
| 237 | } | ||
| 238 | |||
| 239 | fs_initcall(aa_create_aafs); | ||
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c new file mode 100644 index 000000000000..96502b22b268 --- /dev/null +++ b/security/apparmor/audit.c | |||
| @@ -0,0 +1,215 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor auditing 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 | |||
| 15 | #include <linux/audit.h> | ||
| 16 | #include <linux/socket.h> | ||
| 17 | |||
| 18 | #include "include/apparmor.h" | ||
| 19 | #include "include/audit.h" | ||
| 20 | #include "include/policy.h" | ||
| 21 | |||
| 22 | const char *op_table[] = { | ||
| 23 | "null", | ||
| 24 | |||
| 25 | "sysctl", | ||
| 26 | "capable", | ||
| 27 | |||
| 28 | "unlink", | ||
| 29 | "mkdir", | ||
| 30 | "rmdir", | ||
| 31 | "mknod", | ||
| 32 | "truncate", | ||
| 33 | "link", | ||
| 34 | "symlink", | ||
| 35 | "rename_src", | ||
| 36 | "rename_dest", | ||
| 37 | "chmod", | ||
| 38 | "chown", | ||
| 39 | "getattr", | ||
| 40 | "open", | ||
| 41 | |||
| 42 | "file_perm", | ||
| 43 | "file_lock", | ||
| 44 | "file_mmap", | ||
| 45 | "file_mprotect", | ||
| 46 | |||
| 47 | "create", | ||
| 48 | "post_create", | ||
| 49 | "bind", | ||
| 50 | "connect", | ||
| 51 | "listen", | ||
| 52 | "accept", | ||
| 53 | "sendmsg", | ||
| 54 | "recvmsg", | ||
| 55 | "getsockname", | ||
| 56 | "getpeername", | ||
| 57 | "getsockopt", | ||
| 58 | "setsockopt", | ||
| 59 | "socket_shutdown", | ||
| 60 | |||
| 61 | "ptrace", | ||
| 62 | |||
| 63 | "exec", | ||
| 64 | "change_hat", | ||
| 65 | "change_profile", | ||
| 66 | "change_onexec", | ||
| 67 | |||
| 68 | "setprocattr", | ||
| 69 | "setrlimit", | ||
| 70 | |||
| 71 | "profile_replace", | ||
| 72 | "profile_load", | ||
| 73 | "profile_remove" | ||
| 74 | }; | ||
| 75 | |||
| 76 | const char *audit_mode_names[] = { | ||
| 77 | "normal", | ||
| 78 | "quiet_denied", | ||
| 79 | "quiet", | ||
| 80 | "noquiet", | ||
| 81 | "all" | ||
| 82 | }; | ||
| 83 | |||
| 84 | static char *aa_audit_type[] = { | ||
| 85 | "AUDIT", | ||
| 86 | "ALLOWED", | ||
| 87 | "DENIED", | ||
| 88 | "HINT", | ||
| 89 | "STATUS", | ||
| 90 | "ERROR", | ||
| 91 | "KILLED" | ||
| 92 | }; | ||
| 93 | |||
| 94 | /* | ||
| 95 | * Currently AppArmor auditing is fed straight into the audit framework. | ||
| 96 | * | ||
| 97 | * TODO: | ||
| 98 | * netlink interface for complain mode | ||
| 99 | * user auditing, - send user auditing to netlink interface | ||
| 100 | * system control of whether user audit messages go to system log | ||
| 101 | */ | ||
| 102 | |||
| 103 | /** | ||
| 104 | * audit_base - core AppArmor function. | ||
| 105 | * @ab: audit buffer to fill (NOT NULL) | ||
| 106 | * @ca: audit structure containing data to audit (NOT NULL) | ||
| 107 | * | ||
| 108 | * Record common AppArmor audit data from @sa | ||
| 109 | */ | ||
| 110 | static void audit_pre(struct audit_buffer *ab, void *ca) | ||
| 111 | { | ||
| 112 | struct common_audit_data *sa = ca; | ||
| 113 | struct task_struct *tsk = sa->tsk ? sa->tsk : current; | ||
| 114 | |||
| 115 | if (aa_g_audit_header) { | ||
| 116 | audit_log_format(ab, "apparmor="); | ||
| 117 | audit_log_string(ab, aa_audit_type[sa->aad.type]); | ||
| 118 | } | ||
| 119 | |||
| 120 | if (sa->aad.op) { | ||
| 121 | audit_log_format(ab, " operation="); | ||
| 122 | audit_log_string(ab, op_table[sa->aad.op]); | ||
| 123 | } | ||
| 124 | |||
| 125 | if (sa->aad.info) { | ||
| 126 | audit_log_format(ab, " info="); | ||
| 127 | audit_log_string(ab, sa->aad.info); | ||
| 128 | if (sa->aad.error) | ||
| 129 | audit_log_format(ab, " error=%d", sa->aad.error); | ||
| 130 | } | ||
| 131 | |||
| 132 | if (sa->aad.profile) { | ||
| 133 | struct aa_profile *profile = sa->aad.profile; | ||
| 134 | pid_t pid; | ||
| 135 | rcu_read_lock(); | ||
| 136 | pid = tsk->real_parent->pid; | ||
| 137 | rcu_read_unlock(); | ||
| 138 | audit_log_format(ab, " parent=%d", pid); | ||
| 139 | if (profile->ns != root_ns) { | ||
| 140 | audit_log_format(ab, " namespace="); | ||
| 141 | audit_log_untrustedstring(ab, profile->ns->base.hname); | ||
| 142 | } | ||
| 143 | audit_log_format(ab, " profile="); | ||
| 144 | audit_log_untrustedstring(ab, profile->base.hname); | ||
| 145 | } | ||
| 146 | |||
| 147 | if (sa->aad.name) { | ||
| 148 | audit_log_format(ab, " name="); | ||
| 149 | audit_log_untrustedstring(ab, sa->aad.name); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | /** | ||
| 154 | * aa_audit_msg - Log a message to the audit subsystem | ||
| 155 | * @sa: audit event structure (NOT NULL) | ||
| 156 | * @cb: optional callback fn for type specific fields (MAYBE NULL) | ||
| 157 | */ | ||
| 158 | void aa_audit_msg(int type, struct common_audit_data *sa, | ||
| 159 | void (*cb) (struct audit_buffer *, void *)) | ||
| 160 | { | ||
| 161 | sa->aad.type = type; | ||
| 162 | sa->lsm_pre_audit = audit_pre; | ||
| 163 | sa->lsm_post_audit = cb; | ||
| 164 | common_lsm_audit(sa); | ||
| 165 | } | ||
| 166 | |||
| 167 | /** | ||
| 168 | * aa_audit - Log a profile based audit event to the audit subsystem | ||
| 169 | * @type: audit type for the message | ||
| 170 | * @profile: profile to check against (NOT NULL) | ||
| 171 | * @gfp: allocation flags to use | ||
| 172 | * @sa: audit event (NOT NULL) | ||
| 173 | * @cb: optional callback fn for type specific fields (MAYBE NULL) | ||
| 174 | * | ||
| 175 | * Handle default message switching based off of audit mode flags | ||
| 176 | * | ||
| 177 | * Returns: error on failure | ||
| 178 | */ | ||
| 179 | int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, | ||
| 180 | struct common_audit_data *sa, | ||
| 181 | void (*cb) (struct audit_buffer *, void *)) | ||
| 182 | { | ||
| 183 | BUG_ON(!profile); | ||
| 184 | |||
| 185 | if (type == AUDIT_APPARMOR_AUTO) { | ||
| 186 | if (likely(!sa->aad.error)) { | ||
| 187 | if (AUDIT_MODE(profile) != AUDIT_ALL) | ||
| 188 | return 0; | ||
| 189 | type = AUDIT_APPARMOR_AUDIT; | ||
| 190 | } else if (COMPLAIN_MODE(profile)) | ||
| 191 | type = AUDIT_APPARMOR_ALLOWED; | ||
| 192 | else | ||
| 193 | type = AUDIT_APPARMOR_DENIED; | ||
| 194 | } | ||
| 195 | if (AUDIT_MODE(profile) == AUDIT_QUIET || | ||
| 196 | (type == AUDIT_APPARMOR_DENIED && | ||
| 197 | AUDIT_MODE(profile) == AUDIT_QUIET)) | ||
| 198 | return sa->aad.error; | ||
| 199 | |||
| 200 | if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) | ||
| 201 | type = AUDIT_APPARMOR_KILL; | ||
| 202 | |||
| 203 | if (!unconfined(profile)) | ||
| 204 | sa->aad.profile = profile; | ||
| 205 | |||
| 206 | aa_audit_msg(type, sa, cb); | ||
| 207 | |||
| 208 | if (sa->aad.type == AUDIT_APPARMOR_KILL) | ||
| 209 | (void)send_sig_info(SIGKILL, NULL, sa->tsk ? sa->tsk : current); | ||
| 210 | |||
| 211 | if (sa->aad.type == AUDIT_APPARMOR_ALLOWED) | ||
| 212 | return complain_error(sa->aad.error); | ||
| 213 | |||
| 214 | return sa->aad.error; | ||
| 215 | } | ||
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c new file mode 100644 index 000000000000..9982c48def4e --- /dev/null +++ b/security/apparmor/capability.c | |||
| @@ -0,0 +1,141 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor capability mediation 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 | |||
| 15 | #include <linux/capability.h> | ||
| 16 | #include <linux/errno.h> | ||
| 17 | #include <linux/gfp.h> | ||
| 18 | |||
| 19 | #include "include/apparmor.h" | ||
| 20 | #include "include/capability.h" | ||
| 21 | #include "include/context.h" | ||
| 22 | #include "include/policy.h" | ||
| 23 | #include "include/audit.h" | ||
| 24 | |||
| 25 | /* | ||
| 26 | * Table of capability names: we generate it from capabilities.h. | ||
| 27 | */ | ||
| 28 | #include "capability_names.h" | ||
| 29 | |||
| 30 | struct audit_cache { | ||
| 31 | struct aa_profile *profile; | ||
| 32 | kernel_cap_t caps; | ||
| 33 | }; | ||
| 34 | |||
| 35 | static DEFINE_PER_CPU(struct audit_cache, audit_cache); | ||
| 36 | |||
| 37 | /** | ||
| 38 | * audit_cb - call back for capability components of audit struct | ||
| 39 | * @ab - audit buffer (NOT NULL) | ||
| 40 | * @va - audit struct to audit data from (NOT NULL) | ||
| 41 | */ | ||
| 42 | static void audit_cb(struct audit_buffer *ab, void *va) | ||
| 43 | { | ||
| 44 | struct common_audit_data *sa = va; | ||
| 45 | audit_log_format(ab, " capname="); | ||
| 46 | audit_log_untrustedstring(ab, capability_names[sa->u.cap]); | ||
| 47 | } | ||
| 48 | |||
| 49 | /** | ||
| 50 | * audit_caps - audit a capability | ||
| 51 | * @profile: profile confining task (NOT NULL) | ||
| 52 | * @task: task capability test was performed against (NOT NULL) | ||
| 53 | * @cap: capability tested | ||
| 54 | * @error: error code returned by test | ||
| 55 | * | ||
| 56 | * Do auditing of capability and handle, audit/complain/kill modes switching | ||
| 57 | * and duplicate message elimination. | ||
| 58 | * | ||
| 59 | * Returns: 0 or sa->error on success, error code on failure | ||
| 60 | */ | ||
| 61 | static int audit_caps(struct aa_profile *profile, struct task_struct *task, | ||
| 62 | int cap, int error) | ||
| 63 | { | ||
| 64 | struct audit_cache *ent; | ||
| 65 | int type = AUDIT_APPARMOR_AUTO; | ||
| 66 | struct common_audit_data sa; | ||
| 67 | COMMON_AUDIT_DATA_INIT(&sa, CAP); | ||
| 68 | sa.tsk = task; | ||
| 69 | sa.u.cap = cap; | ||
| 70 | sa.aad.op = OP_CAPABLE; | ||
| 71 | sa.aad.error = error; | ||
| 72 | |||
| 73 | if (likely(!error)) { | ||
| 74 | /* test if auditing is being forced */ | ||
| 75 | if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && | ||
| 76 | !cap_raised(profile->caps.audit, cap))) | ||
| 77 | return 0; | ||
| 78 | type = AUDIT_APPARMOR_AUDIT; | ||
| 79 | } else if (KILL_MODE(profile) || | ||
| 80 | cap_raised(profile->caps.kill, cap)) { | ||
| 81 | type = AUDIT_APPARMOR_KILL; | ||
| 82 | } else if (cap_raised(profile->caps.quiet, cap) && | ||
| 83 | AUDIT_MODE(profile) != AUDIT_NOQUIET && | ||
| 84 | AUDIT_MODE(profile) != AUDIT_ALL) { | ||
| 85 | /* quiet auditing */ | ||
| 86 | return error; | ||
| 87 | } | ||
| 88 | |||
| 89 | /* Do simple duplicate message elimination */ | ||
| 90 | ent = &get_cpu_var(audit_cache); | ||
| 91 | if (profile == ent->profile && cap_raised(ent->caps, cap)) { | ||
| 92 | put_cpu_var(audit_cache); | ||
| 93 | if (COMPLAIN_MODE(profile)) | ||
| 94 | return complain_error(error); | ||
| 95 | return error; | ||
| 96 | } else { | ||
| 97 | aa_put_profile(ent->profile); | ||
| 98 | ent->profile = aa_get_profile(profile); | ||
| 99 | cap_raise(ent->caps, cap); | ||
| 100 | } | ||
| 101 | put_cpu_var(audit_cache); | ||
| 102 | |||
| 103 | return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb); | ||
| 104 | } | ||
| 105 | |||
| 106 | /** | ||
| 107 | * profile_capable - test if profile allows use of capability @cap | ||
| 108 | * @profile: profile being enforced (NOT NULL, NOT unconfined) | ||
| 109 | * @cap: capability to test if allowed | ||
| 110 | * | ||
| 111 | * Returns: 0 if allowed else -EPERM | ||
| 112 | */ | ||
| 113 | static int profile_capable(struct aa_profile *profile, int cap) | ||
| 114 | { | ||
| 115 | return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; | ||
| 116 | } | ||
| 117 | |||
| 118 | /** | ||
| 119 | * aa_capable - test permission to use capability | ||
| 120 | * @task: task doing capability test against (NOT NULL) | ||
| 121 | * @profile: profile confining @task (NOT NULL) | ||
| 122 | * @cap: capability to be tested | ||
| 123 | * @audit: whether an audit record should be generated | ||
| 124 | * | ||
| 125 | * Look up capability in profile capability set. | ||
| 126 | * | ||
| 127 | * Returns: 0 on success, or else an error code. | ||
| 128 | */ | ||
| 129 | int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, | ||
| 130 | int audit) | ||
| 131 | { | ||
| 132 | int error = profile_capable(profile, cap); | ||
| 133 | |||
| 134 | if (!audit) { | ||
| 135 | if (COMPLAIN_MODE(profile)) | ||
| 136 | return complain_error(error); | ||
| 137 | return error; | ||
| 138 | } | ||
| 139 | |||
| 140 | return audit_caps(profile, task, cap, error); | ||
| 141 | } | ||
diff --git a/security/apparmor/context.c b/security/apparmor/context.c new file mode 100644 index 000000000000..8a9b5027c813 --- /dev/null +++ b/security/apparmor/context.c | |||
| @@ -0,0 +1,216 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor functions used to manipulate object security | ||
| 5 | * contexts. | ||
| 6 | * | ||
| 7 | * Copyright (C) 1998-2008 Novell/SUSE | ||
| 8 | * Copyright 2009-2010 Canonical Ltd. | ||
| 9 | * | ||
| 10 | * This program is free software; you can redistribute it and/or | ||
| 11 | * modify it under the terms of the GNU General Public License as | ||
| 12 | * published by the Free Software Foundation, version 2 of the | ||
| 13 | * License. | ||
| 14 | * | ||
| 15 | * | ||
| 16 | * AppArmor sets confinement on every task, via the the aa_task_cxt and | ||
| 17 | * the aa_task_cxt.profile, both of which are required and are not allowed | ||
| 18 | * to be NULL. The aa_task_cxt is not reference counted and is unique | ||
| 19 | * to each cred (which is reference count). The profile pointed to by | ||
| 20 | * the task_cxt is reference counted. | ||
| 21 | * | ||
| 22 | * TODO | ||
| 23 | * If a task uses change_hat it currently does not return to the old | ||
| 24 | * cred or task context but instead creates a new one. Ideally the task | ||
| 25 | * should return to the previous cred if it has not been modified. | ||
| 26 | * | ||
| 27 | */ | ||
| 28 | |||
| 29 | #include "include/context.h" | ||
| 30 | #include "include/policy.h" | ||
| 31 | |||
| 32 | /** | ||
| 33 | * aa_alloc_task_context - allocate a new task_cxt | ||
| 34 | * @flags: gfp flags for allocation | ||
| 35 | * | ||
| 36 | * Returns: allocated buffer or NULL on failure | ||
| 37 | */ | ||
| 38 | struct aa_task_cxt *aa_alloc_task_context(gfp_t flags) | ||
| 39 | { | ||
| 40 | return kzalloc(sizeof(struct aa_task_cxt), flags); | ||
| 41 | } | ||
| 42 | |||
| 43 | /** | ||
| 44 | * aa_free_task_context - free a task_cxt | ||
| 45 | * @cxt: task_cxt to free (MAYBE NULL) | ||
| 46 | */ | ||
| 47 | void aa_free_task_context(struct aa_task_cxt *cxt) | ||
| 48 | { | ||
| 49 | if (cxt) { | ||
| 50 | aa_put_profile(cxt->profile); | ||
| 51 | aa_put_profile(cxt->previous); | ||
| 52 | aa_put_profile(cxt->onexec); | ||
| 53 | |||
| 54 | kzfree(cxt); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /** | ||
| 59 | * aa_dup_task_context - duplicate a task context, incrementing reference counts | ||
| 60 | * @new: a blank task context (NOT NULL) | ||
| 61 | * @old: the task context to copy (NOT NULL) | ||
| 62 | */ | ||
| 63 | void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old) | ||
| 64 | { | ||
| 65 | *new = *old; | ||
| 66 | aa_get_profile(new->profile); | ||
| 67 | aa_get_profile(new->previous); | ||
| 68 | aa_get_profile(new->onexec); | ||
| 69 | } | ||
| 70 | |||
| 71 | /** | ||
| 72 | * aa_replace_current_profile - replace the current tasks profiles | ||
| 73 | * @profile: new profile (NOT NULL) | ||
| 74 | * | ||
| 75 | * Returns: 0 or error on failure | ||
| 76 | */ | ||
| 77 | int aa_replace_current_profile(struct aa_profile *profile) | ||
| 78 | { | ||
| 79 | struct aa_task_cxt *cxt = current_cred()->security; | ||
| 80 | struct cred *new; | ||
| 81 | BUG_ON(!profile); | ||
| 82 | |||
| 83 | if (cxt->profile == profile) | ||
| 84 | return 0; | ||
| 85 | |||
| 86 | new = prepare_creds(); | ||
| 87 | if (!new) | ||
| 88 | return -ENOMEM; | ||
| 89 | |||
| 90 | cxt = new->security; | ||
| 91 | if (unconfined(profile) || (cxt->profile->ns != profile->ns)) { | ||
| 92 | /* if switching to unconfined or a different profile namespace | ||
| 93 | * clear out context state | ||
| 94 | */ | ||
| 95 | aa_put_profile(cxt->previous); | ||
| 96 | aa_put_profile(cxt->onexec); | ||
| 97 | cxt->previous = NULL; | ||
| 98 | cxt->onexec = NULL; | ||
| 99 | cxt->token = 0; | ||
| 100 | } | ||
| 101 | /* be careful switching cxt->profile, when racing replacement it | ||
| 102 | * is possible that cxt->profile->replacedby is the reference keeping | ||
| 103 | * @profile valid, so make sure to get its reference before dropping | ||
| 104 | * the reference on cxt->profile */ | ||
| 105 | aa_get_profile(profile); | ||
| 106 | aa_put_profile(cxt->profile); | ||
| 107 | cxt->profile = profile; | ||
| 108 | |||
| 109 | commit_creds(new); | ||
| 110 | return 0; | ||
| 111 | } | ||
| 112 | |||
| 113 | /** | ||
| 114 | * aa_set_current_onexec - set the tasks change_profile to happen onexec | ||
| 115 | * @profile: system profile to set at exec (MAYBE NULL to clear value) | ||
| 116 | * | ||
| 117 | * Returns: 0 or error on failure | ||
| 118 | */ | ||
| 119 | int aa_set_current_onexec(struct aa_profile *profile) | ||
| 120 | { | ||
| 121 | struct aa_task_cxt *cxt; | ||
| 122 | struct cred *new = prepare_creds(); | ||
| 123 | if (!new) | ||
| 124 | return -ENOMEM; | ||
| 125 | |||
| 126 | cxt = new->security; | ||
| 127 | aa_get_profile(profile); | ||
| 128 | aa_put_profile(cxt->onexec); | ||
| 129 | cxt->onexec = profile; | ||
| 130 | |||
| 131 | commit_creds(new); | ||
| 132 | return 0; | ||
| 133 | } | ||
| 134 | |||
| 135 | /** | ||
| 136 | * aa_set_current_hat - set the current tasks hat | ||
| 137 | * @profile: profile to set as the current hat (NOT NULL) | ||
| 138 | * @token: token value that must be specified to change from the hat | ||
| 139 | * | ||
| 140 | * Do switch of tasks hat. If the task is currently in a hat | ||
| 141 | * validate the token to match. | ||
| 142 | * | ||
| 143 | * Returns: 0 or error on failure | ||
| 144 | */ | ||
| 145 | int aa_set_current_hat(struct aa_profile *profile, u64 token) | ||
| 146 | { | ||
| 147 | struct aa_task_cxt *cxt; | ||
| 148 | struct cred *new = prepare_creds(); | ||
| 149 | if (!new) | ||
| 150 | return -ENOMEM; | ||
| 151 | BUG_ON(!profile); | ||
| 152 | |||
| 153 | cxt = new->security; | ||
| 154 | if (!cxt->previous) { | ||
| 155 | /* transfer refcount */ | ||
| 156 | cxt->previous = cxt->profile; | ||
| 157 | cxt->token = token; | ||
| 158 | } else if (cxt->token == token) { | ||
| 159 | aa_put_profile(cxt->profile); | ||
| 160 | } else { | ||
| 161 | /* previous_profile && cxt->token != token */ | ||
| 162 | abort_creds(new); | ||
| 163 | return -EACCES; | ||
| 164 | } | ||
| 165 | cxt->profile = aa_get_profile(aa_newest_version(profile)); | ||
| 166 | /* clear exec on switching context */ | ||
| 167 | aa_put_profile(cxt->onexec); | ||
| 168 | cxt->onexec = NULL; | ||
| 169 | |||
| 170 | commit_creds(new); | ||
| 171 | return 0; | ||
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * aa_restore_previous_profile - exit from hat context restoring the profile | ||
| 176 | * @token: the token that must be matched to exit hat context | ||
| 177 | * | ||
| 178 | * Attempt to return out of a hat to the previous profile. The token | ||
| 179 | * must match the stored token value. | ||
| 180 | * | ||
| 181 | * Returns: 0 or error of failure | ||
| 182 | */ | ||
| 183 | int aa_restore_previous_profile(u64 token) | ||
| 184 | { | ||
| 185 | struct aa_task_cxt *cxt; | ||
| 186 | struct cred *new = prepare_creds(); | ||
| 187 | if (!new) | ||
| 188 | return -ENOMEM; | ||
| 189 | |||
| 190 | cxt = new->security; | ||
| 191 | if (cxt->token != token) { | ||
| 192 | abort_creds(new); | ||
| 193 | return -EACCES; | ||
| 194 | } | ||
| 195 | /* ignore restores when there is no saved profile */ | ||
| 196 | if (!cxt->previous) { | ||
| 197 | abort_creds(new); | ||
| 198 | return 0; | ||
| 199 | } | ||
| 200 | |||
| 201 | aa_put_profile(cxt->profile); | ||
| 202 | cxt->profile = aa_newest_version(cxt->previous); | ||
| 203 | BUG_ON(!cxt->profile); | ||
| 204 | if (unlikely(cxt->profile != cxt->previous)) { | ||
| 205 | aa_get_profile(cxt->profile); | ||
| 206 | aa_put_profile(cxt->previous); | ||
| 207 | } | ||
| 208 | /* clear exec && prev information when restoring to previous context */ | ||
| 209 | cxt->previous = NULL; | ||
| 210 | cxt->token = 0; | ||
| 211 | aa_put_profile(cxt->onexec); | ||
| 212 | cxt->onexec = NULL; | ||
| 213 | |||
| 214 | commit_creds(new); | ||
| 215 | return 0; | ||
| 216 | } | ||
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c new file mode 100644 index 000000000000..c825c6e0b636 --- /dev/null +++ b/security/apparmor/domain.c | |||
| @@ -0,0 +1,823 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor policy attachment and domain transitions | ||
| 5 | * | ||
| 6 | * Copyright (C) 2002-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 | |||
| 15 | #include <linux/errno.h> | ||
| 16 | #include <linux/fdtable.h> | ||
| 17 | #include <linux/file.h> | ||
| 18 | #include <linux/mount.h> | ||
| 19 | #include <linux/syscalls.h> | ||
| 20 | #include <linux/tracehook.h> | ||
| 21 | #include <linux/personality.h> | ||
| 22 | |||
| 23 | #include "include/audit.h" | ||
| 24 | #include "include/apparmorfs.h" | ||
| 25 | #include "include/context.h" | ||
| 26 | #include "include/domain.h" | ||
| 27 | #include "include/file.h" | ||
| 28 | #include "include/ipc.h" | ||
| 29 | #include "include/match.h" | ||
| 30 | #include "include/path.h" | ||
| 31 | #include "include/policy.h" | ||
| 32 | |||
| 33 | /** | ||
| 34 | * aa_free_domain_entries - free entries in a domain table | ||
| 35 | * @domain: the domain table to free (MAYBE NULL) | ||
| 36 | */ | ||
| 37 | void aa_free_domain_entries(struct aa_domain *domain) | ||
| 38 | { | ||
| 39 | int i; | ||
| 40 | if (domain) { | ||
| 41 | if (!domain->table) | ||
| 42 | return; | ||
| 43 | |||
| 44 | for (i = 0; i < domain->size; i++) | ||
| 45 | kzfree(domain->table[i]); | ||
| 46 | kzfree(domain->table); | ||
| 47 | domain->table = NULL; | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | /** | ||
| 52 | * may_change_ptraced_domain - check if can change profile on ptraced task | ||
| 53 | * @task: task we want to change profile of (NOT NULL) | ||
| 54 | * @to_profile: profile to change to (NOT NULL) | ||
| 55 | * | ||
| 56 | * Check if the task is ptraced and if so if the tracing task is allowed | ||
| 57 | * to trace the new domain | ||
| 58 | * | ||
| 59 | * Returns: %0 or error if change not allowed | ||
| 60 | */ | ||
| 61 | static int may_change_ptraced_domain(struct task_struct *task, | ||
| 62 | struct aa_profile *to_profile) | ||
| 63 | { | ||
| 64 | struct task_struct *tracer; | ||
| 65 | const struct cred *cred = NULL; | ||
| 66 | struct aa_profile *tracerp = NULL; | ||
| 67 | int error = 0; | ||
| 68 | |||
| 69 | rcu_read_lock(); | ||
| 70 | tracer = tracehook_tracer_task(task); | ||
| 71 | if (tracer) { | ||
| 72 | /* released below */ | ||
| 73 | cred = get_task_cred(tracer); | ||
| 74 | tracerp = aa_cred_profile(cred); | ||
| 75 | } | ||
| 76 | rcu_read_unlock(); | ||
| 77 | |||
| 78 | /* not ptraced */ | ||
| 79 | if (!tracer || unconfined(tracerp)) | ||
| 80 | goto out; | ||
| 81 | |||
| 82 | error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); | ||
| 83 | |||
| 84 | out: | ||
| 85 | if (cred) | ||
| 86 | put_cred(cred); | ||
| 87 | |||
| 88 | return error; | ||
| 89 | } | ||
| 90 | |||
| 91 | /** | ||
| 92 | * change_profile_perms - find permissions for change_profile | ||
| 93 | * @profile: the current profile (NOT NULL) | ||
| 94 | * @ns: the namespace being switched to (NOT NULL) | ||
| 95 | * @name: the name of the profile to change to (NOT NULL) | ||
| 96 | * @request: requested perms | ||
| 97 | * @start: state to start matching in | ||
| 98 | * | ||
| 99 | * Returns: permission set | ||
| 100 | */ | ||
| 101 | static struct file_perms change_profile_perms(struct aa_profile *profile, | ||
| 102 | struct aa_namespace *ns, | ||
| 103 | const char *name, u32 request, | ||
| 104 | unsigned int start) | ||
| 105 | { | ||
| 106 | struct file_perms perms; | ||
| 107 | struct path_cond cond = { }; | ||
| 108 | unsigned int state; | ||
| 109 | |||
| 110 | if (unconfined(profile)) { | ||
| 111 | perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; | ||
| 112 | perms.audit = perms.quiet = perms.kill = 0; | ||
| 113 | return perms; | ||
| 114 | } else if (!profile->file.dfa) { | ||
| 115 | return nullperms; | ||
| 116 | } else if ((ns == profile->ns)) { | ||
| 117 | /* try matching against rules with out namespace prepended */ | ||
| 118 | aa_str_perms(profile->file.dfa, start, name, &cond, &perms); | ||
| 119 | if (COMBINED_PERM_MASK(perms) & request) | ||
| 120 | return perms; | ||
| 121 | } | ||
| 122 | |||
| 123 | /* try matching with namespace name and then profile */ | ||
| 124 | state = aa_dfa_match(profile->file.dfa, start, ns->base.name); | ||
| 125 | state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); | ||
| 126 | aa_str_perms(profile->file.dfa, state, name, &cond, &perms); | ||
| 127 | |||
| 128 | return perms; | ||
| 129 | } | ||
| 130 | |||
| 131 | /** | ||
| 132 | * __attach_match_ - find an attachment match | ||
| 133 | * @name - to match against (NOT NULL) | ||
| 134 | * @head - profile list to walk (NOT NULL) | ||
| 135 | * | ||
| 136 | * Do a linear search on the profiles in the list. There is a matching | ||
| 137 | * preference where an exact match is preferred over a name which uses | ||
| 138 | * expressions to match, and matching expressions with the greatest | ||
| 139 | * xmatch_len are preferred. | ||
| 140 | * | ||
| 141 | * Requires: @head not be shared or have appropriate locks held | ||
| 142 | * | ||
| 143 | * Returns: profile or NULL if no match found | ||
| 144 | */ | ||
| 145 | static struct aa_profile *__attach_match(const char *name, | ||
| 146 | struct list_head *head) | ||
| 147 | { | ||
| 148 | int len = 0; | ||
| 149 | struct aa_profile *profile, *candidate = NULL; | ||
| 150 | |||
| 151 | list_for_each_entry(profile, head, base.list) { | ||
| 152 | if (profile->flags & PFLAG_NULL) | ||
| 153 | continue; | ||
| 154 | if (profile->xmatch && profile->xmatch_len > len) { | ||
| 155 | unsigned int state = aa_dfa_match(profile->xmatch, | ||
| 156 | DFA_START, name); | ||
| 157 | u32 perm = dfa_user_allow(profile->xmatch, state); | ||
| 158 | /* any accepting state means a valid match. */ | ||
| 159 | if (perm & MAY_EXEC) { | ||
| 160 | candidate = profile; | ||
| 161 | len = profile->xmatch_len; | ||
| 162 | } | ||
| 163 | } else if (!strcmp(profile->base.name, name)) | ||
| 164 | /* exact non-re match, no more searching required */ | ||
| 165 | return profile; | ||
| 166 | } | ||
| 167 | |||
| 168 | return candidate; | ||
| 169 | } | ||
| 170 | |||
| 171 | /** | ||
| 172 | * find_attach - do attachment search for unconfined processes | ||
| 173 | * @ns: the current namespace (NOT NULL) | ||
| 174 | * @list: list to search (NOT NULL) | ||
| 175 | * @name: the executable name to match against (NOT NULL) | ||
| 176 | * | ||
| 177 | * Returns: profile or NULL if no match found | ||
| 178 | */ | ||
| 179 | static struct aa_profile *find_attach(struct aa_namespace *ns, | ||
| 180 | struct list_head *list, const char *name) | ||
| 181 | { | ||
| 182 | struct aa_profile *profile; | ||
| 183 | |||
| 184 | read_lock(&ns->lock); | ||
| 185 | profile = aa_get_profile(__attach_match(name, list)); | ||
| 186 | read_unlock(&ns->lock); | ||
| 187 | |||
| 188 | return profile; | ||
| 189 | } | ||
| 190 | |||
| 191 | /** | ||
| 192 | * separate_fqname - separate the namespace and profile names | ||
| 193 | * @fqname: the fqname name to split (NOT NULL) | ||
| 194 | * @ns_name: the namespace name if it exists (NOT NULL) | ||
| 195 | * | ||
| 196 | * This is the xtable equivalent routine of aa_split_fqname. It finds the | ||
| 197 | * split in an xtable fqname which contains an embedded \0 instead of a : | ||
| 198 | * if a namespace is specified. This is done so the xtable is constant and | ||
| 199 | * isn't re-split on every lookup. | ||
| 200 | * | ||
| 201 | * Either the profile or namespace name may be optional but if the namespace | ||
| 202 | * is specified the profile name termination must be present. This results | ||
| 203 | * in the following possible encodings: | ||
| 204 | * profile_name\0 | ||
| 205 | * :ns_name\0profile_name\0 | ||
| 206 | * :ns_name\0\0 | ||
| 207 | * | ||
| 208 | * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table | ||
| 209 | * | ||
| 210 | * Returns: profile name if it is specified else NULL | ||
| 211 | */ | ||
| 212 | static const char *separate_fqname(const char *fqname, const char **ns_name) | ||
| 213 | { | ||
| 214 | const char *name; | ||
| 215 | |||
| 216 | if (fqname[0] == ':') { | ||
| 217 | /* In this case there is guaranteed to be two \0 terminators | ||
| 218 | * in the string. They are verified at load time by | ||
| 219 | * by unpack_trans_table | ||
| 220 | */ | ||
| 221 | *ns_name = fqname + 1; /* skip : */ | ||
| 222 | name = *ns_name + strlen(*ns_name) + 1; | ||
| 223 | if (!*name) | ||
| 224 | name = NULL; | ||
| 225 | } else { | ||
| 226 | *ns_name = NULL; | ||
| 227 | name = fqname; | ||
| 228 | } | ||
| 229 | |||
| 230 | return name; | ||
| 231 | } | ||
| 232 | |||
| 233 | static const char *next_name(int xtype, const char *name) | ||
| 234 | { | ||
| 235 | return NULL; | ||
| 236 | } | ||
| 237 | |||
| 238 | /** | ||
| 239 | * x_table_lookup - lookup an x transition name via transition table | ||
| 240 | * @profile: current profile (NOT NULL) | ||
| 241 | * @xindex: index into x transition table | ||
| 242 | * | ||
| 243 | * Returns: refcounted profile, or NULL on failure (MAYBE NULL) | ||
| 244 | */ | ||
| 245 | static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) | ||
| 246 | { | ||
| 247 | struct aa_profile *new_profile = NULL; | ||
| 248 | struct aa_namespace *ns = profile->ns; | ||
| 249 | u32 xtype = xindex & AA_X_TYPE_MASK; | ||
| 250 | int index = xindex & AA_X_INDEX_MASK; | ||
| 251 | const char *name; | ||
| 252 | |||
| 253 | /* index is guaranteed to be in range, validated at load time */ | ||
| 254 | for (name = profile->file.trans.table[index]; !new_profile && name; | ||
| 255 | name = next_name(xtype, name)) { | ||
| 256 | struct aa_namespace *new_ns; | ||
| 257 | const char *xname = NULL; | ||
| 258 | |||
| 259 | new_ns = NULL; | ||
| 260 | if (xindex & AA_X_CHILD) { | ||
| 261 | /* release by caller */ | ||
| 262 | new_profile = aa_find_child(profile, name); | ||
| 263 | continue; | ||
| 264 | } else if (*name == ':') { | ||
| 265 | /* switching namespace */ | ||
| 266 | const char *ns_name; | ||
| 267 | xname = name = separate_fqname(name, &ns_name); | ||
| 268 | if (!xname) | ||
| 269 | /* no name so use profile name */ | ||
| 270 | xname = profile->base.hname; | ||
| 271 | if (*ns_name == '@') { | ||
| 272 | /* TODO: variable support */ | ||
| 273 | ; | ||
| 274 | } | ||
| 275 | /* released below */ | ||
| 276 | new_ns = aa_find_namespace(ns, ns_name); | ||
| 277 | if (!new_ns) | ||
| 278 | continue; | ||
| 279 | } else if (*name == '@') { | ||
| 280 | /* TODO: variable support */ | ||
| 281 | continue; | ||
| 282 | } else { | ||
| 283 | /* basic namespace lookup */ | ||
| 284 | xname = name; | ||
| 285 | } | ||
| 286 | |||
| 287 | /* released by caller */ | ||
| 288 | new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); | ||
| 289 | aa_put_namespace(new_ns); | ||
| 290 | } | ||
| 291 | |||
| 292 | /* released by caller */ | ||
| 293 | return new_profile; | ||
| 294 | } | ||
| 295 | |||
| 296 | /** | ||
| 297 | * x_to_profile - get target profile for a given xindex | ||
| 298 | * @profile: current profile (NOT NULL) | ||
| 299 | * @name: name to lookup (NOT NULL) | ||
| 300 | * @xindex: index into x transition table | ||
| 301 | * | ||
| 302 | * find profile for a transition index | ||
| 303 | * | ||
| 304 | * Returns: refcounted profile or NULL if not found available | ||
| 305 | */ | ||
| 306 | static struct aa_profile *x_to_profile(struct aa_profile *profile, | ||
| 307 | const char *name, u32 xindex) | ||
| 308 | { | ||
| 309 | struct aa_profile *new_profile = NULL; | ||
| 310 | struct aa_namespace *ns = profile->ns; | ||
| 311 | u32 xtype = xindex & AA_X_TYPE_MASK; | ||
| 312 | |||
| 313 | switch (xtype) { | ||
| 314 | case AA_X_NONE: | ||
| 315 | /* fail exec unless ix || ux fallback - handled by caller */ | ||
| 316 | return NULL; | ||
| 317 | case AA_X_NAME: | ||
| 318 | if (xindex & AA_X_CHILD) | ||
| 319 | /* released by caller */ | ||
| 320 | new_profile = find_attach(ns, &profile->base.profiles, | ||
| 321 | name); | ||
| 322 | else | ||
| 323 | /* released by caller */ | ||
| 324 | new_profile = find_attach(ns, &ns->base.profiles, | ||
| 325 | name); | ||
| 326 | break; | ||
| 327 | case AA_X_TABLE: | ||
| 328 | /* released by caller */ | ||
| 329 | new_profile = x_table_lookup(profile, xindex); | ||
| 330 | break; | ||
| 331 | } | ||
| 332 | |||
| 333 | /* released by caller */ | ||
| 334 | return new_profile; | ||
| 335 | } | ||
| 336 | |||
| 337 | /** | ||
| 338 | * apparmor_bprm_set_creds - set the new creds on the bprm struct | ||
| 339 | * @bprm: binprm for the exec (NOT NULL) | ||
| 340 | * | ||
| 341 | * Returns: %0 or error on failure | ||
| 342 | */ | ||
| 343 | int apparmor_bprm_set_creds(struct linux_binprm *bprm) | ||
| 344 | { | ||
| 345 | struct aa_task_cxt *cxt; | ||
| 346 | struct aa_profile *profile, *new_profile = NULL; | ||
| 347 | struct aa_namespace *ns; | ||
| 348 | char *buffer = NULL; | ||
| 349 | unsigned int state; | ||
| 350 | struct file_perms perms = {}; | ||
| 351 | struct path_cond cond = { | ||
| 352 | bprm->file->f_path.dentry->d_inode->i_uid, | ||
| 353 | bprm->file->f_path.dentry->d_inode->i_mode | ||
| 354 | }; | ||
| 355 | const char *name = NULL, *target = NULL, *info = NULL; | ||
| 356 | int error = cap_bprm_set_creds(bprm); | ||
| 357 | if (error) | ||
| 358 | return error; | ||
| 359 | |||
| 360 | if (bprm->cred_prepared) | ||
| 361 | return 0; | ||
| 362 | |||
| 363 | cxt = bprm->cred->security; | ||
| 364 | BUG_ON(!cxt); | ||
| 365 | |||
| 366 | profile = aa_get_profile(aa_newest_version(cxt->profile)); | ||
| 367 | /* | ||
| 368 | * get the namespace from the replacement profile as replacement | ||
| 369 | * can change the namespace | ||
| 370 | */ | ||
| 371 | ns = profile->ns; | ||
| 372 | state = profile->file.start; | ||
| 373 | |||
| 374 | /* buffer freed below, name is pointer into buffer */ | ||
| 375 | error = aa_get_name(&bprm->file->f_path, profile->path_flags, &buffer, | ||
| 376 | &name); | ||
| 377 | if (error) { | ||
| 378 | if (profile->flags & | ||
| 379 | (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED)) | ||
| 380 | error = 0; | ||
| 381 | info = "Exec failed name resolution"; | ||
| 382 | name = bprm->filename; | ||
| 383 | goto audit; | ||
| 384 | } | ||
| 385 | |||
| 386 | /* Test for onexec first as onexec directives override other | ||
| 387 | * x transitions. | ||
| 388 | */ | ||
| 389 | if (unconfined(profile)) { | ||
| 390 | /* unconfined task */ | ||
| 391 | if (cxt->onexec) | ||
| 392 | /* change_profile on exec already been granted */ | ||
| 393 | new_profile = aa_get_profile(cxt->onexec); | ||
| 394 | else | ||
| 395 | new_profile = find_attach(ns, &ns->base.profiles, name); | ||
| 396 | if (!new_profile) | ||
| 397 | goto cleanup; | ||
| 398 | goto apply; | ||
| 399 | } | ||
| 400 | |||
| 401 | /* find exec permissions for name */ | ||
| 402 | state = aa_str_perms(profile->file.dfa, state, name, &cond, &perms); | ||
| 403 | if (cxt->onexec) { | ||
| 404 | struct file_perms cp; | ||
| 405 | info = "change_profile onexec"; | ||
| 406 | if (!(perms.allow & AA_MAY_ONEXEC)) | ||
| 407 | goto audit; | ||
| 408 | |||
| 409 | /* test if this exec can be paired with change_profile onexec. | ||
| 410 | * onexec permission is linked to exec with a standard pairing | ||
| 411 | * exec\0change_profile | ||
| 412 | */ | ||
| 413 | state = aa_dfa_null_transition(profile->file.dfa, state); | ||
| 414 | cp = change_profile_perms(profile, cxt->onexec->ns, name, | ||
| 415 | AA_MAY_ONEXEC, state); | ||
| 416 | |||
| 417 | if (!(cp.allow & AA_MAY_ONEXEC)) | ||
| 418 | goto audit; | ||
| 419 | new_profile = aa_get_profile(aa_newest_version(cxt->onexec)); | ||
| 420 | goto apply; | ||
| 421 | } | ||
| 422 | |||
| 423 | if (perms.allow & MAY_EXEC) { | ||
| 424 | /* exec permission determine how to transition */ | ||
| 425 | new_profile = x_to_profile(profile, name, perms.xindex); | ||
| 426 | if (!new_profile) { | ||
| 427 | if (perms.xindex & AA_X_INHERIT) { | ||
| 428 | /* (p|c|n)ix - don't change profile but do | ||
| 429 | * use the newest version, which was picked | ||
| 430 | * up above when getting profile | ||
| 431 | */ | ||
| 432 | info = "ix fallback"; | ||
| 433 | new_profile = aa_get_profile(profile); | ||
| 434 | goto x_clear; | ||
| 435 | } else if (perms.xindex & AA_X_UNCONFINED) { | ||
| 436 | new_profile = aa_get_profile(ns->unconfined); | ||
| 437 | info = "ux fallback"; | ||
| 438 | } else { | ||
| 439 | error = -ENOENT; | ||
| 440 | info = "profile not found"; | ||
| 441 | } | ||
| 442 | } | ||
| 443 | } else if (COMPLAIN_MODE(profile)) { | ||
| 444 | /* no exec permission - are we in learning mode */ | ||
| 445 | new_profile = aa_new_null_profile(profile, 0); | ||
| 446 | if (!new_profile) { | ||
| 447 | error = -ENOMEM; | ||
| 448 | info = "could not create null profile"; | ||
| 449 | } else { | ||
| 450 | error = -EACCES; | ||
| 451 | target = new_profile->base.hname; | ||
| 452 | } | ||
| 453 | perms.xindex |= AA_X_UNSAFE; | ||
| 454 | } else | ||
| 455 | /* fail exec */ | ||
| 456 | error = -EACCES; | ||
| 457 | |||
| 458 | if (!new_profile) | ||
| 459 | goto audit; | ||
| 460 | |||
| 461 | if (bprm->unsafe & LSM_UNSAFE_SHARE) { | ||
| 462 | /* FIXME: currently don't mediate shared state */ | ||
| 463 | ; | ||
| 464 | } | ||
| 465 | |||
| 466 | if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { | ||
| 467 | error = may_change_ptraced_domain(current, new_profile); | ||
| 468 | if (error) { | ||
| 469 | aa_put_profile(new_profile); | ||
| 470 | goto audit; | ||
| 471 | } | ||
| 472 | } | ||
| 473 | |||
| 474 | /* Determine if secure exec is needed. | ||
| 475 | * Can be at this point for the following reasons: | ||
| 476 | * 1. unconfined switching to confined | ||
| 477 | * 2. confined switching to different confinement | ||
| 478 | * 3. confined switching to unconfined | ||
| 479 | * | ||
| 480 | * Cases 2 and 3 are marked as requiring secure exec | ||
| 481 | * (unless policy specified "unsafe exec") | ||
| 482 | * | ||
| 483 | * bprm->unsafe is used to cache the AA_X_UNSAFE permission | ||
| 484 | * to avoid having to recompute in secureexec | ||
| 485 | */ | ||
| 486 | if (!(perms.xindex & AA_X_UNSAFE)) { | ||
| 487 | AA_DEBUG("scrubbing environment variables for %s profile=%s\n", | ||
| 488 | name, new_profile->base.hname); | ||
| 489 | bprm->unsafe |= AA_SECURE_X_NEEDED; | ||
| 490 | } | ||
| 491 | apply: | ||
| 492 | target = new_profile->base.hname; | ||
| 493 | /* when transitioning profiles clear unsafe personality bits */ | ||
| 494 | bprm->per_clear |= PER_CLEAR_ON_SETID; | ||
| 495 | |||
| 496 | x_clear: | ||
| 497 | aa_put_profile(cxt->profile); | ||
| 498 | /* transfer new profile reference will be released when cxt is freed */ | ||
| 499 | cxt->profile = new_profile; | ||
| 500 | |||
| 501 | /* clear out all temporary/transitional state from the context */ | ||
| 502 | aa_put_profile(cxt->previous); | ||
| 503 | aa_put_profile(cxt->onexec); | ||
| 504 | cxt->previous = NULL; | ||
| 505 | cxt->onexec = NULL; | ||
| 506 | cxt->token = 0; | ||
| 507 | |||
| 508 | audit: | ||
| 509 | error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, | ||
| 510 | name, target, cond.uid, info, error); | ||
| 511 | |||
| 512 | cleanup: | ||
| 513 | aa_put_profile(profile); | ||
| 514 | kfree(buffer); | ||
| 515 | |||
| 516 | return error; | ||
| 517 | } | ||
| 518 | |||
| 519 | /** | ||
| 520 | * apparmor_bprm_secureexec - determine if secureexec is needed | ||
| 521 | * @bprm: binprm for exec (NOT NULL) | ||
| 522 | * | ||
| 523 | * Returns: %1 if secureexec is needed else %0 | ||
| 524 | */ | ||
| 525 | int apparmor_bprm_secureexec(struct linux_binprm *bprm) | ||
| 526 | { | ||
| 527 | int ret = cap_bprm_secureexec(bprm); | ||
| 528 | |||
| 529 | /* the decision to use secure exec is computed in set_creds | ||
| 530 | * and stored in bprm->unsafe. | ||
| 531 | */ | ||
| 532 | if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED)) | ||
| 533 | ret = 1; | ||
| 534 | |||
| 535 | return ret; | ||
| 536 | } | ||
| 537 | |||
| 538 | /** | ||
| 539 | * apparmor_bprm_committing_creds - do task cleanup on committing new creds | ||
| 540 | * @bprm: binprm for the exec (NOT NULL) | ||
| 541 | */ | ||
| 542 | void apparmor_bprm_committing_creds(struct linux_binprm *bprm) | ||
| 543 | { | ||
| 544 | struct aa_profile *profile = __aa_current_profile(); | ||
| 545 | struct aa_task_cxt *new_cxt = bprm->cred->security; | ||
| 546 | |||
| 547 | /* bail out if unconfined or not changing profile */ | ||
| 548 | if ((new_cxt->profile == profile) || | ||
| 549 | (unconfined(new_cxt->profile))) | ||
| 550 | return; | ||
| 551 | |||
| 552 | current->pdeath_signal = 0; | ||
| 553 | |||
| 554 | /* reset soft limits and set hard limits for the new profile */ | ||
| 555 | __aa_transition_rlimits(profile, new_cxt->profile); | ||
| 556 | } | ||
| 557 | |||
| 558 | /** | ||
| 559 | * apparmor_bprm_commited_cred - do cleanup after new creds committed | ||
| 560 | * @bprm: binprm for the exec (NOT NULL) | ||
| 561 | */ | ||
| 562 | void apparmor_bprm_committed_creds(struct linux_binprm *bprm) | ||
| 563 | { | ||
| 564 | /* TODO: cleanup signals - ipc mediation */ | ||
| 565 | return; | ||
| 566 | } | ||
| 567 | |||
| 568 | /* | ||
| 569 | * Functions for self directed profile change | ||
| 570 | */ | ||
| 571 | |||
| 572 | /** | ||
| 573 | * new_compound_name - create an hname with @n2 appended to @n1 | ||
| 574 | * @n1: base of hname (NOT NULL) | ||
| 575 | * @n2: name to append (NOT NULL) | ||
| 576 | * | ||
| 577 | * Returns: new name or NULL on error | ||
| 578 | */ | ||
| 579 | static char *new_compound_name(const char *n1, const char *n2) | ||
| 580 | { | ||
| 581 | char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); | ||
| 582 | if (name) | ||
| 583 | sprintf(name, "%s//%s", n1, n2); | ||
| 584 | return name; | ||
| 585 | } | ||
| 586 | |||
| 587 | /** | ||
| 588 | * aa_change_hat - change hat to/from subprofile | ||
| 589 | * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) | ||
| 590 | * @count: number of hat names in @hats | ||
| 591 | * @token: magic value to validate the hat change | ||
| 592 | * @permtest: true if this is just a permission test | ||
| 593 | * | ||
| 594 | * Change to the first profile specified in @hats that exists, and store | ||
| 595 | * the @hat_magic in the current task context. If the count == 0 and the | ||
| 596 | * @token matches that stored in the current task context, return to the | ||
| 597 | * top level profile. | ||
| 598 | * | ||
| 599 | * Returns %0 on success, error otherwise. | ||
| 600 | */ | ||
| 601 | int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) | ||
| 602 | { | ||
| 603 | const struct cred *cred; | ||
| 604 | struct aa_task_cxt *cxt; | ||
| 605 | struct aa_profile *profile, *previous_profile, *hat = NULL; | ||
| 606 | char *name = NULL; | ||
| 607 | int i; | ||
| 608 | struct file_perms perms = {}; | ||
| 609 | const char *target = NULL, *info = NULL; | ||
| 610 | int error = 0; | ||
| 611 | |||
| 612 | /* released below */ | ||
| 613 | cred = get_current_cred(); | ||
| 614 | cxt = cred->security; | ||
| 615 | profile = aa_cred_profile(cred); | ||
| 616 | previous_profile = cxt->previous; | ||
| 617 | |||
| 618 | if (unconfined(profile)) { | ||
| 619 | info = "unconfined"; | ||
| 620 | error = -EPERM; | ||
| 621 | goto audit; | ||
| 622 | } | ||
| 623 | |||
| 624 | if (count) { | ||
| 625 | /* attempting to change into a new hat or switch to a sibling */ | ||
| 626 | struct aa_profile *root; | ||
| 627 | root = PROFILE_IS_HAT(profile) ? profile->parent : profile; | ||
| 628 | |||
| 629 | /* find first matching hat */ | ||
| 630 | for (i = 0; i < count && !hat; i++) | ||
| 631 | /* released below */ | ||
| 632 | hat = aa_find_child(root, hats[i]); | ||
| 633 | if (!hat) { | ||
| 634 | if (!COMPLAIN_MODE(root) || permtest) { | ||
| 635 | if (list_empty(&root->base.profiles)) | ||
| 636 | error = -ECHILD; | ||
| 637 | else | ||
| 638 | error = -ENOENT; | ||
| 639 | goto out; | ||
| 640 | } | ||
| 641 | |||
| 642 | /* | ||
| 643 | * In complain mode and failed to match any hats. | ||
| 644 | * Audit the failure is based off of the first hat | ||
| 645 | * supplied. This is done due how userspace | ||
| 646 | * interacts with change_hat. | ||
| 647 | * | ||
| 648 | * TODO: Add logging of all failed hats | ||
| 649 | */ | ||
| 650 | |||
| 651 | /* freed below */ | ||
| 652 | name = new_compound_name(root->base.hname, hats[0]); | ||
| 653 | target = name; | ||
| 654 | /* released below */ | ||
| 655 | hat = aa_new_null_profile(profile, 1); | ||
| 656 | if (!hat) { | ||
| 657 | info = "failed null profile create"; | ||
| 658 | error = -ENOMEM; | ||
| 659 | goto audit; | ||
| 660 | } | ||
| 661 | } else { | ||
| 662 | target = hat->base.hname; | ||
| 663 | if (!PROFILE_IS_HAT(hat)) { | ||
| 664 | info = "target not hat"; | ||
| 665 | error = -EPERM; | ||
| 666 | goto audit; | ||
| 667 | } | ||
| 668 | } | ||
| 669 | |||
| 670 | error = may_change_ptraced_domain(current, hat); | ||
| 671 | if (error) { | ||
| 672 | info = "ptraced"; | ||
| 673 | error = -EPERM; | ||
| 674 | goto audit; | ||
| 675 | } | ||
| 676 | |||
| 677 | if (!permtest) { | ||
| 678 | error = aa_set_current_hat(hat, token); | ||
| 679 | if (error == -EACCES) | ||
| 680 | /* kill task in case of brute force attacks */ | ||
| 681 | perms.kill = AA_MAY_CHANGEHAT; | ||
| 682 | else if (name && !error) | ||
| 683 | /* reset error for learning of new hats */ | ||
| 684 | error = -ENOENT; | ||
| 685 | } | ||
| 686 | } else if (previous_profile) { | ||
| 687 | /* Return to saved profile. Kill task if restore fails | ||
| 688 | * to avoid brute force attacks | ||
| 689 | */ | ||
| 690 | target = previous_profile->base.hname; | ||
| 691 | error = aa_restore_previous_profile(token); | ||
| 692 | perms.kill = AA_MAY_CHANGEHAT; | ||
| 693 | } else | ||
| 694 | /* ignore restores when there is no saved profile */ | ||
| 695 | goto out; | ||
| 696 | |||
| 697 | audit: | ||
| 698 | if (!permtest) | ||
| 699 | error = aa_audit_file(profile, &perms, GFP_KERNEL, | ||
| 700 | OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, | ||
| 701 | target, 0, info, error); | ||
| 702 | |||
| 703 | out: | ||
| 704 | aa_put_profile(hat); | ||
| 705 | kfree(name); | ||
| 706 | put_cred(cred); | ||
| 707 | |||
| 708 | return error; | ||
| 709 | } | ||
| 710 | |||
| 711 | /** | ||
| 712 | * aa_change_profile - perform a one-way profile transition | ||
| 713 | * @ns_name: name of the profile namespace to change to (MAYBE NULL) | ||
| 714 | * @hname: name of profile to change to (MAYBE NULL) | ||
| 715 | * @onexec: whether this transition is to take place immediately or at exec | ||
| 716 | * @permtest: true if this is just a permission test | ||
| 717 | * | ||
| 718 | * Change to new profile @name. Unlike with hats, there is no way | ||
| 719 | * to change back. If @name isn't specified the current profile name is | ||
| 720 | * used. | ||
| 721 | * If @onexec then the transition is delayed until | ||
| 722 | * the next exec. | ||
| 723 | * | ||
| 724 | * Returns %0 on success, error otherwise. | ||
| 725 | */ | ||
| 726 | int aa_change_profile(const char *ns_name, const char *hname, bool onexec, | ||
| 727 | bool permtest) | ||
| 728 | { | ||
| 729 | const struct cred *cred; | ||
| 730 | struct aa_task_cxt *cxt; | ||
| 731 | struct aa_profile *profile, *target = NULL; | ||
| 732 | struct aa_namespace *ns = NULL; | ||
| 733 | struct file_perms perms = {}; | ||
| 734 | const char *name = NULL, *info = NULL; | ||
| 735 | int op, error = 0; | ||
| 736 | u32 request; | ||
| 737 | |||
| 738 | if (!hname && !ns_name) | ||
| 739 | return -EINVAL; | ||
| 740 | |||
| 741 | if (onexec) { | ||
| 742 | request = AA_MAY_ONEXEC; | ||
| 743 | op = OP_CHANGE_ONEXEC; | ||
| 744 | } else { | ||
| 745 | request = AA_MAY_CHANGE_PROFILE; | ||
| 746 | op = OP_CHANGE_PROFILE; | ||
| 747 | } | ||
| 748 | |||
| 749 | cred = get_current_cred(); | ||
| 750 | cxt = cred->security; | ||
| 751 | profile = aa_cred_profile(cred); | ||
| 752 | |||
| 753 | if (ns_name) { | ||
| 754 | /* released below */ | ||
| 755 | ns = aa_find_namespace(profile->ns, ns_name); | ||
| 756 | if (!ns) { | ||
| 757 | /* we don't create new namespace in complain mode */ | ||
| 758 | name = ns_name; | ||
| 759 | info = "namespace not found"; | ||
| 760 | error = -ENOENT; | ||
| 761 | goto audit; | ||
| 762 | } | ||
| 763 | } else | ||
| 764 | /* released below */ | ||
| 765 | ns = aa_get_namespace(profile->ns); | ||
| 766 | |||
| 767 | /* if the name was not specified, use the name of the current profile */ | ||
| 768 | if (!hname) { | ||
| 769 | if (unconfined(profile)) | ||
| 770 | hname = ns->unconfined->base.hname; | ||
| 771 | else | ||
| 772 | hname = profile->base.hname; | ||
| 773 | } | ||
| 774 | |||
| 775 | perms = change_profile_perms(profile, ns, hname, request, | ||
| 776 | profile->file.start); | ||
| 777 | if (!(perms.allow & request)) { | ||
| 778 | error = -EACCES; | ||
| 779 | goto audit; | ||
| 780 | } | ||
| 781 | |||
| 782 | /* released below */ | ||
| 783 | target = aa_lookup_profile(ns, hname); | ||
| 784 | if (!target) { | ||
| 785 | info = "profile not found"; | ||
| 786 | error = -ENOENT; | ||
| 787 | if (permtest || !COMPLAIN_MODE(profile)) | ||
| 788 | goto audit; | ||
| 789 | /* released below */ | ||
| 790 | target = aa_new_null_profile(profile, 0); | ||
| 791 | if (!target) { | ||
| 792 | info = "failed null profile create"; | ||
| 793 | error = -ENOMEM; | ||
| 794 | goto audit; | ||
| 795 | } | ||
| 796 | } | ||
| 797 | |||
| 798 | /* check if tracing task is allowed to trace target domain */ | ||
| 799 | error = may_change_ptraced_domain(current, target); | ||
| 800 | if (error) { | ||
| 801 | info = "ptrace prevents transition"; | ||
| 802 | goto audit; | ||
| 803 | } | ||
| 804 | |||
| 805 | if (permtest) | ||
| 806 | goto audit; | ||
| 807 | |||
| 808 | if (onexec) | ||
| 809 | error = aa_set_current_onexec(target); | ||
| 810 | else | ||
| 811 | error = aa_replace_current_profile(target); | ||
| 812 | |||
| 813 | audit: | ||
| 814 | if (!permtest) | ||
| 815 | error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, | ||
| 816 | name, hname, 0, info, error); | ||
| 817 | |||
| 818 | aa_put_namespace(ns); | ||
| 819 | aa_put_profile(target); | ||
| 820 | put_cred(cred); | ||
| 821 | |||
| 822 | return error; | ||
| 823 | } | ||
diff --git a/security/apparmor/file.c b/security/apparmor/file.c new file mode 100644 index 000000000000..7312db741219 --- /dev/null +++ b/security/apparmor/file.c | |||
| @@ -0,0 +1,457 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor mediation of files | ||
| 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 | |||
| 15 | #include "include/apparmor.h" | ||
| 16 | #include "include/audit.h" | ||
| 17 | #include "include/file.h" | ||
| 18 | #include "include/match.h" | ||
| 19 | #include "include/path.h" | ||
| 20 | #include "include/policy.h" | ||
| 21 | |||
| 22 | struct file_perms nullperms; | ||
| 23 | |||
| 24 | |||
| 25 | /** | ||
| 26 | * audit_file_mask - convert mask to permission string | ||
| 27 | * @buffer: buffer to write string to (NOT NULL) | ||
| 28 | * @mask: permission mask to convert | ||
| 29 | */ | ||
| 30 | static void audit_file_mask(struct audit_buffer *ab, u32 mask) | ||
| 31 | { | ||
| 32 | char str[10]; | ||
| 33 | |||
| 34 | char *m = str; | ||
| 35 | |||
| 36 | if (mask & AA_EXEC_MMAP) | ||
| 37 | *m++ = 'm'; | ||
| 38 | if (mask & (MAY_READ | AA_MAY_META_READ)) | ||
| 39 | *m++ = 'r'; | ||
| 40 | if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD | | ||
| 41 | AA_MAY_CHOWN)) | ||
| 42 | *m++ = 'w'; | ||
| 43 | else if (mask & MAY_APPEND) | ||
| 44 | *m++ = 'a'; | ||
| 45 | if (mask & AA_MAY_CREATE) | ||
| 46 | *m++ = 'c'; | ||
| 47 | if (mask & AA_MAY_DELETE) | ||
| 48 | *m++ = 'd'; | ||
| 49 | if (mask & AA_MAY_LINK) | ||
| 50 | *m++ = 'l'; | ||
| 51 | if (mask & AA_MAY_LOCK) | ||
| 52 | *m++ = 'k'; | ||
| 53 | if (mask & MAY_EXEC) | ||
| 54 | *m++ = 'x'; | ||
| 55 | *m = '\0'; | ||
| 56 | |||
| 57 | audit_log_string(ab, str); | ||
| 58 | } | ||
| 59 | |||
| 60 | /** | ||
| 61 | * file_audit_cb - call back for file specific audit fields | ||
| 62 | * @ab: audit_buffer (NOT NULL) | ||
| 63 | * @va: audit struct to audit values of (NOT NULL) | ||
| 64 | */ | ||
| 65 | static void file_audit_cb(struct audit_buffer *ab, void *va) | ||
| 66 | { | ||
| 67 | struct common_audit_data *sa = va; | ||
| 68 | uid_t fsuid = current_fsuid(); | ||
| 69 | |||
| 70 | if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { | ||
| 71 | audit_log_format(ab, " requested_mask="); | ||
| 72 | audit_file_mask(ab, sa->aad.fs.request); | ||
| 73 | } | ||
| 74 | if (sa->aad.fs.denied & AA_AUDIT_FILE_MASK) { | ||
| 75 | audit_log_format(ab, " denied_mask="); | ||
| 76 | audit_file_mask(ab, sa->aad.fs.denied); | ||
| 77 | } | ||
| 78 | if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) { | ||
| 79 | audit_log_format(ab, " fsuid=%d", fsuid); | ||
| 80 | audit_log_format(ab, " ouid=%d", sa->aad.fs.ouid); | ||
| 81 | } | ||
| 82 | |||
| 83 | if (sa->aad.fs.target) { | ||
| 84 | audit_log_format(ab, " target="); | ||
| 85 | audit_log_untrustedstring(ab, sa->aad.fs.target); | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | /** | ||
| 90 | * aa_audit_file - handle the auditing of file operations | ||
| 91 | * @profile: the profile being enforced (NOT NULL) | ||
| 92 | * @perms: the permissions computed for the request (NOT NULL) | ||
| 93 | * @gfp: allocation flags | ||
| 94 | * @op: operation being mediated | ||
| 95 | * @request: permissions requested | ||
| 96 | * @name: name of object being mediated (MAYBE NULL) | ||
| 97 | * @target: name of target (MAYBE NULL) | ||
| 98 | * @ouid: object uid | ||
| 99 | * @info: extra information message (MAYBE NULL) | ||
| 100 | * @error: 0 if operation allowed else failure error code | ||
| 101 | * | ||
| 102 | * Returns: %0 or error on failure | ||
| 103 | */ | ||
| 104 | int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, | ||
| 105 | gfp_t gfp, int op, u32 request, const char *name, | ||
| 106 | const char *target, uid_t ouid, const char *info, int error) | ||
| 107 | { | ||
| 108 | int type = AUDIT_APPARMOR_AUTO; | ||
| 109 | struct common_audit_data sa; | ||
| 110 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 111 | sa.aad.op = op, | ||
| 112 | sa.aad.fs.request = request; | ||
| 113 | sa.aad.name = name; | ||
| 114 | sa.aad.fs.target = target; | ||
| 115 | sa.aad.fs.ouid = ouid; | ||
| 116 | sa.aad.info = info; | ||
| 117 | sa.aad.error = error; | ||
| 118 | |||
| 119 | if (likely(!sa.aad.error)) { | ||
| 120 | u32 mask = perms->audit; | ||
| 121 | |||
| 122 | if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) | ||
| 123 | mask = 0xffff; | ||
| 124 | |||
| 125 | /* mask off perms that are not being force audited */ | ||
| 126 | sa.aad.fs.request &= mask; | ||
| 127 | |||
| 128 | if (likely(!sa.aad.fs.request)) | ||
| 129 | return 0; | ||
| 130 | type = AUDIT_APPARMOR_AUDIT; | ||
| 131 | } else { | ||
| 132 | /* only report permissions that were denied */ | ||
| 133 | sa.aad.fs.request = sa.aad.fs.request & ~perms->allow; | ||
| 134 | |||
| 135 | if (sa.aad.fs.request & perms->kill) | ||
| 136 | type = AUDIT_APPARMOR_KILL; | ||
| 137 | |||
| 138 | /* quiet known rejects, assumes quiet and kill do not overlap */ | ||
| 139 | if ((sa.aad.fs.request & perms->quiet) && | ||
| 140 | AUDIT_MODE(profile) != AUDIT_NOQUIET && | ||
| 141 | AUDIT_MODE(profile) != AUDIT_ALL) | ||
| 142 | sa.aad.fs.request &= ~perms->quiet; | ||
| 143 | |||
| 144 | if (!sa.aad.fs.request) | ||
| 145 | return COMPLAIN_MODE(profile) ? 0 : sa.aad.error; | ||
| 146 | } | ||
| 147 | |||
| 148 | sa.aad.fs.denied = sa.aad.fs.request & ~perms->allow; | ||
| 149 | return aa_audit(type, profile, gfp, &sa, file_audit_cb); | ||
| 150 | } | ||
| 151 | |||
| 152 | /** | ||
| 153 | * map_old_perms - map old file perms layout to the new layout | ||
| 154 | * @old: permission set in old mapping | ||
| 155 | * | ||
| 156 | * Returns: new permission mapping | ||
| 157 | */ | ||
| 158 | static u32 map_old_perms(u32 old) | ||
| 159 | { | ||
| 160 | u32 new = old & 0xf; | ||
| 161 | if (old & MAY_READ) | ||
| 162 | new |= AA_MAY_META_READ; | ||
| 163 | if (old & MAY_WRITE) | ||
| 164 | new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | | ||
| 165 | AA_MAY_CHMOD | AA_MAY_CHOWN; | ||
| 166 | if (old & 0x10) | ||
| 167 | new |= AA_MAY_LINK; | ||
| 168 | /* the old mapping lock and link_subset flags where overlaid | ||
| 169 | * and use was determined by part of a pair that they were in | ||
| 170 | */ | ||
| 171 | if (old & 0x20) | ||
| 172 | new |= AA_MAY_LOCK | AA_LINK_SUBSET; | ||
| 173 | if (old & 0x40) /* AA_EXEC_MMAP */ | ||
| 174 | new |= AA_EXEC_MMAP; | ||
| 175 | |||
| 176 | new |= AA_MAY_META_READ; | ||
| 177 | |||
| 178 | return new; | ||
| 179 | } | ||
| 180 | |||
| 181 | /** | ||
| 182 | * compute_perms - convert dfa compressed perms to internal perms | ||
| 183 | * @dfa: dfa to compute perms for (NOT NULL) | ||
| 184 | * @state: state in dfa | ||
| 185 | * @cond: conditions to consider (NOT NULL) | ||
| 186 | * | ||
| 187 | * TODO: convert from dfa + state to permission entry, do computation conversion | ||
| 188 | * at load time. | ||
| 189 | * | ||
| 190 | * Returns: computed permission set | ||
| 191 | */ | ||
| 192 | static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, | ||
| 193 | struct path_cond *cond) | ||
| 194 | { | ||
| 195 | struct file_perms perms; | ||
| 196 | |||
| 197 | /* FIXME: change over to new dfa format | ||
| 198 | * currently file perms are encoded in the dfa, new format | ||
| 199 | * splits the permissions from the dfa. This mapping can be | ||
| 200 | * done at profile load | ||
| 201 | */ | ||
| 202 | perms.kill = 0; | ||
| 203 | |||
| 204 | if (current_fsuid() == cond->uid) { | ||
| 205 | perms.allow = map_old_perms(dfa_user_allow(dfa, state)); | ||
| 206 | perms.audit = map_old_perms(dfa_user_audit(dfa, state)); | ||
| 207 | perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); | ||
| 208 | perms.xindex = dfa_user_xindex(dfa, state); | ||
| 209 | } else { | ||
| 210 | perms.allow = map_old_perms(dfa_other_allow(dfa, state)); | ||
| 211 | perms.audit = map_old_perms(dfa_other_audit(dfa, state)); | ||
| 212 | perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); | ||
| 213 | perms.xindex = dfa_other_xindex(dfa, state); | ||
| 214 | } | ||
| 215 | |||
| 216 | /* change_profile wasn't determined by ownership in old mapping */ | ||
| 217 | if (ACCEPT_TABLE(dfa)[state] & 0x80000000) | ||
| 218 | perms.allow |= AA_MAY_CHANGE_PROFILE; | ||
| 219 | |||
| 220 | return perms; | ||
| 221 | } | ||
| 222 | |||
| 223 | /** | ||
| 224 | * aa_str_perms - find permission that match @name | ||
| 225 | * @dfa: to match against (MAYBE NULL) | ||
| 226 | * @state: state to start matching in | ||
| 227 | * @name: string to match against dfa (NOT NULL) | ||
| 228 | * @cond: conditions to consider for permission set computation (NOT NULL) | ||
| 229 | * @perms: Returns - the permissions found when matching @name | ||
| 230 | * | ||
| 231 | * Returns: the final state in @dfa when beginning @start and walking @name | ||
| 232 | */ | ||
| 233 | unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, | ||
| 234 | const char *name, struct path_cond *cond, | ||
| 235 | struct file_perms *perms) | ||
| 236 | { | ||
| 237 | unsigned int state; | ||
| 238 | if (!dfa) { | ||
| 239 | *perms = nullperms; | ||
| 240 | return DFA_NOMATCH; | ||
| 241 | } | ||
| 242 | |||
| 243 | state = aa_dfa_match(dfa, start, name); | ||
| 244 | *perms = compute_perms(dfa, state, cond); | ||
| 245 | |||
| 246 | return state; | ||
| 247 | } | ||
| 248 | |||
| 249 | /** | ||
| 250 | * is_deleted - test if a file has been completely unlinked | ||
| 251 | * @dentry: dentry of file to test for deletion (NOT NULL) | ||
| 252 | * | ||
| 253 | * Returns: %1 if deleted else %0 | ||
| 254 | */ | ||
| 255 | static inline bool is_deleted(struct dentry *dentry) | ||
| 256 | { | ||
| 257 | if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0) | ||
| 258 | return 1; | ||
| 259 | return 0; | ||
| 260 | } | ||
| 261 | |||
| 262 | /** | ||
| 263 | * aa_path_perm - do permissions check & audit for @path | ||
| 264 | * @op: operation being checked | ||
| 265 | * @profile: profile being enforced (NOT NULL) | ||
| 266 | * @path: path to check permissions of (NOT NULL) | ||
| 267 | * @flags: any additional path flags beyond what the profile specifies | ||
| 268 | * @request: requested permissions | ||
| 269 | * @cond: conditional info for this request (NOT NULL) | ||
| 270 | * | ||
| 271 | * Returns: %0 else error if access denied or other error | ||
| 272 | */ | ||
| 273 | int aa_path_perm(int op, struct aa_profile *profile, struct path *path, | ||
| 274 | int flags, u32 request, struct path_cond *cond) | ||
| 275 | { | ||
| 276 | char *buffer = NULL; | ||
| 277 | struct file_perms perms = {}; | ||
| 278 | const char *name, *info = NULL; | ||
| 279 | int error; | ||
| 280 | |||
| 281 | flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); | ||
| 282 | error = aa_get_name(path, flags, &buffer, &name); | ||
| 283 | if (error) { | ||
| 284 | if (error == -ENOENT && is_deleted(path->dentry)) { | ||
| 285 | /* Access to open files that are deleted are | ||
| 286 | * give a pass (implicit delegation) | ||
| 287 | */ | ||
| 288 | error = 0; | ||
| 289 | perms.allow = request; | ||
| 290 | } else if (error == -ENOENT) | ||
| 291 | info = "Failed name lookup - deleted entry"; | ||
| 292 | else if (error == -ESTALE) | ||
| 293 | info = "Failed name lookup - disconnected path"; | ||
| 294 | else if (error == -ENAMETOOLONG) | ||
| 295 | info = "Failed name lookup - name too long"; | ||
| 296 | else | ||
| 297 | info = "Failed name lookup"; | ||
| 298 | } else { | ||
| 299 | aa_str_perms(profile->file.dfa, profile->file.start, name, cond, | ||
| 300 | &perms); | ||
| 301 | if (request & ~perms.allow) | ||
| 302 | error = -EACCES; | ||
| 303 | } | ||
| 304 | error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name, | ||
| 305 | NULL, cond->uid, info, error); | ||
| 306 | kfree(buffer); | ||
| 307 | |||
| 308 | return error; | ||
| 309 | } | ||
| 310 | |||
| 311 | /** | ||
| 312 | * xindex_is_subset - helper for aa_path_link | ||
| 313 | * @link: link permission set | ||
| 314 | * @target: target permission set | ||
| 315 | * | ||
| 316 | * test target x permissions are equal OR a subset of link x permissions | ||
| 317 | * this is done as part of the subset test, where a hardlink must have | ||
| 318 | * a subset of permissions that the target has. | ||
| 319 | * | ||
| 320 | * Returns: %1 if subset else %0 | ||
| 321 | */ | ||
| 322 | static inline bool xindex_is_subset(u32 link, u32 target) | ||
| 323 | { | ||
| 324 | if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) || | ||
| 325 | ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE))) | ||
| 326 | return 0; | ||
| 327 | |||
| 328 | return 1; | ||
| 329 | } | ||
| 330 | |||
| 331 | /** | ||
| 332 | * aa_path_link - Handle hard link permission check | ||
| 333 | * @profile: the profile being enforced (NOT NULL) | ||
| 334 | * @old_dentry: the target dentry (NOT NULL) | ||
| 335 | * @new_dir: directory the new link will be created in (NOT NULL) | ||
| 336 | * @new_dentry: the link being created (NOT NULL) | ||
| 337 | * | ||
| 338 | * Handle the permission test for a link & target pair. Permission | ||
| 339 | * is encoded as a pair where the link permission is determined | ||
| 340 | * first, and if allowed, the target is tested. The target test | ||
| 341 | * is done from the point of the link match (not start of DFA) | ||
| 342 | * making the target permission dependent on the link permission match. | ||
| 343 | * | ||
| 344 | * The subset test if required forces that permissions granted | ||
| 345 | * on link are a subset of the permission granted to target. | ||
| 346 | * | ||
| 347 | * Returns: %0 if allowed else error | ||
| 348 | */ | ||
| 349 | int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | ||
| 350 | struct path *new_dir, struct dentry *new_dentry) | ||
| 351 | { | ||
| 352 | struct path link = { new_dir->mnt, new_dentry }; | ||
| 353 | struct path target = { new_dir->mnt, old_dentry }; | ||
| 354 | struct path_cond cond = { | ||
| 355 | old_dentry->d_inode->i_uid, | ||
| 356 | old_dentry->d_inode->i_mode | ||
| 357 | }; | ||
| 358 | char *buffer = NULL, *buffer2 = NULL; | ||
| 359 | const char *lname, *tname = NULL, *info = NULL; | ||
| 360 | struct file_perms lperms, perms; | ||
| 361 | u32 request = AA_MAY_LINK; | ||
| 362 | unsigned int state; | ||
| 363 | int error; | ||
| 364 | |||
| 365 | lperms = nullperms; | ||
| 366 | |||
| 367 | /* buffer freed below, lname is pointer in buffer */ | ||
| 368 | error = aa_get_name(&link, profile->path_flags, &buffer, &lname); | ||
| 369 | if (error) | ||
| 370 | goto audit; | ||
| 371 | |||
| 372 | /* buffer2 freed below, tname is pointer in buffer2 */ | ||
| 373 | error = aa_get_name(&target, profile->path_flags, &buffer2, &tname); | ||
| 374 | if (error) | ||
| 375 | goto audit; | ||
| 376 | |||
| 377 | error = -EACCES; | ||
| 378 | /* aa_str_perms - handles the case of the dfa being NULL */ | ||
| 379 | state = aa_str_perms(profile->file.dfa, profile->file.start, lname, | ||
| 380 | &cond, &lperms); | ||
| 381 | |||
| 382 | if (!(lperms.allow & AA_MAY_LINK)) | ||
| 383 | goto audit; | ||
| 384 | |||
| 385 | /* test to see if target can be paired with link */ | ||
| 386 | state = aa_dfa_null_transition(profile->file.dfa, state); | ||
| 387 | aa_str_perms(profile->file.dfa, state, tname, &cond, &perms); | ||
| 388 | |||
| 389 | /* force audit/quiet masks for link are stored in the second entry | ||
| 390 | * in the link pair. | ||
| 391 | */ | ||
| 392 | lperms.audit = perms.audit; | ||
| 393 | lperms.quiet = perms.quiet; | ||
| 394 | lperms.kill = perms.kill; | ||
| 395 | |||
| 396 | if (!(perms.allow & AA_MAY_LINK)) { | ||
| 397 | info = "target restricted"; | ||
| 398 | goto audit; | ||
| 399 | } | ||
| 400 | |||
| 401 | /* done if link subset test is not required */ | ||
| 402 | if (!(perms.allow & AA_LINK_SUBSET)) | ||
| 403 | goto done_tests; | ||
| 404 | |||
| 405 | /* Do link perm subset test requiring allowed permission on link are a | ||
| 406 | * subset of the allowed permissions on target. | ||
| 407 | */ | ||
| 408 | aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, | ||
| 409 | &perms); | ||
| 410 | |||
| 411 | /* AA_MAY_LINK is not considered in the subset test */ | ||
| 412 | request = lperms.allow & ~AA_MAY_LINK; | ||
| 413 | lperms.allow &= perms.allow | AA_MAY_LINK; | ||
| 414 | |||
| 415 | request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow); | ||
| 416 | if (request & ~lperms.allow) { | ||
| 417 | goto audit; | ||
| 418 | } else if ((lperms.allow & MAY_EXEC) && | ||
| 419 | !xindex_is_subset(lperms.xindex, perms.xindex)) { | ||
| 420 | lperms.allow &= ~MAY_EXEC; | ||
| 421 | request |= MAY_EXEC; | ||
| 422 | info = "link not subset of target"; | ||
| 423 | goto audit; | ||
| 424 | } | ||
| 425 | |||
| 426 | done_tests: | ||
| 427 | error = 0; | ||
| 428 | |||
| 429 | audit: | ||
| 430 | error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request, | ||
| 431 | lname, tname, cond.uid, info, error); | ||
| 432 | kfree(buffer); | ||
| 433 | kfree(buffer2); | ||
| 434 | |||
| 435 | return error; | ||
| 436 | } | ||
| 437 | |||
| 438 | /** | ||
| 439 | * aa_file_perm - do permission revalidation check & audit for @file | ||
| 440 | * @op: operation being checked | ||
| 441 | * @profile: profile being enforced (NOT NULL) | ||
| 442 | * @file: file to revalidate access permissions on (NOT NULL) | ||
| 443 | * @request: requested permissions | ||
| 444 | * | ||
| 445 | * Returns: %0 if access allowed else error | ||
| 446 | */ | ||
| 447 | int aa_file_perm(int op, struct aa_profile *profile, struct file *file, | ||
| 448 | u32 request) | ||
| 449 | { | ||
| 450 | struct path_cond cond = { | ||
| 451 | .uid = file->f_path.dentry->d_inode->i_uid, | ||
| 452 | .mode = file->f_path.dentry->d_inode->i_mode | ||
| 453 | }; | ||
| 454 | |||
| 455 | return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, | ||
| 456 | request, &cond); | ||
| 457 | } | ||
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h new file mode 100644 index 000000000000..38ccaea08204 --- /dev/null +++ b/security/apparmor/include/apparmor.h | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor basic global and lib definitions | ||
| 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 | |||
| 15 | #ifndef __APPARMOR_H | ||
| 16 | #define __APPARMOR_H | ||
| 17 | |||
| 18 | #include <linux/fs.h> | ||
| 19 | |||
| 20 | #include "match.h" | ||
| 21 | |||
| 22 | /* Control parameters settable through module/boot flags */ | ||
| 23 | extern enum audit_mode aa_g_audit; | ||
| 24 | extern int aa_g_audit_header; | ||
| 25 | extern int aa_g_debug; | ||
| 26 | extern int aa_g_lock_policy; | ||
| 27 | extern int aa_g_logsyscall; | ||
| 28 | extern int aa_g_paranoid_load; | ||
| 29 | extern unsigned int aa_g_path_max; | ||
| 30 | |||
| 31 | /* | ||
| 32 | * DEBUG remains global (no per profile flag) since it is mostly used in sysctl | ||
| 33 | * which is not related to profile accesses. | ||
| 34 | */ | ||
| 35 | |||
| 36 | #define AA_DEBUG(fmt, args...) \ | ||
| 37 | do { \ | ||
| 38 | if (aa_g_debug && printk_ratelimit()) \ | ||
| 39 | printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ | ||
| 40 | } while (0) | ||
| 41 | |||
| 42 | #define AA_ERROR(fmt, args...) \ | ||
| 43 | do { \ | ||
| 44 | if (printk_ratelimit()) \ | ||
| 45 | printk(KERN_ERR "AppArmor: " fmt, ##args); \ | ||
| 46 | } while (0) | ||
| 47 | |||
| 48 | /* Flag indicating whether initialization completed */ | ||
| 49 | extern int apparmor_initialized __initdata; | ||
| 50 | |||
| 51 | /* fn's in lib */ | ||
| 52 | char *aa_split_fqname(char *args, char **ns_name); | ||
| 53 | void aa_info_message(const char *str); | ||
| 54 | void *kvmalloc(size_t size); | ||
| 55 | void kvfree(void *buffer); | ||
| 56 | |||
| 57 | |||
| 58 | /** | ||
| 59 | * aa_strneq - compare null terminated @str to a non null terminated substring | ||
| 60 | * @str: a null terminated string | ||
| 61 | * @sub: a substring, not necessarily null terminated | ||
| 62 | * @len: length of @sub to compare | ||
| 63 | * | ||
| 64 | * The @str string must be full consumed for this to be considered a match | ||
| 65 | */ | ||
| 66 | static inline bool aa_strneq(const char *str, const char *sub, int len) | ||
| 67 | { | ||
| 68 | return !strncmp(str, sub, len) && !str[len]; | ||
| 69 | } | ||
| 70 | |||
| 71 | /** | ||
| 72 | * aa_dfa_null_transition - step to next state after null character | ||
| 73 | * @dfa: the dfa to match against | ||
| 74 | * @start: the state of the dfa to start matching in | ||
| 75 | * | ||
| 76 | * aa_dfa_null_transition transitions to the next state after a null | ||
| 77 | * character which is not used in standard matching and is only | ||
| 78 | * used to separate pairs. | ||
| 79 | */ | ||
| 80 | static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, | ||
| 81 | unsigned int start) | ||
| 82 | { | ||
| 83 | /* the null transition only needs the string's null terminator byte */ | ||
| 84 | return aa_dfa_match_len(dfa, start, "", 1); | ||
| 85 | } | ||
| 86 | |||
| 87 | static inline bool mediated_filesystem(struct inode *inode) | ||
| 88 | { | ||
| 89 | return !(inode->i_sb->s_flags & MS_NOUSER); | ||
| 90 | } | ||
| 91 | |||
| 92 | #endif /* __APPARMOR_H */ | ||
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h new file mode 100644 index 000000000000..cb1e93a114d7 --- /dev/null +++ b/security/apparmor/include/apparmorfs.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor filesystem definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_APPARMORFS_H | ||
| 16 | #define __AA_APPARMORFS_H | ||
| 17 | |||
| 18 | extern void __init aa_destroy_aafs(void); | ||
| 19 | |||
| 20 | #endif /* __AA_APPARMORFS_H */ | ||
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h new file mode 100644 index 000000000000..1951786d32e9 --- /dev/null +++ b/security/apparmor/include/audit.h | |||
| @@ -0,0 +1,123 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor auditing function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_AUDIT_H | ||
| 16 | #define __AA_AUDIT_H | ||
| 17 | |||
| 18 | #include <linux/audit.h> | ||
| 19 | #include <linux/fs.h> | ||
| 20 | #include <linux/lsm_audit.h> | ||
| 21 | #include <linux/sched.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | |||
| 24 | #include "file.h" | ||
| 25 | |||
| 26 | struct aa_profile; | ||
| 27 | |||
| 28 | extern const char *audit_mode_names[]; | ||
| 29 | #define AUDIT_MAX_INDEX 5 | ||
| 30 | |||
| 31 | #define AUDIT_APPARMOR_AUTO 0 /* auto choose audit message type */ | ||
| 32 | |||
| 33 | enum audit_mode { | ||
| 34 | AUDIT_NORMAL, /* follow normal auditing of accesses */ | ||
| 35 | AUDIT_QUIET_DENIED, /* quiet all denied access messages */ | ||
| 36 | AUDIT_QUIET, /* quiet all messages */ | ||
| 37 | AUDIT_NOQUIET, /* do not quiet audit messages */ | ||
| 38 | AUDIT_ALL /* audit all accesses */ | ||
| 39 | }; | ||
| 40 | |||
| 41 | enum audit_type { | ||
| 42 | AUDIT_APPARMOR_AUDIT, | ||
| 43 | AUDIT_APPARMOR_ALLOWED, | ||
| 44 | AUDIT_APPARMOR_DENIED, | ||
| 45 | AUDIT_APPARMOR_HINT, | ||
| 46 | AUDIT_APPARMOR_STATUS, | ||
| 47 | AUDIT_APPARMOR_ERROR, | ||
| 48 | AUDIT_APPARMOR_KILL | ||
| 49 | }; | ||
| 50 | |||
| 51 | extern const char *op_table[]; | ||
| 52 | enum aa_ops { | ||
| 53 | OP_NULL, | ||
| 54 | |||
| 55 | OP_SYSCTL, | ||
| 56 | OP_CAPABLE, | ||
| 57 | |||
| 58 | OP_UNLINK, | ||
| 59 | OP_MKDIR, | ||
| 60 | OP_RMDIR, | ||
| 61 | OP_MKNOD, | ||
| 62 | OP_TRUNC, | ||
| 63 | OP_LINK, | ||
| 64 | OP_SYMLINK, | ||
| 65 | OP_RENAME_SRC, | ||
| 66 | OP_RENAME_DEST, | ||
| 67 | OP_CHMOD, | ||
| 68 | OP_CHOWN, | ||
| 69 | OP_GETATTR, | ||
| 70 | OP_OPEN, | ||
| 71 | |||
| 72 | OP_FPERM, | ||
| 73 | OP_FLOCK, | ||
| 74 | OP_FMMAP, | ||
| 75 | OP_FMPROT, | ||
| 76 | |||
| 77 | OP_CREATE, | ||
| 78 | OP_POST_CREATE, | ||
| 79 | OP_BIND, | ||
| 80 | OP_CONNECT, | ||
| 81 | OP_LISTEN, | ||
| 82 | OP_ACCEPT, | ||
| 83 | OP_SENDMSG, | ||
| 84 | OP_RECVMSG, | ||
| 85 | OP_GETSOCKNAME, | ||
| 86 | OP_GETPEERNAME, | ||
| 87 | OP_GETSOCKOPT, | ||
| 88 | OP_SETSOCKOPT, | ||
| 89 | OP_SOCK_SHUTDOWN, | ||
| 90 | |||
| 91 | OP_PTRACE, | ||
| 92 | |||
| 93 | OP_EXEC, | ||
| 94 | OP_CHANGE_HAT, | ||
| 95 | OP_CHANGE_PROFILE, | ||
| 96 | OP_CHANGE_ONEXEC, | ||
| 97 | |||
| 98 | OP_SETPROCATTR, | ||
| 99 | OP_SETRLIMIT, | ||
| 100 | |||
| 101 | OP_PROF_REPL, | ||
| 102 | OP_PROF_LOAD, | ||
| 103 | OP_PROF_RM, | ||
| 104 | }; | ||
| 105 | |||
| 106 | |||
| 107 | /* define a short hand for apparmor_audit_data portion of common_audit_data */ | ||
| 108 | #define aad apparmor_audit_data | ||
| 109 | |||
| 110 | void aa_audit_msg(int type, struct common_audit_data *sa, | ||
| 111 | void (*cb) (struct audit_buffer *, void *)); | ||
| 112 | int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, | ||
| 113 | struct common_audit_data *sa, | ||
| 114 | void (*cb) (struct audit_buffer *, void *)); | ||
| 115 | |||
| 116 | static inline int complain_error(int error) | ||
| 117 | { | ||
| 118 | if (error == -EPERM || error == -EACCES) | ||
| 119 | return 0; | ||
| 120 | return error; | ||
| 121 | } | ||
| 122 | |||
| 123 | #endif /* __AA_AUDIT_H */ | ||
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h new file mode 100644 index 000000000000..c24d2959ea02 --- /dev/null +++ b/security/apparmor/include/capability.h | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor capability mediation definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_CAPABILITY_H | ||
| 16 | #define __AA_CAPABILITY_H | ||
| 17 | |||
| 18 | #include <linux/sched.h> | ||
| 19 | |||
| 20 | struct aa_profile; | ||
| 21 | |||
| 22 | /* aa_caps - confinement data for capabilities | ||
| 23 | * @allowed: capabilities mask | ||
| 24 | * @audit: caps that are to be audited | ||
| 25 | * @quiet: caps that should not be audited | ||
| 26 | * @kill: caps that when requested will result in the task being killed | ||
| 27 | * @extended: caps that are subject finer grained mediation | ||
| 28 | */ | ||
| 29 | struct aa_caps { | ||
| 30 | kernel_cap_t allow; | ||
| 31 | kernel_cap_t audit; | ||
| 32 | kernel_cap_t quiet; | ||
| 33 | kernel_cap_t kill; | ||
| 34 | kernel_cap_t extended; | ||
| 35 | }; | ||
| 36 | |||
| 37 | int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, | ||
| 38 | int audit); | ||
| 39 | |||
| 40 | static inline void aa_free_cap_rules(struct aa_caps *caps) | ||
| 41 | { | ||
| 42 | /* NOP */ | ||
| 43 | } | ||
| 44 | |||
| 45 | #endif /* __AA_CAPBILITY_H */ | ||
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h new file mode 100644 index 000000000000..a9cbee4d9e48 --- /dev/null +++ b/security/apparmor/include/context.h | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor contexts used to associate "labels" to objects. | ||
| 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 | |||
| 15 | #ifndef __AA_CONTEXT_H | ||
| 16 | #define __AA_CONTEXT_H | ||
| 17 | |||
| 18 | #include <linux/cred.h> | ||
| 19 | #include <linux/slab.h> | ||
| 20 | #include <linux/sched.h> | ||
| 21 | |||
| 22 | #include "policy.h" | ||
| 23 | |||
| 24 | /* struct aa_file_cxt - the AppArmor context the file was opened in | ||
| 25 | * @perms: the permission the file was opened with | ||
| 26 | * | ||
| 27 | * The file_cxt could currently be directly stored in file->f_security | ||
| 28 | * as the profile reference is now stored in the f_cred. However the | ||
| 29 | * cxt struct will expand in the future so we keep the struct. | ||
| 30 | */ | ||
| 31 | struct aa_file_cxt { | ||
| 32 | u16 allow; | ||
| 33 | }; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * aa_alloc_file_context - allocate file_cxt | ||
| 37 | * @gfp: gfp flags for allocation | ||
| 38 | * | ||
| 39 | * Returns: file_cxt or NULL on failure | ||
| 40 | */ | ||
| 41 | static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) | ||
| 42 | { | ||
| 43 | return kzalloc(sizeof(struct aa_file_cxt), gfp); | ||
| 44 | } | ||
| 45 | |||
| 46 | /** | ||
| 47 | * aa_free_file_context - free a file_cxt | ||
| 48 | * @cxt: file_cxt to free (MAYBE_NULL) | ||
| 49 | */ | ||
| 50 | static inline void aa_free_file_context(struct aa_file_cxt *cxt) | ||
| 51 | { | ||
| 52 | if (cxt) | ||
| 53 | kzfree(cxt); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * struct aa_task_cxt - primary label for confined tasks | ||
| 58 | * @profile: the current profile (NOT NULL) | ||
| 59 | * @exec: profile to transition to on next exec (MAYBE NULL) | ||
| 60 | * @previous: profile the task may return to (MAYBE NULL) | ||
| 61 | * @token: magic value the task must know for returning to @previous_profile | ||
| 62 | * | ||
| 63 | * Contains the task's current profile (which could change due to | ||
| 64 | * change_hat). Plus the hat_magic needed during change_hat. | ||
| 65 | * | ||
| 66 | * TODO: make so a task can be confined by a stack of contexts | ||
| 67 | */ | ||
| 68 | struct aa_task_cxt { | ||
| 69 | struct aa_profile *profile; | ||
| 70 | struct aa_profile *onexec; | ||
| 71 | struct aa_profile *previous; | ||
| 72 | u64 token; | ||
| 73 | }; | ||
| 74 | |||
| 75 | struct aa_task_cxt *aa_alloc_task_context(gfp_t flags); | ||
| 76 | void aa_free_task_context(struct aa_task_cxt *cxt); | ||
| 77 | void aa_dup_task_context(struct aa_task_cxt *new, | ||
| 78 | const struct aa_task_cxt *old); | ||
| 79 | int aa_replace_current_profile(struct aa_profile *profile); | ||
| 80 | int aa_set_current_onexec(struct aa_profile *profile); | ||
| 81 | int aa_set_current_hat(struct aa_profile *profile, u64 token); | ||
| 82 | int aa_restore_previous_profile(u64 cookie); | ||
| 83 | |||
| 84 | /** | ||
| 85 | * __aa_task_is_confined - determine if @task has any confinement | ||
| 86 | * @task: task to check confinement of (NOT NULL) | ||
| 87 | * | ||
| 88 | * If @task != current needs to be called in RCU safe critical section | ||
| 89 | */ | ||
| 90 | static inline bool __aa_task_is_confined(struct task_struct *task) | ||
| 91 | { | ||
| 92 | struct aa_task_cxt *cxt = __task_cred(task)->security; | ||
| 93 | |||
| 94 | BUG_ON(!cxt || !cxt->profile); | ||
| 95 | if (unconfined(aa_newest_version(cxt->profile))) | ||
| 96 | return 0; | ||
| 97 | |||
| 98 | return 1; | ||
| 99 | } | ||
| 100 | |||
| 101 | /** | ||
| 102 | * aa_cred_profile - obtain cred's profiles | ||
| 103 | * @cred: cred to obtain profiles from (NOT NULL) | ||
| 104 | * | ||
| 105 | * Returns: confining profile | ||
| 106 | * | ||
| 107 | * does NOT increment reference count | ||
| 108 | */ | ||
| 109 | static inline struct aa_profile *aa_cred_profile(const struct cred *cred) | ||
| 110 | { | ||
| 111 | struct aa_task_cxt *cxt = cred->security; | ||
| 112 | BUG_ON(!cxt || !cxt->profile); | ||
| 113 | return aa_newest_version(cxt->profile); | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * __aa_current_profile - find the current tasks confining profile | ||
| 118 | * | ||
| 119 | * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) | ||
| 120 | * | ||
| 121 | * This fn will not update the tasks cred to the most up to date version | ||
| 122 | * of the profile so it is safe to call when inside of locks. | ||
| 123 | */ | ||
| 124 | static inline struct aa_profile *__aa_current_profile(void) | ||
| 125 | { | ||
| 126 | return aa_cred_profile(current_cred()); | ||
| 127 | } | ||
| 128 | |||
| 129 | /** | ||
| 130 | * aa_current_profile - find the current tasks confining profile and do updates | ||
| 131 | * | ||
| 132 | * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) | ||
| 133 | * | ||
| 134 | * This fn will update the tasks cred structure if the profile has been | ||
| 135 | * replaced. Not safe to call inside locks | ||
| 136 | */ | ||
| 137 | static inline struct aa_profile *aa_current_profile(void) | ||
| 138 | { | ||
| 139 | const struct aa_task_cxt *cxt = current_cred()->security; | ||
| 140 | struct aa_profile *profile; | ||
| 141 | BUG_ON(!cxt || !cxt->profile); | ||
| 142 | |||
| 143 | profile = aa_newest_version(cxt->profile); | ||
| 144 | /* | ||
| 145 | * Whether or not replacement succeeds, use newest profile so | ||
| 146 | * there is no need to update it after replacement. | ||
| 147 | */ | ||
| 148 | if (unlikely((cxt->profile != profile))) | ||
| 149 | aa_replace_current_profile(profile); | ||
| 150 | |||
| 151 | return profile; | ||
| 152 | } | ||
| 153 | |||
| 154 | #endif /* __AA_CONTEXT_H */ | ||
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h new file mode 100644 index 000000000000..de04464f0a3f --- /dev/null +++ b/security/apparmor/include/domain.h | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor security domain transition function definitions. | ||
| 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 | |||
| 15 | #include <linux/binfmts.h> | ||
| 16 | #include <linux/types.h> | ||
| 17 | |||
| 18 | #ifndef __AA_DOMAIN_H | ||
| 19 | #define __AA_DOMAIN_H | ||
| 20 | |||
| 21 | struct aa_domain { | ||
| 22 | int size; | ||
| 23 | char **table; | ||
| 24 | }; | ||
| 25 | |||
| 26 | int apparmor_bprm_set_creds(struct linux_binprm *bprm); | ||
| 27 | int apparmor_bprm_secureexec(struct linux_binprm *bprm); | ||
| 28 | void apparmor_bprm_committing_creds(struct linux_binprm *bprm); | ||
| 29 | void apparmor_bprm_committed_creds(struct linux_binprm *bprm); | ||
| 30 | |||
| 31 | void aa_free_domain_entries(struct aa_domain *domain); | ||
| 32 | int aa_change_hat(const char *hats[], int count, u64 token, bool permtest); | ||
| 33 | int aa_change_profile(const char *ns_name, const char *name, bool onexec, | ||
| 34 | bool permtest); | ||
| 35 | |||
| 36 | #endif /* __AA_DOMAIN_H */ | ||
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h new file mode 100644 index 000000000000..be36feabb16a --- /dev/null +++ b/security/apparmor/include/file.h | |||
| @@ -0,0 +1,217 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor file mediation function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_FILE_H | ||
| 16 | #define __AA_FILE_H | ||
| 17 | |||
| 18 | #include <linux/path.h> | ||
| 19 | |||
| 20 | #include "domain.h" | ||
| 21 | #include "match.h" | ||
| 22 | |||
| 23 | struct aa_profile; | ||
| 24 | |||
| 25 | /* | ||
| 26 | * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags | ||
| 27 | * for profile permissions | ||
| 28 | */ | ||
| 29 | #define AA_MAY_CREATE 0x0010 | ||
| 30 | #define AA_MAY_DELETE 0x0020 | ||
| 31 | #define AA_MAY_META_WRITE 0x0040 | ||
| 32 | #define AA_MAY_META_READ 0x0080 | ||
| 33 | |||
| 34 | #define AA_MAY_CHMOD 0x0100 | ||
| 35 | #define AA_MAY_CHOWN 0x0200 | ||
| 36 | #define AA_MAY_LOCK 0x0400 | ||
| 37 | #define AA_EXEC_MMAP 0x0800 | ||
| 38 | |||
| 39 | #define AA_MAY_LINK 0x1000 | ||
| 40 | #define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ | ||
| 41 | #define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ | ||
| 42 | #define AA_MAY_CHANGE_PROFILE 0x80000000 | ||
| 43 | #define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ | ||
| 44 | |||
| 45 | #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ | ||
| 46 | AA_MAY_CREATE | AA_MAY_DELETE | \ | ||
| 47 | AA_MAY_META_READ | AA_MAY_META_WRITE | \ | ||
| 48 | AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ | ||
| 49 | AA_EXEC_MMAP | AA_MAY_LINK) | ||
| 50 | |||
| 51 | /* | ||
| 52 | * The xindex is broken into 3 parts | ||
| 53 | * - index - an index into either the exec name table or the variable table | ||
| 54 | * - exec type - which determines how the executable name and index are used | ||
| 55 | * - flags - which modify how the destination name is applied | ||
| 56 | */ | ||
| 57 | #define AA_X_INDEX_MASK 0x03ff | ||
| 58 | |||
| 59 | #define AA_X_TYPE_MASK 0x0c00 | ||
| 60 | #define AA_X_TYPE_SHIFT 10 | ||
| 61 | #define AA_X_NONE 0x0000 | ||
| 62 | #define AA_X_NAME 0x0400 /* use executable name px */ | ||
| 63 | #define AA_X_TABLE 0x0800 /* use a specified name ->n# */ | ||
| 64 | |||
| 65 | #define AA_X_UNSAFE 0x1000 | ||
| 66 | #define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */ | ||
| 67 | #define AA_X_INHERIT 0x4000 | ||
| 68 | #define AA_X_UNCONFINED 0x8000 | ||
| 69 | |||
| 70 | /* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */ | ||
| 71 | #define AA_SECURE_X_NEEDED 0x8000 | ||
| 72 | |||
| 73 | /* need to make conditional which ones are being set */ | ||
| 74 | struct path_cond { | ||
| 75 | uid_t uid; | ||
| 76 | umode_t mode; | ||
| 77 | }; | ||
| 78 | |||
| 79 | /* struct file_perms - file permission | ||
| 80 | * @allow: mask of permissions that are allowed | ||
| 81 | * @audit: mask of permissions to force an audit message for | ||
| 82 | * @quiet: mask of permissions to quiet audit messages for | ||
| 83 | * @kill: mask of permissions that when matched will kill the task | ||
| 84 | * @xindex: exec transition index if @allow contains MAY_EXEC | ||
| 85 | * | ||
| 86 | * The @audit and @queit mask should be mutually exclusive. | ||
| 87 | */ | ||
| 88 | struct file_perms { | ||
| 89 | u32 allow; | ||
| 90 | u32 audit; | ||
| 91 | u32 quiet; | ||
| 92 | u32 kill; | ||
| 93 | u16 xindex; | ||
| 94 | }; | ||
| 95 | |||
| 96 | extern struct file_perms nullperms; | ||
| 97 | |||
| 98 | #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) | ||
| 99 | |||
| 100 | /* FIXME: split perms from dfa and match this to description | ||
| 101 | * also add delegation info. | ||
| 102 | */ | ||
| 103 | static inline u16 dfa_map_xindex(u16 mask) | ||
| 104 | { | ||
| 105 | u16 old_index = (mask >> 10) & 0xf; | ||
| 106 | u16 index = 0; | ||
| 107 | |||
| 108 | if (mask & 0x100) | ||
| 109 | index |= AA_X_UNSAFE; | ||
| 110 | if (mask & 0x200) | ||
| 111 | index |= AA_X_INHERIT; | ||
| 112 | if (mask & 0x80) | ||
| 113 | index |= AA_X_UNCONFINED; | ||
| 114 | |||
| 115 | if (old_index == 1) { | ||
| 116 | index |= AA_X_UNCONFINED; | ||
| 117 | } else if (old_index == 2) { | ||
| 118 | index |= AA_X_NAME; | ||
| 119 | } else if (old_index == 3) { | ||
| 120 | index |= AA_X_NAME | AA_X_CHILD; | ||
| 121 | } else { | ||
| 122 | index |= AA_X_TABLE; | ||
| 123 | index |= old_index - 4; | ||
| 124 | } | ||
| 125 | |||
| 126 | return index; | ||
| 127 | } | ||
| 128 | |||
| 129 | /* | ||
| 130 | * map old dfa inline permissions to new format | ||
| 131 | */ | ||
| 132 | #define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ | ||
| 133 | ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) | ||
| 134 | #define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) | ||
| 135 | #define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) | ||
| 136 | #define dfa_user_xindex(dfa, state) \ | ||
| 137 | (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) | ||
| 138 | |||
| 139 | #define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ | ||
| 140 | 0x7f) | \ | ||
| 141 | ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) | ||
| 142 | #define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) | ||
| 143 | #define dfa_other_quiet(dfa, state) \ | ||
| 144 | ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) | ||
| 145 | #define dfa_other_xindex(dfa, state) \ | ||
| 146 | dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) | ||
| 147 | |||
| 148 | int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, | ||
| 149 | gfp_t gfp, int op, u32 request, const char *name, | ||
| 150 | const char *target, uid_t ouid, const char *info, int error); | ||
| 151 | |||
| 152 | /** | ||
| 153 | * struct aa_file_rules - components used for file rule permissions | ||
| 154 | * @dfa: dfa to match path names and conditionals against | ||
| 155 | * @perms: permission table indexed by the matched state accept entry of @dfa | ||
| 156 | * @trans: transition table for indexed by named x transitions | ||
| 157 | * | ||
| 158 | * File permission are determined by matching a path against @dfa and then | ||
| 159 | * then using the value of the accept entry for the matching state as | ||
| 160 | * an index into @perms. If a named exec transition is required it is | ||
| 161 | * looked up in the transition table. | ||
| 162 | */ | ||
| 163 | struct aa_file_rules { | ||
| 164 | unsigned int start; | ||
| 165 | struct aa_dfa *dfa; | ||
| 166 | /* struct perms perms; */ | ||
| 167 | struct aa_domain trans; | ||
| 168 | /* TODO: add delegate table */ | ||
| 169 | }; | ||
| 170 | |||
| 171 | unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, | ||
| 172 | const char *name, struct path_cond *cond, | ||
| 173 | struct file_perms *perms); | ||
| 174 | |||
| 175 | int aa_path_perm(int op, struct aa_profile *profile, struct path *path, | ||
| 176 | int flags, u32 request, struct path_cond *cond); | ||
| 177 | |||
| 178 | int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, | ||
| 179 | struct path *new_dir, struct dentry *new_dentry); | ||
| 180 | |||
| 181 | int aa_file_perm(int op, struct aa_profile *profile, struct file *file, | ||
| 182 | u32 request); | ||
| 183 | |||
| 184 | static inline void aa_free_file_rules(struct aa_file_rules *rules) | ||
| 185 | { | ||
| 186 | aa_put_dfa(rules->dfa); | ||
| 187 | aa_free_domain_entries(&rules->trans); | ||
| 188 | } | ||
| 189 | |||
| 190 | #define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40)) | ||
| 191 | |||
| 192 | /* from namei.c */ | ||
| 193 | #define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x)) | ||
| 194 | |||
| 195 | /** | ||
| 196 | * aa_map_file_perms - map file flags to AppArmor permissions | ||
| 197 | * @file: open file to map flags to AppArmor permissions | ||
| 198 | * | ||
| 199 | * Returns: apparmor permission set for the file | ||
| 200 | */ | ||
| 201 | static inline u32 aa_map_file_to_perms(struct file *file) | ||
| 202 | { | ||
| 203 | int flags = MAP_OPEN_FLAGS(file->f_flags); | ||
| 204 | u32 perms = ACC_FMODE(file->f_mode); | ||
| 205 | |||
| 206 | if ((flags & O_APPEND) && (perms & MAY_WRITE)) | ||
| 207 | perms = (perms & ~MAY_WRITE) | MAY_APPEND; | ||
| 208 | /* trunc implies write permission */ | ||
| 209 | if (flags & O_TRUNC) | ||
| 210 | perms |= MAY_WRITE; | ||
| 211 | if (flags & O_CREAT) | ||
| 212 | perms |= AA_MAY_CREATE; | ||
| 213 | |||
| 214 | return perms; | ||
| 215 | } | ||
| 216 | |||
| 217 | #endif /* __AA_FILE_H */ | ||
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h new file mode 100644 index 000000000000..aeda0fbc8b2f --- /dev/null +++ b/security/apparmor/include/ipc.h | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor ipc mediation function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_IPC_H | ||
| 16 | #define __AA_IPC_H | ||
| 17 | |||
| 18 | #include <linux/sched.h> | ||
| 19 | |||
| 20 | struct aa_profile; | ||
| 21 | |||
| 22 | int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, | ||
| 23 | struct aa_profile *tracee, unsigned int mode); | ||
| 24 | |||
| 25 | int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, | ||
| 26 | unsigned int mode); | ||
| 27 | |||
| 28 | #endif /* __AA_IPC_H */ | ||
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h new file mode 100644 index 000000000000..734a6d35112c --- /dev/null +++ b/security/apparmor/include/match.h | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor policy dfa matching engine definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_MATCH_H | ||
| 16 | #define __AA_MATCH_H | ||
| 17 | |||
| 18 | #include <linux/workqueue.h> | ||
| 19 | |||
| 20 | #define DFA_NOMATCH 0 | ||
| 21 | #define DFA_START 1 | ||
| 22 | |||
| 23 | #define DFA_VALID_PERM_MASK 0xffffffff | ||
| 24 | #define DFA_VALID_PERM2_MASK 0xffffffff | ||
| 25 | |||
| 26 | /** | ||
| 27 | * The format used for transition tables is based on the GNU flex table | ||
| 28 | * file format (--tables-file option; see Table File Format in the flex | ||
| 29 | * info pages and the flex sources for documentation). The magic number | ||
| 30 | * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because | ||
| 31 | * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used | ||
| 32 | * slightly differently (see the apparmor-parser package). | ||
| 33 | */ | ||
| 34 | |||
| 35 | #define YYTH_MAGIC 0x1B5E783D | ||
| 36 | #define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */ | ||
| 37 | |||
| 38 | struct table_set_header { | ||
| 39 | u32 th_magic; /* YYTH_MAGIC */ | ||
| 40 | u32 th_hsize; | ||
| 41 | u32 th_ssize; | ||
| 42 | u16 th_flags; | ||
| 43 | char th_version[]; | ||
| 44 | }; | ||
| 45 | |||
| 46 | /* The YYTD_ID are one less than flex table mappings. The flex id | ||
| 47 | * has 1 subtracted at table load time, this allows us to directly use the | ||
| 48 | * ID's as indexes. | ||
| 49 | */ | ||
| 50 | #define YYTD_ID_ACCEPT 0 | ||
| 51 | #define YYTD_ID_BASE 1 | ||
| 52 | #define YYTD_ID_CHK 2 | ||
| 53 | #define YYTD_ID_DEF 3 | ||
| 54 | #define YYTD_ID_EC 4 | ||
| 55 | #define YYTD_ID_META 5 | ||
| 56 | #define YYTD_ID_ACCEPT2 6 | ||
| 57 | #define YYTD_ID_NXT 7 | ||
| 58 | #define YYTD_ID_TSIZE 8 | ||
| 59 | |||
| 60 | #define YYTD_DATA8 1 | ||
| 61 | #define YYTD_DATA16 2 | ||
| 62 | #define YYTD_DATA32 4 | ||
| 63 | #define YYTD_DATA64 8 | ||
| 64 | |||
| 65 | /* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the | ||
| 66 | * first flags | ||
| 67 | */ | ||
| 68 | #define ACCEPT1_FLAGS(X) ((X) & 0x3f) | ||
| 69 | #define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2) | ||
| 70 | #define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X) | ||
| 71 | #define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2) | ||
| 72 | #define DFA_FLAG_VERIFY_STATES 0x1000 | ||
| 73 | |||
| 74 | struct table_header { | ||
| 75 | u16 td_id; | ||
| 76 | u16 td_flags; | ||
| 77 | u32 td_hilen; | ||
| 78 | u32 td_lolen; | ||
| 79 | char td_data[]; | ||
| 80 | }; | ||
| 81 | |||
| 82 | #define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data)) | ||
| 83 | #define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data)) | ||
| 84 | #define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data)) | ||
| 85 | #define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data)) | ||
| 86 | #define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data)) | ||
| 87 | #define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data)) | ||
| 88 | #define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data)) | ||
| 89 | |||
| 90 | struct aa_dfa { | ||
| 91 | struct kref count; | ||
| 92 | u16 flags; | ||
| 93 | struct table_header *tables[YYTD_ID_TSIZE]; | ||
| 94 | }; | ||
| 95 | |||
| 96 | #define byte_to_byte(X) (X) | ||
| 97 | |||
| 98 | #define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ | ||
| 99 | do { \ | ||
| 100 | typeof(LEN) __i; \ | ||
| 101 | TYPE *__t = (TYPE *) TABLE; \ | ||
| 102 | TYPE *__b = (TYPE *) BLOB; \ | ||
| 103 | for (__i = 0; __i < LEN; __i++) { \ | ||
| 104 | __t[__i] = NTOHX(__b[__i]); \ | ||
| 105 | } \ | ||
| 106 | } while (0) | ||
| 107 | |||
| 108 | static inline size_t table_size(size_t len, size_t el_size) | ||
| 109 | { | ||
| 110 | return ALIGN(sizeof(struct table_header) + len * el_size, 8); | ||
| 111 | } | ||
| 112 | |||
| 113 | struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); | ||
| 114 | unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, | ||
| 115 | const char *str, int len); | ||
| 116 | unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, | ||
| 117 | const char *str); | ||
| 118 | void aa_dfa_free_kref(struct kref *kref); | ||
| 119 | |||
| 120 | /** | ||
| 121 | * aa_put_dfa - put a dfa refcount | ||
| 122 | * @dfa: dfa to put refcount (MAYBE NULL) | ||
| 123 | * | ||
| 124 | * Requires: if @dfa != NULL that a valid refcount be held | ||
| 125 | */ | ||
| 126 | static inline void aa_put_dfa(struct aa_dfa *dfa) | ||
| 127 | { | ||
| 128 | if (dfa) | ||
| 129 | kref_put(&dfa->count, aa_dfa_free_kref); | ||
| 130 | } | ||
| 131 | |||
| 132 | #endif /* __AA_MATCH_H */ | ||
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h new file mode 100644 index 000000000000..27b327a7fae5 --- /dev/null +++ b/security/apparmor/include/path.h | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor basic path manipulation function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_PATH_H | ||
| 16 | #define __AA_PATH_H | ||
| 17 | |||
| 18 | |||
| 19 | enum path_flags { | ||
| 20 | PATH_IS_DIR = 0x1, /* path is a directory */ | ||
| 21 | PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ | ||
| 22 | PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ | ||
| 23 | PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ | ||
| 24 | |||
| 25 | PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */ | ||
| 26 | PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ | ||
| 27 | }; | ||
| 28 | |||
| 29 | int aa_get_name(struct path *path, int flags, char **buffer, const char **name); | ||
| 30 | |||
| 31 | #endif /* __AA_PATH_H */ | ||
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h new file mode 100644 index 000000000000..aeda5cf56904 --- /dev/null +++ b/security/apparmor/include/policy.h | |||
| @@ -0,0 +1,305 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor policy definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_POLICY_H | ||
| 16 | #define __AA_POLICY_H | ||
| 17 | |||
| 18 | #include <linux/capability.h> | ||
| 19 | #include <linux/cred.h> | ||
| 20 | #include <linux/kref.h> | ||
| 21 | #include <linux/sched.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | #include <linux/socket.h> | ||
| 24 | |||
| 25 | #include "apparmor.h" | ||
| 26 | #include "audit.h" | ||
| 27 | #include "capability.h" | ||
| 28 | #include "domain.h" | ||
| 29 | #include "file.h" | ||
| 30 | #include "resource.h" | ||
| 31 | |||
| 32 | extern const char *profile_mode_names[]; | ||
| 33 | #define APPARMOR_NAMES_MAX_INDEX 3 | ||
| 34 | |||
| 35 | #define COMPLAIN_MODE(_profile) \ | ||
| 36 | ((aa_g_profile_mode == APPARMOR_COMPLAIN) || \ | ||
| 37 | ((_profile)->mode == APPARMOR_COMPLAIN)) | ||
| 38 | |||
| 39 | #define KILL_MODE(_profile) \ | ||
| 40 | ((aa_g_profile_mode == APPARMOR_KILL) || \ | ||
| 41 | ((_profile)->mode == APPARMOR_KILL)) | ||
| 42 | |||
| 43 | #define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT) | ||
| 44 | |||
| 45 | /* | ||
| 46 | * FIXME: currently need a clean way to replace and remove profiles as a | ||
| 47 | * set. It should be done at the namespace level. | ||
| 48 | * Either, with a set of profiles loaded at the namespace level or via | ||
| 49 | * a mark and remove marked interface. | ||
| 50 | */ | ||
| 51 | enum profile_mode { | ||
| 52 | APPARMOR_ENFORCE, /* enforce access rules */ | ||
| 53 | APPARMOR_COMPLAIN, /* allow and log access violations */ | ||
| 54 | APPARMOR_KILL, /* kill task on access violation */ | ||
| 55 | }; | ||
| 56 | |||
| 57 | enum profile_flags { | ||
| 58 | PFLAG_HAT = 1, /* profile is a hat */ | ||
| 59 | PFLAG_UNCONFINED = 2, /* profile is an unconfined profile */ | ||
| 60 | PFLAG_NULL = 4, /* profile is null learning profile */ | ||
| 61 | PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ | ||
| 62 | PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ | ||
| 63 | PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ | ||
| 64 | PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ | ||
| 65 | PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */ | ||
| 66 | |||
| 67 | /* These flags must correspond with PATH_flags */ | ||
| 68 | PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */ | ||
| 69 | }; | ||
| 70 | |||
| 71 | struct aa_profile; | ||
| 72 | |||
| 73 | /* struct aa_policy - common part of both namespaces and profiles | ||
| 74 | * @name: name of the object | ||
| 75 | * @hname - The hierarchical name | ||
| 76 | * @count: reference count of the obj | ||
| 77 | * @list: list policy object is on | ||
| 78 | * @profiles: head of the profiles list contained in the object | ||
| 79 | */ | ||
| 80 | struct aa_policy { | ||
| 81 | char *name; | ||
| 82 | char *hname; | ||
| 83 | struct kref count; | ||
| 84 | struct list_head list; | ||
| 85 | struct list_head profiles; | ||
| 86 | }; | ||
| 87 | |||
| 88 | /* struct aa_ns_acct - accounting of profiles in namespace | ||
| 89 | * @max_size: maximum space allowed for all profiles in namespace | ||
| 90 | * @max_count: maximum number of profiles that can be in this namespace | ||
| 91 | * @size: current size of profiles | ||
| 92 | * @count: current count of profiles (includes null profiles) | ||
| 93 | */ | ||
| 94 | struct aa_ns_acct { | ||
| 95 | int max_size; | ||
| 96 | int max_count; | ||
| 97 | int size; | ||
| 98 | int count; | ||
| 99 | }; | ||
| 100 | |||
| 101 | /* struct aa_namespace - namespace for a set of profiles | ||
| 102 | * @base: common policy | ||
| 103 | * @parent: parent of namespace | ||
| 104 | * @lock: lock for modifying the object | ||
| 105 | * @acct: accounting for the namespace | ||
| 106 | * @unconfined: special unconfined profile for the namespace | ||
| 107 | * @sub_ns: list of namespaces under the current namespace. | ||
| 108 | * | ||
| 109 | * An aa_namespace defines the set profiles that are searched to determine | ||
| 110 | * which profile to attach to a task. Profiles can not be shared between | ||
| 111 | * aa_namespaces and profile names within a namespace are guaranteed to be | ||
| 112 | * unique. When profiles in separate namespaces have the same name they | ||
| 113 | * are NOT considered to be equivalent. | ||
| 114 | * | ||
| 115 | * Namespaces are hierarchical and only namespaces and profiles below the | ||
| 116 | * current namespace are visible. | ||
| 117 | * | ||
| 118 | * Namespace names must be unique and can not contain the characters :/\0 | ||
| 119 | * | ||
| 120 | * FIXME TODO: add vserver support of namespaces (can it all be done in | ||
| 121 | * userspace?) | ||
| 122 | */ | ||
| 123 | struct aa_namespace { | ||
| 124 | struct aa_policy base; | ||
| 125 | struct aa_namespace *parent; | ||
| 126 | rwlock_t lock; | ||
| 127 | struct aa_ns_acct acct; | ||
| 128 | struct aa_profile *unconfined; | ||
| 129 | struct list_head sub_ns; | ||
| 130 | }; | ||
| 131 | |||
| 132 | /* struct aa_profile - basic confinement data | ||
| 133 | * @base - base components of the profile (name, refcount, lists, lock ...) | ||
| 134 | * @parent: parent of profile | ||
| 135 | * @ns: namespace the profile is in | ||
| 136 | * @replacedby: is set to the profile that replaced this profile | ||
| 137 | * @rename: optional profile name that this profile renamed | ||
| 138 | * @xmatch: optional extended matching for unconfined executables names | ||
| 139 | * @xmatch_len: xmatch prefix len, used to determine xmatch priority | ||
| 140 | * @sid: the unique security id number of this profile | ||
| 141 | * @audit: the auditing mode of the profile | ||
| 142 | * @mode: the enforcement mode of the profile | ||
| 143 | * @flags: flags controlling profile behavior | ||
| 144 | * @path_flags: flags controlling path generation behavior | ||
| 145 | * @size: the memory consumed by this profiles rules | ||
| 146 | * @file: The set of rules governing basic file access and domain transitions | ||
| 147 | * @caps: capabilities for the profile | ||
| 148 | * @rlimits: rlimits for the profile | ||
| 149 | * | ||
| 150 | * The AppArmor profile contains the basic confinement data. Each profile | ||
| 151 | * has a name, and exists in a namespace. The @name and @exec_match are | ||
| 152 | * used to determine profile attachment against unconfined tasks. All other | ||
| 153 | * attachments are determined by profile X transition rules. | ||
| 154 | * | ||
| 155 | * The @replacedby field is write protected by the profile lock. Reads | ||
| 156 | * are assumed to be atomic, and are done without locking. | ||
| 157 | * | ||
| 158 | * Profiles have a hierarchy where hats and children profiles keep | ||
| 159 | * a reference to their parent. | ||
| 160 | * | ||
| 161 | * Profile names can not begin with a : and can not contain the \0 | ||
| 162 | * character. If a profile name begins with / it will be considered when | ||
| 163 | * determining profile attachment on "unconfined" tasks. | ||
| 164 | */ | ||
| 165 | struct aa_profile { | ||
| 166 | struct aa_policy base; | ||
| 167 | struct aa_profile *parent; | ||
| 168 | |||
| 169 | struct aa_namespace *ns; | ||
| 170 | struct aa_profile *replacedby; | ||
| 171 | const char *rename; | ||
| 172 | |||
| 173 | struct aa_dfa *xmatch; | ||
| 174 | int xmatch_len; | ||
| 175 | u32 sid; | ||
| 176 | enum audit_mode audit; | ||
| 177 | enum profile_mode mode; | ||
| 178 | u32 flags; | ||
| 179 | u32 path_flags; | ||
| 180 | int size; | ||
| 181 | |||
| 182 | struct aa_file_rules file; | ||
| 183 | struct aa_caps caps; | ||
| 184 | struct aa_rlimit rlimits; | ||
| 185 | }; | ||
| 186 | |||
| 187 | extern struct aa_namespace *root_ns; | ||
| 188 | extern enum profile_mode aa_g_profile_mode; | ||
| 189 | |||
| 190 | void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); | ||
| 191 | |||
| 192 | bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view); | ||
| 193 | const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child); | ||
| 194 | int aa_alloc_root_ns(void); | ||
| 195 | void aa_free_root_ns(void); | ||
| 196 | void aa_free_namespace_kref(struct kref *kref); | ||
| 197 | |||
| 198 | struct aa_namespace *aa_find_namespace(struct aa_namespace *root, | ||
| 199 | const char *name); | ||
| 200 | |||
| 201 | static inline struct aa_policy *aa_get_common(struct aa_policy *c) | ||
| 202 | { | ||
| 203 | if (c) | ||
| 204 | kref_get(&c->count); | ||
| 205 | |||
| 206 | return c; | ||
| 207 | } | ||
| 208 | |||
| 209 | /** | ||
| 210 | * aa_get_namespace - increment references count on @ns | ||
| 211 | * @ns: namespace to increment reference count of (MAYBE NULL) | ||
| 212 | * | ||
| 213 | * Returns: pointer to @ns, if @ns is NULL returns NULL | ||
| 214 | * Requires: @ns must be held with valid refcount when called | ||
| 215 | */ | ||
| 216 | static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) | ||
| 217 | { | ||
| 218 | if (ns) | ||
| 219 | kref_get(&(ns->base.count)); | ||
| 220 | |||
| 221 | return ns; | ||
| 222 | } | ||
| 223 | |||
| 224 | /** | ||
| 225 | * aa_put_namespace - decrement refcount on @ns | ||
| 226 | * @ns: namespace to put reference of | ||
| 227 | * | ||
| 228 | * Decrement reference count of @ns and if no longer in use free it | ||
| 229 | */ | ||
| 230 | static inline void aa_put_namespace(struct aa_namespace *ns) | ||
| 231 | { | ||
| 232 | if (ns) | ||
| 233 | kref_put(&ns->base.count, aa_free_namespace_kref); | ||
| 234 | } | ||
| 235 | |||
| 236 | struct aa_profile *aa_alloc_profile(const char *name); | ||
| 237 | struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat); | ||
| 238 | void aa_free_profile_kref(struct kref *kref); | ||
| 239 | struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); | ||
| 240 | struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); | ||
| 241 | struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); | ||
| 242 | |||
| 243 | ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace); | ||
| 244 | ssize_t aa_remove_profiles(char *name, size_t size); | ||
| 245 | |||
| 246 | #define PROF_ADD 1 | ||
| 247 | #define PROF_REPLACE 0 | ||
| 248 | |||
| 249 | #define unconfined(X) ((X)->flags & PFLAG_UNCONFINED) | ||
| 250 | |||
| 251 | /** | ||
| 252 | * aa_newest_version - find the newest version of @profile | ||
| 253 | * @profile: the profile to check for newer versions of (NOT NULL) | ||
| 254 | * | ||
| 255 | * Returns: newest version of @profile, if @profile is the newest version | ||
| 256 | * return @profile. | ||
| 257 | * | ||
| 258 | * NOTE: the profile returned is not refcounted, The refcount on @profile | ||
| 259 | * must be held until the caller decides what to do with the returned newest | ||
| 260 | * version. | ||
| 261 | */ | ||
| 262 | static inline struct aa_profile *aa_newest_version(struct aa_profile *profile) | ||
| 263 | { | ||
| 264 | while (profile->replacedby) | ||
| 265 | profile = profile->replacedby; | ||
| 266 | |||
| 267 | return profile; | ||
| 268 | } | ||
| 269 | |||
| 270 | /** | ||
| 271 | * aa_get_profile - increment refcount on profile @p | ||
| 272 | * @p: profile (MAYBE NULL) | ||
| 273 | * | ||
| 274 | * Returns: pointer to @p if @p is NULL will return NULL | ||
| 275 | * Requires: @p must be held with valid refcount when called | ||
| 276 | */ | ||
| 277 | static inline struct aa_profile *aa_get_profile(struct aa_profile *p) | ||
| 278 | { | ||
| 279 | if (p) | ||
| 280 | kref_get(&(p->base.count)); | ||
| 281 | |||
| 282 | return p; | ||
| 283 | } | ||
| 284 | |||
| 285 | /** | ||
| 286 | * aa_put_profile - decrement refcount on profile @p | ||
| 287 | * @p: profile (MAYBE NULL) | ||
| 288 | */ | ||
| 289 | static inline void aa_put_profile(struct aa_profile *p) | ||
| 290 | { | ||
| 291 | if (p) | ||
| 292 | kref_put(&p->base.count, aa_free_profile_kref); | ||
| 293 | } | ||
| 294 | |||
| 295 | static inline int AUDIT_MODE(struct aa_profile *profile) | ||
| 296 | { | ||
| 297 | if (aa_g_audit != AUDIT_NORMAL) | ||
| 298 | return aa_g_audit; | ||
| 299 | |||
| 300 | return profile->audit; | ||
| 301 | } | ||
| 302 | |||
| 303 | bool aa_may_manage_policy(int op); | ||
| 304 | |||
| 305 | #endif /* __AA_POLICY_H */ | ||
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h new file mode 100644 index 000000000000..a2dcccac45aa --- /dev/null +++ b/security/apparmor/include/policy_unpack.h | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor policy loading interface function definitions. | ||
| 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 | |||
| 15 | #ifndef __POLICY_INTERFACE_H | ||
| 16 | #define __POLICY_INTERFACE_H | ||
| 17 | |||
| 18 | struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns); | ||
| 19 | |||
| 20 | #endif /* __POLICY_INTERFACE_H */ | ||
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h new file mode 100644 index 000000000000..544aa6b766a4 --- /dev/null +++ b/security/apparmor/include/procattr.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor /proc/<pid>/attr/ interface function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_PROCATTR_H | ||
| 16 | #define __AA_PROCATTR_H | ||
| 17 | |||
| 18 | #define AA_DO_TEST 1 | ||
| 19 | #define AA_ONEXEC 1 | ||
| 20 | |||
| 21 | int aa_getprocattr(struct aa_profile *profile, char **string); | ||
| 22 | int aa_setprocattr_changehat(char *args, size_t size, int test); | ||
| 23 | int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); | ||
| 24 | int aa_setprocattr_permipc(char *fqname); | ||
| 25 | |||
| 26 | #endif /* __AA_PROCATTR_H */ | ||
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h new file mode 100644 index 000000000000..3c88be946494 --- /dev/null +++ b/security/apparmor/include/resource.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor resource limits function definitions. | ||
| 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 | |||
| 15 | #ifndef __AA_RESOURCE_H | ||
| 16 | #define __AA_RESOURCE_H | ||
| 17 | |||
| 18 | #include <linux/resource.h> | ||
| 19 | #include <linux/sched.h> | ||
| 20 | |||
| 21 | struct aa_profile; | ||
| 22 | |||
| 23 | /* struct aa_rlimit - rlimit settings for the profile | ||
| 24 | * @mask: which hard limits to set | ||
| 25 | * @limits: rlimit values that override task limits | ||
| 26 | * | ||
| 27 | * AppArmor rlimits are used to set confined task rlimits. Only the | ||
| 28 | * limits specified in @mask will be controlled by apparmor. | ||
| 29 | */ | ||
| 30 | struct aa_rlimit { | ||
| 31 | unsigned int mask; | ||
| 32 | struct rlimit limits[RLIM_NLIMITS]; | ||
| 33 | }; | ||
| 34 | |||
| 35 | int aa_map_resource(int resource); | ||
| 36 | int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, | ||
| 37 | struct rlimit *new_rlim); | ||
| 38 | |||
| 39 | void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); | ||
| 40 | |||
| 41 | static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) | ||
| 42 | { | ||
| 43 | /* NOP */ | ||
| 44 | } | ||
| 45 | |||
| 46 | #endif /* __AA_RESOURCE_H */ | ||
diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h new file mode 100644 index 000000000000..020db35c3010 --- /dev/null +++ b/security/apparmor/include/sid.h | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor security identifier (sid) definitions | ||
| 5 | * | ||
| 6 | * Copyright 2009-2010 Canonical Ltd. | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or | ||
| 9 | * modify it under the terms of the GNU General Public License as | ||
| 10 | * published by the Free Software Foundation, version 2 of the | ||
| 11 | * License. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #ifndef __AA_SID_H | ||
| 15 | #define __AA_SID_H | ||
| 16 | |||
| 17 | #include <linux/types.h> | ||
| 18 | |||
| 19 | struct aa_profile; | ||
| 20 | |||
| 21 | u32 aa_alloc_sid(void); | ||
| 22 | void aa_free_sid(u32 sid); | ||
| 23 | |||
| 24 | #endif /* __AA_SID_H */ | ||
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c new file mode 100644 index 000000000000..649fad88869b --- /dev/null +++ b/security/apparmor/ipc.c | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor ipc mediation | ||
| 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 | |||
| 15 | #include <linux/gfp.h> | ||
| 16 | #include <linux/ptrace.h> | ||
| 17 | |||
| 18 | #include "include/audit.h" | ||
| 19 | #include "include/capability.h" | ||
| 20 | #include "include/context.h" | ||
| 21 | #include "include/policy.h" | ||
| 22 | |||
| 23 | /* call back to audit ptrace fields */ | ||
| 24 | static void audit_cb(struct audit_buffer *ab, void *va) | ||
| 25 | { | ||
| 26 | struct common_audit_data *sa = va; | ||
| 27 | audit_log_format(ab, " target="); | ||
| 28 | audit_log_untrustedstring(ab, sa->aad.target); | ||
| 29 | } | ||
| 30 | |||
| 31 | /** | ||
| 32 | * aa_audit_ptrace - do auditing for ptrace | ||
| 33 | * @profile: profile being enforced (NOT NULL) | ||
| 34 | * @target: profile being traced (NOT NULL) | ||
| 35 | * @error: error condition | ||
| 36 | * | ||
| 37 | * Returns: %0 or error code | ||
| 38 | */ | ||
| 39 | static int aa_audit_ptrace(struct aa_profile *profile, | ||
| 40 | struct aa_profile *target, int error) | ||
| 41 | { | ||
| 42 | struct common_audit_data sa; | ||
| 43 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 44 | sa.aad.op = OP_PTRACE; | ||
| 45 | sa.aad.target = target; | ||
| 46 | sa.aad.error = error; | ||
| 47 | |||
| 48 | return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, | ||
| 49 | audit_cb); | ||
| 50 | } | ||
| 51 | |||
| 52 | /** | ||
| 53 | * aa_may_ptrace - test if tracer task can trace the tracee | ||
| 54 | * @tracer_task: task who will do the tracing (NOT NULL) | ||
| 55 | * @tracer: profile of the task doing the tracing (NOT NULL) | ||
| 56 | * @tracee: task to be traced | ||
| 57 | * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH | ||
| 58 | * | ||
| 59 | * Returns: %0 else error code if permission denied or error | ||
| 60 | */ | ||
| 61 | int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, | ||
| 62 | struct aa_profile *tracee, unsigned int mode) | ||
| 63 | { | ||
| 64 | /* TODO: currently only based on capability, not extended ptrace | ||
| 65 | * rules, | ||
| 66 | * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH | ||
| 67 | */ | ||
| 68 | |||
| 69 | if (unconfined(tracer) || tracer == tracee) | ||
| 70 | return 0; | ||
| 71 | /* log this capability request */ | ||
| 72 | return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1); | ||
| 73 | } | ||
| 74 | |||
| 75 | /** | ||
| 76 | * aa_ptrace - do ptrace permission check and auditing | ||
| 77 | * @tracer: task doing the tracing (NOT NULL) | ||
| 78 | * @tracee: task being traced (NOT NULL) | ||
| 79 | * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH | ||
| 80 | * | ||
| 81 | * Returns: %0 else error code if permission denied or error | ||
| 82 | */ | ||
| 83 | int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, | ||
| 84 | unsigned int mode) | ||
| 85 | { | ||
| 86 | /* | ||
| 87 | * tracer can ptrace tracee when | ||
| 88 | * - tracer is unconfined || | ||
| 89 | * - tracer is in complain mode | ||
| 90 | * - tracer has rules allowing it to trace tracee currently this is: | ||
| 91 | * - confined by the same profile || | ||
| 92 | * - tracer profile has CAP_SYS_PTRACE | ||
| 93 | */ | ||
| 94 | |||
| 95 | struct aa_profile *tracer_p; | ||
| 96 | /* cred released below */ | ||
| 97 | const struct cred *cred = get_task_cred(tracer); | ||
| 98 | int error = 0; | ||
| 99 | tracer_p = aa_cred_profile(cred); | ||
| 100 | |||
| 101 | if (!unconfined(tracer_p)) { | ||
| 102 | /* lcred released below */ | ||
| 103 | const struct cred *lcred = get_task_cred(tracee); | ||
| 104 | struct aa_profile *tracee_p = aa_cred_profile(lcred); | ||
| 105 | |||
| 106 | error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode); | ||
| 107 | error = aa_audit_ptrace(tracer_p, tracee_p, error); | ||
| 108 | |||
| 109 | put_cred(lcred); | ||
| 110 | } | ||
| 111 | put_cred(cred); | ||
| 112 | |||
| 113 | return error; | ||
| 114 | } | ||
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c new file mode 100644 index 000000000000..6e85cdb4303f --- /dev/null +++ b/security/apparmor/lib.c | |||
| @@ -0,0 +1,133 @@ | |||
| 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 | |||
| 15 | #include <linux/slab.h> | ||
| 16 | #include <linux/string.h> | ||
| 17 | #include <linux/vmalloc.h> | ||
| 18 | |||
| 19 | #include "include/audit.h" | ||
| 20 | |||
| 21 | |||
| 22 | /** | ||
| 23 | * aa_split_fqname - split a fqname into a profile and namespace name | ||
| 24 | * @fqname: a full qualified name in namespace profile format (NOT NULL) | ||
| 25 | * @ns_name: pointer to portion of the string containing the ns name (NOT NULL) | ||
| 26 | * | ||
| 27 | * Returns: profile name or NULL if one is not specified | ||
| 28 | * | ||
| 29 | * Split a namespace name from a profile name (see policy.c for naming | ||
| 30 | * description). If a portion of the name is missing it returns NULL for | ||
| 31 | * that portion. | ||
| 32 | * | ||
| 33 | * NOTE: may modify the @fqname string. The pointers returned point | ||
| 34 | * into the @fqname string. | ||
| 35 | */ | ||
| 36 | char *aa_split_fqname(char *fqname, char **ns_name) | ||
| 37 | { | ||
| 38 | char *name = strim(fqname); | ||
| 39 | |||
| 40 | *ns_name = NULL; | ||
| 41 | if (name[0] == ':') { | ||
| 42 | char *split = strchr(&name[1], ':'); | ||
| 43 | if (split) { | ||
| 44 | /* overwrite ':' with \0 */ | ||
| 45 | *split = 0; | ||
| 46 | name = skip_spaces(split + 1); | ||
| 47 | } else | ||
| 48 | /* a ns name without a following profile is allowed */ | ||
| 49 | name = NULL; | ||
| 50 | *ns_name = &name[1]; | ||
| 51 | } | ||
| 52 | if (name && *name == 0) | ||
| 53 | name = NULL; | ||
| 54 | |||
| 55 | return name; | ||
| 56 | } | ||
| 57 | |||
| 58 | /** | ||
| 59 | * aa_info_message - log a none profile related status message | ||
| 60 | * @str: message to log | ||
| 61 | */ | ||
| 62 | void aa_info_message(const char *str) | ||
| 63 | { | ||
| 64 | if (audit_enabled) { | ||
| 65 | struct common_audit_data sa; | ||
| 66 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 67 | sa.aad.info = str; | ||
| 68 | aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); | ||
| 69 | } | ||
| 70 | printk(KERN_INFO "AppArmor: %s\n", str); | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * kvmalloc - do allocation preferring kmalloc but falling back to vmalloc | ||
| 75 | * @size: size of allocation | ||
| 76 | * | ||
| 77 | * Return: allocated buffer or NULL if failed | ||
| 78 | * | ||
| 79 | * It is possible that policy being loaded from the user is larger than | ||
| 80 | * what can be allocated by kmalloc, in those cases fall back to vmalloc. | ||
| 81 | */ | ||
| 82 | void *kvmalloc(size_t size) | ||
| 83 | { | ||
| 84 | void *buffer = NULL; | ||
| 85 | |||
| 86 | if (size == 0) | ||
| 87 | return NULL; | ||
| 88 | |||
| 89 | /* do not attempt kmalloc if we need more than 16 pages at once */ | ||
| 90 | if (size <= (16*PAGE_SIZE)) | ||
| 91 | buffer = kmalloc(size, GFP_NOIO | __GFP_NOWARN); | ||
| 92 | if (!buffer) { | ||
| 93 | /* see kvfree for why size must be at least work_struct size | ||
| 94 | * when allocated via vmalloc | ||
| 95 | */ | ||
| 96 | if (size < sizeof(struct work_struct)) | ||
| 97 | size = sizeof(struct work_struct); | ||
| 98 | buffer = vmalloc(size); | ||
| 99 | } | ||
| 100 | return buffer; | ||
| 101 | } | ||
| 102 | |||
| 103 | /** | ||
| 104 | * do_vfree - workqueue routine for freeing vmalloced memory | ||
| 105 | * @work: data to be freed | ||
| 106 | * | ||
| 107 | * The work_struct is overlaid to the data being freed, as at the point | ||
| 108 | * the work is scheduled the data is no longer valid, be its freeing | ||
| 109 | * needs to be delayed until safe. | ||
| 110 | */ | ||
| 111 | static void do_vfree(struct work_struct *work) | ||
| 112 | { | ||
| 113 | vfree(work); | ||
| 114 | } | ||
| 115 | |||
| 116 | /** | ||
| 117 | * kvfree - free an allocation do by kvmalloc | ||
| 118 | * @buffer: buffer to free (MAYBE_NULL) | ||
| 119 | * | ||
| 120 | * Free a buffer allocated by kvmalloc | ||
| 121 | */ | ||
| 122 | void kvfree(void *buffer) | ||
| 123 | { | ||
| 124 | if (is_vmalloc_addr(buffer)) { | ||
| 125 | /* Data is no longer valid so just use the allocated space | ||
| 126 | * as the work_struct | ||
| 127 | */ | ||
| 128 | struct work_struct *work = (struct work_struct *) buffer; | ||
| 129 | INIT_WORK(work, do_vfree); | ||
| 130 | schedule_work(work); | ||
| 131 | } else | ||
| 132 | kfree(buffer); | ||
| 133 | } | ||
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c new file mode 100644 index 000000000000..8db33a8b50c4 --- /dev/null +++ b/security/apparmor/lsm.c | |||
| @@ -0,0 +1,938 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor LSM hooks. | ||
| 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 | |||
| 15 | #include <linux/security.h> | ||
| 16 | #include <linux/moduleparam.h> | ||
| 17 | #include <linux/mm.h> | ||
| 18 | #include <linux/mman.h> | ||
| 19 | #include <linux/mount.h> | ||
| 20 | #include <linux/namei.h> | ||
| 21 | #include <linux/ptrace.h> | ||
| 22 | #include <linux/ctype.h> | ||
| 23 | #include <linux/sysctl.h> | ||
| 24 | #include <linux/audit.h> | ||
| 25 | #include <net/sock.h> | ||
| 26 | |||
| 27 | #include "include/apparmor.h" | ||
| 28 | #include "include/apparmorfs.h" | ||
| 29 | #include "include/audit.h" | ||
| 30 | #include "include/capability.h" | ||
| 31 | #include "include/context.h" | ||
| 32 | #include "include/file.h" | ||
| 33 | #include "include/ipc.h" | ||
| 34 | #include "include/path.h" | ||
| 35 | #include "include/policy.h" | ||
| 36 | #include "include/procattr.h" | ||
| 37 | |||
| 38 | /* Flag indicating whether initialization completed */ | ||
| 39 | int apparmor_initialized __initdata; | ||
| 40 | |||
| 41 | /* | ||
| 42 | * LSM hook functions | ||
| 43 | */ | ||
| 44 | |||
| 45 | /* | ||
| 46 | * free the associated aa_task_cxt and put its profiles | ||
| 47 | */ | ||
| 48 | static void apparmor_cred_free(struct cred *cred) | ||
| 49 | { | ||
| 50 | aa_free_task_context(cred->security); | ||
| 51 | cred->security = NULL; | ||
| 52 | } | ||
| 53 | |||
| 54 | /* | ||
| 55 | * allocate the apparmor part of blank credentials | ||
| 56 | */ | ||
| 57 | static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp) | ||
| 58 | { | ||
| 59 | /* freed by apparmor_cred_free */ | ||
| 60 | struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); | ||
| 61 | if (!cxt) | ||
| 62 | return -ENOMEM; | ||
| 63 | |||
| 64 | cred->security = cxt; | ||
| 65 | return 0; | ||
| 66 | } | ||
| 67 | |||
| 68 | /* | ||
| 69 | * prepare new aa_task_cxt for modification by prepare_cred block | ||
| 70 | */ | ||
| 71 | static int apparmor_cred_prepare(struct cred *new, const struct cred *old, | ||
| 72 | gfp_t gfp) | ||
| 73 | { | ||
| 74 | /* freed by apparmor_cred_free */ | ||
| 75 | struct aa_task_cxt *cxt = aa_alloc_task_context(gfp); | ||
| 76 | if (!cxt) | ||
| 77 | return -ENOMEM; | ||
| 78 | |||
| 79 | aa_dup_task_context(cxt, old->security); | ||
| 80 | new->security = cxt; | ||
| 81 | return 0; | ||
| 82 | } | ||
| 83 | |||
| 84 | /* | ||
| 85 | * transfer the apparmor data to a blank set of creds | ||
| 86 | */ | ||
| 87 | static void apparmor_cred_transfer(struct cred *new, const struct cred *old) | ||
| 88 | { | ||
| 89 | const struct aa_task_cxt *old_cxt = old->security; | ||
| 90 | struct aa_task_cxt *new_cxt = new->security; | ||
| 91 | |||
| 92 | aa_dup_task_context(new_cxt, old_cxt); | ||
| 93 | } | ||
| 94 | |||
| 95 | static int apparmor_ptrace_access_check(struct task_struct *child, | ||
| 96 | unsigned int mode) | ||
| 97 | { | ||
| 98 | int error = cap_ptrace_access_check(child, mode); | ||
| 99 | if (error) | ||
| 100 | return error; | ||
| 101 | |||
| 102 | return aa_ptrace(current, child, mode); | ||
| 103 | } | ||
| 104 | |||
| 105 | static int apparmor_ptrace_traceme(struct task_struct *parent) | ||
| 106 | { | ||
| 107 | int error = cap_ptrace_traceme(parent); | ||
| 108 | if (error) | ||
| 109 | return error; | ||
| 110 | |||
| 111 | return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); | ||
| 112 | } | ||
| 113 | |||
| 114 | /* Derived from security/commoncap.c:cap_capget */ | ||
| 115 | static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, | ||
| 116 | kernel_cap_t *inheritable, kernel_cap_t *permitted) | ||
| 117 | { | ||
| 118 | struct aa_profile *profile; | ||
| 119 | const struct cred *cred; | ||
| 120 | |||
| 121 | rcu_read_lock(); | ||
| 122 | cred = __task_cred(target); | ||
| 123 | profile = aa_cred_profile(cred); | ||
| 124 | |||
| 125 | *effective = cred->cap_effective; | ||
| 126 | *inheritable = cred->cap_inheritable; | ||
| 127 | *permitted = cred->cap_permitted; | ||
| 128 | |||
| 129 | if (!unconfined(profile)) { | ||
| 130 | *effective = cap_intersect(*effective, profile->caps.allow); | ||
| 131 | *permitted = cap_intersect(*permitted, profile->caps.allow); | ||
| 132 | } | ||
| 133 | rcu_read_unlock(); | ||
| 134 | |||
| 135 | return 0; | ||
| 136 | } | ||
| 137 | |||
| 138 | static int apparmor_capable(struct task_struct *task, const struct cred *cred, | ||
| 139 | int cap, int audit) | ||
| 140 | { | ||
| 141 | struct aa_profile *profile; | ||
| 142 | /* cap_capable returns 0 on success, else -EPERM */ | ||
| 143 | int error = cap_capable(task, cred, cap, audit); | ||
| 144 | if (!error) { | ||
| 145 | profile = aa_cred_profile(cred); | ||
| 146 | if (!unconfined(profile)) | ||
| 147 | error = aa_capable(task, profile, cap, audit); | ||
| 148 | } | ||
| 149 | return error; | ||
| 150 | } | ||
| 151 | |||
| 152 | /** | ||
| 153 | * common_perm - basic common permission check wrapper fn for paths | ||
| 154 | * @op: operation being checked | ||
| 155 | * @path: path to check permission of (NOT NULL) | ||
| 156 | * @mask: requested permissions mask | ||
| 157 | * @cond: conditional info for the permission request (NOT NULL) | ||
| 158 | * | ||
| 159 | * Returns: %0 else error code if error or permission denied | ||
| 160 | */ | ||
| 161 | static int common_perm(int op, struct path *path, u32 mask, | ||
| 162 | struct path_cond *cond) | ||
| 163 | { | ||
| 164 | struct aa_profile *profile; | ||
| 165 | int error = 0; | ||
| 166 | |||
| 167 | profile = __aa_current_profile(); | ||
| 168 | if (!unconfined(profile)) | ||
| 169 | error = aa_path_perm(op, profile, path, 0, mask, cond); | ||
| 170 | |||
| 171 | return error; | ||
| 172 | } | ||
| 173 | |||
| 174 | /** | ||
| 175 | * common_perm_dir_dentry - common permission wrapper when path is dir, dentry | ||
| 176 | * @op: operation being checked | ||
| 177 | * @dir: directory of the dentry (NOT NULL) | ||
| 178 | * @dentry: dentry to check (NOT NULL) | ||
| 179 | * @mask: requested permissions mask | ||
| 180 | * @cond: conditional info for the permission request (NOT NULL) | ||
| 181 | * | ||
| 182 | * Returns: %0 else error code if error or permission denied | ||
| 183 | */ | ||
| 184 | static int common_perm_dir_dentry(int op, struct path *dir, | ||
| 185 | struct dentry *dentry, u32 mask, | ||
| 186 | struct path_cond *cond) | ||
| 187 | { | ||
| 188 | struct path path = { dir->mnt, dentry }; | ||
| 189 | |||
| 190 | return common_perm(op, &path, mask, cond); | ||
| 191 | } | ||
| 192 | |||
| 193 | /** | ||
| 194 | * common_perm_mnt_dentry - common permission wrapper when mnt, dentry | ||
| 195 | * @op: operation being checked | ||
| 196 | * @mnt: mount point of dentry (NOT NULL) | ||
| 197 | * @dentry: dentry to check (NOT NULL) | ||
| 198 | * @mask: requested permissions mask | ||
| 199 | * | ||
| 200 | * Returns: %0 else error code if error or permission denied | ||
| 201 | */ | ||
| 202 | static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, | ||
| 203 | struct dentry *dentry, u32 mask) | ||
| 204 | { | ||
| 205 | struct path path = { mnt, dentry }; | ||
| 206 | struct path_cond cond = { dentry->d_inode->i_uid, | ||
| 207 | dentry->d_inode->i_mode | ||
| 208 | }; | ||
| 209 | |||
| 210 | return common_perm(op, &path, mask, &cond); | ||
| 211 | } | ||
| 212 | |||
| 213 | /** | ||
| 214 | * common_perm_rm - common permission wrapper for operations doing rm | ||
| 215 | * @op: operation being checked | ||
| 216 | * @dir: directory that the dentry is in (NOT NULL) | ||
| 217 | * @dentry: dentry being rm'd (NOT NULL) | ||
| 218 | * @mask: requested permission mask | ||
| 219 | * | ||
| 220 | * Returns: %0 else error code if error or permission denied | ||
| 221 | */ | ||
| 222 | static int common_perm_rm(int op, struct path *dir, | ||
| 223 | struct dentry *dentry, u32 mask) | ||
| 224 | { | ||
| 225 | struct inode *inode = dentry->d_inode; | ||
| 226 | struct path_cond cond = { }; | ||
| 227 | |||
| 228 | if (!inode || !dir->mnt || !mediated_filesystem(inode)) | ||
| 229 | return 0; | ||
| 230 | |||
| 231 | cond.uid = inode->i_uid; | ||
| 232 | cond.mode = inode->i_mode; | ||
| 233 | |||
| 234 | return common_perm_dir_dentry(op, dir, dentry, mask, &cond); | ||
| 235 | } | ||
| 236 | |||
| 237 | /** | ||
| 238 | * common_perm_create - common permission wrapper for operations doing create | ||
| 239 | * @op: operation being checked | ||
| 240 | * @dir: directory that dentry will be created in (NOT NULL) | ||
| 241 | * @dentry: dentry to create (NOT NULL) | ||
| 242 | * @mask: request permission mask | ||
| 243 | * @mode: created file mode | ||
| 244 | * | ||
| 245 | * Returns: %0 else error code if error or permission denied | ||
| 246 | */ | ||
| 247 | static int common_perm_create(int op, struct path *dir, struct dentry *dentry, | ||
| 248 | u32 mask, umode_t mode) | ||
| 249 | { | ||
| 250 | struct path_cond cond = { current_fsuid(), mode }; | ||
| 251 | |||
| 252 | if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode)) | ||
| 253 | return 0; | ||
| 254 | |||
| 255 | return common_perm_dir_dentry(op, dir, dentry, mask, &cond); | ||
| 256 | } | ||
| 257 | |||
| 258 | static int apparmor_path_unlink(struct path *dir, struct dentry *dentry) | ||
| 259 | { | ||
| 260 | return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE); | ||
| 261 | } | ||
| 262 | |||
| 263 | static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, | ||
| 264 | int mode) | ||
| 265 | { | ||
| 266 | return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE, | ||
| 267 | S_IFDIR); | ||
| 268 | } | ||
| 269 | |||
| 270 | static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry) | ||
| 271 | { | ||
| 272 | return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE); | ||
| 273 | } | ||
| 274 | |||
| 275 | static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, | ||
| 276 | int mode, unsigned int dev) | ||
| 277 | { | ||
| 278 | return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode); | ||
| 279 | } | ||
| 280 | |||
| 281 | static int apparmor_path_truncate(struct path *path) | ||
| 282 | { | ||
| 283 | struct path_cond cond = { path->dentry->d_inode->i_uid, | ||
| 284 | path->dentry->d_inode->i_mode | ||
| 285 | }; | ||
| 286 | |||
| 287 | if (!path->mnt || !mediated_filesystem(path->dentry->d_inode)) | ||
| 288 | return 0; | ||
| 289 | |||
| 290 | return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE, | ||
| 291 | &cond); | ||
| 292 | } | ||
| 293 | |||
| 294 | static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, | ||
| 295 | const char *old_name) | ||
| 296 | { | ||
| 297 | return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE, | ||
| 298 | S_IFLNK); | ||
| 299 | } | ||
| 300 | |||
| 301 | static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, | ||
| 302 | struct dentry *new_dentry) | ||
| 303 | { | ||
| 304 | struct aa_profile *profile; | ||
| 305 | int error = 0; | ||
| 306 | |||
| 307 | if (!mediated_filesystem(old_dentry->d_inode)) | ||
| 308 | return 0; | ||
| 309 | |||
| 310 | profile = aa_current_profile(); | ||
| 311 | if (!unconfined(profile)) | ||
| 312 | error = aa_path_link(profile, old_dentry, new_dir, new_dentry); | ||
| 313 | return error; | ||
| 314 | } | ||
| 315 | |||
| 316 | static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, | ||
| 317 | struct path *new_dir, struct dentry *new_dentry) | ||
| 318 | { | ||
| 319 | struct aa_profile *profile; | ||
| 320 | int error = 0; | ||
| 321 | |||
| 322 | if (!mediated_filesystem(old_dentry->d_inode)) | ||
| 323 | return 0; | ||
| 324 | |||
| 325 | profile = aa_current_profile(); | ||
| 326 | if (!unconfined(profile)) { | ||
| 327 | struct path old_path = { old_dir->mnt, old_dentry }; | ||
| 328 | struct path new_path = { new_dir->mnt, new_dentry }; | ||
| 329 | struct path_cond cond = { old_dentry->d_inode->i_uid, | ||
| 330 | old_dentry->d_inode->i_mode | ||
| 331 | }; | ||
| 332 | |||
| 333 | error = aa_path_perm(OP_RENAME_SRC, profile, &old_path, 0, | ||
| 334 | MAY_READ | AA_MAY_META_READ | MAY_WRITE | | ||
| 335 | AA_MAY_META_WRITE | AA_MAY_DELETE, | ||
| 336 | &cond); | ||
| 337 | if (!error) | ||
| 338 | error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, | ||
| 339 | 0, MAY_WRITE | AA_MAY_META_WRITE | | ||
| 340 | AA_MAY_CREATE, &cond); | ||
| 341 | |||
| 342 | } | ||
| 343 | return error; | ||
| 344 | } | ||
| 345 | |||
| 346 | static int apparmor_path_chmod(struct dentry *dentry, struct vfsmount *mnt, | ||
| 347 | mode_t mode) | ||
| 348 | { | ||
| 349 | if (!mediated_filesystem(dentry->d_inode)) | ||
| 350 | return 0; | ||
| 351 | |||
| 352 | return common_perm_mnt_dentry(OP_CHMOD, mnt, dentry, AA_MAY_CHMOD); | ||
| 353 | } | ||
| 354 | |||
| 355 | static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid) | ||
| 356 | { | ||
| 357 | struct path_cond cond = { path->dentry->d_inode->i_uid, | ||
| 358 | path->dentry->d_inode->i_mode | ||
| 359 | }; | ||
| 360 | |||
| 361 | if (!mediated_filesystem(path->dentry->d_inode)) | ||
| 362 | return 0; | ||
| 363 | |||
| 364 | return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond); | ||
| 365 | } | ||
| 366 | |||
| 367 | static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) | ||
| 368 | { | ||
| 369 | if (!mediated_filesystem(dentry->d_inode)) | ||
| 370 | return 0; | ||
| 371 | |||
| 372 | return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry, | ||
| 373 | AA_MAY_META_READ); | ||
| 374 | } | ||
| 375 | |||
| 376 | static int apparmor_dentry_open(struct file *file, const struct cred *cred) | ||
| 377 | { | ||
| 378 | struct aa_file_cxt *fcxt = file->f_security; | ||
| 379 | struct aa_profile *profile; | ||
| 380 | int error = 0; | ||
| 381 | |||
| 382 | if (!mediated_filesystem(file->f_path.dentry->d_inode)) | ||
| 383 | return 0; | ||
| 384 | |||
| 385 | /* If in exec, permission is handled by bprm hooks. | ||
| 386 | * Cache permissions granted by the previous exec check, with | ||
| 387 | * implicit read and executable mmap which are required to | ||
| 388 | * actually execute the image. | ||
| 389 | */ | ||
| 390 | if (current->in_execve) { | ||
| 391 | fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; | ||
| 392 | return 0; | ||
| 393 | } | ||
| 394 | |||
| 395 | profile = aa_cred_profile(cred); | ||
| 396 | if (!unconfined(profile)) { | ||
| 397 | struct inode *inode = file->f_path.dentry->d_inode; | ||
| 398 | struct path_cond cond = { inode->i_uid, inode->i_mode }; | ||
| 399 | |||
| 400 | error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0, | ||
| 401 | aa_map_file_to_perms(file), &cond); | ||
| 402 | /* todo cache full allowed permissions set and state */ | ||
| 403 | fcxt->allow = aa_map_file_to_perms(file); | ||
| 404 | } | ||
| 405 | |||
| 406 | return error; | ||
| 407 | } | ||
| 408 | |||
| 409 | static int apparmor_file_alloc_security(struct file *file) | ||
| 410 | { | ||
| 411 | /* freed by apparmor_file_free_security */ | ||
| 412 | file->f_security = aa_alloc_file_context(GFP_KERNEL); | ||
| 413 | if (!file->f_security) | ||
| 414 | return -ENOMEM; | ||
| 415 | return 0; | ||
| 416 | |||
| 417 | } | ||
| 418 | |||
| 419 | static void apparmor_file_free_security(struct file *file) | ||
| 420 | { | ||
| 421 | struct aa_file_cxt *cxt = file->f_security; | ||
| 422 | |||
| 423 | aa_free_file_context(cxt); | ||
| 424 | } | ||
| 425 | |||
| 426 | static int common_file_perm(int op, struct file *file, u32 mask) | ||
| 427 | { | ||
| 428 | struct aa_file_cxt *fcxt = file->f_security; | ||
| 429 | struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); | ||
| 430 | int error = 0; | ||
| 431 | |||
| 432 | BUG_ON(!fprofile); | ||
| 433 | |||
| 434 | if (!file->f_path.mnt || | ||
| 435 | !mediated_filesystem(file->f_path.dentry->d_inode)) | ||
| 436 | return 0; | ||
| 437 | |||
| 438 | profile = __aa_current_profile(); | ||
| 439 | |||
| 440 | /* revalidate access, if task is unconfined, or the cached cred | ||
| 441 | * doesn't match or if the request is for more permissions than | ||
| 442 | * was granted. | ||
| 443 | * | ||
| 444 | * Note: the test for !unconfined(fprofile) is to handle file | ||
| 445 | * delegation from unconfined tasks | ||
| 446 | */ | ||
| 447 | if (!unconfined(profile) && !unconfined(fprofile) && | ||
| 448 | ((fprofile != profile) || (mask & ~fcxt->allow))) | ||
| 449 | error = aa_file_perm(op, profile, file, mask); | ||
| 450 | |||
| 451 | return error; | ||
| 452 | } | ||
| 453 | |||
| 454 | static int apparmor_file_permission(struct file *file, int mask) | ||
| 455 | { | ||
| 456 | return common_file_perm(OP_FPERM, file, mask); | ||
| 457 | } | ||
| 458 | |||
| 459 | static int apparmor_file_lock(struct file *file, unsigned int cmd) | ||
| 460 | { | ||
| 461 | u32 mask = AA_MAY_LOCK; | ||
| 462 | |||
| 463 | if (cmd == F_WRLCK) | ||
| 464 | mask |= MAY_WRITE; | ||
| 465 | |||
| 466 | return common_file_perm(OP_FLOCK, file, mask); | ||
| 467 | } | ||
| 468 | |||
| 469 | static int common_mmap(int op, struct file *file, unsigned long prot, | ||
| 470 | unsigned long flags) | ||
| 471 | { | ||
| 472 | struct dentry *dentry; | ||
| 473 | int mask = 0; | ||
| 474 | |||
| 475 | if (!file || !file->f_security) | ||
| 476 | return 0; | ||
| 477 | |||
| 478 | if (prot & PROT_READ) | ||
| 479 | mask |= MAY_READ; | ||
| 480 | /* | ||
| 481 | * Private mappings don't require write perms since they don't | ||
| 482 | * write back to the files | ||
| 483 | */ | ||
| 484 | if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE)) | ||
| 485 | mask |= MAY_WRITE; | ||
| 486 | if (prot & PROT_EXEC) | ||
| 487 | mask |= AA_EXEC_MMAP; | ||
| 488 | |||
| 489 | dentry = file->f_path.dentry; | ||
| 490 | return common_file_perm(op, file, mask); | ||
| 491 | } | ||
| 492 | |||
| 493 | static int apparmor_file_mmap(struct file *file, unsigned long reqprot, | ||
| 494 | unsigned long prot, unsigned long flags, | ||
| 495 | unsigned long addr, unsigned long addr_only) | ||
| 496 | { | ||
| 497 | int rc = 0; | ||
| 498 | |||
| 499 | /* do DAC check */ | ||
| 500 | rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only); | ||
| 501 | if (rc || addr_only) | ||
| 502 | return rc; | ||
| 503 | |||
| 504 | return common_mmap(OP_FMMAP, file, prot, flags); | ||
| 505 | } | ||
| 506 | |||
| 507 | static int apparmor_file_mprotect(struct vm_area_struct *vma, | ||
| 508 | unsigned long reqprot, unsigned long prot) | ||
| 509 | { | ||
| 510 | return common_mmap(OP_FMPROT, vma->vm_file, prot, | ||
| 511 | !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); | ||
| 512 | } | ||
| 513 | |||
| 514 | static int apparmor_getprocattr(struct task_struct *task, char *name, | ||
| 515 | char **value) | ||
| 516 | { | ||
| 517 | int error = -ENOENT; | ||
| 518 | struct aa_profile *profile; | ||
| 519 | /* released below */ | ||
| 520 | const struct cred *cred = get_task_cred(task); | ||
| 521 | struct aa_task_cxt *cxt = cred->security; | ||
| 522 | profile = aa_cred_profile(cred); | ||
| 523 | |||
| 524 | if (strcmp(name, "current") == 0) | ||
| 525 | error = aa_getprocattr(aa_newest_version(cxt->profile), | ||
| 526 | value); | ||
| 527 | else if (strcmp(name, "prev") == 0 && cxt->previous) | ||
| 528 | error = aa_getprocattr(aa_newest_version(cxt->previous), | ||
| 529 | value); | ||
| 530 | else if (strcmp(name, "exec") == 0 && cxt->onexec) | ||
| 531 | error = aa_getprocattr(aa_newest_version(cxt->onexec), | ||
| 532 | value); | ||
| 533 | else | ||
| 534 | error = -EINVAL; | ||
| 535 | |||
| 536 | put_cred(cred); | ||
| 537 | |||
| 538 | return error; | ||
| 539 | } | ||
| 540 | |||
| 541 | static int apparmor_setprocattr(struct task_struct *task, char *name, | ||
| 542 | void *value, size_t size) | ||
| 543 | { | ||
| 544 | char *command, *args = value; | ||
| 545 | size_t arg_size; | ||
| 546 | int error; | ||
| 547 | |||
| 548 | if (size == 0) | ||
| 549 | return -EINVAL; | ||
| 550 | /* args points to a PAGE_SIZE buffer, AppArmor requires that | ||
| 551 | * the buffer must be null terminated or have size <= PAGE_SIZE -1 | ||
| 552 | * so that AppArmor can null terminate them | ||
| 553 | */ | ||
| 554 | if (args[size - 1] != '\0') { | ||
| 555 | if (size == PAGE_SIZE) | ||
| 556 | return -EINVAL; | ||
| 557 | args[size] = '\0'; | ||
| 558 | } | ||
| 559 | |||
| 560 | /* task can only write its own attributes */ | ||
| 561 | if (current != task) | ||
| 562 | return -EACCES; | ||
| 563 | |||
| 564 | args = value; | ||
| 565 | args = strim(args); | ||
| 566 | command = strsep(&args, " "); | ||
| 567 | if (!args) | ||
| 568 | return -EINVAL; | ||
| 569 | args = skip_spaces(args); | ||
| 570 | if (!*args) | ||
| 571 | return -EINVAL; | ||
| 572 | |||
| 573 | arg_size = size - (args - (char *) value); | ||
| 574 | if (strcmp(name, "current") == 0) { | ||
| 575 | if (strcmp(command, "changehat") == 0) { | ||
| 576 | error = aa_setprocattr_changehat(args, arg_size, | ||
| 577 | !AA_DO_TEST); | ||
| 578 | } else if (strcmp(command, "permhat") == 0) { | ||
| 579 | error = aa_setprocattr_changehat(args, arg_size, | ||
| 580 | AA_DO_TEST); | ||
| 581 | } else if (strcmp(command, "changeprofile") == 0) { | ||
| 582 | error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, | ||
| 583 | !AA_DO_TEST); | ||
| 584 | } else if (strcmp(command, "permprofile") == 0) { | ||
| 585 | error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, | ||
| 586 | AA_DO_TEST); | ||
| 587 | } else if (strcmp(command, "permipc") == 0) { | ||
| 588 | error = aa_setprocattr_permipc(args); | ||
| 589 | } else { | ||
| 590 | struct common_audit_data sa; | ||
| 591 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 592 | sa.aad.op = OP_SETPROCATTR; | ||
| 593 | sa.aad.info = name; | ||
| 594 | sa.aad.error = -EINVAL; | ||
| 595 | return aa_audit(AUDIT_APPARMOR_DENIED, NULL, GFP_KERNEL, | ||
| 596 | &sa, NULL); | ||
| 597 | } | ||
| 598 | } else if (strcmp(name, "exec") == 0) { | ||
| 599 | error = aa_setprocattr_changeprofile(args, AA_ONEXEC, | ||
| 600 | !AA_DO_TEST); | ||
| 601 | } else { | ||
| 602 | /* only support the "current" and "exec" process attributes */ | ||
| 603 | return -EINVAL; | ||
| 604 | } | ||
| 605 | if (!error) | ||
| 606 | error = size; | ||
| 607 | return error; | ||
| 608 | } | ||
| 609 | |||
| 610 | static int apparmor_task_setrlimit(unsigned int resource, | ||
| 611 | struct rlimit *new_rlim) | ||
| 612 | { | ||
| 613 | struct aa_profile *profile = aa_current_profile(); | ||
| 614 | int error = 0; | ||
| 615 | |||
| 616 | if (!unconfined(profile)) | ||
| 617 | error = aa_task_setrlimit(profile, resource, new_rlim); | ||
| 618 | |||
| 619 | return error; | ||
| 620 | } | ||
| 621 | |||
| 622 | static struct security_operations apparmor_ops = { | ||
| 623 | .name = "apparmor", | ||
| 624 | |||
| 625 | .ptrace_access_check = apparmor_ptrace_access_check, | ||
| 626 | .ptrace_traceme = apparmor_ptrace_traceme, | ||
| 627 | .capget = apparmor_capget, | ||
| 628 | .capable = apparmor_capable, | ||
| 629 | |||
| 630 | .path_link = apparmor_path_link, | ||
| 631 | .path_unlink = apparmor_path_unlink, | ||
| 632 | .path_symlink = apparmor_path_symlink, | ||
| 633 | .path_mkdir = apparmor_path_mkdir, | ||
| 634 | .path_rmdir = apparmor_path_rmdir, | ||
| 635 | .path_mknod = apparmor_path_mknod, | ||
| 636 | .path_rename = apparmor_path_rename, | ||
| 637 | .path_chmod = apparmor_path_chmod, | ||
| 638 | .path_chown = apparmor_path_chown, | ||
| 639 | .path_truncate = apparmor_path_truncate, | ||
| 640 | .dentry_open = apparmor_dentry_open, | ||
| 641 | .inode_getattr = apparmor_inode_getattr, | ||
| 642 | |||
| 643 | .file_permission = apparmor_file_permission, | ||
| 644 | .file_alloc_security = apparmor_file_alloc_security, | ||
| 645 | .file_free_security = apparmor_file_free_security, | ||
| 646 | .file_mmap = apparmor_file_mmap, | ||
| 647 | .file_mprotect = apparmor_file_mprotect, | ||
| 648 | .file_lock = apparmor_file_lock, | ||
| 649 | |||
| 650 | .getprocattr = apparmor_getprocattr, | ||
| 651 | .setprocattr = apparmor_setprocattr, | ||
| 652 | |||
| 653 | .cred_alloc_blank = apparmor_cred_alloc_blank, | ||
| 654 | .cred_free = apparmor_cred_free, | ||
| 655 | .cred_prepare = apparmor_cred_prepare, | ||
| 656 | .cred_transfer = apparmor_cred_transfer, | ||
| 657 | |||
| 658 | .bprm_set_creds = apparmor_bprm_set_creds, | ||
| 659 | .bprm_committing_creds = apparmor_bprm_committing_creds, | ||
| 660 | .bprm_committed_creds = apparmor_bprm_committed_creds, | ||
| 661 | .bprm_secureexec = apparmor_bprm_secureexec, | ||
| 662 | |||
| 663 | .task_setrlimit = apparmor_task_setrlimit, | ||
| 664 | }; | ||
| 665 | |||
| 666 | /* | ||
| 667 | * AppArmor sysfs module parameters | ||
| 668 | */ | ||
| 669 | |||
| 670 | static int param_set_aabool(const char *val, struct kernel_param *kp); | ||
| 671 | static int param_get_aabool(char *buffer, struct kernel_param *kp); | ||
| 672 | #define param_check_aabool(name, p) __param_check(name, p, int) | ||
| 673 | |||
| 674 | static int param_set_aauint(const char *val, struct kernel_param *kp); | ||
| 675 | static int param_get_aauint(char *buffer, struct kernel_param *kp); | ||
| 676 | #define param_check_aauint(name, p) __param_check(name, p, int) | ||
| 677 | |||
| 678 | static int param_set_aalockpolicy(const char *val, struct kernel_param *kp); | ||
| 679 | static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp); | ||
| 680 | #define param_check_aalockpolicy(name, p) __param_check(name, p, int) | ||
| 681 | |||
| 682 | static int param_set_audit(const char *val, struct kernel_param *kp); | ||
| 683 | static int param_get_audit(char *buffer, struct kernel_param *kp); | ||
| 684 | #define param_check_audit(name, p) __param_check(name, p, int) | ||
| 685 | |||
| 686 | static int param_set_mode(const char *val, struct kernel_param *kp); | ||
| 687 | static int param_get_mode(char *buffer, struct kernel_param *kp); | ||
| 688 | #define param_check_mode(name, p) __param_check(name, p, int) | ||
| 689 | |||
| 690 | /* Flag values, also controllable via /sys/module/apparmor/parameters | ||
| 691 | * We define special types as we want to do additional mediation. | ||
| 692 | */ | ||
| 693 | |||
| 694 | /* AppArmor global enforcement switch - complain, enforce, kill */ | ||
| 695 | enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; | ||
| 696 | module_param_call(mode, param_set_mode, param_get_mode, | ||
| 697 | &aa_g_profile_mode, S_IRUSR | S_IWUSR); | ||
| 698 | |||
| 699 | /* Debug mode */ | ||
| 700 | int aa_g_debug; | ||
| 701 | module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); | ||
| 702 | |||
| 703 | /* Audit mode */ | ||
| 704 | enum audit_mode aa_g_audit; | ||
| 705 | module_param_call(audit, param_set_audit, param_get_audit, | ||
| 706 | &aa_g_audit, S_IRUSR | S_IWUSR); | ||
| 707 | |||
| 708 | /* Determines if audit header is included in audited messages. This | ||
| 709 | * provides more context if the audit daemon is not running | ||
| 710 | */ | ||
| 711 | int aa_g_audit_header = 1; | ||
| 712 | module_param_named(audit_header, aa_g_audit_header, aabool, | ||
| 713 | S_IRUSR | S_IWUSR); | ||
| 714 | |||
| 715 | /* lock out loading/removal of policy | ||
| 716 | * TODO: add in at boot loading of policy, which is the only way to | ||
| 717 | * load policy, if lock_policy is set | ||
| 718 | */ | ||
| 719 | int aa_g_lock_policy; | ||
| 720 | module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy, | ||
| 721 | S_IRUSR | S_IWUSR); | ||
| 722 | |||
| 723 | /* Syscall logging mode */ | ||
| 724 | int aa_g_logsyscall; | ||
| 725 | module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR); | ||
| 726 | |||
| 727 | /* Maximum pathname length before accesses will start getting rejected */ | ||
| 728 | unsigned int aa_g_path_max = 2 * PATH_MAX; | ||
| 729 | module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR); | ||
| 730 | |||
| 731 | /* Determines how paranoid loading of policy is and how much verification | ||
| 732 | * on the loaded policy is done. | ||
| 733 | */ | ||
| 734 | int aa_g_paranoid_load = 1; | ||
| 735 | module_param_named(paranoid_load, aa_g_paranoid_load, aabool, | ||
| 736 | S_IRUSR | S_IWUSR); | ||
| 737 | |||
| 738 | /* Boot time disable flag */ | ||
| 739 | static unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; | ||
| 740 | module_param_named(enabled, apparmor_enabled, aabool, S_IRUSR); | ||
| 741 | |||
| 742 | static int __init apparmor_enabled_setup(char *str) | ||
| 743 | { | ||
| 744 | unsigned long enabled; | ||
| 745 | int error = strict_strtoul(str, 0, &enabled); | ||
| 746 | if (!error) | ||
| 747 | apparmor_enabled = enabled ? 1 : 0; | ||
| 748 | return 1; | ||
| 749 | } | ||
| 750 | |||
| 751 | __setup("apparmor=", apparmor_enabled_setup); | ||
| 752 | |||
| 753 | /* set global flag turning off the ability to load policy */ | ||
| 754 | static int param_set_aalockpolicy(const char *val, struct kernel_param *kp) | ||
| 755 | { | ||
| 756 | if (!capable(CAP_MAC_ADMIN)) | ||
| 757 | return -EPERM; | ||
| 758 | if (aa_g_lock_policy) | ||
| 759 | return -EACCES; | ||
| 760 | return param_set_bool(val, kp); | ||
| 761 | } | ||
| 762 | |||
| 763 | static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp) | ||
| 764 | { | ||
| 765 | if (!capable(CAP_MAC_ADMIN)) | ||
| 766 | return -EPERM; | ||
| 767 | return param_get_bool(buffer, kp); | ||
| 768 | } | ||
| 769 | |||
| 770 | static int param_set_aabool(const char *val, struct kernel_param *kp) | ||
| 771 | { | ||
| 772 | if (!capable(CAP_MAC_ADMIN)) | ||
| 773 | return -EPERM; | ||
| 774 | return param_set_bool(val, kp); | ||
| 775 | } | ||
| 776 | |||
| 777 | static int param_get_aabool(char *buffer, struct kernel_param *kp) | ||
| 778 | { | ||
| 779 | if (!capable(CAP_MAC_ADMIN)) | ||
| 780 | return -EPERM; | ||
| 781 | return param_get_bool(buffer, kp); | ||
| 782 | } | ||
| 783 | |||
| 784 | static int param_set_aauint(const char *val, struct kernel_param *kp) | ||
| 785 | { | ||
| 786 | if (!capable(CAP_MAC_ADMIN)) | ||
| 787 | return -EPERM; | ||
| 788 | return param_set_uint(val, kp); | ||
| 789 | } | ||
| 790 | |||
| 791 | static int param_get_aauint(char *buffer, struct kernel_param *kp) | ||
| 792 | { | ||
| 793 | if (!capable(CAP_MAC_ADMIN)) | ||
| 794 | return -EPERM; | ||
| 795 | return param_get_uint(buffer, kp); | ||
| 796 | } | ||
| 797 | |||
| 798 | static int param_get_audit(char *buffer, struct kernel_param *kp) | ||
| 799 | { | ||
| 800 | if (!capable(CAP_MAC_ADMIN)) | ||
| 801 | return -EPERM; | ||
| 802 | |||
| 803 | if (!apparmor_enabled) | ||
| 804 | return -EINVAL; | ||
| 805 | |||
| 806 | return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); | ||
| 807 | } | ||
| 808 | |||
| 809 | static int param_set_audit(const char *val, struct kernel_param *kp) | ||
| 810 | { | ||
| 811 | int i; | ||
| 812 | if (!capable(CAP_MAC_ADMIN)) | ||
| 813 | return -EPERM; | ||
| 814 | |||
| 815 | if (!apparmor_enabled) | ||
| 816 | return -EINVAL; | ||
| 817 | |||
| 818 | if (!val) | ||
| 819 | return -EINVAL; | ||
| 820 | |||
| 821 | for (i = 0; i < AUDIT_MAX_INDEX; i++) { | ||
| 822 | if (strcmp(val, audit_mode_names[i]) == 0) { | ||
| 823 | aa_g_audit = i; | ||
| 824 | return 0; | ||
| 825 | } | ||
| 826 | } | ||
| 827 | |||
| 828 | return -EINVAL; | ||
| 829 | } | ||
| 830 | |||
| 831 | static int param_get_mode(char *buffer, struct kernel_param *kp) | ||
| 832 | { | ||
| 833 | if (!capable(CAP_MAC_ADMIN)) | ||
| 834 | return -EPERM; | ||
| 835 | |||
| 836 | if (!apparmor_enabled) | ||
| 837 | return -EINVAL; | ||
| 838 | |||
| 839 | return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]); | ||
| 840 | } | ||
| 841 | |||
| 842 | static int param_set_mode(const char *val, struct kernel_param *kp) | ||
| 843 | { | ||
| 844 | int i; | ||
| 845 | if (!capable(CAP_MAC_ADMIN)) | ||
| 846 | return -EPERM; | ||
| 847 | |||
| 848 | if (!apparmor_enabled) | ||
| 849 | return -EINVAL; | ||
| 850 | |||
| 851 | if (!val) | ||
| 852 | return -EINVAL; | ||
| 853 | |||
| 854 | for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) { | ||
| 855 | if (strcmp(val, profile_mode_names[i]) == 0) { | ||
| 856 | aa_g_profile_mode = i; | ||
| 857 | return 0; | ||
| 858 | } | ||
| 859 | } | ||
| 860 | |||
| 861 | return -EINVAL; | ||
| 862 | } | ||
| 863 | |||
| 864 | /* | ||
| 865 | * AppArmor init functions | ||
| 866 | */ | ||
| 867 | |||
| 868 | /** | ||
| 869 | * set_init_cxt - set a task context and profile on the first task. | ||
| 870 | * | ||
| 871 | * TODO: allow setting an alternate profile than unconfined | ||
| 872 | */ | ||
| 873 | static int __init set_init_cxt(void) | ||
| 874 | { | ||
| 875 | struct cred *cred = (struct cred *)current->real_cred; | ||
| 876 | struct aa_task_cxt *cxt; | ||
| 877 | |||
| 878 | cxt = aa_alloc_task_context(GFP_KERNEL); | ||
| 879 | if (!cxt) | ||
| 880 | return -ENOMEM; | ||
| 881 | |||
| 882 | cxt->profile = aa_get_profile(root_ns->unconfined); | ||
| 883 | cred->security = cxt; | ||
| 884 | |||
| 885 | return 0; | ||
| 886 | } | ||
| 887 | |||
| 888 | static int __init apparmor_init(void) | ||
| 889 | { | ||
| 890 | int error; | ||
| 891 | |||
| 892 | if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) { | ||
| 893 | aa_info_message("AppArmor disabled by boot time parameter"); | ||
| 894 | apparmor_enabled = 0; | ||
| 895 | return 0; | ||
| 896 | } | ||
| 897 | |||
| 898 | error = aa_alloc_root_ns(); | ||
| 899 | if (error) { | ||
| 900 | AA_ERROR("Unable to allocate default profile namespace\n"); | ||
| 901 | goto alloc_out; | ||
| 902 | } | ||
| 903 | |||
| 904 | error = set_init_cxt(); | ||
| 905 | if (error) { | ||
| 906 | AA_ERROR("Failed to set context on init task\n"); | ||
| 907 | goto register_security_out; | ||
| 908 | } | ||
| 909 | |||
| 910 | error = register_security(&apparmor_ops); | ||
| 911 | if (error) { | ||
| 912 | AA_ERROR("Unable to register AppArmor\n"); | ||
| 913 | goto register_security_out; | ||
| 914 | } | ||
| 915 | |||
| 916 | /* Report that AppArmor successfully initialized */ | ||
| 917 | apparmor_initialized = 1; | ||
| 918 | if (aa_g_profile_mode == APPARMOR_COMPLAIN) | ||
| 919 | aa_info_message("AppArmor initialized: complain mode enabled"); | ||
| 920 | else if (aa_g_profile_mode == APPARMOR_KILL) | ||
| 921 | aa_info_message("AppArmor initialized: kill mode enabled"); | ||
| 922 | else | ||
| 923 | aa_info_message("AppArmor initialized"); | ||
| 924 | |||
| 925 | return error; | ||
| 926 | |||
| 927 | register_security_out: | ||
| 928 | aa_free_root_ns(); | ||
| 929 | |||
| 930 | alloc_out: | ||
| 931 | aa_destroy_aafs(); | ||
| 932 | |||
| 933 | apparmor_enabled = 0; | ||
| 934 | return error; | ||
| 935 | |||
| 936 | } | ||
| 937 | |||
| 938 | security_initcall(apparmor_init); | ||
diff --git a/security/apparmor/match.c b/security/apparmor/match.c new file mode 100644 index 000000000000..5cb4dc1f6992 --- /dev/null +++ b/security/apparmor/match.c | |||
| @@ -0,0 +1,353 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor dfa based regular expression matching engine | ||
| 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 | |||
| 15 | #include <linux/errno.h> | ||
| 16 | #include <linux/kernel.h> | ||
| 17 | #include <linux/mm.h> | ||
| 18 | #include <linux/slab.h> | ||
| 19 | #include <linux/vmalloc.h> | ||
| 20 | #include <linux/err.h> | ||
| 21 | #include <linux/kref.h> | ||
| 22 | |||
| 23 | #include "include/apparmor.h" | ||
| 24 | #include "include/match.h" | ||
| 25 | |||
| 26 | /** | ||
| 27 | * unpack_table - unpack a dfa table (one of accept, default, base, next check) | ||
| 28 | * @blob: data to unpack (NOT NULL) | ||
| 29 | * @bsize: size of blob | ||
| 30 | * | ||
| 31 | * Returns: pointer to table else NULL on failure | ||
| 32 | * | ||
| 33 | * NOTE: must be freed by kvfree (not kmalloc) | ||
| 34 | */ | ||
| 35 | static struct table_header *unpack_table(char *blob, size_t bsize) | ||
| 36 | { | ||
| 37 | struct table_header *table = NULL; | ||
| 38 | struct table_header th; | ||
| 39 | size_t tsize; | ||
| 40 | |||
| 41 | if (bsize < sizeof(struct table_header)) | ||
| 42 | goto out; | ||
| 43 | |||
| 44 | /* loaded td_id's start at 1, subtract 1 now to avoid doing | ||
| 45 | * it every time we use td_id as an index | ||
| 46 | */ | ||
| 47 | th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1; | ||
| 48 | th.td_flags = be16_to_cpu(*(u16 *) (blob + 2)); | ||
| 49 | th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8)); | ||
| 50 | blob += sizeof(struct table_header); | ||
| 51 | |||
| 52 | if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || | ||
| 53 | th.td_flags == YYTD_DATA8)) | ||
| 54 | goto out; | ||
| 55 | |||
| 56 | tsize = table_size(th.td_lolen, th.td_flags); | ||
| 57 | if (bsize < tsize) | ||
| 58 | goto out; | ||
| 59 | |||
| 60 | table = kvmalloc(tsize); | ||
| 61 | if (table) { | ||
| 62 | *table = th; | ||
| 63 | if (th.td_flags == YYTD_DATA8) | ||
| 64 | UNPACK_ARRAY(table->td_data, blob, th.td_lolen, | ||
| 65 | u8, byte_to_byte); | ||
| 66 | else if (th.td_flags == YYTD_DATA16) | ||
| 67 | UNPACK_ARRAY(table->td_data, blob, th.td_lolen, | ||
| 68 | u16, be16_to_cpu); | ||
| 69 | else if (th.td_flags == YYTD_DATA32) | ||
| 70 | UNPACK_ARRAY(table->td_data, blob, th.td_lolen, | ||
| 71 | u32, be32_to_cpu); | ||
| 72 | else | ||
| 73 | goto fail; | ||
| 74 | } | ||
| 75 | |||
| 76 | out: | ||
| 77 | /* if table was vmalloced make sure the page tables are synced | ||
| 78 | * before it is used, as it goes live to all cpus. | ||
| 79 | */ | ||
| 80 | if (is_vmalloc_addr(table)) | ||
| 81 | vm_unmap_aliases(); | ||
| 82 | return table; | ||
| 83 | fail: | ||
| 84 | kvfree(table); | ||
| 85 | return NULL; | ||
| 86 | } | ||
| 87 | |||
| 88 | /** | ||
| 89 | * verify_dfa - verify that transitions and states in the tables are in bounds. | ||
| 90 | * @dfa: dfa to test (NOT NULL) | ||
| 91 | * @flags: flags controlling what type of accept table are acceptable | ||
| 92 | * | ||
| 93 | * Assumes dfa has gone through the first pass verification done by unpacking | ||
| 94 | * NOTE: this does not valid accept table values | ||
| 95 | * | ||
| 96 | * Returns: %0 else error code on failure to verify | ||
| 97 | */ | ||
| 98 | static int verify_dfa(struct aa_dfa *dfa, int flags) | ||
| 99 | { | ||
| 100 | size_t i, state_count, trans_count; | ||
| 101 | int error = -EPROTO; | ||
| 102 | |||
| 103 | /* check that required tables exist */ | ||
| 104 | if (!(dfa->tables[YYTD_ID_DEF] && | ||
| 105 | dfa->tables[YYTD_ID_BASE] && | ||
| 106 | dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK])) | ||
| 107 | goto out; | ||
| 108 | |||
| 109 | /* accept.size == default.size == base.size */ | ||
| 110 | state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; | ||
| 111 | if (ACCEPT1_FLAGS(flags)) { | ||
| 112 | if (!dfa->tables[YYTD_ID_ACCEPT]) | ||
| 113 | goto out; | ||
| 114 | if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen) | ||
| 115 | goto out; | ||
| 116 | } | ||
| 117 | if (ACCEPT2_FLAGS(flags)) { | ||
| 118 | if (!dfa->tables[YYTD_ID_ACCEPT2]) | ||
| 119 | goto out; | ||
| 120 | if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen) | ||
| 121 | goto out; | ||
| 122 | } | ||
| 123 | if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen) | ||
| 124 | goto out; | ||
| 125 | |||
| 126 | /* next.size == chk.size */ | ||
| 127 | trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; | ||
| 128 | if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen) | ||
| 129 | goto out; | ||
| 130 | |||
| 131 | /* if equivalence classes then its table size must be 256 */ | ||
| 132 | if (dfa->tables[YYTD_ID_EC] && | ||
| 133 | dfa->tables[YYTD_ID_EC]->td_lolen != 256) | ||
| 134 | goto out; | ||
| 135 | |||
| 136 | if (flags & DFA_FLAG_VERIFY_STATES) { | ||
| 137 | for (i = 0; i < state_count; i++) { | ||
| 138 | if (DEFAULT_TABLE(dfa)[i] >= state_count) | ||
| 139 | goto out; | ||
| 140 | /* TODO: do check that DEF state recursion terminates */ | ||
| 141 | if (BASE_TABLE(dfa)[i] + 255 >= trans_count) { | ||
| 142 | printk(KERN_ERR "AppArmor DFA next/check upper " | ||
| 143 | "bounds error\n"); | ||
| 144 | goto out; | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | for (i = 0; i < trans_count; i++) { | ||
| 149 | if (NEXT_TABLE(dfa)[i] >= state_count) | ||
| 150 | goto out; | ||
| 151 | if (CHECK_TABLE(dfa)[i] >= state_count) | ||
| 152 | goto out; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | error = 0; | ||
| 157 | out: | ||
| 158 | return error; | ||
| 159 | } | ||
| 160 | |||
| 161 | /** | ||
| 162 | * dfa_free - free a dfa allocated by aa_dfa_unpack | ||
| 163 | * @dfa: the dfa to free (MAYBE NULL) | ||
| 164 | * | ||
| 165 | * Requires: reference count to dfa == 0 | ||
| 166 | */ | ||
| 167 | static void dfa_free(struct aa_dfa *dfa) | ||
| 168 | { | ||
| 169 | if (dfa) { | ||
| 170 | int i; | ||
| 171 | |||
| 172 | for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) { | ||
| 173 | kvfree(dfa->tables[i]); | ||
| 174 | dfa->tables[i] = NULL; | ||
| 175 | } | ||
| 176 | kfree(dfa); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | /** | ||
| 181 | * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa) | ||
| 182 | * @kr: kref callback for freeing of a dfa (NOT NULL) | ||
| 183 | */ | ||
| 184 | void aa_dfa_free_kref(struct kref *kref) | ||
| 185 | { | ||
| 186 | struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count); | ||
| 187 | dfa_free(dfa); | ||
| 188 | } | ||
| 189 | |||
| 190 | /** | ||
| 191 | * aa_dfa_unpack - unpack the binary tables of a serialized dfa | ||
| 192 | * @blob: aligned serialized stream of data to unpack (NOT NULL) | ||
| 193 | * @size: size of data to unpack | ||
| 194 | * @flags: flags controlling what type of accept tables are acceptable | ||
| 195 | * | ||
| 196 | * Unpack a dfa that has been serialized. To find information on the dfa | ||
| 197 | * format look in Documentation/apparmor.txt | ||
| 198 | * Assumes the dfa @blob stream has been aligned on a 8 byte boundry | ||
| 199 | * | ||
| 200 | * Returns: an unpacked dfa ready for matching or ERR_PTR on failure | ||
| 201 | */ | ||
| 202 | struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) | ||
| 203 | { | ||
| 204 | int hsize; | ||
| 205 | int error = -ENOMEM; | ||
| 206 | char *data = blob; | ||
| 207 | struct table_header *table = NULL; | ||
| 208 | struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL); | ||
| 209 | if (!dfa) | ||
| 210 | goto fail; | ||
| 211 | |||
| 212 | kref_init(&dfa->count); | ||
| 213 | |||
| 214 | error = -EPROTO; | ||
| 215 | |||
| 216 | /* get dfa table set header */ | ||
| 217 | if (size < sizeof(struct table_set_header)) | ||
| 218 | goto fail; | ||
| 219 | |||
| 220 | if (ntohl(*(u32 *) data) != YYTH_MAGIC) | ||
| 221 | goto fail; | ||
| 222 | |||
| 223 | hsize = ntohl(*(u32 *) (data + 4)); | ||
| 224 | if (size < hsize) | ||
| 225 | goto fail; | ||
| 226 | |||
| 227 | dfa->flags = ntohs(*(u16 *) (data + 12)); | ||
| 228 | data += hsize; | ||
| 229 | size -= hsize; | ||
| 230 | |||
| 231 | while (size > 0) { | ||
| 232 | table = unpack_table(data, size); | ||
| 233 | if (!table) | ||
| 234 | goto fail; | ||
| 235 | |||
| 236 | switch (table->td_id) { | ||
| 237 | case YYTD_ID_ACCEPT: | ||
| 238 | if (!(table->td_flags & ACCEPT1_FLAGS(flags))) | ||
| 239 | goto fail; | ||
| 240 | break; | ||
| 241 | case YYTD_ID_ACCEPT2: | ||
| 242 | if (!(table->td_flags & ACCEPT2_FLAGS(flags))) | ||
| 243 | goto fail; | ||
| 244 | break; | ||
| 245 | case YYTD_ID_BASE: | ||
| 246 | if (table->td_flags != YYTD_DATA32) | ||
| 247 | goto fail; | ||
| 248 | break; | ||
| 249 | case YYTD_ID_DEF: | ||
| 250 | case YYTD_ID_NXT: | ||
| 251 | case YYTD_ID_CHK: | ||
| 252 | if (table->td_flags != YYTD_DATA16) | ||
| 253 | goto fail; | ||
| 254 | break; | ||
| 255 | case YYTD_ID_EC: | ||
| 256 | if (table->td_flags != YYTD_DATA8) | ||
| 257 | goto fail; | ||
| 258 | break; | ||
| 259 | default: | ||
| 260 | goto fail; | ||
| 261 | } | ||
| 262 | /* check for duplicate table entry */ | ||
| 263 | if (dfa->tables[table->td_id]) | ||
| 264 | goto fail; | ||
| 265 | dfa->tables[table->td_id] = table; | ||
| 266 | data += table_size(table->td_lolen, table->td_flags); | ||
| 267 | size -= table_size(table->td_lolen, table->td_flags); | ||
| 268 | table = NULL; | ||
| 269 | } | ||
| 270 | |||
| 271 | error = verify_dfa(dfa, flags); | ||
| 272 | if (error) | ||
| 273 | goto fail; | ||
| 274 | |||
| 275 | return dfa; | ||
| 276 | |||
| 277 | fail: | ||
| 278 | kvfree(table); | ||
| 279 | dfa_free(dfa); | ||
| 280 | return ERR_PTR(error); | ||
| 281 | } | ||
| 282 | |||
| 283 | /** | ||
| 284 | * aa_dfa_match_len - traverse @dfa to find state @str stops at | ||
| 285 | * @dfa: the dfa to match @str against (NOT NULL) | ||
| 286 | * @start: the state of the dfa to start matching in | ||
| 287 | * @str: the string of bytes to match against the dfa (NOT NULL) | ||
| 288 | * @len: length of the string of bytes to match | ||
| 289 | * | ||
| 290 | * aa_dfa_match_len will match @str against the dfa and return the state it | ||
| 291 | * finished matching in. The final state can be used to look up the accepting | ||
| 292 | * label, or as the start state of a continuing match. | ||
| 293 | * | ||
| 294 | * This function will happily match again the 0 byte and only finishes | ||
| 295 | * when @len input is consumed. | ||
| 296 | * | ||
| 297 | * Returns: final state reached after input is consumed | ||
| 298 | */ | ||
| 299 | unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, | ||
| 300 | const char *str, int len) | ||
| 301 | { | ||
| 302 | u16 *def = DEFAULT_TABLE(dfa); | ||
| 303 | u32 *base = BASE_TABLE(dfa); | ||
| 304 | u16 *next = NEXT_TABLE(dfa); | ||
| 305 | u16 *check = CHECK_TABLE(dfa); | ||
| 306 | unsigned int state = start, pos; | ||
| 307 | |||
| 308 | if (state == 0) | ||
| 309 | return 0; | ||
| 310 | |||
| 311 | /* current state is <state>, matching character *str */ | ||
| 312 | if (dfa->tables[YYTD_ID_EC]) { | ||
| 313 | /* Equivalence class table defined */ | ||
| 314 | u8 *equiv = EQUIV_TABLE(dfa); | ||
| 315 | /* default is direct to next state */ | ||
| 316 | for (; len; len--) { | ||
| 317 | pos = base[state] + equiv[(u8) *str++]; | ||
| 318 | if (check[pos] == state) | ||
| 319 | state = next[pos]; | ||
| 320 | else | ||
| 321 | state = def[state]; | ||
| 322 | } | ||
| 323 | } else { | ||
| 324 | /* default is direct to next state */ | ||
| 325 | for (; len; len--) { | ||
| 326 | pos = base[state] + (u8) *str++; | ||
| 327 | if (check[pos] == state) | ||
| 328 | state = next[pos]; | ||
| 329 | else | ||
| 330 | state = def[state]; | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | return state; | ||
| 335 | } | ||
| 336 | |||
| 337 | /** | ||
| 338 | * aa_dfa_next_state - traverse @dfa to find state @str stops at | ||
| 339 | * @dfa: the dfa to match @str against (NOT NULL) | ||
| 340 | * @start: the state of the dfa to start matching in | ||
| 341 | * @str: the null terminated string of bytes to match against the dfa (NOT NULL) | ||
| 342 | * | ||
| 343 | * aa_dfa_next_state will match @str against the dfa and return the state it | ||
| 344 | * finished matching in. The final state can be used to look up the accepting | ||
| 345 | * label, or as the start state of a continuing match. | ||
| 346 | * | ||
| 347 | * Returns: final state reached after input is consumed | ||
| 348 | */ | ||
| 349 | unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, | ||
| 350 | const char *str) | ||
| 351 | { | ||
| 352 | return aa_dfa_match_len(dfa, start, str, strlen(str)); | ||
| 353 | } | ||
diff --git a/security/apparmor/path.c b/security/apparmor/path.c new file mode 100644 index 000000000000..96bab9469d48 --- /dev/null +++ b/security/apparmor/path.c | |||
| @@ -0,0 +1,235 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor function for pathnames | ||
| 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 | |||
| 15 | #include <linux/magic.h> | ||
| 16 | #include <linux/mnt_namespace.h> | ||
| 17 | #include <linux/mount.h> | ||
| 18 | #include <linux/namei.h> | ||
| 19 | #include <linux/nsproxy.h> | ||
| 20 | #include <linux/path.h> | ||
| 21 | #include <linux/sched.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | #include <linux/fs_struct.h> | ||
| 24 | |||
| 25 | #include "include/apparmor.h" | ||
| 26 | #include "include/path.h" | ||
| 27 | #include "include/policy.h" | ||
| 28 | |||
| 29 | |||
| 30 | /* modified from dcache.c */ | ||
| 31 | static int prepend(char **buffer, int buflen, const char *str, int namelen) | ||
| 32 | { | ||
| 33 | buflen -= namelen; | ||
| 34 | if (buflen < 0) | ||
| 35 | return -ENAMETOOLONG; | ||
| 36 | *buffer -= namelen; | ||
| 37 | memcpy(*buffer, str, namelen); | ||
| 38 | return 0; | ||
| 39 | } | ||
| 40 | |||
| 41 | #define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT) | ||
| 42 | |||
| 43 | /** | ||
| 44 | * d_namespace_path - lookup a name associated with a given path | ||
| 45 | * @path: path to lookup (NOT NULL) | ||
| 46 | * @buf: buffer to store path to (NOT NULL) | ||
| 47 | * @buflen: length of @buf | ||
| 48 | * @name: Returns - pointer for start of path name with in @buf (NOT NULL) | ||
| 49 | * @flags: flags controlling path lookup | ||
| 50 | * | ||
| 51 | * Handle path name lookup. | ||
| 52 | * | ||
| 53 | * Returns: %0 else error code if path lookup fails | ||
| 54 | * When no error the path name is returned in @name which points to | ||
| 55 | * to a position in @buf | ||
| 56 | */ | ||
| 57 | static int d_namespace_path(struct path *path, char *buf, int buflen, | ||
| 58 | char **name, int flags) | ||
| 59 | { | ||
| 60 | struct path root, tmp; | ||
| 61 | char *res; | ||
| 62 | int deleted, connected; | ||
| 63 | int error = 0; | ||
| 64 | |||
| 65 | /* Get the root we want to resolve too */ | ||
| 66 | if (flags & PATH_CHROOT_REL) { | ||
| 67 | /* resolve paths relative to chroot */ | ||
| 68 | read_lock(¤t->fs->lock); | ||
| 69 | root = current->fs->root; | ||
| 70 | /* released below */ | ||
| 71 | path_get(&root); | ||
| 72 | read_unlock(¤t->fs->lock); | ||
| 73 | } else { | ||
| 74 | /* resolve paths relative to namespace */ | ||
| 75 | root.mnt = current->nsproxy->mnt_ns->root; | ||
| 76 | root.dentry = root.mnt->mnt_root; | ||
| 77 | /* released below */ | ||
| 78 | path_get(&root); | ||
| 79 | } | ||
| 80 | |||
| 81 | spin_lock(&dcache_lock); | ||
| 82 | /* There is a race window between path lookup here and the | ||
| 83 | * need to strip the " (deleted) string that __d_path applies | ||
| 84 | * Detect the race and relookup the path | ||
| 85 | * | ||
| 86 | * The stripping of (deleted) is a hack that could be removed | ||
| 87 | * with an updated __d_path | ||
| 88 | */ | ||
| 89 | do { | ||
| 90 | tmp = root; | ||
| 91 | deleted = d_unlinked(path->dentry); | ||
| 92 | res = __d_path(path, &tmp, buf, buflen); | ||
| 93 | |||
| 94 | } while (deleted != d_unlinked(path->dentry)); | ||
| 95 | spin_unlock(&dcache_lock); | ||
| 96 | |||
| 97 | *name = res; | ||
| 98 | /* handle error conditions - and still allow a partial path to | ||
| 99 | * be returned. | ||
| 100 | */ | ||
| 101 | if (IS_ERR(res)) { | ||
| 102 | error = PTR_ERR(res); | ||
| 103 | *name = buf; | ||
| 104 | goto out; | ||
| 105 | } | ||
| 106 | if (deleted) { | ||
| 107 | /* On some filesystems, newly allocated dentries appear to the | ||
| 108 | * security_path hooks as a deleted dentry except without an | ||
| 109 | * inode allocated. | ||
| 110 | * | ||
| 111 | * Remove the appended deleted text and return as string for | ||
| 112 | * normal mediation, or auditing. The (deleted) string is | ||
| 113 | * guaranteed to be added in this case, so just strip it. | ||
| 114 | */ | ||
| 115 | buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */ | ||
| 116 | |||
| 117 | if (path->dentry->d_inode && !(flags & PATH_MEDIATE_DELETED)) { | ||
| 118 | error = -ENOENT; | ||
| 119 | goto out; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | /* Determine if the path is connected to the expected root */ | ||
| 124 | connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt; | ||
| 125 | |||
| 126 | /* If the path is not connected, | ||
| 127 | * check if it is a sysctl and handle specially else remove any | ||
| 128 | * leading / that __d_path may have returned. | ||
| 129 | * Unless | ||
| 130 | * specifically directed to connect the path, | ||
| 131 | * OR | ||
| 132 | * if in a chroot and doing chroot relative paths and the path | ||
| 133 | * resolves to the namespace root (would be connected outside | ||
| 134 | * of chroot) and specifically directed to connect paths to | ||
| 135 | * namespace root. | ||
| 136 | */ | ||
| 137 | if (!connected) { | ||
| 138 | /* is the disconnect path a sysctl? */ | ||
| 139 | if (tmp.dentry->d_sb->s_magic == PROC_SUPER_MAGIC && | ||
| 140 | strncmp(*name, "/sys/", 5) == 0) { | ||
| 141 | /* TODO: convert over to using a per namespace | ||
| 142 | * control instead of hard coded /proc | ||
| 143 | */ | ||
| 144 | error = prepend(name, *name - buf, "/proc", 5); | ||
| 145 | } else if (!(flags & PATH_CONNECT_PATH) && | ||
| 146 | !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && | ||
| 147 | (tmp.mnt == current->nsproxy->mnt_ns->root && | ||
| 148 | tmp.dentry == tmp.mnt->mnt_root))) { | ||
| 149 | /* disconnected path, don't return pathname starting | ||
| 150 | * with '/' | ||
| 151 | */ | ||
| 152 | error = -ESTALE; | ||
| 153 | if (*res == '/') | ||
| 154 | *name = res + 1; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | out: | ||
| 159 | path_put(&root); | ||
| 160 | |||
| 161 | return error; | ||
| 162 | } | ||
| 163 | |||
| 164 | /** | ||
| 165 | * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended | ||
| 166 | * @path: path to get name for (NOT NULL) | ||
| 167 | * @flags: flags controlling path lookup | ||
| 168 | * @buffer: buffer to put name in (NOT NULL) | ||
| 169 | * @size: size of buffer | ||
| 170 | * @name: Returns - contains position of path name in @buffer (NOT NULL) | ||
| 171 | * | ||
| 172 | * Returns: %0 else error on failure | ||
| 173 | */ | ||
| 174 | static int get_name_to_buffer(struct path *path, int flags, char *buffer, | ||
| 175 | int size, char **name) | ||
| 176 | { | ||
| 177 | int adjust = (flags & PATH_IS_DIR) ? 1 : 0; | ||
| 178 | int error = d_namespace_path(path, buffer, size - adjust, name, flags); | ||
| 179 | |||
| 180 | if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') | ||
| 181 | /* | ||
| 182 | * Append "/" to the pathname. The root directory is a special | ||
| 183 | * case; it already ends in slash. | ||
| 184 | */ | ||
| 185 | strcpy(&buffer[size - 2], "/"); | ||
| 186 | |||
| 187 | return error; | ||
| 188 | } | ||
| 189 | |||
| 190 | /** | ||
| 191 | * aa_get_name - compute the pathname of a file | ||
| 192 | * @path: path the file (NOT NULL) | ||
| 193 | * @flags: flags controlling path name generation | ||
| 194 | * @buffer: buffer that aa_get_name() allocated (NOT NULL) | ||
| 195 | * @name: Returns - the generated path name if !error (NOT NULL) | ||
| 196 | * | ||
| 197 | * @name is a pointer to the beginning of the pathname (which usually differs | ||
| 198 | * from the beginning of the buffer), or NULL. If there is an error @name | ||
| 199 | * may contain a partial or invalid name that can be used for audit purposes, | ||
| 200 | * but it can not be used for mediation. | ||
| 201 | * | ||
| 202 | * We need PATH_IS_DIR to indicate whether the file is a directory or not | ||
| 203 | * because the file may not yet exist, and so we cannot check the inode's | ||
| 204 | * file type. | ||
| 205 | * | ||
| 206 | * Returns: %0 else error code if could retrieve name | ||
| 207 | */ | ||
| 208 | int aa_get_name(struct path *path, int flags, char **buffer, const char **name) | ||
| 209 | { | ||
| 210 | char *buf, *str = NULL; | ||
| 211 | int size = 256; | ||
| 212 | int error; | ||
| 213 | |||
| 214 | *name = NULL; | ||
| 215 | *buffer = NULL; | ||
| 216 | for (;;) { | ||
| 217 | /* freed by caller */ | ||
| 218 | buf = kmalloc(size, GFP_KERNEL); | ||
| 219 | if (!buf) | ||
| 220 | return -ENOMEM; | ||
| 221 | |||
| 222 | error = get_name_to_buffer(path, flags, buf, size, &str); | ||
| 223 | if (error != -ENAMETOOLONG) | ||
| 224 | break; | ||
| 225 | |||
| 226 | kfree(buf); | ||
| 227 | size <<= 1; | ||
| 228 | if (size > aa_g_path_max) | ||
| 229 | return -ENAMETOOLONG; | ||
| 230 | } | ||
| 231 | *buffer = buf; | ||
| 232 | *name = str; | ||
| 233 | |||
| 234 | return error; | ||
| 235 | } | ||
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c new file mode 100644 index 000000000000..3cdc1ad0787e --- /dev/null +++ b/security/apparmor/policy.c | |||
| @@ -0,0 +1,1184 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor policy manipulation 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 | * | ||
| 15 | * AppArmor policy is based around profiles, which contain the rules a | ||
| 16 | * task is confined by. Every task in the system has a profile attached | ||
| 17 | * to it determined either by matching "unconfined" tasks against the | ||
| 18 | * visible set of profiles or by following a profiles attachment rules. | ||
| 19 | * | ||
| 20 | * Each profile exists in a profile namespace which is a container of | ||
| 21 | * visible profiles. Each namespace contains a special "unconfined" profile, | ||
| 22 | * which doesn't enforce any confinement on a task beyond DAC. | ||
| 23 | * | ||
| 24 | * Namespace and profile names can be written together in either | ||
| 25 | * of two syntaxes. | ||
| 26 | * :namespace:profile - used by kernel interfaces for easy detection | ||
| 27 | * namespace://profile - used by policy | ||
| 28 | * | ||
| 29 | * Profile names can not start with : or @ or ^ and may not contain \0 | ||
| 30 | * | ||
| 31 | * Reserved profile names | ||
| 32 | * unconfined - special automatically generated unconfined profile | ||
| 33 | * inherit - special name to indicate profile inheritance | ||
| 34 | * null-XXXX-YYYY - special automatically generated learning profiles | ||
| 35 | * | ||
| 36 | * Namespace names may not start with / or @ and may not contain \0 or : | ||
| 37 | * Reserved namespace names | ||
| 38 | * user-XXXX - user defined profiles | ||
| 39 | * | ||
| 40 | * a // in a profile or namespace name indicates a hierarchical name with the | ||
| 41 | * name before the // being the parent and the name after the child. | ||
| 42 | * | ||
| 43 | * Profile and namespace hierarchies serve two different but similar purposes. | ||
| 44 | * The namespace contains the set of visible profiles that are considered | ||
| 45 | * for attachment. The hierarchy of namespaces allows for virtualizing | ||
| 46 | * the namespace so that for example a chroot can have its own set of profiles | ||
| 47 | * which may define some local user namespaces. | ||
| 48 | * The profile hierarchy severs two distinct purposes, | ||
| 49 | * - it allows for sub profiles or hats, which allows an application to run | ||
| 50 | * subprograms under its own profile with different restriction than it | ||
| 51 | * self, and not have it use the system profile. | ||
| 52 | * eg. if a mail program starts an editor, the policy might make the | ||
| 53 | * restrictions tighter on the editor tighter than the mail program, | ||
| 54 | * and definitely different than general editor restrictions | ||
| 55 | * - it allows for binary hierarchy of profiles, so that execution history | ||
| 56 | * is preserved. This feature isn't exploited by AppArmor reference policy | ||
| 57 | * but is allowed. NOTE: this is currently suboptimal because profile | ||
| 58 | * aliasing is not currently implemented so that a profile for each | ||
| 59 | * level must be defined. | ||
| 60 | * eg. /bin/bash///bin/ls as a name would indicate /bin/ls was started | ||
| 61 | * from /bin/bash | ||
| 62 | * | ||
| 63 | * A profile or namespace name that can contain one or more // separators | ||
| 64 | * is referred to as an hname (hierarchical). | ||
| 65 | * eg. /bin/bash//bin/ls | ||
| 66 | * | ||
| 67 | * An fqname is a name that may contain both namespace and profile hnames. | ||
| 68 | * eg. :ns:/bin/bash//bin/ls | ||
| 69 | * | ||
| 70 | * NOTES: | ||
| 71 | * - locking of profile lists is currently fairly coarse. All profile | ||
| 72 | * lists within a namespace use the namespace lock. | ||
| 73 | * FIXME: move profile lists to using rcu_lists | ||
| 74 | */ | ||
| 75 | |||
| 76 | #include <linux/slab.h> | ||
| 77 | #include <linux/spinlock.h> | ||
| 78 | #include <linux/string.h> | ||
| 79 | |||
| 80 | #include "include/apparmor.h" | ||
| 81 | #include "include/capability.h" | ||
| 82 | #include "include/context.h" | ||
| 83 | #include "include/file.h" | ||
| 84 | #include "include/ipc.h" | ||
| 85 | #include "include/match.h" | ||
| 86 | #include "include/path.h" | ||
| 87 | #include "include/policy.h" | ||
| 88 | #include "include/policy_unpack.h" | ||
| 89 | #include "include/resource.h" | ||
| 90 | #include "include/sid.h" | ||
| 91 | |||
| 92 | |||
| 93 | /* root profile namespace */ | ||
| 94 | struct aa_namespace *root_ns; | ||
| 95 | |||
| 96 | const char *profile_mode_names[] = { | ||
| 97 | "enforce", | ||
| 98 | "complain", | ||
| 99 | "kill", | ||
| 100 | }; | ||
| 101 | |||
| 102 | /** | ||
| 103 | * hname_tail - find the last component of an hname | ||
| 104 | * @name: hname to find the base profile name component of (NOT NULL) | ||
| 105 | * | ||
| 106 | * Returns: the tail (base profile name) name component of an hname | ||
| 107 | */ | ||
| 108 | static const char *hname_tail(const char *hname) | ||
| 109 | { | ||
| 110 | char *split; | ||
| 111 | hname = strim((char *)hname); | ||
| 112 | for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) | ||
| 113 | hname = split + 2; | ||
| 114 | |||
| 115 | return hname; | ||
| 116 | } | ||
| 117 | |||
| 118 | /** | ||
| 119 | * policy_init - initialize a policy structure | ||
| 120 | * @policy: policy to initialize (NOT NULL) | ||
| 121 | * @prefix: prefix name if any is required. (MAYBE NULL) | ||
| 122 | * @name: name of the policy, init will make a copy of it (NOT NULL) | ||
| 123 | * | ||
| 124 | * Note: this fn creates a copy of strings passed in | ||
| 125 | * | ||
| 126 | * Returns: true if policy init successful | ||
| 127 | */ | ||
| 128 | static bool policy_init(struct aa_policy *policy, const char *prefix, | ||
| 129 | const char *name) | ||
| 130 | { | ||
| 131 | /* freed by policy_free */ | ||
| 132 | if (prefix) { | ||
| 133 | policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, | ||
| 134 | GFP_KERNEL); | ||
| 135 | if (policy->hname) | ||
| 136 | sprintf(policy->hname, "%s//%s", prefix, name); | ||
| 137 | } else | ||
| 138 | policy->hname = kstrdup(name, GFP_KERNEL); | ||
| 139 | if (!policy->hname) | ||
| 140 | return 0; | ||
| 141 | /* base.name is a substring of fqname */ | ||
| 142 | policy->name = (char *)hname_tail(policy->hname); | ||
| 143 | INIT_LIST_HEAD(&policy->list); | ||
| 144 | INIT_LIST_HEAD(&policy->profiles); | ||
| 145 | kref_init(&policy->count); | ||
| 146 | |||
| 147 | return 1; | ||
| 148 | } | ||
| 149 | |||
| 150 | /** | ||
| 151 | * policy_destroy - free the elements referenced by @policy | ||
| 152 | * @policy: policy that is to have its elements freed (NOT NULL) | ||
| 153 | */ | ||
| 154 | static void policy_destroy(struct aa_policy *policy) | ||
| 155 | { | ||
| 156 | /* still contains profiles -- invalid */ | ||
| 157 | if (!list_empty(&policy->profiles)) { | ||
| 158 | AA_ERROR("%s: internal error, " | ||
| 159 | "policy '%s' still contains profiles\n", | ||
| 160 | __func__, policy->name); | ||
| 161 | BUG(); | ||
| 162 | } | ||
| 163 | if (!list_empty(&policy->list)) { | ||
| 164 | AA_ERROR("%s: internal error, policy '%s' still on list\n", | ||
| 165 | __func__, policy->name); | ||
| 166 | BUG(); | ||
| 167 | } | ||
| 168 | |||
| 169 | /* don't free name as its a subset of hname */ | ||
| 170 | kzfree(policy->hname); | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * __policy_find - find a policy by @name on a policy list | ||
| 175 | * @head: list to search (NOT NULL) | ||
| 176 | * @name: name to search for (NOT NULL) | ||
| 177 | * | ||
| 178 | * Requires: correct locks for the @head list be held | ||
| 179 | * | ||
| 180 | * Returns: unrefcounted policy that match @name or NULL if not found | ||
| 181 | */ | ||
| 182 | static struct aa_policy *__policy_find(struct list_head *head, const char *name) | ||
| 183 | { | ||
| 184 | struct aa_policy *policy; | ||
| 185 | |||
| 186 | list_for_each_entry(policy, head, list) { | ||
| 187 | if (!strcmp(policy->name, name)) | ||
| 188 | return policy; | ||
| 189 | } | ||
| 190 | return NULL; | ||
| 191 | } | ||
| 192 | |||
| 193 | /** | ||
| 194 | * __policy_strn_find - find a policy that's name matches @len chars of @str | ||
| 195 | * @head: list to search (NOT NULL) | ||
| 196 | * @str: string to search for (NOT NULL) | ||
| 197 | * @len: length of match required | ||
| 198 | * | ||
| 199 | * Requires: correct locks for the @head list be held | ||
| 200 | * | ||
| 201 | * Returns: unrefcounted policy that match @str or NULL if not found | ||
| 202 | * | ||
| 203 | * if @len == strlen(@strlen) then this is equiv to __policy_find | ||
| 204 | * other wise it allows searching for policy by a partial match of name | ||
| 205 | */ | ||
| 206 | static struct aa_policy *__policy_strn_find(struct list_head *head, | ||
| 207 | const char *str, int len) | ||
| 208 | { | ||
| 209 | struct aa_policy *policy; | ||
| 210 | |||
| 211 | list_for_each_entry(policy, head, list) { | ||
| 212 | if (aa_strneq(policy->name, str, len)) | ||
| 213 | return policy; | ||
| 214 | } | ||
| 215 | |||
| 216 | return NULL; | ||
| 217 | } | ||
| 218 | |||
| 219 | /* | ||
| 220 | * Routines for AppArmor namespaces | ||
| 221 | */ | ||
| 222 | |||
| 223 | static const char *hidden_ns_name = "---"; | ||
| 224 | /** | ||
| 225 | * aa_ns_visible - test if @view is visible from @curr | ||
| 226 | * @curr: namespace to treat as the parent (NOT NULL) | ||
| 227 | * @view: namespace to test if visible from @curr (NOT NULL) | ||
| 228 | * | ||
| 229 | * Returns: true if @view is visible from @curr else false | ||
| 230 | */ | ||
| 231 | bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view) | ||
| 232 | { | ||
| 233 | if (curr == view) | ||
| 234 | return true; | ||
| 235 | |||
| 236 | for ( ; view; view = view->parent) { | ||
| 237 | if (view->parent == curr) | ||
| 238 | return true; | ||
| 239 | } | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | |||
| 243 | /** | ||
| 244 | * aa_na_name - Find the ns name to display for @view from @curr | ||
| 245 | * @curr - current namespace (NOT NULL) | ||
| 246 | * @view - namespace attempting to view (NOT NULL) | ||
| 247 | * | ||
| 248 | * Returns: name of @view visible from @curr | ||
| 249 | */ | ||
| 250 | const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view) | ||
| 251 | { | ||
| 252 | /* if view == curr then the namespace name isn't displayed */ | ||
| 253 | if (curr == view) | ||
| 254 | return ""; | ||
| 255 | |||
| 256 | if (aa_ns_visible(curr, view)) { | ||
| 257 | /* at this point if a ns is visible it is in a view ns | ||
| 258 | * thus the curr ns.hname is a prefix of its name. | ||
| 259 | * Only output the virtualized portion of the name | ||
| 260 | * Add + 2 to skip over // separating curr hname prefix | ||
| 261 | * from the visible tail of the views hname | ||
| 262 | */ | ||
| 263 | return view->base.hname + strlen(curr->base.hname) + 2; | ||
| 264 | } else | ||
| 265 | return hidden_ns_name; | ||
| 266 | } | ||
| 267 | |||
| 268 | /** | ||
| 269 | * alloc_namespace - allocate, initialize and return a new namespace | ||
| 270 | * @prefix: parent namespace name (MAYBE NULL) | ||
| 271 | * @name: a preallocated name (NOT NULL) | ||
| 272 | * | ||
| 273 | * Returns: refcounted namespace or NULL on failure. | ||
| 274 | */ | ||
| 275 | static struct aa_namespace *alloc_namespace(const char *prefix, | ||
| 276 | const char *name) | ||
| 277 | { | ||
| 278 | struct aa_namespace *ns; | ||
| 279 | |||
| 280 | ns = kzalloc(sizeof(*ns), GFP_KERNEL); | ||
| 281 | AA_DEBUG("%s(%p)\n", __func__, ns); | ||
| 282 | if (!ns) | ||
| 283 | return NULL; | ||
| 284 | if (!policy_init(&ns->base, prefix, name)) | ||
| 285 | goto fail_ns; | ||
| 286 | |||
| 287 | INIT_LIST_HEAD(&ns->sub_ns); | ||
| 288 | rwlock_init(&ns->lock); | ||
| 289 | |||
| 290 | /* released by free_namespace */ | ||
| 291 | ns->unconfined = aa_alloc_profile("unconfined"); | ||
| 292 | if (!ns->unconfined) | ||
| 293 | goto fail_unconfined; | ||
| 294 | |||
| 295 | ns->unconfined->sid = aa_alloc_sid(); | ||
| 296 | ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR | | ||
| 297 | PFLAG_IMMUTABLE; | ||
| 298 | |||
| 299 | /* | ||
| 300 | * released by free_namespace, however __remove_namespace breaks | ||
| 301 | * the cyclic references (ns->unconfined, and unconfined->ns) and | ||
| 302 | * replaces with refs to parent namespace unconfined | ||
| 303 | */ | ||
| 304 | ns->unconfined->ns = aa_get_namespace(ns); | ||
| 305 | |||
| 306 | return ns; | ||
| 307 | |||
| 308 | fail_unconfined: | ||
| 309 | kzfree(ns->base.name); | ||
| 310 | fail_ns: | ||
| 311 | kzfree(ns); | ||
| 312 | return NULL; | ||
| 313 | } | ||
| 314 | |||
| 315 | /** | ||
| 316 | * free_namespace - free a profile namespace | ||
| 317 | * @ns: the namespace to free (MAYBE NULL) | ||
| 318 | * | ||
| 319 | * Requires: All references to the namespace must have been put, if the | ||
| 320 | * namespace was referenced by a profile confining a task, | ||
| 321 | */ | ||
| 322 | static void free_namespace(struct aa_namespace *ns) | ||
| 323 | { | ||
| 324 | if (!ns) | ||
| 325 | return; | ||
| 326 | |||
| 327 | policy_destroy(&ns->base); | ||
| 328 | aa_put_namespace(ns->parent); | ||
| 329 | |||
| 330 | if (ns->unconfined && ns->unconfined->ns == ns) | ||
| 331 | ns->unconfined->ns = NULL; | ||
| 332 | |||
| 333 | aa_put_profile(ns->unconfined); | ||
| 334 | kzfree(ns); | ||
| 335 | } | ||
| 336 | |||
| 337 | /** | ||
| 338 | * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace) | ||
| 339 | * @kr: kref callback for freeing of a namespace (NOT NULL) | ||
| 340 | */ | ||
| 341 | void aa_free_namespace_kref(struct kref *kref) | ||
| 342 | { | ||
| 343 | free_namespace(container_of(kref, struct aa_namespace, base.count)); | ||
| 344 | } | ||
| 345 | |||
| 346 | /** | ||
| 347 | * __aa_find_namespace - find a namespace on a list by @name | ||
| 348 | * @head: list to search for namespace on (NOT NULL) | ||
| 349 | * @name: name of namespace to look for (NOT NULL) | ||
| 350 | * | ||
| 351 | * Returns: unrefcounted namespace | ||
| 352 | * | ||
| 353 | * Requires: ns lock be held | ||
| 354 | */ | ||
| 355 | static struct aa_namespace *__aa_find_namespace(struct list_head *head, | ||
| 356 | const char *name) | ||
| 357 | { | ||
| 358 | return (struct aa_namespace *)__policy_find(head, name); | ||
| 359 | } | ||
| 360 | |||
| 361 | /** | ||
| 362 | * aa_find_namespace - look up a profile namespace on the namespace list | ||
| 363 | * @root: namespace to search in (NOT NULL) | ||
| 364 | * @name: name of namespace to find (NOT NULL) | ||
| 365 | * | ||
| 366 | * Returns: a refcounted namespace on the list, or NULL if no namespace | ||
| 367 | * called @name exists. | ||
| 368 | * | ||
| 369 | * refcount released by caller | ||
| 370 | */ | ||
| 371 | struct aa_namespace *aa_find_namespace(struct aa_namespace *root, | ||
| 372 | const char *name) | ||
| 373 | { | ||
| 374 | struct aa_namespace *ns = NULL; | ||
| 375 | |||
| 376 | read_lock(&root->lock); | ||
| 377 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | ||
| 378 | read_unlock(&root->lock); | ||
| 379 | |||
| 380 | return ns; | ||
| 381 | } | ||
| 382 | |||
| 383 | /** | ||
| 384 | * aa_prepare_namespace - find an existing or create a new namespace of @name | ||
| 385 | * @name: the namespace to find or add (MAYBE NULL) | ||
| 386 | * | ||
| 387 | * Returns: refcounted namespace or NULL if failed to create one | ||
| 388 | */ | ||
| 389 | static struct aa_namespace *aa_prepare_namespace(const char *name) | ||
| 390 | { | ||
| 391 | struct aa_namespace *ns, *root; | ||
| 392 | |||
| 393 | root = aa_current_profile()->ns; | ||
| 394 | |||
| 395 | write_lock(&root->lock); | ||
| 396 | |||
| 397 | /* if name isn't specified the profile is loaded to the current ns */ | ||
| 398 | if (!name) { | ||
| 399 | /* released by caller */ | ||
| 400 | ns = aa_get_namespace(root); | ||
| 401 | goto out; | ||
| 402 | } | ||
| 403 | |||
| 404 | /* try and find the specified ns and if it doesn't exist create it */ | ||
| 405 | /* released by caller */ | ||
| 406 | ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); | ||
| 407 | if (!ns) { | ||
| 408 | /* namespace not found */ | ||
| 409 | struct aa_namespace *new_ns; | ||
| 410 | write_unlock(&root->lock); | ||
| 411 | new_ns = alloc_namespace(root->base.hname, name); | ||
| 412 | if (!new_ns) | ||
| 413 | return NULL; | ||
| 414 | write_lock(&root->lock); | ||
| 415 | /* test for race when new_ns was allocated */ | ||
| 416 | ns = __aa_find_namespace(&root->sub_ns, name); | ||
| 417 | if (!ns) { | ||
| 418 | /* add parent ref */ | ||
| 419 | new_ns->parent = aa_get_namespace(root); | ||
| 420 | |||
| 421 | list_add(&new_ns->base.list, &root->sub_ns); | ||
| 422 | /* add list ref */ | ||
| 423 | ns = aa_get_namespace(new_ns); | ||
| 424 | } else { | ||
| 425 | /* raced so free the new one */ | ||
| 426 | free_namespace(new_ns); | ||
| 427 | /* get reference on namespace */ | ||
| 428 | aa_get_namespace(ns); | ||
| 429 | } | ||
| 430 | } | ||
| 431 | out: | ||
| 432 | write_unlock(&root->lock); | ||
| 433 | |||
| 434 | /* return ref */ | ||
| 435 | return ns; | ||
| 436 | } | ||
| 437 | |||
| 438 | /** | ||
| 439 | * __list_add_profile - add a profile to a list | ||
| 440 | * @list: list to add it to (NOT NULL) | ||
| 441 | * @profile: the profile to add (NOT NULL) | ||
| 442 | * | ||
| 443 | * refcount @profile, should be put by __list_remove_profile | ||
| 444 | * | ||
| 445 | * Requires: namespace lock be held, or list not be shared | ||
| 446 | */ | ||
| 447 | static void __list_add_profile(struct list_head *list, | ||
| 448 | struct aa_profile *profile) | ||
| 449 | { | ||
| 450 | list_add(&profile->base.list, list); | ||
| 451 | /* get list reference */ | ||
| 452 | aa_get_profile(profile); | ||
| 453 | } | ||
| 454 | |||
| 455 | /** | ||
| 456 | * __list_remove_profile - remove a profile from the list it is on | ||
| 457 | * @profile: the profile to remove (NOT NULL) | ||
| 458 | * | ||
| 459 | * remove a profile from the list, warning generally removal should | ||
| 460 | * be done with __replace_profile as most profile removals are | ||
| 461 | * replacements to the unconfined profile. | ||
| 462 | * | ||
| 463 | * put @profile list refcount | ||
| 464 | * | ||
| 465 | * Requires: namespace lock be held, or list not have been live | ||
| 466 | */ | ||
| 467 | static void __list_remove_profile(struct aa_profile *profile) | ||
| 468 | { | ||
| 469 | list_del_init(&profile->base.list); | ||
| 470 | if (!(profile->flags & PFLAG_NO_LIST_REF)) | ||
| 471 | /* release list reference */ | ||
| 472 | aa_put_profile(profile); | ||
| 473 | } | ||
| 474 | |||
| 475 | /** | ||
| 476 | * __replace_profile - replace @old with @new on a list | ||
| 477 | * @old: profile to be replaced (NOT NULL) | ||
| 478 | * @new: profile to replace @old with (NOT NULL) | ||
| 479 | * | ||
| 480 | * Will duplicate and refcount elements that @new inherits from @old | ||
| 481 | * and will inherit @old children. | ||
| 482 | * | ||
| 483 | * refcount @new for list, put @old list refcount | ||
| 484 | * | ||
| 485 | * Requires: namespace list lock be held, or list not be shared | ||
| 486 | */ | ||
| 487 | static void __replace_profile(struct aa_profile *old, struct aa_profile *new) | ||
| 488 | { | ||
| 489 | struct aa_policy *policy; | ||
| 490 | struct aa_profile *child, *tmp; | ||
| 491 | |||
| 492 | if (old->parent) | ||
| 493 | policy = &old->parent->base; | ||
| 494 | else | ||
| 495 | policy = &old->ns->base; | ||
| 496 | |||
| 497 | /* released when @new is freed */ | ||
| 498 | new->parent = aa_get_profile(old->parent); | ||
| 499 | new->ns = aa_get_namespace(old->ns); | ||
| 500 | new->sid = old->sid; | ||
| 501 | __list_add_profile(&policy->profiles, new); | ||
| 502 | /* inherit children */ | ||
| 503 | list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) { | ||
| 504 | aa_put_profile(child->parent); | ||
| 505 | child->parent = aa_get_profile(new); | ||
| 506 | /* list refcount transferred to @new*/ | ||
| 507 | list_move(&child->base.list, &new->base.profiles); | ||
| 508 | } | ||
| 509 | |||
| 510 | /* released by free_profile */ | ||
| 511 | old->replacedby = aa_get_profile(new); | ||
| 512 | __list_remove_profile(old); | ||
| 513 | } | ||
| 514 | |||
| 515 | static void __profile_list_release(struct list_head *head); | ||
| 516 | |||
| 517 | /** | ||
| 518 | * __remove_profile - remove old profile, and children | ||
| 519 | * @profile: profile to be replaced (NOT NULL) | ||
| 520 | * | ||
| 521 | * Requires: namespace list lock be held, or list not be shared | ||
| 522 | */ | ||
| 523 | static void __remove_profile(struct aa_profile *profile) | ||
| 524 | { | ||
| 525 | /* release any children lists first */ | ||
| 526 | __profile_list_release(&profile->base.profiles); | ||
| 527 | /* released by free_profile */ | ||
| 528 | profile->replacedby = aa_get_profile(profile->ns->unconfined); | ||
| 529 | __list_remove_profile(profile); | ||
| 530 | } | ||
| 531 | |||
| 532 | /** | ||
| 533 | * __profile_list_release - remove all profiles on the list and put refs | ||
| 534 | * @head: list of profiles (NOT NULL) | ||
| 535 | * | ||
| 536 | * Requires: namespace lock be held | ||
| 537 | */ | ||
| 538 | static void __profile_list_release(struct list_head *head) | ||
| 539 | { | ||
| 540 | struct aa_profile *profile, *tmp; | ||
| 541 | list_for_each_entry_safe(profile, tmp, head, base.list) | ||
| 542 | __remove_profile(profile); | ||
| 543 | } | ||
| 544 | |||
| 545 | static void __ns_list_release(struct list_head *head); | ||
| 546 | |||
| 547 | /** | ||
| 548 | * destroy_namespace - remove everything contained by @ns | ||
| 549 | * @ns: namespace to have it contents removed (NOT NULL) | ||
| 550 | */ | ||
| 551 | static void destroy_namespace(struct aa_namespace *ns) | ||
| 552 | { | ||
| 553 | if (!ns) | ||
| 554 | return; | ||
| 555 | |||
| 556 | write_lock(&ns->lock); | ||
| 557 | /* release all profiles in this namespace */ | ||
| 558 | __profile_list_release(&ns->base.profiles); | ||
| 559 | |||
| 560 | /* release all sub namespaces */ | ||
| 561 | __ns_list_release(&ns->sub_ns); | ||
| 562 | |||
| 563 | write_unlock(&ns->lock); | ||
| 564 | } | ||
| 565 | |||
| 566 | /** | ||
| 567 | * __remove_namespace - remove a namespace and all its children | ||
| 568 | * @ns: namespace to be removed (NOT NULL) | ||
| 569 | * | ||
| 570 | * Requires: ns->parent->lock be held and ns removed from parent. | ||
| 571 | */ | ||
| 572 | static void __remove_namespace(struct aa_namespace *ns) | ||
| 573 | { | ||
| 574 | struct aa_profile *unconfined = ns->unconfined; | ||
| 575 | |||
| 576 | /* remove ns from namespace list */ | ||
| 577 | list_del_init(&ns->base.list); | ||
| 578 | |||
| 579 | /* | ||
| 580 | * break the ns, unconfined profile cyclic reference and forward | ||
| 581 | * all new unconfined profiles requests to the parent namespace | ||
| 582 | * This will result in all confined tasks that have a profile | ||
| 583 | * being removed, inheriting the parent->unconfined profile. | ||
| 584 | */ | ||
| 585 | if (ns->parent) | ||
| 586 | ns->unconfined = aa_get_profile(ns->parent->unconfined); | ||
| 587 | |||
| 588 | destroy_namespace(ns); | ||
| 589 | |||
| 590 | /* release original ns->unconfined ref */ | ||
| 591 | aa_put_profile(unconfined); | ||
| 592 | /* release ns->base.list ref, from removal above */ | ||
| 593 | aa_put_namespace(ns); | ||
| 594 | } | ||
| 595 | |||
| 596 | /** | ||
| 597 | * __ns_list_release - remove all profile namespaces on the list put refs | ||
| 598 | * @head: list of profile namespaces (NOT NULL) | ||
| 599 | * | ||
| 600 | * Requires: namespace lock be held | ||
| 601 | */ | ||
| 602 | static void __ns_list_release(struct list_head *head) | ||
| 603 | { | ||
| 604 | struct aa_namespace *ns, *tmp; | ||
| 605 | list_for_each_entry_safe(ns, tmp, head, base.list) | ||
| 606 | __remove_namespace(ns); | ||
| 607 | |||
| 608 | } | ||
| 609 | |||
| 610 | /** | ||
| 611 | * aa_alloc_root_ns - allocate the root profile namespace | ||
| 612 | * | ||
| 613 | * Returns: %0 on success else error | ||
| 614 | * | ||
| 615 | */ | ||
| 616 | int __init aa_alloc_root_ns(void) | ||
| 617 | { | ||
| 618 | /* released by aa_free_root_ns - used as list ref*/ | ||
| 619 | root_ns = alloc_namespace(NULL, "root"); | ||
| 620 | if (!root_ns) | ||
| 621 | return -ENOMEM; | ||
| 622 | |||
| 623 | return 0; | ||
| 624 | } | ||
| 625 | |||
| 626 | /** | ||
| 627 | * aa_free_root_ns - free the root profile namespace | ||
| 628 | */ | ||
| 629 | void __init aa_free_root_ns(void) | ||
| 630 | { | ||
| 631 | struct aa_namespace *ns = root_ns; | ||
| 632 | root_ns = NULL; | ||
| 633 | |||
| 634 | destroy_namespace(ns); | ||
| 635 | aa_put_namespace(ns); | ||
| 636 | } | ||
| 637 | |||
| 638 | /** | ||
| 639 | * aa_alloc_profile - allocate, initialize and return a new profile | ||
| 640 | * @hname: name of the profile (NOT NULL) | ||
| 641 | * | ||
| 642 | * Returns: refcount profile or NULL on failure | ||
| 643 | */ | ||
| 644 | struct aa_profile *aa_alloc_profile(const char *hname) | ||
| 645 | { | ||
| 646 | struct aa_profile *profile; | ||
| 647 | |||
| 648 | /* freed by free_profile - usually through aa_put_profile */ | ||
| 649 | profile = kzalloc(sizeof(*profile), GFP_KERNEL); | ||
| 650 | if (!profile) | ||
| 651 | return NULL; | ||
| 652 | |||
| 653 | if (!policy_init(&profile->base, NULL, hname)) { | ||
| 654 | kzfree(profile); | ||
| 655 | return NULL; | ||
| 656 | } | ||
| 657 | |||
| 658 | /* refcount released by caller */ | ||
| 659 | return profile; | ||
| 660 | } | ||
| 661 | |||
| 662 | /** | ||
| 663 | * aa_new_null_profile - create a new null-X learning profile | ||
| 664 | * @parent: profile that caused this profile to be created (NOT NULL) | ||
| 665 | * @hat: true if the null- learning profile is a hat | ||
| 666 | * | ||
| 667 | * Create a null- complain mode profile used in learning mode. The name of | ||
| 668 | * the profile is unique and follows the format of parent//null-sid. | ||
| 669 | * | ||
| 670 | * null profiles are added to the profile list but the list does not | ||
| 671 | * hold a count on them so that they are automatically released when | ||
| 672 | * not in use. | ||
| 673 | * | ||
| 674 | * Returns: new refcounted profile else NULL on failure | ||
| 675 | */ | ||
| 676 | struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) | ||
| 677 | { | ||
| 678 | struct aa_profile *profile = NULL; | ||
| 679 | char *name; | ||
| 680 | u32 sid = aa_alloc_sid(); | ||
| 681 | |||
| 682 | /* freed below */ | ||
| 683 | name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL); | ||
| 684 | if (!name) | ||
| 685 | goto fail; | ||
| 686 | sprintf(name, "%s//null-%x", parent->base.hname, sid); | ||
| 687 | |||
| 688 | profile = aa_alloc_profile(name); | ||
| 689 | kfree(name); | ||
| 690 | if (!profile) | ||
| 691 | goto fail; | ||
| 692 | |||
| 693 | profile->sid = sid; | ||
| 694 | profile->mode = APPARMOR_COMPLAIN; | ||
| 695 | profile->flags = PFLAG_NULL; | ||
| 696 | if (hat) | ||
| 697 | profile->flags |= PFLAG_HAT; | ||
| 698 | |||
| 699 | /* released on free_profile */ | ||
| 700 | profile->parent = aa_get_profile(parent); | ||
| 701 | profile->ns = aa_get_namespace(parent->ns); | ||
| 702 | |||
| 703 | write_lock(&profile->ns->lock); | ||
| 704 | __list_add_profile(&parent->base.profiles, profile); | ||
| 705 | write_unlock(&profile->ns->lock); | ||
| 706 | |||
| 707 | /* refcount released by caller */ | ||
| 708 | return profile; | ||
| 709 | |||
| 710 | fail: | ||
| 711 | aa_free_sid(sid); | ||
| 712 | return NULL; | ||
| 713 | } | ||
| 714 | |||
| 715 | /** | ||
| 716 | * free_profile - free a profile | ||
| 717 | * @profile: the profile to free (MAYBE NULL) | ||
| 718 | * | ||
| 719 | * Free a profile, its hats and null_profile. All references to the profile, | ||
| 720 | * its hats and null_profile must have been put. | ||
| 721 | * | ||
| 722 | * If the profile was referenced from a task context, free_profile() will | ||
| 723 | * be called from an rcu callback routine, so we must not sleep here. | ||
| 724 | */ | ||
| 725 | static void free_profile(struct aa_profile *profile) | ||
| 726 | { | ||
| 727 | AA_DEBUG("%s(%p)\n", __func__, profile); | ||
| 728 | |||
| 729 | if (!profile) | ||
| 730 | return; | ||
| 731 | |||
| 732 | if (!list_empty(&profile->base.list)) { | ||
| 733 | AA_ERROR("%s: internal error, " | ||
| 734 | "profile '%s' still on ns list\n", | ||
| 735 | __func__, profile->base.name); | ||
| 736 | BUG(); | ||
| 737 | } | ||
| 738 | |||
| 739 | /* free children profiles */ | ||
| 740 | policy_destroy(&profile->base); | ||
| 741 | aa_put_profile(profile->parent); | ||
| 742 | |||
| 743 | aa_put_namespace(profile->ns); | ||
| 744 | kzfree(profile->rename); | ||
| 745 | |||
| 746 | aa_free_file_rules(&profile->file); | ||
| 747 | aa_free_cap_rules(&profile->caps); | ||
| 748 | aa_free_rlimit_rules(&profile->rlimits); | ||
| 749 | |||
| 750 | aa_free_sid(profile->sid); | ||
| 751 | aa_put_dfa(profile->xmatch); | ||
| 752 | |||
| 753 | aa_put_profile(profile->replacedby); | ||
| 754 | |||
| 755 | kzfree(profile); | ||
| 756 | } | ||
| 757 | |||
| 758 | /** | ||
| 759 | * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) | ||
| 760 | * @kr: kref callback for freeing of a profile (NOT NULL) | ||
| 761 | */ | ||
| 762 | void aa_free_profile_kref(struct kref *kref) | ||
| 763 | { | ||
| 764 | struct aa_profile *p = container_of(kref, struct aa_profile, | ||
| 765 | base.count); | ||
| 766 | |||
| 767 | free_profile(p); | ||
| 768 | } | ||
| 769 | |||
| 770 | /* TODO: profile accounting - setup in remove */ | ||
| 771 | |||
| 772 | /** | ||
| 773 | * __find_child - find a profile on @head list with a name matching @name | ||
| 774 | * @head: list to search (NOT NULL) | ||
| 775 | * @name: name of profile (NOT NULL) | ||
| 776 | * | ||
| 777 | * Requires: ns lock protecting list be held | ||
| 778 | * | ||
| 779 | * Returns: unrefcounted profile ptr, or NULL if not found | ||
| 780 | */ | ||
| 781 | static struct aa_profile *__find_child(struct list_head *head, const char *name) | ||
| 782 | { | ||
| 783 | return (struct aa_profile *)__policy_find(head, name); | ||
| 784 | } | ||
| 785 | |||
| 786 | /** | ||
| 787 | * __strn_find_child - find a profile on @head list using substring of @name | ||
| 788 | * @head: list to search (NOT NULL) | ||
| 789 | * @name: name of profile (NOT NULL) | ||
| 790 | * @len: length of @name substring to match | ||
| 791 | * | ||
| 792 | * Requires: ns lock protecting list be held | ||
| 793 | * | ||
| 794 | * Returns: unrefcounted profile ptr, or NULL if not found | ||
| 795 | */ | ||
| 796 | static struct aa_profile *__strn_find_child(struct list_head *head, | ||
| 797 | const char *name, int len) | ||
| 798 | { | ||
| 799 | return (struct aa_profile *)__policy_strn_find(head, name, len); | ||
| 800 | } | ||
| 801 | |||
| 802 | /** | ||
| 803 | * aa_find_child - find a profile by @name in @parent | ||
| 804 | * @parent: profile to search (NOT NULL) | ||
| 805 | * @name: profile name to search for (NOT NULL) | ||
| 806 | * | ||
| 807 | * Returns: a refcounted profile or NULL if not found | ||
| 808 | */ | ||
| 809 | struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) | ||
| 810 | { | ||
| 811 | struct aa_profile *profile; | ||
| 812 | |||
| 813 | read_lock(&parent->ns->lock); | ||
| 814 | profile = aa_get_profile(__find_child(&parent->base.profiles, name)); | ||
| 815 | read_unlock(&parent->ns->lock); | ||
| 816 | |||
| 817 | /* refcount released by caller */ | ||
| 818 | return profile; | ||
| 819 | } | ||
| 820 | |||
| 821 | /** | ||
| 822 | * __lookup_parent - lookup the parent of a profile of name @hname | ||
| 823 | * @ns: namespace to lookup profile in (NOT NULL) | ||
| 824 | * @hname: hierarchical profile name to find parent of (NOT NULL) | ||
| 825 | * | ||
| 826 | * Lookups up the parent of a fully qualified profile name, the profile | ||
| 827 | * that matches hname does not need to exist, in general this | ||
| 828 | * is used to load a new profile. | ||
| 829 | * | ||
| 830 | * Requires: ns->lock be held | ||
| 831 | * | ||
| 832 | * Returns: unrefcounted policy or NULL if not found | ||
| 833 | */ | ||
| 834 | static struct aa_policy *__lookup_parent(struct aa_namespace *ns, | ||
| 835 | const char *hname) | ||
| 836 | { | ||
| 837 | struct aa_policy *policy; | ||
| 838 | struct aa_profile *profile = NULL; | ||
| 839 | char *split; | ||
| 840 | |||
| 841 | policy = &ns->base; | ||
| 842 | |||
| 843 | for (split = strstr(hname, "//"); split;) { | ||
| 844 | profile = __strn_find_child(&policy->profiles, hname, | ||
| 845 | split - hname); | ||
| 846 | if (!profile) | ||
| 847 | return NULL; | ||
| 848 | policy = &profile->base; | ||
| 849 | hname = split + 2; | ||
| 850 | split = strstr(hname, "//"); | ||
| 851 | } | ||
| 852 | if (!profile) | ||
| 853 | return &ns->base; | ||
| 854 | return &profile->base; | ||
| 855 | } | ||
| 856 | |||
| 857 | /** | ||
| 858 | * __lookup_profile - lookup the profile matching @hname | ||
| 859 | * @base: base list to start looking up profile name from (NOT NULL) | ||
| 860 | * @hname: hierarchical profile name (NOT NULL) | ||
| 861 | * | ||
| 862 | * Requires: ns->lock be held | ||
| 863 | * | ||
| 864 | * Returns: unrefcounted profile pointer or NULL if not found | ||
| 865 | * | ||
| 866 | * Do a relative name lookup, recursing through profile tree. | ||
| 867 | */ | ||
| 868 | static struct aa_profile *__lookup_profile(struct aa_policy *base, | ||
| 869 | const char *hname) | ||
| 870 | { | ||
| 871 | struct aa_profile *profile = NULL; | ||
| 872 | char *split; | ||
| 873 | |||
| 874 | for (split = strstr(hname, "//"); split;) { | ||
| 875 | profile = __strn_find_child(&base->profiles, hname, | ||
| 876 | split - hname); | ||
| 877 | if (!profile) | ||
| 878 | return NULL; | ||
| 879 | |||
| 880 | base = &profile->base; | ||
| 881 | hname = split + 2; | ||
| 882 | split = strstr(hname, "//"); | ||
| 883 | } | ||
| 884 | |||
| 885 | profile = __find_child(&base->profiles, hname); | ||
| 886 | |||
| 887 | return profile; | ||
| 888 | } | ||
| 889 | |||
| 890 | /** | ||
| 891 | * aa_lookup_profile - find a profile by its full or partial name | ||
| 892 | * @ns: the namespace to start from (NOT NULL) | ||
| 893 | * @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL) | ||
| 894 | * | ||
| 895 | * Returns: refcounted profile or NULL if not found | ||
| 896 | */ | ||
| 897 | struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) | ||
| 898 | { | ||
| 899 | struct aa_profile *profile; | ||
| 900 | |||
| 901 | read_lock(&ns->lock); | ||
| 902 | profile = aa_get_profile(__lookup_profile(&ns->base, hname)); | ||
| 903 | read_unlock(&ns->lock); | ||
| 904 | |||
| 905 | /* refcount released by caller */ | ||
| 906 | return profile; | ||
| 907 | } | ||
| 908 | |||
| 909 | /** | ||
| 910 | * replacement_allowed - test to see if replacement is allowed | ||
| 911 | * @profile: profile to test if it can be replaced (MAYBE NULL) | ||
| 912 | * @noreplace: true if replacement shouldn't be allowed but addition is okay | ||
| 913 | * @info: Returns - info about why replacement failed (NOT NULL) | ||
| 914 | * | ||
| 915 | * Returns: %0 if replacement allowed else error code | ||
| 916 | */ | ||
| 917 | static int replacement_allowed(struct aa_profile *profile, int noreplace, | ||
| 918 | const char **info) | ||
| 919 | { | ||
| 920 | if (profile) { | ||
| 921 | if (profile->flags & PFLAG_IMMUTABLE) { | ||
| 922 | *info = "cannot replace immutible profile"; | ||
| 923 | return -EPERM; | ||
| 924 | } else if (noreplace) { | ||
| 925 | *info = "profile already exists"; | ||
| 926 | return -EEXIST; | ||
| 927 | } | ||
| 928 | } | ||
| 929 | return 0; | ||
| 930 | } | ||
| 931 | |||
| 932 | /** | ||
| 933 | * __add_new_profile - simple wrapper around __list_add_profile | ||
| 934 | * @ns: namespace that profile is being added to (NOT NULL) | ||
| 935 | * @policy: the policy container to add the profile to (NOT NULL) | ||
| 936 | * @profile: profile to add (NOT NULL) | ||
| 937 | * | ||
| 938 | * add a profile to a list and do other required basic allocations | ||
| 939 | */ | ||
| 940 | static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy, | ||
| 941 | struct aa_profile *profile) | ||
| 942 | { | ||
| 943 | if (policy != &ns->base) | ||
| 944 | /* released on profile replacement or free_profile */ | ||
| 945 | profile->parent = aa_get_profile((struct aa_profile *) policy); | ||
| 946 | __list_add_profile(&policy->profiles, profile); | ||
| 947 | /* released on free_profile */ | ||
| 948 | profile->sid = aa_alloc_sid(); | ||
| 949 | profile->ns = aa_get_namespace(ns); | ||
| 950 | } | ||
| 951 | |||
| 952 | /** | ||
| 953 | * aa_audit_policy - Do auditing of policy changes | ||
| 954 | * @op: policy operation being performed | ||
| 955 | * @gfp: memory allocation flags | ||
| 956 | * @name: name of profile being manipulated (NOT NULL) | ||
| 957 | * @info: any extra information to be audited (MAYBE NULL) | ||
| 958 | * @error: error code | ||
| 959 | * | ||
| 960 | * Returns: the error to be returned after audit is done | ||
| 961 | */ | ||
| 962 | static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, | ||
| 963 | int error) | ||
| 964 | { | ||
| 965 | struct common_audit_data sa; | ||
| 966 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 967 | sa.aad.op = op; | ||
| 968 | sa.aad.name = name; | ||
| 969 | sa.aad.info = info; | ||
| 970 | sa.aad.error = error; | ||
| 971 | |||
| 972 | return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, | ||
| 973 | &sa, NULL); | ||
| 974 | } | ||
| 975 | |||
| 976 | /** | ||
| 977 | * aa_may_manage_policy - can the current task manage policy | ||
| 978 | * @op: the policy manipulation operation being done | ||
| 979 | * | ||
| 980 | * Returns: true if the task is allowed to manipulate policy | ||
| 981 | */ | ||
| 982 | bool aa_may_manage_policy(int op) | ||
| 983 | { | ||
| 984 | /* check if loading policy is locked out */ | ||
| 985 | if (aa_g_lock_policy) { | ||
| 986 | audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); | ||
| 987 | return 0; | ||
| 988 | } | ||
| 989 | |||
| 990 | if (!capable(CAP_MAC_ADMIN)) { | ||
| 991 | audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); | ||
| 992 | return 0; | ||
| 993 | } | ||
| 994 | |||
| 995 | return 1; | ||
| 996 | } | ||
| 997 | |||
| 998 | /** | ||
| 999 | * aa_replace_profiles - replace profile(s) on the profile list | ||
| 1000 | * @udata: serialized data stream (NOT NULL) | ||
| 1001 | * @size: size of the serialized data stream | ||
| 1002 | * @noreplace: true if only doing addition, no replacement allowed | ||
| 1003 | * | ||
| 1004 | * unpack and replace a profile on the profile list and uses of that profile | ||
| 1005 | * by any aa_task_cxt. If the profile does not exist on the profile list | ||
| 1006 | * it is added. | ||
| 1007 | * | ||
| 1008 | * Returns: size of data consumed else error code on failure. | ||
| 1009 | */ | ||
| 1010 | ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace) | ||
| 1011 | { | ||
| 1012 | struct aa_policy *policy; | ||
| 1013 | struct aa_profile *old_profile = NULL, *new_profile = NULL; | ||
| 1014 | struct aa_profile *rename_profile = NULL; | ||
| 1015 | struct aa_namespace *ns = NULL; | ||
| 1016 | const char *ns_name, *name = NULL, *info = NULL; | ||
| 1017 | int op = OP_PROF_REPL; | ||
| 1018 | ssize_t error; | ||
| 1019 | |||
| 1020 | /* released below */ | ||
| 1021 | new_profile = aa_unpack(udata, size, &ns_name); | ||
| 1022 | if (IS_ERR(new_profile)) { | ||
| 1023 | error = PTR_ERR(new_profile); | ||
| 1024 | new_profile = NULL; | ||
| 1025 | goto fail; | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | /* released below */ | ||
| 1029 | ns = aa_prepare_namespace(ns_name); | ||
| 1030 | if (!ns) { | ||
| 1031 | info = "failed to prepare namespace"; | ||
| 1032 | error = -ENOMEM; | ||
| 1033 | name = ns_name; | ||
| 1034 | goto fail; | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | name = new_profile->base.hname; | ||
| 1038 | |||
| 1039 | write_lock(&ns->lock); | ||
| 1040 | /* no ref on policy only use inside lock */ | ||
| 1041 | policy = __lookup_parent(ns, new_profile->base.hname); | ||
| 1042 | |||
| 1043 | if (!policy) { | ||
| 1044 | info = "parent does not exist"; | ||
| 1045 | error = -ENOENT; | ||
| 1046 | goto audit; | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | old_profile = __find_child(&policy->profiles, new_profile->base.name); | ||
| 1050 | /* released below */ | ||
| 1051 | aa_get_profile(old_profile); | ||
| 1052 | |||
| 1053 | if (new_profile->rename) { | ||
| 1054 | rename_profile = __lookup_profile(&ns->base, | ||
| 1055 | new_profile->rename); | ||
| 1056 | /* released below */ | ||
| 1057 | aa_get_profile(rename_profile); | ||
| 1058 | |||
| 1059 | if (!rename_profile) { | ||
| 1060 | info = "profile to rename does not exist"; | ||
| 1061 | name = new_profile->rename; | ||
| 1062 | error = -ENOENT; | ||
| 1063 | goto audit; | ||
| 1064 | } | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | error = replacement_allowed(old_profile, noreplace, &info); | ||
| 1068 | if (error) | ||
| 1069 | goto audit; | ||
| 1070 | |||
| 1071 | error = replacement_allowed(rename_profile, noreplace, &info); | ||
| 1072 | if (error) | ||
| 1073 | goto audit; | ||
| 1074 | |||
| 1075 | audit: | ||
| 1076 | if (!old_profile && !rename_profile) | ||
| 1077 | op = OP_PROF_LOAD; | ||
| 1078 | |||
| 1079 | error = audit_policy(op, GFP_ATOMIC, name, info, error); | ||
| 1080 | |||
| 1081 | if (!error) { | ||
| 1082 | if (rename_profile) | ||
| 1083 | __replace_profile(rename_profile, new_profile); | ||
| 1084 | if (old_profile) { | ||
| 1085 | /* when there are both rename and old profiles | ||
| 1086 | * inherit old profiles sid | ||
| 1087 | */ | ||
| 1088 | if (rename_profile) | ||
| 1089 | aa_free_sid(new_profile->sid); | ||
| 1090 | __replace_profile(old_profile, new_profile); | ||
| 1091 | } | ||
| 1092 | if (!(old_profile || rename_profile)) | ||
| 1093 | __add_new_profile(ns, policy, new_profile); | ||
| 1094 | } | ||
| 1095 | write_unlock(&ns->lock); | ||
| 1096 | |||
| 1097 | out: | ||
| 1098 | aa_put_namespace(ns); | ||
| 1099 | aa_put_profile(rename_profile); | ||
| 1100 | aa_put_profile(old_profile); | ||
| 1101 | aa_put_profile(new_profile); | ||
| 1102 | if (error) | ||
| 1103 | return error; | ||
| 1104 | return size; | ||
| 1105 | |||
| 1106 | fail: | ||
| 1107 | error = audit_policy(op, GFP_KERNEL, name, info, error); | ||
| 1108 | goto out; | ||
| 1109 | } | ||
| 1110 | |||
| 1111 | /** | ||
| 1112 | * aa_remove_profiles - remove profile(s) from the system | ||
| 1113 | * @fqname: name of the profile or namespace to remove (NOT NULL) | ||
| 1114 | * @size: size of the name | ||
| 1115 | * | ||
| 1116 | * Remove a profile or sub namespace from the current namespace, so that | ||
| 1117 | * they can not be found anymore and mark them as replaced by unconfined | ||
| 1118 | * | ||
| 1119 | * NOTE: removing confinement does not restore rlimits to preconfinemnet values | ||
| 1120 | * | ||
| 1121 | * Returns: size of data consume else error code if fails | ||
| 1122 | */ | ||
| 1123 | ssize_t aa_remove_profiles(char *fqname, size_t size) | ||
| 1124 | { | ||
| 1125 | struct aa_namespace *root, *ns = NULL; | ||
| 1126 | struct aa_profile *profile = NULL; | ||
| 1127 | const char *name = fqname, *info = NULL; | ||
| 1128 | ssize_t error = 0; | ||
| 1129 | |||
| 1130 | if (*fqname == 0) { | ||
| 1131 | info = "no profile specified"; | ||
| 1132 | error = -ENOENT; | ||
| 1133 | goto fail; | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | root = aa_current_profile()->ns; | ||
| 1137 | |||
| 1138 | if (fqname[0] == ':') { | ||
| 1139 | char *ns_name; | ||
| 1140 | name = aa_split_fqname(fqname, &ns_name); | ||
| 1141 | if (ns_name) { | ||
| 1142 | /* released below */ | ||
| 1143 | ns = aa_find_namespace(root, ns_name); | ||
| 1144 | if (!ns) { | ||
| 1145 | info = "namespace does not exist"; | ||
| 1146 | error = -ENOENT; | ||
| 1147 | goto fail; | ||
| 1148 | } | ||
| 1149 | } | ||
| 1150 | } else | ||
| 1151 | /* released below */ | ||
| 1152 | ns = aa_get_namespace(root); | ||
| 1153 | |||
| 1154 | write_lock(&ns->lock); | ||
| 1155 | if (!name) { | ||
| 1156 | /* remove namespace - can only happen if fqname[0] == ':' */ | ||
| 1157 | __remove_namespace(ns); | ||
| 1158 | } else { | ||
| 1159 | /* remove profile */ | ||
| 1160 | profile = aa_get_profile(__lookup_profile(&ns->base, name)); | ||
| 1161 | if (!profile) { | ||
| 1162 | error = -ENOENT; | ||
| 1163 | info = "profile does not exist"; | ||
| 1164 | goto fail_ns_lock; | ||
| 1165 | } | ||
| 1166 | name = profile->base.hname; | ||
| 1167 | __remove_profile(profile); | ||
| 1168 | } | ||
| 1169 | write_unlock(&ns->lock); | ||
| 1170 | |||
| 1171 | /* don't fail removal if audit fails */ | ||
| 1172 | (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); | ||
| 1173 | aa_put_namespace(ns); | ||
| 1174 | aa_put_profile(profile); | ||
| 1175 | return size; | ||
| 1176 | |||
| 1177 | fail_ns_lock: | ||
| 1178 | write_unlock(&ns->lock); | ||
| 1179 | aa_put_namespace(ns); | ||
| 1180 | |||
| 1181 | fail: | ||
| 1182 | (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); | ||
| 1183 | return error; | ||
| 1184 | } | ||
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c new file mode 100644 index 000000000000..eb3700e9fd37 --- /dev/null +++ b/security/apparmor/policy_unpack.c | |||
| @@ -0,0 +1,703 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor functions for unpacking policy loaded from | ||
| 5 | * userspace. | ||
| 6 | * | ||
| 7 | * Copyright (C) 1998-2008 Novell/SUSE | ||
| 8 | * Copyright 2009-2010 Canonical Ltd. | ||
| 9 | * | ||
| 10 | * This program is free software; you can redistribute it and/or | ||
| 11 | * modify it under the terms of the GNU General Public License as | ||
| 12 | * published by the Free Software Foundation, version 2 of the | ||
| 13 | * License. | ||
| 14 | * | ||
| 15 | * AppArmor uses a serialized binary format for loading policy. | ||
| 16 | * To find policy format documentation look in Documentation/apparmor.txt | ||
| 17 | * All policy is validated before it is used. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <asm/unaligned.h> | ||
| 21 | #include <linux/ctype.h> | ||
| 22 | #include <linux/errno.h> | ||
| 23 | |||
| 24 | #include "include/apparmor.h" | ||
| 25 | #include "include/audit.h" | ||
| 26 | #include "include/context.h" | ||
| 27 | #include "include/match.h" | ||
| 28 | #include "include/policy.h" | ||
| 29 | #include "include/policy_unpack.h" | ||
| 30 | #include "include/sid.h" | ||
| 31 | |||
| 32 | /* | ||
| 33 | * The AppArmor interface treats data as a type byte followed by the | ||
| 34 | * actual data. The interface has the notion of a a named entry | ||
| 35 | * which has a name (AA_NAME typecode followed by name string) followed by | ||
| 36 | * the entries typecode and data. Named types allow for optional | ||
| 37 | * elements and extensions to be added and tested for without breaking | ||
| 38 | * backwards compatibility. | ||
| 39 | */ | ||
| 40 | |||
| 41 | enum aa_code { | ||
| 42 | AA_U8, | ||
| 43 | AA_U16, | ||
| 44 | AA_U32, | ||
| 45 | AA_U64, | ||
| 46 | AA_NAME, /* same as string except it is items name */ | ||
| 47 | AA_STRING, | ||
| 48 | AA_BLOB, | ||
| 49 | AA_STRUCT, | ||
| 50 | AA_STRUCTEND, | ||
| 51 | AA_LIST, | ||
| 52 | AA_LISTEND, | ||
| 53 | AA_ARRAY, | ||
| 54 | AA_ARRAYEND, | ||
| 55 | }; | ||
| 56 | |||
| 57 | /* | ||
| 58 | * aa_ext is the read of the buffer containing the serialized profile. The | ||
| 59 | * data is copied into a kernel buffer in apparmorfs and then handed off to | ||
| 60 | * the unpack routines. | ||
| 61 | */ | ||
| 62 | struct aa_ext { | ||
| 63 | void *start; | ||
| 64 | void *end; | ||
| 65 | void *pos; /* pointer to current position in the buffer */ | ||
| 66 | u32 version; | ||
| 67 | }; | ||
| 68 | |||
| 69 | /* audit callback for unpack fields */ | ||
| 70 | static void audit_cb(struct audit_buffer *ab, void *va) | ||
| 71 | { | ||
| 72 | struct common_audit_data *sa = va; | ||
| 73 | if (sa->aad.iface.target) { | ||
| 74 | struct aa_profile *name = sa->aad.iface.target; | ||
| 75 | audit_log_format(ab, " name="); | ||
| 76 | audit_log_untrustedstring(ab, name->base.hname); | ||
| 77 | } | ||
| 78 | if (sa->aad.iface.pos) | ||
| 79 | audit_log_format(ab, " offset=%ld", sa->aad.iface.pos); | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * audit_iface - do audit message for policy unpacking/load/replace/remove | ||
| 84 | * @new: profile if it has been allocated (MAYBE NULL) | ||
| 85 | * @name: name of the profile being manipulated (MAYBE NULL) | ||
| 86 | * @info: any extra info about the failure (MAYBE NULL) | ||
| 87 | * @e: buffer position info (NOT NULL) | ||
| 88 | * @error: error code | ||
| 89 | * | ||
| 90 | * Returns: %0 or error | ||
| 91 | */ | ||
| 92 | static int audit_iface(struct aa_profile *new, const char *name, | ||
| 93 | const char *info, struct aa_ext *e, int error) | ||
| 94 | { | ||
| 95 | struct aa_profile *profile = __aa_current_profile(); | ||
| 96 | struct common_audit_data sa; | ||
| 97 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 98 | sa.aad.iface.pos = e->pos - e->start; | ||
| 99 | sa.aad.iface.target = new; | ||
| 100 | sa.aad.name = name; | ||
| 101 | sa.aad.info = info; | ||
| 102 | sa.aad.error = error; | ||
| 103 | |||
| 104 | return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa, | ||
| 105 | audit_cb); | ||
| 106 | } | ||
| 107 | |||
| 108 | /* test if read will be in packed data bounds */ | ||
| 109 | static bool inbounds(struct aa_ext *e, size_t size) | ||
| 110 | { | ||
| 111 | return (size <= e->end - e->pos); | ||
| 112 | } | ||
| 113 | |||
| 114 | /** | ||
| 115 | * aa_u16_chunck - test and do bounds checking for a u16 size based chunk | ||
| 116 | * @e: serialized data read head (NOT NULL) | ||
| 117 | * @chunk: start address for chunk of data (NOT NULL) | ||
| 118 | * | ||
| 119 | * Returns: the size of chunk found with the read head at the end of the chunk. | ||
| 120 | */ | ||
| 121 | static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk) | ||
| 122 | { | ||
| 123 | size_t size = 0; | ||
| 124 | |||
| 125 | if (!inbounds(e, sizeof(u16))) | ||
| 126 | return 0; | ||
| 127 | size = le16_to_cpu(get_unaligned((u16 *) e->pos)); | ||
| 128 | e->pos += sizeof(u16); | ||
| 129 | if (!inbounds(e, size)) | ||
| 130 | return 0; | ||
| 131 | *chunk = e->pos; | ||
| 132 | e->pos += size; | ||
| 133 | return size; | ||
| 134 | } | ||
| 135 | |||
| 136 | /* unpack control byte */ | ||
| 137 | static bool unpack_X(struct aa_ext *e, enum aa_code code) | ||
| 138 | { | ||
| 139 | if (!inbounds(e, 1)) | ||
| 140 | return 0; | ||
| 141 | if (*(u8 *) e->pos != code) | ||
| 142 | return 0; | ||
| 143 | e->pos++; | ||
| 144 | return 1; | ||
| 145 | } | ||
| 146 | |||
| 147 | /** | ||
| 148 | * unpack_nameX - check is the next element is of type X with a name of @name | ||
| 149 | * @e: serialized data extent information (NOT NULL) | ||
| 150 | * @code: type code | ||
| 151 | * @name: name to match to the serialized element. (MAYBE NULL) | ||
| 152 | * | ||
| 153 | * check that the next serialized data element is of type X and has a tag | ||
| 154 | * name @name. If @name is specified then there must be a matching | ||
| 155 | * name element in the stream. If @name is NULL any name element will be | ||
| 156 | * skipped and only the typecode will be tested. | ||
| 157 | * | ||
| 158 | * Returns 1 on success (both type code and name tests match) and the read | ||
| 159 | * head is advanced past the headers | ||
| 160 | * | ||
| 161 | * Returns: 0 if either match fails, the read head does not move | ||
| 162 | */ | ||
| 163 | static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) | ||
| 164 | { | ||
| 165 | /* | ||
| 166 | * May need to reset pos if name or type doesn't match | ||
| 167 | */ | ||
| 168 | void *pos = e->pos; | ||
| 169 | /* | ||
| 170 | * Check for presence of a tagname, and if present name size | ||
| 171 | * AA_NAME tag value is a u16. | ||
| 172 | */ | ||
| 173 | if (unpack_X(e, AA_NAME)) { | ||
| 174 | char *tag = NULL; | ||
| 175 | size_t size = unpack_u16_chunk(e, &tag); | ||
| 176 | /* if a name is specified it must match. otherwise skip tag */ | ||
| 177 | if (name && (!size || strcmp(name, tag))) | ||
| 178 | goto fail; | ||
| 179 | } else if (name) { | ||
| 180 | /* if a name is specified and there is no name tag fail */ | ||
| 181 | goto fail; | ||
| 182 | } | ||
| 183 | |||
| 184 | /* now check if type code matches */ | ||
| 185 | if (unpack_X(e, code)) | ||
| 186 | return 1; | ||
| 187 | |||
| 188 | fail: | ||
| 189 | e->pos = pos; | ||
| 190 | return 0; | ||
| 191 | } | ||
| 192 | |||
| 193 | static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) | ||
| 194 | { | ||
| 195 | if (unpack_nameX(e, AA_U32, name)) { | ||
| 196 | if (!inbounds(e, sizeof(u32))) | ||
| 197 | return 0; | ||
| 198 | if (data) | ||
| 199 | *data = le32_to_cpu(get_unaligned((u32 *) e->pos)); | ||
| 200 | e->pos += sizeof(u32); | ||
| 201 | return 1; | ||
| 202 | } | ||
| 203 | return 0; | ||
| 204 | } | ||
| 205 | |||
| 206 | static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name) | ||
| 207 | { | ||
| 208 | if (unpack_nameX(e, AA_U64, name)) { | ||
| 209 | if (!inbounds(e, sizeof(u64))) | ||
| 210 | return 0; | ||
| 211 | if (data) | ||
| 212 | *data = le64_to_cpu(get_unaligned((u64 *) e->pos)); | ||
| 213 | e->pos += sizeof(u64); | ||
| 214 | return 1; | ||
| 215 | } | ||
| 216 | return 0; | ||
| 217 | } | ||
| 218 | |||
| 219 | static size_t unpack_array(struct aa_ext *e, const char *name) | ||
| 220 | { | ||
| 221 | if (unpack_nameX(e, AA_ARRAY, name)) { | ||
| 222 | int size; | ||
| 223 | if (!inbounds(e, sizeof(u16))) | ||
| 224 | return 0; | ||
| 225 | size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos)); | ||
| 226 | e->pos += sizeof(u16); | ||
| 227 | return size; | ||
| 228 | } | ||
| 229 | return 0; | ||
| 230 | } | ||
| 231 | |||
| 232 | static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name) | ||
| 233 | { | ||
| 234 | if (unpack_nameX(e, AA_BLOB, name)) { | ||
| 235 | u32 size; | ||
| 236 | if (!inbounds(e, sizeof(u32))) | ||
| 237 | return 0; | ||
| 238 | size = le32_to_cpu(get_unaligned((u32 *) e->pos)); | ||
| 239 | e->pos += sizeof(u32); | ||
| 240 | if (inbounds(e, (size_t) size)) { | ||
| 241 | *blob = e->pos; | ||
| 242 | e->pos += size; | ||
| 243 | return size; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | return 0; | ||
| 247 | } | ||
| 248 | |||
| 249 | static int unpack_str(struct aa_ext *e, const char **string, const char *name) | ||
| 250 | { | ||
| 251 | char *src_str; | ||
| 252 | size_t size = 0; | ||
| 253 | void *pos = e->pos; | ||
| 254 | *string = NULL; | ||
| 255 | if (unpack_nameX(e, AA_STRING, name)) { | ||
| 256 | size = unpack_u16_chunk(e, &src_str); | ||
| 257 | if (size) { | ||
| 258 | /* strings are null terminated, length is size - 1 */ | ||
| 259 | if (src_str[size - 1] != 0) | ||
| 260 | goto fail; | ||
| 261 | *string = src_str; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | return size; | ||
| 265 | |||
| 266 | fail: | ||
| 267 | e->pos = pos; | ||
| 268 | return 0; | ||
| 269 | } | ||
| 270 | |||
| 271 | static int unpack_strdup(struct aa_ext *e, char **string, const char *name) | ||
| 272 | { | ||
| 273 | const char *tmp; | ||
| 274 | void *pos = e->pos; | ||
| 275 | int res = unpack_str(e, &tmp, name); | ||
| 276 | *string = NULL; | ||
| 277 | |||
| 278 | if (!res) | ||
| 279 | return 0; | ||
| 280 | |||
| 281 | *string = kmemdup(tmp, res, GFP_KERNEL); | ||
| 282 | if (!*string) { | ||
| 283 | e->pos = pos; | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | |||
| 287 | return res; | ||
| 288 | } | ||
| 289 | |||
| 290 | /** | ||
| 291 | * verify_accept - verify the accept tables of a dfa | ||
| 292 | * @dfa: dfa to verify accept tables of (NOT NULL) | ||
| 293 | * @flags: flags governing dfa | ||
| 294 | * | ||
| 295 | * Returns: 1 if valid accept tables else 0 if error | ||
| 296 | */ | ||
| 297 | static bool verify_accept(struct aa_dfa *dfa, int flags) | ||
| 298 | { | ||
| 299 | int i; | ||
| 300 | |||
| 301 | /* verify accept permissions */ | ||
| 302 | for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { | ||
| 303 | int mode = ACCEPT_TABLE(dfa)[i]; | ||
| 304 | |||
| 305 | if (mode & ~DFA_VALID_PERM_MASK) | ||
| 306 | return 0; | ||
| 307 | |||
| 308 | if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK) | ||
| 309 | return 0; | ||
| 310 | } | ||
| 311 | return 1; | ||
| 312 | } | ||
| 313 | |||
| 314 | /** | ||
| 315 | * unpack_dfa - unpack a file rule dfa | ||
| 316 | * @e: serialized data extent information (NOT NULL) | ||
| 317 | * | ||
| 318 | * returns dfa or ERR_PTR or NULL if no dfa | ||
| 319 | */ | ||
| 320 | static struct aa_dfa *unpack_dfa(struct aa_ext *e) | ||
| 321 | { | ||
| 322 | char *blob = NULL; | ||
| 323 | size_t size; | ||
| 324 | struct aa_dfa *dfa = NULL; | ||
| 325 | |||
| 326 | size = unpack_blob(e, &blob, "aadfa"); | ||
| 327 | if (size) { | ||
| 328 | /* | ||
| 329 | * The dfa is aligned with in the blob to 8 bytes | ||
| 330 | * from the beginning of the stream. | ||
| 331 | */ | ||
| 332 | size_t sz = blob - (char *)e->start; | ||
| 333 | size_t pad = ALIGN(sz, 8) - sz; | ||
| 334 | int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | | ||
| 335 | TO_ACCEPT2_FLAG(YYTD_DATA32); | ||
| 336 | |||
| 337 | |||
| 338 | if (aa_g_paranoid_load) | ||
| 339 | flags |= DFA_FLAG_VERIFY_STATES; | ||
| 340 | |||
| 341 | dfa = aa_dfa_unpack(blob + pad, size - pad, flags); | ||
| 342 | |||
| 343 | if (IS_ERR(dfa)) | ||
| 344 | return dfa; | ||
| 345 | |||
| 346 | if (!verify_accept(dfa, flags)) | ||
| 347 | goto fail; | ||
| 348 | } | ||
| 349 | |||
| 350 | return dfa; | ||
| 351 | |||
| 352 | fail: | ||
| 353 | aa_put_dfa(dfa); | ||
| 354 | return ERR_PTR(-EPROTO); | ||
| 355 | } | ||
| 356 | |||
| 357 | /** | ||
| 358 | * unpack_trans_table - unpack a profile transition table | ||
| 359 | * @e: serialized data extent information (NOT NULL) | ||
| 360 | * @profile: profile to add the accept table to (NOT NULL) | ||
| 361 | * | ||
| 362 | * Returns: 1 if table succesfully unpacked | ||
| 363 | */ | ||
| 364 | static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) | ||
| 365 | { | ||
| 366 | void *pos = e->pos; | ||
| 367 | |||
| 368 | /* exec table is optional */ | ||
| 369 | if (unpack_nameX(e, AA_STRUCT, "xtable")) { | ||
| 370 | int i, size; | ||
| 371 | |||
| 372 | size = unpack_array(e, NULL); | ||
| 373 | /* currently 4 exec bits and entries 0-3 are reserved iupcx */ | ||
| 374 | if (size > 16 - 4) | ||
| 375 | goto fail; | ||
| 376 | profile->file.trans.table = kzalloc(sizeof(char *) * size, | ||
| 377 | GFP_KERNEL); | ||
| 378 | if (!profile->file.trans.table) | ||
| 379 | goto fail; | ||
| 380 | |||
| 381 | profile->file.trans.size = size; | ||
| 382 | for (i = 0; i < size; i++) { | ||
| 383 | char *str; | ||
| 384 | int c, j, size = unpack_strdup(e, &str, NULL); | ||
| 385 | /* unpack_strdup verifies that the last character is | ||
| 386 | * null termination byte. | ||
| 387 | */ | ||
| 388 | if (!size) | ||
| 389 | goto fail; | ||
| 390 | profile->file.trans.table[i] = str; | ||
| 391 | /* verify that name doesn't start with space */ | ||
| 392 | if (isspace(*str)) | ||
| 393 | goto fail; | ||
| 394 | |||
| 395 | /* count internal # of internal \0 */ | ||
| 396 | for (c = j = 0; j < size - 2; j++) { | ||
| 397 | if (!str[j]) | ||
| 398 | c++; | ||
| 399 | } | ||
| 400 | if (*str == ':') { | ||
| 401 | /* beginning with : requires an embedded \0, | ||
| 402 | * verify that exactly 1 internal \0 exists | ||
| 403 | * trailing \0 already verified by unpack_strdup | ||
| 404 | */ | ||
| 405 | if (c != 1) | ||
| 406 | goto fail; | ||
| 407 | /* first character after : must be valid */ | ||
| 408 | if (!str[1]) | ||
| 409 | goto fail; | ||
| 410 | } else if (c) | ||
| 411 | /* fail - all other cases with embedded \0 */ | ||
| 412 | goto fail; | ||
| 413 | } | ||
| 414 | if (!unpack_nameX(e, AA_ARRAYEND, NULL)) | ||
| 415 | goto fail; | ||
| 416 | if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | ||
| 417 | goto fail; | ||
| 418 | } | ||
| 419 | return 1; | ||
| 420 | |||
| 421 | fail: | ||
| 422 | aa_free_domain_entries(&profile->file.trans); | ||
| 423 | e->pos = pos; | ||
| 424 | return 0; | ||
| 425 | } | ||
| 426 | |||
| 427 | static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) | ||
| 428 | { | ||
| 429 | void *pos = e->pos; | ||
| 430 | |||
| 431 | /* rlimits are optional */ | ||
| 432 | if (unpack_nameX(e, AA_STRUCT, "rlimits")) { | ||
| 433 | int i, size; | ||
| 434 | u32 tmp = 0; | ||
| 435 | if (!unpack_u32(e, &tmp, NULL)) | ||
| 436 | goto fail; | ||
| 437 | profile->rlimits.mask = tmp; | ||
| 438 | |||
| 439 | size = unpack_array(e, NULL); | ||
| 440 | if (size > RLIM_NLIMITS) | ||
| 441 | goto fail; | ||
| 442 | for (i = 0; i < size; i++) { | ||
| 443 | u64 tmp = 0; | ||
| 444 | int a = aa_map_resource(i); | ||
| 445 | if (!unpack_u64(e, &tmp, NULL)) | ||
| 446 | goto fail; | ||
| 447 | profile->rlimits.limits[a].rlim_max = tmp; | ||
| 448 | } | ||
| 449 | if (!unpack_nameX(e, AA_ARRAYEND, NULL)) | ||
| 450 | goto fail; | ||
| 451 | if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | ||
| 452 | goto fail; | ||
| 453 | } | ||
| 454 | return 1; | ||
| 455 | |||
| 456 | fail: | ||
| 457 | e->pos = pos; | ||
| 458 | return 0; | ||
| 459 | } | ||
| 460 | |||
| 461 | /** | ||
| 462 | * unpack_profile - unpack a serialized profile | ||
| 463 | * @e: serialized data extent information (NOT NULL) | ||
| 464 | * | ||
| 465 | * NOTE: unpack profile sets audit struct if there is a failure | ||
| 466 | */ | ||
| 467 | static struct aa_profile *unpack_profile(struct aa_ext *e) | ||
| 468 | { | ||
| 469 | struct aa_profile *profile = NULL; | ||
| 470 | const char *name = NULL; | ||
| 471 | int error = -EPROTO; | ||
| 472 | kernel_cap_t tmpcap; | ||
| 473 | u32 tmp; | ||
| 474 | |||
| 475 | /* check that we have the right struct being passed */ | ||
| 476 | if (!unpack_nameX(e, AA_STRUCT, "profile")) | ||
| 477 | goto fail; | ||
| 478 | if (!unpack_str(e, &name, NULL)) | ||
| 479 | goto fail; | ||
| 480 | |||
| 481 | profile = aa_alloc_profile(name); | ||
| 482 | if (!profile) | ||
| 483 | return ERR_PTR(-ENOMEM); | ||
| 484 | |||
| 485 | /* profile renaming is optional */ | ||
| 486 | (void) unpack_str(e, &profile->rename, "rename"); | ||
| 487 | |||
| 488 | /* xmatch is optional and may be NULL */ | ||
| 489 | profile->xmatch = unpack_dfa(e); | ||
| 490 | if (IS_ERR(profile->xmatch)) { | ||
| 491 | error = PTR_ERR(profile->xmatch); | ||
| 492 | profile->xmatch = NULL; | ||
| 493 | goto fail; | ||
| 494 | } | ||
| 495 | /* xmatch_len is not optional if xmatch is set */ | ||
| 496 | if (profile->xmatch) { | ||
| 497 | if (!unpack_u32(e, &tmp, NULL)) | ||
| 498 | goto fail; | ||
| 499 | profile->xmatch_len = tmp; | ||
| 500 | } | ||
| 501 | |||
| 502 | /* per profile debug flags (complain, audit) */ | ||
| 503 | if (!unpack_nameX(e, AA_STRUCT, "flags")) | ||
| 504 | goto fail; | ||
| 505 | if (!unpack_u32(e, &tmp, NULL)) | ||
| 506 | goto fail; | ||
| 507 | if (tmp) | ||
| 508 | profile->flags |= PFLAG_HAT; | ||
| 509 | if (!unpack_u32(e, &tmp, NULL)) | ||
| 510 | goto fail; | ||
| 511 | if (tmp) | ||
| 512 | profile->mode = APPARMOR_COMPLAIN; | ||
| 513 | if (!unpack_u32(e, &tmp, NULL)) | ||
| 514 | goto fail; | ||
| 515 | if (tmp) | ||
| 516 | profile->audit = AUDIT_ALL; | ||
| 517 | |||
| 518 | if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | ||
| 519 | goto fail; | ||
| 520 | |||
| 521 | /* path_flags is optional */ | ||
| 522 | if (unpack_u32(e, &profile->path_flags, "path_flags")) | ||
| 523 | profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; | ||
| 524 | else | ||
| 525 | /* set a default value if path_flags field is not present */ | ||
| 526 | profile->path_flags = PFLAG_MEDIATE_DELETED; | ||
| 527 | |||
| 528 | if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) | ||
| 529 | goto fail; | ||
| 530 | if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL)) | ||
| 531 | goto fail; | ||
| 532 | if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL)) | ||
| 533 | goto fail; | ||
| 534 | if (!unpack_u32(e, &tmpcap.cap[0], NULL)) | ||
| 535 | goto fail; | ||
| 536 | |||
| 537 | if (unpack_nameX(e, AA_STRUCT, "caps64")) { | ||
| 538 | /* optional upper half of 64 bit caps */ | ||
| 539 | if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL)) | ||
| 540 | goto fail; | ||
| 541 | if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL)) | ||
| 542 | goto fail; | ||
| 543 | if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL)) | ||
| 544 | goto fail; | ||
| 545 | if (!unpack_u32(e, &(tmpcap.cap[1]), NULL)) | ||
| 546 | goto fail; | ||
| 547 | if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | ||
| 548 | goto fail; | ||
| 549 | } | ||
| 550 | |||
| 551 | if (unpack_nameX(e, AA_STRUCT, "capsx")) { | ||
| 552 | /* optional extended caps mediation mask */ | ||
| 553 | if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL)) | ||
| 554 | goto fail; | ||
| 555 | if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) | ||
| 556 | goto fail; | ||
| 557 | } | ||
| 558 | |||
| 559 | if (!unpack_rlimits(e, profile)) | ||
| 560 | goto fail; | ||
| 561 | |||
| 562 | /* get file rules */ | ||
| 563 | profile->file.dfa = unpack_dfa(e); | ||
| 564 | if (IS_ERR(profile->file.dfa)) { | ||
| 565 | error = PTR_ERR(profile->file.dfa); | ||
| 566 | profile->file.dfa = NULL; | ||
| 567 | goto fail; | ||
| 568 | } | ||
| 569 | |||
| 570 | if (!unpack_u32(e, &profile->file.start, "dfa_start")) | ||
| 571 | /* default start state */ | ||
| 572 | profile->file.start = DFA_START; | ||
| 573 | |||
| 574 | if (!unpack_trans_table(e, profile)) | ||
| 575 | goto fail; | ||
| 576 | |||
| 577 | if (!unpack_nameX(e, AA_STRUCTEND, NULL)) | ||
| 578 | goto fail; | ||
| 579 | |||
| 580 | return profile; | ||
| 581 | |||
| 582 | fail: | ||
| 583 | if (profile) | ||
| 584 | name = NULL; | ||
| 585 | else if (!name) | ||
| 586 | name = "unknown"; | ||
| 587 | audit_iface(profile, name, "failed to unpack profile", e, error); | ||
| 588 | aa_put_profile(profile); | ||
| 589 | |||
| 590 | return ERR_PTR(error); | ||
| 591 | } | ||
| 592 | |||
| 593 | /** | ||
| 594 | * verify_head - unpack serialized stream header | ||
| 595 | * @e: serialized data read head (NOT NULL) | ||
| 596 | * @ns: Returns - namespace if one is specified else NULL (NOT NULL) | ||
| 597 | * | ||
| 598 | * Returns: error or 0 if header is good | ||
| 599 | */ | ||
| 600 | static int verify_header(struct aa_ext *e, const char **ns) | ||
| 601 | { | ||
| 602 | int error = -EPROTONOSUPPORT; | ||
| 603 | /* get the interface version */ | ||
| 604 | if (!unpack_u32(e, &e->version, "version")) { | ||
| 605 | audit_iface(NULL, NULL, "invalid profile format", e, error); | ||
| 606 | return error; | ||
| 607 | } | ||
| 608 | |||
| 609 | /* check that the interface version is currently supported */ | ||
| 610 | if (e->version != 5) { | ||
| 611 | audit_iface(NULL, NULL, "unsupported interface version", e, | ||
| 612 | error); | ||
| 613 | return error; | ||
| 614 | } | ||
| 615 | |||
| 616 | /* read the namespace if present */ | ||
| 617 | if (!unpack_str(e, ns, "namespace")) | ||
| 618 | *ns = NULL; | ||
| 619 | |||
| 620 | return 0; | ||
| 621 | } | ||
| 622 | |||
| 623 | static bool verify_xindex(int xindex, int table_size) | ||
| 624 | { | ||
| 625 | int index, xtype; | ||
| 626 | xtype = xindex & AA_X_TYPE_MASK; | ||
| 627 | index = xindex & AA_X_INDEX_MASK; | ||
| 628 | if (xtype == AA_X_TABLE && index > table_size) | ||
| 629 | return 0; | ||
| 630 | return 1; | ||
| 631 | } | ||
| 632 | |||
| 633 | /* verify dfa xindexes are in range of transition tables */ | ||
| 634 | static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) | ||
| 635 | { | ||
| 636 | int i; | ||
| 637 | for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { | ||
| 638 | if (!verify_xindex(dfa_user_xindex(dfa, i), table_size)) | ||
| 639 | return 0; | ||
| 640 | if (!verify_xindex(dfa_other_xindex(dfa, i), table_size)) | ||
| 641 | return 0; | ||
| 642 | } | ||
| 643 | return 1; | ||
| 644 | } | ||
| 645 | |||
| 646 | /** | ||
| 647 | * verify_profile - Do post unpack analysis to verify profile consistency | ||
| 648 | * @profile: profile to verify (NOT NULL) | ||
| 649 | * | ||
| 650 | * Returns: 0 if passes verification else error | ||
| 651 | */ | ||
| 652 | static int verify_profile(struct aa_profile *profile) | ||
| 653 | { | ||
| 654 | if (aa_g_paranoid_load) { | ||
| 655 | if (profile->file.dfa && | ||
| 656 | !verify_dfa_xindex(profile->file.dfa, | ||
| 657 | profile->file.trans.size)) { | ||
| 658 | audit_iface(profile, NULL, "Invalid named transition", | ||
| 659 | NULL, -EPROTO); | ||
| 660 | return -EPROTO; | ||
| 661 | } | ||
| 662 | } | ||
| 663 | |||
| 664 | return 0; | ||
| 665 | } | ||
| 666 | |||
| 667 | /** | ||
| 668 | * aa_unpack - unpack packed binary profile data loaded from user space | ||
| 669 | * @udata: user data copied to kmem (NOT NULL) | ||
| 670 | * @size: the size of the user data | ||
| 671 | * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) | ||
| 672 | * | ||
| 673 | * Unpack user data and return refcounted allocated profile or ERR_PTR | ||
| 674 | * | ||
| 675 | * Returns: profile else error pointer if fails to unpack | ||
| 676 | */ | ||
| 677 | struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns) | ||
| 678 | { | ||
| 679 | struct aa_profile *profile = NULL; | ||
| 680 | int error; | ||
| 681 | struct aa_ext e = { | ||
| 682 | .start = udata, | ||
| 683 | .end = udata + size, | ||
| 684 | .pos = udata, | ||
| 685 | }; | ||
| 686 | |||
| 687 | error = verify_header(&e, ns); | ||
| 688 | if (error) | ||
| 689 | return ERR_PTR(error); | ||
| 690 | |||
| 691 | profile = unpack_profile(&e); | ||
| 692 | if (IS_ERR(profile)) | ||
| 693 | return profile; | ||
| 694 | |||
| 695 | error = verify_profile(profile); | ||
| 696 | if (error) { | ||
| 697 | aa_put_profile(profile); | ||
| 698 | profile = ERR_PTR(error); | ||
| 699 | } | ||
| 700 | |||
| 701 | /* return refcount */ | ||
| 702 | return profile; | ||
| 703 | } | ||
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c new file mode 100644 index 000000000000..04a2cf8d1b65 --- /dev/null +++ b/security/apparmor/procattr.c | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor /proc/<pid>/attr/ 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 | |||
| 15 | #include "include/apparmor.h" | ||
| 16 | #include "include/context.h" | ||
| 17 | #include "include/policy.h" | ||
| 18 | #include "include/domain.h" | ||
| 19 | |||
| 20 | |||
| 21 | /** | ||
| 22 | * aa_getprocattr - Return the profile information for @profile | ||
| 23 | * @profile: the profile to print profile info about (NOT NULL) | ||
| 24 | * @string: Returns - string containing the profile info (NOT NULL) | ||
| 25 | * | ||
| 26 | * Returns: length of @string on success else error on failure | ||
| 27 | * | ||
| 28 | * Requires: profile != NULL | ||
| 29 | * | ||
| 30 | * Creates a string containing the namespace_name://profile_name for | ||
| 31 | * @profile. | ||
| 32 | * | ||
| 33 | * Returns: size of string placed in @string else error code on failure | ||
| 34 | */ | ||
| 35 | int aa_getprocattr(struct aa_profile *profile, char **string) | ||
| 36 | { | ||
| 37 | char *str; | ||
| 38 | int len = 0, mode_len = 0, ns_len = 0, name_len; | ||
| 39 | const char *mode_str = profile_mode_names[profile->mode]; | ||
| 40 | const char *ns_name = NULL; | ||
| 41 | struct aa_namespace *ns = profile->ns; | ||
| 42 | struct aa_namespace *current_ns = __aa_current_profile()->ns; | ||
| 43 | char *s; | ||
| 44 | |||
| 45 | if (!aa_ns_visible(current_ns, ns)) | ||
| 46 | return -EACCES; | ||
| 47 | |||
| 48 | ns_name = aa_ns_name(current_ns, ns); | ||
| 49 | ns_len = strlen(ns_name); | ||
| 50 | |||
| 51 | /* if the visible ns_name is > 0 increase size for : :// seperator */ | ||
| 52 | if (ns_len) | ||
| 53 | ns_len += 4; | ||
| 54 | |||
| 55 | /* unconfined profiles don't have a mode string appended */ | ||
| 56 | if (!unconfined(profile)) | ||
| 57 | mode_len = strlen(mode_str) + 3; /* + 3 for _() */ | ||
| 58 | |||
| 59 | name_len = strlen(profile->base.hname); | ||
| 60 | len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ | ||
| 61 | s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ | ||
| 62 | if (!str) | ||
| 63 | return -ENOMEM; | ||
| 64 | |||
| 65 | if (ns_len) { | ||
| 66 | /* skip over prefix current_ns->base.hname and separating // */ | ||
| 67 | sprintf(s, ":%s://", ns_name); | ||
| 68 | s += ns_len; | ||
| 69 | } | ||
| 70 | if (unconfined(profile)) | ||
| 71 | /* mode string not being appended */ | ||
| 72 | sprintf(s, "%s\n", profile->base.hname); | ||
| 73 | else | ||
| 74 | sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); | ||
| 75 | *string = str; | ||
| 76 | |||
| 77 | /* NOTE: len does not include \0 of string, not saved as part of file */ | ||
| 78 | return len; | ||
| 79 | } | ||
| 80 | |||
| 81 | /** | ||
| 82 | * split_token_from_name - separate a string of form <token>^<name> | ||
| 83 | * @op: operation being checked | ||
| 84 | * @args: string to parse (NOT NULL) | ||
| 85 | * @token: stores returned parsed token value (NOT NULL) | ||
| 86 | * | ||
| 87 | * Returns: start position of name after token else NULL on failure | ||
| 88 | */ | ||
| 89 | static char *split_token_from_name(int op, char *args, u64 * token) | ||
| 90 | { | ||
| 91 | char *name; | ||
| 92 | |||
| 93 | *token = simple_strtoull(args, &name, 16); | ||
| 94 | if ((name == args) || *name != '^') { | ||
| 95 | AA_ERROR("%s: Invalid input '%s'", op_table[op], args); | ||
| 96 | return ERR_PTR(-EINVAL); | ||
| 97 | } | ||
| 98 | |||
| 99 | name++; /* skip ^ */ | ||
| 100 | if (!*name) | ||
| 101 | name = NULL; | ||
| 102 | return name; | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * aa_setprocattr_chagnehat - handle procattr interface to change_hat | ||
| 107 | * @args: args received from writing to /proc/<pid>/attr/current (NOT NULL) | ||
| 108 | * @size: size of the args | ||
| 109 | * @test: true if this is a test of change_hat permissions | ||
| 110 | * | ||
| 111 | * Returns: %0 or error code if change_hat fails | ||
| 112 | */ | ||
| 113 | int aa_setprocattr_changehat(char *args, size_t size, int test) | ||
| 114 | { | ||
| 115 | char *hat; | ||
| 116 | u64 token; | ||
| 117 | const char *hats[16]; /* current hard limit on # of names */ | ||
| 118 | int count = 0; | ||
| 119 | |||
| 120 | hat = split_token_from_name(OP_CHANGE_HAT, args, &token); | ||
| 121 | if (IS_ERR(hat)) | ||
| 122 | return PTR_ERR(hat); | ||
| 123 | |||
| 124 | if (!hat && !token) { | ||
| 125 | AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic"); | ||
| 126 | return -EINVAL; | ||
| 127 | } | ||
| 128 | |||
| 129 | if (hat) { | ||
| 130 | /* set up hat name vector, args guaranteed null terminated | ||
| 131 | * at args[size] by setprocattr. | ||
| 132 | * | ||
| 133 | * If there are multiple hat names in the buffer each is | ||
| 134 | * separated by a \0. Ie. userspace writes them pre tokenized | ||
| 135 | */ | ||
| 136 | char *end = args + size; | ||
| 137 | for (count = 0; (hat < end) && count < 16; ++count) { | ||
| 138 | char *next = hat + strlen(hat) + 1; | ||
| 139 | hats[count] = hat; | ||
| 140 | hat = next; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", | ||
| 145 | __func__, token, hat ? hat : NULL); | ||
| 146 | |||
| 147 | return aa_change_hat(hats, count, token, test); | ||
| 148 | } | ||
| 149 | |||
| 150 | /** | ||
| 151 | * aa_setprocattr_changeprofile - handle procattr interface to changeprofile | ||
| 152 | * @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL) | ||
| 153 | * @onexec: true if change_profile should be delayed until exec | ||
| 154 | * @test: true if this is a test of change_profile permissions | ||
| 155 | * | ||
| 156 | * Returns: %0 or error code if change_profile fails | ||
| 157 | */ | ||
| 158 | int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test) | ||
| 159 | { | ||
| 160 | char *name, *ns_name; | ||
| 161 | |||
| 162 | name = aa_split_fqname(fqname, &ns_name); | ||
| 163 | return aa_change_profile(ns_name, name, onexec, test); | ||
| 164 | } | ||
| 165 | |||
| 166 | int aa_setprocattr_permipc(char *fqname) | ||
| 167 | { | ||
| 168 | /* TODO: add ipc permission querying */ | ||
| 169 | return -ENOTSUPP; | ||
| 170 | } | ||
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c new file mode 100644 index 000000000000..4a368f1fd36d --- /dev/null +++ b/security/apparmor/resource.c | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor resource mediation and attachment | ||
| 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 | |||
| 15 | #include <linux/audit.h> | ||
| 16 | |||
| 17 | #include "include/audit.h" | ||
| 18 | #include "include/resource.h" | ||
| 19 | #include "include/policy.h" | ||
| 20 | |||
| 21 | /* | ||
| 22 | * Table of rlimit names: we generate it from resource.h. | ||
| 23 | */ | ||
| 24 | #include "rlim_names.h" | ||
| 25 | |||
| 26 | /* audit callback for resource specific fields */ | ||
| 27 | static void audit_cb(struct audit_buffer *ab, void *va) | ||
| 28 | { | ||
| 29 | struct common_audit_data *sa = va; | ||
| 30 | |||
| 31 | audit_log_format(ab, " rlimit=%s value=%lu", | ||
| 32 | rlim_names[sa->aad.rlim.rlim], sa->aad.rlim.max); | ||
| 33 | } | ||
| 34 | |||
| 35 | /** | ||
| 36 | * audit_resource - audit setting resource limit | ||
| 37 | * @profile: profile being enforced (NOT NULL) | ||
| 38 | * @resoure: rlimit being auditing | ||
| 39 | * @value: value being set | ||
| 40 | * @error: error value | ||
| 41 | * | ||
| 42 | * Returns: 0 or sa->error else other error code on failure | ||
| 43 | */ | ||
| 44 | static int audit_resource(struct aa_profile *profile, unsigned int resource, | ||
| 45 | unsigned long value, int error) | ||
| 46 | { | ||
| 47 | struct common_audit_data sa; | ||
| 48 | |||
| 49 | COMMON_AUDIT_DATA_INIT(&sa, NONE); | ||
| 50 | sa.aad.op = OP_SETRLIMIT, | ||
| 51 | sa.aad.rlim.rlim = resource; | ||
| 52 | sa.aad.rlim.max = value; | ||
| 53 | sa.aad.error = error; | ||
| 54 | return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa, | ||
| 55 | audit_cb); | ||
| 56 | } | ||
| 57 | |||
| 58 | /** | ||
| 59 | * aa_map_resouce - map compiled policy resource to internal # | ||
| 60 | * @resource: flattened policy resource number | ||
| 61 | * | ||
| 62 | * Returns: resource # for the current architecture. | ||
| 63 | * | ||
| 64 | * rlimit resource can vary based on architecture, map the compiled policy | ||
| 65 | * resource # to the internal representation for the architecture. | ||
| 66 | */ | ||
| 67 | int aa_map_resource(int resource) | ||
| 68 | { | ||
| 69 | return rlim_map[resource]; | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * aa_task_setrlimit - test permission to set an rlimit | ||
| 74 | * @profile - profile confining the task (NOT NULL) | ||
| 75 | * @resource - the resource being set | ||
| 76 | * @new_rlim - the new resource limit (NOT NULL) | ||
| 77 | * | ||
| 78 | * Control raising the processes hard limit. | ||
| 79 | * | ||
| 80 | * Returns: 0 or error code if setting resource failed | ||
| 81 | */ | ||
| 82 | int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, | ||
| 83 | struct rlimit *new_rlim) | ||
| 84 | { | ||
| 85 | int error = 0; | ||
| 86 | |||
| 87 | if (profile->rlimits.mask & (1 << resource) && | ||
| 88 | new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max) | ||
| 89 | |||
| 90 | error = audit_resource(profile, resource, new_rlim->rlim_max, | ||
| 91 | -EACCES); | ||
| 92 | |||
| 93 | return error; | ||
| 94 | } | ||
| 95 | |||
| 96 | /** | ||
| 97 | * __aa_transition_rlimits - apply new profile rlimits | ||
| 98 | * @old: old profile on task (NOT NULL) | ||
| 99 | * @new: new profile with rlimits to apply (NOT NULL) | ||
| 100 | */ | ||
| 101 | void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) | ||
| 102 | { | ||
| 103 | unsigned int mask = 0; | ||
| 104 | struct rlimit *rlim, *initrlim; | ||
| 105 | int i; | ||
| 106 | |||
| 107 | /* for any rlimits the profile controlled reset the soft limit | ||
| 108 | * to the less of the tasks hard limit and the init tasks soft limit | ||
| 109 | */ | ||
| 110 | if (old->rlimits.mask) { | ||
| 111 | for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { | ||
| 112 | if (old->rlimits.mask & mask) { | ||
| 113 | rlim = current->signal->rlim + i; | ||
| 114 | initrlim = init_task.signal->rlim + i; | ||
| 115 | rlim->rlim_cur = min(rlim->rlim_max, | ||
| 116 | initrlim->rlim_cur); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | } | ||
| 120 | |||
| 121 | /* set any new hard limits as dictated by the new profile */ | ||
| 122 | if (!new->rlimits.mask) | ||
| 123 | return; | ||
| 124 | for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { | ||
| 125 | if (!(new->rlimits.mask & mask)) | ||
| 126 | continue; | ||
| 127 | |||
| 128 | rlim = current->signal->rlim + i; | ||
| 129 | rlim->rlim_max = min(rlim->rlim_max, | ||
| 130 | new->rlimits.limits[i].rlim_max); | ||
| 131 | /* soft limit should not exceed hard limit */ | ||
| 132 | rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c new file mode 100644 index 000000000000..f0b34f76ebef --- /dev/null +++ b/security/apparmor/sid.c | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | /* | ||
| 2 | * AppArmor security module | ||
| 3 | * | ||
| 4 | * This file contains AppArmor security identifier (sid) manipulation fns | ||
| 5 | * | ||
| 6 | * Copyright 2009-2010 Canonical Ltd. | ||
| 7 | * | ||
| 8 | * This program is free software; you can redistribute it and/or | ||
| 9 | * modify it under the terms of the GNU General Public License as | ||
| 10 | * published by the Free Software Foundation, version 2 of the | ||
| 11 | * License. | ||
| 12 | * | ||
| 13 | * | ||
| 14 | * AppArmor allocates a unique sid for every profile loaded. If a profile | ||
| 15 | * is replaced it receives the sid of the profile it is replacing. | ||
| 16 | * | ||
| 17 | * The sid value of 0 is invalid. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/spinlock.h> | ||
| 21 | #include <linux/errno.h> | ||
| 22 | #include <linux/err.h> | ||
| 23 | |||
| 24 | #include "include/sid.h" | ||
| 25 | |||
| 26 | /* global counter from which sids are allocated */ | ||
| 27 | static u32 global_sid; | ||
| 28 | static DEFINE_SPINLOCK(sid_lock); | ||
| 29 | |||
| 30 | /* TODO FIXME: add sid to profile mapping, and sid recycling */ | ||
| 31 | |||
| 32 | /** | ||
| 33 | * aa_alloc_sid - allocate a new sid for a profile | ||
| 34 | */ | ||
| 35 | u32 aa_alloc_sid(void) | ||
| 36 | { | ||
| 37 | u32 sid; | ||
| 38 | |||
| 39 | /* | ||
| 40 | * TODO FIXME: sid recycling - part of profile mapping table | ||
| 41 | */ | ||
| 42 | spin_lock(&sid_lock); | ||
| 43 | sid = (++global_sid); | ||
| 44 | spin_unlock(&sid_lock); | ||
| 45 | return sid; | ||
| 46 | } | ||
| 47 | |||
| 48 | /** | ||
| 49 | * aa_free_sid - free a sid | ||
| 50 | * @sid: sid to free | ||
| 51 | */ | ||
| 52 | void aa_free_sid(u32 sid) | ||
| 53 | { | ||
| 54 | ; /* NOP ATM */ | ||
| 55 | } | ||
