diff --git a/kernel-patches/for-mainline/apparmor-lsm.diff b/kernel-patches/for-mainline/apparmor-lsm.diff index 3195e26a8..6060fc7da 100644 --- a/kernel-patches/for-mainline/apparmor-lsm.diff +++ b/kernel-patches/for-mainline/apparmor-lsm.diff @@ -7,12 +7,12 @@ Signed-off-by: John Johansen Signed-off-by: Andreas Gruenbacher --- - security/apparmor/lsm.c | 841 ++++++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 841 insertions(+) + security/apparmor/lsm.c | 879 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 879 insertions(+) --- /dev/null +++ b/security/apparmor/lsm.c -@@ -0,0 +1,841 @@ +@@ -0,0 +1,879 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -37,6 +37,9 @@ Signed-off-by: Andreas Gruenbacher +#include "apparmor.h" +#include "inline.h" + ++/* Flag indicating whether initialization completed */ ++int apparmor_initialized = 0; ++ +static int param_set_aabool(const char *val, struct kernel_param *kp); +static int param_get_aabool(char *buffer, struct kernel_param *kp); +#define param_check_aabool(name, p) __param_check(name, p, int) @@ -78,6 +81,25 @@ Signed-off-by: Andreas Gruenbacher +module_param_named(path_max, apparmor_path_max, aauint, S_IRUSR | S_IWUSR); +MODULE_PARM_DESC(apparmor_path_max, "Maximum pathname length allowed"); + ++/* Boot time disable flag */ ++#ifdef CONFIG_SECURITY_APPARMOR_DISABLE ++#define AA_ENABLED_PERMS 0600 ++#else ++#define AA_ENABLED_PERMS 0400 ++#endif ++static int param_set_aa_enabled(const char *val, struct kernel_param *kp); ++unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; ++module_param_call(enabled, param_set_aa_enabled, param_get_aauint, ++ &apparmor_enabled, AA_ENABLED_PERMS); ++MODULE_PARM_DESC(apparmor_enabled, "Enable/Disable Apparmor on boot"); ++ ++static int __init apparmor_enabled_setup(char *str) ++{ ++ apparmor_enabled = simple_strtol(str, NULL, 0); ++ return 1; ++} ++__setup("apparmor=", apparmor_enabled_setup); ++ +static int param_set_aabool(const char *val, struct kernel_param *kp) +{ + if (aa_task_context(current)) @@ -106,6 +128,35 @@ Signed-off-by: Andreas Gruenbacher + return param_get_uint(buffer, kp); +} + ++/* allow run time disabling of apparmor */ ++static int param_set_aa_enabled(const char *val, struct kernel_param *kp) ++{ ++ char *endp; ++ unsigned long l; ++ ++ if (!apparmor_initialized) { ++ apparmor_enabled = 0; ++ return 0; ++ } ++ ++ if (aa_task_context(current)) ++ return -EPERM; ++ ++ if (!apparmor_enabled) ++ return -EINVAL; ++ ++ if (!val) ++ return -EINVAL; ++ ++ l = simple_strtoul(val, &endp, 0); ++ if (endp == val || l != 0) ++ return -EINVAL; ++ ++ apparmor_enabled = 0; ++ apparmor_disable(); ++ return 0; ++} ++ +static int aa_reject_syscall(struct task_struct *task, gfp_t flags, + const char *name) +{ @@ -173,19 +224,16 @@ Signed-off-by: Andreas Gruenbacher +static int apparmor_capable(struct task_struct *task, int cap) +{ + int error; ++ struct aa_task_context *cxt; + + /* cap_capable returns 0 on success, else -EPERM */ + error = cap_capable(task, cap); + -+ if (!error) { -+ struct aa_task_context *cxt; -+ -+ rcu_read_lock(); -+ cxt = aa_task_context(task); -+ if (cxt) -+ error = aa_capability(cxt, cap); -+ rcu_read_unlock(); -+ } ++ rcu_read_lock(); ++ cxt = aa_task_context(task); ++ if (cxt && (!error || cap_raised(cxt->profile->set_caps, cap))) ++ error = aa_capability(cxt, cap); ++ rcu_read_unlock(); + + return error; +} @@ -213,12 +261,13 @@ Signed-off-by: Andreas Gruenbacher + if (name && name - buffer >= 5) { + name -= 5; + memcpy(name, "/proc", 5); -+ error = aa_perm_path(profile, "sysctl", name, mask); ++ error = aa_perm_path(profile, "sysctl", name, mask, 0); + } + free_page((unsigned long)buffer); + } + +out: ++ aa_put_profile(profile); + return error; +} + @@ -328,8 +377,7 @@ Signed-off-by: Andreas Gruenbacher +static int apparmor_inode_create(struct inode *dir, struct dentry *dentry, + struct vfsmount *mnt, int mask) +{ -+ /* FIXME: may move to MAY_APPEND later */ -+ return aa_permission("inode_create", dir, dentry, mnt, MAY_WRITE, 0); ++ return aa_permission("inode_create", dir, dentry, mnt, MAY_APPEND, 0); +} + +static int apparmor_inode_link(struct dentry *old_dentry, @@ -760,35 +808,43 @@ Signed-off-by: Andreas Gruenbacher + .setprocattr = apparmor_setprocattr, +}; + -+static void info_message(const char *str) ++void info_message(const char *str) +{ + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.gfp_mask = GFP_KERNEL; + sa.info = str; -+ printk(KERN_INFO "AppArmor: %s", str); -+ aa_audit_message(NULL, &sa, AUDIT_APPARMOR_STATUS); ++ printk(KERN_INFO "AppArmor: %s\n", str); ++ if (audit_enabled) ++ aa_audit_message(NULL, &sa, AUDIT_APPARMOR_STATUS); +} + +static int __init apparmor_init(void) +{ + int error; + ++ if (!apparmor_enabled) { ++ info_message("AppArmor disabled by boottime parameter\n"); ++ return 0; ++ } ++ + if ((error = create_apparmorfs())) { + AA_ERROR("Unable to activate AppArmor filesystem\n"); + goto createfs_out; + } + -+ if ((error = alloc_null_complain_profile())){ -+ AA_ERROR("Unable to allocate null complain profile\n"); ++ if ((error = alloc_default_namespace())){ ++ AA_ERROR("Unable to allocate default profile namespace\n"); + goto alloc_out; + } + -+ if ((error = register_security(&apparmor_ops))) { ++ if ((error = register_security(&apparmor_ops))) { + AA_ERROR("Unable to register AppArmor\n"); + goto register_security_out; + } + ++ /* Report that AppArmor successfully initialized */ ++ apparmor_initialized = 1; + if (apparmor_complain) + info_message("AppArmor initialized: complainmode enabled"); + else @@ -797,40 +853,26 @@ Signed-off-by: Andreas Gruenbacher + return error; + +register_security_out: -+ free_null_complain_profile(); ++ free_default_namespace(); + +alloc_out: -+ destroy_apparmorfs(); ++ destroy_apparmorfs(); + +createfs_out: + return error; + +} + -+static void __exit apparmor_exit(void) ++security_initcall(apparmor_init); ++ ++void apparmor_disable(void) +{ + /* Remove and release all the profiles on the profile list. */ + mutex_lock(&aa_interface_lock); -+ write_lock(&profile_list_lock); -+ while (!list_empty(&profile_list)) { -+ struct aa_profile *profile = -+ list_entry(profile_list.next, struct aa_profile, list); -+ -+ /* Remove the profile from each task context it is on. */ -+ lock_profile(profile); -+ profile->isstale = 1; -+ aa_unconfine_tasks(profile); -+ unlock_profile(profile); -+ -+ /* Release the profile itself. */ -+ list_del_init(&profile->list); -+ aa_put_profile(profile); -+ } -+ write_unlock(&profile_list_lock); ++ aa_profile_ns_list_release(); + + /* FIXME: cleanup profiles references on files */ -+ -+ free_null_complain_profile(); ++ free_default_namespace(); + + /* + * Delay for an rcu cycle to make sure that all active task @@ -842,15 +884,11 @@ Signed-off-by: Andreas Gruenbacher + destroy_apparmorfs(); + mutex_unlock(&aa_interface_lock); + -+ if (unregister_security(&apparmor_ops)) -+ info_message("Unable to properly unregister AppArmor"); ++ apparmor_initialized = 0; + + info_message("AppArmor protection removed"); +} + -+module_init(apparmor_init); -+module_exit(apparmor_exit); -+ +MODULE_DESCRIPTION("AppArmor process confinement"); +MODULE_AUTHOR("Novell/Immunix, http://bugs.opensuse.org"); +MODULE_LICENSE("GPL"); diff --git a/kernel-patches/for-mainline/apparmor-main.diff b/kernel-patches/for-mainline/apparmor-main.diff index 280258296..4e64e6b40 100644 --- a/kernel-patches/for-mainline/apparmor-main.diff +++ b/kernel-patches/for-mainline/apparmor-main.diff @@ -7,12 +7,12 @@ Signed-off-by: John Johansen Signed-off-by: Andreas Gruenbacher --- - security/apparmor/main.c | 1251 +++++++++++++++++++++++++++++++++++++++++++++++ - 1 file changed, 1251 insertions(+) + security/apparmor/main.c | 1421 +++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 1421 insertions(+) --- /dev/null +++ b/security/apparmor/main.c -@@ -0,0 +1,1251 @@ +@@ -0,0 +1,1421 @@ +/* + * Copyright (C) 2002-2007 Novell/SUSE + * @@ -41,31 +41,317 @@ Signed-off-by: Andreas Gruenbacher +#include "capability_names.h" +}; + -+/* NULL complain profile -+ * -+ * Used when in complain mode, to emit Permitting messages for non-existant -+ * profiles and hats. This is necessary because of selective mode, in which -+ * case we need a complain null_profile and enforce null_profile -+ * -+ * The null_complain_profile cannot be statically allocated, because it -+ * can be associated to files which keep their reference even if apparmor is -+ * unloaded ++struct aa_namespace *default_namespace; ++ ++static int aa_inode_mode(struct inode *inode) ++{ ++ /* if the inode doesn't exist the user is creating it */ ++ if (!inode || current->fsuid == inode->i_uid) ++ return AA_USER_SHIFT; ++ return AA_OTHER_SHIFT; ++} ++ ++int alloc_default_namespace(void) ++{ ++ struct aa_namespace *ns; ++ char *name = kstrdup("default", GFP_KERNEL); ++ if (!name) ++ return -ENOMEM; ++ ns = alloc_aa_namespace(name); ++ if (!ns) { ++ kfree(name); ++ return -ENOMEM; ++ } ++ ++ write_lock(&profile_ns_list_lock); ++ default_namespace = ns; ++ aa_get_namespace(ns); ++ list_add(&ns->list, &profile_ns_list); ++ write_unlock(&profile_ns_list_lock); ++ ++ return 0; ++} ++ ++void free_default_namespace(void) ++{ ++ write_lock(&profile_ns_list_lock); ++ list_del_init(&default_namespace->list); ++ write_unlock(&profile_ns_list_lock); ++ aa_put_namespace(default_namespace); ++ default_namespace = NULL; ++} ++ ++static void aa_audit_file_sub_mask(struct audit_buffer *ab, char *buffer, ++ int mask) ++{ ++ char *m = buffer; ++ ++ if (mask & AA_EXEC_MMAP) ++ *m++ = 'm'; ++ if (mask & MAY_READ) ++ *m++ = 'r'; ++ if (mask & MAY_WRITE) ++ *m++ = 'w'; ++ else if (mask & MAY_APPEND) ++ *m++ = 'a'; ++ if (mask & MAY_EXEC) { ++ if (mask & AA_EXEC_UNSAFE) { ++ switch(mask & AA_EXEC_MODIFIERS) { ++ case AA_EXEC_UNCONFINED: ++ *m++ = 'u'; ++ break; ++ case AA_EXEC_PIX: ++ *m++ = 'p'; ++ /* fall through */ ++ case AA_EXEC_INHERIT: ++ *m++ = 'i'; ++ break; ++ case AA_EXEC_PROFILE: ++ *m++ = 'p'; ++ break; ++ } ++ } else { ++ switch(mask & AA_EXEC_MODIFIERS) { ++ case AA_EXEC_UNCONFINED: ++ *m++ = 'U'; ++ break; ++ case AA_EXEC_PIX: ++ *m++ = 'P'; ++ /* fall through */ ++ case AA_EXEC_INHERIT: ++ *m++ = 'I'; ++ break; ++ case AA_EXEC_PROFILE: ++ *m++ = 'P'; ++ break; ++ } ++ } ++ *m++ = 'x'; ++ } ++ if (mask & AA_MAY_LINK) ++ *m++ = 'l'; ++ if (mask & AA_MAY_LOCK) ++ *m++ = 'k'; ++ *m++ = '\0'; ++} ++ ++static void aa_audit_file_mask(struct audit_buffer *ab, const char *name, ++ int mask) ++{ ++ char user[10], other[10]; ++ ++ aa_audit_file_sub_mask(ab, user, ++ (mask & AA_USER_PERMS) >> AA_USER_SHIFT); ++ aa_audit_file_sub_mask(ab, other, ++ (mask & AA_OTHER_PERMS) >> AA_OTHER_SHIFT); ++ ++ audit_log_format(ab, " %s=\"%s::%s\"", name, user, other); ++} ++ ++/** ++ * aa_audit - Log an audit event to the audit subsystem ++ * @profile: profile to check against ++ * @sa: audit event ++ * @audit_cxt: audit context to log message to ++ * @type: audit event number + */ -+struct aa_profile *null_complain_profile; ++static int aa_audit_base(struct aa_profile *profile, struct aa_audit *sa, ++ struct audit_context *audit_cxt, int type) ++{ ++ struct audit_buffer *ab = NULL; ++ ++ ab = audit_log_start(audit_cxt, sa->gfp_mask, type); ++ ++ if (!ab) { ++ AA_ERROR("Unable to log event (%d) to audit subsys\n", ++ type); ++ /* don't fail operations in complain mode even if logging ++ * fails */ ++ return type == AUDIT_APPARMOR_ALLOWED ? 0 : -ENOMEM; ++ } ++ ++ if (sa->operation) ++ audit_log_format(ab, "operation=\"%s\"", sa->operation); ++ ++ if (sa->info) { ++ audit_log_format(ab, " info=\"%s\"", sa->info); ++ if (sa->error_code) ++ audit_log_format(ab, " error=%d", sa->error_code); ++ } ++ ++ if (sa->request_mask) ++ aa_audit_file_mask(ab, "requested_mask", sa->request_mask); ++ ++ if (sa->denied_mask) ++ aa_audit_file_mask(ab, "denied_mask", sa->denied_mask); ++ ++ if (sa->request_mask) ++ audit_log_format(ab, " fsuid=%d", current->fsuid); ++ ++ if (sa->iattr) { ++ struct iattr *iattr = sa->iattr; ++ ++ audit_log_format(ab, " attribute=\"%s%s%s%s%s%s%s\"", ++ iattr->ia_valid & ATTR_MODE ? "mode," : "", ++ iattr->ia_valid & ATTR_UID ? "uid," : "", ++ iattr->ia_valid & ATTR_GID ? "gid," : "", ++ iattr->ia_valid & ATTR_SIZE ? "size," : "", ++ iattr->ia_valid & (ATTR_ATIME | ATTR_ATIME_SET) ? ++ "atime," : "", ++ iattr->ia_valid & (ATTR_MTIME | ATTR_MTIME_SET) ? ++ "mtime," : "", ++ iattr->ia_valid & ATTR_CTIME ? "ctime," : ""); ++ } ++ ++ if (sa->task) ++ audit_log_format(ab, " task=%d", sa->task); ++ ++ if (sa->parent) ++ audit_log_format(ab, " parent=%d", sa->parent); ++ ++ if (sa->name) { ++ audit_log_format(ab, " name="); ++ audit_log_untrustedstring(ab, sa->name); ++ } ++ ++ if (sa->name2) { ++ audit_log_format(ab, " name2="); ++ audit_log_untrustedstring(ab, sa->name2); ++ } ++ ++ audit_log_format(ab, " pid=%d", current->pid); ++ ++ if (profile) { ++ audit_log_format(ab, " profile="); ++ audit_log_untrustedstring(ab, profile->name); ++ ++ if (profile->ns != default_namespace) { ++ audit_log_format(ab, " namespace="); ++ audit_log_untrustedstring(ab, profile->ns->name); ++ } ++ } ++ ++ audit_log_end(ab); ++ ++ return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error_code; ++} ++ ++/** ++ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem ++ * @profile: profile to check against ++ * @gfp: memory allocation flags ++ * @msg: string describing syscall being rejected ++ */ ++int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, ++ const char *msg) ++{ ++ struct aa_audit sa; ++ memset(&sa, 0, sizeof(sa)); ++ sa.operation = "syscall"; ++ sa.name = msg; ++ sa.gfp_mask = gfp; ++ sa.error_code = -EPERM; ++ ++ return aa_audit_base(profile, &sa, current->audit_context, ++ AUDIT_APPARMOR_DENIED); ++} ++ ++int aa_audit_message(struct aa_profile *profile, struct aa_audit *sa, ++ int type) ++{ ++ struct audit_context *audit_cxt; ++ ++ audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; ++ return aa_audit_base(profile, sa, audit_cxt, type); ++} ++ ++void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa) ++{ ++ aa_audit_message(profile, sa, AUDIT_APPARMOR_HINT); ++} ++ ++void aa_audit_status(struct aa_profile *profile, struct aa_audit *sa) ++{ ++ aa_audit_message(profile, sa, AUDIT_APPARMOR_STATUS); ++} ++ ++int aa_audit_reject(struct aa_profile *profile, struct aa_audit *sa) ++{ ++ return aa_audit_message(profile, sa, AUDIT_APPARMOR_DENIED); ++} ++ ++/** ++ * aa_audit - Log an audit event to the audit subsystem ++ * @profile: profile to check against ++ * @sa: audit event ++ */ ++int aa_audit(struct aa_profile *profile, struct aa_audit *sa) ++{ ++ int type = AUDIT_APPARMOR_DENIED; ++ struct audit_context *audit_cxt; ++ ++ if (likely(!sa->error_code)) ++ type = AUDIT_APPARMOR_AUDIT; ++ else if (PROFILE_COMPLAIN(profile)) ++ type = AUDIT_APPARMOR_ALLOWED; ++ ++ audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; ++ return aa_audit_base(profile, sa, audit_cxt, type); ++} ++ ++static int aa_audit_file(struct aa_profile *profile, struct aa_audit *sa) ++{ ++ if (likely(!sa->error_code)) { ++ int mask = sa->audit_mask & AUDIT_FILE_MASK; ++ ++ if (unlikely(PROFILE_AUDIT(profile))) ++ mask |= AUDIT_FILE_MASK; ++ ++ if (likely(!(sa->request_mask & mask))) ++ return 0; ++ ++ /* mask off perms that are not being force audited */ ++ sa->request_mask &= mask | ALL_AA_EXEC_TYPE; ++ } else { ++ int mask = AUDIT_QUIET_MASK(sa->audit_mask); ++ ++ if (!(sa->denied_mask & ~mask)) ++ return sa->error_code; ++ ++ /* mask off perms whose denial is being silenced */ ++ sa->denied_mask &= (~mask) | ALL_AA_EXEC_TYPE; ++ } ++ ++ return aa_audit(profile, sa); ++} ++ ++static int aa_audit_caps(struct aa_profile *profile, struct aa_audit *sa, ++ int cap) ++{ ++ if (likely(!sa->error_code)) { ++ if (likely(!PROFILE_AUDIT(profile) && ++ !cap_raised(profile->audit_caps, cap))) ++ return 0; ++ } ++ ++ /* quieting of capabilities is handled the caps_logged cache */ ++ return aa_audit(profile, sa); ++} + +/** + * aa_file_denied - check for @mask access on a file + * @profile: profile to check against + * @name: pathname of file + * @mask: permission mask requested for file ++ * @audit_mask: return audit mask for the match + * + * Return %0 on success, or else the permissions in @mask that the + * profile denies. + */ +static int aa_file_denied(struct aa_profile *profile, const char *name, -+ int mask) ++ int mask, int *audit_mask) +{ -+ return (mask & ~aa_match(profile->file_rules, name)); ++ return (mask & ~aa_match(profile->file_rules, name, audit_mask)); +} + +/** @@ -73,39 +359,68 @@ Signed-off-by: Andreas Gruenbacher + * @profile: profile to check against + * @link: pathname of link being created + * @target: pathname of target to be linked to ++ * @target_mode: UGO shift for target inode + * @request_mask: the permissions subset valid only if link succeeds ++ * @audit_mask: return the audit_mask for the link permission + * Return %0 on success, or else the permissions that the profile denies. + */ +static int aa_link_denied(struct aa_profile *profile, const char *link, -+ const char *target, int *request_mask) ++ const char *target, int target_mode, ++ int *request_mask, int *audit_mask) +{ -+ int l_mode, t_mode, denied_mask; ++ unsigned int state; ++ int l_mode, t_mode, denied_mask = 0; ++ int link_mask = AA_MAY_LINK << target_mode; + -+ l_mode = aa_match(profile->file_rules, link); -+ t_mode = aa_match(profile->file_rules, target); ++ *request_mask = link_mask; + -+ /* Ignore valid-profile-transition flags. */ -+ l_mode &= ~AA_CHANGE_PROFILE; -+ t_mode &= ~AA_CHANGE_PROFILE; ++ l_mode = aa_match_state(profile->file_rules, DFA_START, link, &state); + -+ *request_mask = l_mode | AA_MAY_LINK; ++ if (l_mode & link_mask) { ++ int mode; ++ /* test to see if target can be paired with link */ ++ state = aa_dfa_null_transition(profile->file_rules, state); ++ mode = aa_match_state(profile->file_rules, state, target, ++ &state); + -+ /* Link always requires 'l' on the link, a subset of the -+ * target's 'r', 'w', 'x', 'a', 'z', and 'm' permissions on the link, -+ * and if the link has 'x', an exact match of all the execute flags -+ * ('i', 'u', 'U', 'p', 'P'). ++ if (!(mode & link_mask)) ++ denied_mask |= link_mask; ++ ++ *audit_mask = dfa_audit_mask(profile->file_rules, state); ++ ++ /* return if link subset test is not required */ ++ if (!(mode & (AA_LINK_SUBSET_TEST << target_mode))) ++ return denied_mask; ++ } ++ ++ /* Do link perm subset test ++ * If a subset test is required a permission subset test of the ++ * perms for the link are done against the user::other of the ++ * target's 'r', 'w', 'x', 'a', 'k', and 'm' permissions. ++ * ++ * If the link has 'x', an exact match of all the execute flags ++ * must match. ++ */ ++ denied_mask |= ~l_mode & link_mask; ++ ++ t_mode = aa_match(profile->file_rules, target, NULL); ++ ++ /* For actual subset test ignore valid-profile-transition flags, ++ * and link bits + */ -+#define RWXAZM (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND | AA_MAY_LOCK | \ -+ AA_EXEC_MMAP) -+ denied_mask = ~l_mode & AA_MAY_LINK; -+ if (l_mode & RWXAZM) -+ denied_mask |= (l_mode & ~ AA_MAY_LINK) & ~t_mode; -+ else -+ denied_mask |= t_mode | AA_MAY_LINK; -+ if (denied_mask & AA_EXEC_MODIFIERS) -+ denied_mask |= MAY_EXEC; ++ l_mode &= AA_FILE_PERMS & ~AA_LINK_BITS; ++ t_mode &= AA_FILE_PERMS & ~AA_LINK_BITS; + -+#undef RWXAZM ++ *request_mask = l_mode | link_mask; ++ ++ if (l_mode) { ++ denied_mask |= l_mode & ~t_mode; ++ if ((l_mode & AA_EXEC_BITS) && ++ (l_mode & ALL_AA_EXEC_TYPE) != ++ (t_mode & ALL_AA_EXEC_TYPE)) ++ denied_mask = (denied_mask & ~ALL_AA_EXEC_TYPE) | ++ (l_mode & (ALL_AA_EXEC_TYPE | AA_EXEC_BITS)); ++ } + + return denied_mask; +} @@ -195,7 +510,7 @@ Signed-off-by: Andreas Gruenbacher + char *buffer = NULL; + + sa->name = aa_get_name(dentry, mnt, &buffer, check); -+ ++ sa->request_mask <<= aa_inode_mode(dentry->d_inode); + if (IS_ERR(sa->name)) { + /* + * deleted files are given a pass on permission checks when @@ -203,247 +518,32 @@ Signed-off-by: Andreas Gruenbacher + */ + if (PTR_ERR(sa->name) == -ENOENT && (check & AA_CHECK_FD)) + sa->denied_mask = 0; -+ else -+ sa->denied_mask = PTR_ERR(sa->name); ++ else { ++ sa->denied_mask = sa->request_mask; ++ sa->error_code = PTR_ERR(sa->name); ++ if (sa->error_code == -ENOENT) ++ sa->info = "Failed name resolution - object not a valid entry"; ++ else if (sa->error_code == -ENAMETOOLONG) ++ sa->info = "Failed name resolution - name too long"; ++ else ++ sa->info = "Failed name resolution"; ++ } + sa->name = NULL; + } else + sa->denied_mask = aa_file_denied(profile, sa->name, -+ sa->requested_mask); ++ sa->request_mask, ++ &sa->audit_mask); + + if (!sa->denied_mask) + sa->error_code = 0; + -+ error = aa_audit(profile, sa); ++ error = aa_audit_file(profile, sa); + aa_put_name_buffer(buffer); + + return error; +} + +/** -+ * alloc_null_complain_profile - Allocate the global null_complain_profile. -+ * -+ * Return %0 (success) or error (-%ENOMEM) -+ */ -+int alloc_null_complain_profile(void) -+{ -+ null_complain_profile = alloc_aa_profile(); -+ if (!null_complain_profile) -+ goto fail; -+ -+ null_complain_profile->name = -+ kstrdup("null-complain-profile", GFP_KERNEL); -+ -+ if (!null_complain_profile->name) -+ goto fail; -+ -+ null_complain_profile->flags.complain = 1; -+ -+ return 0; -+ -+fail: -+ /* free_aa_profile is safe for freeing partially constructed objects */ -+ free_aa_profile(null_complain_profile); -+ null_complain_profile = NULL; -+ -+ return -ENOMEM; -+} -+ -+/** -+ * free_null_complain_profile - Free null profiles -+ */ -+void free_null_complain_profile(void) -+{ -+ aa_put_profile(null_complain_profile); -+ null_complain_profile = NULL; -+} -+ -+static void aa_audit_file_mask(struct audit_buffer *ab, const char *name, -+ int mask) -+{ -+ char mask_str[10], *m = mask_str; -+ -+ if (mask & AA_EXEC_MMAP) -+ *m++ = 'm'; -+ if (mask & MAY_READ) -+ *m++ = 'r'; -+ if (mask & MAY_WRITE) -+ *m++ = 'w'; -+ else if (mask & MAY_APPEND) -+ *m++ = 'a'; -+ if (mask & (MAY_EXEC | AA_EXEC_MODIFIERS)) { -+ if (mask & AA_EXEC_UNSAFE) { -+ if (mask & AA_EXEC_INHERIT) -+ *m++ = 'i'; -+ if (mask & AA_EXEC_UNCONFINED) -+ *m++ = 'u'; -+ if (mask & AA_EXEC_PROFILE) -+ *m++ = 'p'; -+ } else { -+ if (mask & AA_EXEC_INHERIT) -+ *m++ = 'I'; -+ if (mask & AA_EXEC_UNCONFINED) -+ *m++ = 'U'; -+ if (mask & AA_EXEC_PROFILE) -+ *m++ = 'P'; -+ } -+ if (mask & MAY_EXEC) -+ *m++ = 'x'; -+ } -+ if (mask & AA_MAY_LINK) -+ *m++ = 'l'; -+ if (mask & AA_MAY_LOCK) -+ *m++ = 'k'; -+ *m++ = '\0'; -+ -+ audit_log_format(ab, " %s=\"%s\"", name, mask_str); -+} -+ -+/** -+ * aa_audit - Log an audit event to the audit subsystem -+ * @profile: profile to check against -+ * @sa: audit event -+ * @audit_cxt: audit context to log message to -+ * @type: audit event number -+ */ -+static int aa_audit_base(struct aa_profile *profile, struct aa_audit *sa, -+ struct audit_context *audit_cxt, int type) -+{ -+ struct audit_buffer *ab = NULL; -+ -+ ab = audit_log_start(audit_cxt, sa->gfp_mask, type); -+ -+ if (!ab) { -+ AA_ERROR("Unable to log event (%d) to audit subsys\n", -+ type); -+ /* don't fail operations in complain mode even if logging -+ * fails */ -+ return type == AUDIT_APPARMOR_ALLOWED ? 0 : -ENOMEM; -+ } -+ -+ if (sa->operation) -+ audit_log_format(ab, "operation=\"%s\"", sa->operation); -+ -+ if (sa->info) -+ audit_log_format(ab, " info=\"%s\"", sa->info); -+ -+ if (sa->requested_mask) -+ aa_audit_file_mask(ab, "requested_mask", sa->requested_mask); -+ -+ if (sa->denied_mask) -+ aa_audit_file_mask(ab, "denied_mask", sa->denied_mask); -+ -+ if (sa->iattr) { -+ struct iattr *iattr = sa->iattr; -+ -+ audit_log_format(ab, " attribute=\"%s%s%s%s%s%s%s\"", -+ iattr->ia_valid & ATTR_MODE ? "mode," : "", -+ iattr->ia_valid & ATTR_UID ? "uid," : "", -+ iattr->ia_valid & ATTR_GID ? "gid," : "", -+ iattr->ia_valid & ATTR_SIZE ? "size," : "", -+ iattr->ia_valid & (ATTR_ATIME | ATTR_ATIME_SET) ? -+ "atime," : "", -+ iattr->ia_valid & (ATTR_MTIME | ATTR_MTIME_SET) ? -+ "mtime," : "", -+ iattr->ia_valid & ATTR_CTIME ? "ctime," : ""); -+ } -+ -+ if (sa->task) -+ audit_log_format(ab, " task=%d", sa->task); -+ -+ if (sa->parent) -+ audit_log_format(ab, " parent=%d", sa->parent); -+ -+ if (sa->name) { -+ audit_log_format(ab, " name="); -+ audit_log_untrustedstring(ab, sa->name); -+ } -+ -+ if (sa->name2) { -+ audit_log_format(ab, " name2="); -+ audit_log_untrustedstring(ab, sa->name2); -+ } -+ -+ audit_log_format(ab, " pid=%d", current->pid); -+ -+ if (profile) { -+ audit_log_format(ab, " profile="); -+ audit_log_untrustedstring(ab, profile->name); -+ } -+ -+ audit_log_end(ab); -+ -+ return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error_code; -+} -+ -+/** -+ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem -+ * @profile: profile to check against -+ * @gfp: memory allocation flags -+ * @msg: string describing syscall being rejected -+ */ -+int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, -+ const char *msg) -+{ -+ struct aa_audit sa; -+ memset(&sa, 0, sizeof(sa)); -+ sa.operation = "syscall"; -+ sa.name = msg; -+ sa.gfp_mask = gfp; -+ sa.error_code = -EPERM; -+ -+ return aa_audit_base(profile, &sa, current->audit_context, -+ AUDIT_APPARMOR_DENIED); -+} -+ -+int aa_audit_message(struct aa_profile *profile, struct aa_audit *sa, -+ int type) -+{ -+ struct audit_context *audit_cxt; -+ -+ audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; -+ return aa_audit_base(profile, sa, audit_cxt, type); -+} -+ -+void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa) -+{ -+ aa_audit_message(profile, sa, AUDIT_APPARMOR_HINT); -+} -+ -+void aa_audit_status(struct aa_profile *profile, struct aa_audit *sa) -+{ -+ aa_audit_message(profile, sa, AUDIT_APPARMOR_STATUS); -+} -+ -+int aa_audit_reject(struct aa_profile *profile, struct aa_audit *sa) -+{ -+ return aa_audit_message(profile, sa, AUDIT_APPARMOR_DENIED); -+} -+ -+/** -+ * aa_audit - Log an audit event to the audit subsystem -+ * @profile: profile to check against -+ * @sa: audit event -+ */ -+int aa_audit(struct aa_profile *profile, struct aa_audit *sa) -+{ -+ int type = AUDIT_APPARMOR_DENIED; -+ struct audit_context *audit_cxt; -+ -+ if (likely(!sa->error_code)) { -+ if (likely(!PROFILE_AUDIT(profile))) -+ /* nothing to log */ -+ return 0; -+ else -+ type = AUDIT_APPARMOR_AUDIT; -+ } else if (PROFILE_COMPLAIN(profile)) { -+ type = AUDIT_APPARMOR_ALLOWED; -+ } -+ -+ audit_cxt = apparmor_logsyscall ? current->audit_context : NULL; -+ return aa_audit_base(profile, sa, audit_cxt, type); -+} -+ -+/** + * aa_attr - check if attribute change is allowed + * @profile: profile to check against + * @dentry: dentry of the file to check @@ -461,7 +561,7 @@ Signed-off-by: Andreas Gruenbacher + sa.operation = "setattr"; + sa.gfp_mask = GFP_KERNEL; + sa.iattr = iattr; -+ sa.requested_mask = MAY_WRITE; ++ sa.request_mask = MAY_WRITE; + sa.error_code = -EACCES; + + check = 0; @@ -495,7 +595,7 @@ Signed-off-by: Andreas Gruenbacher + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; -+ sa.requested_mask = mask; ++ sa.request_mask = mask; + sa.error_code = -EACCES; + + if (inode && S_ISDIR(inode->i_mode)) @@ -529,7 +629,7 @@ Signed-off-by: Andreas Gruenbacher + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; -+ sa.requested_mask = mask; ++ sa.request_mask = mask; + sa.error_code = -EACCES; + + error = aa_perm_dentry(profile, dentry, mnt, &sa, check); @@ -558,27 +658,32 @@ Signed-off-by: Andreas Gruenbacher + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; -+ sa.requested_mask = mask; ++ sa.request_mask = mask; + sa.error_code = -EACCES; + + return aa_perm_dentry(profile, dentry, mnt, &sa, AA_CHECK_DIR); +} + +int aa_perm_path(struct aa_profile *profile, const char *operation, -+ const char *name, int mask) ++ const char *name, int mask, uid_t uid) +{ + struct aa_audit sa; + + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; -+ sa.requested_mask = mask; ++ sa.request_mask = mask; + sa.name = name; ++ if (current->fsuid == uid) ++ sa.request_mask = mask << AA_USER_SHIFT; ++ else ++ sa.request_mask = mask << AA_OTHER_SHIFT; + -+ sa.denied_mask = aa_file_denied(profile, name, mask); ++ sa.denied_mask = aa_file_denied(profile, name, sa.request_mask, ++ &sa.audit_mask) ; + sa.error_code = sa.denied_mask ? -EACCES : 0; + -+ return aa_audit(profile, &sa); ++ return aa_audit_file(profile, &sa); +} + +/** @@ -614,7 +719,7 @@ Signed-off-by: Andreas Gruenbacher + sa.name = capability_names[cap]; + sa.error_code = error; + -+ error = aa_audit(cxt->profile, &sa); ++ error = aa_audit_caps(cxt->profile, &sa, cap); + + return error; +} @@ -641,15 +746,15 @@ Signed-off-by: Andreas Gruenbacher + struct dentry *link, struct vfsmount *link_mnt, + struct dentry *target, struct vfsmount *target_mnt) +{ -+ int error, check = 0; ++ int error; + struct aa_audit sa; + char *buffer = NULL, *buffer2 = NULL; + + memset(&sa, 0, sizeof(sa)); + sa.operation = "inode_link"; + sa.gfp_mask = GFP_KERNEL; -+ sa.name = aa_get_name(link, link_mnt, &buffer, check); -+ sa.name2 = aa_get_name(target, target_mnt, &buffer2, check); ++ sa.name = aa_get_name(link, link_mnt, &buffer, 0); ++ sa.name2 = aa_get_name(target, target_mnt, &buffer2, 0); + + if (IS_ERR(sa.name)) { + sa.error_code = PTR_ERR(sa.name); @@ -661,13 +766,14 @@ Signed-off-by: Andreas Gruenbacher + } + + if (sa.name && sa.name2) { -+ sa.requested_mask = AA_MAY_LINK; + sa.denied_mask = aa_link_denied(profile, sa.name, sa.name2, -+ &sa.requested_mask); ++ aa_inode_mode(target->d_inode), ++ &sa.request_mask, ++ &sa.audit_mask); + sa.error_code = sa.denied_mask ? -EACCES : 0; + } + -+ error = aa_audit(profile, &sa); ++ error = aa_audit_file(profile, &sa); + + aa_put_name_buffer(buffer); + aa_put_name_buffer(buffer2); @@ -718,7 +824,7 @@ Signed-off-by: Andreas Gruenbacher + unlock_profile(profile); + + if (APPARMOR_COMPLAIN(child_cxt) && -+ profile == null_complain_profile) { ++ profile == profile->ns->null_complain_profile) { + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.operation = "clone"; @@ -740,23 +846,28 @@ Signed-off-by: Andreas Gruenbacher + struct aa_profile *new_profile; + + /* Locate new profile */ -+ new_profile = aa_find_profile(name); ++ if (profile) ++ new_profile = aa_find_profile(profile->ns, name); ++ else ++ new_profile = aa_find_profile(default_namespace, name); ++ + if (new_profile) { + AA_DEBUG("%s: setting profile %s\n", + __FUNCTION__, new_profile->name); + } else if (mandatory && profile) { + sa->info = "mandatory profile missing"; -+ sa->denied_mask = MAY_EXEC; ++ sa->denied_mask = sa->request_mask; /* shifted MAY_EXEC */ + if (complain) { + aa_audit_hint(profile, sa); -+ new_profile = aa_dup_profile(null_complain_profile); ++ new_profile = ++ aa_dup_profile(profile->ns->null_complain_profile); + } else { -+ aa_audit_reject(profile, sa); -+ return ERR_PTR(-EACCES); /* was -EPERM */ ++ sa->error_code = -EACCES; ++ return ERR_PTR(aa_audit_file(profile, sa)); + } + } else { + /* Only way we can get into this code is if task -+ * is unconfined. ++ * is unconfined, or pix. + */ + AA_DEBUG("%s: No profile found for exec image '%s'\n", + __FUNCTION__, @@ -778,43 +889,53 @@ Signed-off-by: Andreas Gruenbacher + char *buffer = NULL; + struct file *filp = bprm->file; + struct aa_profile *profile, *old_profile, *new_profile = NULL; -+ int exec_mode = AA_EXEC_UNSAFE, complain = 0; ++ int exec_mode, complain = 0, shift; + struct aa_audit sa; + + AA_DEBUG("%s\n", __FUNCTION__); + -+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt, &buffer, 0); -+ if (IS_ERR(filename)) { -+ AA_ERROR("%s: Failed to get filename", __FUNCTION__); -+ return -ENOENT; -+ } ++ profile = aa_get_profile(current); + ++ shift = aa_inode_mode(filp->f_dentry->d_inode); + memset(&sa, 0, sizeof(sa)); + sa.operation = "exec"; + sa.gfp_mask = GFP_KERNEL; ++ sa.request_mask = MAY_EXEC << shift; ++ ++ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt, &buffer, 0); ++ if (IS_ERR(filename)) { ++ if (profile) { ++ sa.info = "Failed name resolution - exec failed"; ++ sa.error_code = PTR_ERR(filename); ++ aa_audit_reject(profile, &sa); ++ return sa.error_code; ++ } else ++ return 0; ++ } + sa.name = filename; -+ sa.requested_mask = MAY_EXEC; ++ ++ exec_mode = AA_EXEC_UNSAFE << shift; + +repeat: -+ profile = aa_get_profile(current); + if (profile) { + complain = PROFILE_COMPLAIN(profile); + + /* Confined task, determine what mode inherit, unconfined or + * mandatory to load new profile + */ -+ exec_mode = aa_match(profile->file_rules, filename); ++ exec_mode = aa_match(profile->file_rules, filename, ++ &sa.audit_mask); + -+ if (exec_mode & (MAY_EXEC | AA_EXEC_MODIFIERS)) { -+ switch (exec_mode & (MAY_EXEC | AA_EXEC_MODIFIERS)) { -+ case MAY_EXEC | AA_EXEC_INHERIT: ++ if (exec_mode & sa.request_mask) { ++ switch ((exec_mode >> shift) & AA_EXEC_MODIFIERS) { ++ case AA_EXEC_INHERIT: + AA_DEBUG("%s: INHERIT %s\n", + __FUNCTION__, + filename); + /* nothing to be done here */ + goto cleanup; + -+ case MAY_EXEC | AA_EXEC_UNCONFINED: ++ case AA_EXEC_UNCONFINED: + AA_DEBUG("%s: UNCONFINED %s\n", + __FUNCTION__, + filename); @@ -823,7 +944,7 @@ Signed-off-by: Andreas Gruenbacher + new_profile = NULL; + break; + -+ case MAY_EXEC | AA_EXEC_PROFILE: ++ case AA_EXEC_PROFILE: + AA_DEBUG("%s: PROFILE %s\n", + __FUNCTION__, + filename); @@ -832,19 +953,35 @@ Signed-off-by: Andreas Gruenbacher + 1, complain, + &sa); + break; ++ case AA_EXEC_PIX: ++ AA_DEBUG("%s: PROFILE %s\n", ++ __FUNCTION__, ++ filename); ++ new_profile = aa_register_find(profile, ++ filename, ++ 0, complain, ++ &sa); ++ if (!new_profile) ++ /* inherit - nothing to be done here */ ++ goto cleanup; ++ break; + } + ++ } else if (sa.request_mask & AUDIT_QUIET_MASK(sa.audit_mask)) { ++ /* quiet failed exit */ ++ new_profile = ERR_PTR(-EACCES); + } else if (complain) { + /* There was no entry in calling profile + * describing mode to execute image in. + * Drop into null-profile (disabling secure exec). + */ -+ new_profile = aa_dup_profile(null_complain_profile); -+ exec_mode |= AA_EXEC_UNSAFE; ++ new_profile = ++ aa_dup_profile(profile->ns->null_complain_profile); ++ exec_mode |= AA_EXEC_UNSAFE << shift; + } else { -+ sa.denied_mask = MAY_EXEC; -+ aa_audit_reject(profile, &sa); -+ new_profile = ERR_PTR(-EPERM); ++ sa.denied_mask = sa.request_mask; ++ sa.error_code = -EACCES; ++ new_profile = ERR_PTR(aa_audit_file(profile, &sa)); + } + } else { + /* Unconfined task, load profile if it exists */ @@ -860,10 +997,12 @@ Signed-off-by: Andreas Gruenbacher + if (IS_ERR(old_profile)) { + aa_put_profile(new_profile); + aa_put_profile(profile); -+ if (PTR_ERR(old_profile) == -ESTALE) ++ if (PTR_ERR(old_profile) == -ESTALE) { ++ profile = aa_get_profile(current); + goto repeat; ++ } + if (PTR_ERR(old_profile) == -EPERM) { -+ sa.denied_mask = MAY_EXEC; ++ sa.denied_mask = sa.request_mask; + sa.info = "unable to set profile due to ptrace"; + sa.task = current->parent->pid; + aa_audit_reject(profile, &sa); @@ -883,7 +1022,7 @@ Signed-off-by: Andreas Gruenbacher + * Cases 2 and 3 are marked as requiring secure exec + * (unless policy specified "unsafe exec") + */ -+ if (!(exec_mode & AA_EXEC_UNSAFE)) { ++ if (!(exec_mode & (AA_EXEC_UNSAFE << shift))) { + unsigned long bprm_flags; + + bprm_flags = AA_SECURE_EXEC_NEEDED; @@ -891,12 +1030,14 @@ Signed-off-by: Andreas Gruenbacher + ((unsigned long)bprm->security | bprm_flags); + } + -+ if (complain && new_profile == null_complain_profile) { -+ sa.requested_mask = 0; ++ if (complain && new_profile && ++ new_profile == new_profile->ns->null_complain_profile) { ++ sa.request_mask = 0; + sa.name = NULL; + sa.info = "set profile"; + aa_audit_hint(new_profile, &sa); + } ++ +cleanup: + aa_put_name_buffer(buffer); + if (IS_ERR(new_profile)) @@ -951,7 +1092,8 @@ Signed-off-by: Andreas Gruenbacher + } +} + -+static int do_change_profile(struct aa_profile *expected, const char *name, ++static int do_change_profile(struct aa_profile *expected, ++ struct aa_namespace *ns, const char *name, + u64 cookie, int restore, struct aa_audit *sa) +{ + struct aa_profile *new_profile = NULL, *old_profile = NULL, @@ -965,11 +1107,11 @@ Signed-off-by: Andreas Gruenbacher + if (!new_cxt) + return -ENOMEM; + -+ new_profile = aa_find_profile(name); ++ new_profile = aa_find_profile(ns, name); + if (!new_profile && !restore) { + if (!PROFILE_COMPLAIN(expected)) + return -ENOENT; -+ new_profile = aa_dup_profile(null_complain_profile); ++ new_profile = aa_dup_profile(ns->null_complain_profile); + } + + cxt = lock_task_and_profiles(current, new_profile); @@ -1004,7 +1146,7 @@ Signed-off-by: Andreas Gruenbacher + goto out; + } + -+ if (new_profile == null_complain_profile) ++ if (new_profile == ns->null_complain_profile) + aa_audit_hint(cxt->profile, sa); + + if (APPARMOR_AUDIT(cxt)) @@ -1027,22 +1169,25 @@ Signed-off-by: Andreas Gruenbacher +} + +/** -+ * aa_change_profile - change profile to/from previous stored profile ++ * aa_change_profile - perform a one-way profile transition ++ * @ns_name: name of the profile namespace to change to + * @name: name of profile to change to -+ * @cookie: magic value to validate the profile change -+ * -+ * Change to new profile @name, and store the @cookie in the current task -+ * context. If the new @name is %NULL and the @cookie matches that -+ * stored in the current task context, return to the previous profile. ++ * Change to new profile @name. Unlike with hats, there is no way ++ * to change back. + * + * Returns %0 on success, error otherwise. + */ -+int aa_change_profile(const char *name, u64 cookie) ++int aa_change_profile(const char *ns_name, const char *name) +{ + struct aa_task_context *cxt; -+ struct aa_profile *profile, *previous_profile; ++ struct aa_profile *profile; ++ struct aa_namespace *ns = NULL; + struct aa_audit sa; -+ int error = 0; ++ unsigned int state; ++ int error = -EINVAL; ++ ++ if (!name) ++ return -EINVAL; + + memset(&sa, 0, sizeof(sa)); + sa.gfp_mask = GFP_ATOMIC; @@ -1056,24 +1201,36 @@ Signed-off-by: Andreas Gruenbacher + return -EPERM; + } + profile = aa_dup_profile(cxt->profile); -+ previous_profile = aa_dup_profile(cxt->previous_profile); + task_unlock(current); + -+ if (name) { -+ if (profile != null_complain_profile && -+ !(aa_match(profile->file_rules, name) & -+ AA_CHANGE_PROFILE)) { -+ /* no permission to transition to profile @name */ -+ aa_put_profile(profile); -+ return -EACCES; -+ } -+ error = do_change_profile(profile, name, cookie, 0, &sa); -+ } else if (previous_profile) -+ error = do_change_profile(profile, previous_profile->name, -+ cookie, 1, &sa); -+ /* else ignore restores when there is no saved profile */ ++ if (ns_name) ++ ns = aa_find_namespace(ns_name); ++ else ++ ns = aa_get_namespace(profile->ns); ++ if (!ns) { ++ aa_put_profile(profile); ++ return -ENOENT; ++ } + -+ aa_put_profile(previous_profile); ++ if (PROFILE_COMPLAIN(profile) || ++ (ns == profile->ns && ++ (aa_match(profile->file_rules, name, NULL) & AA_CHANGE_PROFILE))) ++ error = do_change_profile(profile, ns, name, 0, 0, &sa); ++ else { ++ /* check for a rule with a namespace prepended */ ++ aa_match_state(profile->file_rules, DFA_START, ns->name, ++ &state); ++ state = aa_dfa_null_transition(profile->file_rules, state); ++ if ((aa_match_state(profile->file_rules, state, name, NULL) & ++ AA_CHANGE_PROFILE)) ++ error = do_change_profile(profile, ns, name, 0, 0, ++ &sa); ++ else ++ /* no permission to transition to profile @name */ ++ error = -EACCES; ++ } ++ ++ aa_put_namespace(ns); + aa_put_profile(profile); + if (error == -ESTALE) + goto repeat; @@ -1116,6 +1273,15 @@ Signed-off-by: Andreas Gruenbacher + + if (hat_name) { + char *name, *profile_name; ++ if (!PROFILE_COMPLAIN(profile) && ++ !(aa_match(profile->file_rules, hat_name, NULL) ++ & AA_CHANGE_HAT)) { ++ /* missing permission to change_hat is treated the ++ * same as a failed hat search */ ++ error = -ENOENT; ++ goto out; ++ } ++ + if (previous_profile) + profile_name = previous_profile->name; + else @@ -1128,11 +1294,13 @@ Signed-off-by: Andreas Gruenbacher + goto out; + } + sprintf(name, "%s//%s", profile_name, hat_name); -+ error = do_change_profile(profile, name, cookie, 0, &sa); ++ error = do_change_profile(profile, profile->ns, name, cookie, ++ 0, &sa); + kfree(name); + } else if (previous_profile) -+ error = do_change_profile(profile, previous_profile->name, -+ cookie, 1, &sa); ++ error = do_change_profile(profile, profile->ns, ++ previous_profile->name, cookie, 1, ++ &sa); + /* else ignore restores when there is no saved profile */ + +out: @@ -1253,9 +1421,11 @@ Signed-off-by: Andreas Gruenbacher + call_rcu(&old_cxt->rcu, free_aa_task_context_rcu_callback); + } + if (new_cxt) { -+ /* clear the caps_logged cache, so that new profile/hat has -+ * chance to emit its own set of cap messages */ -+ new_cxt->caps_logged = CAP_EMPTY_SET; ++ /* set the caps_logged cache to the quiet_caps mask ++ * this has the effect of quieting caps that are not ++ * supposed to be logged ++ */ ++ new_cxt->caps_logged = profile->quiet_caps; + new_cxt->cookie = cookie; + new_cxt->task = task; + new_cxt->profile = aa_dup_profile(profile); diff --git a/kernel-patches/for-mainline/apparmor-misc.diff b/kernel-patches/for-mainline/apparmor-misc.diff index 4b6c42675..0195310c0 100644 --- a/kernel-patches/for-mainline/apparmor-misc.diff +++ b/kernel-patches/for-mainline/apparmor-misc.diff @@ -11,21 +11,21 @@ Signed-off-by: John Johansen Signed-off-by: Andreas Gruenbacher --- - security/apparmor/Kconfig | 10 + - security/apparmor/Makefile | 13 ++ - security/apparmor/apparmor.h | 257 +++++++++++++++++++++++++++++++++++++++++ - security/apparmor/apparmorfs.c | 252 ++++++++++++++++++++++++++++++++++++++++ - security/apparmor/inline.h | 211 +++++++++++++++++++++++++++++++++ - security/apparmor/list.c | 94 ++++++++++++++ - security/apparmor/locking.txt | 68 ++++++++++ - security/apparmor/procattr.c | 155 ++++++++++++++++++++++++ - 8 files changed, 1060 insertions(+) + security/apparmor/Kconfig | 42 ++++ + security/apparmor/Makefile | 13 + + security/apparmor/apparmor.h | 358 +++++++++++++++++++++++++++++++++++++++++ + security/apparmor/apparmorfs.c | 280 ++++++++++++++++++++++++++++++++ + security/apparmor/inline.h | 250 ++++++++++++++++++++++++++++ + security/apparmor/list.c | 156 +++++++++++++++++ + security/apparmor/locking.txt | 68 +++++++ + security/apparmor/procattr.c | 195 ++++++++++++++++++++++ + 8 files changed, 1362 insertions(+) --- /dev/null +++ b/security/apparmor/Kconfig -@@ -0,0 +1,10 @@ +@@ -0,0 +1,42 @@ +config SECURITY_APPARMOR -+ tristate "AppArmor support" ++ bool "AppArmor support" + depends on SECURITY + select AUDIT + help @@ -33,6 +33,38 @@ Signed-off-by: Andreas Gruenbacher + Required userspace tools (if they are not included in your + distribution) and further information may be found at + ++ ++ If you are unsure how to answer this question, answer N. ++ ++config SECURITY_APPARMOR_BOOTPARAM_VALUE ++ int "AppArmor boot parameter default value" ++ depends on SECURITY_APPARMOR ++ range 0 1 ++ default 1 ++ help ++ This option sets the default value for the kernel parameter ++ 'apparmor', which allows AppArmor to be enabled or disabled ++ at boot. If this option is set to 0 (zero), the AppArmor ++ kernel parameter will default to 0, disabling AppArmor at ++ bootup. If this option is set to 1 (one), the AppArmor ++ kernel parameter will default to 1, enabling AppArmor at ++ bootup. ++ ++ If you are unsure how to answer this question, answer 1. ++ ++config SECURITY_APPARMOR_DISABLE ++ bool "AppArmor runtime disable" ++ depends on SECURITY_APPARMOR ++ default n ++ help ++ This option enables writing to a apparmorfs node 'disable', which ++ allows AppArmor to be disabled at runtime prior to the policy load. ++ AppArmor will then remain disabled until the next boot. ++ This option is similar to the apparmor.enabled=0 boot parameter, ++ but is to support runtime disabling of AppArmor, e.g. from ++ /sbin/init, for portability across platforms where boot ++ parameters are difficult to employ. ++ + If you are unsure how to answer this question, answer N. --- /dev/null +++ b/security/apparmor/Makefile @@ -52,7 +84,7 @@ Signed-off-by: Andreas Gruenbacher + $(call cmd,make-caps) --- /dev/null +++ b/security/apparmor/apparmor.h -@@ -0,0 +1,257 @@ +@@ -0,0 +1,358 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -79,26 +111,74 @@ Signed-off-by: Andreas Gruenbacher +#define AA_MAY_LINK 0x0010 +#define AA_MAY_LOCK 0x0020 +#define AA_EXEC_MMAP 0x0040 ++#define AA_MAY_MOUNT 0x0080 /* no direct audit mapping */ ++#define AA_EXEC_UNSAFE 0x0100 ++#define AA_EXEC_MOD_0 0x0200 ++#define AA_EXEC_MOD_1 0x0400 ++#define AA_EXEC_MOD_2 0x0800 ++#define AA_EXEC_MOD_3 0x1000 ++#define AA_EXEC_MOD_4 0x2000 + -+#define AA_CHANGE_PROFILE 0x04000000 -+#define AA_EXEC_INHERIT 0x08000000 -+#define AA_EXEC_UNCONFINED 0x10000000 -+#define AA_EXEC_PROFILE 0x20000000 -+#define AA_EXEC_UNSAFE 0x40000000 -+ -+#define AA_EXEC_MODIFIERS (AA_EXEC_INHERIT | \ -+ AA_EXEC_UNCONFINED | \ -+ AA_EXEC_PROFILE) -+ -+#define AA_VALID_PERM_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | \ ++#define AA_BASE_PERMS (MAY_READ | MAY_WRITE | MAY_EXEC | \ + MAY_APPEND | AA_MAY_LINK | \ -+ AA_MAY_LOCK | \ -+ AA_EXEC_MODIFIERS | AA_EXEC_MMAP | \ -+ AA_EXEC_UNSAFE | AA_CHANGE_PROFILE) ++ AA_MAY_LOCK | AA_EXEC_MMAP | \ ++ AA_MAY_MOUNT | AA_EXEC_UNSAFE | \ ++ AA_EXEC_MOD_0 | AA_EXEC_MOD_1 | \ ++ AA_EXEC_MOD_2 | AA_EXEC_MOD_3 | \ ++ AA_EXEC_MOD_4) ++ ++#define AA_EXEC_MODIFIERS (AA_EXEC_MOD_0 | AA_EXEC_MOD_1 | \ ++ AA_EXEC_MOD_2 | AA_EXEC_MOD_3 | \ ++ AA_EXEC_MOD_4) ++ ++#define AA_EXEC_TYPE (AA_EXEC_UNSAFE | AA_EXEC_MODIFIERS) ++ ++#define AA_EXEC_UNCONFINED AA_EXEC_MOD_0 ++#define AA_EXEC_INHERIT AA_EXEC_MOD_1 ++#define AA_EXEC_PROFILE (AA_EXEC_MOD_0 | AA_EXEC_MOD_1) ++#define AA_EXEC_PIX AA_EXEC_MOD_2 ++ ++#define AA_USER_SHIFT 0 ++#define AA_OTHER_SHIFT 14 ++ ++#define AA_USER_PERMS (AA_BASE_PERMS << AA_USER_SHIFT) ++#define AA_OTHER_PERMS (AA_BASE_PERMS << AA_OTHER_SHIFT) ++ ++#define AA_FILE_PERMS (AA_USER_PERMS | AA_OTHER_PERMS) ++ ++#define AA_LINK_BITS ((AA_MAY_LINK << AA_USER_SHIFT) | \ ++ (AA_MAY_LINK << AA_OTHER_SHIFT)) ++ ++#define AA_USER_EXEC (MAY_EXEC << AA_USER_SHIFT) ++#define AA_OTHER_EXEC (MAY_EXEC << AA_OTHER_SHIFT) ++ ++#define AA_USER_EXEC_TYPE (AA_EXEC_TYPE << AA_USER_SHIFT) ++#define AA_OTHER_EXEC_TYPE (AA_EXEC_TYPE << AA_OTHER_SHIFT) ++ ++#define AA_EXEC_BITS (AA_USER_EXEC | AA_OTHER_EXEC) ++ ++#define ALL_AA_EXEC_TYPE (AA_USER_EXEC_TYPE | AA_OTHER_EXEC_TYPE) ++ ++/* overloaded permissions for link pairs */ ++#define AA_LINK_SUBSET_TEST 0x0020 ++ ++/* shared permissions that are not duplicated in user::other */ ++#define AA_AUDIT_FIELD 0x10000000 ++#define AA_CHANGE_HAT 0x20000000 ++#define AA_CHANGE_PROFILE 0x40000000 ++ ++#define AA_SHARED_PERMS (AA_CHANGE_HAT | AA_CHANGE_PROFILE | \ ++ AA_AUDIT_FIELD) ++ ++#define AA_VALID_PERM_MASK (AA_FILE_PERMS | AA_SHARED_PERMS) ++ ++/* audit bits for the second accept field */ ++#define AUDIT_FILE_MASK 0x1fc07f ++#define AUDIT_QUIET_MASK(mask) ((mask >> 7) & AUDIT_FILE_MASK) ++#define AA_VALID_PERM2_MASK 0x0fffffff + +#define AA_SECURE_EXEC_NEEDED 1 + -+ +/* Control parameters (0 or 1), settable thru module/boot flags or + * via /sys/kernel/security/apparmor/control */ +extern int apparmor_complain; @@ -134,18 +214,49 @@ Signed-off-by: Andreas Gruenbacher + +#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args) + ++struct aa_profile; ++ ++/* struct aa_namespace - namespace for a set of profiles ++ * @name: the name of the namespace ++ * @list: list the namespace is on ++ * @profiles: list of profile in the namespace ++ * @profile_count: the number of profiles in the namespace ++ * @null_complain_profile: special profile used for learning in this namespace ++ * @count: reference count on the namespace ++ * @lock: lock for adding/removing profile to the namespace ++ */ ++struct aa_namespace { ++ char *name; ++ struct list_head list; ++ struct list_head profiles; ++ int profile_count; ++ struct aa_profile *null_complain_profile; ++ ++ struct kref count; ++ rwlock_t lock; ++}; ++ +/* struct aa_profile - basic confinement data + * @name: the profiles name -+ * @file_rules: dfa containing the profiles file rules + * @list: list this profile is on ++ * @ns: namespace the profile is in ++ * @file_rules: dfa containing the profiles file rules + * @flags: flags controlling profile behavior + * @isstale: flag indicating if profile is stale ++ * @set_caps: capabilities that are being set ++ * @capabilities: capabilities mask ++ * @audit_caps: caps that are to be audited ++ * @quiet_caps: caps that should not be audited + * @capabilities: capabilities granted by the process + * @count: reference count of the profile ++ * @task_contexts: list of tasks confined by profile ++ * @lock: lock for the task_contexts list ++ * @network_families: basic network permissions ++ * @audit_network: which network permissions to force audit ++ * @quiet_network: which network permissions to quiet rejects + * + * The AppArmor profile contains the basic confinement data. Each profile -+ * has a name and potentially a list of sub profile entries. All non stale -+ * profiles are on the profile_list. ++ * has a name, and all nonstale profile are in a profile namespace. + * + * The task_contexts list and the isstale flag are protected by the + * profile lock. @@ -156,23 +267,29 @@ Signed-off-by: Andreas Gruenbacher + */ +struct aa_profile { + char *name; -+ struct aa_dfa *file_rules; + struct list_head list; ++ struct aa_namespace *ns; ++ ++ struct aa_dfa *file_rules; + struct { + int complain; + int audit; + } flags; + int isstale; + ++ kernel_cap_t set_caps; + kernel_cap_t capabilities; ++ kernel_cap_t audit_caps; ++ kernel_cap_t quiet_caps; ++ + struct kref count; + struct list_head task_contexts; + spinlock_t lock; + unsigned long int_flags; +}; + -+extern struct list_head profile_list; -+extern rwlock_t profile_list_lock; ++extern struct list_head profile_ns_list; ++extern rwlock_t profile_ns_list_lock; +extern struct mutex aa_interface_lock; + +/** @@ -198,7 +315,7 @@ Signed-off-by: Andreas Gruenbacher + kernel_cap_t caps_logged; +}; + -+extern struct aa_profile *null_complain_profile; ++extern struct aa_namespace *default_namespace; + +/* aa_audit - AppArmor auditing structure + * Structure is populated by access control code and passed to aa_audit which @@ -211,7 +328,8 @@ Signed-off-by: Andreas Gruenbacher + const char *info; + const char *name; + const char *name2; -+ int requested_mask, denied_mask; ++ const char *name3; ++ int request_mask, denied_mask, audit_mask; + struct iattr *iattr; + pid_t task, parent; + int error_code; @@ -229,9 +347,8 @@ Signed-off-by: Andreas Gruenbacher +}; + +/* main.c */ -+extern int alloc_null_complain_profile(void); -+extern void free_null_complain_profile(void); -+extern int attach_nullprofile(struct aa_profile *profile); ++extern int alloc_default_namespace(void); ++extern void free_default_namespace(void); +extern int aa_audit_message(struct aa_profile *profile, struct aa_audit *sa, + int type); +void aa_audit_hint(struct aa_profile *profile, struct aa_audit *sa); @@ -254,7 +371,7 @@ Signed-off-by: Andreas Gruenbacher + struct dentry *dentry, struct vfsmount *mnt, + int mask); +extern int aa_perm_path(struct aa_profile *, const char *operation, -+ const char *name, int); ++ const char *name, int mask, uid_t uid); +extern int aa_link(struct aa_profile *profile, + struct dentry *link, struct vfsmount *link_mnt, + struct dentry *target, struct vfsmount *target_mnt); @@ -262,9 +379,7 @@ Signed-off-by: Andreas Gruenbacher +extern int aa_register(struct linux_binprm *bprm); +extern void aa_release(struct task_struct *task); +extern int aa_change_hat(const char *id, u64 hat_magic); -+extern int aa_change_profile(const char *name, u64 cookie); -+extern struct aa_profile *__aa_find_profile(const char *name, -+ struct list_head *list); ++extern int aa_change_profile(const char *ns_name, const char *name); +extern struct aa_profile *__aa_replace_profile(struct task_struct *task, + struct aa_profile *profile); +extern struct aa_task_context *lock_task_and_profiles(struct task_struct *task, @@ -279,13 +394,25 @@ Signed-off-by: Andreas Gruenbacher +extern int aa_may_ptrace(struct aa_task_context *cxt, + struct aa_profile *tracee); + ++/* lsm.c */ ++extern int apparmor_initialized; ++extern void info_message(const char *str); ++extern void apparmor_disable(void); ++ +/* list.c */ -+extern void aa_profilelist_release(void); ++extern struct aa_namespace *__aa_find_namespace(const char *name, ++ struct list_head *list); ++extern struct aa_profile *__aa_find_profile(const char *name, ++ struct list_head *list); ++extern void aa_profile_ns_list_release(void); + +/* module_interface.c */ +extern ssize_t aa_add_profile(void *, size_t); +extern ssize_t aa_replace_profile(void *, size_t); -+extern ssize_t aa_remove_profile(const char *, size_t); ++extern ssize_t aa_remove_profile(char *, size_t); ++extern struct aa_namespace *alloc_aa_namespace(char *name); ++extern void free_aa_namespace(struct aa_namespace *ns); ++extern void free_aa_namespace_kref(struct kref *kref); +extern struct aa_profile *alloc_aa_profile(void); +extern void free_aa_profile(struct aa_profile *profile); +extern void free_aa_profile_kref(struct kref *kref); @@ -307,12 +434,18 @@ Signed-off-by: Andreas Gruenbacher +extern void aa_match_free(struct aa_dfa *dfa); +extern int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size); +extern int verify_dfa(struct aa_dfa *dfa); -+extern unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str); ++extern unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str, int *); ++extern unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start, ++ const char *str); ++extern unsigned int aa_match_state(struct aa_dfa *dfa, unsigned int start, ++ const char *str, unsigned int *final); ++extern unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, ++ unsigned int start); + +#endif /* __APPARMOR_H */ --- /dev/null +++ b/security/apparmor/apparmorfs.c -@@ -0,0 +1,252 @@ +@@ -0,0 +1,280 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -404,7 +537,7 @@ Signed-off-by: Andreas Gruenbacher +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ -+ const char *matching = "pattern=aadfa perms=rwxamlz"; ++ const char *matching = "pattern=aadfa audit perms=rwxamlk/ user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + strlen(matching)); @@ -414,6 +547,22 @@ Signed-off-by: Andreas Gruenbacher + .read = aa_matching_read, +}; + ++/* apparmor/features */ ++static ssize_t aa_features_read(struct file *file, char __user *buf, ++ size_t size, loff_t *ppos) ++{ ++ const char *features = "file=3.0 capability=2.0 network=1.0 " ++ "change_hat=1.4 change_profile=1.0 " ++ "aanamespaces=1.0"; ++ ++ return simple_read_from_buffer(buf, size, ppos, features, ++ strlen(features)); ++} ++ ++static struct file_operations apparmorfs_features_fops = { ++ .read = aa_features_read, ++}; ++ +/* apparmor/.load */ +static ssize_t aa_profile_load(struct file *f, const char __user *buf, + size_t size, loff_t *pos) @@ -519,6 +668,7 @@ Signed-off-by: Andreas Gruenbacher + aafs_remove(".replace"); + aafs_remove(".load"); + aafs_remove("matching"); ++ aafs_remove("features"); + aafs_remove("profiles"); + securityfs_remove(apparmor_dentry); + apparmor_dentry = NULL; @@ -529,6 +679,9 @@ Signed-off-by: Andreas Gruenbacher +{ + int error; + ++ if (!apparmor_initialized) ++ return 0; ++ + if (apparmor_dentry) { + AA_ERROR("%s: AppArmor securityfs already exists\n", + __FUNCTION__); @@ -547,6 +700,9 @@ Signed-off-by: Andreas Gruenbacher + error = aafs_create("matching", 0444, &apparmorfs_matching_fops); + if (error) + goto error; ++ error = aafs_create("features", 0444, &apparmorfs_features_fops); ++ if (error) ++ goto error; + error = aafs_create(".load", 0640, &apparmorfs_profile_load); + if (error) + goto error; @@ -557,17 +713,22 @@ Signed-off-by: Andreas Gruenbacher + if (error) + goto error; + ++ /* Report that AppArmor fs is enabled */ ++ info_message("AppArmor Filesystem Enabled"); + return 0; + +error: + destroy_apparmorfs(); + AA_ERROR("Error creating AppArmor securityfs\n"); ++ apparmor_disable(); + return error; +} + ++fs_initcall(create_apparmorfs); ++ --- /dev/null +++ b/security/apparmor/inline.h -@@ -0,0 +1,211 @@ +@@ -0,0 +1,250 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -582,6 +743,8 @@ Signed-off-by: Andreas Gruenbacher + +#include + ++#include "match.h" ++ +static inline int mediated_filesystem(struct inode *inode) +{ + return !(inode->i_sb->s_flags & MS_NOUSER); @@ -589,7 +752,33 @@ Signed-off-by: Andreas Gruenbacher + +static inline struct aa_task_context *aa_task_context(struct task_struct *task) +{ -+ return rcu_dereference((struct aa_task_context *)task->security); ++ return (struct aa_task_context *) rcu_dereference(task->security); ++} ++ ++static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) ++{ ++ if (ns) ++ kref_get(&(ns->count)); ++ ++ return ns; ++} ++ ++static inline void aa_put_namespace(struct aa_namespace *ns) ++{ ++ if (ns) ++ kref_put(&ns->count, free_aa_namespace_kref); ++} ++ ++ ++static inline struct aa_namespace *aa_find_namespace(const char *name) ++{ ++ struct aa_namespace *ns = NULL; ++ ++ read_lock(&profile_ns_list_lock); ++ ns = aa_get_namespace(__aa_find_namespace(name, &profile_ns_list)); ++ read_unlock(&profile_ns_list_lock); ++ ++ return ns; +} + +/** @@ -630,13 +819,14 @@ Signed-off-by: Andreas Gruenbacher + return profile; +} + -+static inline struct aa_profile *aa_find_profile(const char *name) ++static inline struct aa_profile *aa_find_profile(struct aa_namespace *ns, ++ const char *name) +{ + struct aa_profile *profile = NULL; + -+ read_lock(&profile_list_lock); -+ profile = aa_dup_profile(__aa_find_profile(name, &profile_list)); -+ read_unlock(&profile_list_lock); ++ read_lock(&ns->lock); ++ profile = aa_dup_profile(__aa_find_profile(name, &ns->profiles)); ++ read_unlock(&ns->lock); + + return profile; +} @@ -773,15 +963,25 @@ Signed-off-by: Andreas Gruenbacher + } +} + -+static inline unsigned int aa_match(struct aa_dfa *dfa, const char *pathname) ++static inline unsigned int aa_match(struct aa_dfa *dfa, const char *pathname, ++ int *audit_mask) +{ -+ return dfa ? aa_dfa_match(dfa, pathname) : 0; ++ if (dfa) ++ return aa_dfa_match(dfa, pathname, audit_mask); ++ if (audit_mask) ++ *audit_mask = 0; ++ return 0; ++} ++ ++static inline int dfa_audit_mask(struct aa_dfa *dfa, unsigned int state) ++{ ++ return ACCEPT_TABLE2(dfa)[state]; +} + +#endif /* __INLINE_H__ */ --- /dev/null +++ b/security/apparmor/list.c -@@ -0,0 +1,94 @@ +@@ -0,0 +1,156 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -797,9 +997,30 @@ Signed-off-by: Andreas Gruenbacher +#include "apparmor.h" +#include "inline.h" + -+/* list of all profiles and lock */ -+LIST_HEAD(profile_list); -+rwlock_t profile_list_lock = RW_LOCK_UNLOCKED; ++/* list of profile namespaces and lock */ ++LIST_HEAD(profile_ns_list); ++rwlock_t profile_ns_list_lock = RW_LOCK_UNLOCKED; ++ ++/** ++ * __aa_find_namespace - look up a profile namespace on the namespace list ++ * @name: name of namespace to find ++ * @head: list to search ++ * ++ * Returns a pointer to the namespace on the list, or NULL if no namespace ++ * called @name exists. The caller must hold the profile_ns_list_lock. ++ */ ++struct aa_namespace *__aa_find_namespace(const char *name, ++ struct list_head *head) ++{ ++ struct aa_namespace *ns; ++ ++ list_for_each_entry(ns, head, list) { ++ if (!strcmp(ns->name, name)) ++ return ns; ++ } ++ ++ return NULL; ++} + +/** + * __aa_find_profile - look up a profile on the profile list @@ -821,51 +1042,92 @@ Signed-off-by: Andreas Gruenbacher + return NULL; +} + ++static void aa_profile_list_release(struct list_head *head) ++{ ++ struct aa_profile *profile, *tmp; ++ list_for_each_entry_safe(profile, tmp, head, list) { ++ /* Remove the profile from each task context it is on. */ ++ lock_profile(profile); ++ profile->isstale = 1; ++ aa_unconfine_tasks(profile); ++ list_del_init(&profile->list); ++ unlock_profile(profile); ++ aa_put_profile(profile); ++ } ++} ++ +/** + * aa_profilelist_release - Remove all profiles from profile_list + */ -+void aa_profilelist_release(void) ++void aa_profile_ns_list_release(void) +{ -+ struct aa_profile *p, *tmp; ++ struct aa_namespace *ns, *tmp; + -+ write_lock(&profile_list_lock); -+ list_for_each_entry_safe(p, tmp, &profile_list, list) { -+ list_del_init(&p->list); -+ aa_put_profile(p); ++ /* Remove and release all the profiles on namespace profile lists. */ ++ write_lock(&profile_ns_list_lock); ++ list_for_each_entry_safe(ns, tmp, &profile_ns_list, list) { ++ write_lock(&ns->lock); ++ aa_profile_list_release(&ns->profiles); ++ list_del_init(&ns->list); ++ write_unlock(&ns->lock); ++ aa_put_namespace(ns); + } -+ write_unlock(&profile_list_lock); ++ write_unlock(&profile_ns_list_lock); +} + +static void *p_start(struct seq_file *f, loff_t *pos) +{ -+ struct aa_profile *node; ++ struct aa_namespace *ns; ++ struct aa_profile *profile; + loff_t l = *pos; -+ -+ read_lock(&profile_list_lock); -+ list_for_each_entry(node, &profile_list, list) -+ if (!l--) -+ return node; ++ read_lock(&profile_ns_list_lock); ++ if (l--) ++ return NULL; ++ list_for_each_entry(ns, &profile_ns_list, list) { ++ read_lock(&ns->lock); ++ list_for_each_entry(profile, &ns->profiles, list) ++ return profile; ++ read_unlock(&ns->lock); ++ } + return NULL; +} + +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ -+ struct list_head *lh = ((struct aa_profile *)p)->list.next; ++ struct aa_profile *profile = (struct aa_profile *) p; ++ struct list_head *lh = profile->list.next; ++ struct aa_namespace *ns; + (*pos)++; -+ return lh == &profile_list ? -+ NULL : list_entry(lh, struct aa_profile, list); ++ if (lh != &profile->ns->profiles) ++ return list_entry(lh, struct aa_profile, list); ++ ++ lh = profile->ns->list.next; ++ read_unlock(&profile->ns->lock); ++ while (lh != &profile_ns_list) { ++ ns = list_entry(lh, struct aa_namespace, list); ++ read_lock(&ns->lock); ++ list_for_each_entry(profile, &ns->profiles, list) ++ return profile; ++ read_unlock(&ns->lock); ++ lh = ns->list.next; ++ } ++ return NULL; +} + +static void p_stop(struct seq_file *f, void *v) +{ -+ read_unlock(&profile_list_lock); ++ read_unlock(&profile_ns_list_lock); +} + +static int seq_show_profile(struct seq_file *f, void *v) +{ + struct aa_profile *profile = (struct aa_profile *)v; -+ seq_printf(f, "%s (%s)\n", profile->name, -+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); ++ if (profile->ns == default_namespace) ++ seq_printf(f, "%s (%s)\n", profile->name, ++ PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); ++ else ++ seq_printf(f, ":%s:%s (%s)\n", profile->ns->name, profile->name, ++ PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); + return 0; +} + @@ -949,7 +1211,7 @@ Signed-off-by: Andreas Gruenbacher +the task would not be out of its quiescent period. --- /dev/null +++ b/security/apparmor/procattr.c -@@ -0,0 +1,155 @@ +@@ -0,0 +1,195 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -971,15 +1233,23 @@ Signed-off-by: Andreas Gruenbacher + if (profile) { + const char *mode_str = PROFILE_COMPLAIN(profile) ? + " (complain)" : " (enforce)"; -+ int mode_len, name_len; ++ int mode_len, name_len, ns_len = 0; + + mode_len = strlen(mode_str); + name_len = strlen(profile->name); -+ *len = mode_len + name_len + 1; ++ if (profile->ns != default_namespace) ++ ns_len = strlen(profile->ns->name) + 2; ++ *len = mode_len + ns_len + name_len + 1; + str = kmalloc(*len, GFP_ATOMIC); + if (!str) + return -ENOMEM; + ++ if (ns_len) { ++ *str++ = ':'; ++ memcpy(str, profile->ns->name, ns_len - 2); ++ str += ns_len - 2; ++ *str++ = ':'; ++ } + memcpy(str, profile->name, name_len); + str += name_len; + memcpy(str, mode_str, mode_len); @@ -1039,20 +1309,26 @@ Signed-off-by: Andreas Gruenbacher + +int aa_setprocattr_changeprofile(char *args) +{ -+ char *name; -+ u64 cookie; ++ char *name = args, *ns_name = NULL; + -+ name = split_token_from_name("change_profile", args, &cookie); -+ if (IS_ERR(name)) -+ return PTR_ERR(name); ++ if (name[0] == ':') { ++ char *split = strchr(&name[1], ':'); ++ if (split) { ++ *split = 0; ++ ns_name = &name[1]; ++ name = split + 1; ++ } ++ } + -+ return aa_change_profile(name, cookie); ++ return aa_change_profile(ns_name, name); +} + +int aa_setprocattr_setprofile(struct task_struct *task, char *args) +{ + struct aa_profile *old_profile, *new_profile; ++ struct aa_namespace *ns; + struct aa_audit sa; ++ char *name, *ns_name = NULL; + + memset(&sa, 0, sizeof(sa)); + sa.operation = "profile_set"; @@ -1062,15 +1338,38 @@ Signed-off-by: Andreas Gruenbacher + AA_DEBUG("%s: current %d\n", + __FUNCTION__, current->pid); + ++ name = args; ++ if (args[0] != '/') { ++ char *split = strchr(args, ':'); ++ if (split) { ++ *split = 0; ++ ns_name = args; ++ name = split + 1; ++ } ++ } ++ if (ns_name) ++ ns = aa_find_namespace(ns_name); ++ else ++ ns = aa_get_namespace(default_namespace); ++ if (!ns) { ++ sa.name = ns_name; ++ sa.info = "unknown namespace"; ++ aa_audit_reject(NULL, &sa); ++ aa_put_namespace(ns); ++ return -EINVAL; ++ } ++ +repeat: -+ if (strcmp(args, "unconfined") == 0) ++ if (strcmp(name, "unconfined") == 0) + new_profile = NULL; + else { -+ new_profile = aa_find_profile(args); ++ new_profile = aa_find_profile(ns, name); + if (!new_profile) { -+ sa.name = args; ++ sa.name = ns_name; ++ sa.name2 = name; + sa.info = "unknown profile"; + aa_audit_reject(NULL, &sa); ++ aa_put_namespace(ns); + return -EINVAL; + } + } @@ -1083,12 +1382,14 @@ Signed-off-by: Andreas Gruenbacher + error = PTR_ERR(old_profile); + if (error == -ESTALE) + goto repeat; ++ aa_put_namespace(ns); + return error; + } + + if (new_profile) { -+ sa.name = args; -+ sa.name2 = old_profile ? old_profile->name : ++ sa.name = ns_name; ++ sa.name2 = name; ++ sa.name3 = old_profile ? old_profile->name : + "unconfined"; + aa_audit_status(NULL, &sa); + } else { @@ -1101,6 +1402,7 @@ Signed-off-by: Andreas Gruenbacher + aa_audit_status(NULL, &sa); + } + } ++ aa_put_namespace(ns); + aa_put_profile(old_profile); + aa_put_profile(new_profile); + return 0; diff --git a/kernel-patches/for-mainline/apparmor-module_interface.diff b/kernel-patches/for-mainline/apparmor-module_interface.diff index 69b5c77b1..fb744c0eb 100644 --- a/kernel-patches/for-mainline/apparmor-module_interface.diff +++ b/kernel-patches/for-mainline/apparmor-module_interface.diff @@ -8,14 +8,14 @@ Signed-off-by: John Johansen Signed-off-by: Andreas Gruenbacher --- - security/apparmor/match.c | 248 +++++++++++++ - security/apparmor/match.h | 83 ++++ - security/apparmor/module_interface.c | 623 +++++++++++++++++++++++++++++++++++ - 3 files changed, 954 insertions(+) + security/apparmor/match.c | 364 +++++++++++++++ + security/apparmor/match.h | 87 +++ + security/apparmor/module_interface.c | 815 +++++++++++++++++++++++++++++++++++ + 3 files changed, 1266 insertions(+) --- /dev/null +++ b/security/apparmor/match.c -@@ -0,0 +1,248 @@ +@@ -0,0 +1,364 @@ +/* + * Copyright (C) 2007 Novell/SUSE + * @@ -32,6 +32,7 @@ Signed-off-by: Andreas Gruenbacher +#include +#include "apparmor.h" +#include "match.h" ++#include "inline.h" + +static struct table_header *unpack_table(void *blob, size_t bsize) +{ @@ -101,6 +102,7 @@ Signed-off-by: Andreas Gruenbacher + + switch(table->td_id) { + case YYTD_ID_ACCEPT: ++ case YYTD_ID_ACCEPT2: + case YYTD_ID_BASE: + dfa->tables[table->td_id - 1] = table; + if (table->td_flags != YYTD_DATA32) @@ -152,7 +154,8 @@ Signed-off-by: Andreas Gruenbacher + int error = -EPROTO; + + /* check that required tables exist */ -+ if (!(dfa->tables[YYTD_ID_ACCEPT -1 ] && ++ if (!(dfa->tables[YYTD_ID_ACCEPT - 1] && ++ dfa->tables[YYTD_ID_ACCEPT2 - 1] && + dfa->tables[YYTD_ID_DEF - 1] && + dfa->tables[YYTD_ID_BASE - 1] && + dfa->tables[YYTD_ID_NXT - 1] && @@ -162,7 +165,8 @@ Signed-off-by: Andreas Gruenbacher + /* accept.size == default.size == base.size */ + state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen; + if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen && -+ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen)) ++ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen && ++ state_count == dfa->tables[YYTD_ID_ACCEPT2 - 1]->td_lolen)) + goto out; + + /* next.size == chk.size */ @@ -195,13 +199,14 @@ Signed-off-by: Andreas Gruenbacher + + if (mode & ~AA_VALID_PERM_MASK) + goto out; ++ if (ACCEPT_TABLE2(dfa)[i] & ~AA_VALID_PERM2_MASK) ++ goto out; + -+ /* if MAY_EXEC, exactly one exec modifier must be set */ -+ if (mode & MAY_EXEC) { -+ mode &= AA_EXEC_MODIFIERS; -+ if (mode & (mode - 1)) -+ goto out; -+ } ++ /* if any exec modifier is set MAY_EXEC must be set */ ++ if ((mode & AA_USER_EXEC_TYPE) && !(mode & AA_USER_EXEC)) ++ goto out; ++ if ((mode & AA_OTHER_EXEC_TYPE) && !(mode & AA_OTHER_EXEC)) ++ goto out; + } + + error = 0; @@ -226,22 +231,76 @@ Signed-off-by: Andreas Gruenbacher +} + +/** -+ * aa_dfa_match - match @path against @dfa starting in @state -+ * @dfa: the dfa to match @path against -+ * @state: the state to start matching in -+ * @path: the path to match against the dfa ++ * aa_dfa_next_state_len - traverse @dfa to find state @str stops at ++ * @dfa: the dfa to match @str against ++ * @start: the state of the dfa to start matching in ++ * @str: the string of bytes to match against the dfa ++ * @len: length of the string of bytes to match + * -+ * aa_dfa_match will match the full path length and return the state it -+ * finished matching in. The final state is used to look up the accepting -+ * label. ++ * aa_dfa_next_state will match @str against the dfa and return the state it ++ * finished matching in. The final state can be used to look up the accepting ++ * label, or as the start state of a continuing match. ++ * ++ * aa_dfa_next_state could be implement using this function by doing ++ * return aa_dfa_next_state_len(dfa, start, str, strlen(str)); ++ * but that would require traversing the string twice and be slightly ++ * slower. + */ -+unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str) ++unsigned int aa_dfa_next_state_len(struct aa_dfa *dfa, unsigned int start, ++ const char *str, int len) +{ + u16 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u16 *next = NEXT_TABLE(dfa); + u16 *check = CHECK_TABLE(dfa); -+ unsigned int state = 1, pos; ++ unsigned int state = start, pos; ++ ++ if (state == 0) ++ return 0; ++ ++ /* current state is , matching character *str */ ++ if (dfa->tables[YYTD_ID_EC - 1]) { ++ u8 *equiv = EQUIV_TABLE(dfa); ++ for (; len; len--) { ++ pos = base[state] + equiv[(u8)*str++]; ++ if (check[pos] == state) ++ state = next[pos]; ++ else ++ state = def[state]; ++ } ++ } else { ++ for (; len; len--) { ++ pos = base[state] + (u8)*str++; ++ if (check[pos] == state) ++ state = next[pos]; ++ else ++ state = def[state]; ++ } ++ } ++ return state; ++} ++ ++/** ++ * aa_dfa_next_state - traverse @dfa to find state @str stops at ++ * @dfa: the dfa to match @str against ++ * @start: the state of the dfa to start matching in ++ * @str: the null terminated string of bytes to match against the dfa ++ * ++ * aa_dfa_next_state will match @str against the dfa and return the state it ++ * finished matching in. The final state can be used to look up the accepting ++ * label, or as the start state of a continuing match. ++ */ ++unsigned int aa_dfa_next_state(struct aa_dfa *dfa, unsigned int start, ++ const char *str) ++{ ++ u16 *def = DEFAULT_TABLE(dfa); ++ u32 *base = BASE_TABLE(dfa); ++ u16 *next = NEXT_TABLE(dfa); ++ u16 *check = CHECK_TABLE(dfa); ++ unsigned int state = start, pos; ++ ++ if (state == 0) ++ return 0; + + /* current state is , matching character *str */ + if (dfa->tables[YYTD_ID_EC - 1]) { @@ -262,11 +321,68 @@ Signed-off-by: Andreas Gruenbacher + state = def[state]; + } + } ++ return state; ++} ++ ++/** ++ * aa_dfa_null_transition - step to next state after null character ++ * @dfa: the dfa to match against ++ * @start: the state of the dfa to start matching in ++ * ++ * aa_dfa_null_transition transitions to the next state after a null ++ * character which is not used in standard matching and is only ++ * used to seperate pairs. ++ */ ++unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start) ++{ ++ return aa_dfa_next_state_len(dfa, start, "", 1); ++} ++ ++/** ++ * aa_dfa_match - find accept perm for @str in @dfa ++ * @dfa: the dfa to match @str against ++ * @str: the string to match against the dfa ++ * @audit_mask: the audit_mask for the final state ++ * ++ * aa_dfa_match will match @str and return the accept perms for the ++ * final state. ++ */ ++unsigned int aa_dfa_match(struct aa_dfa *dfa, const char *str, int *audit_mask) ++{ ++ int state = aa_dfa_next_state(dfa, DFA_START, str); ++ if (audit_mask) ++ *audit_mask = dfa_audit_mask(dfa, state); + return ACCEPT_TABLE(dfa)[state]; +} ++ ++/** ++ * aa_match_state - find accept perm and state for @str in @dfa ++ * @dfa: the dfa to match @str against ++ * @start: the state to start the match from ++ * @str: the string to match against the dfa ++ * @final: the state that the match finished in ++ * ++ * aa_match_state will match @str and return the accept perms, and @final ++ * state, the match occured in. ++ */ ++unsigned int aa_match_state(struct aa_dfa *dfa, unsigned int start, ++ const char *str, unsigned int *final) ++{ ++ unsigned int state; ++ if (dfa) { ++ state = aa_dfa_next_state(dfa, start, str); ++ if (final) ++ *final = state; ++ return ACCEPT_TABLE(dfa)[state]; ++ } ++ if (final) ++ *final = 0; ++ return 0; ++} ++ --- /dev/null +++ b/security/apparmor/match.h -@@ -0,0 +1,83 @@ +@@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 Novell/SUSE + * @@ -281,6 +397,8 @@ Signed-off-by: Andreas Gruenbacher +#ifndef __MATCH_H +#define __MATCH_H + ++#define DFA_START 1 ++ +/** + * The format used for transition tables is based on the GNU flex table + * file format (--tables-file option; see Table File Format in the flex @@ -306,6 +424,7 @@ Signed-off-by: Andreas Gruenbacher +#define YYTD_ID_DEF 4 +#define YYTD_ID_EC 5 +#define YYTD_ID_META 6 ++#define YYTD_ID_ACCEPT2 7 +#define YYTD_ID_NXT 8 + + @@ -327,6 +446,7 @@ Signed-off-by: Andreas Gruenbacher +#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK - 1]->td_data)) +#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC - 1]->td_data)) +#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT - 1]->td_data)) ++#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2 -1]->td_data)) + +struct aa_dfa { + struct table_header *tables[YYTD_ID_NXT]; @@ -352,7 +472,7 @@ Signed-off-by: Andreas Gruenbacher +#endif /* __MATCH_H */ --- /dev/null +++ b/security/apparmor/module_interface.c -@@ -0,0 +1,623 @@ +@@ -0,0 +1,815 @@ +/* + * Copyright (C) 1998-2007 Novell/SUSE + * @@ -413,6 +533,7 @@ Signed-off-by: Andreas Gruenbacher + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; ++ char *ns_name; +}; + +static inline int aa_inbounds(struct aa_ext *e, size_t size) @@ -664,6 +785,12 @@ Signed-off-by: Andreas Gruenbacher + + if (!aa_is_u32(e, &(profile->capabilities), NULL)) + goto fail; ++ if (!aa_is_u32(e, &(profile->audit_caps), NULL)) ++ goto fail; ++ if (!aa_is_u32(e, &(profile->quiet_caps), NULL)) ++ goto fail; ++ if (!aa_is_u32(e, &(profile->set_caps), NULL)) ++ goto fail; + + /* get file rules */ + profile->file_rules = aa_unpack_dfa(e); @@ -671,6 +798,10 @@ Signed-off-by: Andreas Gruenbacher + error = PTR_ERR(profile->file_rules); + profile->file_rules = NULL; + goto fail; ++ if (!aa_is_u16(e, &profile->audit_network[i], NULL)) ++ goto fail; ++ if (!aa_is_u16(e, &profile->quiet_network[i], NULL)) ++ goto fail; + } + + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) @@ -683,7 +814,8 @@ Signed-off-by: Andreas Gruenbacher + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; + sa.name = profile && profile->name ? profile->name : "unknown"; -+ sa.info = "failed to unpack profile"; ++ if (!sa.info) ++ sa.info = "failed to unpack profile"; + aa_audit_status(NULL, &sa); + + if (profile) @@ -713,7 +845,7 @@ Signed-off-by: Andreas Gruenbacher + } + + /* check that the interface version is currently supported */ -+ if (e->version != 3) { ++ if (e->version != 4) { + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; @@ -722,6 +854,12 @@ Signed-off-by: Andreas Gruenbacher + aa_audit_status(NULL, &sa); + return -EPROTONOSUPPORT; + } ++ ++ /* read the namespace if present */ ++ if (!aa_is_dynstring(e, &e->ns_name, "namespace")) { ++ e->ns_name = NULL; ++ } ++ + return 0; +} + @@ -733,10 +871,12 @@ Signed-off-by: Andreas Gruenbacher +ssize_t aa_add_profile(void *data, size_t size) +{ + struct aa_profile *profile = NULL; ++ struct aa_namespace *ns = NULL; + struct aa_ext e = { + .start = data, + .end = data + size, -+ .pos = data ++ .pos = data, ++ .ns_name = NULL + }; + ssize_t error = aa_verify_header(&e, "profile_load"); + if (error) @@ -747,16 +887,42 @@ Signed-off-by: Andreas Gruenbacher + return PTR_ERR(profile); + + mutex_lock(&aa_interface_lock); -+ write_lock(&profile_list_lock); -+ if (__aa_find_profile(profile->name, &profile_list)) { ++ write_lock(&profile_ns_list_lock); ++ if (e.ns_name) ++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list); ++ else ++ ns = default_namespace; ++ if (!ns) { ++ struct aa_namespace *new_ns; ++ write_unlock(&profile_ns_list_lock); ++ new_ns = alloc_aa_namespace(e.ns_name); ++ if (!new_ns) { ++ mutex_unlock(&aa_interface_lock); ++ return -ENOMEM; ++ } ++ write_lock(&profile_ns_list_lock); ++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list); ++ if (!ns) { ++ list_add(&new_ns->list, &profile_ns_list); ++ ns = new_ns; ++ } else ++ free_aa_namespace(new_ns); ++ } ++ ++ write_lock(&ns->lock); ++ if (__aa_find_profile(profile->name, &ns->profiles)) { + /* A profile with this name exists already. */ -+ write_unlock(&profile_list_lock); ++ write_unlock(&ns->lock); ++ write_unlock(&profile_ns_list_lock); + mutex_unlock(&aa_interface_lock); + aa_put_profile(profile); + return -EEXIST; + } -+ list_add(&profile->list, &profile_list); -+ write_unlock(&profile_list_lock); ++ profile->ns = aa_get_namespace(ns); ++ ns->profile_count++; ++ list_add(&profile->list, &ns->profiles); ++ write_unlock(&ns->lock); ++ write_unlock(&profile_ns_list_lock); + mutex_unlock(&aa_interface_lock); + + return size; @@ -796,12 +962,15 @@ Signed-off-by: Andreas Gruenbacher +ssize_t aa_replace_profile(void *udata, size_t size) +{ + struct aa_profile *old_profile, *new_profile; ++ struct aa_namespace *ns; + struct aa_task_context *new_cxt; + struct aa_ext e = { + .start = udata, + .end = udata + size, -+ .pos = udata ++ .pos = udata, ++ .ns_name = NULL + }; ++ + ssize_t error = aa_verify_header(&e, "profile_replace"); + if (error) + return error; @@ -811,16 +980,42 @@ Signed-off-by: Andreas Gruenbacher + return PTR_ERR(new_profile); + + mutex_lock(&aa_interface_lock); -+ write_lock(&profile_list_lock); -+ old_profile = __aa_find_profile(new_profile->name, &profile_list); ++ write_lock(&profile_ns_list_lock); ++ if (e.ns_name) ++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list); ++ else ++ ns = default_namespace; ++ if (!ns) { ++ struct aa_namespace *new_ns; ++ write_unlock(&profile_ns_list_lock); ++ new_ns = alloc_aa_namespace(e.ns_name); ++ if (!new_ns) { ++ mutex_unlock(&aa_interface_lock); ++ return -ENOMEM; ++ } ++ write_lock(&profile_ns_list_lock); ++ ns = __aa_find_namespace(e.ns_name, &profile_ns_list); ++ if (!ns) { ++ list_add(&new_ns->list, &profile_ns_list); ++ ns = new_ns; ++ } else ++ free_aa_namespace(new_ns); ++ } ++ ++ write_lock(&ns->lock); ++ old_profile = __aa_find_profile(new_profile->name, &ns->profiles); + if (old_profile) { + lock_profile(old_profile); + old_profile->isstale = 1; -+ unlock_profile(old_profile); + list_del_init(&old_profile->list); ++ unlock_profile(old_profile); ++ ns->profile_count--; + } -+ list_add(&new_profile->list, &profile_list); -+ write_unlock(&profile_list_lock); ++ new_profile->ns = aa_get_namespace(ns); ++ ns->profile_count++; ++ list_add(&new_profile->list, &ns->profiles); ++ write_unlock(&ns->lock); ++ write_unlock(&profile_ns_list_lock); + + if (!old_profile) + goto out; @@ -865,32 +1060,148 @@ Signed-off-by: Andreas Gruenbacher + * remove a profile from the profile list and all aa_task_context references + * to said profile. + */ -+ssize_t aa_remove_profile(const char *name, size_t size) ++ssize_t aa_remove_profile(char *name, size_t size) +{ ++ struct aa_namespace *ns; + struct aa_profile *profile; + + mutex_lock(&aa_interface_lock); -+ write_lock(&profile_list_lock); -+ profile = __aa_find_profile(name, &profile_list); ++ write_lock(&profile_ns_list_lock); ++ ++ if (name[0] == '/') { ++ ns = default_namespace; ++ } else { ++ char *split = strchr(name, ':'); ++ if (!split) ++ goto noent; ++ *split = 0; ++ ns = __aa_find_namespace(name, &profile_ns_list); ++ name = split + 1; ++ } ++ ++ if (!ns) ++ goto noent; ++ write_lock(&ns->lock); ++ profile = __aa_find_profile(name, &ns->profiles); + if (!profile) { -+ write_unlock(&profile_list_lock); -+ mutex_unlock(&aa_interface_lock); -+ return -ENOENT; ++ write_unlock(&ns->lock); ++ goto noent; + } + + /* Remove the profile from each task context it is on. */ + lock_profile(profile); + profile->isstale = 1; + aa_unconfine_tasks(profile); -+ unlock_profile(profile); -+ -+ /* Release the profile itself. */ + list_del_init(&profile->list); -+ aa_put_profile(profile); -+ write_unlock(&profile_list_lock); ++ ns->profile_count--; ++ unlock_profile(profile); ++ /* Release the profile itself. */ ++ write_unlock(&ns->lock); ++ /* check to see if the namespace has become stale */ ++ if (ns != default_namespace && ns->profile_count == 0) { ++ list_del_init(&ns->list); ++ aa_put_namespace(ns); ++ } ++ write_unlock(&profile_ns_list_lock); + mutex_unlock(&aa_interface_lock); ++ aa_put_profile(profile); + + return size; ++ ++noent: ++ write_unlock(&profile_ns_list_lock); ++ mutex_unlock(&aa_interface_lock); ++ return -ENOENT; ++} ++ ++/** ++ * free_aa_namespace_kref - free aa_namespace by kref (see aa_put_namespace) ++ * @kr: kref callback for freeing of a namespace ++ */ ++void free_aa_namespace_kref(struct kref *kref) ++{ ++ struct aa_namespace *ns=container_of(kref, struct aa_namespace, count); ++ ++ free_aa_namespace(ns); ++} ++ ++/** ++ * alloc_aa_namespace - allocate, initialize and return a new namespace ++ * @name: a preallocated name ++ * Returns NULL on failure. ++ */ ++struct aa_namespace *alloc_aa_namespace(char *name) ++{ ++ struct aa_namespace *ns; ++ ++ ns = kzalloc(sizeof(*ns), GFP_KERNEL); ++ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns); ++ if (ns) { ++ ns->name = name; ++ INIT_LIST_HEAD(&ns->list); ++ INIT_LIST_HEAD(&ns->profiles); ++ kref_init(&ns->count); ++ rwlock_init(&ns->lock); ++ ++ ns->null_complain_profile = alloc_aa_profile(); ++ if (!ns->null_complain_profile) { ++ if (!name) ++ kfree(ns->name); ++ kfree(ns); ++ return NULL; ++ } ++ ns->null_complain_profile->name = ++ kstrdup("null-complain-profile", GFP_KERNEL); ++ if (!ns->null_complain_profile->name) { ++ free_aa_profile(ns->null_complain_profile); ++ if (!name) ++ kfree(ns->name); ++ kfree(ns); ++ return NULL; ++ } ++ ns->null_complain_profile->flags.complain = 1; ++ /* null_complain_profile doesn't contribute to ns ref count */ ++ ns->null_complain_profile->ns = ns; ++ } ++ return ns; ++} ++ ++/** ++ * free_aa_namespace - free a profile namespace ++ * @namespace: the namespace to free ++ * ++ * Free a namespace. All references to the namespace must have been put. ++ * If the namespace was referenced by a profile confining a task, ++ * free_aa_namespace will be called indirectly (through free_aa_profile) ++ * from an rcu callback routine, so we must not sleep here. ++ */ ++void free_aa_namespace(struct aa_namespace *ns) ++{ ++ AA_DEBUG("%s(%p)\n", __FUNCTION__, ns); ++ ++ if (!ns) ++ return; ++ ++ /* namespace still contains profiles -- invalid */ ++ if (!list_empty(&ns->profiles)) { ++ AA_ERROR("%s: internal error, " ++ "namespace '%s' still contains profiles\n", ++ __FUNCTION__, ++ ns->name); ++ BUG(); ++ } ++ if (!list_empty(&ns->list)) { ++ AA_ERROR("%s: internal error, " ++ "namespace '%s' still on list\n", ++ __FUNCTION__, ++ ns->name); ++ BUG(); ++ } ++ /* null_complain_profile doesn't contribute to ns ref counting */ ++ ns->null_complain_profile->ns = NULL; ++ aa_put_profile(ns->null_complain_profile); ++ kfree(ns->name); ++ kfree(ns); +} + +/** @@ -940,7 +1251,7 @@ Signed-off-by: Andreas Gruenbacher + if (!profile) + return; + -+ /* profile is still on global profile list -- invalid */ ++ /* profile is still on profile namespace list -- invalid */ + if (!list_empty(&profile->list)) { + AA_ERROR("%s: internal error, " + "profile '%s' still on global list\n", @@ -948,6 +1259,7 @@ Signed-off-by: Andreas Gruenbacher + profile->name); + BUG(); + } ++ aa_put_namespace(profile->ns); + + aa_match_free(profile->file_rules); + diff --git a/kernel-patches/for-mainline/apparmor-network.diff b/kernel-patches/for-mainline/apparmor-network.diff index 96f8e4761..1649b2fd3 100644 --- a/kernel-patches/for-mainline/apparmor-network.diff +++ b/kernel-patches/for-mainline/apparmor-network.diff @@ -1,10 +1,10 @@ --- security/apparmor/Makefile | 7 + security/apparmor/apparmor.h | 9 ++ - security/apparmor/lsm.c | 147 ++++++++++++++++++++++++++++++++++- - security/apparmor/main.c | 106 +++++++++++++++++++++++++ - security/apparmor/module_interface.c | 20 ++++ - 5 files changed, 285 insertions(+), 4 deletions(-) + security/apparmor/lsm.c | 129 ++++++++++++++++++++++++++++++++++- + security/apparmor/main.c | 106 ++++++++++++++++++++++++++++ + security/apparmor/module_interface.c | 20 +++++ + 5 files changed, 267 insertions(+), 4 deletions(-) --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -70,7 +70,7 @@ #include "apparmor.h" #include "inline.h" -@@ -653,6 +654,133 @@ static void apparmor_task_free_security( +@@ -653,6 +654,117 @@ static void apparmor_task_free_security( aa_release(task); } @@ -184,27 +184,11 @@ + + return aa_revalidate_sk(sk, "socket_shutdown"); +} -+ -+static int apparmor_socket_getpeersec_stream(struct socket *sock, -+ char __user *optval, int __user *optlen, unsigned len) -+{ -+ struct sock *sk = sock->sk; -+ -+ return aa_revalidate_sk(sk, "socket_getpeersec_stream"); -+} -+ -+static int apparmor_socket_getpeersec_dgram(struct socket *sock, -+ struct sk_buff *skb, u32 *secid) -+{ -+ struct sock *sk = sock->sk; -+ -+ return aa_revalidate_sk(sk, "socket_getpeersec_dgram"); -+} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { -@@ -753,9 +881,6 @@ struct security_operations apparmor_ops +@@ -753,9 +865,6 @@ struct security_operations apparmor_ops .capable = apparmor_capable, .syslog = cap_syslog, @@ -214,7 +198,7 @@ .bprm_apply_creds = cap_bprm_apply_creds, .bprm_set_security = apparmor_bprm_set_security, .bprm_secureexec = apparmor_bprm_secureexec, -@@ -791,6 +916,22 @@ struct security_operations apparmor_ops +@@ -791,6 +900,20 @@ struct security_operations apparmor_ops .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, @@ -232,8 +216,6 @@ + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, -+ .socket_getpeersec_stream = apparmor_socket_getpeersec_stream, -+ .socket_getpeersec_dgram = apparmor_socket_getpeersec_dgram, }; void info_message(const char *str) diff --git a/kernel-patches/for-mainline/series b/kernel-patches/for-mainline/series index 51ce9e5c6..3857b7f51 100644 --- a/kernel-patches/for-mainline/series +++ b/kernel-patches/for-mainline/series @@ -50,33 +50,35 @@ apparmor-module_interface.diff apparmor-misc.diff apparmor-intree.diff -fix-rcu-deref.diff -fix-name-errorpath.diff -change-profile-kernel-v2.diff -expand-dfa.diff +#fix-rcu-deref.diff +#fix-name-errorpath.diff +#change-profile-kernel-v2.diff +#expand-dfa.diff #slash-null-dfa.diff -profile-namespaces.diff -owner-perm-set.diff -apparmor-link-pairs.diff -apparmor-bootdisable.diff -apparmor-builtin-only.diff -apparmor-security-goal.diff -apparmor-features.diff -split_init.diff -apparmor-fix-sysctl-refcount.diff -apparmor-fix-lock-letter.diff -fix-link-subset.diff -hat_perm.diff -extend-x-mods.diff -no-safex-link-subset.diff -apparmor-create-append.diff -apparmor-failed-name-error.diff -audit-uid.diff -apparmor-secondary-accept.diff -apparmor-audit-flags2.diff -fix-profile-namespaces.diff -fix-dfa.diff -cap-set.diff +#profile-namespaces.diff +#owner-perm-set.diff +#apparmor-link-pairs.diff +#apparmor-bootdisable.diff +#apparmor-builtin-only.diff +#apparmor-security-goal.diff +#apparmor-features.diff +#split_init.diff +#apparmor-fix-sysctl-refcount.diff +#apparmor-fix-lock-letter.diff +#fix-link-subset.diff +#hat_perm.diff +#extend-x-mods.diff +#no-safex-link-subset.diff +#apparmor-create-append.diff +#apparmor-failed-name-error.diff +#audit-uid.diff +#apparmor-secondary-accept.diff +#apparmor-audit-flags2.diff +#fix-profile-namespaces.diff +#fix-dfa.diff +#cap-set.diff + + #foobar.diff # # NOT YET # ecryptfs-d_revalidate.diff @@ -109,6 +111,6 @@ cap-set.diff #FS2.1.3_fix-unionfs-with-AppArmor.patch apparmor-network.diff -fix-net.diff +#fix-net.diff rlimits.diff audit-log-type-in-syslog.diff