From 05bf1eb7276886a3eda0588a8e012b558b693e96 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 Jul 2010 02:32:02 -0700 Subject: [PATCH] UBUNTU: SAUCE: AppArmor: Add profile introspection file to interface Add the dynamic profiles file to the interace, to allow load policy introspection. Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Kconfig | 9 ++ security/apparmor/apparmorfs.c | 231 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 0 deletions(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 9b9013b..51ebf96 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,12 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE boot. If you are unsure how to answer this question, answer 1. + +config SECURITY_APPARMOR_COMPAT_24 + bool "Enable AppArmor 2.4 compatability" + depends on SECURITY_APPARMOR + default y + help + This option enables compatability with AppArmor 2.4. It is + recommended if compatability with older versions of AppArmor + is desired. diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 16c15ec..42b7c9f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -182,6 +182,234 @@ const struct file_operations aa_fs_seq_file_ops = { .release = single_release, }; +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 +/** + * __next_namespace - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * NOTE: will not unlock root->lock + */ +static struct aa_namespace *__next_namespace(struct aa_namespace *root, + struct aa_namespace *ns) +{ + struct aa_namespace *parent; + + /* is next namespace a child */ + if (!list_empty(&ns->sub_ns)) { + struct aa_namespace *next; + next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list); + read_lock(&next->lock); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (parent) { + read_unlock(&ns->lock); + list_for_each_entry_continue(ns, &parent->sub_ns, base.list) { + read_lock(&ns->lock); + return ns; + } + if (parent == root) + return NULL; + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + */ +static struct aa_profile *__first_profile(struct aa_namespace *root, + struct aa_namespace *ns) +{ + for ( ; ns; ns = __next_namespace(root, ns)) { + if (!list_empty(&ns->base.profiles)) + return list_first_entry(&ns->base.profiles, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @profile: current profile in tree (NOT NULL) + * + * Perform a depth first taversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_namespace *ns = p->ns; + + /* is next profile a child */ + if (!list_empty(&p->base.profiles)) + return list_first_entry(&p->base.profiles, typeof(*p), + base.list); + + /* is next profile a sibling, parent sibling, gp, subling, .. */ + parent = p->parent; + while (parent) { + list_for_each_entry_continue(p, &parent->base.profiles, + base.list) + return p; + p = parent; + parent = parent->parent; + } + + /* is next another profile in the namespace */ + list_for_each_entry_continue(p, &ns->base.profiles, base.list) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_namespace *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_namespace(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) + __acquires(root->lock) +{ + struct aa_profile *profile = NULL; + struct aa_namespace *root = aa_current_profile()->ns; + loff_t l = *pos; + f->private = aa_get_namespace(root); + + + /* find the first profile */ + read_lock(&root->lock); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private; + (*pos)++; + + return next_profile(root, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile writen + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) + __releases(root->lock) +{ + struct aa_profile *profile = p; + struct aa_namespace *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + read_unlock(&ns->lock); + } + read_unlock(&root->lock); + aa_put_namespace(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_namespace *root = f->private; + + if (profile->ns != root) + seq_printf(f, ":%s://", aa_ns_name(root, profile->ns)); + seq_printf(f, "%s (%s)\n", profile->base.hname, + COMPLAIN_MODE(profile) ? "complain" : "enforce"); + + return 0; +} + +static const struct seq_operations aa_fs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &aa_fs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +const struct file_operations aa_fs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; +#endif /* CONFIG_SECURITY_APPARMOR_COMPAT_24 */ + /** Base file system setup **/ static struct aa_fs_entry aa_fs_entry_file[] = { @@ -210,6 +438,9 @@ static struct aa_fs_entry aa_fs_entry_apparmor[] = { AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), +#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24 + AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops), +#endif AA_FS_DIR("features", aa_fs_entry_features), { } }; -- 1.7.6.5 From 4facdf9db37c12ff655c91270d9030e2ed805ca2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 4 Oct 2010 15:03:36 -0700 Subject: [PATCH] UBUNTU: SAUCE: AppArmor: basic networking rules Base support for network mediation. Signed-off-by: John Johansen --- security/apparmor/.gitignore | 2 +- security/apparmor/Makefile | 42 +++++++++- security/apparmor/apparmorfs.c | 1 + security/apparmor/include/audit.h | 4 + security/apparmor/include/net.h | 44 ++++++++++ security/apparmor/include/policy.h | 3 + security/apparmor/lsm.c | 112 +++++++++++++++++++++++++ security/apparmor/net.c | 162 ++++++++++++++++++++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 46 ++++++++++ 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 806bd19..19daa85 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,9 +4,9 @@ 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_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o sid.o file.o net.o -clean-files := capability_names.h rlim_names.h +clean-files := capability_names.h rlim_names.h net_names.h # Build a lower case string table of capability names @@ -20,6 +20,38 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ echo "};" >> $@ +# Build a lower case string table of address family names +# Transform lines from +# define AF_LOCAL 1 /* POSIX name for AF_UNIX */ +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# [1] = "local", +# [2] = "inet", +# +# and build the securityfs entries for the mapping. +# Transforms lines from +# #define AF_INET 2 /* Internet IP Protocol */ +# to +# #define AA_FS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ + sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e \ + 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ ;\ + echo -n '\#define AA_FS_AF_MASK "' >> $@ ;\ + sed -r -n 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/\L\1/p'\ + $< | tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ + +# Build a lower case string table of sock type names +# Transform lines from +# SOCK_STREAM = 1, +# to +# [1] = "stream", +quiet_cmd_make-sock = GEN $@ +cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ + sed $^ >>$@ -r -n \ + -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ + echo "};" >> $@ # Build a lower case string table of rlimit names. # Transforms lines from @@ -56,6 +88,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(src)/Makefile @@ -63,3 +96,8 @@ $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) +$(obj)/net_names.h : $(srctree)/include/linux/socket.h \ + $(srctree)/include/linux/net.h \ + $(src)/Makefile + $(call cmd,make-af) + $(call cmd,make-sock) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 42b7c9f..114fb23 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -429,6 +429,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), + AA_FS_DIR("network", aa_fs_entry_network), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4b7e189..17734f9 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -127,6 +127,10 @@ struct apparmor_audit_data { u32 denied; uid_t ouid; } fs; + struct { + int type, protocol; + struct sock *sk; + } net; }; }; diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h new file mode 100644 index 0000000..cb8a121 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,44 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 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 + +#include "apparmorfs.h" + +/* 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 allow[AF_MAX]; + u16 audit[AF_MAX]; + u16 quiet[AF_MAX]; +}; + +extern struct aa_fs_entry aa_fs_entry_network[]; + +extern int aa_net_perm(int op, struct aa_profile *profile, u16 family, + int type, int protocol, struct sock *sk); +extern int aa_revalidate_sk(int op, struct sock *sk); + +static inline void aa_free_net_rules(struct aa_net *new) +{ + /* NOP */ +} + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bda4569..eb13a73 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -27,6 +27,7 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "net.h" #include "resource.h" extern const char *const profile_mode_names[]; @@ -157,6 +158,7 @@ struct aa_policydb { * @policy: general match rules governing policy * @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 @@ -194,6 +196,7 @@ struct aa_profile { struct aa_policydb policy; struct aa_file_rules file; struct aa_caps caps; + struct aa_net net; struct aa_rlimit rlimits; }; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8ea39aa..f628734 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -32,6 +32,7 @@ #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" @@ -614,6 +615,104 @@ static int apparmor_task_setrlimit(struct task_struct *task, return error; } +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 (!unconfined(profile)) + error = aa_net_perm(OP_CREATE, profile, family, type, protocol, + NULL); + return error; +} + +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_BIND, sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_CONNECT, sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_LISTEN, sk); +} + +static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_ACCEPT, sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SENDMSG, sk); +} + +static int apparmor_socket_recvmsg(struct socket *sock, + struct msghdr *msg, int size, int flags) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_RECVMSG, sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKNAME, sk); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETPEERNAME, sk); +} + +static int apparmor_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_GETSOCKOPT, sk); +} + +static int apparmor_socket_setsockopt(struct socket *sock, int level, + int optname) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SETSOCKOPT, sk); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + struct sock *sk = sock->sk; + + return aa_revalidate_sk(OP_SOCK_SHUTDOWN, sk); +} + static struct security_operations apparmor_ops = { .name = "apparmor", @@ -646,6 +745,19 @@ static struct security_operations apparmor_ops = { .getprocattr = apparmor_getprocattr, .setprocattr = apparmor_setprocattr, + .socket_create = apparmor_socket_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, + .cred_alloc_blank = apparmor_cred_alloc_blank, .cred_free = apparmor_cred_free, .cred_prepare = apparmor_cred_prepare, diff --git a/security/apparmor/net.c b/security/apparmor/net.c new file mode 100644 index 0000000..003dd18 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,162 @@ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 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 "net_names.h" + +struct aa_fs_entry aa_fs_entry_network[] = { + AA_FS_FILE_STRING("af_mask", AA_FS_AF_MASK), + { } +}; + +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + audit_log_format(ab, " family="); + if (address_family_names[sa->u.net->family]) { + audit_log_string(ab, address_family_names[sa->u.net->family]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->u.net->family); + } + audit_log_format(ab, " sock_type="); + if (sock_type_names[sa->aad->net.type]) { + audit_log_string(ab, sock_type_names[sa->aad->net.type]); + } else { + audit_log_format(ab, "\"unknown(%d)\"", sa->aad->net.type); + } + audit_log_format(ab, " protocol=%d", sa->aad->net.protocol); +} + +/** + * audit_net - audit network access + * @profile: profile being enforced (NOT NULL) + * @op: operation being checked + * @family: network family + * @type: network type + * @protocol: network protocol + * @sk: socket auditing is being applied to + * @error: error code for failure else 0 + * + * Returns: %0 or sa->error else other errorcode on failure + */ +static int audit_net(struct aa_profile *profile, int op, u16 family, int type, + int protocol, struct sock *sk, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa; + struct apparmor_audit_data aad = { }; + struct lsm_network_audit net = { }; + if (sk) { + sa.type = LSM_AUDIT_DATA_NET; + } else { + sa.type = LSM_AUDIT_DATA_NONE; + } + /* todo fill in socket addr info */ + sa.aad = &aad; + sa.u.net = &net; + sa.aad->op = op, + sa.u.net->family = family; + sa.u.net->sk = sk; + sa.aad->net.type = type; + sa.aad->net.protocol = protocol; + sa.aad->error = error; + + if (likely(!sa.aad->error)) { + u16 audit_mask = profile->net.audit[sa.u.net->family]; + if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && + !(1 << sa.aad->net.type & audit_mask))) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + u16 quiet_mask = profile->net.quiet[sa.u.net->family]; + u16 kill_mask = 0; + u16 denied = (1 << sa.aad->net.type) & ~quiet_mask; + + if (denied & kill_mask) + audit_type = AUDIT_APPARMOR_KILL; + + if ((denied & quiet_mask) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + } + + return aa_audit(audit_type, profile, GFP_KERNEL, &sa, audit_cb); +} + +/** + * aa_net_perm - very course network access check + * @op: operation being checked + * @profile: profile being enforced (NOT NULL) + * @family: network family + * @type: network type + * @protocol: network protocol + * + * Returns: %0 else error if permission denied + */ +int aa_net_perm(int op, struct aa_profile *profile, u16 family, int type, + int protocol, struct sock *sk) +{ + u16 family_mask; + int error; + + 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.allow[family]; + + error = (family_mask & (1 << type)) ? 0 : -EACCES; + + return audit_net(profile, op, family, type, protocol, sk, error); +} + +/** + * aa_revalidate_sk - Revalidate access to a sock + * @op: operation being checked + * @sk: sock being revalidated (NOT NULL) + * + * Returns: %0 else error if permission denied + */ +int aa_revalidate_sk(int op, struct sock *sk) +{ + struct aa_profile *profile; + int error = 0; + + /* aa_revalidate_sk should not be called from interrupt context + * don't mediate these calls as they are not task related + */ + if (in_interrupt()) + return 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_net_perm(op, profile, sk->sk_family, sk->sk_type, + sk->sk_protocol, sk); + + return error; +} diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf5fd22..27c8161 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -745,6 +745,7 @@ static void free_profile(struct aa_profile *profile) 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); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 329b1fd..1b90dfa 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -193,6 +193,19 @@ fail: return 0; } +static bool unpack_u16(struct aa_ext *e, u16 *data, const char *name) +{ + if (unpack_nameX(e, AA_U16, name)) { + if (!inbounds(e, sizeof(u16))) + return 0; + if (data) + *data = le16_to_cpu(get_unaligned((u16 *) e->pos)); + e->pos += sizeof(u16); + return 1; + } + return 0; +} + static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) { if (unpack_nameX(e, AA_U32, name)) { @@ -471,6 +484,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) { struct aa_profile *profile = NULL; const char *name = NULL; + size_t size = 0; int i, error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; @@ -564,6 +578,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e) if (!unpack_rlimits(e, profile)) goto fail; + size = unpack_array(e, "net_allowed_af"); + if (size) { + + for (i = 0; i < size; i++) { + /* discard extraneous rules that this kernel will + * never request + */ + if (i >= AF_MAX) { + u16 tmp; + if (!unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL) || + !unpack_u16(e, &tmp, NULL)) + goto fail; + continue; + } + if (!unpack_u16(e, &profile->net.allow[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.audit[i], NULL)) + goto fail; + if (!unpack_u16(e, &profile->net.quiet[i], NULL)) + goto fail; + } + if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + } + /* + * allow unix domain and netlink sockets they are handled + * by IPC + */ + profile->net.allow[AF_UNIX] = 0xffff; + profile->net.allow[AF_NETLINK] = 0xffff; + if (unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ profile->policy.dfa = unpack_dfa(e); -- 1.7.7.6 From 888a3d71db1ffd3a19d9f621b07e60c4ab9e1c44 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 May 2012 10:58:05 -0700 Subject: [PATCH] UBUNTU: SAUCE: apparmor: Add the ability to mediate mount Add the ability for apparmor to do mediation of mount operations. Mount rules require an updated apparmor_parser (2.8 series) for policy compilation. The basic form of the rules are. [audit] [deny] mount [conds]* [device] [ -> [conds] path], [audit] [deny] remount [conds]* [path], [audit] [deny] umount [conds]* [path], [audit] [deny] pivotroot [oldroot=] remount is just a short cut for mount options=remount where [conds] can be fstype= options= Example mount commands mount, # allow all mounts, but not umount or pivotroot mount fstype=procfs, # allow mounting procfs anywhere mount options=(bind, ro) /foo -> /bar, # readonly bind mount mount /dev/sda -> /mnt, mount /dev/sd** -> /mnt/**, mount fstype=overlayfs options=(rw,upperdir=/tmp/upper/,lowerdir=/) -> /mnt/ umount, umount /m*, See the apparmor userspace for full documentation Signed-off-by: John Johansen Acked-by: Kees Cook --- security/apparmor/Makefile | 2 +- security/apparmor/apparmorfs.c | 13 + security/apparmor/audit.c | 4 + security/apparmor/domain.c | 2 +- security/apparmor/include/apparmor.h | 3 +- security/apparmor/include/audit.h | 11 + security/apparmor/include/domain.h | 2 + security/apparmor/include/mount.h | 54 +++ security/apparmor/lsm.c | 59 ++++ security/apparmor/mount.c | 620 ++++++++++++++++++++++++++++++++++ 10 files changed, 767 insertions(+), 3 deletions(-) create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/mount.c diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 19daa85..63e0a4c 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ 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_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o net.o + resource.o sid.o file.o net.o mount.o clean-files := capability_names.h rlim_names.h net_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 114fb23..ee77ec9 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -426,10 +426,23 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { { } }; +static struct aa_fs_entry aa_fs_entry_mount[] = { + AA_FS_FILE_STRING("mask", "mount umount"), + { } +}; + +static struct aa_fs_entry aa_fs_entry_namespaces[] = { + AA_FS_FILE_BOOLEAN("profile", 1), + AA_FS_FILE_BOOLEAN("pivot_root", 1), + { } +}; + static struct aa_fs_entry aa_fs_entry_features[] = { AA_FS_DIR("domain", aa_fs_entry_domain), AA_FS_DIR("file", aa_fs_entry_file), AA_FS_DIR("network", aa_fs_entry_network), + AA_FS_DIR("mount", aa_fs_entry_mount), + AA_FS_DIR("namespaces", aa_fs_entry_namespaces), AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_FS_DIR("rlimit", aa_fs_entry_rlimit), { } diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 3ae28db..e267963 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -44,6 +44,10 @@ const char *const op_table[] = { "file_mmap", "file_mprotect", + "pivotroot", + "mount", + "umount", + "create", "post_create", "bind", diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b81ea10..afa8671 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -242,7 +242,7 @@ static const char *next_name(int xtype, const char *name) * * Returns: refcounted profile, or NULL on failure (MAYBE NULL) */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) { struct aa_profile *new_profile = NULL; struct aa_namespace *ns = profile->ns; diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 40aedd9..e243d96 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -29,8 +29,9 @@ #define AA_CLASS_NET 4 #define AA_CLASS_RLIMITS 5 #define AA_CLASS_DOMAIN 6 +#define AA_CLASS_MOUNT 7 -#define AA_CLASS_LAST AA_CLASS_DOMAIN +#define AA_CLASS_LAST AA_CLASS_MOUNT /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 17734f9..66a738c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -73,6 +73,10 @@ enum aa_ops { OP_FMMAP, OP_FMPROT, + OP_PIVOTROOT, + OP_MOUNT, + OP_UMOUNT, + OP_CREATE, OP_POST_CREATE, OP_BIND, @@ -122,6 +126,13 @@ struct apparmor_audit_data { unsigned long max; } rlim; struct { + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { const char *target; u32 request; u32 denied; diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464..a3f70c5 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -23,6 +23,8 @@ struct aa_domain { char **table; }; +struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex); + int apparmor_bprm_set_creds(struct linux_binprm *bprm); int apparmor_bprm_secureexec(struct linux_binprm *bprm); void apparmor_bprm_committing_creds(struct linux_binprm *bprm); diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 0000000..bc17a53 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 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_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags); + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *old_name); + +int aa_new_mount(struct aa_profile *profile, const char *dev_name, + struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags); + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path); + +#endif /* __AA_MOUNT_H */ diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f628734..65ff9e4 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -36,6 +36,7 @@ #include "include/path.h" #include "include/policy.h" #include "include/procattr.h" +#include "include/mount.h" /* Flag indicating whether initialization completed */ int apparmor_initialized __initdata; @@ -504,6 +505,60 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); } +static int apparmor_sb_mount(char *dev_name, struct path *path, char *type, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + int error = 0; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + profile = __aa_current_profile(); + if (!unconfined(profile)) { + if (flags & MS_REMOUNT) + error = aa_remount(profile, path, flags, data); + else if (flags & MS_BIND) + error = aa_bind_mount(profile, path, dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(profile, path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount(profile, path, dev_name); + else + error = aa_new_mount(profile, dev_name, path, type, + flags, data); + } + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_umount(profile, mnt, flags); + + return error; +} + +static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) +{ + struct aa_profile *profile; + int error = 0; + + profile = __aa_current_profile(); + if (!unconfined(profile)) + error = aa_pivotroot(profile, old_path, new_path); + + return error; +} + static int apparmor_getprocattr(struct task_struct *task, char *name, char **value) { @@ -721,6 +776,10 @@ static struct security_operations apparmor_ops = { .capget = apparmor_capget, .capable = apparmor_capable, + .sb_mount = apparmor_sb_mount, + .sb_umount = apparmor_sb_umount, + .sb_pivotroot = apparmor_sb_pivotroot, + .path_link = apparmor_path_link, .path_unlink = apparmor_path_unlink, .path_symlink = apparmor_path_symlink, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 0000000..478aa4d --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,620 @@ +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2012 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/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (sa->aad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, sa->aad->mnt.type); + } + if (sa->aad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, sa->aad->mnt.src_name); + } + if (sa->aad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, sa->aad->mnt.trans); + } + if (sa->aad->mnt.flags || sa->aad->op == OP_MOUNT) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, sa->aad->mnt.flags); + audit_log_format(ab, "\""); + } + if (sa->aad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, sa->aad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @profile: the profile being enforced (NOT NULL) + * @gfp: allocation flags + * @op: operation being mediated (NOT NULL) + * @name: name of object being mediated (MAYBE NULL) + * @src_name: src_name of object being mediated (MAYBE_NULL) + * @type: type of filesystem (MAYBE_NULL) + * @trans: name of trans (MAYBE NULL) + * @flags: filesystem idependent mount flags + * @data: filesystem mount flags + * @request: permissions requested + * @perms: the permissions computed for the request (NOT NULL) + * @info: extra information message (MAYBE NULL) + * @error: 0 if operation allowed else failure error code + * + * Returns: %0 or error on failure + */ +static int audit_mount(struct aa_profile *profile, gfp_t gfp, int op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct file_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + struct common_audit_data sa = { }; + struct apparmor_audit_data aad = { }; + + if (likely(!error)) { + u32 mask = perms->audit; + + if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) + mask = 0xffff; + + /* mask off perms that are not being force audited */ + request &= mask; + + if (likely(!request)) + return 0; + audit_type = AUDIT_APPARMOR_AUDIT; + } else { + /* only report permissions that were denied */ + request = request & ~perms->allow; + + if (request & perms->kill) + audit_type = AUDIT_APPARMOR_KILL; + + /* quiet known rejects, assumes quiet and kill do not overlap */ + if ((request & perms->quiet) && + AUDIT_MODE(profile) != AUDIT_NOQUIET && + AUDIT_MODE(profile) != AUDIT_ALL) + request &= ~perms->quiet; + + if (!request) + return COMPLAIN_MODE(profile) ? + complain_error(error) : error; + } + + sa.type = LSM_AUDIT_DATA_NONE; + sa.aad = &aad; + sa.aad->op = op; + sa.aad->name = name; + sa.aad->mnt.src_name = src_name; + sa.aad->mnt.type = type; + sa.aad->mnt.trans = trans; + sa.aad->mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + sa.aad->mnt.data = data; + sa.aad->info = info; + sa.aad->error = error; + + return aa_audit(audit_type, profile, gfp, &sa, audit_cb); +} + +/** + * match_mnt_flags - Do an ordered match on mount flags + * @dfa: dfa to match against + * @state: state to start in + * @flags: mount flags to match against + * + * Mount flags are encoded as an ordered match. This is done instead of + * checking against a simple bitmask, to allow for logical operations + * on the flags. + * + * Returns: next state after flags match + */ +static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state, + unsigned long flags) +{ + unsigned int i; + + for (i = 0; i <= 31 ; ++i) { + if ((1 << i) & flags) + state = aa_dfa_next(dfa, state, i + 1); + } + + return state; +} + +/** + * compute_mnt_perms - compute mount permission associated with @state + * @dfa: dfa to match against (NOT NULL) + * @state: state match finished in + * + * Returns: mount permissions + */ +static struct file_perms compute_mnt_perms(struct aa_dfa *dfa, + unsigned int state) +{ + struct file_perms perms; + + perms.kill = 0; + perms.allow = 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); + + return perms; +} + +static const char const *mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match" +}; + +/* + * Returns 0 on success else element that match failed in, this is the + * index into the mnt_info_table above + */ +static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct file_perms *perms) +{ + unsigned int state; + + state = aa_dfa_match(dfa, start, mntpnt); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(dfa, state, devname); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(dfa, state, type); + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(dfa, state, flags); + if (!state) + return 4; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + + /* only match data if not binary and the DFA flags data is expected */ + if (data && !binary && (perms->allow & AA_CONT_MATCH)) { + state = aa_dfa_null_transition(dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(dfa, state, data); + if (!state) + return 5; + *perms = compute_mnt_perms(dfa, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at end of flags match */ + return 4; +} + +/** + * match_mnt - handle path matching for mount + * @profile: the confining profile + * @mntpnt: string for the mntpnt (NOT NULL) + * @devname: string for the devname/src_name (MAYBE NULL) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @perms: Returns: permission found by the match + * @info: Returns: infomation string about the match for logging + * + * Returns: 0 on success else error + */ +static int match_mnt(struct aa_profile *profile, const char *mntpnt, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + struct file_perms *perms, const char **info) +{ + int pos; + + if (!profile->policy.dfa) + return -EACCES; + + pos = do_match_mnt(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + mntpnt, devname, type, flags, data, binary, perms); + if (pos) { + *info = mnt_info_table[pos]; + return -EACCES; + } + + return 0; +} + +static int path_flags(struct aa_profile *profile, struct path *path) +{ + return profile->path_flags | + S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0; +} + +int aa_remount(struct aa_profile *profile, struct path *path, + unsigned long flags, void *data) +{ + struct file_perms perms = { }; + const char *name, *info = NULL; + char *buffer = NULL; + int binary, error; + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_bind_mount(struct aa_profile *profile, struct path *path, + const char *dev_name, unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, flags, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, flags, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_mount_change_type(struct aa_profile *profile, struct path *path, + unsigned long flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, NULL, NULL, flags, NULL, 0, &perms, + &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, NULL, NULL, + NULL, flags, NULL, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + + return error; +} + +int aa_move_mount(struct aa_profile *profile, struct path *path, + const char *orig_name) +{ + struct file_perms perms = { }; + char *buffer = NULL, *old_buffer = NULL; + const char *name, *old_name = NULL, *info = NULL; + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + goto audit; + + error = aa_path_name(&old_path, path_flags(profile, &old_path), + &old_buffer, &old_name, &info); + path_put(&old_path); + if (error) + goto audit; + + error = match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, 0, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, old_name, + NULL, NULL, MS_MOVE, NULL, AA_MAY_MOUNT, &perms, + info, error); + kfree(buffer); + kfree(old_buffer); + + return error; +} + +int aa_new_mount(struct aa_profile *profile, const char *orig_dev_name, + struct path *path, const char *type, unsigned long flags, + void *data) +{ + struct file_perms perms = { }; + char *buffer = NULL, *dev_buffer = NULL; + const char *name = NULL, *dev_name = NULL, *info = NULL; + int binary = 1; + int error; + + dev_name = orig_dev_name; + if (type) { + int requires_dev; + struct file_system_type *fstype = get_fs_type(type); + if (!fstype) + return -ENODEV; + + binary = fstype->fs_flags & FS_BINARY_MOUNTDATA; + requires_dev = fstype->fs_flags & FS_REQUIRES_DEV; + put_filesystem(fstype); + + if (requires_dev) { + struct path dev_path; + + if (!dev_name || !*dev_name) { + error = -ENOENT; + goto out; + } + + error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); + if (error) + goto audit; + + error = aa_path_name(&dev_path, + path_flags(profile, &dev_path), + &dev_buffer, &dev_name, &info); + path_put(&dev_path); + if (error) + goto audit; + } + } + + error = aa_path_name(path, path_flags(profile, path), &buffer, &name, + &info); + if (error) + goto audit; + + error = match_mnt(profile, name, dev_name, type, flags, data, binary, + &perms, &info); + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_MOUNT, name, dev_name, + type, NULL, flags, data, AA_MAY_MOUNT, &perms, info, + error); + kfree(buffer); + kfree(dev_buffer); + +out: + return error; + +} + +int aa_umount(struct aa_profile *profile, struct vfsmount *mnt, int flags) +{ + struct file_perms perms = { }; + char *buffer = NULL; + const char *name, *info = NULL; + int error; + + struct path path = { mnt, mnt->mnt_root }; + error = aa_path_name(&path, path_flags(profile, &path), &buffer, &name, + &info); + if (error) + goto audit; + + if (!error && profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, AA_MAY_UMOUNT, &perms, info, error); + kfree(buffer); + + return error; +} + +int aa_pivotroot(struct aa_profile *profile, struct path *old_path, + struct path *new_path) +{ + struct file_perms perms = { }; + struct aa_profile *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL; + const char *old_name, *new_name = NULL, *info = NULL; + int error; + + error = aa_path_name(old_path, path_flags(profile, old_path), + &old_buffer, &old_name, &info); + if (error) + goto audit; + + error = aa_path_name(new_path, path_flags(profile, new_path), + &new_buffer, &new_name, &info); + if (error) + goto audit; + + if (profile->policy.dfa) { + unsigned int state; + state = aa_dfa_match(profile->policy.dfa, + profile->policy.start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(profile->policy.dfa, state); + state = aa_dfa_match(profile->policy.dfa, state, old_name); + perms = compute_mnt_perms(profile->policy.dfa, state); + } + + if (AA_MAY_PIVOTROOT & perms.allow) { + if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + target = x_table_lookup(profile, perms.xindex); + if (!target) + error = -ENOENT; + else + error = aa_replace_current_profile(target); + } + } else + error = -EACCES; + +audit: + error = audit_mount(profile, GFP_KERNEL, OP_PIVOTROOT, new_name, + old_name, NULL, target ? target->base.name : NULL, + 0, NULL, AA_MAY_PIVOTROOT, &perms, info, error); + aa_put_profile(target); + kfree(old_buffer); + kfree(new_buffer); + + return error; +} -- 1.7.7.6