diff -urN linux-2.6.31.org/Documentation/kernel-parameters.txt linux-2.6.31/Documentation/kernel-parameters.txt --- linux-2.6.31.org/Documentation/kernel-parameters.txt 2009-09-10 00:13:59.000000000 +0200 +++ linux-2.6.31/Documentation/kernel-parameters.txt 2009-09-10 22:17:58.000000000 +0200 @@ -90,6 +90,7 @@ A lot of drivers has their options described inside of Documentation/scsi/. SECURITY Different security models are enabled. + SECURITY_DEFAULT set a default security module SELINUX SELinux support is enabled. SERIAL Serial support is enabled. SH SuperH architecture is enabled. diff -urN linux-2.6.31.org/include/linux/audit.h linux-2.6.31/include/linux/audit.h --- linux-2.6.31.org/include/linux/audit.h 2009-09-10 00:13:59.000000000 +0200 +++ linux-2.6.31/include/linux/audit.h 2009-09-10 22:18:05.000000000 +0200 @@ -33,7 +33,7 @@ * 1200 - 1299 messages internal to the audit daemon * 1300 - 1399 audit event messages * 1400 - 1499 SE Linux use - * 1500 - 1599 kernel LSPP events + * 1500 - 1599 AppArmor use * 1600 - 1699 kernel crypto events * 1700 - 1799 kernel anomaly records * 1800 - 1899 kernel integrity events @@ -122,6 +122,14 @@ #define AUDIT_MAC_UNLBL_STCADD 1416 /* NetLabel: add a static label */ #define AUDIT_MAC_UNLBL_STCDEL 1417 /* NetLabel: del a static label */ +#define AUDIT_APPARMOR_AUDIT 1501 /* AppArmor audited grants */ +#define AUDIT_APPARMOR_ALLOWED 1502 /* Allowed Access for learning */ +#define AUDIT_APPARMOR_DENIED 1503 +#define AUDIT_APPARMOR_HINT 1504 /* Process Tracking information */ +#define AUDIT_APPARMOR_STATUS 1505 /* Changes in config */ +#define AUDIT_APPARMOR_ERROR 1506 /* Internal AppArmor Errors */ +#define AUDIT_APPARMOR_KILL 1507 /* AppArmor killing processes */ + #define AUDIT_FIRST_KERN_ANOM_MSG 1700 #define AUDIT_LAST_KERN_ANOM_MSG 1799 #define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */ diff -urN linux-2.6.31.org/security/Kconfig linux-2.6.31/security/Kconfig --- linux-2.6.31.org/security/Kconfig 2009-09-10 00:13:59.000000000 +0200 +++ linux-2.6.31/security/Kconfig 2009-09-11 08:37:07.888942907 +0200 @@ -132,6 +132,7 @@ source security/selinux/Kconfig source security/smack/Kconfig source security/tomoyo/Kconfig +source security/apparmor/Kconfig source security/integrity/ima/Kconfig diff -urN kernel.org/security/apparmor/apparmorfs.c kernel/security/apparmor/apparmorfs.c --- kernel.org/security/apparmor/apparmorfs.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/apparmorfs.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,391 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /proc//attr interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/policy.h" +#include "include/policy_interface.h" + +static char *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, size_t copy_size, + loff_t *pos, const char *operation) +{ + const struct cred *cred; + struct aa_profile *profile; + char *data; + + if (*pos != 0) { + /* only writes from pos 0, that is complete writes */ + data = ERR_PTR(-ESPIPE); + goto out; + } + + /* + * Don't allow confined processes to load/replace/remove profiles. + * No sane person would add rules allowing this to a profile + * but we enforce the restriction anyways. + */ + cred = aa_current_policy(&profile); + if (profile) { + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.operation = operation; + sa.gfp_mask = GFP_KERNEL; + sa.error = -EACCES; + data = ERR_PTR(aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa, + NULL)); + goto out; + } + + data = vmalloc(alloc_size); + if (data == NULL) { + data = ERR_PTR(-ENOMEM); + goto out; + } + + if (copy_from_user(data, userbuf, copy_size)) { + vfree(data); + data = ERR_PTR(-EFAULT); + goto out; + } + +out: + return data; +} + +static struct aa_profile *next_profile(struct aa_profile *profile) +{ + struct aa_profile *parent; + struct aa_namespace *ns = profile->ns; + + if (!list_empty(&profile->base.profiles)) + return list_first_entry(&profile->base.profiles, + struct aa_profile, base.list); + + parent = profile->parent; + while (parent) { + list_for_each_entry_continue(profile, &parent->base.profiles, + base.list) + return profile; + profile = parent; + parent = parent->parent; + } + + list_for_each_entry_continue(profile, &ns->base.profiles, base.list) + return profile; + + read_unlock(&ns->base.lock); + list_for_each_entry_continue(ns, &ns_list, base.list) { + read_lock(&ns->base.lock); + return list_first_entry(&ns->base.profiles, struct aa_profile, + base.list); + read_unlock(&ns->base.lock); + } + return NULL; +} + +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(ns_list_lock) +{ + struct aa_namespace *ns; + loff_t l = *pos; + + read_lock(&ns_list_lock); + if (!list_empty(&ns_list)) { + struct aa_profile *profile = NULL; + ns = list_first_entry(&ns_list, typeof(*ns), base.list); + read_lock(&ns->base.lock); + if (!list_empty(&ns->base.profiles)) { + profile = list_first_entry(&ns->base.profiles, + typeof(*profile), base.list); + for ( ; profile && l > 0; l--) + profile = next_profile(profile); + return profile; + } else + read_unlock(&ns->base.lock); + } + return NULL; +} + +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = (struct aa_profile *) p; + + (*pos)++; + profile = next_profile(profile); + + return profile; +} + +static void p_stop(struct seq_file *f, void *p) + __releases(ns_list_lock) +{ + struct aa_profile *profile = (struct aa_profile *) p; + + if (profile) + read_unlock(&profile->ns->base.lock); + read_unlock(&ns_list_lock); +} + +static void print_name(struct seq_file *f, struct aa_profile *profile) +{ + if (profile->parent) { + print_name(f, profile->parent); + seq_printf(f, "//"); + } + seq_printf(f, "%s", profile->base.name); +} + +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + + if (profile->ns != default_namespace) + seq_printf(f, ":%s:", profile->ns->base.name); + print_name(f, profile); + seq_printf(f, " (%s)\n", + PROFILE_COMPLAIN(profile) ? "complain" : "enforce"); + + return 0; +} + +/* Used in apparmorfs.c */ +static struct seq_operations apparmorfs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int aa_profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &apparmorfs_profiles_op); +} + + +static int aa_profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static struct file_operations apparmorfs_profiles_fops = { + .open = aa_profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = aa_profiles_release, +}; + +/* apparmor/matching */ +static ssize_t aa_matching_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) +{ + const char *matching = "pattern=aadfa audit perms=crwxamlk/ user::other"; + + return simple_read_from_buffer(buf, size, ppos, matching, + strlen(matching)); +} + +static struct file_operations apparmorfs_matching_fops = { + .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.1 capability=2.0 network=1.0 " + "change_hat=1.5 change_profile=1.1 " + "aanamespaces=1.1 rlimit=1.1"; + + 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) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(buf, size, size, pos, "profile_load"); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_interface_add_profiles(data, size); + vfree(data); + } + + return error; +} + + +static struct file_operations apparmorfs_profile_load = { + .write = aa_profile_load +}; + +/* apparmor/.replace */ +static ssize_t aa_profile_replace(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + data = aa_simple_write_to_buffer(buf, size, size, pos, + "profile_replace"); + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_interface_replace_profiles(data, size); + vfree(data); + } + + return error; +} + + +static struct file_operations apparmorfs_profile_replace = { + .write = aa_profile_replace +}; + +/* apparmor/.remove */ +static ssize_t aa_profile_remove(struct file *f, const char __user *buf, + size_t size, loff_t *pos) +{ + char *data; + ssize_t error; + + /* + * aa_remove_profile needs a null terminated string so 1 extra + * byte is allocated and the copied data is null terminated. + */ + data = aa_simple_write_to_buffer(buf, size + 1, size, pos, + "profile_remove"); + + error = PTR_ERR(data); + if (!IS_ERR(data)) { + data[size] = 0; + error = aa_interface_remove_profiles(data, size); + vfree(data); + } + + return error; +} + +static struct file_operations apparmorfs_profile_remove = { + .write = aa_profile_remove +}; + +static struct dentry *apparmorfs_dentry; +struct dentry *apparmorfs_null; +struct vfsmount *apparmorfs_mnt; + +static void aafs_remove(const char *name) +{ + struct dentry *dentry; + + dentry = lookup_one_len(name, apparmorfs_dentry, strlen(name)); + if (!IS_ERR(dentry)) { + securityfs_remove(dentry); + dput(dentry); + } +} + +static int aafs_create(const char *name, int mask, struct file_operations *fops) +{ + struct dentry *dentry; + + dentry = securityfs_create_file(name, S_IFREG | mask, apparmorfs_dentry, + NULL, fops); + + return IS_ERR(dentry) ? PTR_ERR(dentry) : 0; +} + +void destroy_apparmorfs(void) +{ + if (apparmorfs_dentry) { + aafs_remove(".remove"); + aafs_remove(".replace"); + aafs_remove(".load"); + aafs_remove("matching"); + aafs_remove("features"); + aafs_remove("profiles"); + securityfs_remove(apparmorfs_dentry); + apparmorfs_dentry = NULL; + } +} + +int create_apparmorfs(void) +{ + int error; + + if (!apparmor_initialized) + return 0; + + if (apparmorfs_dentry) { + AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); + return -EEXIST; + } + + apparmorfs_dentry = securityfs_create_dir("apparmor", NULL); + if (IS_ERR(apparmorfs_dentry)) { + error = PTR_ERR(apparmorfs_dentry); + apparmorfs_dentry = NULL; + goto error; + } + error = aafs_create("profiles", 0440, &apparmorfs_profiles_fops); + if (error) + goto error; + 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; + error = aafs_create(".replace", 0640, &apparmorfs_profile_replace); + if (error) + goto error; + error = aafs_create(".remove", 0640, &apparmorfs_profile_remove); + if (error) + goto error; + + /* TODO: add support for apparmorfs_null and apparmorfs_mnt */ + + /* 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); + diff -urN kernel.org/security/apparmor/audit.c kernel/security/apparmor/audit.c --- kernel.org/security/apparmor/audit.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/audit.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,153 @@ +/* + * AppArmor security module + * + * This file contains AppArmor auditing functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/policy.h" + +const char *audit_mode_names[] = { + "normal", + "quiet_denied", + "quiet" + "noquiet", + "all" +}; + +static char* aa_audit_type[] = { + "APPARMOR_AUDIT", + "APPARMOR_ALLOWED", + "APPARMOR_DENIED", + "APPARMOR_HINT", + "APPARMOR_STATUS", + "APPARMOR_ERROR", + "APPARMOR_KILLED" +}; + +/* + * TODO: + * user auditing - netlink interface + * system control of whether user audit messages go to system log + */ +static int aa_audit_base(int type, struct aa_profile *profile, + struct aa_audit *sa, struct audit_context *audit_cxt, + void(*cb)(struct audit_buffer *, void *)) +{ + struct audit_buffer *ab = NULL; + + if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED) + type = AUDIT_APPARMOR_KILL; + + ab = audit_log_start(audit_cxt, sa->gfp_mask, type); + + if (!ab) { + AA_ERROR("(%d) Unable to log event of type (%d)\n", + -ENOMEM, type); + /* don't fail operations in complain mode even if logging + * fails */ + return type == AUDIT_APPARMOR_ALLOWED ? 0 : -ENOMEM; + } + + if (g_apparmor_audit_header) + audit_log_format(ab, "type=%s ", + aa_audit_type[type - AUDIT_APPARMOR_AUDIT]); + + 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) + audit_log_format(ab, " error=%d", sa->error); + } + + audit_log_format(ab, " pid=%d", sa->task ?sa->task->pid : current->pid); + + if (profile) { + pid_t pid = sa->task ? sa->task->real_parent->pid : + current->real_parent->pid; + audit_log_format(ab, " parent=%d", pid); + audit_log_format(ab, " profile="); + audit_log_untrustedstring(ab, profile->fqname); + + if (profile->ns != default_namespace) { + audit_log_format(ab, " namespace="); + audit_log_untrustedstring(ab, profile->ns->base.name); + } + } + + if (cb) + cb(ab, sa); + + audit_log_end(ab); + + if (type == AUDIT_APPARMOR_KILL) + (void)send_sig_info(SIGKILL, NULL, + sa->task ? sa->task : current); + + return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error; +} + +/** + * aa_audit - Log an audit event to the audit subsystem + * @type: audit type for the message + * @profile: profile to check against + * @sa: audit event + */ +int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa, + void(*cb)(struct audit_buffer *, void *)) +{ + struct audit_context *audit_cxt; + audit_cxt = g_apparmor_logsyscall ? current->audit_context : NULL; + + if (type == AUDIT_APPARMOR_AUTO) { + if (likely(!sa->error)) { + if (PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else if (PROFILE_COMPLAIN(profile)) + type = AUDIT_APPARMOR_ALLOWED; + else + type = AUDIT_APPARMOR_DENIED; + } + if (PROFILE_AUDIT_MODE(profile) == AUDIT_QUIET || + (type == AUDIT_APPARMOR_DENIED && + PROFILE_AUDIT_MODE(profile) == AUDIT_QUIET)) + return sa->error; + + return aa_audit_base(type, profile, sa, audit_cxt, cb); +} + +/** + * 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, + void(*cb)(struct audit_buffer *, void *)) +{ + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.operation = "syscall"; + sa.info = msg; + sa.gfp_mask = gfp; + sa.error = -EACCES; + + return aa_audit_base(AUDIT_APPARMOR_DENIED, profile, &sa, + current->audit_context, NULL); +} diff -urN kernel.org/security/apparmor/capability.c kernel/security/apparmor/capability.c --- kernel.org/security/apparmor/capability.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/capability.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,122 @@ +/* + * AppArmor security module + * + * This file contains AppArmor capability mediation functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/policy.h" +#include "include/audit.h" + +/* + * Table of capability names: we generate it from capabilities.h. + */ +#include "capability_names.h" + +struct audit_cache { + struct task_struct *task; + kernel_cap_t caps; +}; + +static DEFINE_PER_CPU(struct audit_cache, audit_cache); + +struct aa_audit_caps { + struct aa_audit base; + + int cap; +}; + +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_caps *sa = va; + + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, capability_names[sa->cap]); +} + +static int aa_audit_caps(struct aa_profile *profile, struct aa_audit_caps *sa) +{ + struct audit_cache *ent; + int type = AUDIT_APPARMOR_AUTO; + + if (likely(!sa->base.error)) { + /* test if auditing is being forced */ + if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) && + !cap_raised(profile->caps.audit, sa->cap))) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else if (PROFILE_KILL(profile) || + cap_raised(profile->caps.kill, sa->cap)) { + type = AUDIT_APPARMOR_KILL; + } else if (cap_raised(profile->caps.quiet, sa->cap) && + PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET && + PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) { + /* quiet auditing */ + return sa->base.error; + } + + /* Do simple duplicate message elimination */ + ent = &get_cpu_var(audit_cache); + if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) { + if (PROFILE_COMPLAIN(profile)) + return 0; + return sa->base.error; + } else { + ent->task = sa->base.task; + cap_raise(ent->caps, sa->cap); + } + put_cpu_var(audit_cache); + + return aa_audit(type, profile, &sa->base, audit_cb); +} + +int aa_profile_capable(struct aa_profile *profile, int cap) +{ + return cap_raised(profile->caps.allowed, cap) ? 0 : -EPERM; +} + +/** + * aa_capable - test permission to use capability + * @task: task doing capability test against + * @profile: profile confining @task + * @cap: capability to be tested + * @audit: whether an audit record should be generated + * + * Look up capability in profile capability set. + * Returns 0 on success, or else an error code. + */ +int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, + int audit) +{ + int error = aa_profile_capable(profile, cap); + struct aa_audit_caps sa; + + if (!audit) { + if (PROFILE_COMPLAIN(profile)) + return 0; + return error; + } + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = "capable"; + sa.base.task = task; + sa.base.gfp_mask = GFP_ATOMIC; + sa.base.error = error; + sa.cap = cap; + + return aa_audit_caps(profile, &sa); +} diff -urN kernel.org/security/apparmor/context.c kernel/security/apparmor/context.c --- kernel.org/security/apparmor/context.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/context.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,209 @@ +/* + * AppArmor security module + * + * This file contains AppArmor functions used to manipulate object security + * contexts. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include "include/context.h" +#include "include/policy.h" + + + +struct aa_task_context *aa_alloc_task_context(gfp_t flags) +{ + return kzalloc(sizeof(struct aa_task_context), flags); +} + +void aa_free_task_context(struct aa_task_context *cxt) +{ + if (cxt) { + aa_put_profile(cxt->sys.profile); + aa_put_profile(cxt->sys.previous); + aa_put_profile(cxt->sys.onexec); + + memset(cxt, 0, sizeof(*cxt)); + kfree(cxt); + } +} + +/* + * duplicate a task context, incrementing reference counts + */ +struct aa_task_context *aa_dup_task_context(struct aa_task_context *old_cxt, + gfp_t gfp) +{ + struct aa_task_context *cxt; + + cxt = kmemdup(old_cxt, sizeof(*cxt), gfp); + if (!cxt) + return NULL; + + aa_get_profile(cxt->sys.profile); + aa_get_profile(cxt->sys.previous); + aa_get_profile(cxt->sys.onexec); + + return cxt; +} + +/** + * aa_cred_policy - obtain cred's profiles + * @cred: cred to obtain profiles from + * @sys: return system profile + * does NOT increment reference count + */ +void aa_cred_policy(const struct cred *cred, struct aa_profile **sys) +{ + struct aa_task_context *cxt = cred->security; + BUG_ON(!cxt); + *sys = aa_filtered_profile(aa_profile_newest(cxt->sys.profile)); +} + +/** + * aa_get_task_policy - get the cred with the task policy, and current profiles + * @task: task to get policy of + * @sys: return - pointer to system profile + * + * Only gets the cred ref count which has ref counts on the profiles returned + */ +struct cred *aa_get_task_policy(const struct task_struct *task, + struct aa_profile **sys) +{ + struct cred *cred = get_task_cred(task); + aa_cred_policy(cred, sys); + return cred; +} + +void aa_put_task_policy(struct cred *cred) +{ + put_cred(cred); +} + +static void replace_group(struct aa_task_cxt_group *cgrp, + struct aa_profile *profile) +{ + if (cgrp->profile == profile) + return; + + if (!profile || (profile->flags & PFLAG_UNCONFINED) || + (cgrp->profile && cgrp->profile->ns != profile->ns)) { + aa_put_profile(cgrp->previous); + aa_put_profile(cgrp->onexec); + cgrp->previous = NULL; + cgrp->onexec = NULL; + cgrp->token = 0; + } + aa_put_profile(cgrp->profile); + cgrp->profile = aa_get_profile(profile); +} + +/** + * aa_replace_current_profiles - replace the current tasks profiles + * @sys: new system profile + * + * Returns: error on failure + */ +int aa_replace_current_profiles(struct aa_profile *sys) +{ + struct aa_task_context *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = new->security; + replace_group(&cxt->sys, sys); + + commit_creds(new); + return 0; +} + +int aa_set_current_onexec(struct aa_profile *sys) +{ + struct aa_task_context *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = new->security; + aa_put_profile(cxt->sys.onexec); + cxt->sys.onexec = aa_get_profile(sys); + + commit_creds(new); + return 0; +} + +/* + * Do the actual cred switching of a changehat + * profile must be valid + */ +int aa_set_current_hat(struct aa_profile *profile, u64 token) +{ + struct aa_task_context *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = new->security; + if (!cxt->sys.previous) { + cxt->sys.previous = cxt->sys.profile; + cxt->sys.token = token; + } else if (cxt->sys.token == token) { + aa_put_profile(cxt->sys.profile); + } else { + /* previous_profile && cxt->token != token */ + abort_creds(new); + return -EACCES; + } + cxt->sys.profile = aa_get_profile(profile); + /* clear exec on switching context */ + aa_put_profile(cxt->sys.onexec); + cxt->sys.onexec = NULL; + + commit_creds(new); + return 0; +} + +/* + * Attempt to return out of a hat to the previous profile + */ +int aa_restore_previous_profile(u64 token) +{ + struct aa_task_context *cxt; + struct cred *new = prepare_creds(); + if (!new) + return -ENOMEM; + + cxt = new->security; + if (cxt->sys.token != token) { + abort_creds(new); + return -EACCES; + } + /* ignore restores when there is no saved profile */ + if (!cxt->sys.previous) { + abort_creds(new); + return 0; + } + + aa_put_profile(cxt->sys.profile); + cxt->sys.profile = aa_profile_newest(cxt->sys.previous); + if (unlikely(cxt->sys.profile != cxt->sys.previous)) { + aa_get_profile(cxt->sys.profile); + aa_put_profile(cxt->sys.previous); + } + /* clear exec && prev information when restoring to previous context */ + cxt->sys.previous = NULL; + cxt->sys.token = 0; + aa_put_profile(cxt->sys.onexec); + cxt->sys.onexec = NULL; + + commit_creds(new); + return 0; +} diff -urN kernel.org/security/apparmor/domain.c kernel/security/apparmor/domain.c --- kernel.org/security/apparmor/domain.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/domain.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,719 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy attachment and domain transitions + * + * Copyright (C) 2002-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "include/audit.h" +#include "include/apparmorfs.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" + +/** + * aa_free_domain_entries - free entries in a domain table + * @domain: the domain table to free + */ +void aa_free_domain_entries(struct aa_domain *domain) +{ + int i; + + if (!domain->table) + return; + + for (i = 0; i < domain->size; i++) + kfree(domain->table[i]); + kfree(domain->table); +} + +/* + * check if the task is ptraced and if so if the tracing task is allowed + * to trace the new domain + */ +static int aa_may_change_ptraced_domain(struct task_struct *task, + struct aa_profile *to_profile) +{ + struct task_struct *tracer; + struct cred *cred = NULL; + struct aa_profile *tracerp = NULL; + int error = 0; + + rcu_read_lock(); + tracer = tracehook_tracer_task(task); + if (tracer) + cred = aa_get_task_policy(tracer, &tracerp); + rcu_read_unlock(); + + if (!tracerp) + return error; + + error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); + put_cred(cred); + + return error; +} + +/** + * change_profile_perms + */ +static struct file_perms change_profile_perms(struct aa_profile *profile, + struct aa_namespace *ns, + const char *name, + unsigned int *rstate) +{ + struct file_perms perms; + struct path_cond cond = { 0, 0 }; + unsigned int state; + + if (!profile) { + /* unconfined */ + perms.allowed = AA_MAY_CHANGE_PROFILE; + perms.xindex = perms.dindex = 0; + perms.audit = perms.quiet = perms.kill = 0; + if (rstate) + *rstate = 0; + return perms; + } else if (!profile->file.dfa) { + return nullperms; + } else if ((ns == profile->ns)) { + /* try matching against rules with out namespace prependend */ + perms = aa_str_perms(profile->file.dfa, DFA_START, name, &cond, + rstate); + if (COMBINED_PERM_MASK(perms) & AA_MAY_CHANGE_PROFILE) + return perms; + } + + /* try matching with namespace name and then profile */ + state = aa_dfa_match(profile->file.dfa, DFA_START, ns->base.name); + state = aa_dfa_null_transition(profile->file.dfa, state); + return aa_str_perms(profile->file.dfa, state, name, &cond, rstate); +} + +/* + * TODO: fix parser to detect unconfined, inherit, + * check for next name in list of names that is double null terminated + * The names list is a set of strings that \0 seperated with a double + * \0 terminating the list + * names that belong to namespaces begin with a : + * and are followed by a name a \0 seperated name. If the name is + * unspecified it is 0 length. This double \0\0 does not count as + * the end of the list + * + * profile\0\0 # single profile + * profile\0profile\0\0 # 2 profiles in list + * :namespace\0profile\0\0 # profile & namespace + * :namespace\0\0\0 # namespace without profile + * :namespace\0\0profile\0\0 # namespace without profile followed by profile +*/ +static const char *next_name(int xtype, const char *name) +{ +/* TODO: fix parser and enable + if (xtype == AA_X_TABLE) { + name = name + strlen(name) + 1; + if (*name != 0) + return name; + } +*/ + return NULL; +} + +/* + * get target profile for xindex + */ +static struct aa_profile *x_to_profile(struct aa_namespace *ns, + struct aa_profile *profile, + const char *name, u16 xindex) + +{ + struct aa_profile *new_profile = NULL; + u16 xtype = xindex & AA_X_TYPE_MASK; + int index = xindex & AA_X_INDEX_MASK; + + if (!profile) + profile = ns->unconfined; + + switch(xtype) { + case AA_X_NONE: + /* fail exec unless ix || ux fallback - handled by caller */ + return ERR_PTR(-EACCES); + case AA_X_NAME: + if (xindex & AA_X_CHILD) + new_profile = aa_sys_find_attach(&profile->base, name); + else + new_profile = aa_sys_find_attach(&ns->base, name); + + goto out; + case AA_X_TABLE: + if (index > profile->file.trans.size) { + AA_ERROR("Invalid named transition\n"); + return ERR_PTR(-EACCES); + } + name = profile->file.trans.table[index]; + break; + } + + for (; !new_profile && name; name = next_name(xtype, name)) { + struct aa_namespace *new_ns; + const char *xname = NULL; + + new_ns = NULL; + if (xindex & AA_X_CHILD) { + new_profile = aa_find_child(profile, name); + if (new_profile) + return new_profile; + continue; + } else if (*name == ':') { + /* switching namespace */ + const char *ns_name = name + 1; + name = xname = ns_name + strlen(ns_name) + 1; + if (!*xname) + /* no name so use profile name */ + xname = profile->fqname; + if (*ns_name == '@') { + /* TODO: variable support */ + ; + } + new_ns = aa_find_namespace(ns_name); + if (!new_ns) + continue; + } else if (*name == '@') { + /* TODO: variable support */ + + } else { + xname = name; + } + + new_profile = aa_find_profile_by_fqname(new_ns ? new_ns : ns, + xname); + aa_put_namespace(new_ns); + } + +out: + if (!new_profile) + return ERR_PTR(-ENOENT); + + return new_profile; +} + +int apparmor_bprm_set_creds(struct linux_binprm *bprm) +{ + struct aa_task_context *cxt; + struct aa_profile *profile, *new_profile = NULL; + struct aa_namespace *ns; + char *buffer = NULL; + unsigned int state = DFA_START; + struct aa_audit_file sa; + struct path_cond cond = { bprm->file->f_path.dentry->d_inode->i_uid, + bprm->file->f_path.dentry->d_inode->i_mode }; + + sa.base.error = cap_bprm_set_creds(bprm); + if (sa.base.error) + return sa.base.error; + + if (bprm->cred_prepared) + return 0; + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = "exec"; + sa.base.gfp_mask = GFP_KERNEL; + sa.request = MAY_EXEC; + sa.cond = &cond; + + cxt = bprm->cred->security; + BUG_ON(!cxt); + + profile = aa_filtered_profile(aa_profile_newest(cxt->sys.profile)); + ns = cxt->sys.profile->ns; + + sa.base.error = aa_get_name(&bprm->file->f_path, 0, &buffer, + (char **) &sa.name); + if (sa.base.error) { + if (profile || profile->flags & PFLAG_IX_ON_NAME_ERROR) + sa.base.error = 0; + sa.base.info = "Exec failed name resolution"; + sa.name = bprm->filename; + goto audit; + } + + if (!profile) { + /* unconfined task - attach profile if one matches */ + new_profile = aa_sys_find_attach(&ns->base, sa.name); + if (!new_profile) + goto cleanup; + goto apply; + } else if (cxt->sys.onexec) { + /* + * onexec permissions are stored in a pair, rewalk the + * dfa to get start of the exec path match. + */ + sa.perms = change_profile_perms(profile, cxt->sys.onexec->ns, + sa.name, &state); + state = aa_dfa_null_transition(profile->file.dfa, state); + } + sa.perms = aa_str_perms(profile->file.dfa, state, sa.name, &cond, + NULL); + if (cxt->sys.onexec && sa.perms.allowed & AA_MAY_ONEXEC) { + new_profile = cxt->sys.onexec; + cxt->sys.onexec = NULL; + sa.base.info = "change_profile onexec"; + } else if (sa.perms.allowed & MAY_EXEC) { + new_profile = x_to_profile(ns, profile, sa.name, + sa.perms.xindex); + if (IS_ERR(new_profile)) { + if (sa.perms.xindex & AA_X_INHERIT) { + /* (p|c|n)ix - don't change profile */ + sa.base.info = "ix fallback"; + goto x_clear; + } else if (sa.perms.xindex & AA_X_UNCONFINED) { + new_profile = aa_get_profile(ns->unconfined); + sa.base.info = "ux fallback"; + } else { + sa.base.error = PTR_ERR(new_profile); + if (sa.base.error == -ENOENT) + sa.base.info = "profile not found"; + new_profile = NULL; + } + } + } else if (PROFILE_COMPLAIN(profile)) { + new_profile = aa_alloc_null_profile(profile, 0); + sa.base.error = -EACCES; + if (!new_profile) + sa.base.error = -ENOMEM; + sa.name2 = new_profile->fqname; + sa.perms.xindex |= AA_X_UNSAFE; + } else { + sa.base.error = -EACCES; + } + + if (!new_profile) + goto audit; + + if (profile == new_profile) { + aa_put_profile(new_profile); + goto audit; + } + + if (bprm->unsafe & LSM_UNSAFE_SHARE) { + /* FIXME: currently don't mediate shared state */ + ; + } + + if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { + sa.base.error = aa_may_change_ptraced_domain(current, + new_profile); + if (sa.base.error) + goto audit; + } + + /* Determine if secure exec is needed. + * Can be at this point for the following reasons: + * 1. unconfined switching to confined + * 2. confined switching to different confinement + * 3. confined switching to unconfined + * + * Cases 2 and 3 are marked as requiring secure exec + * (unless policy specified "unsafe exec") + * + * bprm->unsafe is used to cache the AA_X_UNSAFE permission + * to avoid having to recompute in secureexec + */ + if (!(sa.perms.xindex & AA_X_UNSAFE)) + bprm->unsafe |= AA_SECURE_X_NEEDED; + +apply: + sa.name2 = new_profile->fqname; + /* When switching namespace ensure its part of audit message */ + if (new_profile->ns != ns) + sa.name3 = new_profile->ns->base.name; + + /* when transitioning profiles clear unsafe personality bits */ + bprm->per_clear |= PER_CLEAR_ON_SETID; + + aa_put_profile(cxt->sys.profile); + cxt->sys.profile = new_profile; + +x_clear: + aa_put_profile(cxt->sys.previous); + aa_put_profile(cxt->sys.onexec); + cxt->sys.previous = NULL; + cxt->sys.onexec = NULL; + cxt->sys.token = 0; + +audit: + sa.base.error = aa_audit_file(profile, &sa); + +cleanup: + kfree(buffer); + + return sa.base.error; +} + +int apparmor_bprm_secureexec(struct linux_binprm *bprm) +{ + int ret = cap_bprm_secureexec(bprm); + + /* the decision to use secure exec is computed in set_creds + * and stored in bprm->unsafe. The AppArmor X_UNSAFE flag is + * indicates don't + */ + if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED)) + ret = 1; + + return ret; +} + + +static int aa_revalidate_perm(struct aa_profile *profile, struct file *file, + char *buffer, int size) +{ + umode_t mode = file->f_path.dentry->d_inode->i_mode; + char *name; + int error; + + error = aa_get_name_to_buffer(&file->f_path, S_ISDIR(mode), buffer, + size, &name); + return aa_file_common_perm(profile, "file_inherit", file, + aa_map_file_to_perms(file), name, + error); +} + +static void revalidate_file(struct aa_profile *profile, struct file *file, + unsigned long i, char *buffer, int size, + struct cred *cred) +{ + if (aa_revalidate_perm(profile, file, buffer, size)) { + struct file *devnull = NULL; + int fd = get_unused_fd(); + sys_close(i); + if (fd != i) { + if (fd >= 0) + put_unused_fd(fd); + return; + } + if (devnull) { + get_file(devnull); + } else if (apparmorfs_null) { + devnull = dentry_open(dget(apparmorfs_null), + mntget(apparmorfs_mnt), + O_RDWR, cred); + if (IS_ERR(devnull)) { + devnull = NULL; + put_unused_fd(fd); + return; + } + } else { + /* apparmorfs_null not setup */ + put_unused_fd(fd); + return; + } + fd_install(fd, devnull); + } +} + +/* + * derived from security/selinux/hooks.c: flush_unauthorized_files && + * fs/exec.c:flush_old_files + */ +static int revalidate_files(struct aa_profile *profile, + struct files_struct *files, gfp_t gfp, + struct cred *cred) +{ + struct file *file; + struct fdtable *fdt; + long j = -1; + char *buffer = kmalloc(g_apparmor_path_max, gfp); + if (!buffer) + return -ENOMEM; + + spin_lock(&files->file_lock); + for (;;) { + unsigned long set, i; + + j++; + i = j * __NFDBITS; + fdt = files_fdtable(files); + if (i >= fdt->max_fds) + break; + set = fdt->open_fds->fds_bits[j]; + if (!set) + continue; + spin_unlock(&files->file_lock); + for ( ; set ; i++,set >>= 1) { + if (set & 1) { + file = fget(i); + if (!file) + continue; + revalidate_file(profile, file, i, buffer, + g_apparmor_path_max, cred); + fput(file); + } + } + spin_lock(&files->file_lock); + } + spin_unlock(&files->file_lock); + kfree(buffer); + return 0; +} + +int apparmor_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct aa_profile *profile; + struct cred *cred = aa_get_task_policy(current, &profile); + struct aa_task_context *new_cxt = bprm->cred->security; + int error; + + if ((new_cxt->sys.profile == profile) || + (new_cxt->sys.profile->flags & PFLAG_UNCONFINED)) { + put_cred(cred); + return 0; + } + put_cred(cred); + + error = revalidate_files(new_cxt->sys.profile, current->files, + GFP_KERNEL, bprm->cred); + if (error) + return error; + + current->pdeath_signal = 0; + + /* reset soft limits and set hard limits for the new profile */ + __aa_transition_rlimits(profile, new_cxt->sys.profile); + return 0; +} + +void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +{ + /* TODO: cleanup signals - ipc mediation */ + return; +} + +/** + * aa_change_hat - change hat to/from subprofile + * @hat_name: hat to change to + * @token: magic value to validate the hat change + * @permtest: true if this is just a permission test + * + * Change to new @hat_name, and store the @hat_magic in the current task + * context. If the new @hat_name is %NULL and the @token matches that + * stored in the current task context and is not 0, return to the top level + * profile. + * Returns %0 on success, error otherwise. + */ +int aa_change_hat(const char *hat_name, u64 token, int permtest) +{ + const struct cred *cred; + struct aa_task_context *cxt; + struct aa_profile *profile, *previous_profile, *hat = NULL; + struct aa_audit_file sa; + char *name = NULL; + + memset(&sa, 0, sizeof(sa)); + sa.base.gfp_mask = GFP_KERNEL; + sa.base.operation = "change_hat"; + sa.request = AA_MAY_CHANGEHAT; + + cred = aa_current_policy(&profile); + cxt = cred->security; + previous_profile = cxt->sys.previous; + + if (!profile) { + sa.base.info = "unconfined"; + sa.base.error = -EPERM; + goto audit; + } + + if (hat_name) { + sa.name2 = profile->ns->base.name; + + if (PROFILE_IS_HAT(profile)) + hat = aa_find_child(profile->parent, hat_name); + else + hat = aa_find_child(profile, hat_name); + if (!hat) { + if (PROFILE_IS_HAT(profile)) + name = new_compound_name(profile->parent->fqname, + hat_name); + else + name = new_compound_name(profile->fqname, + hat_name); + sa.name = name; + sa.base.info = "hat not found"; + sa.base.error = -ENOENT; + if (permtest || !PROFILE_COMPLAIN(profile)) + /* probing is an expected unfortunate behavior + * of the change_hat api is traditionally quiet + */ + goto out; + hat = aa_alloc_null_profile(profile, 1); + if (!hat) { + sa.base.info = "failed null profile create"; + sa.base.error = -ENOMEM; + goto audit; + } + } else { + sa.name = hat->fqname; + if (!PROFILE_IS_HAT(hat)) { + sa.base.info = "target not hat"; + sa.base.error = -EPERM; + goto audit; + } + } + + sa.base.error = aa_may_change_ptraced_domain(current, hat); + if (sa.base.error) { + sa.base.info = "ptraced"; + sa.base.error = -EPERM; + goto audit; + } + + if (!permtest) { + sa.base.error = aa_set_current_hat(hat, token); + if (sa.base.error == -EACCES) { + (void)send_sig_info(SIGKILL, NULL, current); + sa.base.error = aa_audit(AUDIT_APPARMOR_KILL, + profile, &sa.base, + file_audit_cb); + goto out; + } else if (name && !sa.base.error) + /* reset error for learning of new hats */ + sa.base.error = -ENOENT; + } + } else if (previous_profile) { + sa.name = previous_profile->fqname; + sa.base.error = aa_restore_previous_profile(token); + sa.perms.kill = AA_MAY_CHANGEHAT; + } + /* else + ignore restores when there is no saved profile + */ + +audit: + if (!permtest) + sa.base.error = aa_audit_file(profile, &sa); + + +out: + aa_put_profile(hat); + kfree(name); + + return sa.base.error; +} + +/** + * aa_change_profile - perform a one-way profile transition + * @ns_name: name of the profile namespace to change to + * @fqname: name of profile to change to + * @onexec: whether this transition is to take place immediately or at exec + * @permtest: true if this is just a permission test + * + * Change to new profile @name. Unlike with hats, there is no way + * to change back. If @onexec then the transition is delayed until + * the next exec. + * + * Returns %0 on success, error otherwise. + */ +int aa_change_profile(const char *ns_name, const char *fqname, int onexec, + int permtest) +{ + const struct cred *cred; + struct aa_task_context *cxt; + struct aa_profile *profile, *target = NULL; + struct aa_namespace *ns = NULL; + struct aa_audit_file sa; + + if (!fqname && !ns_name) + return -EINVAL; + + memset(&sa, 0, sizeof(sa)); + sa.request = AA_MAY_CHANGE_PROFILE; + sa.base.gfp_mask = GFP_KERNEL; + if (onexec) + sa.base.operation = "change_onexec"; + else + sa.base.operation = "change_profile"; + + cred = aa_current_policy(&profile); + cxt = cred->security; + + if (ns_name) { + sa.name2 = ns_name; + ns = aa_find_namespace(ns_name); + if (!ns) { + /* we don't create new namespace in complain mode */ + sa.base.info = "namespace not found"; + sa.base.error = -ENOENT; + goto audit; + } + } else { + ns = aa_get_namespace(cxt->sys.profile->ns); + sa.name2 = ns->base.name; + } + + /* if the name was not specified, use the name of the current profile */ + if (!fqname) { + if (!profile) + fqname = ns->unconfined->fqname; + else + fqname = profile->fqname; + } + sa.name = fqname; + + sa.perms = change_profile_perms(profile, ns, fqname, NULL); + if (!(sa.perms.allowed & AA_MAY_CHANGE_PROFILE)) { + sa.base.error = -EACCES; + goto audit; + } + + target = aa_find_profile_by_fqname(ns, fqname); + if (!target) { + sa.base.info = "profile not found"; + sa.base.error = -ENOENT; + if (permtest || !PROFILE_COMPLAIN(profile)) + goto audit; + target = aa_alloc_null_profile(profile, 0); + } + + /* check if tracing task is allowed to trace target domain */ + sa.base.error = aa_may_change_ptraced_domain(current, target); + if (sa.base.error) { + sa.base.info = "ptrace prevents transition"; + goto audit; + } + + if (permtest) + goto audit; + + if (onexec) + sa.base.error = aa_set_current_onexec(target); + else + sa.base.error = aa_replace_current_profiles(target); + +audit: + if (!permtest) + sa.base.error = aa_audit_file(profile, &sa); + + aa_put_namespace(ns); + aa_put_profile(target); + + return sa.base.error; +} diff -urN kernel.org/security/apparmor/file.c kernel/security/apparmor/file.c --- kernel.org/security/apparmor/file.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/file.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,426 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/file.h" +#include "include/match.h" +#include "include/path.h" +#include "include/policy.h" + +struct file_perms nullperms; + +static void aa_audit_file_sub_mask(struct audit_buffer *ab, char *buffer, + u16 mask, u16 xindex) +{ + + /* const char xchar[] = "PpCc";*/ + + char *m = buffer; + + if (mask & AA_EXEC_MMAP) + *m++ = 'm'; + if (mask & MAY_READ) + *m++ = 'r'; + if (mask & (MAY_WRITE | AA_MAY_CREATE)) + *m++ = 'w'; + else if (mask & MAY_APPEND) + *m++ = 'a'; + if (mask & AA_MAY_LINK) + *m++ = 'l'; + if (mask & AA_MAY_LOCK) + *m++ = 'k'; + if (mask & MAY_EXEC) { + *m++ = 'x'; + +/* FIXME: only want more advanced auditing of x if in audit/hint mode + u16 index = xindex & AA_X_INDEX_MASK; + u16 xtype = xindex & AA_X_TYPE_MASK; + if (xtype > AA_X_NONE) + *m++ = xchar[(xindex >> 12) & 0x3]; + if (xindex & AA_X_INHERIT) { + *m++ = 'i'; + } else if (xindex & AA_X_UNCONFINED) { + if (xindex & AA_X_UNSAFE) + *m++ = 'u'; + else + *m++ = 'U'; + } + *m++ = 'x'; + / * at most 7 character including trailing \0 * / + if (xtype == AA_X_VARIABLE) { + m += sprintf(m, "->v%x", index); + } else if (xtype == AA_X_TABLE) { + m += sprintf(m, "->n%x", index); + } +*/ + } + *m++ = '\0'; +} + +static void aa_audit_file_mask(struct audit_buffer *ab, const char *name, + u16 mask, int xindex, int owner) +{ +/* char str[18]; */ + char str[10]; + + aa_audit_file_sub_mask(ab, str, mask, xindex); + if (owner) + audit_log_format(ab, " %s=\"%s::\"", name, str); + else + audit_log_format(ab, " %s=\"::%s\"", name, str); +} + +void file_audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_file *sa = va; + u16 denied = sa->request & ~sa->perms.allowed; + uid_t fsuid; + + if (sa->base.task) + fsuid = task_uid(sa->base.task); + else + fsuid = current_fsuid(); + + if (sa->request & AA_AUDIT_FILE_MASK) + aa_audit_file_mask(ab, "requested_mask", sa->request, + AA_X_NONE, fsuid == sa->cond->uid); + + if (denied & AA_AUDIT_FILE_MASK) + aa_audit_file_mask(ab, "denied_mask", denied, sa->perms.xindex, + fsuid == sa->cond->uid); + + if (sa->request & AA_AUDIT_FILE_MASK) { + audit_log_format(ab, " fsuid=%d", fsuid); + audit_log_format(ab, " ouid=%d", sa->cond->uid); + } + + 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); + } + + if (sa->name3) { + audit_log_format(ab, " name3="); + audit_log_untrustedstring(ab, sa->name3); + } +} + +int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa) +{ + int type = AUDIT_APPARMOR_AUTO; + + if (likely(!sa->base.error)) { + u16 mask = sa->perms.audit; + + if (unlikely(PROFILE_AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + sa->request &= mask; + + if (likely(!sa->request)) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else { + /* quiet auditing of specific known rejects */ + u16 mask = sa->perms.quiet; + u16 denied = sa->request & ~sa->perms.allowed; + + if (denied & sa->perms.kill) + type = AUDIT_APPARMOR_KILL; + + /* assumes quiet and kill do not overlap */ + if ((denied & mask) && + PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET && + PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) + denied &= ~mask; + + if (!denied) + return PROFILE_COMPLAIN(profile) ? 0 : sa->base.error; + } + return aa_audit(type, profile, (struct aa_audit *)sa, file_audit_cb); +} + +/* FIXME: convert from dfa + state to permission entry */ +struct file_perms aa_compute_perms(struct aa_dfa *dfa, unsigned int state, + struct path_cond *cond) +{ + struct file_perms perms; + + /* FIXME: change over to new dfa format */ + /* currently file perms are encoded in the dfa */ + perms.kill = 0; + perms.dindex = 0; + + if (current_fsuid() == cond->uid) { + perms.allowed = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + perms.xindex = dfa_user_xindex(dfa, state); + } else { + perms.allowed = dfa_other_allow(dfa, state); + perms.audit = dfa_other_audit(dfa, state); + perms.quiet = dfa_other_quiet(dfa, state); + perms.xindex = dfa_other_xindex(dfa, state); + } + /* in the old mapping MAY_WRITE implies AA_MAY_CREATE */ + perms.allowed |= (perms.allowed & MAY_WRITE) << 6; + perms.audit |= (perms.audit & MAY_WRITE) << 6; + perms.quiet |= (perms.quiet & MAY_WRITE) << 6; + + /* in the old mapping AA_MAY_LOCK and link subset are overlayed + * and only determined by which part of a pair they are in + */ + if (perms.allowed & AA_MAY_LOCK) + perms.allowed |= AA_LINK_SUBSET; + + /* change_profile wasn't determined by ownership in old mapping */ + if (ACCEPT_TABLE(dfa)[state] & 0x80000000) + perms.allowed |= AA_MAY_CHANGE_PROFILE; + + return perms; +} + +struct file_perms aa_str_perms(struct aa_dfa *dfa, unsigned int start, + const char *name, struct path_cond *cond, + unsigned int *rstate) +{ + unsigned int state; + if (!dfa) + return nullperms; + + state = aa_dfa_match(dfa, start, name); + + if (rstate) + *rstate = state; + + /* TODO: convert to new dfa format */ + + return aa_compute_perms(dfa, state, cond); +} + +int aa_pathstr_perm(struct aa_profile *profile, const char *op, + const char *name, u16 request, struct path_cond *cond) +{ + struct aa_audit_file sa; + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = op; + sa.base.gfp_mask = GFP_KERNEL; + sa.request = request; + sa.name = name; + sa.cond = cond; + + sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, cond, + NULL); + if (request & ~sa.perms.allowed) + sa.base.error = -EACCES; + return aa_audit_file(profile, &sa); +} + +int aa_path_perm(struct aa_profile *profile, const char *operation, + struct path *path, u16 request, struct path_cond *cond) +{ + struct aa_audit_file sa; + char *buffer, *name; + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = operation; + sa.base.gfp_mask = GFP_KERNEL; + sa.request = request; + sa.cond = cond; + + sa.base.error = aa_get_name(path, S_ISDIR(cond->mode), &buffer, + &name); + sa.name = name; + if (sa.base.error) { + sa.perms = nullperms; + if (sa.base.error == -ENOENT) + sa.base.info = "Failed name lookup - deleted entry"; + else if (sa.base.error == -ESTALE) + sa.base.info = "Failed name lookup - disconnected path"; + else if (sa.base.error == -ENAMETOOLONG) + sa.base.info = "Failed name lookup - name too long"; + else + sa.base.info = "Failed name lookup"; + } else { + sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, + cond, NULL); + if (request & ~sa.perms.allowed) + sa.base.error = -EACCES; + } + sa.base.error = aa_audit_file(profile, &sa); + kfree(buffer); + + return sa.base.error; +} + +int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry) +{ + struct path link = { new_dir->mnt, new_dentry }; + struct path target = { new_dir->mnt, old_dentry }; + struct path_cond cond = { old_dentry->d_inode->i_uid, + old_dentry->d_inode->i_mode }; + char *buffer = NULL, *buffer2 = NULL; + char *lname, *tname; + struct file_perms perms; + unsigned int state; + + struct aa_audit_file sa; + memset(&sa, 0, sizeof(sa)); + sa.base.operation = "link"; + sa.base.gfp_mask = GFP_KERNEL; + sa.request = AA_MAY_LINK; + sa.cond = &cond; + sa.perms = nullperms; + + sa.base.error = aa_get_name(&link, 0, &buffer, &lname); + sa.name = lname; + if (sa.base.error) + goto audit; + + sa.base.error = aa_get_name(&target, 0, &buffer2, &tname); + sa.name2 = tname; + if (sa.base.error) + goto audit; + + + sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, &cond, + &state); + sa.perms.audit &= AA_MAY_LINK; + sa.perms.quiet &= AA_MAY_LINK; + sa.perms.kill &= AA_MAY_LINK; + + if (!(sa.perms.allowed & AA_MAY_LINK)) { + sa.base.error = -EACCES; + goto audit; + } + + /* test to see if target can be paired with link */ + state = aa_dfa_null_transition(profile->file.dfa, state); + perms = aa_str_perms(profile->file.dfa, state, sa.name2, &cond, NULL); + if (!(perms.allowed & AA_MAY_LINK)) { + sa.base.error = -EACCES; + sa.base.info = "target restricted"; + goto audit; + } + + /* done if link subset test is not required */ + if (!(perms.allowed & AA_LINK_SUBSET)) + goto audit; + + /* Do link perm subset test requiring allowed permission on link are a + * subset of the allowed permissions on target. + */ + perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name2, &cond, + NULL); + + /* AA_MAY_LINK is not considered in the subset test */ + sa.request = sa.perms.allowed & ~AA_MAY_LINK; + sa.perms.allowed &= perms.allowed | AA_MAY_LINK; + + sa.request |= AA_AUDIT_FILE_MASK & (sa.perms.allowed & ~perms.allowed); + if (sa.request & ~sa.perms.allowed) + sa.base.error = -EACCES; + else if (sa.perms.allowed & MAY_EXEC) { + if (((sa.perms.xindex & ~AA_X_UNSAFE) != + (perms.xindex &~AA_X_UNSAFE)) || + ((sa.perms.xindex & AA_X_UNSAFE) && + !(perms.xindex & AA_X_UNSAFE))) { + sa.perms.allowed &= ~MAY_EXEC; + sa.request |= MAY_EXEC; + sa.base.error = -EACCES; + sa.base.info = "link not subset of target"; + } + } + +audit: + sa.base.error = aa_audit_file(profile, &sa); + kfree(buffer); + kfree(buffer2); + + return sa.base.error; +} + + +static inline int aa_is_deleted_file(struct dentry *dentry) +{ + if (d_unhashed(dentry)) + return 1; + return 0; +} + +int aa_file_common_perm(struct aa_profile *profile, const char *operation, + struct file *file, u16 request, const char *name, + int error) +{ + struct path_cond cond = { .uid = file->f_path.dentry->d_inode->i_uid, + .mode = file->f_path.dentry->d_inode->i_mode }; + struct aa_audit_file sa; + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = operation; + sa.base.gfp_mask = GFP_KERNEL; + sa.request = request; + sa.base.error = error; + sa.name = name; + sa.cond = &cond; + + if (sa.base.error) { + sa.perms = nullperms; + if (sa.base.error == -ENOENT && + aa_is_deleted_file(file->f_path.dentry)) { + /* Access to open files that are deleted are + * give a pass (implicit delegation + */ + sa.base.error = 0; + sa.perms.allowed = sa.request; + } else if (sa.base.error == -ENOENT) + sa.base.info = "Failed name lookup - deleted entry"; + else if (sa.base.error == -ESTALE) + sa.base.info = "Failed name lookup - disconnected path"; + else if (sa.base.error == -ENAMETOOLONG) + sa.base.info = "Failed name lookup - name too long"; + else + sa.base.info = "Failed name lookup"; + } else { + sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, + &cond, NULL); + if (request & ~sa.perms.allowed) + sa.base.error = -EACCES; + } + sa.base.error = aa_audit_file(profile, &sa); + + return sa.base.error; +} + +int aa_file_perm(struct aa_profile *profile, const char *operation, + struct file *file, u16 request) +{ + char *buffer, *name; + umode_t mode = file->f_path.dentry->d_inode->i_mode; + int error = aa_get_name(&file->f_path, S_ISDIR(mode), &buffer, &name); + + error = aa_file_common_perm(profile, operation, file, request, name, + error); + kfree(buffer); + return error; +} diff -urN kernel.org/security/apparmor/.gitignore kernel/security/apparmor/.gitignore --- kernel.org/security/apparmor/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/.gitignore 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,5 @@ +# +# Generated include files +# +af_names.h +capability_names.h diff -urN kernel.org/security/apparmor/include/apparmorfs.h kernel/security/apparmor/include/apparmorfs.h --- kernel.org/security/apparmor/include/apparmorfs.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/apparmorfs.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,24 @@ +/* + * AppArmor security module + * + * This file contains AppArmor filesystem definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_APPARMORFS_H +#define __AA_APPARMORFS_H + +extern struct dentry *apparmorfs_null; +extern struct vfsmount *apparmorfs_mnt; + +extern int create_apparmorfs(void); +extern void destroy_apparmorfs(void); + +#endif /* __AA_APPARMORFS_H */ diff -urN kernel.org/security/apparmor/include/apparmor.h kernel/security/apparmor/include/apparmor.h --- kernel.org/security/apparmor/include/apparmor.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/apparmor.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,65 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic global and lib definitions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __APPARMOR_H +#define __APPARMOR_H + +#include + +/* Control parameters settable thru module/boot flags or + * via /sys/kernel/security/apparmor/control */ +extern enum audit_mode g_apparmor_audit; +extern int g_apparmor_audit_header; +extern int g_apparmor_debug; +extern int g_apparmor_lock_policy; +extern int g_apparmor_logsyscall; +extern unsigned int g_apparmor_path_max; + + +/* + * DEBUG remains global (no per profile flag) since it is mostly used in sysctl + * which is not related to profile accesses. + */ + +#define AA_DEBUG(fmt, args...) \ + do { \ + if (g_apparmor_debug && printk_ratelimit()) \ + printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ + } while (0) + +#define AA_ERROR(fmt, args...) \ + do { \ + if (printk_ratelimit()) \ + printk(KERN_ERR "AppArmor: " fmt, ##args); \ + } while (0) + +/* Flag indicating whether initialization completed */ +extern int apparmor_initialized; +void apparmor_disable(void); + +/* fn's in lib */ +void info_message(const char *str); +char *aa_split_name_from_ns(char *args, char **ns_name); +char *new_compound_name(const char *n1, const char *n2); +int aa_strneq(const char *str, const char *sub, int len); +char *strchrnul(const char *s, int c); +const char *fqname_subname(const char *name); + +static inline int mediated_filesystem(struct inode *inode) +{ + return !(inode->i_sb->s_flags & MS_NOUSER); +} + +#endif /* __APPARMOR_H */ + diff -urN kernel.org/security/apparmor/include/audit.h kernel/security/apparmor/include/audit.h --- kernel.org/security/apparmor/include/audit.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/audit.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,59 @@ +/* + * AppArmor security module + * + * This file contains AppArmor auditing function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_AUDIT_H +#define __AA_AUDIT_H + +#include +#include +#include +#include + +struct aa_profile; + + +extern const char *audit_mode_names[]; +#define AUDIT_MAX_INDEX 5 + +#define AUDIT_APPARMOR_AUTO 0 /* auto choose audit message type */ + +enum audit_mode { + AUDIT_NORMAL, /* follow normal auditing of accesses */ + AUDIT_QUIET_DENIED, /* quiet all denied access messages */ + AUDIT_QUIET, /* quiet all messages */ + AUDIT_NOQUIET, /* do not quiet audit messages */ + AUDIT_ALL /* audit all accesses */ +}; + +/* + * aa_audit - AppArmor auditing structure + * Structure is populated by access control code and passed to aa_audit which + * provides for a single point of logging. + */ +struct aa_audit { + struct task_struct *task; + gfp_t gfp_mask; + int error; + const char *operation; + const char *info; +}; + +int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa, + void(*cb)(struct audit_buffer *, void *)); + +int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, const char *, + void(*cb)(struct audit_buffer *, void *)); + + +#endif /* __AA_AUDIT_H */ diff -urN kernel.org/security/apparmor/include/capability.h kernel/security/apparmor/include/capability.h --- kernel.org/security/apparmor/include/capability.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/capability.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,45 @@ +/* + * AppArmor security module + * + * This file contains AppArmor capability mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_CAPABILITY_H +#define __AA_CAPABILITY_H + +#include + +struct aa_profile; + +/* aa_caps - confinement data for capabilities + * @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 + */ +struct aa_caps { + kernel_cap_t set; + kernel_cap_t allowed; + kernel_cap_t audit; + kernel_cap_t quiet; + kernel_cap_t kill; +}; + +int aa_profile_capable(struct aa_profile *profile, int cap); +int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, + int audit); + +static inline void aa_free_cap_rules(struct aa_caps *caps) +{ + /* NOP */ +} + +#endif /* __AA_CAPBILITY_H */ diff -urN kernel.org/security/apparmor/include/context.h kernel/security/apparmor/include/context.h --- kernel.org/security/apparmor/include/context.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/context.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,153 @@ +/* + * AppArmor security module + * + * This file contains AppArmor contexts used to associate "labels" to objects. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_CONTEXT_H +#define __AA_CONTEXT_H + +#include +#include +#include + +#include "policy.h" + + +/* struct aa_file_cxt - the AppArmor context the file was opened in + * @profile: the profile the file was opened under + * @perms: the permission the file was opened with + */ +struct aa_file_cxt { + struct aa_profile *profile; + u16 allowed; +}; + +static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) +{ + return kzalloc(sizeof(struct aa_file_cxt), gfp); +} + +static inline void aa_free_file_context(struct aa_file_cxt *cxt) +{ + aa_put_profile(cxt->profile); + memset(cxt, 0, sizeof(struct aa_file_cxt)); + kfree(cxt); +} + + + + + +/* struct aa_task_cxt_group - a grouping label data for confined tasks + * @profile: the current profile + * @exec: profile to transition to on next exec + * @previous: profile the task may return to + * @token: magic value the task must know for returning to @previous_profile + * + * Contains the task's current profile (which could change due to + * change_hat). Plus the hat_magic needed during change_hat. + */ +struct aa_task_cxt_group { + struct aa_profile *profile; + struct aa_profile *onexec; + struct aa_profile *previous; + u64 token; +}; + +/** + * struct aa_task_context - primary label for confined tasks + * @sys: the system labeling for the task + * + * A task is confined by the intersection of its system and user profiles + */ +struct aa_task_context { + struct aa_task_cxt_group sys; +}; + +struct aa_task_context *aa_alloc_task_context(gfp_t flags); +void aa_free_task_context(struct aa_task_context *cxt); +struct aa_task_context *aa_dup_task_context(struct aa_task_context *old_cxt, + gfp_t gfp); +void aa_cred_policy(const struct cred *cred, struct aa_profile **sys); +struct cred *aa_get_task_policy(const struct task_struct *task, + struct aa_profile **sys); +int aa_replace_current_profiles(struct aa_profile *sys); +void aa_put_task_policy(struct cred *cred); +int aa_set_current_onexec(struct aa_profile *sys); +int aa_set_current_hat(struct aa_profile *profile, u64 token); +int aa_restore_previous_profile(u64 cookie); + + +static inline struct aa_task_context *__aa_task_cxt(struct task_struct *task) +{ + return __task_cred(task)->security; +} + +/** + * __aa_task_is_confined - determine if @task has any confinement + * @task: task to check confinement of + * + * If @task != current needs to be in RCU safe critical section + */ +static inline int __aa_task_is_confined(struct task_struct *task) +{ + struct aa_task_context *cxt; + int rc = 1; + + cxt = __aa_task_cxt(task); + if (!cxt || (cxt->sys.profile->flags & PFLAG_UNCONFINED)) + rc = 0; + + return rc; +} + +static inline const struct cred *aa_current_policy(struct aa_profile **sys) +{ + const struct cred *cred = current_cred(); + struct aa_task_context *cxt = cred->security; + BUG_ON(!cxt); + *sys = aa_filtered_profile(aa_profile_newest(cxt->sys.profile)); + + return cred; +} + +static inline const struct cred *aa_current_policy_wupd(struct aa_profile **sys) +{ + const struct cred *cred = current_cred(); + struct aa_task_context *cxt = cred->security; + BUG_ON(!cxt); + + *sys = aa_profile_newest(cxt->sys.profile); + if (unlikely((cxt->sys.profile != *sys))) + aa_replace_current_profiles(*sys); + *sys = aa_filtered_profile(*sys); + + return cred; +} + +static inline struct aa_profile *aa_current_profile(void) +{ + const struct cred *cred = current_cred(); + struct aa_task_context *cxt = cred->security; + BUG_ON(!cxt); + return aa_filtered_profile(aa_profile_newest(cxt->sys.profile)); +} + +static inline struct aa_profile *aa_current_profile_wupd(void) +{ + struct aa_profile *p; + aa_current_policy_wupd(&p); + return p; +} + + +#endif /* __AA_CONTEXT_H */ diff -urN kernel.org/security/apparmor/include/domain.h kernel/security/apparmor/include/domain.h --- kernel.org/security/apparmor/include/domain.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/domain.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,37 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security domain transition function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include + +#ifndef __AA_DOMAIN_H +#define __AA_DOMAIN_H + +struct aa_domain { + int size; + char **table; +}; + +int apparmor_bprm_set_creds(struct linux_binprm *bprm); +int apparmor_bprm_secureexec(struct linux_binprm *bprm); +int apparmor_bprm_committing_creds(struct linux_binprm *bprm); +void apparmor_bprm_committed_creds(struct linux_binprm *bprm); + +void aa_free_domain_entries(struct aa_domain *domain); +int aa_change_hat(const char *hat_name, u64 token, int permtest); +int aa_change_profile(const char *ns_name, const char *name, int onexec, + int permtest); + + +#endif /* __AA_DOMAIN_H */ diff -urN kernel.org/security/apparmor/include/file.h kernel/security/apparmor/include/file.h --- kernel.org/security/apparmor/include/file.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/file.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,229 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_FILE_H +#define __AA_FILE_H + +#include + +#include "audit.h" +#include "domain.h" +#include "match.h" + +struct aa_profile; + +/* + * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags + * for profile permissions + */ +#define AA_MAY_LINK 0x0010 +#define AA_MAY_LOCK 0x0020 +#define AA_EXEC_MMAP 0x0040 + +#define AA_MAY_CREATE 0x0080 +#define AA_LINK_SUBSET 0x0100 +#define AA_MAY_DELEGATE 0x0200 +#define AA_EXEC_DELEGATE 0x0400 /*exec allows delegate*/ + +#define AA_MAY_CHANGEHAT 0x2000 /* ctrl auditing only */ +#define AA_MAY_ONEXEC 0x4000 /* exec allows onexec */ +#define AA_MAY_CHANGE_PROFILE 0x8000 + + +#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ + AA_MAY_LINK | AA_MAY_LOCK | AA_EXEC_MMAP | \ + AA_MAY_CREATE) + +/* + * The xindex is broken into 3 parts + * - index - an index into either the exec name table or the variable table + * - exec type - which determines how the executable name and index are used + * - flags - which modify how the destination name is applied + */ +#define AA_X_INDEX_MASK 0x03ff + +#define AA_X_TYPE_MASK 0x0c00 +#define AA_X_TYPE_SHIFT 10 +#define AA_X_NONE 0x0000 +#define AA_X_NAME 0x0400 /* use executable name px */ +#define AA_X_TABLE 0x0800 /* use a specified name ->n# */ + +#define AA_X_UNSAFE 0x1000 +#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */ +#define AA_X_INHERIT 0x4000 +#define AA_X_UNCONFINED 0x8000 + + +/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */ +#define AA_SECURE_X_NEEDED 0x8000 + +/* need to conditionalize which ones are being set */ +struct path_cond { + uid_t uid; + umode_t mode; +}; + +/* struct file_perms - file permission fo + * @allowed: mask of permissions that are allowed + * @audit: mask of permissions to force an audit message for + * @quiet: mask of permissions to quiet audit messages for + * @kill: mask of permissions that when matched will kill the task + * @xindex: exec transition index if @allowed contains MAY_EXEC + * @dindex: delegate table index if @allowed contain AA_MAY_DELEGATE + * + * The @audit and @queit mask should be mutually exclusive. + */ +struct file_perms { + u16 allowed; + u16 audit; + u16 quiet; + u16 kill; + u16 xindex; + u16 dindex; +}; + +extern struct file_perms nullperms; + +#define COMBINED_PERM_MASK(X) ((X).allowed | (X).audit | (X).quiet | (X).kill) + +/* FIXME: split perms from dfa and match this to description + * also add delegation info. + */ +static inline u16 dfa_map_xindex(u16 mask) +{ + u16 old_index = (mask >> 10) & 0xf; + u16 index = 0; + +//printk("mask x%x\n", mask); + if (mask & 0x100) + index |= AA_X_UNSAFE; + if (mask & 0x200) + index |= AA_X_INHERIT; + if (mask & 0x80) + index |= AA_X_UNCONFINED; + + if (old_index == 1) { + index |= AA_X_UNCONFINED; + } else if (old_index == 2) { + index |= AA_X_NAME; + } else if (old_index == 3) { + index |= AA_X_NAME | AA_X_CHILD; + } else { + index |= AA_X_TABLE; + index |= old_index - 4; + } + + return index; +} + +/* + * map old dfa inline permissions to new format + */ +#define dfa_user_allow(dfa, state) ((ACCEPT_TABLE(dfa)[state]) & 0x7f) +#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) +#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_xindex(dfa, state) \ + (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) + +#define dfa_other_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 14) & 0x7f) +#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) +#define dfa_other_quiet(dfa, state) ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) +#define dfa_other_xindex(dfa, state) \ + dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) + + +struct aa_audit_file { + struct aa_audit base; + + const char *name; + const char *name2; + const char *name3; + struct file_perms perms; + u16 request; + struct path_cond *cond; +}; + +int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa); +void file_audit_cb(struct audit_buffer *ab, void *va); + +/** + * struct aa_file_rules - components used for file rule permissions + * @dfa: dfa to match path names and conditionals against + * @perms: permission table indexed by the matched state accept entry of @dfa + * @trans: transition table for indexed by named x transitions + * + * File permission are determined by matching a path against @dfa and then + * then using the value of the accept entry for the matching state as + * an index into @perms. If a named exec transition is required it is + * looked up in the transition table. + */ +struct aa_file_rules { + struct aa_dfa *dfa; + /* struct perms perms; */ + struct aa_domain trans; + /* TODO: add delegate table */ +}; + +struct file_perms aa_str_perms(struct aa_dfa *dfa, unsigned int start, + const char *name, struct path_cond *cond, + unsigned int *rstate); + +int aa_pathstr_perm(struct aa_profile *profile, const char *op, + const char *name, u16 request, struct path_cond *cond); + +int aa_path_perm(struct aa_profile *profile, const char *operation, + struct path *path, u16 request, struct path_cond *cond); + +int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry); + +int aa_file_common_perm(struct aa_profile *profile, const char *operation, + struct file *file, u16 request, const char *name, + int error); + +int aa_file_perm(struct aa_profile *profile, const char *operation, + struct file *file, u16 request); + + +static inline void aa_free_file_rules(struct aa_file_rules *rules) +{ + aa_match_free(rules->dfa); + aa_free_domain_entries(&rules->trans); +} + +#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40)) + +/* from namei.c */ +#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE]) +#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x)) +/* + * map file flags to AppArmor permissions + */ +static inline u16 aa_map_file_to_perms(struct file *file) +{ + int flags = MAP_OPEN_FLAGS(file->f_flags); + u16 perms = ACC_FMODE(file->f_mode); + + if ((flags & O_APPEND) && (perms & MAY_WRITE)) + perms = (perms & ~MAY_WRITE) | MAY_APPEND; + /* trunc implies write permission */ + if (flags & O_TRUNC) + perms |= MAY_WRITE; + if (flags & O_CREAT) + perms |= AA_MAY_CREATE; + + return perms; +} + +#endif /* __AA_FILE_H */ diff -urN kernel.org/security/apparmor/include/ipc.h kernel/security/apparmor/include/ipc.h --- kernel.org/security/apparmor/include/ipc.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/ipc.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,28 @@ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_IPC_H +#define __AA_IPC_H + +#include + +struct aa_profile; + +int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, + struct aa_profile *tracee, unsigned int mode); + +int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, + unsigned int mode); + +#endif /* __AA_IPC_H */ diff -urN kernel.org/security/apparmor/include/match.h kernel/security/apparmor/include/match.h --- kernel.org/security/apparmor/include/match.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/match.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,105 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy dfa matching engine definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_MATCH_H +#define __AA_MATCH_H + +#define DFA_NOMATCH 0 +#define DFA_START 1 + +#define DFA_VALID_PERM_MASK 0xffffffff +#define DFA_VALID_PERM2_MASK 0xffffffff + + +/** + * 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 + * info pages and the flex sources for documentation). The magic number + * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because + * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used + * slightly differently (see the apparmor-parser package). + */ + +#define YYTH_MAGIC 0x1B5E783D + +struct table_set_header { + u32 th_magic; /* YYTH_MAGIC */ + u32 th_hsize; + u32 th_ssize; + u16 th_flags; + char th_version[]; +}; + +#define YYTD_ID_ACCEPT 1 +#define YYTD_ID_BASE 2 +#define YYTD_ID_CHK 3 +#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 + + +#define YYTD_DATA8 1 +#define YYTD_DATA16 2 +#define YYTD_DATA32 4 + +struct table_header { + u16 td_id; + u16 td_flags; + u32 td_hilen; + u32 td_lolen; + char td_data[]; +}; + +#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF - 1]->td_data)) +#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE - 1]->td_data)) +#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT - 1]->td_data)) +#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]; +}; + +#define byte_to_byte(X) (X) + +#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ + do { \ + typeof(LEN) __i; \ + TYPE *__t = (TYPE *) TABLE; \ + TYPE *__b = (TYPE *) BLOB; \ + for (__i = 0; __i < LEN; __i++) { \ + __t[__i] = NTOHX(__b[__i]); \ + } \ + } while (0) + +static inline size_t table_size(size_t len, size_t el_size) +{ + return ALIGN(sizeof(struct table_header) + len * el_size, 8); +} + +struct aa_dfa *aa_match_alloc(void); +void aa_match_free(struct aa_dfa *dfa); +int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size); +int verify_dfa(struct aa_dfa *dfa); +unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, + const char *str, int len); +unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, + const char *str); +unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start); + +#endif /* __AA_MATCH_H */ diff -urN kernel.org/security/apparmor/include/net.h kernel/security/apparmor/include/net.h --- kernel.org/security/apparmor/include/net.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/net.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,40 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_NET_H +#define __AA_NET_H + +#include + +/* struct aa_net - network confinement data + * @allowed: basic network families permissions + * @audit_network: which network permissions to force audit + * @quiet_network: which network permissions to quiet rejects + */ +struct aa_net { + u16 allowed[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern int aa_net_perm(struct aa_profile *profile, char *operation, + int family, int type, int protocol); +extern int aa_revalidate_sk(struct sock *sk, char *operation); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff -urN kernel.org/security/apparmor/include/path.h kernel/security/apparmor/include/path.h --- kernel.org/security/apparmor/include/path.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/path.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,24 @@ +/* + * AppArmor security module + * + * This file contains AppArmor basic path manipulation function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_PATH_H +#define __AA_PATH_H + +int aa_get_name_to_buffer(struct path *path, int is_dir, char *buffer, int size, + char **name); +int aa_get_name(struct path *path, int is_dir, char **buffer, char **name); +int d_namespace_path(struct path *path, char *buf, int buflen, char **name); +char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen); + +#endif /* __AA_PATH_H */ diff -urN kernel.org/security/apparmor/include/policy.h kernel/security/apparmor/include/policy.h --- kernel.org/security/apparmor/include/policy.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/policy.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,301 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_POLICY_H +#define __AA_POLICY_H + +#include +#include +#include +#include +#include +#include + +#include "apparmor.h" +#include "audit.h" +#include "capability.h" +#include "domain.h" +#include "file.h" +#include "net.h" +#include "resource.h" + +extern const char *profile_mode_names[]; +#define APPARMOR_NAMES_MAX_INDEX 3 + +#define PROFILE_COMPLAIN(_profile) \ + ((g_profile_mode == APPARMOR_COMPLAIN) || ((_profile) && \ + (_profile)->mode == APPARMOR_COMPLAIN)) + +#define PROFILE_KILL(_profile) \ + ((g_profile_mode == APPARMOR_KILL) || ((_profile) && \ + (_profile)->mode == APPARMOR_KILL)) + +#define PROFILE_IS_HAT(_profile) \ + ((_profile) && (_profile)->flags & PFLAG_HAT) + + +/* + * FIXME: currently need a clean way to replace and remove profiles as a + * set. It should be done at the namespace level. + * Either, with a set of profiles loaded at the namespace level or via + * a mark and remove marked interface. + */ +enum profile_mode { + APPARMOR_ENFORCE, /* enforce access rules */ + APPARMOR_COMPLAIN, /* allow and log access violations */ + APPARMOR_KILL, /* kill task on access violation */ +}; + +enum profile_flags { + PFLAG_HAT = 1, /* profile is a hat */ + PFLAG_UNCONFINED = 2, /* profile is the unconfined profile */ + PFLAG_NULL = 4, /* profile is null learning profile */ + PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ + PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ + PFLAG_USER_DEFINED = 0x20, /* user based profile */ + PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ +}; + +#define AA_NEW_SID 0 + +struct aa_profile; + +/* struct aa_policy_common - common part of both namespaces and profiles + * @name: name of the object + * @count: reference count of the obj + * lock: lock for modifying the object + * @list: list object is on + * @profiles: head of the profiles list contained in the object + */ +struct aa_policy_common { + char *name; + struct kref count; + rwlock_t lock; + struct list_head list; + struct list_head profiles; +}; + +/* struct aa_ns_acct - accounting of profiles in namespace + * @max_size: maximum space allowed for all profiles in namespace + * @max_count: maximum number of profiles that can be in this namespace + * @size: current size of profiles + * @count: current count of profiles (includes null profiles) + */ +struct aa_ns_acct { + int max_size; + int max_count; + int size; + int count; +}; + +/* 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 + * @acct: accounting for the namespace + * @profile_count: count of profiles on @profiles list + * @size: accounting of how much memory is consumed by the contained profiles + * @unconfined: special unconfined profile for the namespace + * @count: reference count on the namespace + * @lock: lock for adding/removing profile to the namespace + * + * An aa_namespace defines the set profiles that are searched to determine + * which profile to attach to a task. Profiles can not be shared between + * aa_namespaces and profile names within a namespace are guarenteed to be + * unique. When profiles in seperate namespaces have the same name they + * are NOT considered to be equivalent. + * + * Namespace names must be unique and can not contain the characters :/\0 + * + * FIXME TODO: add vserver support so a vserer gets a default namespace + */ +struct aa_namespace { + struct aa_policy_common base; + struct aa_ns_acct acct; + int is_stale; + struct aa_profile *unconfined; +}; + + +/* struct aa_profile - basic confinement data + * @base - base componets of the profile (name, refcount, lists, lock ...) + * @fqname - The fully qualified profile name, less the namespace name + * @ns: namespace the profile is in + * @parent: parent profile of this profile, if one exists + * @replacedby: is set profile that replaced this profile + * @xmatch: optional extended matching for unconfined executables names + * @xmatch_plen: xmatch prefix len, used to determine xmatch priority + * @sid: the unique security id number of this profile + * @audit: the auditing mode of the profile + * @mode: the enforcement mode of the profile + * @flags: flags controlling profile behavior + * @size: the memory consumed by this profiles rules + * @file: The set of rules governing basic file access and domain transitions + * @caps: capabilities for the profile + * @net: network controls for the profile + * @rlimits: rlimits for the profile + * + * The AppArmor profile contains the basic confinement data. Each profile + * has a name, and exist in a namespace. The @name and @exec_match are + * used to determine profile attachment against unconfined tasks. All other + * attachments are determined by in profile X transition rules. + * + * The @replacedby field is write protected by the profile lock. Reads + * are assumed to be atomic, and are done without locking. + * + * Profiles have a hierachy where hats and children profiles keep + * a reference to their parent. + * + * Profile names can not begin with a : and can not contain the \0 + * character. If a profile name begins with / it will be considered when + * determining profile attachment on "unconfined" tasks. + */ +struct aa_profile { + struct aa_policy_common base; + char *fqname; + + struct aa_namespace *ns; + struct aa_profile *parent; + struct aa_profile *replacedby; + + struct aa_dfa *xmatch; + int xmatch_len; + u32 sid; + enum audit_mode audit; + enum profile_mode mode; + u32 flags; + int size; + + struct aa_file_rules file; + struct aa_caps caps; + struct aa_net net; + struct aa_rlimit rlimits; +}; + + +extern struct list_head ns_list; +extern rwlock_t ns_list_lock; + +extern struct aa_namespace *default_namespace; +extern enum profile_mode g_profile_mode; + + +void aa_add_profile(struct aa_policy_common *common, + struct aa_profile *profile); + +int alloc_default_namespace(void); +void free_default_namespace(void); +struct aa_namespace *alloc_aa_namespace(const char *name); +void free_aa_namespace_kref(struct kref *kref); +void free_aa_namespace(struct aa_namespace *ns); +struct aa_namespace *__aa_find_namespace(struct list_head *head, + const char *name); + +struct aa_namespace *aa_find_namespace(const char *name); +struct aa_namespace *aa_prepare_namespace(const char *name); +void aa_remove_namespace(struct aa_namespace *ns); +struct aa_namespace *aa_prepare_namespace(const char *name); +void aa_profile_list_release(struct list_head *head); +void aa_profile_ns_list_release(void); +void __aa_remove_namespace(struct aa_namespace *ns); + + +static inline struct aa_policy_common *aa_get_common(struct aa_policy_common *c) +{ + if (c) + kref_get(&c->count); + + return c; +} + +static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) +{ + if (ns) + kref_get(&(ns->base.count)); + + return ns; +} + +static inline void aa_put_namespace(struct aa_namespace *ns) +{ + if (ns) + kref_put(&ns->base.count, free_aa_namespace_kref); +} + + + +struct aa_profile *alloc_aa_profile(const char *name); +struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat); +void free_aa_profile_kref(struct kref *kref); +void free_aa_profile(struct aa_profile *profile); +struct aa_profile *__aa_find_profile(struct list_head *head, const char *name); +struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); +struct aa_policy_common *__aa_find_parent_by_fqname(struct aa_namespace *ns, + const char *fqname); +struct aa_profile *__aa_find_profile_by_fqname(struct aa_namespace *ns, + const char *fqname); +struct aa_profile *aa_find_profile_by_fqname(struct aa_namespace *ns, + const char *name); +struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); +struct aa_profile *aa_profile_newest(struct aa_profile *profile); +struct aa_profile *aa_sys_find_attach(struct aa_policy_common *base, + const char *name); +void __aa_add_profile(struct aa_policy_common *common, + struct aa_profile *profile); +void __aa_remove_profile(struct aa_profile *profile, + struct aa_profile *replacement); +void __aa_replace_profile(struct aa_profile *profile, + struct aa_profile *replacement); +void __aa_profile_list_release(struct list_head *head); + +static inline struct aa_profile *aa_filtered_profile(struct aa_profile *profile) +{ + if (profile->flags & PFLAG_UNCONFINED) + return NULL; + return profile; +} + +/** + * aa_get_profile - increment refcount on profile @p + * @p: profile + */ +static inline struct aa_profile *aa_get_profile(struct aa_profile *p) +{ + if (p) + kref_get(&(p->base.count)); + + return p; +} + +/** + * aa_put_profile - decrement refcount on profile @p + * @p: profile + */ +static inline void aa_put_profile(struct aa_profile *p) +{ + if (p) + kref_put(&p->base.count, free_aa_profile_kref); +} + +static inline int PROFILE_AUDIT_MODE(struct aa_profile *profile) +{ + if (g_apparmor_audit != AUDIT_NORMAL) + return g_apparmor_audit; + if (profile) + return profile->audit; + return AUDIT_NORMAL; +} + +#endif /* __AA_POLICY_H */ + diff -urN kernel.org/security/apparmor/include/policy_interface.h kernel/security/apparmor/include/policy_interface.h --- kernel.org/security/apparmor/include/policy_interface.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/policy_interface.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,22 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __POLICY_INTERFACE_H +#define __POLICY_INTERFACE_H + +ssize_t aa_interface_add_profiles(void *data, size_t size); +ssize_t aa_interface_replace_profiles(void *udata, size_t size); +ssize_t aa_interface_remove_profiles(char *name, size_t size); + +#endif /* __POLICY_INTERFACE_H */ diff -urN kernel.org/security/apparmor/include/procattr.h kernel/security/apparmor/include/procattr.h --- kernel.org/security/apparmor/include/procattr.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/procattr.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,26 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /proc//attr/ interface function defintions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_PROCATTR_H +#define __AA_PROCATTR_H + +#define AA_DO_TEST 1 + +int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile, + char **string); +int aa_setprocattr_changehat(char *args, int test); +int aa_setprocattr_changeprofile(char *args, int onexec, int test); +int aa_setprocattr_permipc(char *args); + +#endif /* __AA_PROCATTR_H */ diff -urN kernel.org/security/apparmor/include/resource.h kernel/security/apparmor/include/resource.h --- kernel.org/security/apparmor/include/resource.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/resource.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,46 @@ +/* + * AppArmor security module + * + * This file contains AppArmor resource limits function defintions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_RESOURCE_H +#define __AA_RESOURCE_H + +#include +#include + +struct aa_profile; + +/* struct aa_rlimit - rlimits settings for the profile + * @mask: which hard limits to set + * @limits: rlimit values that override task limits + * + * AppArmor rlimits are used to set confined task rlimits. Only the + * limits specified in @mask will be controlled by apparmor. + */ +struct aa_rlimit { + unsigned int mask; + struct rlimit limits[RLIM_NLIMITS]; +}; + + +int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, + struct rlimit *new_rlim); + +void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new); + +static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) +{ + /* NOP */ +} + +#endif /* __AA_RESOURCE_H */ diff -urN kernel.org/security/apparmor/include/sid.h kernel/security/apparmor/include/sid.h --- kernel.org/security/apparmor/include/sid.h 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/include/sid.h 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,46 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (sid) definitions + * + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_SID_H +#define __AA_SID_H + +#include + +struct aa_profile; + +#define AA_ALLOC_USR_SID 1 +#define AA_ALLOC_SYS_SID 0 + +u32 aa_alloc_sid(int is_usr); +void aa_free_sid(u32 sid); +int aa_add_sid_profile(u32 sid, struct aa_profile *profile); +int aa_replace_sid_profile(u32 sid, struct aa_profile *profile); +struct aa_profile *aa_get_sid_profile(u32 sid); + + +static inline u32 aa_compound_sid(u32 sys, u32 usr) +{ + return sys | usr; +} + +static inline u32 aa_usr_sid(u32 sid) +{ + return sid & 0xffff0000; +} + +static inline u32 aa_sys_sid(u32 sid) +{ + return sid & 0xffff; +} + +#endif /* __AA_SID_H */ diff -urN kernel.org/security/apparmor/ipc.c kernel/security/apparmor/ipc.c --- kernel.org/security/apparmor/ipc.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/ipc.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,106 @@ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include + +#include "include/audit.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/policy.h" + + +struct aa_audit_ptrace { + struct aa_audit base; + + pid_t tracer, tracee; +}; + +/* call back to audit ptrace fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_ptrace *sa = va; + audit_log_format(ab, " tracer=%d tracee=%d", sa->tracer, sa->tracee); +} + +static int aa_audit_ptrace(struct aa_profile *profile, + struct aa_audit_ptrace *sa) +{ + return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa, + audit_cb); +} + +int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, + struct aa_profile *tracee, unsigned int mode) +{ + /* TODO: currently only based on capability, not extended ptrace + * rules, + * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH + */ + + if (!tracer || tracer == tracee) + return 0; + /* log this capability request */ + return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1); +} + +int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, + unsigned int mode) +{ + /* + * tracer can ptrace tracee when + * - tracer is unconfined || + * - tracer & tracee are in the same namespace && + * - tracer is in complain mode + * - tracer has rules allowing it to trace tracee currently this is: + * - confined by the same profile || + * - tracer profile has CAP_SYS_PTRACE + */ + + struct aa_profile *tracer_p; + const struct cred *cred = aa_get_task_policy(tracer, &tracer_p); + int error = 0; + + if (tracer_p) { + struct aa_audit_ptrace sa; + memset(&sa, 0, sizeof(sa)); + sa.base.operation = "ptrace"; + sa.base.gfp_mask = GFP_ATOMIC; + sa.tracer = tracer->pid; + sa.tracee = tracee->pid; + /* FIXME: different namespace restriction can be lifted + * if, namespace are matched to AppArmor namespaces + */ + if (tracer->nsproxy != tracee->nsproxy) { + sa.base.info = "different namespaces"; + sa.base.error = -EPERM; + aa_audit(AUDIT_APPARMOR_DENIED, tracer_p, &sa.base, + audit_cb); + } else { + struct aa_profile *tracee_p; + struct cred *lcred = aa_get_task_policy(tracee, + &tracee_p); + + sa.base.error = aa_may_ptrace(tracer, tracer_p, + tracee_p, mode); + sa.base.error = aa_audit_ptrace(tracer_p, &sa); + + put_cred(lcred); + } + error = sa.base.error; + } + put_cred(cred); + + return error; +} diff -urN kernel.org/security/apparmor/Kconfig kernel/security/apparmor/Kconfig --- kernel.org/security/apparmor/Kconfig 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/Kconfig 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,53 @@ +config SECURITY_APPARMOR + bool "AppArmor support" + depends on SECURITY && SECURITY_NETWORK && NET && INET + select AUDIT + select SECURITY_PATH + select SECURITYFS + default n + help + This enables the AppArmor security module. + 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_NETWORK + bool "AppArmor network support" + depends on SECURITY_APPARMOR + default n + help + This enables AppArmor to mediate applications network use. + This will enable the SECURITY_NETWORK hooks. + +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. diff -urN kernel.org/security/apparmor/lib.c kernel/security/apparmor/lib.c --- kernel.org/security/apparmor/lib.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/lib.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,100 @@ +/* + * AppArmor security module + * + * This file contains basic common functions used in AppArmor + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include + +#include "include/audit.h" + +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\n", str); + if (audit_enabled) + aa_audit(AUDIT_APPARMOR_STATUS, NULL, &sa, NULL); +} + +char *strchrnul(const char *s, int c) +{ + for (; *s != (char)c && *s != '\0'; ++s) + ; + return (char *)s; +} + +char *aa_split_name_from_ns(char *args, char **ns_name) +{ + char *name = strstrip(args); + + *ns_name = NULL; + if (args[0] == ':') { + char *split = strstrip(strchr(&args[1], ':')); + + if (!split) + return NULL; + + *split = 0; + *ns_name = &args[1]; + name = strstrip(split + 1); + } + if (*name == 0) + name = NULL; + + return name; +} + +char *new_compound_name(const char *n1, const char *n2) +{ + char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); + if (name) + sprintf(name, "%s//%s", n1, n2); + return name; +} + +/** + * aa_strneq - compare null terminated @str to a non null terminated substring + * @str: a null terminated string + * @sub: a substring, not necessarily null terminated + * @len: length of @sub to compare + * + * The @str string must be full consumed for this to be considered a match + */ +int aa_strneq(const char *str, const char *sub, int len) +{ + int res = strncmp(str, sub, len); + if (res) + return 0; + if (str[len] == 0) + return 1; + return 0; +} + +const char *fqname_subname(const char *name) +{ + char *split; + /* check for namespace which begins with a : and ends with : or \0 */ + name = strstrip((char *) name); + if (*name == ':') { + split = strchrnul(name + 1, ':'); + if (*split == '\0') + return NULL; + name = strstrip(split + 1); + } + for (split = strstr(name, "//"); split; split = strstr(name, "//")) { + name = split + 2; + } + return name; +} diff -urN kernel.org/security/apparmor/lsm.c kernel/security/apparmor/lsm.c --- kernel.org/security/apparmor/lsm.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/lsm.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,1063 @@ +/* + * AppArmor security module + * + * This file contains AppArmor LSM hooks. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/apparmorfs.h" +#include "include/audit.h" +#include "include/capability.h" +#include "include/context.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/net.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/procattr.h" + +/* Flag indicating whether initialization completed */ +int apparmor_initialized; + + +/* + * LSM hook functions + */ + +/* + * prepare new aa_task_context for modification by prepare_cred block + */ +static int apparmor_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + struct aa_task_context *cxt = aa_dup_task_context(old->security, gfp); + if (!cxt) + return -ENOMEM; + new->security = cxt; + return 0; +} + +/* + * free the associated aa_task_context and put its profiles + */ +static void apparmor_cred_free(struct cred *cred) +{ + struct aa_task_context *cxt = cred->security; + cred->security = NULL; + aa_free_task_context(cxt); +} + + +static int apparmor_ptrace_may_access(struct task_struct *child, + unsigned int mode) +{ + return aa_ptrace(current, child, mode); +} + + +static int apparmor_ptrace_traceme(struct task_struct *parent) +{ + return aa_ptrace(parent, current, PTRACE_MODE_ATTACH); +} + +/* Derived from security/commoncap.c:cap_capget */ +static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + struct aa_profile *profile; + const struct cred *cred; + + rcu_read_lock(); + cred = __task_cred(target); + aa_cred_policy(cred, &profile); + + *effective = cred->cap_effective; + *inheritable = cred->cap_inheritable; + *permitted = cred->cap_permitted; + + if (profile) { + *effective = cap_combine(*effective, profile->caps.set); + *effective = cap_intersect(*effective, profile->caps.allowed); + } + rcu_read_unlock(); + + return 0; +} + +static int apparmor_capable(struct task_struct *task, const struct cred *cred, + int cap, int audit) +{ + struct aa_profile *profile; + /* cap_capable returns 0 on success, else -EPERM */ + int error = cap_capable(task, cred, cap, audit); + + aa_cred_policy(cred, &profile); + if (profile && (!error || cap_raised(profile->caps.set, cap))) + error = aa_capable(task, profile, cap, audit); + + return error; +} + +static int apparmor_sysctl(struct ctl_table *table, int op) +{ + int error = 0; + struct aa_profile *profile = aa_current_profile_wupd(); + + if (profile) { + char *buffer, *name; + int mask; + + mask = 0; + if (op & 4) + mask |= MAY_READ; + if (op & 2) + mask |= MAY_WRITE; + + error = -ENOMEM; + buffer = (char *)__get_free_page(GFP_KERNEL); + if (!buffer) + goto out; + + /* + * TODO: convert this over to using a global or per + * namespace control instead of a hard coded /proc + */ + name = sysctl_pathname(table, buffer, PAGE_SIZE); + if (name && name - buffer >= 5) { + struct path_cond cond = { 0, S_IFREG }; + name -= 5; + memcpy(name, "/proc", 5); + error = aa_pathstr_perm(profile, "sysctl", name, mask, + &cond); + } + free_page((unsigned long)buffer); + } + +out: + return error; +} + +static int common_perm(const char *op, struct path *path, u16 mask, + struct path_cond *cond) +{ + struct aa_profile *profile; + int error = 0; + + profile = aa_current_profile(); + if (profile) + error = aa_path_perm(profile, op, path, mask, cond); + + return error; +} + +static int common_perm_dentry(const char *op, struct path *dir, + struct dentry *dentry, u16 mask, + struct path_cond *cond) +{ + struct path path = { dir->mnt, dentry }; + + return common_perm(op, &path, mask, cond); +} + +static int common_perm_rm(const char *op, struct path *dir, + struct dentry *dentry, u16 mask) +{ + struct inode *inode = dentry->d_inode; + struct path_cond cond = {}; + + if (!dir->mnt || !inode || !mediated_filesystem(inode)) + return 0; + + cond.uid = inode->i_uid; + cond.mode = inode->i_mode; + + return common_perm_dentry(op, dir, dentry, mask, &cond); +} + +static int common_perm_create(const char *op, struct path *dir, + struct dentry *dentry, u16 mask, umode_t mode) +{ + struct path_cond cond = { current_fsuid(), mode }; + + if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode)) + return 0; + + return common_perm_dentry(op, dir, dentry, mask, &cond); +} + +static int apparmor_path_unlink(struct path *dir, struct dentry *dentry) +{ + return common_perm_rm("unlink", dir, dentry, MAY_WRITE); +} + +static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, + int mode) +{ + return common_perm_create("mkdir", dir, dentry, AA_MAY_CREATE, S_IFDIR); +} + +static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry) +{ + return common_perm_rm("rmdir", dir, dentry, MAY_WRITE); +} + +static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, + int mode, unsigned int dev) +{ + return common_perm_create("mknod", dir, dentry, AA_MAY_CREATE, mode); +} + +static int apparmor_path_truncate(struct path *path, loff_t length, + unsigned int time_attrs) +{ + struct path_cond cond = { path->dentry->d_inode->i_uid, + path->dentry->d_inode->i_mode }; + + if (!path->mnt || !mediated_filesystem(path->dentry->d_inode)) + return 0; + return common_perm("truncate", path, MAY_WRITE, &cond); +} + +static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, + const char *old_name) +{ + return common_perm_create("symlink_create", dir, dentry, AA_MAY_CREATE, + S_IFLNK); +} + +static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, + struct dentry *new_dentry) +{ + struct aa_profile *profile; + int error = 0; + + if (!mediated_filesystem(old_dentry->d_inode)) + return 0; + + profile = aa_current_profile_wupd(); + if (profile) + error = aa_path_link(profile, old_dentry, new_dir, new_dentry); + return error; +} + +static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, + struct path *new_dir, struct dentry *new_dentry) +{ + struct aa_profile *profile; + int error = 0; + + if (!mediated_filesystem(old_dentry->d_inode)) + return 0; + + profile = aa_current_profile_wupd(); + if (profile) { + struct path old_path = { old_dir->mnt, old_dentry }; + struct path new_path = { new_dir->mnt, new_dentry }; + struct path_cond cond = { old_dentry->d_inode->i_uid, + old_dentry->d_inode->i_mode }; + + error = aa_path_perm(profile, "rename_src", &old_path, + MAY_READ | MAY_WRITE, &cond); + if (!error) + error = aa_path_perm(profile, "rename_dest", &new_path, + AA_MAY_CREATE | MAY_WRITE, &cond); + + } + return error; +} + +static int apparmor_dentry_open(struct file *file, const struct cred *cred) +{ + struct aa_profile *profile; + int error = 0; + + /* If in exec permission is handled by bprm hooks */ + if (current->in_execve || + !mediated_filesystem(file->f_path.dentry->d_inode)) + return 0; + + aa_cred_policy(cred, &profile); + if (profile) { + struct aa_file_cxt *fcxt = file->f_security; + struct inode *inode = file->f_path.dentry->d_inode; + struct path_cond cond = { inode->i_uid, inode->i_mode }; + + error = aa_path_perm(profile, "open", &file->f_path, + aa_map_file_to_perms(file), &cond); + fcxt->profile = aa_get_profile(profile); + /* todo cache actual allowed permissions */ + fcxt->allowed = 0; + } + + return error; +} + +static int apparmor_file_alloc_security(struct file *file) +{ + file->f_security = aa_alloc_file_context(GFP_KERNEL); + if (!file->f_security) + return -ENOMEM; + return 0; + +} + +static void apparmor_file_free_security(struct file *file) +{ + struct aa_file_cxt *cxt = file->f_security; + + aa_free_file_context(cxt); +} + +static int apparmor_file_permission(struct file *file, int mask) +{ + /* + * Most basic (rw) file access is revalidated at exec. + * The revalidation done here is for parent/child hat + * file accesses. + * + * Currently profile replacement does not cause revalidation + * or file revocation. + * + * TODO: cache profiles that have revalidated? + */ + struct aa_file_cxt *fcxt = file->f_security; + struct aa_profile *profile, *fprofile = fcxt->profile; + int error = 0; + + if (!fprofile || !file->f_path.mnt || + !mediated_filesystem(file->f_path.dentry->d_inode)) + return 0; + + profile = aa_current_profile(); + /* TODO: Enable at exec time revalidation of files + if (profile && (fprofile != profile) && + ((PROFILE_IS_HAT(profile) && (profile->parent == fprofile)) || + (PROFILE_IS_HAT(fprofile) && (fprofile->parent == profile)))) + error = aa_file_perm(profile, "file_perm", file, mask); + */ + if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed))) + error = aa_file_perm(profile, "file_perm", file, mask); + + return error; +} + +static int common_file_perm(const char *op, struct file *file, u16 mask) +{ + const struct aa_file_cxt *fcxt = file->f_security; + struct aa_profile *profile, *fprofile = fcxt->profile; + int error = 0; + + if (!fprofile || !file->f_path.mnt || + !mediated_filesystem(file->f_path.dentry->d_inode)) + return 0; + + profile = aa_current_profile_wupd(); + if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed))) + error = aa_file_perm(profile, op, file, mask); + + return error; +} + +static int apparmor_file_lock(struct file *file, unsigned int cmd) +{ + u16 mask = AA_MAY_LOCK; + + if (cmd == F_WRLCK) + mask |= MAY_WRITE; + + return common_file_perm("file_lock", file, mask); +} + + +/* + * AppArmor doesn't current use the fcntl hook. + * + * FIXME - these are not implemented yet - REMOVE file_fcntl hook + * NOTE: some of the file control commands are further mediated + * by other hooks + * F_SETOWN - security_file_set_fowner + * F_SETLK - security_file_lock + * F_SETLKW - security_file_lock + * O_APPEND - AppArmor mediates append as a subset of full write + * so changing from full write to appending write is + * dropping priviledge and not restricted. + + +static int apparmor_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} +*/ + +static int common_mmap(struct file *file, const char *operation, + unsigned long prot, unsigned long flags) +{ + struct dentry *dentry; + int mask = 0; + + if (!file || !file->f_security) + return 0; + + if (prot & PROT_READ) + mask |= MAY_READ; + /* Private mappings don't require write perms since they don't + * write back to the files */ + if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE)) + mask |= MAY_WRITE; + if (prot & PROT_EXEC) + mask |= AA_EXEC_MMAP; + + dentry = file->f_path.dentry; + return common_file_perm(operation, file, mask); +} + +static int apparmor_file_mmap(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags, + unsigned long addr, unsigned long addr_only) +{ + if ((addr < mmap_min_addr) && !capable(CAP_SYS_RAWIO)) { + struct aa_profile *profile = aa_current_profile_wupd(); + if (profile) + /* future control check here */ + return -EACCES; + else + return -EACCES; + } + + return common_mmap(file, "file_mmap", prot, flags); +} + +static int apparmor_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, unsigned long prot) +{ + return common_mmap(vma->vm_file, "file_mprotect", prot, + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); +} + +static int apparmor_getprocattr(struct task_struct *task, char *name, + char **value) +{ + int error = -ENOENT; + struct aa_namespace *ns; + struct aa_profile *profile, *onexec, *prev; + const struct cred *cred = aa_get_task_policy(task, &profile); + struct aa_task_context *cxt = cred->security; + ns = cxt->sys.profile->ns; + onexec = cxt->sys.onexec; + prev = cxt->sys.previous; + + /* task must be either querying itself, unconfined or can ptrace */ + if (current != task && profile && !capable(CAP_SYS_PTRACE)) { + error = -EPERM; + } else { + if (strcmp(name, "current") == 0) { + error = aa_getprocattr(ns, profile, value); + } else if (strcmp(name, "prev") == 0) { + if (prev) + error = aa_getprocattr(ns, prev, value); + } else if (strcmp(name, "exec") == 0) { + if (onexec) + error = aa_getprocattr(ns, onexec, value); + } else { + error = -EINVAL; + } + } + + put_cred(cred); + + return error; +} + +static int apparmor_setprocattr(struct task_struct *task, char *name, + void *value, size_t size) +{ + char *command, *args; + int error; + + if (size == 0 || size >= PAGE_SIZE) + return -EINVAL; + + /* task can only write its own attributes */ + if (current != task) + return -EACCES; + + args = value; + args[size] = '\0'; + args = strstrip(args); + command = strsep(&args, " "); + if (!args) + return -EINVAL; + while (isspace(*args)) + args++; + if (!*args) + return -EINVAL; + + if (strcmp(name, "current") == 0) { + if (strcmp(command, "changehat") == 0) { + error = aa_setprocattr_changehat(args, !AA_DO_TEST); + } else if (strcmp(command, "permhat") == 0) { + error = aa_setprocattr_changehat(args, AA_DO_TEST); + } else if (strcmp(command, "changeprofile") == 0) { + error = aa_setprocattr_changeprofile(args, 0, + !AA_DO_TEST); + } else if (strcmp(command, "permprofile") == 0) { + error = aa_setprocattr_changeprofile(args, 0, + AA_DO_TEST); + } else if (strcmp(command, "permipc") == 0) { + error = aa_setprocattr_permipc(args); + } else { + struct aa_audit sa; + memset(&sa, 0, sizeof(sa)); + sa.operation = "setprocattr"; + sa.gfp_mask = GFP_KERNEL; + sa.info = name; + sa.error = -EINVAL; + return aa_audit(AUDIT_APPARMOR_DENIED, NULL, &sa, NULL); + } + } else if (strcmp(name, "exec") == 0) { + error = aa_setprocattr_changeprofile(strstrip(args), 1, + !AA_DO_TEST); + } else { + /* only support the "current" and "exec" process attributes */ + return -EINVAL; + } + if (!error) + error = size; + return error; +} + +static int apparmor_task_setrlimit(unsigned int resource, + struct rlimit *new_rlim) +{ + struct aa_profile *profile = aa_current_profile_wupd(); + int error = 0; + + if (profile) { + error = aa_task_setrlimit(profile, resource, new_rlim); + } + + return error; +} + +#ifdef CONFIG_SECURITY_APPARMOR_NETWORK +static int apparmor_socket_create(int family, int type, int protocol, int kern){ + struct aa_profile *profile; + int error = 0; + + if (kern) + return 0; + + profile = aa_current_profile(); + if (profile) + error = aa_net_perm(profile, "socket_create", family, + type, protocol); + return error; +} + +static int apparmor_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + struct sock *sk = sock->sk; + + if (kern) + return 0; + + return aa_revalidate_sk(sk, "socket_post_create"); +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_bind"); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_connect"); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_listen"); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_accept"); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_sendmsg"); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_recvmsg"); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_getsockname"); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_getpeername"); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_getsockopt"); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_setsockopt"); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(sk, "socket_shutdown"); +} +#endif + +static struct security_operations apparmor_ops = { + .name = "apparmor", + + .ptrace_may_access = apparmor_ptrace_may_access, + .ptrace_traceme = apparmor_ptrace_traceme, + .capget = apparmor_capget, + .sysctl = apparmor_sysctl, + .capable = apparmor_capable, +/* + .inode_create = apparmor_inode_create, + .inode_setattr = apparmor_inode_setattr, + .inode_setxattr = apparmor_inode_setxattr, + .inode_getxattr = apparmor_inode_getxattr, + .inode_listxattr = apparmor_inode_listxattr, + .inode_removexattr = apparmor_inode_removexattr, + .inode_permission = ??? use to mediate owner access to non-mediated fs +*/ + + .path_link = apparmor_path_link, + .path_unlink = apparmor_path_unlink, + .path_symlink = apparmor_path_symlink, + .path_mkdir = apparmor_path_mkdir, + .path_rmdir = apparmor_path_rmdir, + .path_mknod = apparmor_path_mknod, + .path_rename = apparmor_path_rename, + .path_truncate = apparmor_path_truncate, + .dentry_open = apparmor_dentry_open, + + .file_permission = apparmor_file_permission, + .file_alloc_security = apparmor_file_alloc_security, + .file_free_security = apparmor_file_free_security, + .file_mmap = apparmor_file_mmap, + .file_mprotect = apparmor_file_mprotect, + .file_lock = apparmor_file_lock, + +/* .file_fcntl = apparmor_file_fcntl, */ + + .getprocattr = apparmor_getprocattr, + .setprocattr = apparmor_setprocattr, + +#ifdef CONFIG_SECURITY_APPARMOR_NETWORK + .socket_create = apparmor_socket_create, + .socket_post_create = apparmor_socket_post_create, + .socket_bind = apparmor_socket_bind, + .socket_connect = apparmor_socket_connect, + .socket_listen = apparmor_socket_listen, + .socket_accept = apparmor_socket_accept, + .socket_sendmsg = apparmor_socket_sendmsg, + .socket_recvmsg = apparmor_socket_recvmsg, + .socket_getsockname = apparmor_socket_getsockname, + .socket_getpeername = apparmor_socket_getpeername, + .socket_getsockopt = apparmor_socket_getsockopt, + .socket_setsockopt = apparmor_socket_setsockopt, + .socket_shutdown = apparmor_socket_shutdown, +#endif + + .cred_free = apparmor_cred_free, + .cred_prepare = apparmor_cred_prepare, + + .bprm_set_creds = apparmor_bprm_set_creds, + // .bprm_committing_creds = apparmor_bprm_committing_creds, + .bprm_committed_creds = apparmor_bprm_committed_creds, + .bprm_secureexec = apparmor_bprm_secureexec, + + .task_setrlimit = apparmor_task_setrlimit, +}; + + +/* + * AppArmor sysfs module parameters + */ + +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) + +static int param_set_aauint(const char *val, struct kernel_param *kp); +static int param_get_aauint(char *buffer, struct kernel_param *kp); +#define param_check_aauint(name, p) __param_check(name, p, int) + +static int param_set_aalockpolicy(const char *val, struct kernel_param *kp); +static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp); +#define param_check_aalockpolicy(name, p) __param_check(name, p, int) + +static int param_set_audit(const char *val, struct kernel_param *kp); +static int param_get_audit(char *buffer, struct kernel_param *kp); +#define param_check_audit(name, p) __param_check(name, p, int) + +static int param_set_mode(const char *val, struct kernel_param *kp); +static int param_get_mode(char *buffer, struct kernel_param *kp); +#define param_check_mode(name, p) __param_check(name, p, int) + +/* Flag values, also controllable via /sys/module/apparmor/parameters + * We define special types as we want to do additional mediation. + */ + +/* AppArmor global enforcement switch - complain, enforce, kill */ +enum profile_mode g_profile_mode = APPARMOR_ENFORCE; +module_param_call(mode, param_set_mode, param_get_mode, + &g_profile_mode, S_IRUSR | S_IWUSR); + +/* Debug mode */ +int g_apparmor_debug; +module_param_named(debug, g_apparmor_debug, aabool, S_IRUSR | S_IWUSR); + +/* Audit mode */ +enum audit_mode g_apparmor_audit; +module_param_call(audit, param_set_audit, param_get_audit, + &g_apparmor_audit, S_IRUSR | S_IWUSR); + +/* Determines if audit header is included in audited messages. This + * provides more context if the audit daemon is not running + */ +int g_apparmor_audit_header; +module_param_named(audit_header, g_apparmor_audit_header, aabool, + S_IRUSR | S_IWUSR); + +/* lock out loading/removal of policy + * TODO: add in at boot loading of policy, which is the only way to + * load policy, if lock_policy is set + */ +int g_apparmor_lock_policy; +module_param_named(lock_policy, g_apparmor_lock_policy, aalockpolicy, + S_IRUSR | S_IWUSR); + +/* Syscall logging mode */ +int g_apparmor_logsyscall; +module_param_named(logsyscall, g_apparmor_logsyscall, aabool, + S_IRUSR | S_IWUSR); + +/* Maximum pathname length before accesses will start getting rejected */ +unsigned int g_apparmor_path_max = 2 * PATH_MAX; +module_param_named(path_max, g_apparmor_path_max, aauint, S_IRUSR | S_IWUSR); + +/* 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); +static 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); + +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_aalockpolicy(const char *val, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + if (g_apparmor_lock_policy) + return -EACCES; + return param_set_bool(val, kp); +} + +static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + return param_get_bool(buffer, kp); +} + +static int param_set_aabool(const char *val, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + return param_set_bool(val, kp); +} + +static int param_get_aabool(char *buffer, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + return param_get_bool(buffer, kp); +} + +static int param_set_aauint(const char *val, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + return param_set_uint(val, kp); +} + +static int param_get_aauint(char *buffer, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + 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) +{ + unsigned long l; + + if (!apparmor_initialized) { + apparmor_enabled = 0; + return 0; + } + + if (__aa_task_is_confined(current)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + if (!val) + return -EINVAL; + + if (strict_strtoul(val, 0, &l) || l != 0) + return -EINVAL; + + apparmor_enabled = 0; + apparmor_disable(); + return 0; +} + +static int param_get_audit(char *buffer, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + return sprintf(buffer, "%s", audit_mode_names[g_apparmor_audit]); +} + +static int param_set_audit(const char *val, struct kernel_param *kp) +{ + int i; + if (__aa_task_is_confined(current)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + if (!val) + return -EINVAL; + + for (i = 0; i < AUDIT_MAX_INDEX; i++) { + if (strcmp(val, audit_mode_names[i]) == 0) { + g_apparmor_audit = i; + return 0; + } + } + + return -EINVAL; +} + +static int param_get_mode(char *buffer, struct kernel_param *kp) +{ + if (__aa_task_is_confined(current)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + return sprintf(buffer, "%s", profile_mode_names[g_profile_mode]); +} + +static int param_set_mode(const char *val, struct kernel_param *kp) +{ + int i; + if (__aa_task_is_confined(current)) + return -EPERM; + + if (!apparmor_enabled) + return -EINVAL; + + if (!val) + return -EINVAL; + + for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) { + if (strcmp(val, profile_mode_names[i]) == 0) { + g_profile_mode = i; + return 0; + } + } + + return -EINVAL; +} + + +/* + * AppArmor init functions + */ + +static int set_init_cxt(void) +{ + struct cred *cred = (struct cred *) current->real_cred; + struct aa_task_context *cxt; + + cxt = aa_alloc_task_context(GFP_KERNEL); + if (!cxt) + return -ENOMEM; + + cxt->sys.profile = aa_get_profile(default_namespace->unconfined); + cred->security = cxt; + + return 0; +} + +static int __init apparmor_init(void) +{ + int error; + + if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) { + info_message("AppArmor disabled by boot time parameter\n"); + apparmor_enabled = 0; + return 0; + } + + /* + * Activated with fs_initcall + error = create_apparmorfs(); + if (error) { + AA_ERROR("Unable to activate AppArmor filesystem\n"); + goto createfs_out; + } + */ + + error = alloc_default_namespace(); + if (error) { + AA_ERROR("Unable to allocate default profile namespace\n"); + goto alloc_out; + } + + error = set_init_cxt(); + if (error) { + AA_ERROR("Failed to set context on init task\n"); + goto alloc_out; + } + + error = register_security(&apparmor_ops); + if (error) { + AA_ERROR("Unable to register AppArmor\n"); + goto register_security_out; + } + + /* Report that AppArmor successfully initialized */ + apparmor_initialized = 1; + if (g_profile_mode == APPARMOR_COMPLAIN) + info_message("AppArmor initialized: complain mode enabled"); + else if (g_profile_mode == APPARMOR_KILL) + info_message("AppArmor initialized: kill mode enabled"); + else + info_message("AppArmor initialized"); + + return error; + +register_security_out: + free_default_namespace(); + +alloc_out: + destroy_apparmorfs(); + +/*createfs_out:*/ + apparmor_enabled = 0; + return error; + +} + +security_initcall(apparmor_init); + +void apparmor_disable(void) +{ + /* Remove and release all the profiles on the profile list. */ + aa_profile_ns_list_release(); + + /* FIXME: cleanup profiles references on files */ + free_default_namespace(); + + /* + * Delay for an rcu cycle to make sure that all active task + * context readers have finished, and all profiles have been + * freed by their rcu callbacks. + */ + synchronize_rcu(); + destroy_apparmorfs(); + apparmor_initialized = 0; + + info_message("AppArmor protection disabled"); +} + diff -urN kernel.org/security/apparmor/Makefile kernel/security/apparmor/Makefile --- kernel.org/security/apparmor/Makefile 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/Makefile 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,24 @@ +# Makefile for AppArmor Linux Security Module +# +obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o + +apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ + path.o domain.o policy.o policy_interface.o procattr.o lsm.o \ + resource.o sid.o file.o + +apparmor-$(CONFIG_SECURITY_APPARMOR_NETWORK) += net.o + +clean-files: capability_names.h af_names.h + +quiet_cmd_make-caps = GEN $@ +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 "};" >> $@ + +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ; sed -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "s/^\#define[ \\t]\\+AF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@ + +$(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/af_names.h +$(obj)/capability_names.h : $(srctree)/include/linux/capability.h + $(call cmd,make-caps) +$(obj)/af_names.h : $(srctree)/include/linux/socket.h + $(call cmd,make-af) diff -urN kernel.org/security/apparmor/match.c kernel/security/apparmor/match.c --- kernel.org/security/apparmor/match.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/match.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,305 @@ +/* + * AppArmor security module + * + * This file contains AppArmor dfa based regular expression matching engine + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include +#include + +/* TODO: remove !!!! */ +// #include + +#include "include/apparmor.h" +#include "include/match.h" +#include "include/file.h" + +static void free_table(struct table_header *table) +{ + if (is_vmalloc_addr(table)) + vfree(table); + else + kfree(table); +} + +static struct table_header *unpack_table(void *blob, size_t bsize) +{ + struct table_header *table = NULL; + struct table_header th; + size_t tsize; + + if (bsize < sizeof(struct table_header)) + goto out; + + th.td_id = be16_to_cpu(*(u16 *) (blob)); + th.td_flags = be16_to_cpu(*(u16 *) (blob + 2)); + th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8)); + blob += sizeof(struct table_header); + + if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || + th.td_flags == YYTD_DATA8)) + goto out; + + tsize = table_size(th.td_lolen, th.td_flags); + if (bsize < tsize) + goto out; + + table = kmalloc(tsize, GFP_KERNEL); + if (!table) + table = vmalloc(tsize); + if (table) { + *table = th; + if (th.td_flags == YYTD_DATA8) + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u8, byte_to_byte); + else if (th.td_flags == YYTD_DATA16) + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u16, be16_to_cpu); + else + UNPACK_ARRAY(table->td_data, blob, th.td_lolen, + u32, be32_to_cpu); + } + +out: + return table; +} + +int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size) +{ + int hsize, i; + int error = -ENOMEM; + + /* get dfa table set header */ + if (size < sizeof(struct table_set_header)) + goto fail; + + if (ntohl(*(u32 *)blob) != YYTH_MAGIC) + goto fail; + + hsize = ntohl(*(u32 *)(blob + 4)); + if (size < hsize) + goto fail; + + blob += hsize; + size -= hsize; + + error = -EPROTO; + while (size > 0) { + struct table_header *table; + table = unpack_table(blob, size); + if (!table) + goto fail; + + 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) + goto fail; + break; + case YYTD_ID_DEF: + case YYTD_ID_NXT: + case YYTD_ID_CHK: + dfa->tables[table->td_id - 1] = table; + if (table->td_flags != YYTD_DATA16) + goto fail; + break; + case YYTD_ID_EC: + dfa->tables[table->td_id - 1] = table; + if (table->td_flags != YYTD_DATA8) + goto fail; + break; + default: + free_table(table); + goto fail; + } + + blob += table_size(table->td_lolen, table->td_flags); + size -= table_size(table->td_lolen, table->td_flags); + } + + return 0; + +fail: + for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) { + free_table(dfa->tables[i]); + dfa->tables[i] = NULL; + } + return error; +} + +/** + * verify_dfa - verify that all the transitions and states in the dfa tables + * are in bounds. + * @dfa: dfa to test + * + * assumes dfa has gone through the verification done by unpacking + */ +int verify_dfa(struct aa_dfa *dfa) +{ + size_t i, state_count, trans_count; + int error = -EPROTO; + + /* check that required tables exist */ + 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] && + dfa->tables[YYTD_ID_CHK - 1])) + goto out; + + /* 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_ACCEPT2 - 1]->td_lolen)) + goto out; + + /* next.size == chk.size */ + trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen; + if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen) + goto out; + + /* if equivalence classes then its table size must be 256 */ + if (dfa->tables[YYTD_ID_EC - 1] && + dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256) + goto out; + + for (i = 0; i < state_count; i++) { + if (DEFAULT_TABLE(dfa)[i] >= state_count) + goto out; + if (BASE_TABLE(dfa)[i] >= trans_count + 256) + goto out; + } + + for (i = 0; i < trans_count ; i++) { + if (NEXT_TABLE(dfa)[i] >= state_count) + goto out; + if (CHECK_TABLE(dfa)[i] >= state_count) + goto out; + } + + /* verify accept permissions */ + for (i = 0; i < state_count; i++) { + int mode = ACCEPT_TABLE(dfa)[i]; + + if (mode & ~DFA_VALID_PERM_MASK) + goto out; + if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK) + goto out; + + } + + error = 0; +out: + return error; +} + +struct aa_dfa *aa_match_alloc(void) +{ + return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL); +} + +void aa_match_free(struct aa_dfa *dfa) +{ + if (dfa) { + int i; + + for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) + free_table(dfa->tables[i]); + } + kfree(dfa); +} + +/** + * aa_dfa_match_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_len 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. + * + * This function will happily match again the 0 byte and only finishes + * when @len input is consumed. + */ +unsigned int aa_dfa_match_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 = 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_match(struct aa_dfa *dfa, unsigned int start, + const char *str) +{ + return aa_dfa_match_len(dfa, start, str, strlen(str)); +} + +/** + * 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_match_len(dfa, start, "", 1); +} + diff -urN kernel.org/security/apparmor/net.c kernel/security/apparmor/net.c --- kernel.org/security/apparmor/net.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/net.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,146 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/net.h" +#include "include/policy.h" + +#include "af_names.h" + +static const char *sock_type_names[] = { + "unknown(0)", + "stream", + "dgram", + "raw", + "rdm", + "seqpacket", + "dccp", + "unknown(7)", + "unknown(8)", + "unknown(9)", + "packet", +}; + +struct aa_audit_net { + struct aa_audit base; + + int family, type, protocol; + +}; + +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_net *sa = va; + + if (sa->family || sa->type) { + if (address_family_names[sa->family]) + audit_log_format(ab, " family=\"%s\"", + address_family_names[sa->family]); + else + audit_log_format(ab, " family=\"unknown(%d)\"", + sa->family); + + if (sock_type_names[sa->type]) + audit_log_format(ab, " sock_type=\"%s\"", + sock_type_names[sa->type]); + else + audit_log_format(ab, " sock_type=\"unknown(%d)\"", + sa->type); + + audit_log_format(ab, " protocol=%d", sa->protocol); + } + +} + +static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa) +{ + int type = AUDIT_APPARMOR_AUTO; + + if (likely(!sa->base.error)) { + u16 audit_mask = profile->net.audit[sa->family]; + if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa->type & audit_mask))) + return 0; + type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa->type) & ~quiet_mask; + + if (denied & kill_mask) + type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET && + PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) + return PROFILE_COMPLAIN(profile) ? 0 : sa->base.error; + } + + return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb); +} + +int aa_net_perm(struct aa_profile *profile, char *operation, + int family, int type, int protocol) +{ + struct aa_audit_net sa; + u16 family_mask; + + if ((family < 0) || (family >= AF_MAX)) + return -EINVAL; + + if ((type < 0) || (type >= SOCK_MAX)) + return -EINVAL; + + /* unix domain and netlink sockets are handled by ipc */ + if (family == AF_UNIX || family == AF_NETLINK) + return 0; + + family_mask = profile->net.allowed[family]; + + memset(&sa, 0, sizeof(sa)); + sa.base.error = (family_mask & (1 << type)) ? 0 : -EACCES; + sa.base.operation = operation; + sa.base.gfp_mask = GFP_KERNEL; + sa.family = family; + sa.type = type; + sa.protocol = protocol; + + return aa_audit_net(profile, &sa); +} + +int aa_revalidate_sk(struct sock *sk, char *operation) +{ + struct aa_profile *profile; + struct cred *cred; + int error = 0; + + /* this is some debugging code to flush out the network hooks that + that are called in interrupt context */ + if (in_interrupt()) { + printk(KERN_WARNING "AppArmor Debug: Hook being called from interrupt context\n"); + dump_stack(); + return 0; + } + + cred = aa_get_task_policy(current, &profile); + if (profile) + error = aa_net_perm(profile, operation, + sk->sk_family, sk->sk_type, + sk->sk_protocol); + put_cred(cred); + + return error; +} diff -urN kernel.org/security/apparmor/path.c kernel/security/apparmor/path.c --- kernel.org/security/apparmor/path.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/path.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,170 @@ +/* + * AppArmor security module + * + * This file contains AppArmor function for pathnames + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "include/apparmor.h" +#include "include/path.h" + +int aa_get_name_to_buffer(struct path *path, int is_dir, char *buffer, int size, + char **name) +{ + int error = d_namespace_path(path, buffer, size - is_dir, name); + + if (!error && is_dir && (*name)[1] != '\0') + /* + * Append "/" to the pathname. The root directory is a special + * case; it already ends in slash. + */ + strcpy(&buffer[size - 2], "/"); + + return error; +} + +/** + * aa_get_name - compute the pathname of a file + * @path: path the file + * @is_dir: set if the file is a directory + * @buffer: buffer that aa_get_name() allocated + * @name: the error code indicating whether aa_get_name failed + * + * Returns an error code if the there was a failure in obtaining the + * name. + * + * @name is apointer to the beginning of the pathname (which usually differs + * from the beginning of the buffer), or NULL. If there is an error @name + * may contain a partial or invalid name (in the case of a deleted file), that + * can be used for audit purposes, but it can not be used for mediation. + * + * We need @is_dir to indicate whether the file is a directory or not because + * the file may not yet exist, and so we cannot check the inode's file type. + */ +int aa_get_name(struct path *path, int is_dir, char **buffer, char **name) +{ + char *buf, *str = NULL; + int size = 256; + int error; + + *name = NULL; + *buffer = NULL; + for (;;) { + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + error = aa_get_name_to_buffer(path, is_dir, buf, size, &str); + if (!error || (error == -ENOENT) || (error == -ESTALE)) + break; + + kfree(buf); + size <<= 1; + if (size > g_apparmor_path_max) + return -ENAMETOOLONG; + } + *buffer = buf; + *name = str; + + return error; +} + +int d_namespace_path(struct path *path, char *buf, int buflen, char **name) +{ + struct path root, tmp, ns_root = { }; + char *res; + int error = 0; + + read_lock(¤t->fs->lock); + root = current->fs->root; + path_get(¤t->fs->root); + read_unlock(¤t->fs->lock); + spin_lock(&vfsmount_lock); + if (root.mnt && root.mnt->mnt_ns) + ns_root.mnt = mntget(root.mnt->mnt_ns->root); + if (ns_root.mnt) + ns_root.dentry = dget(ns_root.mnt->mnt_root); + spin_unlock(&vfsmount_lock); + spin_lock(&dcache_lock); + tmp = ns_root; + res = __d_path(path, &tmp, buf, buflen); + + *name = res; + /* handle error conditions - and still allow a partial path to + * be returned */ + if (IS_ERR(res)) { + error = PTR_ERR(res); + *name = buf; + } else if (d_unhashed(path->dentry) && !path->dentry->d_inode) { + /* On some filesystems, newly allocated dentries appear + * to the security_path hooks as a deleted + * dentry except without an inode allocated. + * + * Remove the appended deleted text and return as a + * string for normal mediation. The (deleted) string + * is guarenteed to be added in this case, so just + * strip it. + */ + buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */ + } else if (d_unhashed(path->dentry) && (buf + buflen) - res > 11 && + strcmp(buf + buflen - 11, " (deleted)") == 0) { + /* For now allow mediation of deleted paths */ + buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */ + } else if (!IS_ROOT(path->dentry) && d_unhashed(path->dentry)) { + error = -ENOENT; +#if 0 + } else if (tmp.dentry != ns_root.dentry && tmp.mnt != ns_root.mnt) { + /* disconnected path don return pathname starting with '/' */ + error = -ESTALE; + if (*res == '/') + *name = res + 1; +#endif + } + + spin_unlock(&dcache_lock); + path_put(&root); + path_put(&ns_root); + + return error; +} + +char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen) +{ + if (buflen < 1) + return NULL; + buffer += --buflen; + *buffer = '\0'; + + while (table) { + int namelen = strlen(table->procname); + + if (buflen < namelen + 1) + return NULL; + buflen -= namelen + 1; + buffer -= namelen; + memcpy(buffer, table->procname, namelen); + *--buffer = '/'; + table = table->parent; + } + if (buflen < 4) + return NULL; + buffer -= 4; + memcpy(buffer, "/sys", 4); + + return buffer; +} diff -urN kernel.org/security/apparmor/policy.c kernel/security/apparmor/policy.c --- kernel.org/security/apparmor/policy.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/policy.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,729 @@ +/* + * AppArmor security module + * + * This file contains AppArmor policy manipulation functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * + * AppArmor policy is based around profiles, which contain the rules a + * task is confined by. Every task in the sytem has a profile attached + * to it determined either by matching "unconfined" tasks against the + * visible set of profiles or by following a profiles attachment rules. + * + * Each profile exists in an AppArmor profile namespace which is a + * container of related profiles. Each namespace contains a special + * "unconfined" profile, which doesn't efforce any confinement on + * a task beyond DAC. + * + * Namespace and profile names can be written together in either + * of two syntaxes. + * :namespace:profile - used by kernel interfaces for easy detection + * namespace://profile - used by policy + * + * Profile names name not start with : or @ and may not contain \0 + * a // in a profile name indicates a compound name with the name before + * the // being the parent profile and the name after the child + * + * Reserved profile names + * unconfined - special automatically generated unconfined profile + * inherit - special name to indicate profile inheritance + * null-XXXX-YYYY - special automically generated learning profiles + * + * Namespace names may not start with / or @ and may not contain \0 or // + * it is recommend that they do not contain any '/' characters + * Reserved namespace namespace + * default - the default namespace setup by AppArmor + * user-XXXX - user defined profiles + */ + +#include +#include +#include + +#include "include/apparmor.h" +#include "include/capability.h" +#include "include/file.h" +#include "include/ipc.h" +#include "include/match.h" +#include "include/policy.h" +#include "include/resource.h" +#include "include/sid.h" + +/* list of profile namespaces and lock */ +LIST_HEAD(ns_list); +DEFINE_RWLOCK(ns_list_lock); + +struct aa_namespace *default_namespace; + +const char *profile_mode_names[] = { + "enforce", + "complain", + "kill", +}; + +#define AA_SYS_SID 0 +#define AA_USR_SID 1 + + +static int common_init(struct aa_policy_common *common, const char *name) +{ + common->name = kstrdup(name, GFP_KERNEL); + if (!common->name) + return 0; + INIT_LIST_HEAD(&common->list); + INIT_LIST_HEAD(&common->profiles); + kref_init(&common->count); + rwlock_init(&common->lock); + + return 1; +} + +static void common_free(struct aa_policy_common *common) +{ + /* still contains profiles -- invalid */ + if (!list_empty(&common->profiles)) { + AA_ERROR("%s: internal error, " + "policy '%s' still contains profiles\n", + __func__, common->name); + BUG(); + } + if (!list_empty(&common->list)) { + AA_ERROR("%s: internal error, policy '%s' still on list\n", + __func__, common->name); + BUG(); + } + + kfree(common->name); +} + +static struct aa_policy_common *__common_find(struct list_head *head, + const char *name) + +{ + struct aa_policy_common *common; + + list_for_each_entry(common, head, list) { + if (!strcmp(common->name, name)) + return common; + } + return NULL; +} + +static struct aa_policy_common *__common_find_strn(struct list_head *head, + const char *str, int len) +{ + struct aa_policy_common *common; + + list_for_each_entry(common, head, list) { + if (aa_strneq(common->name, str, len)) + return common; + } + + return NULL; +} + +/* + * Routines for AppArmor namespaces + */ + +int alloc_default_namespace(void) +{ + struct aa_namespace *ns; + ns = alloc_aa_namespace("default"); + if (!ns) + return -ENOMEM; + + default_namespace = aa_get_namespace(ns); + write_lock(&ns_list_lock); + list_add(&ns->base.list, &ns_list); + write_unlock(&ns_list_lock); + + return 0; +} + +void free_default_namespace(void) +{ + write_lock(&ns_list_lock); + list_del_init(&default_namespace->base.list); + aa_put_namespace(default_namespace); + write_unlock(&ns_list_lock); + aa_put_namespace(default_namespace); + default_namespace = NULL; +} + +/** + * alloc_aa_namespace - allocate, initialize and return a new namespace + * @name: a preallocated name + * Returns NULL on failure. + */ +struct aa_namespace *alloc_aa_namespace(const char *name) +{ + struct aa_namespace *ns; + + ns = kzalloc(sizeof(*ns), GFP_KERNEL); + AA_DEBUG("%s(%p)\n", __func__, ns); + if (!ns) + return NULL; + + if (!common_init(&ns->base, name)) + goto fail_ns; + + /* null profile is not added to the profile list */ + ns->unconfined = alloc_aa_profile("unconfined"); + if (!ns->unconfined) + goto fail_unconfined; + + ns->unconfined->sid = aa_alloc_sid(AA_ALLOC_SYS_SID); + ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR | + PFLAG_IMMUTABLE; + ns->unconfined->ns = aa_get_namespace(ns); + + return ns; + +fail_unconfined: + if (ns->base.name) + kfree(ns->base.name); +fail_ns: + kfree(ns); + return NULL; +} + +/** + * 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) +{ + free_aa_namespace(container_of(kref, struct aa_namespace, base.count)); +} + +/** + * 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, + */ +void free_aa_namespace(struct aa_namespace *ns) +{ + if (!ns) + return; + + common_free(&ns->base); + + if (ns->unconfined && ns->unconfined->ns == ns) + ns->unconfined->ns = NULL; + + aa_put_profile(ns->unconfined); + memset(ns, 0, sizeof(*ns)); + kfree(ns); +} + +struct aa_namespace *__aa_find_namespace(struct list_head *head, + const char *name) + +{ + return (struct aa_namespace *) __common_find(head, name); +} + +/** + * aa_find_namespace - look up a profile namespace on the namespace list + * @name: name of namespace to find + * + * Returns a pointer to the namespace on the list, or NULL if no namespace + * called @name exists. + */ +struct aa_namespace *aa_find_namespace(const char *name) +{ + struct aa_namespace *ns = NULL; + + read_lock(&ns_list_lock); + ns = aa_get_namespace(__aa_find_namespace(&ns_list, name)); + read_unlock(&ns_list_lock); + + return ns; +} + +static struct aa_namespace *__aa_find_namespace_by_strn(struct list_head *head, + const char *name, + int len) +{ + return (struct aa_namespace *) __common_find_strn(head, name, len); +} + +struct aa_namespace *aa_find_namespace_by_strn(const char *name, int len) +{ + struct aa_namespace *ns = NULL; + + read_lock(&ns_list_lock); + ns = aa_get_namespace(__aa_find_namespace_by_strn(&ns_list, name, len)); + read_unlock(&ns_list_lock); + + return ns; +} + +/** + * aa_prepare_namespace - find an existing or create a new namespace of @name + * @name: the namespace to find or add + */ +struct aa_namespace *aa_prepare_namespace(const char *name) +{ + struct aa_namespace *ns; + + write_lock(&ns_list_lock); + if (name) + ns = aa_get_namespace(__aa_find_namespace(&ns_list, name)); + else + ns = aa_get_namespace(default_namespace); + if (!ns) { + struct aa_namespace *new_ns; + write_unlock(&ns_list_lock); + new_ns = alloc_aa_namespace(name); + if (!new_ns) + return NULL; + write_lock(&ns_list_lock); + ns = __aa_find_namespace(&ns_list, name); + if (!ns) { + list_add(&new_ns->base.list, &ns_list); + ns = new_ns; + } else { + /* raced so free the new one */ + free_aa_namespace(new_ns); + aa_get_namespace(ns); + } + } + write_unlock(&ns_list_lock); + + return ns; +} + +/* + * requires profile->ns set first, takes profiles refcount + * TODO: add accounting + */ +void __aa_add_profile(struct aa_policy_common *common, + struct aa_profile *profile) +{ + list_add(&profile->base.list, &common->profiles); + if (!(profile->flags & PFLAG_NO_LIST_REF)) + aa_get_profile(profile); +} + +void __aa_remove_profile(struct aa_profile *profile, + struct aa_profile *replacement) +{ + if (replacement) + profile->replacedby = aa_get_profile(replacement); + else + profile->replacedby = ERR_PTR(-EINVAL); + list_del_init(&profile->base.list); + if (!(profile->flags & PFLAG_NO_LIST_REF)) + aa_put_profile(profile); +} + +/* TODO: add accounting */ +void __aa_replace_profile(struct aa_profile *profile, + struct aa_profile *replacement) +{ + if (replacement) { + struct aa_policy_common *common; + + if (profile->parent) + common = &profile->parent->base; + else + common = &profile->ns->base; + + __aa_remove_profile(profile, replacement); + __aa_add_profile(common, replacement); + } else + __aa_remove_profile(profile, NULL); +} + +/** + * __aa_profile_list_release - remove all profiles on the list and put refs + * @head: list of profiles + */ +void __aa_profile_list_release(struct list_head *head) +{ + struct aa_profile *profile, *tmp; + list_for_each_entry_safe(profile, tmp, head, base.list) { + __aa_profile_list_release(&profile->base.profiles); + __aa_remove_profile(profile, NULL); + } +} + +void __aa_remove_namespace(struct aa_namespace *ns) +{ + struct aa_profile *unconfined = ns->unconfined; + list_del_init(&ns->base.list); + + /* + * break the ns, unconfined profile cyclic reference and forward + * all new unconfined profiles requests to the default namespace + */ + ns->unconfined = aa_get_profile(default_namespace->unconfined); + __aa_profile_list_release(&ns->base.profiles); + aa_put_profile(unconfined); + aa_put_namespace(ns); +} + +/** + * aa_remove_namespace = Remove namespace from the list + * @ns: namespace to remove + */ +void aa_remove_namespace(struct aa_namespace *ns) +{ + write_lock(&ns_list_lock); + write_lock(&ns->base.lock); + __aa_remove_namespace(ns); + write_unlock(&ns->base.lock); + write_unlock(&ns_list_lock); +} + +/** + * aa_profilelist_release - remove all namespaces and all associated profiles + */ +void aa_profile_ns_list_release(void) +{ + struct aa_namespace *ns, *tmp; + + /* Remove and release all the profiles on namespace profile lists. */ + write_lock(&ns_list_lock); + list_for_each_entry_safe(ns, tmp, &ns_list, base.list) { + write_lock(&ns->base.lock); + __aa_remove_namespace(ns); + write_unlock(&ns->base.lock); + } + write_unlock(&ns_list_lock); +} + +/** + * alloc_aa_profile - allocate, initialize and return a new profile + * @fqname: name of the profile + * + * Returns NULL on failure. + */ +struct aa_profile *alloc_aa_profile(const char *fqname) +{ + struct aa_profile *profile; + + profile = kzalloc(sizeof(*profile), GFP_KERNEL); + if (!profile) + return NULL; + + if (!common_init(&profile->base, fqname)) { + kfree(profile); + return NULL; + } + + profile->fqname = profile->base.name; + profile->base.name = (char *) fqname_subname((const char *) profile->fqname); + return profile; +} + +/** + * aa_new_null_profile - create a new null-X learning profile + * @parent: profile that caused this profile to be created + * @hat: true if the null- learning profile is a hat + * + * Create a null- complain mode profile used in learning mode. The name of + * the profile is unique and follows the format of parent//null-sid. + * + * null profiles are added to the profile list but the list does not + * hold a count on them so that they are automatically released when + * not in use. + */ +struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat) +{ + struct aa_profile *profile = NULL; + char *name; + u32 sid = aa_alloc_sid(AA_ALLOC_SYS_SID); + + name = kmalloc(strlen(parent->fqname) + 2 + 7 + 8, GFP_KERNEL); + if (!name) + goto fail; + sprintf(name, "%s//null-%x", parent->fqname, sid); + + profile = alloc_aa_profile(name); + kfree(name); + if (!profile) + goto fail; + + profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID); + profile->mode = APPARMOR_COMPLAIN; + profile->flags = PFLAG_NULL | PFLAG_NO_LIST_REF; + if (hat) + profile->flags |= PFLAG_HAT; + + profile->parent = aa_get_profile(parent); + profile->ns = aa_get_namespace(parent->ns); + + write_lock(&profile->ns->base.lock); + __aa_add_profile(&parent->base, profile); + write_unlock(&profile->ns->base.lock); + + return profile; + +fail: + aa_free_sid(sid); + return NULL; +} + +/** + * free_aa_profile_kref - free aa_profile by kref (called by aa_put_profile) + * @kr: kref callback for freeing of a profile + */ +void free_aa_profile_kref(struct kref *kref) +{ + struct aa_profile *p = container_of(kref, struct aa_profile, + base.count); + + free_aa_profile(p); +} + +/** + * free_aa_profile - free a profile + * @profile: the profile to free + * + * Free a profile, its hats and null_profile. All references to the profile, + * its hats and null_profile must have been put. + * + * If the profile was referenced from a task context, free_aa_profile() will + * be called from an rcu callback routine, so we must not sleep here. + */ +void free_aa_profile(struct aa_profile *profile) +{ + AA_DEBUG("%s(%p)\n", __func__, profile); + + if (!profile) + return; + + /* + * profile can still be on the list if the list doesn't hold a + * reference. There is no race as NULL profiles can't be attached + */ + if (!list_empty(&profile->base.list)) { + if ((profile->flags & PFLAG_NULL) && profile->ns) { + write_lock(&profile->ns->base.lock); + list_del_init(&profile->base.list); + write_unlock(&profile->ns->base.lock); + } else { + AA_ERROR("%s: internal error, " + "profile '%s' still on ns list\n", + __func__, profile->base.name); + BUG(); + } + } + + /* profile->name is a substring of fqname */ + profile->base.name = NULL; + common_free(&profile->base); + + BUG_ON(!list_empty(&profile->base.profiles)); + + kfree(profile->fqname); + + aa_put_namespace(profile->ns); + aa_put_profile(profile->parent); + + aa_free_file_rules(&profile->file); + aa_free_cap_rules(&profile->caps); + aa_free_net_rules(&profile->net); + aa_free_rlimit_rules(&profile->rlimits); + + aa_free_sid(profile->sid); + aa_match_free(profile->xmatch); + + if (profile->replacedby && !PTR_ERR(profile->replacedby)) + aa_put_profile(profile->replacedby); + + memset(profile, 0, sizeof(profile)); + kfree(profile); +} + + +/* TODO: profile count accounting - setup in remove */ + + +struct aa_profile *__aa_find_profile(struct list_head *head, const char *name) +{ + return (struct aa_profile *) __common_find(head, name); +} + +struct aa_profile *__aa_find_profile_by_strn(struct list_head *head, + const char *name, int len) +{ + return (struct aa_profile *) __common_find_strn(head, name, len); +} + + +/** + * aa_find_child - find a profile by @name in @parent + * @parent: profile to search + * @name: profile name to search for + * + * Returns a ref counted profile or NULL if not found + */ +struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) +{ + struct aa_profile *profile; + + read_lock(&parent->ns->base.lock); + profile = aa_get_profile(__aa_find_profile(&parent->base.profiles, + name)); + read_unlock(&parent->ns->base.lock); + + return profile; +} + + +struct aa_policy_common *__aa_find_parent_by_fqname(struct aa_namespace *ns, + const char *fqname) +{ + struct aa_policy_common *common; + struct aa_profile *profile = NULL; + char *split; + + common = &ns->base; + + + for (split = strstr(fqname, "//"); split; ) { + profile = __aa_find_profile_by_strn(&common->profiles, fqname, + split - fqname); + if (!profile) + return NULL; + common = &profile->base; + fqname = split + 2; + split = strstr(fqname, "//"); + } + if (!profile) + return &ns->base; + return &profile->base; +} + +struct aa_profile *__aa_find_profile_by_fqname(struct aa_namespace *ns, + const char *fqname) +{ + struct aa_policy_common *common; + struct aa_profile *profile = NULL; + char *split; + + common = &ns->base; + for (split = strstr(fqname, "//"); split; ) { + profile = __aa_find_profile_by_strn(&common->profiles, fqname, + split - fqname); + if (!profile) + return NULL; + + common = &profile->base; + fqname = split + 2; + split = strstr(fqname, "//"); + } + + profile = __aa_find_profile(&common->profiles, fqname); + + return profile; +} + +/** + * aa_find_profile_by_name - find a profile by its full or partial name + * @ns: the namespace to start from + * @fqname: name to do lookup on. Does not contain namespace prefix + */ +struct aa_profile *aa_find_profile_by_fqname(struct aa_namespace *ns, + const char *fqname) +{ + struct aa_profile *profile; + + read_lock(&ns->base.lock); + profile = aa_get_profile(__aa_find_profile_by_fqname(ns, fqname)); + read_unlock(&ns->base.lock); + return profile; +} + + +/* __aa_attach_match_ - find an attachment match + * @name - to match against + * @head - profile list to walk + * + * Do a linear search on the profiles in the list. There is a matching + * preference where an exact match is prefered over a name which uses + * expressions to match, and matching expressions with the greatest + * xmatch_len are prefered. + */ +static struct aa_profile *__aa_attach_match(const char *name, + struct list_head *head) +{ + int len = 0; + struct aa_profile *profile, *candidate = NULL; + + list_for_each_entry(profile, head, base.list) { + if (profile->flags & PFLAG_NULL) + continue; + if (profile->xmatch && profile->xmatch_len > len) { + unsigned int state = aa_dfa_match(profile->xmatch, + DFA_START, name); + u16 perm = dfa_user_allow(profile->xmatch, state); + /* any accepting state means a valid match. */ + if (perm & MAY_EXEC) { + candidate = profile; + len = profile->xmatch_len; + } + } else if (!strcmp(profile->base.name, name)) + /* exact non-re match, no more searching required */ + return profile; + } + + return candidate; +} + +/** + * aa_sys_find_attach - do attachment search for sys unconfined processes + * @base: the base to search + * name: the executable name to match against + */ +struct aa_profile *aa_sys_find_attach(struct aa_policy_common *base, + const char *name) +{ + struct aa_profile *profile; + + read_lock(&base->lock); + profile = aa_get_profile(__aa_attach_match(name, &base->profiles)); + read_unlock(&base->lock); + + return profile; +} + +/** + * aa_profile_newest - find the newest version of @profile + * @profile: the profile to check for newer versions of + * + * Find the newest version of @profile, if @profile is the newest version + * return @profile. If @profile has been removed return NULL. + * + * NOTE: the profile returned is not refcounted, The refcount on @profile + * must be held until the caller decides what to do with the returned newest + * version. + */ +struct aa_profile *aa_profile_newest(struct aa_profile *profile) +{ + if (unlikely(profile && profile->replacedby)) { + for (;profile->replacedby; profile = profile->replacedby) { + if (IS_ERR(profile->replacedby)) { + /* profile has been removed */ + profile = NULL; + break; + } + } + } + + return profile; +} + diff -urN kernel.org/security/apparmor/policy_interface.c kernel/security/apparmor/policy_interface.c --- kernel.org/security/apparmor/policy_interface.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/policy_interface.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,852 @@ +/* + * AppArmor security module + * + * This file contains AppArmor functions for unpacking policy loaded from + * userspace. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * AppArmor uses a serialized binary format for loading policy. + * The policy format is documented in Documentation/??? + * All policy is validated all before it is used. + */ + +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/match.h" +#include "include/policy.h" +#include "include/policy_interface.h" +#include "include/sid.h" + +/* FIXME: convert profiles to internal hieracy, accounting + * FIXME: have replacement routines set replaced_by profile instead of error + * FIXME: name mapping to hierarchy + */ + +/* + * The AppArmor interface treats data as a type byte followed by the + * actual data. The interface has the notion of a a named entry + * which has a name (AA_NAME typecode followed by name string) followed by + * the entries typecode and data. Named types allow for optional + * elements and extensions to be added and tested for without breaking + * backwards compatability. + */ + +enum aa_code { + AA_U8, + AA_U16, + AA_U32, + AA_U64, + AA_NAME, /* same as string except it is items name */ + AA_STRING, + AA_BLOB, + AA_STRUCT, + AA_STRUCTEND, + AA_LIST, + AA_LISTEND, + AA_ARRAY, + AA_ARRAYEND, +}; + +/* + * aa_ext is the read of the buffer containing the serialized profile. The + * data is copied into a kernel buffer in apparmorfs and then handed off to + * the unpack routines. + */ +struct aa_ext { + void *start; + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; + char *ns_name; +}; + + +struct aa_audit_iface { + struct aa_audit base; + + const char *name; + const char *name2; + const struct aa_ext *e; +}; + +static void aa_audit_init(struct aa_audit_iface *sa, const char *operation, + struct aa_ext *e) +{ + memset(sa, 0, sizeof(*sa)); + sa->base.operation = operation; + sa->base.gfp_mask = GFP_KERNEL; + sa->e = e; +} + +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_iface *sa = va; + + if (sa->name) + audit_log_format(ab, " name=%s", sa->name); + if (sa->name2) + audit_log_format(ab, " namespace=%s", sa->name2); + if (sa->base.error && sa->e) { + long len = sa->e->pos - sa->e->start; + audit_log_format(ab, " offset=%ld", len); + } +} + +static int aa_audit_iface(struct aa_audit_iface *sa) +{ + struct aa_profile *profile; + struct cred *cred = aa_get_task_policy(current, &profile); + int error = aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa->base, + audit_cb); + put_cred(cred); + return error; +} + +static int aa_inbounds(struct aa_ext *e, size_t size) +{ + return (size <= e->end - e->pos); +} + +/** + * aa_u16_chunck - test and do bounds checking for a u16 size based chunk + * @e: serialized data read head + * @chunk: start address for chunk of data + * + * return the size of chunk found with the read head at the end of + * the chunk. + */ +static size_t aa_is_u16_chunk(struct aa_ext *e, char **chunk) +{ + void *pos = e->pos; + size_t size = 0; + + if (!aa_inbounds(e, sizeof(u16))) + goto fail; + size = le16_to_cpu(get_unaligned((u16 *)e->pos)); + e->pos += sizeof(u16); + if (!aa_inbounds(e, size)) + goto fail; + *chunk = e->pos; + e->pos += size; + return size; + +fail: + e->pos = pos; + return 0; +} + +static int aa_is_X(struct aa_ext *e, enum aa_code code) +{ + if (!aa_inbounds(e, 1)) + return 0; + if (*(u8 *) e->pos != code) + return 0; + e->pos++; + return 1; +} + +/** + * aa_is_nameX - check is the next element is of type X with a name of @name + * @e: serialized data extent information + * @code: type code + * @name: name to match to the serialized element. + * + * check that the next serialized data element is of type X and has a tag + * name @name. If @name is specified then there must be a matching + * name element in the stream. If @name is NULL any name element will be + * skipped and only the typecode will be tested. + * returns 1 on success (both type code and name tests match) and the read + * head is advanced past the headers + * returns %0 if either match failes, the read head does not move + */ +static int aa_is_nameX(struct aa_ext *e, enum aa_code code, const char *name) +{ + void *pos = e->pos; + /* + * Check for presence of a tagname, and if present name size + * AA_NAME tag value is a u16. + */ + if (aa_is_X(e, AA_NAME)) { + char *tag = NULL; + size_t size = aa_is_u16_chunk(e, &tag); + /* if a name is specified it must match. otherwise skip tag */ + if (name && (!size || strcmp(name, tag))) + goto fail; + } else if (name) { + /* if a name is specified and there is no name tag fail */ + goto fail; + } + + /* now check if type code matches */ + if (aa_is_X(e, code)) + return 1; + +fail: + e->pos = pos; + return 0; +} + +static int aa_is_u16(struct aa_ext *e, u16 *data, const char *name) +{ + void *pos = e->pos; + if (aa_is_nameX(e, AA_U16, name)) { + if (!aa_inbounds(e, sizeof(u16))) + goto fail; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *)e->pos)); + e->pos += sizeof(u16); + return 1; + } +fail: + e->pos = pos; + return 0; +} + +static int aa_is_u32(struct aa_ext *e, u32 *data, const char *name) +{ + void *pos = e->pos; + if (aa_is_nameX(e, AA_U32, name)) { + if (!aa_inbounds(e, sizeof(u32))) + goto fail; + if (data) + *data = le32_to_cpu(get_unaligned((u32 *)e->pos)); + e->pos += sizeof(u32); + return 1; + } +fail: + e->pos = pos; + return 0; +} + +static int aa_is_u64(struct aa_ext *e, u64 *data, const char *name) +{ + void *pos = e->pos; + if (aa_is_nameX(e, AA_U64, name)) { + if (!aa_inbounds(e, sizeof(u64))) + goto fail; + if (data) + *data = le64_to_cpu(get_unaligned((u64 *)e->pos)); + e->pos += sizeof(u64); + return 1; + } +fail: + e->pos = pos; + return 0; +} + +static size_t aa_is_array(struct aa_ext *e, const char *name) +{ + void *pos = e->pos; + if (aa_is_nameX(e, AA_ARRAY, name)) { + int size; + if (!aa_inbounds(e, sizeof(u16))) + goto fail; + size = (int) le16_to_cpu(get_unaligned((u16 *)e->pos)); + e->pos += sizeof(u16); + return size; + } +fail: + e->pos = pos; + return 0; +} + +static size_t aa_is_blob(struct aa_ext *e, char **blob, const char *name) +{ + void *pos = e->pos; + if (aa_is_nameX(e, AA_BLOB, name)) { + u32 size; + if (!aa_inbounds(e, sizeof(u32))) + goto fail; + size = le32_to_cpu(get_unaligned((u32 *)e->pos)); + e->pos += sizeof(u32); + if (aa_inbounds(e, (size_t) size)) { + *blob = e->pos; + e->pos += size; + return size; + } + } +fail: + e->pos = pos; + return 0; +} + +static int aa_is_string(struct aa_ext *e, char **string, const char *name) +{ + char *src_str; + size_t size = 0; + void *pos = e->pos; + *string = NULL; + if (aa_is_nameX(e, AA_STRING, name) && + (size = aa_is_u16_chunk(e, &src_str))) { + /* strings are null terminated, length is size - 1 */ + if (src_str[size - 1] != 0) + goto fail; + *string = src_str; + } + + return size; + +fail: + e->pos = pos; + return 0; +} + +static int aa_is_dynstring(struct aa_ext *e, char **string, const char *name) +{ + char *tmp; + void *pos = e->pos; + int res = aa_is_string(e, &tmp, name); + *string = NULL; + + if (!res) + return res; + + *string = kstrdup(tmp, GFP_KERNEL); + if (!*string) { + e->pos = pos; + return 0; + } + + return res; +} + +/** + * aa_unpack_dfa - unpack a file rule dfa + * @e: serialized data extent information + * + * returns dfa or ERR_PTR + */ +static struct aa_dfa *aa_unpack_dfa(struct aa_ext *e) +{ + char *blob = NULL; + size_t size, error = 0; + struct aa_dfa *dfa = NULL; + + size = aa_is_blob(e, &blob, "aadfa"); + if (size) { + dfa = aa_match_alloc(); + if (dfa) { + /* + * The dfa is aligned with in the blob to 8 bytes + * from the beginning of the stream. + */ + size_t sz = blob - (char *) e->start; + size_t pad = ALIGN(sz, 8) - sz; + error = unpack_dfa(dfa, blob + pad, size - pad); + if (!error) + error = verify_dfa(dfa); + } else { + error = -ENOMEM; + } + + if (error) { + aa_match_free(dfa); + dfa = ERR_PTR(error); + } + } + + return dfa; +} + +static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + /* exec table is optional */ + if (aa_is_nameX(e, AA_STRUCT, "xtable")) { + int i, size; + + size = aa_is_array(e, NULL); + /* currently 4 exec bits and entries 0-3 are reserved iupcx */ + if (size > 16 - 4) + goto fail; + profile->file.trans.table = kzalloc(sizeof(char *) * size, + GFP_KERNEL); + if (!profile->file.trans.table) + goto fail; + + for (i = 0; i < size; i++) { + char *tmp; + if (!aa_is_dynstring(e, &tmp, NULL)) + goto fail; + /* note: strings beginning with a : have an embedded + \0 seperating the profile ns name from the profile + name */ + profile->file.trans.table[i] = tmp; + } + if (!aa_is_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + profile->file.trans.size = size; + } + return 1; + +fail: + e->pos = pos; + return 0; +} + +int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + /* rlimits are optional */ + if (aa_is_nameX(e, AA_STRUCT, "rlimits")) { + int i, size; + u32 tmp = 0; + if (!aa_is_u32(e, &tmp, NULL)) + goto fail; + profile->rlimits.mask = tmp; + + size = aa_is_array(e, NULL); + if (size > RLIM_NLIMITS) + goto fail; + for (i = 0; i < size; i++) { + u64 tmp = 0; + if (!aa_is_u64(e, &tmp, NULL)) + goto fail; + profile->rlimits.limits[i].rlim_max = tmp; + } + if (!aa_is_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + return 1; + +fail: + e->pos = pos; + return 0; +} + +/** + * aa_unpack_profile - unpack a serialized profile + * @e: serialized data extent information + * @sa: audit struct for the operation + */ +static struct aa_profile *aa_unpack_profile(struct aa_ext *e, + struct aa_audit_iface *sa) +{ + struct aa_profile *profile = NULL; + char *name; + size_t size = 0; + int i, error = -EPROTO; + u32 tmp; + + /* check that we have the right struct being passed */ + if (!aa_is_nameX(e, AA_STRUCT, "profile")) + goto fail; + if (!aa_is_string(e, &name, NULL)) + goto fail; + + profile = alloc_aa_profile(name); + if (!profile) + return ERR_PTR(-ENOMEM); + + /* xmatch is optional and may be NULL */ + profile->xmatch = aa_unpack_dfa(e); + if (IS_ERR(profile->xmatch)) { + error = PTR_ERR(profile->xmatch); + profile->xmatch = NULL; + goto fail; + } + /* xmatch_len is not optional is xmatch is set */ + if (profile->xmatch && !aa_is_u32(e, &tmp, NULL)) + goto fail; + profile->xmatch_len = tmp; + + /* per profile debug flags (complain, audit) */ + if (!aa_is_nameX(e, AA_STRUCT, "flags")) + goto fail; + if (!aa_is_u32(e, &tmp, NULL)) + goto fail; + if (tmp) + profile->flags |= PFLAG_HAT; + if (!aa_is_u32(e, &tmp, NULL)) + goto fail; + if (tmp) + profile->mode = APPARMOR_COMPLAIN; + if (!aa_is_u32(e, &tmp, NULL)) + goto fail; + if (tmp) + profile->audit = AUDIT_ALL; + + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + if (!aa_is_u32(e, &(profile->caps.allowed.cap[0]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.audit.cap[0]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.quiet.cap[0]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.set.cap[0]), NULL)) + goto fail; + + if (aa_is_nameX(e, AA_STRUCT, "caps64")) { + /* optional upper half of 64 bit caps */ + if (!aa_is_u32(e, &(profile->caps.allowed.cap[1]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.audit.cap[1]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.quiet.cap[1]), NULL)) + goto fail; + if (!aa_is_u32(e, &(profile->caps.set.cap[1]), NULL)) + goto fail; + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + if (!aa_unpack_rlimits(e, profile)) + goto fail; + + size = aa_is_array(e, "net_allowed_af"); + if (size) { + if (size > AF_MAX) + goto fail; + + for (i = 0; i < size; i++) { + if (!aa_is_u16(e, &profile->net.allowed[i], NULL)) + goto fail; + if (!aa_is_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!aa_is_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!aa_is_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + /* allow unix domain and netlink sockets they are handled + * by IPC + */ + } + profile->net.allowed[AF_UNIX] = 0xffff; + profile->net.allowed[AF_NETLINK] = 0xffff; + + /* get file rules */ + profile->file.dfa = aa_unpack_dfa(e); + if (IS_ERR(profile->file.dfa)) { + error = PTR_ERR(profile->file.dfa); + profile->file.dfa = NULL; + goto fail; + } + + if (!aa_unpack_trans_table(e, profile)) + goto fail; + + if (!aa_is_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + return profile; + +fail: + sa->name = profile && profile->base.name ? profile->base.name : + "unknown"; + if (!sa->base.info) + sa->base.info = "failed to unpack profile"; + aa_audit_iface(sa); + + free_aa_profile(profile); + + return ERR_PTR(error); +} + +/** + * aa_verify_head - unpack serialized stream header + * @e: serialized data read head + * @operation: operation header is being verified for + * + * returns error or 0 if header is good + */ +static int aa_verify_header(struct aa_ext *e, struct aa_audit_iface *sa) +{ + /* get the interface version */ + if (!aa_is_u32(e, &e->version, "version")) { + sa->base.info = "invalid profile format"; + aa_audit_iface(sa); + return -EPROTONOSUPPORT; + } + + /* check that the interface version is currently supported */ + if (e->version != 5) { + sa->base.info = "unsupported interface version"; + aa_audit_iface(sa); + return -EPROTONOSUPPORT; + } + + /* read the namespace if present */ + if (!aa_is_string(e, &e->ns_name, "namespace")) + e->ns_name = NULL; + + return 0; +} + + + +/** + * aa_interface_add_profiles - Unpack and add new profile(s) to the profile list + * @data: serialized data stream + * @size: size of the serialized data stream + */ +ssize_t aa_interface_add_profiles(void *data, size_t size) +{ + struct aa_profile *profile; + struct aa_namespace *ns = NULL; + struct aa_policy_common *common; + struct aa_ext e = { + .start = data, + .end = data + size, + .pos = data, + .ns_name = NULL + }; + ssize_t error; + struct aa_audit_iface sa; + aa_audit_init(&sa, "profile_load", &e); + + error = aa_verify_header(&e, &sa); + if (error) + return error; + + profile = aa_unpack_profile(&e, &sa); + if (IS_ERR(profile)) + return PTR_ERR(profile); + + sa.name2 = e.ns_name; + ns = aa_prepare_namespace(e.ns_name); + if (IS_ERR(ns)) { + sa.base.info = "failed to prepare namespace"; + sa.base.error = PTR_ERR(ns); + goto fail; + } + /* profiles are currently loaded flat with fqnames */ + sa.name = profile->fqname; + + write_lock(&ns->base.lock); + + common = __aa_find_parent_by_fqname(ns, sa.name); + if (!common) { + sa.base.info = "parent does not exist"; + sa.base.error = -ENOENT; + goto fail2; + } + + if (common != &ns->base) + profile->parent = aa_get_profile((struct aa_profile *) common); + + if (__aa_find_profile(&common->profiles, profile->base.name)) { + /* A profile with this name exists already. */ + sa.base.info = "profile already exists"; + sa.base.error = -EEXIST; + goto fail2; + } + profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID); + profile->ns = aa_get_namespace(ns); + + __aa_add_profile(common, profile); + write_unlock(&ns->base.lock); + + aa_audit_iface(&sa); + aa_put_namespace(ns); + kfree(e.ns_name); + return size; + +fail2: + write_unlock(&ns->base.lock); + +fail: + error = aa_audit_iface(&sa); + aa_put_namespace(ns); + aa_put_profile(profile); + kfree(e.ns_name); + return error; +} + +/** + * aa_interface_replace_profiles - replace profile(s) on the profile list + * @udata: serialized data stream + * @size: size of the serialized data stream + * + * unpack and replace a profile on the profile list and uses of that profile + * by any aa_task_context. If the profile does not exist on the profile list + * it is added. Return %0 or error. + */ +ssize_t aa_interface_replace_profiles(void *udata, size_t size) +{ + struct aa_policy_common *common; + struct aa_profile *old_profile = NULL, *new_profile; + struct aa_namespace *ns; + struct aa_ext e = { + .start = udata, + .end = udata + size, + .pos = udata, + .ns_name = NULL + }; + ssize_t error; + struct aa_audit_iface sa; + aa_audit_init(&sa, "profile_replace", &e); + + if (g_apparmor_lock_policy) + return -EACCES; + + error = aa_verify_header(&e, &sa); + if (error) + return error; + + new_profile = aa_unpack_profile(&e, &sa); + if (IS_ERR(new_profile)) + return PTR_ERR(new_profile); + + sa.name2 = e.ns_name; + ns = aa_prepare_namespace(e.ns_name); + if (!ns) { + sa.base.info = "failed to prepare namespace"; + sa.base.error = -ENOMEM; + goto fail; + } + + sa.name = new_profile->fqname; + + write_lock(&ns->base.lock); + common = __aa_find_parent_by_fqname(ns, sa.name); + + if (!common) { + sa.base.info = "parent does not exist"; + sa.base.error = -ENOENT; + goto fail2; + } + + if (common != &ns->base) + new_profile->parent = aa_get_profile((struct aa_profile *) + common); + + old_profile = __aa_find_profile(&common->profiles, + new_profile->base.name); + aa_get_profile(old_profile); + if (old_profile && old_profile->flags & PFLAG_IMMUTABLE) { + sa.base.info = "cannot replace immutible profile"; + sa.base.error = -EPERM; + goto fail2; + } else if (old_profile) { + // __aa_profile_list_release(&old_profile->base.profiles); + /* TODO: remove for new interface + * move children profiles over to the new profile so + * that replacement behaves correctly + */ + // list_replace_init(&old_profile->base.profiles, + // &new_profile->base.profiles); + struct aa_profile *profile, *tmp; + list_for_each_entry_safe(profile, tmp, &old_profile->base.profiles, + base.list) { + aa_put_profile(profile->parent); + list_del(&profile->base.list); + profile->parent = aa_get_profile(new_profile); + list_add(&profile->base.list, + &new_profile->base.profiles); + } + __aa_replace_profile(old_profile, new_profile); + new_profile->sid = old_profile->sid; + } else { + __aa_add_profile(common, new_profile); + new_profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID); + } + + new_profile->ns = aa_get_namespace(ns); + + write_unlock(&ns->base.lock); + + if (!old_profile) + sa.base.operation = "profile_load"; + + aa_audit_iface(&sa); + aa_put_namespace(ns); + aa_put_profile(old_profile); + kfree(e.ns_name); + return size; + +fail2: + write_unlock(&ns->base.lock); +fail: + error = aa_audit_iface(&sa); + aa_put_namespace(ns); + aa_put_profile(old_profile); + aa_put_profile(new_profile); + kfree(e.ns_name); + return error; +} + +/** + * aa_interface_remove_profiles - remove profile(s) from the system + * @name: name of the profile to remove + * @size: size of the name + * + * remove a profile from the profile list and all aa_task_context references + * to said profile. + * NOTE: removing confinement does not restore rlimits to preconfinemnet values + */ +ssize_t aa_interface_remove_profiles(char *name, size_t size) +{ + struct aa_namespace *ns; + struct aa_profile *profile; + struct aa_audit_iface sa; + aa_audit_init(&sa, "profile_remove", NULL); + + if (g_apparmor_lock_policy) + return -EACCES; + + write_lock(&ns_list_lock); + if (name[0] == ':') { + char *ns_name; + name = aa_split_name_from_ns(name, &ns_name); + ns = __aa_find_namespace(&ns_list, ns_name); + } else { + ns = aa_get_namespace(default_namespace); + } + + if (!ns) { + sa.base.info = "failed: namespace does not exist"; + goto fail_ns_list_lock; + } + + sa.name2 = ns->base.name; + write_lock(&ns->base.lock); + if (!name) { + /* remove namespace */ + // __aa_remove_namespace(ns); + } else { + /* remove profile */ + profile = __aa_find_profile_by_fqname(ns, name); + if (!profile) { + sa.name = name; + sa.base.info = "failed: profile does not exist"; + goto fail_ns_lock; + } + sa.name = profile->fqname; + __aa_profile_list_release(&profile->base.profiles); + __aa_remove_profile(profile, profile->ns->unconfined); + } + write_unlock(&ns->base.lock); + write_unlock(&ns_list_lock); + + aa_audit_iface(&sa); + aa_put_namespace(ns); + return size; + +fail_ns_lock: + write_unlock(&ns->base.lock); + +fail_ns_list_lock: + write_unlock(&ns_list_lock); + aa_audit_iface(&sa); + return -ENOENT; +} diff -urN kernel.org/security/apparmor/procattr.c kernel/security/apparmor/procattr.c --- kernel.org/security/apparmor/procattr.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/procattr.c 2009-11-03 20:34:45.000000000 +0100 @@ -0,0 +1,117 @@ +/* + * AppArmor security module + * + * This file contains AppArmor /proc//attr/ interface functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include "include/apparmor.h" +#include "include/policy.h" +#include "include/domain.h" + +/* FIXME show profile multiplexing */ +int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile, + char **string) +{ + char *str; + int len = 0; + + if (profile) { + int mode_len, name_len, ns_len = 0; + const char *mode_str = profile_mode_names[profile->mode]; + char *s; + + mode_len = strlen(mode_str) + 3; /* _(mode_str)\n */ + name_len = strlen(profile->fqname); + if (ns != default_namespace) + ns_len = strlen(ns->base.name) + 3; + len = mode_len + ns_len + name_len + 1; + s = str = kmalloc(len + 1, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + if (ns_len) { + sprintf(s, "%s://", ns->base.name); + s += ns_len; + } + memcpy(s, profile->fqname, name_len); + s += name_len; + sprintf(s, " (%s)\n", mode_str); + } else { + const char *unconfined_str = "unconfined\n"; + + len = strlen(unconfined_str); + if (ns != default_namespace) + len += strlen(ns->base.name) + 3; /* :// */ + + str = kmalloc(len + 1, GFP_ATOMIC); + if (!str) + return -ENOMEM; + + if (ns != default_namespace) + sprintf(str, "%s://%s", ns->base.name, unconfined_str); + else + memcpy(str, unconfined_str, len); + } + *string = str; + + return len; +} + +static char *split_token_from_name(const char *op, char *args, u64 *token) +{ + char *name; + + *token = simple_strtoull(args, &name, 16); + if ((name == args) || *name != '^') { + AA_ERROR("%s: Invalid input '%s'", op, args); + return ERR_PTR(-EINVAL); + } + + name++; /* skip ^ */ + if (!*name) + name = NULL; + return name; +} + +int aa_setprocattr_changehat(char *args, int test) +{ + char *hat; + u64 token; + + hat = split_token_from_name("change_hat", args, &token); + if (IS_ERR(hat)) + return PTR_ERR(hat); + + if (!hat && !token) { + AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic"); + return -EINVAL; + } + + AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", + __func__, token, hat ? hat : NULL); + + return aa_change_hat(hat, token, test); +} + +int aa_setprocattr_changeprofile(char *args, int onexec, int test) +{ + char *name, *ns_name; + + name = aa_split_name_from_ns(args, &ns_name); + return aa_change_profile(ns_name, name, onexec, test); +} + + +int aa_setprocattr_permipc(char *args) +{ + /* TODO: add ipc permission querying */ + return -ENOTSUPP; +} diff -urN kernel.org/security/apparmor/resource.c kernel/security/apparmor/resource.c --- kernel.org/security/apparmor/resource.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/resource.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,104 @@ +/* + * AppArmor security module + * + * This file contains AppArmor resource mediation and attachment + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include + +#include "include/audit.h" +#include "include/resource.h" +#include "include/policy.h" + +struct aa_audit_resource { + struct aa_audit base; + + int rlimit; +}; + +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct aa_audit_resource *sa = va; + + if (sa->rlimit) + audit_log_format(ab, " rlimit=%d", sa->rlimit - 1); +} + +static int aa_audit_resource(struct aa_profile *profile, + struct aa_audit_resource *sa) +{ + return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa, + audit_cb); +} + +/** + * aa_task_setrlimit - test permission to set an rlimit + * @profile - profile confining the task + * @resource - the resource being set + * @new_rlim - the new resource limit + * + * Control raising the processes hard limit. + */ +int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource, + struct rlimit *new_rlim) +{ + struct aa_audit_resource sa; + int error = 0; + + memset(&sa, 0, sizeof(sa)); + sa.base.operation = "setrlimit"; + sa.base.gfp_mask = GFP_KERNEL; + sa.rlimit = resource + 1; + + if (profile->rlimits.mask & (1 << resource) && + new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max) { + sa.base.error = -EACCES; + + error = aa_audit_resource(profile, &sa); + } + + return error; +} + +void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) +{ + unsigned int mask = 0; + struct rlimit *rlim, *initrlim; + int i; + + /* for any rlimits the profile controlled reset the soft limit + * to the less of the tasks hard limit and the init tasks soft limit + */ + if (old && old->rlimits.mask) { + for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<=1) { + if (old->rlimits.mask & mask) { + rlim = current->signal->rlim + i; + initrlim = init_task.signal->rlim + i; + rlim->rlim_cur = min(rlim->rlim_max, + initrlim->rlim_cur); + } + } + } + + /* set any new hard limits as dictated by the new profile */ + if (!(new && new->rlimits.mask)) + return; + for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<=1) { + if (!(new->rlimits.mask & mask)) + continue; + + rlim = current->signal->rlim + i; + rlim->rlim_max = min(rlim->rlim_max, + new->rlimits.limits[i].rlim_max); + /* soft limit should not exceed hard limit */ + rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + } +} diff -urN kernel.org/security/apparmor/sid.c kernel/security/apparmor/sid.c --- kernel.org/security/apparmor/sid.c 1970-01-01 01:00:00.000000000 +0100 +++ kernel/security/apparmor/sid.c 2009-09-10 22:18:06.000000000 +0200 @@ -0,0 +1,113 @@ +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (sid) manipulation fns + * + * Copyright 2009 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * + * AppArmor allocates a unique sid for every profile loaded. If a profile + * is replaced it receive the sid of the profile it is replacing. Each sid + * is a u32 with the lower u16 being sids of system profiles and the + * upper u16 being user profile sids. + * + * The sid value of 0 is invalid for system sids and is used to indicate + * unconfined for user sids. + * + * A compound sid is a pair of user and system sids that is used to identify + * both profiles confining a task. + * + * Both system and user sids are globally unique with all users pulling + * from the same sid pool. User sid allocation is limited by the + * user controls, that can limit how many profiles are loaded by a user. + */ + +#include +#include +#include + +#include "include/sid.h" + +/* global counter from which sids are allocated */ +static u16 global_sys_sid; +static u16 global_usr_sid; +static DEFINE_SPINLOCK(sid_lock); + + +/* TODO FIXME: add sid to profile mapping, and sid recycling */ + + +/** + * aa_alloc_sid - allocate a new sid for a profile + * @is_usr: true if the new sid is a user based sid + */ +u32 aa_alloc_sid(int is_usr) +{ + u32 sid; + + /* + * TODO FIXME: sid recycling - part of profile mapping table + */ + spin_lock(&sid_lock); + if (is_usr) { + sid = (++global_usr_sid) << 16; + + } else { + sid = ++global_sys_sid; + } + spin_unlock(&sid_lock); + return sid; +} + +/** + * aa_free_sid - free a sid + * @sid: sid to free + */ +void aa_free_sid(u32 sid) +{ + ; /* NOP ATM */ +} + +/** + * aa_add_sid_profile - associate a profile to a sid for sid -> profile lookup + * @sid: sid of te profile + * @profile: profile to associate + * + * return 0 or error + */ +int aa_add_sid_profile(u32 sid, struct aa_profile *profile) +{ + /* NOP ATM */ + return 0; +} + +/** + * aa_replace_sid_profile - replace the profile associated with a sid + * @sid: sid to associate a new profile with + * @profile: profile to associate with side + * + * return 0 or error + */ +int aa_replace_sid_profile(u32 sid, struct aa_profile *profile) +{ + /* NOP ATM */ + return 0; +} + +/** + * aa_get_sid_profile - get the profile associated with the sid + * @sid: sid to lookup + * + * returns - the profile, or NULL for unconfined user. + * - if there is an error -ENOENT, -EINVAL + */ +struct aa_profile *aa_get_sid_profile(u32 sid) +{ + return ERR_PTR(-EINVAL); +} +