summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Johansen <john.johansen@canonical.com>2015-06-10 10:43:00 (GMT)
committerJohn Johansen <john.johansen@canonical.com>2015-06-10 10:43:00 (GMT)
commit032f9cfb647704388b6ccaefe25a036c5d3f5f86 (patch)
treef6544907dc676ebd8b0d1e94d02aeb6a34e746a7
parent8bb495e3f02401ee6f76d1b1d77f3ac9f079e376 (diff)
UBUNTU: SAUCE: (no-up) apparmor: Sync to apparmor3 - RC1 snapshot
This is a sync and squash to the apparmor 3 RC 1 development snapshot. The set of patches in this squash are available at the apparmor-3.RC1 tag in git://kernel.ubuntu.com/jj/ubuntu-utopic.git. This cleans up several functions over the alpha6 sync, and includes multiple bug fixes. In addition it picks up - new network mediation - fine grained mediation of all unix socket types BugLink: http://bugs.launchpad.net/bugs/1362199 Signed-off-by: John Johansen <john.johansen@canonical.com> Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
-rw-r--r--security/apparmor/.gitignore1
-rw-r--r--security/apparmor/Kconfig47
-rw-r--r--security/apparmor/Makefile50
-rw-r--r--security/apparmor/af_unix.c632
-rw-r--r--security/apparmor/apparmorfs.c923
-rw-r--r--security/apparmor/audit.c89
-rw-r--r--security/apparmor/capability.c62
-rw-r--r--security/apparmor/context.c122
-rw-r--r--security/apparmor/crypto.c95
-rw-r--r--security/apparmor/domain.c205
-rw-r--r--security/apparmor/file.c555
-rw-r--r--security/apparmor/include/af_unix.h121
-rw-r--r--security/apparmor/include/apparmor.h79
-rw-r--r--security/apparmor/include/apparmorfs.h42
-rw-r--r--security/apparmor/include/audit.h74
-rw-r--r--security/apparmor/include/capability.h13
-rw-r--r--security/apparmor/include/context.h208
-rw-r--r--security/apparmor/include/crypto.h36
-rw-r--r--security/apparmor/include/domain.h2
-rw-r--r--security/apparmor/include/file.h106
-rw-r--r--security/apparmor/include/ipc.h22
-rw-r--r--security/apparmor/include/label.h419
-rw-r--r--security/apparmor/include/match.h36
-rw-r--r--security/apparmor/include/mount.h54
-rw-r--r--security/apparmor/include/net.h110
-rw-r--r--security/apparmor/include/path.h63
-rw-r--r--security/apparmor/include/perms.h174
-rw-r--r--security/apparmor/include/policy.h267
-rw-r--r--security/apparmor/include/policy_unpack.h21
-rw-r--r--security/apparmor/include/procattr.h3
-rw-r--r--security/apparmor/include/resource.h4
-rw-r--r--security/apparmor/include/sid.h4
-rw-r--r--security/apparmor/include/sig_names.h95
-rw-r--r--security/apparmor/ipc.c247
-rw-r--r--security/apparmor/label.c1844
-rw-r--r--security/apparmor/lib.c469
-rw-r--r--security/apparmor/lsm.c880
-rw-r--r--security/apparmor/match.c23
-rw-r--r--security/apparmor/mount.c703
-rw-r--r--security/apparmor/net.c397
-rw-r--r--security/apparmor/path.c175
-rw-r--r--security/apparmor/policy.c927
-rw-r--r--security/apparmor/policy_unpack.c244
-rw-r--r--security/apparmor/procattr.c66
-rw-r--r--security/apparmor/resource.c117
45 files changed, 9307 insertions, 1519 deletions
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore
index 9cdec70..d5b291e 100644
--- a/security/apparmor/.gitignore
+++ b/security/apparmor/.gitignore
@@ -1,5 +1,6 @@
#
# Generated include files
#
+net_names.h
capability_names.h
rlim_names.h
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 9b9013b..a738fee2 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -29,3 +29,50 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
boot.
If you are unsure how to answer this question, answer 1.
+
+config SECURITY_APPARMOR_STATS
+ bool "enable debug statistics"
+ depends on SECURITY_APPARMOR
+ select APPARMOR_LABEL_STATS
+ default n
+ help
+ This enables keeping statistics on various internal structures
+ and functions in apparmor.
+
+ If you are unsure how to answer this question, answer N.
+
+config SECURITY_APPARMOR_UNCONFINED_INIT
+ bool "Set init to unconfined on boot"
+ depends on SECURITY_APPARMOR
+ default y
+ help
+ This option determines policy behavior during early boot by
+ placing the init process in the unconfined state, or the
+ 'default' profile.
+
+ This option determines policy behavior during early boot by
+ placing the init process in the unconfined state, or the
+ 'default' profile.
+
+ 'Y' means init and its children are not confined, unless the
+ init process is re-execed after a policy load; loaded policy
+ will only apply to processes started after the load.
+
+ 'N' means init and its children are confined in a profile
+ named 'default', which can be replaced later and thus
+ provide for confinement for processes started early at boot,
+ though not confined during early boot.
+
+ If you are unsure how to answer this question, answer Y.
+
+config SECURITY_APPARMOR_HASH
+ bool "SHA1 hash of loaded profiles"
+ depends on SECURITY_APPARMOR
+ depends on CRYPTO
+ select CRYPTO_SHA1
+ default y
+
+ help
+ This option selects whether sha1 hashing is done against loaded
+ profiles and exported for inspection to user space via the apparmor
+ filesystem.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index 5706b74..69ce26e 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -4,10 +4,44 @@ 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 label.o mount.o net.o af_unix.o
+apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.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 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 "/AF_ROUTE/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 -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \
+ '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 capability names
# Transforms lines from
@@ -18,7 +52,11 @@ quiet_cmd_make-caps = GEN $@
cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\
sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \
-e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\
- echo "};" >> $@
+ echo "};" >> $@ ;\
+ echo -n '\#define AA_FS_CAPS_MASK "' >> $@ ;\
+ sed $< -r -n -e '/CAP_FS_MASK/d' \
+ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \
+ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@
# Build a lower case string table of rlimit names.
@@ -56,6 +94,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/uapi/linux/capability.h \
$(src)/Makefile
@@ -63,3 +102,8 @@ $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \
$(obj)/rlim_names.h : $(srctree)/include/uapi/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/af_unix.c b/security/apparmor/af_unix.c
new file mode 100644
index 0000000..73bd94d
--- /dev/null
+++ b/security/apparmor/af_unix.c
@@ -0,0 +1,632 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor af_unix fine grained mediation
+ *
+ * Copyright 2014 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 <net/tcp_states.h>
+
+#include "include/af_unix.h"
+#include "include/apparmor.h"
+#include "include/context.h"
+#include "include/file.h"
+#include "include/label.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+static inline int unix_fs_perm(int op, u32 mask, struct aa_label *label,
+ struct unix_sock *u, int flags)
+{
+ AA_BUG(!label);
+ AA_BUG(!u);
+ AA_BUG(!UNIX_FS(u));
+
+ if (unconfined(label) || !LABEL_MEDIATES(label, AA_CLASS_FILE))
+ return 0;
+
+ if (!u->path.dentry) {
+ struct path_cond cond = { };
+ struct file_perms perms = { };
+ struct aa_profile *profile;
+
+ /* socket path has been cleared because it is being shutdown */
+ /* TODO: fix flags */
+ if (!(flags & PATH_MEDIATE_DELETED))
+ return -EACCES;
+ /* Mediate at original socket location */
+ /* TODO: ns disconnected paths */
+ /* TODO: after switch to newer audit provide deleted/shutdown
+ * message as part of audit info
+ */
+ return fn_for_each_confined(label, profile,
+ __aa_path_perm(op, profile,
+ u->addr->name->sun_path,
+ mask, &cond, flags, &perms));
+ } else {
+ /* the sunpath may not be valid for this ns so use the path */
+ struct path_cond cond = { u->path.dentry->d_inode->i_uid,
+ u->path.dentry->d_inode->i_mode
+ };
+
+ return aa_path_perm(op, label, &u->path, flags, mask & NET_FS_PERMS,
+ &cond);
+ }
+
+ return 0;
+}
+
+/* passing in state returned by PROFILE_MEDIATES_AF */
+static unsigned int match_to_prot(struct aa_profile *profile,
+ unsigned int state, int type, int protocol,
+ const char **info)
+{
+ u16 buffer[2];
+ buffer[0] = cpu_to_be16(type);
+ buffer[1] = cpu_to_be16(protocol);
+ state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer,
+ 4);
+ if (!state)
+ *info = "failed type and protocol match";
+ return state;
+}
+
+static unsigned int match_addr(struct aa_profile *profile, unsigned int state,
+ struct sockaddr_un *addr, int addrlen)
+{
+ if (addr)
+ /* include leading \0 */
+ state = aa_dfa_match_len(profile->policy.dfa, state,
+ addr->sun_path,
+ unix_addr_len(addrlen));
+ else
+ /* anonymous end point */
+ state = aa_dfa_match_len(profile->policy.dfa, state, "\x01",
+ 1);
+ /* todo change to out of band */
+ state = aa_dfa_null_transition(profile->policy.dfa, state);
+ return state;
+}
+
+static unsigned int match_to_local(struct aa_profile *profile,
+ unsigned int state, int type, int protocol,
+ struct sockaddr_un *addr, int addrlen,
+ const char **info)
+{
+ state = match_to_prot(profile, state, type, protocol, info);
+ if (state) {
+ state = match_addr(profile, state, addr, addrlen);
+ if (state) {
+ /* todo: local label matching */
+ state = aa_dfa_null_transition(profile->policy.dfa,
+ state);
+ if (!state)
+ *info = "failed local label match";
+ } else
+ *info = "failed local address match";
+ }
+
+ return state;
+}
+
+static unsigned int match_to_sk(struct aa_profile *profile,
+ unsigned int state, struct unix_sock *u,
+ const char **info)
+{
+ struct sockaddr_un *addr = NULL;
+ int addrlen = 0;
+
+ if (u->addr) {
+ addr = u->addr->name;
+ addrlen = u->addr->len;
+ }
+
+ return match_to_local(profile, state, u->sk.sk_type, u->sk.sk_protocol,
+ addr, addrlen, info);
+}
+
+#define CMD_ADDR 1
+#define CMD_LISTEN 2
+#define CMD_OPT 4
+
+static inline unsigned int match_to_cmd(struct aa_profile *profile,
+ unsigned int state, struct unix_sock *u,
+ char cmd, const char **info)
+{
+ state = match_to_sk(profile, state, u, info);
+ if (state) {
+ state = aa_dfa_match_len(profile->policy.dfa, state, &cmd, 1);
+ if (!state)
+ *info = "failed cmd selection match";
+ }
+
+ return state;
+}
+
+static inline unsigned int match_to_peer(struct aa_profile *profile,
+ unsigned int state,
+ struct unix_sock *u,
+ struct sockaddr_un *peer_addr,
+ int peer_addrlen,
+ const char **info)
+{
+ state = match_to_cmd(profile, state, u, CMD_ADDR, info);
+ if (state) {
+ state = match_addr(profile, state, peer_addr, peer_addrlen);
+ if (!state)
+ *info = "failed peer address match";
+ }
+ return state;
+}
+
+static int do_perms(struct aa_profile *profile, unsigned int state, u32 request,
+ struct common_audit_data *sa)
+{
+ struct aa_perms perms;
+
+ AA_BUG(!profile);
+
+ aa_compute_perms(profile->policy.dfa, state, &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ return aa_check_perms(profile, &perms, request, sa,
+ audit_net_cb);
+}
+
+static int match_label(struct aa_profile *profile, struct aa_profile *peer,
+ unsigned int state, u32 request,
+ struct common_audit_data *sa)
+{
+ AA_BUG(!profile);
+ AA_BUG(!peer);
+
+ aad(sa)->target = aa_peer_name(peer);
+
+ if (state) {
+ state = aa_dfa_match(profile->policy.dfa, state, aa_peer_name(peer));
+ if (!state)
+ aad(sa)->info = "failed peer label match";
+ }
+ return do_perms(profile, state, request, sa);
+}
+
+
+/* unix sock creation comes before we know if the socket will be an fs
+ * socket
+ * v6 - semantics are handled by mapping in profile load
+ * v7 - semantics require sock create for tasks creating an fs socket.
+ */
+static int profile_create_perm(struct aa_profile *profile, int family,
+ int type, int protocol)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(profile_unconfined(profile));
+
+ if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) {
+ DEFINE_AUDIT_UNIX(sa, OP_CREATE, NULL, type, protocol);
+
+ state = match_to_prot(profile, state, type, protocol,
+ &aad(&sa)->info);
+ return do_perms(profile, state, AA_MAY_CREATE, &sa);
+ }
+
+ return aa_profile_af_perm(profile, OP_CREATE, family, type, protocol,
+ NULL);
+}
+
+int aa_unix_create_perm(struct aa_label *label, int family, int type,
+ int protocol)
+{
+ struct aa_profile *profile;
+
+ if (unconfined(label))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_create_perm(profile, family, type, protocol));
+}
+
+
+static inline int profile_sk_perm(struct aa_profile *profile, int op,
+ u32 request, struct sock *sk)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(UNIX_FS(sk));
+ AA_BUG(profile_unconfined(profile));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
+
+ state = match_to_sk(profile, state, unix_sk(sk),
+ &aad(&sa)->info);
+ return do_perms(profile, state, request, &sa);
+ }
+
+ return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+}
+
+int aa_unix_label_sk_perm(struct aa_label *label, int op, u32 request,
+ struct sock *sk)
+{
+ struct aa_profile *profile;
+
+ return fn_for_each_confined(label, profile,
+ profile_sk_perm(profile, op, request, sk));
+}
+
+static int unix_label_sock_perm(struct aa_label *label, int op, u32 request,
+ struct socket *sock)
+{
+ if (unconfined(label))
+ return 0;
+ if (UNIX_FS(sock->sk))
+ return unix_fs_perm(op, request, label, unix_sk(sock->sk), 0);
+
+ return aa_unix_label_sk_perm(label, op, request, sock->sk);
+}
+
+/* revaliation, get/set attr */
+int aa_unix_sock_perm(int op, u32 request, struct socket *sock)
+{
+ return unix_label_sock_perm(aa_current_label(), op, request, sock);
+}
+
+static int profile_bind_perm(struct aa_profile *profile, struct sock *sk,
+ struct sockaddr *addr, int addrlen)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(addr->sa_family != AF_UNIX);
+ AA_BUG(profile_unconfined(profile));
+ AA_BUG(unix_addr_fs(addr, addrlen));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ /* bind for abstract socket */
+ DEFINE_AUDIT_UNIX(sa, OP_BIND, sk, sk->sk_type,
+ sk->sk_protocol);
+ aad(&sa)->net.addr = unix_addr(addr);
+ aad(&sa)->net.addrlen = addrlen;
+
+ state = match_to_local(profile, state,
+ sk->sk_type, sk->sk_protocol,
+ unix_addr(addr), addrlen,
+ &aad(&sa)->info);
+ return do_perms(profile, state, AA_MAY_BIND, &sa);
+ }
+
+ return aa_profile_af_perm(profile, OP_BIND, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+}
+
+int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ struct aa_profile *profile;
+ struct aa_label *label = aa_current_label();
+
+ /* fs bind is handled by mknod */
+ if (unconfined(label) || unix_addr_fs(address, addrlen))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_bind_perm(profile, sock->sk, address, addrlen));
+}
+
+int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ /* unix connections are covered by the
+ * - unix_stream_connect (stream) and unix_may_send hooks (dgram)
+ * - fs connect is handled by open
+ */
+ return 0;
+}
+
+static int profile_listen_perm(struct aa_profile *profile, struct sock *sk,
+ int backlog)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(UNIX_FS(sk));
+ AA_BUG(profile_unconfined(profile));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ u16 b = cpu_to_be16(backlog);
+ DEFINE_AUDIT_UNIX(sa, OP_LISTEN, sk, sk->sk_type,
+ sk->sk_protocol);
+
+ state = match_to_cmd(profile, state, unix_sk(sk), CMD_LISTEN,
+ &aad(&sa)->info);
+ if (state) {
+ state = aa_dfa_match_len(profile->policy.dfa, state,
+ (char *) &b, 2);
+ if (!state)
+ aad(&sa)->info = "failed listen backlog match";
+ }
+ return do_perms(profile, state, AA_MAY_LISTEN, &sa);
+ }
+
+ return aa_profile_af_perm(profile, OP_LISTEN, sk->sk_family,
+ sk->sk_type, sk->sk_protocol, sk);
+}
+
+int aa_unix_listen_perm(struct socket *sock, int backlog)
+{
+ struct aa_profile *profile;
+ struct aa_label *label = aa_current_label();
+
+ if (unconfined(label) || UNIX_FS(sock->sk))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_listen_perm(profile, sock->sk, backlog));
+}
+
+
+static inline int profile_accept_perm(struct aa_profile *profile,
+ struct sock *sk,
+ struct sock *newsk)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(UNIX_FS(sk));
+ AA_BUG(profile_unconfined(profile));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ DEFINE_AUDIT_UNIX(sa, OP_ACCEPT, sk, sk->sk_type,
+ sk->sk_protocol);
+
+ state = match_to_sk(profile, state, unix_sk(sk),
+ &aad(&sa)->info);
+ return do_perms(profile, state, AA_MAY_ACCEPT, &sa);
+ }
+
+ return aa_profile_af_perm(profile, OP_ACCEPT, sk->sk_family,
+ sk->sk_type, sk->sk_protocol, sk);
+}
+
+/* ability of sock to connect, not peer address binding */
+int aa_unix_accept_perm(struct socket *sock, struct socket *newsock)
+{
+ struct aa_profile *profile;
+ struct aa_label *label = aa_current_label();
+
+ if (unconfined(label) || UNIX_FS(sock->sk))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_accept_perm(profile, sock->sk, newsock->sk));
+}
+
+
+/* dgram handled by unix_may_sendmsg, right to send on stream done at connect
+ * could do per msg unix_stream here
+ */
+/* sendmsg, recvmsg */
+int aa_unix_msg_perm(int op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ return 0;
+}
+
+
+static int profile_opt_perm(struct aa_profile *profile, int op, u32 request,
+ struct sock *sk, int level, int optname)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(!sk);
+ AA_BUG(UNIX_FS(sk));
+ AA_BUG(profile_unconfined(profile));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ u16 b = cpu_to_be16(optname);
+ DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
+
+ state = match_to_cmd(profile, state, unix_sk(sk), CMD_OPT,
+ &aad(&sa)->info);
+ if (state) {
+ state = aa_dfa_match_len(profile->policy.dfa, state,
+ (char *) &b, 2);
+ if (!state)
+ aad(&sa)->info = "failed sockopt match";
+ }
+ return do_perms(profile, state, request, &sa);
+ }
+
+ return aa_profile_af_perm(profile, op, sk->sk_family,
+ sk->sk_type, sk->sk_protocol, sk);
+}
+
+int aa_unix_opt_perm(int op, u32 request, struct socket *sock, int level,
+ int optname)
+{
+ struct aa_profile *profile;
+ struct aa_label *label = aa_current_label();
+
+ if (unconfined(label) || UNIX_FS(sock->sk))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_opt_perm(profile, op, request, sock->sk,
+ level, optname));
+}
+
+/* null peer_label is allowed, in which case the peer_sk label is used */
+static int profile_peer_perm(struct aa_profile *profile, int op, u32 request,
+ struct sock *sk, struct sock *peer_sk,
+ struct aa_label *peer_label,
+ struct common_audit_data *sa)
+{
+ unsigned int state;
+
+ AA_BUG(!profile);
+ AA_BUG(profile_unconfined(profile));
+ AA_BUG(!sk);
+ AA_BUG(!peer_sk);
+ AA_BUG(UNIX_FS(peer_sk));
+
+ state = PROFILE_MEDIATES_AF(profile, AF_UNIX);
+ if (state) {
+ struct aa_sk_cxt *peer_cxt = SK_CXT(peer_sk);
+ struct aa_profile *peerp;
+ struct sockaddr_un *addr = NULL;
+ int len = 0;
+ if (unix_sk(peer_sk)->addr) {
+ addr = unix_sk(peer_sk)->addr->name;
+ len = unix_sk(peer_sk)->addr->len;
+ }
+ state = match_to_peer(profile, state, unix_sk(sk),
+ addr, len, &aad(sa)->info);
+ if (!peer_label)
+ peer_label = peer_cxt->label;
+ return fn_for_each(peer_label, peerp,
+ match_label(profile, peerp, state, request,
+ sa));
+ }
+
+ return aa_profile_af_perm(profile, op, sk->sk_family, sk->sk_type,
+ sk->sk_protocol, sk);
+}
+
+/**
+ *
+ * Requires: lock held on both @sk and @peer_sk
+ */
+int aa_unix_peer_perm(struct aa_label *label, int op, u32 request,
+ struct sock *sk, struct sock *peer_sk,
+ struct aa_label *peer_label)
+{
+ struct unix_sock *peeru = unix_sk(peer_sk);
+ struct unix_sock *u = unix_sk(sk);
+
+ AA_BUG(!label);
+ AA_BUG(!sk);
+ AA_BUG(!peer_sk);
+
+ if (UNIX_FS(peeru))
+ return unix_fs_perm(op, request, label, peeru, 0);
+ else if (UNIX_FS(u))
+ return unix_fs_perm(op, request, label, u, 0);
+ else {
+ struct aa_profile *profile;
+ DEFINE_AUDIT_UNIX(sa, op, sk, sk->sk_type, sk->sk_protocol);
+ aad(&sa)->net.peer_sk = peer_sk;
+
+ /* TODO: ns!!! */
+ if (!net_eq(sock_net(sk), sock_net(peer_sk))) {
+ ;
+ }
+
+ if (unconfined(label))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ profile_peer_perm(profile, op, request, sk,
+ peer_sk, peer_label, &sa));
+ }
+}
+
+
+/* from net/unix/af_unix.c */
+static void unix_state_double_lock(struct sock *sk1, struct sock *sk2)
+{
+ if (unlikely(sk1 == sk2) || !sk2) {
+ unix_state_lock(sk1);
+ return;
+ }
+ if (sk1 < sk2) {
+ unix_state_lock(sk1);
+ unix_state_lock_nested(sk2);
+ } else {
+ unix_state_lock(sk2);
+ unix_state_lock_nested(sk1);
+ }
+}
+
+static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2)
+{
+ if (unlikely(sk1 == sk2) || !sk2) {
+ unix_state_unlock(sk1);
+ return;
+ }
+ unix_state_unlock(sk1);
+ unix_state_unlock(sk2);
+}
+
+int aa_unix_file_perm(struct aa_label *label, int op, u32 request,
+ struct socket *sock)
+{
+ struct sock *peer_sk = NULL;
+ u32 sk_req = request & ~NET_PEER_MASK;
+ int error = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(sock->sk->sk_family != AF_UNIX);
+
+ /* TODO: update sock label with new task label */
+ unix_state_lock(sock->sk);
+ peer_sk = unix_peer(sock->sk);
+ if (peer_sk)
+ sock_hold(peer_sk);
+ if (!unix_connected(sock) && sk_req) {
+ error = unix_label_sock_perm(label, op, sk_req, sock);
+ if (!error) {
+ // update label
+ }
+ }
+ unix_state_unlock(sock->sk);
+ if (!peer_sk)
+ return error;
+
+ unix_state_double_lock(sock->sk, peer_sk);
+ if (UNIX_FS(sock->sk)) {
+ error = unix_fs_perm(op, request, label, unix_sk(sock->sk),
+ PATH_SOCK_COND);
+ } else if (UNIX_FS(peer_sk)) {
+ error = unix_fs_perm(op, request, label, unix_sk(peer_sk),
+ PATH_SOCK_COND);
+ } else {
+ struct aa_sk_cxt *pcxt = SK_CXT(peer_sk);
+ if (sk_req)
+ error = aa_unix_label_sk_perm(label, op, sk_req,
+ sock->sk);
+ last_error(error,
+ xcheck(aa_unix_peer_perm(label, op,
+ MAY_READ | MAY_WRITE,
+ sock->sk, peer_sk, NULL),
+ aa_unix_peer_perm(pcxt->label, op,
+ MAY_READ | MAY_WRITE,
+ peer_sk, sock->sk, label)));
+ }
+
+ unix_state_double_unlock(sock->sk, peer_sk);
+ sock_put(peer_sk);
+
+ return error;
+}
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 16c15ec..f86f56e 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -12,22 +12,67 @@
* License.
*/
+#include <linux/ctype.h>
#include <linux/security.h>
#include <linux/vmalloc.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>
+#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/capability.h>
+#include <linux/rcupdate.h>
+#include <uapi/linux/major.h>
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
#include "include/context.h"
+#include "include/crypto.h"
+#include "include/ipc.h"
#include "include/policy.h"
#include "include/resource.h"
/**
+ * aa_mangle_name - mangle a profile name to std profile layout form
+ * @name: profile name to mangle (NOT NULL)
+ * @target: buffer to store mangled name, same length as @name (MAYBE NULL)
+ *
+ * Returns: length of mangled name
+ */
+static int mangle_name(const char *name, char *target)
+{
+ char *t = target;
+
+ while (*name == '/' || *name == '.')
+ name++;
+
+ if (target) {
+ for (; *name; name++) {
+ if (*name == '/')
+ *(t)++ = '.';
+ else if (isspace(*name))
+ *(t)++ = '_';
+ else if (isalnum(*name) || strchr("._-", *name))
+ *(t)++ = *name;
+ }
+
+ *t = 0;
+ } else {
+ int len = 0;
+ for (; *name; name++) {
+ if (isalnum(*name) || isspace(*name) ||
+ strchr("/._-", *name))
+ len++;
+ }
+
+ return len;
+ }
+
+ return t - target;
+}
+
+/**
* aa_simple_write_to_buffer - common routine for getting policy from user
* @op: operation doing the user buffer copy
* @userbuf: user buffer to copy data from (NOT NULL)
@@ -144,6 +189,147 @@ static const struct file_operations aa_fs_profile_remove = {
.llseek = default_llseek,
};
+/**
+ * query_label - queries a label and writes permissions to buf
+ * @buf: the resulting permissions string is stored here (NOT NULL)
+ * @buf_len: size of buf
+ * @query: binary query string to match against the dfa
+ * @query_len: size of query
+ *
+ * The buffers pointed to by buf and query may overlap. The query buffer is
+ * parsed before buf is written to.
+ *
+ * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is
+ * the name of the label, in the current namespace, that is to be queried and
+ * DFA_STRING is a binary string to match against the label(s)'s DFA.
+ *
+ * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters
+ * but must *not* be NUL terminated.
+ *
+ * Returns: number of characters written to buf or -errno on failure
+ */
+static ssize_t query_label(char *buf, size_t buf_len,
+ char *query, size_t query_len)
+{
+ struct aa_profile *profile;
+ struct aa_label *label;
+ char *label_name, *match_str;
+ size_t label_name_len, match_len;
+ struct aa_perms perms;
+ unsigned int state = 0;
+ struct label_it i;
+
+ if (!query_len)
+ return -EINVAL;
+
+ label_name = query;
+ label_name_len = strnlen(query, query_len);
+ if (!label_name_len || label_name_len == query_len)
+ return -EINVAL;
+
+ /**
+ * The extra byte is to account for the null byte between the
+ * profile name and dfa string. profile_name_len is greater
+ * than zero and less than query_len, so a byte can be safely
+ * added or subtracted.
+ */
+ match_str = label_name + label_name_len + 1;
+ match_len = query_len - label_name_len - 1;
+
+ label = aa_label_parse(aa_current_label(), label_name, GFP_KERNEL,
+ false);
+ if (IS_ERR(label))
+ return PTR_ERR(label);
+
+ aa_perms_all(&perms);
+ label_for_each_confined(i, label, profile) {
+ struct aa_perms tmp;
+ struct aa_dfa *dfa;
+ if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
+ dfa = profile->file.dfa;
+ state = aa_dfa_match_len(dfa, profile->file.start,
+ match_str + 1, match_len - 1);
+ } else if (profile->policy.dfa) {
+ if (!PROFILE_MEDIATES_SAFE(profile, *match_str))
+ continue; /* no change to current perms */
+ dfa = profile->policy.dfa;
+ state = aa_dfa_match_len(dfa, profile->policy.start[0],
+ match_str, match_len);
+ }
+ if (state)
+ aa_compute_perms(dfa, state, &tmp);
+ else
+ aa_perms_clear(&tmp);
+ aa_apply_modes_to_perms(profile, &tmp);
+ aa_perms_accum_raw(&perms, &tmp);
+ }
+ aa_put_label(label);
+
+ return scnprintf(buf, buf_len,
+ "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n",
+ perms.allow, perms.deny, perms.audit, perms.quiet);
+}
+
+#define QUERY_CMD_LABEL "label\0"
+#define QUERY_CMD_LABEL_LEN 6
+#define QUERY_CMD_PROFILE "profile\0"
+#define QUERY_CMD_PROFILE_LEN 8
+
+/**
+ * aa_write_access - generic permissions query
+ * @file: pointer to open apparmorfs/access file
+ * @ubuf: user buffer containing the complete query string (NOT NULL)
+ * @count: size of ubuf
+ * @ppos: position in the file (MUST BE ZERO)
+ *
+ * Allows for one permission query per open(), write(), and read() sequence.
+ * The only query currently supported is a label-based query. For this query
+ * ubuf must begin with "label\0", followed by the profile query specific
+ * format described in the query_label() function documentation.
+ *
+ * Returns: number of bytes written or -errno on failure
+ */
+static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ char *buf;
+ ssize_t len;
+
+ if (*ppos)
+ return -ESPIPE;
+
+ buf = simple_transaction_get(file, ubuf, count);
+ if (IS_ERR(buf))
+ return PTR_ERR(buf);
+
+ if (count > QUERY_CMD_PROFILE_LEN &&
+ !memcmp(buf, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) {
+ len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
+ buf + QUERY_CMD_PROFILE_LEN,
+ count - QUERY_CMD_PROFILE_LEN);
+ } else if (count > QUERY_CMD_LABEL_LEN &&
+ !memcmp(buf, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) {
+ len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
+ buf + QUERY_CMD_LABEL_LEN,
+ count - QUERY_CMD_LABEL_LEN);
+ } else
+ len = -EINVAL;
+
+ if (len < 0)
+ return len;
+
+ simple_transaction_set(file, len);
+
+ return count;
+}
+
+static const struct file_operations aa_fs_access = {
+ .write = aa_write_access,
+ .read = simple_transaction_read,
+ .release = simple_transaction_release,
+ .llseek = generic_file_llseek,
+};
+
static int aa_fs_seq_show(struct seq_file *seq, void *v)
{
struct aa_fs_entry *fs_file = seq->private;
@@ -182,14 +368,629 @@ const struct file_operations aa_fs_seq_file_ops = {
.release = single_release,
};
-/** Base file system setup **/
+static int aa_fs_seq_profile_open(struct inode *inode, struct file *file,
+ int (*show)(struct seq_file *, void *))
+{
+ struct aa_replacedby *r = aa_get_replacedby(inode->i_private);
+ int error = single_open(file, show, r);
+
+ if (error) {
+ file->private_data = NULL;
+ aa_put_replacedby(r);
+ }
+
+ return error;
+}
+
+static int aa_fs_seq_profile_release(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq = (struct seq_file *) file->private_data;
+ if (seq)
+ aa_put_replacedby(seq->private);
+ return single_release(inode, file);
+}
+
+static int aa_fs_seq_profname_show(struct seq_file *seq, void *v)
+{
+ struct aa_replacedby *r = seq->private;
+ struct aa_label *label = aa_get_label_rcu(&r->label);
+ struct aa_profile *profile = labels_profile(label);
+ seq_printf(seq, "%s\n", profile->base.name);
+ aa_put_label(label);
+
+ return 0;
+}
+
+static int aa_fs_seq_profname_open(struct inode *inode, struct file *file)
+{
+ return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profname_show);
+}
+
+static const struct file_operations aa_fs_profname_fops = {
+ .owner = THIS_MODULE,
+ .open = aa_fs_seq_profname_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_fs_seq_profile_release,
+};
+
+static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v)
+{
+ struct aa_replacedby *r = seq->private;
+ struct aa_label *label = aa_get_label_rcu(&r->label);
+ struct aa_profile *profile = labels_profile(label);
+ seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]);
+ aa_put_label(label);
+
+ return 0;
+}
+
+static int aa_fs_seq_profmode_open(struct inode *inode, struct file *file)
+{
+ return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profmode_show);
+}
+
+static const struct file_operations aa_fs_profmode_fops = {
+ .owner = THIS_MODULE,
+ .open = aa_fs_seq_profmode_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_fs_seq_profile_release,
+};
+
+static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v)
+{
+ struct aa_replacedby *r = seq->private;
+ struct aa_label *label = aa_get_label_rcu(&r->label);
+ struct aa_profile *profile = labels_profile(label);
+ if (profile->attach)
+ seq_printf(seq, "%s\n", profile->attach);
+ else if (profile->xmatch)
+ seq_puts(seq, "<unknown>\n");
+ else
+ seq_printf(seq, "%s\n", profile->base.name);
+ aa_put_label(label);
+
+ return 0;
+}
+
+static int aa_fs_seq_profattach_open(struct inode *inode, struct file *file)
+{
+ return aa_fs_seq_profile_open(inode, file, aa_fs_seq_profattach_show);
+}
+
+static const struct file_operations aa_fs_profattach_fops = {
+ .owner = THIS_MODULE,
+ .open = aa_fs_seq_profattach_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_fs_seq_profile_release,
+};
+
+static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
+{
+ struct aa_replacedby *r = seq->private;
+ struct aa_label *label = aa_get_label_rcu(&r->label);
+ struct aa_profile *profile = labels_profile(label);
+ unsigned int i, size = aa_hash_size();
+
+ if (profile->hash) {
+ for (i = 0; i < size; i++)
+ seq_printf(seq, "%.2x", profile->hash[i]);
+ seq_puts(seq, "\n");
+ }
+
+ return 0;
+}
+
+static int aa_fs_seq_hash_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, aa_fs_seq_hash_show, inode->i_private);
+}
+
+static const struct file_operations aa_fs_seq_hash_fops = {
+ .owner = THIS_MODULE,
+ .open = aa_fs_seq_hash_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/** fns to setup dynamic per profile/namespace files **/
+
+/**
+ *
+ * Requires: @profile->ns->lock held
+ */
+void __aa_fs_profile_rmdir(struct aa_profile *profile)
+{
+ struct aa_profile *child;
+ int i;
+
+ if (!profile)
+ return;
+ AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
+
+ list_for_each_entry(child, &profile->base.profiles, base.list)
+ __aa_fs_profile_rmdir(child);
+
+ for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) {
+ struct aa_replacedby *r;
+ if (!profile->dents[i])
+ continue;
+
+ r = profile->dents[i]->d_inode->i_private;
+ securityfs_remove(profile->dents[i]);
+ aa_put_replacedby(r);
+ profile->dents[i] = NULL;
+ }
+}
+
+/**
+ *
+ * Requires: @old->ns->lock held
+ */
+void __aa_fs_profile_migrate_dents(struct aa_profile *old,
+ struct aa_profile *new)
+{
+ int i;
+
+ AA_BUG(!old);
+ AA_BUG(!new);
+ AA_BUG(!mutex_is_locked(&profiles_ns(old)->lock));
+
+ for (i = 0; i < AAFS_PROF_SIZEOF; i++) {
+ new->dents[i] = old->dents[i];
+ if (new->dents[i])
+ new->dents[i]->d_inode->i_mtime = CURRENT_TIME;
+ old->dents[i] = NULL;
+ }
+}
+
+static struct dentry *create_profile_file(struct dentry *dir, const char *name,
+ struct aa_profile *profile,
+ const struct file_operations *fops)
+{
+ struct aa_replacedby *r = aa_get_replacedby(profile->label.replacedby);
+ struct dentry *dent;
+
+ dent = securityfs_create_file(name, S_IFREG | 0444, dir, r, fops);
+ if (IS_ERR(dent))
+ aa_put_replacedby(r);
+
+ return dent;
+}
+
+/**
+ *
+ * Requires: @profile->ns->lock held
+ */
+int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
+{
+ struct aa_profile *child;
+ struct dentry *dent = NULL, *dir;
+ int error;
+
+ AA_BUG(!profile);
+ AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock));
+
+ if (!parent) {
+ struct aa_profile *p;
+ p = aa_deref_parent(profile);
+ dent = prof_dir(p);
+ /* adding to parent that previously didn't have children */
+ dent = securityfs_create_dir("profiles", dent);
+ if (IS_ERR(dent))
+ goto fail;
+ prof_child_dir(p) = parent = dent;
+ }
+
+ if (!profile->dirname) {
+ int len, id_len;
+ len = mangle_name(profile->base.name, NULL);
+ id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id);
+
+ profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL);
+ if (!profile->dirname)
+ goto fail;
+
+ mangle_name(profile->base.name, profile->dirname);
+ sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++);
+ }
+
+ dent = securityfs_create_dir(profile->dirname, parent);
+ if (IS_ERR(dent))
+ goto fail;
+ prof_dir(profile) = dir = dent;
+
+ dent = create_profile_file(dir, "name", profile, &aa_fs_profname_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ profile->dents[AAFS_PROF_NAME] = dent;
+
+ dent = create_profile_file(dir, "mode", profile, &aa_fs_profmode_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ profile->dents[AAFS_PROF_MODE] = dent;
+
+ dent = create_profile_file(dir, "attach", profile,
+ &aa_fs_profattach_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ profile->dents[AAFS_PROF_ATTACH] = dent;
+
+ if (profile->hash) {
+ dent = create_profile_file(dir, "sha1", profile,
+ &aa_fs_seq_hash_fops);
+ if (IS_ERR(dent))
+ goto fail;
+ profile->dents[AAFS_PROF_HASH] = dent;
+ }
+
+ list_for_each_entry(child, &profile->base.profiles, base.list) {
+ error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
+ if (error)
+ goto fail2;
+ }
+
+ return 0;
+
+fail:
+ error = PTR_ERR(dent);
+
+fail2:
+ __aa_fs_profile_rmdir(profile);
+
+ return error;
+}
+
+/**
+ *
+ * Requires: @ns->lock held
+ */
+void __aa_fs_namespace_rmdir(struct aa_namespace *ns)
+{
+ struct aa_namespace *sub;
+ struct aa_profile *child;
+ int i;
+
+ if (!ns)
+ return;
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ list_for_each_entry(child, &ns->base.profiles, base.list)
+ __aa_fs_profile_rmdir(child);
+
+ list_for_each_entry(sub, &ns->sub_ns, base.list) {
+ mutex_lock(&sub->lock);
+ __aa_fs_namespace_rmdir(sub);
+ mutex_unlock(&sub->lock);
+ }
+
+ for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) {
+ securityfs_remove(ns->dents[i]);
+ ns->dents[i] = NULL;
+ }
+}
+
+/**
+ *
+ * Requires: @ns->lock held
+ */
+int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
+ const char *name)
+{
+ struct aa_namespace *sub;
+ struct aa_profile *child;
+ struct dentry *dent, *dir;
+ int error;
+
+ AA_BUG(!ns);
+ AA_BUG(!parent);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ if (!name)
+ name = ns->base.name;
+
+ dent = securityfs_create_dir(name, parent);
+ if (IS_ERR(dent))
+ goto fail;
+ ns_dir(ns) = dir = dent;
+
+ dent = securityfs_create_dir("profiles", dir);
+ if (IS_ERR(dent))
+ goto fail;
+ ns_subprofs_dir(ns) = dent;
+
+ dent = securityfs_create_dir("namespaces", dir);
+ if (IS_ERR(dent))
+ goto fail;
+ ns_subns_dir(ns) = dent;
+
+ list_for_each_entry(child, &ns->base.profiles, base.list) {
+ error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns));
+ if (error)
+ goto fail2;
+ }
+
+ list_for_each_entry(sub, &ns->sub_ns, base.list) {
+ mutex_lock(&sub->lock);
+ error = __aa_fs_namespace_mkdir(sub, ns_subns_dir(ns), NULL);
+ mutex_unlock(&sub->lock);
+ if (error)
+ goto fail2;
+ }
+
+ return 0;
+
+fail:
+ error = PTR_ERR(dent);
+
+fail2:
+ __aa_fs_namespace_rmdir(ns);
+
+ return error;
+}
+
+
+#define list_entry_next(pos, member) \
+ list_entry(pos->member.next, typeof(*pos), member)
+#define list_entry_is_head(pos, head, member) (&pos->member == (head))
+
+/**
+ * __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
+ * Requires: ns->parent->lock to be held
+ * NOTE: will not unlock root->lock
+ */
+static struct aa_namespace *__next_namespace(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ struct aa_namespace *parent, *next;
+
+ AA_BUG(!root);
+ AA_BUG(!ns);
+ AA_BUG(ns != root && !mutex_is_locked(&ns->parent->lock));
+
+ /* is next namespace a child */
+ if (!list_empty(&ns->sub_ns)) {
+ next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
+ mutex_lock(&next->lock);
+ return next;
+ }
+
+ /* check if the next ns is a sibling, parent, gp, .. */
+ parent = ns->parent;
+ while (ns != root) {
+ mutex_unlock(&ns->lock);
+ next = list_entry_next(ns, base.list);
+ if (!list_entry_is_head(next, &parent->sub_ns, base.list)) {
+ mutex_lock(&next->lock);
+ return next;
+ }
+ 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 (MAY BE NULL)
+ *
+ * Returns: unrefcounted profile or NULL if no profile
+ * Requires: ns.lock to be held
+ */
+static struct aa_profile *__first_profile(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ AA_BUG(!root);
+ AA_BUG(ns && !mutex_is_locked(&ns->lock));
+
+ 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 traversal 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;
+
+ AA_BUG(!mutex_is_locked(&profiles_ns(p)->lock));
+
+ /* 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, sibling, .. */
+ parent = rcu_dereference_protected(p->parent,
+ mutex_is_locked(&p->ns->lock));
+ while (parent) {
+ p = list_entry_next(p, base.list);
+ if (!list_entry_is_head(p, &parent->base.profiles, base.list))
+ return p;
+ p = parent;
+ parent = rcu_dereference_protected(parent->parent,
+ mutex_is_locked(&parent->ns->lock));
+ }
+
+ /* is next another profile in the namespace */
+ p = list_entry_next(p, base.list);
+ if (!list_entry_is_head(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)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_namespace *root = labels_ns(aa_current_label());
+ loff_t l = *pos;
+ f->private = aa_get_namespace(root);
+
+
+ /* find the first profile */
+ mutex_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 *ns = f->private;
+ (*pos)++;
+
+ return next_profile(ns, 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)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private, *ns;
+
+ if (profile) {
+ for (ns = profile->ns; ns && ns != root; ns = ns->parent)
+ mutex_unlock(&ns->lock);
+ }
+ mutex_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,
+ aa_profile_mode_names[profile->mode]);
+
+ 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);
+}
+
+static const struct file_operations aa_fs_profiles_fops = {
+ .open = profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = profiles_release,
+};
+
+
+/** Base file system setup **/
static struct aa_fs_entry aa_fs_entry_file[] = {
AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \
"link lock"),
{ }
};
+static struct aa_fs_entry aa_fs_entry_ptrace[] = {
+ AA_FS_FILE_STRING("mask", "read trace"),
+ { }
+};
+
+static struct aa_fs_entry aa_fs_entry_signal[] = {
+ AA_FS_FILE_STRING("mask", AA_FS_SIG_MASK),
+ { }
+};
+
static struct aa_fs_entry aa_fs_entry_domain[] = {
AA_FS_FILE_BOOLEAN("change_hat", 1),
AA_FS_FILE_BOOLEAN("change_hatv", 1),
@@ -198,11 +999,48 @@ static struct aa_fs_entry aa_fs_entry_domain[] = {
{ }
};
+static struct aa_fs_entry aa_fs_entry_versions[] = {
+ AA_FS_FILE_BOOLEAN("v5", 1),
+ AA_FS_FILE_BOOLEAN("v6", 1),
+ AA_FS_FILE_BOOLEAN("v7", 1),
+ { }
+};
+
+static struct aa_fs_entry aa_fs_entry_policy[] = {
+ AA_FS_DIR("versions", aa_fs_entry_versions),
+ AA_FS_FILE_BOOLEAN("set_load", 1),
+ { }
+};
+
+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_dbus[] = {
+ AA_FS_FILE_STRING("mask", "acquire send receive"),
+ { }
+};
+
static struct aa_fs_entry aa_fs_entry_features[] = {
+ AA_FS_DIR("policy", aa_fs_entry_policy),
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),
+ AA_FS_DIR("caps", aa_fs_entry_caps),
+ AA_FS_DIR("ptrace", aa_fs_entry_ptrace),
+ AA_FS_DIR("signal", aa_fs_entry_signal),
+ AA_FS_DIR("dbus", aa_fs_entry_dbus),
{ }
};
@@ -210,6 +1048,8 @@ 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),
+ AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access),
+ AA_FS_FILE_FOPS("profiles", 0640, &aa_fs_profiles_fops),
AA_FS_DIR("features", aa_fs_entry_features),
{ }
};
@@ -240,6 +1080,7 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file,
return error;
}
+static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir);
/**
* aafs_create_dir - recursively create a directory entry in the securityfs
* @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL)
@@ -250,17 +1091,16 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file,
static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
struct dentry *parent)
{
- int error;
struct aa_fs_entry *fs_file;
+ struct dentry *dir;
+ int error;
- fs_dir->dentry = securityfs_create_dir(fs_dir->name, parent);
- if (IS_ERR(fs_dir->dentry)) {
- error = PTR_ERR(fs_dir->dentry);
- fs_dir->dentry = NULL;
- goto failed;
- }
+ dir = securityfs_create_dir(fs_dir->name, parent);
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+ fs_dir->dentry = dir;
- for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) {
+ for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
if (fs_file->v_type == AA_FS_TYPE_DIR)
error = aafs_create_dir(fs_file, fs_dir->dentry);
else
@@ -272,6 +1112,8 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir,
return 0;
failed:
+ aafs_remove_dir(fs_dir);
+
return error;
}
@@ -296,7 +1138,7 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir)
{
struct aa_fs_entry *fs_file;
- for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) {
+ for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) {
if (fs_file->v_type == AA_FS_TYPE_DIR)
aafs_remove_dir(fs_file);
else
@@ -316,6 +1158,51 @@ void __init aa_destroy_aafs(void)
aafs_remove_dir(&aa_fs_entry);
}
+
+#define NULL_FILE_NAME ".null"
+struct path aa_null;
+
+static int aa_mk_null_file(struct dentry *parent)
+{
+ struct vfsmount *mount = NULL;
+ struct dentry *dentry;
+ struct inode *inode;
+ int count = 0;
+ int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count);
+ if (error)
+ return error;
+
+ mutex_lock(&parent->d_inode->i_mutex);
+ dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME));
+ if (IS_ERR(dentry)) {
+ error = PTR_ERR(dentry);
+ goto out;
+ }
+ inode = new_inode(parent->d_inode->i_sb);
+ if (!inode) {
+ error = -ENOMEM;
+ goto out1;
+ }
+
+ inode->i_ino = get_next_ino();
+ inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO;
+ inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+ init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO,
+ MKDEV(MEM_MAJOR, 3));
+ d_instantiate(dentry, inode);
+ aa_null.dentry = dget(dentry);
+ aa_null.mnt = mntget(mount);
+
+ error = 0;
+
+out1:
+ dput(dentry);
+out:
+ mutex_unlock(&parent->d_inode->i_mutex);
+ simple_release_fs(&mount, &count);
+ return error;
+}
+
/**
* aa_create_aafs - create the apparmor security filesystem
*
@@ -340,7 +1227,21 @@ static int __init aa_create_aafs(void)
if (error)
goto error;
- /* TODO: add support for apparmorfs_null and apparmorfs_mnt */
+ mutex_lock(&root_ns->lock);
+ error = __aa_fs_namespace_mkdir(root_ns, aa_fs_entry.dentry,
+ "policy");
+ mutex_unlock(&root_ns->lock);
+
+ if (error)
+ goto error;
+
+ error = aa_mk_null_file(aa_fs_entry.dentry);
+ if (error)
+ goto error;
+
+ if (!aa_g_unconfined_init) {
+ /* TODO: add default profile to apparmorfs */
+ }
/* Report that AppArmor fs is enabled */
aa_info_message("AppArmor Filesystem Enabled");
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
index 3ae28db..62b9dd7 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -39,10 +39,16 @@ const char *const op_table[] = {
"getattr",
"open",
+ "file_receive",
"file_perm",
"file_lock",
"file_mmap",
"file_mprotect",
+ "file_inherit",
+
+ "pivotroot",
+ "mount",
+ "umount",
"create",
"post_create",
@@ -59,6 +65,7 @@ const char *const op_table[] = {
"socket_shutdown",
"ptrace",
+ "signal",
"exec",
"change_hat",
@@ -88,7 +95,7 @@ static const char *const aa_audit_type[] = {
"HINT",
"STATUS",
"ERROR",
- "KILLED"
+ "KILLED",
"AUTO"
};
@@ -111,50 +118,45 @@ static const char *const aa_audit_type[] = {
static void audit_pre(struct audit_buffer *ab, void *ca)
{
struct common_audit_data *sa = ca;
- struct task_struct *tsk = sa->aad->tsk ? sa->aad->tsk : current;
if (aa_g_audit_header) {
audit_log_format(ab, "apparmor=");
- audit_log_string(ab, aa_audit_type[sa->aad->type]);
+ audit_log_string(ab, aa_audit_type[aad(sa)->type]);
}
- if (sa->aad->op) {
+ if (aad(sa)->op) {
audit_log_format(ab, " operation=");
- audit_log_string(ab, op_table[sa->aad->op]);
+ audit_log_string(ab, op_table[aad(sa)->op]);
}
- if (sa->aad->info) {
+ if (aad(sa)->info) {
audit_log_format(ab, " info=");
- audit_log_string(ab, sa->aad->info);
- if (sa->aad->error)
- audit_log_format(ab, " error=%d", sa->aad->error);
+ audit_log_string(ab, aad(sa)->info);
+ if (aad(sa)->error)
+ audit_log_format(ab, " error=%d", aad(sa)->error);
}
- if (sa->aad->profile) {
- struct aa_profile *profile = sa->aad->profile;
- pid_t pid;
- rcu_read_lock();
- pid = rcu_dereference(tsk->real_parent)->pid;
- rcu_read_unlock();
- audit_log_format(ab, " parent=%d", pid);
- if (profile->ns != root_ns) {
- audit_log_format(ab, " namespace=");
- audit_log_untrustedstring(ab, profile->ns->base.hname);
+ if (aad(sa)->label) {
+ struct aa_label *label = aad(sa)->label;
+ if (label_isprofile(label)) {
+ struct aa_profile *profile = labels_profile(label);
+ if (profile->ns != root_ns) {
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab,
+ profile->ns->base.hname);
+ }
+ audit_log_format(ab, " profile=");
+ audit_log_untrustedstring(ab, profile->base.hname);
+ } else {
+ audit_log_format(ab, " label=");
+ aa_label_audit(ab, root_ns, label, false, GFP_ATOMIC);
}
- audit_log_format(ab, " profile=");
- audit_log_untrustedstring(ab, profile->base.hname);
}
- if (sa->aad->name) {
+ if (aad(sa)->name) {
audit_log_format(ab, " name=");
- audit_log_untrustedstring(ab, sa->aad->name);
- }
-
- if (sa->aad->tsk) {
- audit_log_format(ab, " pid=%d comm=", tsk->pid);
- audit_log_untrustedstring(ab, tsk->comm);
+ audit_log_untrustedstring(ab, aad(sa)->name);
}
-
}
/**
@@ -165,7 +167,12 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
void aa_audit_msg(int type, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *))
{
- sa->aad->type = type;
+ /* TODO: redirect messages for profile to the correct ns
+ * rejects from subns should goto the audit associated
+ * with it, and audits from parent ns should got ns
+ * associated with it
+ */
+ aad(sa)->type = type;
common_lsm_audit(sa, audit_pre, cb);
}
@@ -173,7 +180,6 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
* aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message
* @profile: profile to check against (NOT NULL)
- * @gfp: allocation flags to use
* @sa: audit event (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*
@@ -181,14 +187,13 @@ void aa_audit_msg(int type, struct common_audit_data *sa,
*
* Returns: error on failure
*/
-int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
- struct common_audit_data *sa,
+int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *))
{
BUG_ON(!profile);
if (type == AUDIT_APPARMOR_AUTO) {
- if (likely(!sa->aad->error)) {
+ if (likely(!aad(sa)->error)) {
if (AUDIT_MODE(profile) != AUDIT_ALL)
return 0;
type = AUDIT_APPARMOR_AUDIT;
@@ -200,22 +205,22 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
(type == AUDIT_APPARMOR_DENIED &&
AUDIT_MODE(profile) == AUDIT_QUIET))
- return sa->aad->error;
+ return aad(sa)->error;
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL;
- if (!unconfined(profile))
- sa->aad->profile = profile;
+ aad(sa)->label = &profile->label;
aa_audit_msg(type, sa, cb);
- if (sa->aad->type == AUDIT_APPARMOR_KILL)
+ if (aad(sa)->type == AUDIT_APPARMOR_KILL)
(void)send_sig_info(SIGKILL, NULL,
- sa->aad->tsk ? sa->aad->tsk : current);
+ sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ?
+ sa->u.tsk : current);
- if (sa->aad->type == AUDIT_APPARMOR_ALLOWED)
- return complain_error(sa->aad->error);
+ if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED)
+ return complain_error(aad(sa)->error);
- return sa->aad->error;
+ return aad(sa)->error;
}
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index 887a5e9..0b28993 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -27,6 +27,11 @@
*/
#include "capability_names.h"
+struct aa_fs_entry aa_fs_entry_caps[] = {
+ AA_FS_FILE_STRING("mask", AA_FS_CAPS_MASK),
+ { }
+};
+
struct audit_cache {
struct aa_profile *profile;
kernel_cap_t caps;
@@ -48,8 +53,8 @@ static void audit_cb(struct audit_buffer *ab, void *va)
/**
* audit_caps - audit a capability
- * @profile: profile confining task (NOT NULL)
- * @task: task capability test was performed against (NOT NULL)
+ * @sa: audit data
+ * @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested
* @error: error code returned by test
*
@@ -58,19 +63,12 @@ static void audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: 0 or sa->error on success, error code on failure
*/
-static int audit_caps(struct aa_profile *profile, struct task_struct *task,
+static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
int cap, int error)
{
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_CAP;
- sa.aad = &aad;
- sa.u.cap = cap;
- sa.aad->tsk = task;
- sa.aad->op = OP_CAPABLE;
- sa.aad->error = error;
+ aad(sa)->error = error;
if (likely(!error)) {
/* test if auditing is being forced */
@@ -102,25 +100,40 @@ static int audit_caps(struct aa_profile *profile, struct task_struct *task,
}
put_cpu_var(audit_cache);
- return aa_audit(type, profile, GFP_ATOMIC, &sa, audit_cb);
+ return aa_audit(type, profile, sa, audit_cb);
}
/**
* profile_capable - test if profile allows use of capability @cap
* @profile: profile being enforced (NOT NULL, NOT unconfined)
* @cap: capability to test if allowed
+ * @sa: audit data (MAY BE NULL indicating no auditing)
*
* Returns: 0 if allowed else -EPERM
*/
-static int profile_capable(struct aa_profile *profile, int cap)
+static int profile_capable(struct aa_profile *profile, int cap,
+ struct common_audit_data *sa)
{
- return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM;
+ int error;
+
+ if (cap_raised(profile->caps.allow, cap) &&
+ !cap_raised(profile->caps.denied, cap))
+ error = 0;
+ else
+ error = -EPERM;
+
+ if (!sa) {
+ if (COMPLAIN_MODE(profile))
+ return complain_error(error);
+ return error;
+ }
+
+ return audit_caps(sa, profile, cap, error);
}
/**
* aa_capable - test permission to use capability
- * @task: task doing capability test against (NOT NULL)
- * @profile: profile confining @task (NOT NULL)
+ * @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested
* @audit: whether an audit record should be generated
*
@@ -128,16 +141,15 @@ static int profile_capable(struct aa_profile *profile, int cap)
*
* 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 aa_capable(struct aa_label *label, int cap, int audit)
{
- int error = profile_capable(profile, cap);
+ struct aa_profile *profile;
+ int error = 0;
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
+ sa.u.cap = cap;
- if (!audit) {
- if (COMPLAIN_MODE(profile))
- return complain_error(error);
- return error;
- }
+ error = fn_for_each_confined(label, profile,
+ profile_capable(profile, cap, audit ? &sa : NULL));
- return audit_caps(profile, task, cap, error);
+ return error;
}
diff --git a/security/apparmor/context.c b/security/apparmor/context.c
index 8a9b502..475682c 100644
--- a/security/apparmor/context.c
+++ b/security/apparmor/context.c
@@ -14,9 +14,9 @@
*
*
* AppArmor sets confinement on every task, via the the aa_task_cxt and
- * the aa_task_cxt.profile, both of which are required and are not allowed
+ * the aa_task_cxt.label, both of which are required and are not allowed
* to be NULL. The aa_task_cxt is not reference counted and is unique
- * to each cred (which is reference count). The profile pointed to by
+ * to each cred (which is reference count). The label pointed to by
* the task_cxt is reference counted.
*
* TODO
@@ -47,9 +47,9 @@ struct aa_task_cxt *aa_alloc_task_context(gfp_t flags)
void aa_free_task_context(struct aa_task_cxt *cxt)
{
if (cxt) {
- aa_put_profile(cxt->profile);
- aa_put_profile(cxt->previous);
- aa_put_profile(cxt->onexec);
+ aa_put_label(cxt->label);
+ aa_put_label(cxt->previous);
+ aa_put_label(cxt->onexec);
kzfree(cxt);
}
@@ -63,48 +63,57 @@ void aa_free_task_context(struct aa_task_cxt *cxt)
void aa_dup_task_context(struct aa_task_cxt *new, const struct aa_task_cxt *old)
{
*new = *old;
- aa_get_profile(new->profile);
- aa_get_profile(new->previous);
- aa_get_profile(new->onexec);
+ aa_get_label(new->label);
+ aa_get_label(new->previous);
+ aa_get_label(new->onexec);
}
/**
- * aa_replace_current_profile - replace the current tasks profiles
- * @profile: new profile (NOT NULL)
+ * aa_get_task_label - Get another task's label
+ * @task: task to query (NOT NULL)
+ *
+ * Returns: counted reference to @task's label
+ */
+struct aa_label *aa_get_task_label(struct task_struct *task)
+{
+ struct aa_label *p;
+
+ rcu_read_lock();
+ p = aa_get_newest_label(__aa_task_raw_label(task));
+ rcu_read_unlock();
+
+ return p;
+}
+
+/**
+ * aa_replace_current_label - replace the current tasks label
+ * @label: new label (NOT NULL)
*
* Returns: 0 or error on failure
*/
-int aa_replace_current_profile(struct aa_profile *profile)
+int aa_replace_current_label(struct aa_label *label)
{
- struct aa_task_cxt *cxt = current_cred()->security;
+ struct aa_task_cxt *cxt = current_cxt();
struct cred *new;
- BUG_ON(!profile);
+ BUG_ON(!label);
- if (cxt->profile == profile)
+ if (cxt->label == label)
return 0;
new = prepare_creds();
if (!new)
return -ENOMEM;
- cxt = new->security;
- if (unconfined(profile) || (cxt->profile->ns != profile->ns)) {
- /* if switching to unconfined or a different profile namespace
+ cxt = cred_cxt(new);
+ if (unconfined(label) || (labels_ns(cxt->label) != labels_ns(label)))
+ /* if switching to unconfined or a different label namespace
* clear out context state
*/
- aa_put_profile(cxt->previous);
- aa_put_profile(cxt->onexec);
- cxt->previous = NULL;
- cxt->onexec = NULL;
- cxt->token = 0;
- }
- /* be careful switching cxt->profile, when racing replacement it
- * is possible that cxt->profile->replacedby is the reference keeping
- * @profile valid, so make sure to get its reference before dropping
- * the reference on cxt->profile */
- aa_get_profile(profile);
- aa_put_profile(cxt->profile);
- cxt->profile = profile;
+ aa_clear_task_cxt_trans(cxt);
+
+ aa_get_label(label);
+ aa_put_label(cxt->label);
+ cxt->label = label;
commit_creds(new);
return 0;
@@ -112,21 +121,21 @@ int aa_replace_current_profile(struct aa_profile *profile)
/**
* aa_set_current_onexec - set the tasks change_profile to happen onexec
- * @profile: system profile to set at exec (MAYBE NULL to clear value)
+ * @label: system label to set at exec (MAYBE NULL to clear value)
*
* Returns: 0 or error on failure
*/
-int aa_set_current_onexec(struct aa_profile *profile)
+int aa_set_current_onexec(struct aa_label *label)
{
struct aa_task_cxt *cxt;
struct cred *new = prepare_creds();
if (!new)
return -ENOMEM;
- cxt = new->security;
- aa_get_profile(profile);
- aa_put_profile(cxt->onexec);
- cxt->onexec = profile;
+ cxt = cred_cxt(new);
+ aa_get_label(label);
+ aa_put_label(cxt->onexec);
+ cxt->onexec = label;
commit_creds(new);
return 0;
@@ -134,7 +143,7 @@ int aa_set_current_onexec(struct aa_profile *profile)
/**
* aa_set_current_hat - set the current tasks hat
- * @profile: profile to set as the current hat (NOT NULL)
+ * @label: label to set as the current hat (NOT NULL)
* @token: token value that must be specified to change from the hat
*
* Do switch of tasks hat. If the task is currently in a hat
@@ -142,29 +151,29 @@ int aa_set_current_onexec(struct aa_profile *profile)
*
* Returns: 0 or error on failure
*/
-int aa_set_current_hat(struct aa_profile *profile, u64 token)
+int aa_set_current_hat(struct aa_label *label, u64 token)
{
struct aa_task_cxt *cxt;
struct cred *new = prepare_creds();
if (!new)
return -ENOMEM;
- BUG_ON(!profile);
+ BUG_ON(!label);
- cxt = new->security;
+ cxt = cred_cxt(new);
if (!cxt->previous) {
/* transfer refcount */
- cxt->previous = cxt->profile;
+ cxt->previous = cxt->label;
cxt->token = token;
} else if (cxt->token == token) {
- aa_put_profile(cxt->profile);
+ aa_put_label(cxt->label);
} else {
/* previous_profile && cxt->token != token */
abort_creds(new);
return -EACCES;
}
- cxt->profile = aa_get_profile(aa_newest_version(profile));
+ cxt->label = aa_get_newest_label(label);
/* clear exec on switching context */
- aa_put_profile(cxt->onexec);
+ aa_put_label(cxt->onexec);
cxt->onexec = NULL;
commit_creds(new);
@@ -172,44 +181,37 @@ int aa_set_current_hat(struct aa_profile *profile, u64 token)
}
/**
- * aa_restore_previous_profile - exit from hat context restoring the profile
+ * aa_restore_previous_label - exit from hat context restoring previous label
* @token: the token that must be matched to exit hat context
*
- * Attempt to return out of a hat to the previous profile. The token
+ * Attempt to return out of a hat to the previous label. The token
* must match the stored token value.
*
* Returns: 0 or error of failure
*/
-int aa_restore_previous_profile(u64 token)
+int aa_restore_previous_label(u64 token)
{
struct aa_task_cxt *cxt;
struct cred *new = prepare_creds();
if (!new)
return -ENOMEM;
- cxt = new->security;
+ cxt = cred_cxt(new);
if (cxt->token != token) {
abort_creds(new);
return -EACCES;
}
- /* ignore restores when there is no saved profile */
+ /* ignore restores when there is no saved label */
if (!cxt->previous) {
abort_creds(new);
return 0;
}
- aa_put_profile(cxt->profile);
- cxt->profile = aa_newest_version(cxt->previous);
- BUG_ON(!cxt->profile);
- if (unlikely(cxt->profile != cxt->previous)) {
- aa_get_profile(cxt->profile);
- aa_put_profile(cxt->previous);
- }
+ aa_put_label(cxt->label);
+ cxt->label = aa_get_newest_label(cxt->previous);
+ BUG_ON(!cxt->label);
/* clear exec && prev information when restoring to previous context */
- cxt->previous = NULL;
- cxt->token = 0;
- aa_put_profile(cxt->onexec);
- cxt->onexec = NULL;
+ aa_clear_task_cxt_trans(cxt);
commit_creds(new);
return 0;
diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c
new file mode 100644
index 0000000..532471d
--- /dev/null
+++ b/security/apparmor/crypto.c
@@ -0,0 +1,95 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy loading interface function definitions.
+ *
+ * Copyright 2013 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.
+ *
+ * Fns to provide a checksum of policy that has been loaded this can be
+ * compared to userspace policy compiles to check loaded policy is what
+ * it should be.
+ */
+
+#include <crypto/hash.h>
+
+#include "include/apparmor.h"
+#include "include/crypto.h"
+
+static unsigned int apparmor_hash_size;
+
+static struct crypto_shash *apparmor_tfm;
+
+unsigned int aa_hash_size(void)
+{
+ return apparmor_hash_size;
+}
+
+int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
+ size_t len)
+{
+ struct {
+ struct shash_desc shash;
+ char ctx[crypto_shash_descsize(apparmor_tfm)];
+ } desc;
+ int error = -ENOMEM;
+ u32 le32_version = cpu_to_le32(version);
+
+ if (!apparmor_tfm)
+ return 0;
+
+ profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
+ if (!profile->hash)
+ goto fail;
+
+ desc.shash.tfm = apparmor_tfm;
+ desc.shash.flags = 0;
+
+ error = crypto_shash_init(&desc.shash);
+ if (error)
+ goto fail;
+ error = crypto_shash_update(&desc.shash, (u8 *) &le32_version, 4);
+ if (error)
+ goto fail;
+ error = crypto_shash_update(&desc.shash, (u8 *) start, len);
+ if (error)
+ goto fail;
+ error = crypto_shash_final(&desc.shash, profile->hash);
+ if (error)
+ goto fail;
+
+ return 0;
+
+fail:
+ kfree(profile->hash);
+ profile->hash = NULL;
+
+ return error;
+}
+
+static int __init init_profile_hash(void)
+{
+ struct crypto_shash *tfm;
+
+ if (!apparmor_initialized)
+ return 0;
+
+ tfm = crypto_alloc_shash("sha1", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(tfm)) {
+ int error = PTR_ERR(tfm);
+ AA_ERROR("failed to setup profile sha1 hashing: %d\n", error);
+ return error;
+ }
+ apparmor_tfm = tfm;
+ apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm);
+
+ aa_info_message("AppArmor sha1 policy hashing enabled");
+
+ return 0;
+}
+
+late_initcall(init_profile_hash);
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 859abda..6344b3f 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -50,41 +50,39 @@ void aa_free_domain_entries(struct aa_domain *domain)
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
- * @task: task we want to change profile of (NOT NULL)
* @to_profile: profile to change to (NOT NULL)
+ * @info: message if there is an error
*
- * Check if the task is ptraced and if so if the tracing task is allowed
+ * Check if current is ptraced and if so if the tracing task is allowed
* to trace the new domain
*
* Returns: %0 or error if change not allowed
*/
-static int may_change_ptraced_domain(struct task_struct *task,
- struct aa_profile *to_profile)
+static int may_change_ptraced_domain(struct aa_profile *to_profile,
+ const char **info)
{
struct task_struct *tracer;
- const struct cred *cred = NULL;
- struct aa_profile *tracerp = NULL;
+ struct aa_label *tracerl = NULL;
int error = 0;
rcu_read_lock();
- tracer = ptrace_parent(task);
- if (tracer) {
+ tracer = ptrace_parent(current);
+ if (tracer)
/* released below */
- cred = get_task_cred(tracer);
- tracerp = aa_cred_profile(cred);
- }
+ tracerl = aa_get_task_label(tracer);
/* not ptraced */
- if (!tracer || unconfined(tracerp))
+ if (!tracer || unconfined(tracerl))
goto out;
- error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
+ error = aa_may_ptrace(tracerl, &to_profile->label, PTRACE_MODE_ATTACH);
out:
rcu_read_unlock();
- if (cred)
- put_cred(cred);
+ aa_put_label(tracerl);
+ if (error)
+ *info = "ptrace prevents transition";
return error;
}
@@ -107,7 +105,7 @@ static struct file_perms change_profile_perms(struct aa_profile *profile,
struct path_cond cond = { };
unsigned int state;
- if (unconfined(profile)) {
+ if (profile_unconfined(profile)) {
perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC;
perms.audit = perms.quiet = perms.kill = 0;
return perms;
@@ -148,8 +146,8 @@ static struct aa_profile *__attach_match(const char *name,
int len = 0;
struct aa_profile *profile, *candidate = NULL;
- list_for_each_entry(profile, head, base.list) {
- if (profile->flags & PFLAG_NULL)
+ list_for_each_entry_rcu(profile, head, base.list) {
+ if (profile->label.flags & FLAG_NULL)
continue;
if (profile->xmatch && profile->xmatch_len > len) {
unsigned int state = aa_dfa_match(profile->xmatch,
@@ -181,9 +179,9 @@ static struct aa_profile *find_attach(struct aa_namespace *ns,
{
struct aa_profile *profile;
- read_lock(&ns->lock);
+ rcu_read_lock();
profile = aa_get_profile(__attach_match(name, list));
- read_unlock(&ns->lock);
+ rcu_read_unlock();
return profile;
}
@@ -242,7 +240,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;
@@ -343,6 +341,7 @@ static struct aa_profile *x_to_profile(struct aa_profile *profile,
int apparmor_bprm_set_creds(struct linux_binprm *bprm)
{
struct aa_task_cxt *cxt;
+ struct aa_label *label;
struct aa_profile *profile, *new_profile = NULL;
struct aa_namespace *ns;
char *buffer = NULL;
@@ -360,10 +359,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
if (bprm->cred_prepared)
return 0;
- cxt = bprm->cred->security;
+ cxt = cred_cxt(bprm->cred);
BUG_ON(!cxt);
- profile = aa_get_profile(aa_newest_version(cxt->profile));
+ label = aa_get_newest_label(cxt->label);
+ profile = labels_profile(label);
/*
* get the namespace from the replacement profile as replacement
* can change the namespace
@@ -372,11 +372,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
state = profile->file.start;
/* buffer freed below, name is pointer into buffer */
- error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer,
- &name, &info);
+ get_buffers(buffer);
+ error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
+ &name, &info, profile->disconnected);
if (error) {
- if (profile->flags &
- (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
+ if (profile_unconfined(profile) ||
+ (profile->label.flags & FLAG_IX_ON_NAME_ERROR))
error = 0;
name = bprm->filename;
goto audit;
@@ -385,11 +386,11 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
/* Test for onexec first as onexec directives override other
* x transitions.
*/
- if (unconfined(profile)) {
+ if (profile_unconfined(profile)) {
/* unconfined task */
if (cxt->onexec)
/* change_profile on exec already been granted */
- new_profile = aa_get_profile(cxt->onexec);
+ new_profile = labels_profile(aa_get_label(cxt->onexec));
else
new_profile = find_attach(ns, &ns->base.profiles, name);
if (!new_profile)
@@ -415,13 +416,13 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
* exec\0change_profile
*/
state = aa_dfa_null_transition(profile->file.dfa, state);
- cp = change_profile_perms(profile, cxt->onexec->ns,
- cxt->onexec->base.name,
+ cp = change_profile_perms(profile, labels_profile(cxt->onexec)->ns,
+ labels_profile(cxt->onexec)->base.name,
AA_MAY_ONEXEC, state);
if (!(cp.allow & AA_MAY_ONEXEC))
goto audit;
- new_profile = aa_get_profile(aa_newest_version(cxt->onexec));
+ new_profile = labels_profile(aa_get_newest_label(cxt->onexec));
goto apply;
}
@@ -438,15 +439,19 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
new_profile = aa_get_profile(profile);
goto x_clear;
} else if (perms.xindex & AA_X_UNCONFINED) {
- new_profile = aa_get_profile(ns->unconfined);
+ new_profile = labels_profile(aa_get_newest_label(&ns->unconfined->label));
info = "ux fallback";
} else {
- error = -ENOENT;
+ error = -EACCES;
info = "profile not found";
+ /* remove MAY_EXEC to audit as failure */
+ perms.allow &= ~MAY_EXEC;
}
}
} else if (COMPLAIN_MODE(profile)) {
- /* no exec permission - are we in learning mode */
+ /* no exec permission - learning mode. break rcu lock */
+ put_buffers(buffer);
+ name = NULL;
new_profile = aa_new_null_profile(profile, 0);
if (!new_profile) {
error = -ENOMEM;
@@ -456,6 +461,12 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
target = new_profile->base.hname;
}
perms.xindex |= AA_X_UNSAFE;
+ /* re-aquire buffer and rcu readlock and re-get name */
+ get_buffers(buffer);
+ if (!error)
+ error = aa_path_name(&bprm->file->f_path,
+ profile->path_flags, buffer,
+ &name, &info, profile->disconnected);
} else
/* fail exec */
error = -EACCES;
@@ -479,7 +490,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
}
if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
- error = may_change_ptraced_domain(current, new_profile);
+ error = may_change_ptraced_domain(new_profile, &info);
if (error) {
aa_put_profile(new_profile);
goto audit;
@@ -509,24 +520,20 @@ apply:
bprm->per_clear |= PER_CLEAR_ON_SETID;
x_clear:
- aa_put_profile(cxt->profile);
+ aa_put_label(cxt->label);
/* transfer new profile reference will be released when cxt is freed */
- cxt->profile = new_profile;
+ cxt->label = &new_profile->label;
/* clear out all temporary/transitional state from the context */
- aa_put_profile(cxt->previous);
- aa_put_profile(cxt->onexec);
- cxt->previous = NULL;
- cxt->onexec = NULL;
- cxt->token = 0;
+ aa_clear_task_cxt_trans(cxt);
audit:
- error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC,
- name, target, cond.uid, info, error);
+ error = aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target,
+ cond.uid, info, error);
cleanup:
- aa_put_profile(profile);
- kfree(buffer);
+ aa_put_label(label);
+ put_buffers(buffer);
return error;
}
@@ -550,36 +557,6 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm)
return ret;
}
-/**
- * apparmor_bprm_committing_creds - do task cleanup on committing new creds
- * @bprm: binprm for the exec (NOT NULL)
- */
-void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
-{
- struct aa_profile *profile = __aa_current_profile();
- struct aa_task_cxt *new_cxt = bprm->cred->security;
-
- /* bail out if unconfined or not changing profile */
- if ((new_cxt->profile == profile) ||
- (unconfined(new_cxt->profile)))
- return;
-
- current->pdeath_signal = 0;
-
- /* reset soft limits and set hard limits for the new profile */
- __aa_transition_rlimits(profile, new_cxt->profile);
-}
-
-/**
- * apparmor_bprm_commited_cred - do cleanup after new creds committed
- * @bprm: binprm for the exec (NOT NULL)
- */
-void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
-{
- /* TODO: cleanup signals - ipc mediation */
- return;
-}
-
/*
* Functions for self directed profile change
*/
@@ -617,7 +594,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
{
const struct cred *cred;
struct aa_task_cxt *cxt;
- struct aa_profile *profile, *previous_profile, *hat = NULL;
+ struct aa_label *label, *previous;
+ struct aa_profile *profile, *hat = NULL;
char *name = NULL;
int i;
struct file_perms perms = {};
@@ -629,16 +607,18 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
* There is no exception for unconfined as change_hat is not
* available.
*/
- if (current->no_new_privs)
+ if (task_no_new_privs(current))
return -EPERM;
/* released below */
cred = get_current_cred();
- cxt = cred->security;
- profile = aa_cred_profile(cred);
- previous_profile = cxt->previous;
+ cxt = cred_cxt(cred);
+ label = aa_get_newest_cred_label(cred);
+ previous = cxt->previous;
- if (unconfined(profile)) {
+ profile = labels_profile(label);
+
+ if (unconfined(label)) {
info = "unconfined";
error = -EPERM;
goto audit;
@@ -647,7 +627,10 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
if (count) {
/* attempting to change into a new hat or switch to a sibling */
struct aa_profile *root;
- root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
+ if (PROFILE_IS_HAT(profile))
+ root = aa_get_profile_rcu(&profile->parent);
+ else
+ root = aa_get_profile(labels_profile(label));
/* find first matching hat */
for (i = 0; i < count && !hat; i++)
@@ -659,6 +642,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
error = -ECHILD;
else
error = -ENOENT;
+ aa_put_profile(root);
goto out;
}
@@ -673,6 +657,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* freed below */
name = new_compound_name(root->base.hname, hats[0]);
+ aa_put_profile(root);
target = name;
/* released below */
hat = aa_new_null_profile(profile, 1);
@@ -682,6 +667,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
goto audit;
}
} else {
+ aa_put_profile(root);
target = hat->base.hname;
if (!PROFILE_IS_HAT(hat)) {
info = "target not hat";
@@ -690,15 +676,14 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
}
}
- error = may_change_ptraced_domain(current, hat);
+ error = may_change_ptraced_domain(hat, &info);
if (error) {
- info = "ptraced";
error = -EPERM;
goto audit;
}
if (!permtest) {
- error = aa_set_current_hat(hat, token);
+ error = aa_set_current_hat(&hat->label, token);
if (error == -EACCES)
/* kill task in case of brute force attacks */
perms.kill = AA_MAY_CHANGEHAT;
@@ -706,12 +691,12 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
/* reset error for learning of new hats */
error = -ENOENT;
}
- } else if (previous_profile) {
- /* Return to saved profile. Kill task if restore fails
+ } else if (previous) {
+ /* Return to saved label. Kill task if restore fails
* to avoid brute force attacks
*/
- target = previous_profile->base.hname;
- error = aa_restore_previous_profile(token);
+ target = previous->hname;
+ error = aa_restore_previous_label(token);
perms.kill = AA_MAY_CHANGEHAT;
} else
/* ignore restores when there is no saved profile */
@@ -719,12 +704,13 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
audit:
if (!permtest)
- error = aa_audit_file(profile, &perms, GFP_KERNEL,
- OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL,
- target, GLOBAL_ROOT_UID, info, error);
+ error = aa_audit_file(profile, &perms, OP_CHANGE_HAT,
+ AA_MAY_CHANGEHAT, NULL, target,
+ GLOBAL_ROOT_UID, info, error);
out:
aa_put_profile(hat);
+ aa_put_label(label);
kfree(name);
put_cred(cred);
@@ -750,7 +736,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
bool permtest)
{
const struct cred *cred;
- struct aa_task_cxt *cxt;
+ struct aa_label *label;
struct aa_profile *profile, *target = NULL;
struct aa_namespace *ns = NULL;
struct file_perms perms = {};
@@ -770,8 +756,8 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
}
cred = get_current_cred();
- cxt = cred->security;
- profile = aa_cred_profile(cred);
+ label = aa_get_newest_cred_label(cred);
+ profile = labels_profile(label);
/*
* Fail explicitly requested domain transitions if no_new_privs
@@ -780,7 +766,8 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
* no_new_privs is set because this aways results in a reduction
* of permissions.
*/
- if (current->no_new_privs && !unconfined(profile)) {
+ if (task_no_new_privs(current) && !unconfined(label)) {
+ aa_put_label(label);
put_cred(cred);
return -EPERM;
}
@@ -801,7 +788,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
/* if the name was not specified, use the name of the current profile */
if (!hname) {
- if (unconfined(profile))
+ if (profile_unconfined(profile))
hname = ns->unconfined->base.hname;
else
hname = profile->base.hname;
@@ -831,27 +818,33 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec,
}
/* check if tracing task is allowed to trace target domain */
- error = may_change_ptraced_domain(current, target);
- if (error) {
- info = "ptrace prevents transition";
+ error = may_change_ptraced_domain(target, &info);
+ if (error)
+ goto audit;
+
+ if (onexec && !current_is_single_threaded()) {
+ info = "not a single threaded task";
+ error = -EACCES;
goto audit;
}
+
if (permtest)
goto audit;
if (onexec)
- error = aa_set_current_onexec(target);
+ error = aa_set_current_onexec(&target->label);
else
- error = aa_replace_current_profile(target);
+ error = aa_replace_current_label(&target->label);
audit:
if (!permtest)
- error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request,
- name, hname, GLOBAL_ROOT_UID, info, error);
+ error = aa_audit_file(profile, &perms, op, request, name,
+ hname, GLOBAL_ROOT_UID, info, error);
aa_put_namespace(ns);
aa_put_profile(target);
+ aa_put_label(label);
put_cred(cred);
return error;
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index fdaa50c..7a82812 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -12,8 +12,14 @@
* License.
*/
+#include <linux/tty.h>
+#include <linux/fdtable.h>
+#include <linux/file.h>
+
+#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
+#include "include/context.h"
#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
@@ -22,6 +28,17 @@
struct file_perms nullperms;
+static u32 map_mask_to_chr_mask(u32 mask)
+{
+ u32 m = mask & PERMS_CHRS_MASK;
+ if (mask & AA_MAY_GETATTR)
+ m |= MAY_READ;
+ if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))
+ m |= MAY_WRITE;
+
+ return m;
+}
+
/**
* audit_file_mask - convert mask to permission string
* @buffer: buffer to write string to (NOT NULL)
@@ -31,29 +48,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask)
{
char str[10];
- char *m = str;
-
- if (mask & AA_EXEC_MMAP)
- *m++ = 'm';
- if (mask & (MAY_READ | AA_MAY_META_READ))
- *m++ = 'r';
- if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
- AA_MAY_CHOWN))
- *m++ = 'w';
- else if (mask & MAY_APPEND)
- *m++ = 'a';
- if (mask & AA_MAY_CREATE)
- *m++ = 'c';
- if (mask & AA_MAY_DELETE)
- *m++ = 'd';
- if (mask & AA_MAY_LINK)
- *m++ = 'l';
- if (mask & AA_MAY_LOCK)
- *m++ = 'k';
- if (mask & MAY_EXEC)
- *m++ = 'x';
- *m = '\0';
-
+ aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask));
audit_log_string(ab, str);
}
@@ -67,24 +62,24 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
kuid_t fsuid = current_fsuid();
- if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " requested_mask=");
- audit_file_mask(ab, sa->aad->fs.request);
+ audit_file_mask(ab, aad(sa)->request);
}
- if (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->denied & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " denied_mask=");
- audit_file_mask(ab, sa->aad->fs.denied);
+ audit_file_mask(ab, aad(sa)->denied);
}
- if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) {
+ if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " fsuid=%d",
from_kuid(&init_user_ns, fsuid));
audit_log_format(ab, " ouid=%d",
- from_kuid(&init_user_ns, sa->aad->fs.ouid));
+ from_kuid(&init_user_ns, aad(sa)->fs.ouid));
}
- if (sa->aad->fs.target) {
+ if (aad(sa)->target) {
audit_log_format(ab, " target=");
- audit_log_untrustedstring(ab, sa->aad->fs.target);
+ audit_log_untrustedstring(ab, aad(sa)->target);
}
}
@@ -92,7 +87,6 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* aa_audit_file - handle the auditing of file operations
* @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL)
- * @gfp: allocation flags
* @op: operation being mediated
* @request: permissions requested
* @name: name of object being mediated (MAYBE NULL)
@@ -104,53 +98,85 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
* Returns: %0 or error on failure
*/
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
- gfp_t gfp, int op, u32 request, const char *name,
- const char *target, kuid_t ouid, const char *info, int error)
+ int op, u32 request, const char *name, const char *target,
+ kuid_t ouid, const char *info, int error)
{
int type = AUDIT_APPARMOR_AUTO;
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.op = op,
- aad.fs.request = request;
- aad.name = name;
- aad.fs.target = target;
- aad.fs.ouid = ouid;
- aad.info = info;
- aad.error = error;
-
- if (likely(!sa.aad->error)) {
+
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
+ aad(&sa)->request = request;
+ aad(&sa)->name = name;
+ aad(&sa)->target = target;
+ aad(&sa)->fs.ouid = ouid;
+ aad(&sa)->info = info;
+ aad(&sa)->error = error;
+ sa.u.tsk = NULL;
+
+ if (likely(!aad(&sa)->error)) {
u32 mask = perms->audit;
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
mask = 0xffff;
/* mask off perms that are not being force audited */
- sa.aad->fs.request &= mask;
+ aad(&sa)->request &= mask;
- if (likely(!sa.aad->fs.request))
+ if (likely(!aad(&sa)->request))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else {
/* only report permissions that were denied */
- sa.aad->fs.request = sa.aad->fs.request & ~perms->allow;
+ aad(&sa)->request = aad(&sa)->request & ~perms->allow;
- if (sa.aad->fs.request & perms->kill)
+ if (aad(&sa)->request & perms->kill)
type = AUDIT_APPARMOR_KILL;
/* quiet known rejects, assumes quiet and kill do not overlap */
- if ((sa.aad->fs.request & perms->quiet) &&
+ if ((aad(&sa)->request & perms->quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
- sa.aad->fs.request &= ~perms->quiet;
+ aad(&sa)->request &= ~perms->quiet;
+
+ if (!aad(&sa)->request)
+ return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error;
+ }
+
+ aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
+ return aa_audit(type, profile, &sa, file_audit_cb);
+}
+
+/**
+ * is_deleted - test if a file has been completely unlinked
+ * @dentry: dentry of file to test for deletion (NOT NULL)
+ *
+ * Returns: %1 if deleted else %0
+ */
+static inline bool is_deleted(struct dentry *dentry)
+{
+ if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
+ return 1;
+ return 0;
+}
- if (!sa.aad->fs.request)
- return COMPLAIN_MODE(profile) ? 0 : sa.aad->error;
+static int path_name(int op, struct aa_label *label, struct path *path,
+ int flags, char *buffer, const char**name,
+ struct path_cond *cond, u32 request, bool delegate_deleted)
+{
+ struct aa_profile *profile;
+ const char *info = NULL;
+ int error = aa_path_name(path, flags, buffer, name, &info,
+ labels_profile(label)->disconnected);
+ if (error) {
+ if (error == -ENOENT && is_deleted(path->dentry) &&
+ delegate_deleted)
+ return 0;
+ fn_for_each_confined(label, profile,
+ aa_audit_file(profile, &nullperms, op, request, *name,
+ NULL, cond->uid, info, error));
+ return error;
}
- sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow;
- return aa_audit(type, profile, gfp, &sa, file_audit_cb);
+ return 0;
}
/**
@@ -163,10 +189,11 @@ static u32 map_old_perms(u32 old)
{
u32 new = old & 0xf;
if (old & MAY_READ)
- new |= AA_MAY_META_READ;
+ new |= AA_MAY_GETATTR | AA_MAY_OPEN;
if (old & MAY_WRITE)
- new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
- AA_MAY_CHMOD | AA_MAY_CHOWN;
+ new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
+ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN |
+ AA_MAY_DELETE;
if (old & 0x10)
new |= AA_MAY_LINK;
/* the old mapping lock and link_subset flags where overlaid
@@ -214,7 +241,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
perms.xindex = dfa_other_xindex(dfa, state);
}
- perms.allow |= AA_MAY_META_READ;
+ perms.allow |= AA_MAY_GETATTR;
/* change_profile wasn't determined by ownership in old mapping */
if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
@@ -251,23 +278,25 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
return state;
}
-/**
- * is_deleted - test if a file has been completely unlinked
- * @dentry: dentry of file to test for deletion (NOT NULL)
- *
- * Returns: %1 if deleted else %0
- */
-static inline bool is_deleted(struct dentry *dentry)
+int __aa_path_perm(int op, struct aa_profile *profile, const char *name,
+ u32 request, struct path_cond *cond, int flags,
+ struct file_perms *perms)
{
- if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
- return 1;
- return 0;
+ int e = 0;
+ if (profile_unconfined(profile) ||
+ ((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX)))
+ return 0;
+ aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms);
+ if (request & ~perms->allow)
+ e = -EACCES;
+ return aa_audit_file(profile, perms, op, request, name, NULL,
+ cond->uid, NULL, e);
}
/**
* aa_path_perm - do permissions check & audit for @path
* @op: operation being checked
- * @profile: profile being enforced (NOT NULL)
+ * @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies
* @request: requested permissions
@@ -275,35 +304,28 @@ static inline bool is_deleted(struct dentry *dentry)
*
* Returns: %0 else error if access denied or other error
*/
-int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
+int aa_path_perm(int op, struct aa_label *label, struct path *path,
int flags, u32 request, struct path_cond *cond)
{
- char *buffer = NULL;
struct file_perms perms = {};
- const char *name, *info = NULL;
+ char *buffer = NULL;
+ const char *name;
+ struct aa_profile *profile;
int error;
- flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
- error = aa_path_name(path, flags, &buffer, &name, &info);
- if (error) {
- if (error == -ENOENT && is_deleted(path->dentry)) {
- /* Access to open files that are deleted are
- * give a pass (implicit delegation)
- */
- error = 0;
- info = NULL;
- perms.allow = request;
- }
- } else {
- aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
- &perms);
- if (request & ~perms.allow)
- error = -EACCES;
- }
- error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name,
- NULL, cond->uid, info, error);
- kfree(buffer);
+ /* TODO: fix path lookup flags */
+ flags |= labels_profile(label)->path_flags |
+ (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
+ get_buffers(buffer);
+
+ error = path_name(op, label, path, flags, buffer, &name, cond,
+ request, true);
+ if (!error)
+ error = fn_for_each_confined(label, profile,
+ __aa_path_perm(op, profile, name, request, cond,
+ flags, &perms));
+ put_buffers(buffer);
return error;
}
@@ -327,65 +349,25 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return 1;
}
-/**
- * aa_path_link - Handle hard link permission check
- * @profile: the profile being enforced (NOT NULL)
- * @old_dentry: the target dentry (NOT NULL)
- * @new_dir: directory the new link will be created in (NOT NULL)
- * @new_dentry: the link being created (NOT NULL)
- *
- * Handle the permission test for a link & target pair. Permission
- * is encoded as a pair where the link permission is determined
- * first, and if allowed, the target is tested. The target test
- * is done from the point of the link match (not start of DFA)
- * making the target permission dependent on the link permission match.
- *
- * The subset test if required forces that permissions granted
- * on link are a subset of the permission granted to target.
- *
- * Returns: %0 if allowed else error
- */
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
- struct path *new_dir, struct dentry *new_dentry)
+static int profile_path_link(struct aa_profile *profile, const char *lname,
+ const char *tname, struct path_cond *cond)
{
- 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;
- const char *lname, *tname = NULL, *info = NULL;
struct file_perms lperms, perms;
+ const char *info = NULL;
u32 request = AA_MAY_LINK;
unsigned int state;
- int error;
-
- lperms = nullperms;
-
- /* buffer freed below, lname is pointer in buffer */
- error = aa_path_name(&link, profile->path_flags, &buffer, &lname,
- &info);
- if (error)
- goto audit;
-
- /* buffer2 freed below, tname is pointer in buffer2 */
- error = aa_path_name(&target, profile->path_flags, &buffer2, &tname,
- &info);
- if (error)
- goto audit;
+ int e = -EACCES;
- error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */
state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
- &cond, &lperms);
+ cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
state = aa_dfa_null_transition(profile->file.dfa, state);
- aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
+ aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry
* in the link pair.
@@ -403,10 +385,10 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
if (!(perms.allow & AA_LINK_SUBSET))
goto done_tests;
- /* Do link perm subset test requiring allowed permission on link are a
- * subset of the allowed permissions on target.
+ /* Do link perm subset test requiring allowed permission on link are
+ * a subset of the allowed permissions on target.
*/
- aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
+ aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
&perms);
/* AA_MAY_LINK is not considered in the subset test */
@@ -425,13 +407,175 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
}
done_tests:
- error = 0;
+ e = 0;
audit:
- error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
- lname, tname, cond.uid, info, error);
- kfree(buffer);
- kfree(buffer2);
+ return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
+ cond->uid, info, e);
+}
+
+/**
+ * aa_path_link - Handle hard link permission check
+ * @label: the label being enforced (NOT NULL)
+ * @old_dentry: the target dentry (NOT NULL)
+ * @new_dir: directory the new link will be created in (NOT NULL)
+ * @new_dentry: the link being created (NOT NULL)
+ *
+ * Handle the permission test for a link & target pair. Permission
+ * is encoded as a pair where the link permission is determined
+ * first, and if allowed, the target is tested. The target test
+ * is done from the point of the link match (not start of DFA)
+ * making the target permission dependent on the link permission match.
+ *
+ * The subset test if required forces that permissions granted
+ * on link are a subset of the permission granted to target.
+ *
+ * Returns: %0 if allowed else error
+ */
+int aa_path_link(struct aa_label *label, 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;
+ const char *lname, *tname = NULL;
+ struct aa_profile *profile;
+ int error;
+
+ /* TODO: fix path lookup flags, auditing of failed path for profile */
+ profile = labels_profile(label);
+ /* buffer freed below, lname is pointer in buffer */
+ get_buffers(buffer, buffer2);
+ error = path_name(OP_LINK, label, &link,
+ labels_profile(label)->path_flags, buffer,
+ &lname, &cond, AA_MAY_LINK, false);
+ if (error)
+ goto out;
+
+ /* buffer2 freed below, tname is pointer in buffer2 */
+ error = path_name(OP_LINK, label, &target,
+ labels_profile(label)->path_flags, buffer2, &tname,
+ &cond, AA_MAY_LINK, false);
+ if (error)
+ goto out;
+
+ error = fn_for_each_confined(label, profile,
+ profile_path_link(profile, lname, tname, &cond));
+
+out:
+ put_buffers(buffer, buffer2);
+
+ return error;
+}
+
+static void update_file_cxt(struct aa_file_cxt *fcxt, struct aa_label *label,
+ u32 request)
+{
+ struct aa_label *l, *old;
+
+ /* update caching of label on file_cxt */
+ spin_lock(&fcxt->lock);
+ old = rcu_dereference_protected(fcxt->label,
+ spin_is_locked(&fcxt->lock));
+ l = aa_label_merge(old, label, GFP_ATOMIC);
+ if (l) {
+ if (l != old) {
+ rcu_assign_pointer(fcxt->label, l);
+ aa_put_label(old);
+ } else
+ aa_put_label(l);
+ fcxt->allow |= request;
+ }
+ spin_unlock(&fcxt->lock);
+}
+
+static int __file_path_perm(int op, struct aa_label *label,
+ struct aa_label *flabel, struct file *file,
+ u32 request, u32 denied)
+{
+ struct aa_profile *profile;
+ struct file_perms perms = {};
+ struct path_cond cond = {
+ .uid = file_inode(file)->i_uid,
+ .mode = file_inode(file)->i_mode
+ };
+ const char *name;
+ char *buffer;
+ int flags, error;
+
+ /* revalidation due to label out of date. No revocation at this time */
+ if (!denied && aa_label_is_subset(flabel, label))
+ /* TODO: check for revocation on stale profiles */
+ return 0;
+
+ /* TODO: fix path lookup flags */
+ flags = PATH_DELEGATE_DELETED | labels_profile(label)->path_flags |
+ (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
+ get_buffers(buffer);
+
+ error = path_name(op, label, &file->f_path, flags, buffer, &name, &cond,
+ request, true);
+ if (error) {
+ if (error == 1)
+ /* Access to open files that are deleted are
+ * given a pass (implicit delegation)
+ */
+ /* TODO not needed when full perms cached */
+ error = 0;
+ goto out;
+ }
+
+ /* check every profile in task label not in current cache */
+ error = fn_for_each_not_in_set(flabel, label, profile,
+ __aa_path_perm(op, profile, name, request, &cond, 0,
+ &perms));
+ if (denied) {
+ /* check every profile in file label that was not tested
+ * in the initial check above.
+ */
+ /* TODO: cache full perms so this only happens because of
+ * conditionals */
+ /* TODO: don't audit here */
+ last_error(error,
+ fn_for_each_not_in_set(label, flabel, profile,
+ __aa_path_perm(op, profile, name, request,
+ &cond, 0, &perms)));
+ }
+ if (!error)
+ update_file_cxt(file_cxt(file), label, request);
+
+out:
+ put_buffers(buffer);
+
+ return error;
+}
+
+static int __file_sock_perm(int op, struct aa_label *label,
+ struct aa_label *flabel, struct file *file,
+ u32 request, u32 denied)
+{
+ struct socket *sock = (struct socket *) file->private_data;
+ int error;
+
+ AA_BUG(!sock);
+
+ /* revalidation due to label out of date. No revocation at this time */
+ if (!denied && aa_label_is_subset(flabel, label))
+ return 0;
+
+ /* TODO: improve to skip profiles cached in flabel */
+ error = aa_sock_file_perm(label, op, request, sock);
+ if (denied) {
+ /* TODO: improve to skip profiles checked above */
+ /* check every profile in file label to is cached */
+ last_error(error, aa_sock_file_perm(flabel, op, request, sock));
+ }
+ if (!error)
+ update_file_cxt(file_cxt(file), label, request);
return error;
}
@@ -439,20 +583,117 @@ audit:
/**
* aa_file_perm - do permission revalidation check & audit for @file
* @op: operation being checked
- * @profile: profile being enforced (NOT NULL)
+ * @label: label being enforced (NOT NULL)
* @file: file to revalidate access permissions on (NOT NULL)
* @request: requested permissions
*
* Returns: %0 if access allowed else error
*/
-int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
+int aa_file_perm(int op, struct aa_label *label, struct file *file,
u32 request)
{
- struct path_cond cond = {
- .uid = file_inode(file)->i_uid,
- .mode = file_inode(file)->i_mode
- };
+ struct aa_file_cxt *fcxt;
+ struct aa_label *flabel;
+ u32 denied;
+ int error = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!file);
+
+ fcxt = file_cxt(file);
+
+ rcu_read_lock();
+ flabel = rcu_dereference(fcxt->label);
+ AA_BUG(!flabel);
+
+ /* revalidate access, if task is unconfined, or the cached cred
+ * doesn't match or if the request is for more permissions than
+ * was granted.
+ *
+ * Note: the test for !unconfined(flabel) is to handle file
+ * delegation from unconfined tasks
+ */
+ denied = request & ~fcxt->allow;
+ if (unconfined(label) || unconfined(flabel) ||
+ (!denied && aa_label_is_subset(flabel, label)))
+ goto done;
+
+ /* TODO: label cross check */
+
+ if (file->f_path.mnt && path_mediated_fs(file_inode(file))) {
+ error = __file_path_perm(op, label, flabel, file, request,
+ denied);
+
+ } else if (S_ISSOCK(file_inode(file)->i_mode)) {
+ error = __file_sock_perm(op, label, flabel, file, request,
+ denied);
+ }
+done:
+ rcu_read_unlock();
+
+ return error;
+}
- return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
- request, &cond);
+static void revalidate_tty(struct aa_label *label)
+{
+ struct tty_struct *tty;
+ int drop_tty = 0;
+
+ tty = get_current_tty();
+ if (!tty)
+ return;
+
+ spin_lock(&tty_files_lock);
+ if (!list_empty(&tty->tty_files)) {
+ struct tty_file_private *file_priv;
+ struct file *file;
+ /* TODO: Revalidate access to controlling tty. */
+ file_priv = list_first_entry(&tty->tty_files,
+ struct tty_file_private, list);
+ file = file_priv->file;
+
+ if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE))
+ drop_tty = 1;
+ }
+ spin_unlock(&tty_files_lock);
+ tty_kref_put(tty);
+
+ if (drop_tty)
+ no_tty();
+}
+
+static int match_file(const void *p, struct file *file, unsigned fd)
+{
+ struct aa_label *label = (struct aa_label *)p;
+ if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file)))
+ return fd + 1;
+ return 0;
+}
+
+
+/* based on selinux's flush_unauthorized_files */
+void aa_inherit_files(const struct cred *cred, struct files_struct *files)
+{
+ struct aa_label *label = aa_get_newest_cred_label(cred);
+ struct file *devnull = NULL;
+ unsigned n;
+
+ revalidate_tty(label);
+
+ /* Revalidate access to inherited open files. */
+ n = iterate_fd(files, 0, match_file, label);
+ if (!n) /* none found? */
+ goto out;
+
+ devnull = dentry_open(&aa_null, O_RDWR, cred);
+ if (IS_ERR(devnull))
+ devnull = NULL;
+ /* replace all the matching ones with this */
+ do {
+ replace_fd(n - 1, devnull, 0);
+ } while ((n = iterate_fd(files, n, match_file, label)) != 0);
+ if (devnull)
+ fput(devnull);
+out:
+ aa_put_label(label);
}
diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h
new file mode 100644
index 0000000..24902b3
--- /dev/null
+++ b/security/apparmor/include/af_unix.h
@@ -0,0 +1,121 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor af_unix fine grained mediation
+ *
+ * Copyright 2014 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_AF_UNIX_H
+
+#include <net/af_unix.h>
+
+#include "label.h"
+//#include "include/net.h"
+
+#define unix_addr_len(L) ((L) - sizeof(sa_family_t))
+#define unix_abstract_name_len(L) (unix_addr_len(L) - 1)
+#define unix_abstract_len(U) (unix_abstract_name_len((U)->addr->len))
+#define addr_unix_abstract_name(B) ((B)[0] == 0)
+#define addr_unix_anonymous(U) (addr_unix_len(U) <= 0)
+#define addr_unix_abstract(U) (!addr_unix_anonymous(U) && addr_unix_abstract_name((U)->addr))
+//#define unix_addr_fs(U) (!unix_addr_anonymous(U) && !unix_addr_abstract_name((U)->addr))
+
+#define unix_addr(A) ((struct sockaddr_un *)(A))
+#define unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0)
+#define unix_addr_fs(A, L) (!unix_addr_anon(A, L) && unix_addr(A)->sun_path[0] == '/')
+
+#define UNIX_ANONYMOUS(U) (!unix_sk(U)->addr)
+/* from net/unix/af_unix.c */
+#define UNIX_ABSTRACT(U) (!UNIX_ANONYMOUS(U) && \
+ unix_sk(U)->addr->hash < UNIX_HASH_SIZE)
+#define UNIX_FS(U) (!UNIX_ANONYMOUS(U) && unix_sk(U)->addr->name->sun_path[0])
+#define unix_peer(sk) (unix_sk(sk)->peer)
+#define unix_connected(S) ((S)->state == SS_CONNECTED)
+
+static inline void print_unix_addr(struct sockaddr_un *A, int L)
+{
+ char *buf = (A) ? (char *) &(A)->sun_path : NULL;
+ int len = unix_addr_len(L);
+ if (!buf || len <= 0)
+ printk(" <anonymous>");
+ else if (buf[0])
+ printk(" %s", buf);
+ else
+ /* abstract name len includes leading \0 */
+ printk(" %d @%.*s", len - 1, len - 1, buf+1);
+};
+
+/*
+ printk("%s: %s: f %d, t %d, p %d", __FUNCTION__, \
+ #SK , \
+*/
+#define print_unix_sk(SK) \
+do { \
+ struct unix_sock *u = unix_sk(SK); \
+ printk("%s: f %d, t %d, p %d", #SK , \
+ (SK)->sk_family, (SK)->sk_type, (SK)->sk_protocol); \
+ if (u->addr) \
+ print_unix_addr(u->addr->name, u->addr->len); \
+ else \
+ print_unix_addr(NULL, sizeof(sa_family_t)); \
+ /* printk("\n");*/ \
+} while (0)
+
+#define print_sk(SK) \
+do { \
+ if (!(SK)) { \
+ printk("%s: %s is null\n", __FUNCTION__, #SK); \
+ } else if ((SK)->sk_family == PF_UNIX) { \
+ print_unix_sk(SK); \
+ printk("\n"); \
+ } else { \
+ printk("%s: %s: family %d\n", __FUNCTION__, #SK , \
+ (SK)->sk_family); \
+ } \
+} while (0)
+
+#define print_sock_addr(U) \
+do { \
+ printk("%s:\n", __FUNCTION__); \
+ printk(" sock %s:", sock_cxt && sock_cxt->label && sock_cxt->label->hname ? sock_cxt->label->hname : "<null>"); print_sk(sock); \
+ printk(" other %s:", other_cxt && other_cxt->label && other_cxt->label->hname ? other_cxt->label->hname : "<null>"); print_sk(other); \
+ printk(" new %s", new_cxt && new_cxt->label && new_cxt->label->hname ? new_cxt->label->hname : "<null>"); print_sk(newsk); \
+} while (0)
+
+
+#define DEFINE_AUDIT_UNIX(NAME, OP, SK, T, P) \
+ struct lsm_network_audit NAME ## _net = { .sk = (SK), \
+ .family = (AF_UNIX)}; \
+ DEFINE_AUDIT_DATA(NAME, LSM_AUDIT_DATA_NONE, OP); \
+ NAME.u.net = &(NAME ## _net); \
+ aad(&NAME)->net.type = (T); \
+ aad(&NAME)->net.protocol = (P)
+
+
+int aa_unix_peer_perm(struct aa_label *label, int op, u32 request,
+ struct sock *sk, struct sock *peer_sk,
+ struct aa_label *peer_label);
+int aa_unix_label_sk_perm(struct aa_label *label, int op, u32 request,
+ struct sock *sk);
+int aa_unix_sock_perm(int op, u32 request, struct socket *sock);
+int aa_unix_create_perm(struct aa_label *label, int family, int type,
+ int protocol);
+int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_unix_listen_perm(struct socket *sock, int backlog);
+int aa_unix_accept_perm(struct socket *sock, struct socket *newsock);
+int aa_unix_msg_perm(int op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size);
+int aa_unix_opt_perm(int op, u32 request, struct socket *sock, int level,
+ int optname);
+int aa_unix_file_perm(struct aa_label *label, int op, u32 request,
+ struct socket *sock);
+
+#endif /* __AA_AF_UNIX_H */
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index 40aedd9..a59a330 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -4,7 +4,7 @@
* This file contains AppArmor basic global and lib definitions
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2013 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
@@ -15,10 +15,22 @@
#ifndef __APPARMOR_H
#define __APPARMOR_H
+#include <linux/slab.h>
#include <linux/fs.h>
#include "match.h"
+/* Provide our own test for whether a write lock is held for asserts
+ * this is because on none SMP systems write_can_lock will always
+ * resolve to true, which is what you want for code making decisions
+ * based on it, but wrong for asserts checking that the lock is held
+ */
+#ifdef CONFIG_SMP
+#define write_is_locked(X) !write_can_lock(X)
+#else
+#define write_is_locked(X) (1)
+#endif /* CONFIG_SMP */
+
/*
* Class of mediation types in the AppArmor policy db
*/
@@ -29,8 +41,12 @@
#define AA_CLASS_NET 4
#define AA_CLASS_RLIMITS 5
#define AA_CLASS_DOMAIN 6
+#define AA_CLASS_MOUNT 7
+#define AA_CLASS_PTRACE 9
+#define AA_CLASS_SIGNAL 10
+#define AA_CLASS_LABEL 16
-#define AA_CLASS_LAST AA_CLASS_DOMAIN
+#define AA_CLASS_LAST AA_CLASS_LABEL
/* Control parameters settable through module/boot flags */
extern enum audit_mode aa_g_audit;
@@ -40,6 +56,7 @@ extern bool aa_g_lock_policy;
extern bool aa_g_logsyscall;
extern bool aa_g_paranoid_load;
extern unsigned int aa_g_path_max;
+extern bool aa_g_unconfined_init;
/*
* DEBUG remains global (no per profile flag) since it is mostly used in sysctl
@@ -52,6 +69,12 @@ extern unsigned int aa_g_path_max;
printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
} while (0)
+#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __FUNCTION__, #X)
+
+#define AA_BUG(X, args...) AA_BUG_FMT((X), "" args )
+#define AA_BUG_FMT(X, fmt, args...) \
+ WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __FUNCTION__ , ##args )
+
#define AA_ERROR(fmt, args...) \
do { \
if (printk_ratelimit()) \
@@ -63,10 +86,25 @@ extern int apparmor_initialized __initdata;
/* fn's in lib */
char *aa_split_fqname(char *args, char **ns_name);
+char *aa_splitn_fqname(char *fqname, size_t n, char **ns_name, size_t *ns_len);
void aa_info_message(const char *str);
-void *kvmalloc(size_t size);
-void kvfree(void *buffer);
+void *__aa_kvmalloc(size_t size, gfp_t flags);
+static inline void *kvmalloc(size_t size)
+{
+ return __aa_kvmalloc(size, 0);
+}
+
+static inline void *kvzalloc(size_t size)
+{
+ return __aa_kvmalloc(size, __GFP_ZERO);
+}
+
+/* returns 0 if kref not incremented */
+static inline int kref_get_not0(struct kref *kref)
+{
+ return atomic_inc_not_zero(&kref->refcount);
+}
/**
* aa_strneq - compare null terminated @str to a non null terminated substring
@@ -97,9 +135,40 @@ static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
return aa_dfa_next(dfa, start, 0);
}
-static inline bool mediated_filesystem(struct inode *inode)
+static inline bool path_mediated_fs(struct inode *inode)
{
return !(inode->i_sb->s_flags & MS_NOUSER);
}
+
+struct counted_str {
+ struct kref count;
+ char name[];
+};
+
+#define str_to_counted(str) \
+ ((struct counted_str *)(str - offsetof(struct counted_str,name)))
+
+#define __counted /* atm just a notation */
+
+void aa_str_kref(struct kref *kref);
+char *aa_str_alloc(int size, gfp_t gfp);
+
+
+static inline __counted char *aa_get_str(__counted char *str)
+{
+ if (str)
+ kref_get(&(str_to_counted(str)->count));
+
+ return str;
+}
+
+static inline void aa_put_str(__counted char *str)
+{
+ if (str)
+ kref_put(&str_to_counted(str)->count, aa_str_kref);
+}
+
+const char *aa_imode_name(umode_t mode);
+
#endif /* __APPARMOR_H */
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 7ea4769..0ce350d 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -15,6 +15,8 @@
#ifndef __AA_APPARMORFS_H
#define __AA_APPARMORFS_H
+extern struct path aa_null;
+
enum aa_fs_type {
AA_FS_TYPE_BOOLEAN,
AA_FS_TYPE_STRING,
@@ -61,4 +63,44 @@ extern const struct file_operations aa_fs_seq_file_ops;
extern void __init aa_destroy_aafs(void);
+struct aa_profile;
+struct aa_namespace;
+
+enum aafs_ns_type {
+ AAFS_NS_DIR,
+ AAFS_NS_PROFS,
+ AAFS_NS_NS,
+ AAFS_NS_COUNT,
+ AAFS_NS_MAX_COUNT,
+ AAFS_NS_SIZE,
+ AAFS_NS_MAX_SIZE,
+ AAFS_NS_OWNER,
+ AAFS_NS_SIZEOF,
+};
+
+enum aafs_prof_type {
+ AAFS_PROF_DIR,
+ AAFS_PROF_PROFS,
+ AAFS_PROF_NAME,
+ AAFS_PROF_MODE,
+ AAFS_PROF_ATTACH,
+ AAFS_PROF_HASH,
+ AAFS_PROF_SIZEOF,
+};
+
+#define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
+#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
+#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
+
+#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
+#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
+
+void __aa_fs_profile_rmdir(struct aa_profile *profile);
+void __aa_fs_profile_migrate_dents(struct aa_profile *old,
+ struct aa_profile *new);
+int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
+void __aa_fs_namespace_rmdir(struct aa_namespace *ns);
+int __aa_fs_namespace_mkdir(struct aa_namespace *ns, struct dentry *parent,
+ const char *name);
+
#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index 69d8cae..b0e5074 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -22,12 +22,10 @@
#include <linux/slab.h>
#include "file.h"
-
-struct aa_profile;
+#include "label.h"
extern const char *const audit_mode_names[];
#define AUDIT_MAX_INDEX 5
-
enum audit_mode {
AUDIT_NORMAL, /* follow normal auditing of accesses */
AUDIT_QUIET_DENIED, /* quiet all denied access messages */
@@ -68,10 +66,16 @@ enum aa_ops {
OP_GETATTR,
OP_OPEN,
+ OP_FRECEIVE,
OP_FPERM,
OP_FLOCK,
OP_FMMAP,
OP_FMPROT,
+ OP_INHERIT,
+
+ OP_PIVOTROOT,
+ OP_MOUNT,
+ OP_UMOUNT,
OP_CREATE,
OP_POST_CREATE,
@@ -85,9 +89,10 @@ enum aa_ops {
OP_GETPEERNAME,
OP_GETSOCKOPT,
OP_SETSOCKOPT,
- OP_SOCK_SHUTDOWN,
+ OP_SHUTDOWN,
OP_PTRACE,
+ OP_SIGNAL,
OP_EXEC,
OP_CHANGE_HAT,
@@ -107,38 +112,69 @@ struct apparmor_audit_data {
int error;
int op;
int type;
- void *profile;
+ struct aa_label *label;
const char *name;
const char *info;
- struct task_struct *tsk;
+ u32 request;
+ u32 denied;
union {
- void *target;
struct {
- long pos;
- void *target;
- } iface;
+ const void *target;
+ union {
+ struct {
+ long pos;
+ } iface;
+ struct {
+ kuid_t ouid;
+ } fs;
+ struct {
+ int type, protocol;
+ struct sock *peer_sk;
+ void *addr;
+ int addrlen;
+ } net;
+ int signal;
+ };
+ };
struct {
int rlim;
unsigned long max;
} rlim;
struct {
- const char *target;
- u32 request;
- u32 denied;
- kuid_t ouid;
- } fs;
+ const char *src_name;
+ const char *type;
+ const char *trans;
+ const char *data;
+ unsigned long flags;
+ } mnt;
};
};
-/* define a short hand for apparmor_audit_data structure */
-#define aad apparmor_audit_data
+/* macros for dealing with apparmor_audit_data structure */
+#define aad(SA) (SA)->apparmor_audit_data
+#define DEFINE_AUDIT_DATA(NAME, T, X) \
+ /* TODO: cleanup audit init so we don't need _aad = {0,} */ \
+ struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \
+ struct common_audit_data NAME = \
+ { \
+ .type = (T), \
+ .u.tsk = NULL, \
+ }; \
+ NAME.apparmor_audit_data = &(NAME ## _aad)
void aa_audit_msg(int type, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *));
-int aa_audit(int type, struct aa_profile *profile, gfp_t gfp,
- struct common_audit_data *sa,
+int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
void (*cb) (struct audit_buffer *, void *));
+#define aa_audit_error(ERROR, SA, CB) \
+({ \
+ aad((SA))->error = (ERROR); \
+ aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \
+ aad((SA))->error; \
+})
+
+
static inline int complain_error(int error)
{
if (error == -EPERM || error == -EACCES)
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
index c24d295..e01d97f 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -4,7 +4,7 @@
* This file contains AppArmor capability mediation definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2013 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
@@ -17,11 +17,14 @@
#include <linux/sched.h>
-struct aa_profile;
+#include "apparmorfs.h"
+
+struct aa_label;
/* aa_caps - confinement data for capabilities
* @allowed: capabilities mask
* @audit: caps that are to be audited
+ * @denied: caps that are explicitly denied
* @quiet: caps that should not be audited
* @kill: caps that when requested will result in the task being killed
* @extended: caps that are subject finer grained mediation
@@ -29,13 +32,15 @@ struct aa_profile;
struct aa_caps {
kernel_cap_t allow;
kernel_cap_t audit;
+ kernel_cap_t denied;
kernel_cap_t quiet;
kernel_cap_t kill;
kernel_cap_t extended;
};
-int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
- int audit);
+extern struct aa_fs_entry aa_fs_entry_caps[];
+
+int aa_capable(struct aa_label *label, int cap, int audit);
static inline void aa_free_cap_rules(struct aa_caps *caps)
{
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h
index a9cbee4..a8f3ef6d 100644
--- a/security/apparmor/include/context.h
+++ b/security/apparmor/include/context.h
@@ -19,67 +19,81 @@
#include <linux/slab.h>
#include <linux/sched.h>
+#include "label.h"
#include "policy.h"
-/* struct aa_file_cxt - the AppArmor context the file was opened in
- * @perms: the permission the file was opened with
+#define cred_cxt(X) (X)->security
+#define current_cxt() cred_cxt(current_cred())
+#define current_ns() labels_ns(aa_current_raw_label())
+
+/**
+ * struct aa_task_cxt - primary label for confined tasks
+ * @label: the current label (NOT NULL)
+ * @exec: label to transition to on next exec (MAYBE NULL)
+ * @previous: label the task may return to (MAYBE NULL)
+ * @token: magic value the task must know for returning to @previous
*
- * The file_cxt could currently be directly stored in file->f_security
- * as the profile reference is now stored in the f_cred. However the
- * cxt struct will expand in the future so we keep the struct.
+ * Contains the task's current label (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ *
+ * TODO: make so a task can be confined by a stack of contexts
*/
-struct aa_file_cxt {
- u16 allow;
+struct aa_task_cxt {
+ struct aa_label *label;
+ struct aa_label *onexec;
+ struct aa_label *previous;
+ u64 token;
};
+struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
+void aa_free_task_context(struct aa_task_cxt *cxt);
+void aa_dup_task_context(struct aa_task_cxt *new,
+ const struct aa_task_cxt *old);
+int aa_replace_current_label(struct aa_label *label);
+int aa_set_current_onexec(struct aa_label *label);
+int aa_set_current_hat(struct aa_label *label, u64 token);
+int aa_restore_previous_label(u64 cookie);
+struct aa_label *aa_get_task_label(struct task_struct *task);
+
+
/**
- * aa_alloc_file_context - allocate file_cxt
- * @gfp: gfp flags for allocation
+ * aa_cred_raw_label - obtain cred's label
+ * @cred: cred to obtain label from (NOT NULL)
*
- * Returns: file_cxt or NULL on failure
+ * Returns: confining label
+ *
+ * does NOT increment reference count
*/
-static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
+static inline struct aa_label *aa_cred_raw_label(const struct cred *cred)
{
- return kzalloc(sizeof(struct aa_file_cxt), gfp);
+ struct aa_task_cxt *cxt = cred_cxt(cred);
+ BUG_ON(!cxt || !cxt->label);
+ return cxt->label;
}
/**
- * aa_free_file_context - free a file_cxt
- * @cxt: file_cxt to free (MAYBE_NULL)
+ * aa_get_newest_cred_label - obtain the newest version of the label on a cred
+ * @cred: cred to obtain label from (NOT NULL)
+ *
+ * Returns: newest version of confining label
*/
-static inline void aa_free_file_context(struct aa_file_cxt *cxt)
+static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
{
- if (cxt)
- kzfree(cxt);
+ return aa_get_newest_label(aa_cred_raw_label(cred));
}
/**
- * struct aa_task_cxt - primary label for confined tasks
- * @profile: the current profile (NOT NULL)
- * @exec: profile to transition to on next exec (MAYBE NULL)
- * @previous: profile the task may return to (MAYBE NULL)
- * @token: magic value the task must know for returning to @previous_profile
+ * __aa_task_raw_label - retrieve another task's label
+ * @task: task to query (NOT NULL)
*
- * Contains the task's current profile (which could change due to
- * change_hat). Plus the hat_magic needed during change_hat.
+ * Returns: @task's label without incrementing its ref count
*
- * TODO: make so a task can be confined by a stack of contexts
+ * If @task != current needs to be called in RCU safe critical section
*/
-struct aa_task_cxt {
- struct aa_profile *profile;
- struct aa_profile *onexec;
- struct aa_profile *previous;
- u64 token;
-};
-
-struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
-void aa_free_task_context(struct aa_task_cxt *cxt);
-void aa_dup_task_context(struct aa_task_cxt *new,
- const struct aa_task_cxt *old);
-int aa_replace_current_profile(struct aa_profile *profile);
-int aa_set_current_onexec(struct aa_profile *profile);
-int aa_set_current_hat(struct aa_profile *profile, u64 token);
-int aa_restore_previous_profile(u64 cookie);
+static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
+{
+ return aa_cred_raw_label(__task_cred(task));
+}
/**
* __aa_task_is_confined - determine if @task has any confinement
@@ -89,66 +103,106 @@ int aa_restore_previous_profile(u64 cookie);
*/
static inline bool __aa_task_is_confined(struct task_struct *task)
{
- struct aa_task_cxt *cxt = __task_cred(task)->security;
-
- BUG_ON(!cxt || !cxt->profile);
- if (unconfined(aa_newest_version(cxt->profile)))
- return 0;
+ return !unconfined(__aa_task_raw_label(task));
+}
- return 1;
+/**
+ * aa_current_raw_label - find the current tasks confining label
+ *
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL)
+ *
+ * This fn will not update the tasks cred to the most up to date version
+ * of the label so it is safe to call when inside of locks.
+ */
+static inline struct aa_label *aa_current_raw_label(void)
+{
+ return aa_cred_raw_label(current_cred());
}
/**
- * aa_cred_profile - obtain cred's profiles
- * @cred: cred to obtain profiles from (NOT NULL)
+ * aa_get_current_label - get the newest version of the current tasks label
*
- * Returns: confining profile
+ * Returns: newest version of confining label (NOT NULL)
*
- * does NOT increment reference count
+ * This fn will not update the tasks cred, so it is safe inside of locks
+ *
+ * The returned reference must be put with aa_put_label()
*/
-static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
+static inline struct aa_label *aa_get_current_label(void)
{
- struct aa_task_cxt *cxt = cred->security;
- BUG_ON(!cxt || !cxt->profile);
- return aa_newest_version(cxt->profile);
+ struct aa_label *l = aa_current_raw_label();
+
+ if (label_invalid(l))
+ return aa_get_newest_label(l);
+ return aa_get_label(l);
}
/**
- * __aa_current_profile - find the current tasks confining profile
+ * aa_begin_current_label - find newest version of the current tasks label
*
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
+ * Returns: newest version of confining label (NOT NULL)
*
- * This fn will not update the tasks cred to the most up to date version
- * of the profile so it is safe to call when inside of locks.
+ * This fn will not update the tasks cred, so it is safe inside of locks
+ *
+ * The returned reference must be put with aa_end_current_label()
+ */
+static inline struct aa_label *aa_begin_current_label(void)
+{
+ struct aa_label *l = aa_current_raw_label();
+
+ if (label_invalid(l))
+ l = aa_get_newest_label(l);
+ return l;
+}
+
+/**
+ * aa_end_current_label - put a reference found with aa_begin_current_label
+ * @label: label reference to put
+ *
+ * Should only be used with a reference obtained with aa_begin_current_label
+ * and never used in situations where the task cred may be updated
*/
-static inline struct aa_profile *__aa_current_profile(void)
+static inline void aa_end_current_label(struct aa_label *label)
{
- return aa_cred_profile(current_cred());
+ if (label != aa_current_raw_label())
+ aa_put_label(label);
}
/**
- * aa_current_profile - find the current tasks confining profile and do updates
+ * aa_current_label - find the current tasks confining label and update it
*
- * Returns: up to date confining profile or the ns unconfined profile (NOT NULL)
+ * Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
- * This fn will update the tasks cred structure if the profile has been
+ * This fn will update the tasks cred structure if the label has been
* replaced. Not safe to call inside locks
*/
-static inline struct aa_profile *aa_current_profile(void)
+static inline struct aa_label *aa_current_label(void)
+{
+ const struct aa_task_cxt *cxt = current_cxt();
+ struct aa_label *label;
+ BUG_ON(!cxt || !cxt->label);
+
+ if (label_invalid(cxt->label)) {
+ label = aa_get_newest_label(cxt->label);
+ aa_replace_current_label(label);
+ aa_put_label(label);
+ cxt = current_cxt();
+ }
+
+ return cxt->label;
+}
+
+/**
+ * aa_clear_task_cxt_trans - clear transition tracking info from the cxt
+ * @cxt: task context to clear (NOT NULL)
+ */
+static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt)
{
- const struct aa_task_cxt *cxt = current_cred()->security;
- struct aa_profile *profile;
- BUG_ON(!cxt || !cxt->profile);
-
- profile = aa_newest_version(cxt->profile);
- /*
- * Whether or not replacement succeeds, use newest profile so
- * there is no need to update it after replacement.
- */
- if (unlikely((cxt->profile != profile)))
- aa_replace_current_profile(profile);
-
- return profile;
+ aa_put_label(cxt->previous);
+ aa_put_label(cxt->onexec);
+ cxt->previous = NULL;
+ cxt->onexec = NULL;
+ cxt->token = 0;
}
#endif /* __AA_CONTEXT_H */
diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h
new file mode 100644
index 0000000..dc418e5
--- /dev/null
+++ b/security/apparmor/include/crypto.h
@@ -0,0 +1,36 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy loading interface function definitions.
+ *
+ * Copyright 2013 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_CRYPTO_H
+#define __APPARMOR_CRYPTO_H
+
+#include "policy.h"
+
+#ifdef CONFIG_SECURITY_APPARMOR_HASH
+unsigned int aa_hash_size(void);
+int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
+ size_t len);
+#else
+static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
+ void *start, size_t len)
+{
+ return 0;
+}
+
+static inline unsigned int aa_hash_size(void)
+{
+ return 0;
+}
+#endif
+
+#endif /* __APPARMOR_CRYPTO_H */
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/file.h b/security/apparmor/include/file.h
index 967b2de..83f7000 100644
--- a/security/apparmor/include/file.h
+++ b/security/apparmor/include/file.h
@@ -15,38 +15,75 @@
#ifndef __AA_FILE_H
#define __AA_FILE_H
+#include <linux/spinlock.h>
+
#include "domain.h"
#include "match.h"
+#include "label.h"
+#include "perms.h"
struct aa_profile;
struct path;
-/*
- * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
- * for profile permissions
- */
-#define AA_MAY_CREATE 0x0010
-#define AA_MAY_DELETE 0x0020
-#define AA_MAY_META_WRITE 0x0040
-#define AA_MAY_META_READ 0x0080
-
-#define AA_MAY_CHMOD 0x0100
-#define AA_MAY_CHOWN 0x0200
-#define AA_MAY_LOCK 0x0400
-#define AA_EXEC_MMAP 0x0800
-
-#define AA_MAY_LINK 0x1000
-#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
-#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */
-#define AA_MAY_CHANGE_PROFILE 0x80000000
-#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */
+#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND))
#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
AA_MAY_CREATE | AA_MAY_DELETE | \
- AA_MAY_META_READ | AA_MAY_META_WRITE | \
+ AA_MAY_GETATTR | AA_MAY_SETATTR | \
AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \
AA_EXEC_MMAP | AA_MAY_LINK)
+#define file_cxt(X) ((struct aa_file_cxt *)(X)->f_security)
+
+/* struct aa_file_cxt - the AppArmor context the file was opened in
+ * @lock: lock to update the cxt
+ * @label: label currently cached on the cxt
+ * @perms: the permission the file was opened with
+ */
+struct aa_file_cxt {
+ spinlock_t lock;
+ struct aa_label __rcu *label;
+ u32 allow;
+};
+
+/**
+ * aa_alloc_file_cxt - allocate file_cxt
+ * @label: initial label of task creating the file
+ * @gfp: gfp flags for allocation
+ *
+ * Returns: file_cxt or NULL on failure
+ */
+static inline struct aa_file_cxt *aa_alloc_file_cxt(struct aa_label *label, gfp_t gfp)
+{
+ struct aa_file_cxt *cxt;
+
+ cxt = kzalloc(sizeof(struct aa_file_cxt), gfp);
+ if (cxt) {
+ spin_lock_init(&cxt->lock);
+ rcu_assign_pointer(cxt->label, aa_get_label(label));
+ }
+ return cxt;
+}
+
+/**
+ * aa_free_file_cxt - free a file_cxt
+ * @cxt: file_cxt to free (MAYBE_NULL)
+ */
+static inline void aa_free_file_cxt(struct aa_file_cxt *cxt)
+{
+ if (cxt) {
+ aa_put_label(rcu_access_pointer(cxt->label));
+ kzfree(cxt);
+ }
+}
+
+static inline struct aa_label *aa_get_file_label(struct aa_file_cxt *cxt)
+{
+ return aa_get_label_rcu(&cxt->label);
+}
+
+#define inode_cxt(X) (X)->i_security
+
/*
* The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table
@@ -145,8 +182,8 @@ static inline u16 dfa_map_xindex(u16 mask)
dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
- gfp_t gfp, int op, u32 request, const char *name,
- const char *target, kuid_t ouid, const char *info, int error);
+ int op, u32 request, const char *name, const char *target,
+ kuid_t ouid, const char *info, int error);
/**
* struct aa_file_rules - components used for file rule permissions
@@ -171,26 +208,26 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond,
struct file_perms *perms);
-int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
+int __aa_path_perm(int op, struct aa_profile *profile, const char *name,
+ u32 request, struct path_cond *cond, int flags,
+ struct file_perms *perms);
+int aa_path_perm(int op, struct aa_label *label, struct path *path,
int flags, u32 request, struct path_cond *cond);
-int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
+int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry);
-int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
+int aa_file_perm(int op, struct aa_label *label, struct file *file,
u32 request);
+void aa_inherit_files(const struct cred *cred, struct files_struct *files);
+
static inline void aa_free_file_rules(struct aa_file_rules *rules)
{
aa_put_dfa(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 MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x))
-
/**
* aa_map_file_perms - map file flags to AppArmor permissions
* @file: open file to map flags to AppArmor permissions
@@ -199,8 +236,13 @@ static inline void aa_free_file_rules(struct aa_file_rules *rules)
*/
static inline u32 aa_map_file_to_perms(struct file *file)
{
- int flags = MAP_OPEN_FLAGS(file->f_flags);
- u32 perms = ACC_FMODE(file->f_mode);
+ int flags = file->f_flags;
+ u32 perms = 0;
+
+ if (file->f_mode & FMODE_WRITE)
+ perms |= MAY_WRITE;
+ if (file->f_mode & FMODE_READ)
+ perms |= MAY_READ;
if ((flags & O_APPEND) && (perms & MAY_WRITE))
perms = (perms & ~MAY_WRITE) | MAY_APPEND;
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
index aeda0fbc..a786685 100644
--- a/security/apparmor/include/ipc.h
+++ b/security/apparmor/include/ipc.h
@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation function definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2013 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
@@ -19,10 +19,22 @@
struct aa_profile;
-int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
- struct aa_profile *tracee, unsigned int mode);
+#define AA_PTRACE_TRACE MAY_WRITE
+#define AA_PTRACE_READ MAY_READ
+#define AA_MAY_BE_TRACED AA_MAY_APPEND
+#define AA_MAY_BE_READ AA_MAY_CREATE
+#define PTRACE_PERM_SHIFT 2
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
- unsigned int mode);
+#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \
+ AA_MAY_BE_READ | AA_MAY_BE_TRACED)
+#define AA_SIGNAL_PERM_MASK (MAY_READ | MAY_WRITE)
+
+#define AA_FS_SIG_MASK "hup int quit ill trap abrt bus fpe kill usr1 " \
+ "segv usr2 pipe alrm term stkflt chld cont stop stp ttin ttou urg " \
+ "xcpu xfsz vtalrm prof winch io pwr sys emt lost"
+
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
+ u32 request);
+int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig);
#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
new file mode 100644
index 0000000..071eabe
--- /dev/null
+++ b/security/apparmor/include/label.h
@@ -0,0 +1,419 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label definitions
+ *
+ * Copyright 2013 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_LABEL_H
+#define __AA_LABEL_H
+
+#include <linux/atomic.h>
+#include <linux/audit.h>
+#include <linux/rbtree.h>
+#include <linux/rcupdate.h>
+
+#include "apparmor.h"
+
+struct aa_namespace;
+
+struct labelset_stats {
+ atomic_t sread;
+ atomic_t fread;
+ atomic_t msread;
+ atomic_t mfread;
+
+ atomic_t insert;
+ atomic_t existing;
+ atomic_t minsert;
+ atomic_t mexisting;
+
+ atomic_t invalid; /* outstanding invalid */
+};
+
+struct label_stats {
+ struct labelset_stats set_stats;
+
+ atomic_t allocated;
+ atomic_t failed;
+ atomic_t freed;
+
+ atomic_t printk_name_alloc;
+ atomic_t printk_name_fail;
+ atomic_t seq_print_name_alloc;
+ atomic_t seq_print_name_fail;
+ atomic_t audit_name_alloc;
+ atomic_t audit_name_fail;
+};
+
+
+#ifdef AA_LABEL_STATS
+#define labelstats_inc(X) atomic_inc(stats.(X))
+#define labelstats_dec(X) atomic_dec(stats.(X))
+#define labelsetstats_inc(LS, X) \
+ do { \
+ labelstats_inc(set_stats.##X); \
+ atomic_inc((LS)->stats.(X)); \
+ } while (0)
+#define labelsetstats_dec(LS, X) \
+ do { \
+ labelstats_dec(set_stats.##X); \
+ atomic_dec((LS)->stats.(X)); \
+ } while (0)
+#else
+#define labelstats_inc(X)
+#define labelstats_dec(X)
+#define labelsetstats_inc(LS, X)
+#define labelsetstats_dec(LS, X)
+#endif
+#define labelstats_init(X)
+
+/* struct aa_labelset - set of labels for a namespace
+ *
+ * Labels are reference counted; aa_labelset does not contribute to label
+ * reference counts. Once a label's last refcount is put it is removed from
+ * the set.
+ */
+struct aa_labelset {
+ rwlock_t lock;
+
+ struct rb_root root;
+
+ /* stats */
+#ifdef APPARMOR_LABEL_STATS
+ struct labelset_stats stats;
+#endif
+
+};
+
+#define __labelset_for_each(LS, N) \
+ for((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N))
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+
+
+enum label_flags {
+ FLAG_HAT = 1, /* profile is a hat */
+ FLAG_UNCONFINED = 2, /* label unconfined only if all
+ * constituant profiles unconfined */
+ FLAG_NULL = 4, /* profile is null learning profile */
+ FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
+ FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */
+ FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */
+ FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
+ FLAG_NS_COUNT = 0x80, /* carries NS ref count */
+ FLAG_IN_TREE = 0x100, /* label is in tree */
+ FLAG_PROFILE = 0x200, /* label is a profile */
+ FALG_EXPLICIT = 0x400, /* explict static label */
+ FLAG_INVALID = 0x800, /* replaced/removed */
+ FLAG_RENAMED = 0x1000, /* label has renaming in it */
+ FLAG_REVOKED = 0x2000, /* label has revocation in it */
+
+ /* These flags must correspond with PATH_flags */
+ /* TODO: add new path flags */
+};
+
+struct aa_label;
+struct aa_replacedby {
+ struct kref count;
+ struct aa_label __rcu *label;
+};
+
+struct label_it {
+ int i, j;
+};
+
+/* struct aa_label - lazy labeling struct
+ * @count: ref count of active users
+ * @node: rbtree position
+ * @rcu: rcu callback struct
+ * @replacedby: is set to the label that replaced this label
+ * @hname: text representation of the label (MAYBE_NULL)
+ * @flags: invalid and other flags - values may change under label set lock
+ * @sid: sid that references this label
+ * @size: number of entries in @ent[]
+ * @ent: set of profiles for label, actual size determined by @size
+ */
+struct aa_label {
+ struct kref count;
+ struct rb_node node;
+ struct rcu_head rcu;
+ struct aa_replacedby *replacedby;
+ __counted char *hname;
+ long flags;
+ u32 sid;
+ int size;
+ struct aa_profile *ent[2];
+};
+
+#define last_error(E, FN) \
+do { \
+ int __subE = (FN); \
+ if (__subE) \
+ (E) = __subE; \
+} while (0)
+
+#define label_isprofile(X) ((X)->flags & FLAG_PROFILE)
+#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED)
+#define unconfined(X) label_unconfined(X)
+#define label_invalid(X) ((X)->flags & FLAG_INVALID)
+#define __label_invalidate(X) do { \
+ labelsetstats_inc(labels_set(X), invalid); \
+ ((X)->flags |= FLAG_INVALID); \
+} while (0)
+#define labels_last(X) ((X)->ent[(X)->size - 1])
+#define labels_ns(X) (labels_last(X)->ns)
+#define labels_set(X) (&labels_ns(X)->labels)
+#define labels_profile(X) ({ \
+ AA_BUG(!label_isprofile(X)); \
+ container_of((X), struct aa_profile, label); \
+})
+
+int aa_label_next_confined(struct aa_label *l, int i);
+
+/* for each profile in a label */
+#define label_for_each(I, L, P) \
+ for ((I).i = 0; ((P) = (L)->ent[(I).i]); ++((I).i))
+
+#define label_for_each_at(I, L, P) \
+ for (; ((P) = (L)->ent[(I).i]); ++((I).i))
+
+#define next_comb(I, L1, L2) \
+do { \
+ (I).j++; \
+ if ((I).j >= (L2)->size) { \
+ (I).i++; \
+ (I).j = 0; \
+ } \
+} while (0)
+
+/* TODO: label_for_each_ns_comb */
+
+/* for each combination of P1 in L1, and P2 in L2 */
+#define label_for_each_comb(I, L1, L2, P1, P2) \
+for ((I).i = (I).j = 0; \
+ ((P1) = (L1)->ent[(I).i]) && ((P2) = (L2)->ent[(I).j]); \
+ (I) = next_comb(I, L1, L2))
+
+#define fn_for_each_comb(L1, L2, P1, P2, FN) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+/* internal cross check */
+//fn_for_each_comb(L1, L2, P1, P2, xcheck(...));
+
+/* external cross check */
+// xcheck(fn_for_each_comb(L1, L2, ...),
+// fn_for_each_comb(L2, L1, ...));
+
+/* for each profile that is enforcing confinement in a label */
+#define label_for_each_confined(I, L, P) \
+ for ((I).i = aa_label_next_confined((L), 0); \
+ ((P) = (L)->ent[(I).i]); \
+ (I).i = aa_label_next_confined((L), (I).i + 1))
+
+#define label_for_each_in_merge(I, A, B, P) \
+ for ((I).i = (I).j = 0; \
+ ((P) = aa_label_next_in_merge(&(I), (A), (B))); \
+ )
+
+#define label_for_each_not_in_set(I, SET, SUB, P) \
+ for ((I).i = (I).j = 0; \
+ ((P) = aa_label_next_not_in_set(&(I), (SET), (SUB))); \
+ )
+
+#define fn_for_each_XXX(L, P, FN, ...) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each ## __VA_ARGS__ (i, (L), (P)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN)
+#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined)
+
+#define fn_for_each2_XXX(L1, L2, P, FN, ...) \
+({ \
+ struct label_it i; \
+ int __E = 0; \
+ label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \
+ last_error(__E, (FN)); \
+ } \
+ __E; \
+})
+
+#define fn_for_each_in_merge(L1, L2, P, FN) \
+ fn_for_each2_XXX((L1), (L2), P, FN, _in_merge)
+#define fn_for_each_not_in_set(L1, L2, P, FN) \
+ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set)
+
+#define LABEL_MEDIATES(L, C) \
+({ \
+ struct aa_profile *profile; \
+ struct label_it i; \
+ int ret = 0; \
+ label_for_each(i, (L), profile) { \
+ if (PROFILE_MEDIATES(profile, (C))) { \
+ ret = 1; \
+ break; \
+ } \
+ } \
+ ret; \
+})
+
+void aa_labelset_destroy(struct aa_labelset *ls);
+void aa_labelset_init(struct aa_labelset *ls);
+void __aa_labelset_update_all(struct aa_namespace *ns);
+
+void aa_label_destroy(struct aa_label *label);
+void aa_label_free(struct aa_label *label);
+void aa_label_kref(struct kref *kref);
+bool aa_label_init(struct aa_label *label, int size);
+struct aa_label *aa_label_alloc(int size, gfp_t gfp);
+
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub);
+struct aa_profile * aa_label_next_not_in_set(struct label_it *I,
+ struct aa_label *set,
+ struct aa_label *sub);
+bool aa_label_remove(struct aa_labelset *ls, struct aa_label *label);
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l);
+struct aa_label *aa_label_remove_and_insert(struct aa_labelset *ls,
+ struct aa_label *remove,
+ struct aa_label *insert);
+bool aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+ struct aa_label *new);
+bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
+ struct aa_label *new);
+
+struct aa_label *aa_label_find(struct aa_labelset *ls, struct aa_label *l);
+struct aa_label *aa_label_vec_find(struct aa_labelset *ls,
+ struct aa_profile **vec,
+ int n);
+struct aa_label *aa_label_vec_merge(struct aa_profile **vec, int len,
+ gfp_t gfp);
+
+struct aa_profile *aa_label_next_in_merge(struct label_it *I,
+ struct aa_label *a,
+ struct aa_label *b);
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b);
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
+ gfp_t gfp);
+
+bool aa_update_label_name(struct aa_namespace *ns, struct aa_label *label,
+ gfp_t gfp);
+
+int aa_profile_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_profile *profile, bool mode);
+int aa_label_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_label *label, bool mode);
+int aa_label_asprint(char **strp, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp);
+int aa_label_acntsprint(char __counted **strp, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_audit(struct audit_buffer *ab, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_seq_print(struct seq_file *f, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp);
+void aa_label_printk(struct aa_namespace *ns, struct aa_label *label,
+ bool mode, gfp_t gfp);
+struct aa_label *aa_label_parse(struct aa_label *base, char *str,
+ gfp_t gfp, bool create);
+
+static inline struct aa_label *aa_get_label(struct aa_label *l)
+{
+ if (l)
+ kref_get(&(l->count));
+
+ return l;
+}
+
+static inline struct aa_label *aa_get_label_not0(struct aa_label *l)
+{
+ if (l && kref_get_not0(&l->count))
+ return l;
+
+ return NULL;
+}
+
+/**
+ * aa_get_label_rcu - increment refcount on a label that can be replaced
+ * @l: pointer to label that can be replaced (NOT NULL)
+ *
+ * Returns: pointer to a refcounted label.
+ * else NULL if no label
+ */
+static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l)
+{
+ struct aa_label *c;
+
+ rcu_read_lock();
+ do {
+ c = rcu_dereference(*l);
+ } while (c && !kref_get_not0(&c->count));
+ rcu_read_unlock();
+
+ return c;
+}
+
+/**
+ * aa_get_newest_label - find the newest version of @l
+ * @l: the label to check for newer versions of
+ *
+ * Returns: refcounted newest version of @l taking into account
+ * replacement, renames and removals
+ * return @l.
+ */
+static inline struct aa_label *aa_get_newest_label(struct aa_label *l)
+{
+ if (!l)
+ return NULL;
+
+ if (label_invalid(l))
+ return aa_get_label_rcu(&l->replacedby->label);
+
+ return aa_get_label(l);
+}
+
+static inline void aa_put_label(struct aa_label *l)
+{
+ if (l)
+ kref_put(&l->count, aa_label_kref);
+}
+
+
+struct aa_replacedby *aa_alloc_replacedby(struct aa_label *l);
+void aa_free_replacedby_kref(struct kref *kref);
+
+static inline struct aa_replacedby *aa_get_replacedby(struct aa_replacedby *r)
+{
+ if (r)
+ kref_get(&(r->count));
+
+ return r;
+}
+
+static inline void aa_put_replacedby(struct aa_replacedby *r)
+{
+ if (r)
+ kref_put(&r->count, aa_free_replacedby_kref);
+}
+
+void __aa_update_replacedby(struct aa_label *orig, struct aa_label *new);
+
+#endif /* __AA_LABEL_H */
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
index 775843e..3122845 100644
--- a/security/apparmor/include/match.h
+++ b/security/apparmor/include/match.h
@@ -4,7 +4,7 @@
* This file contains AppArmor policy dfa matching engine definitions.
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * 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
@@ -16,25 +16,30 @@
#define __AA_MATCH_H
#include <linux/kref.h>
-#include <linux/workqueue.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 instead of 0xF13C57B1 though, because
- * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
- * slightly differently (see the apparmor-parser package).
+ * new tables have been defined and others YY_ID_CHK (check) and YY_ID_DEF
+ * (default) tables are used slightly differently (see the apparmor-parser
+ * package).
+ *
+ *
+ * The data in the packed dfa is stored in network byte order, and the tables
+ * are arranged for flexibility. We convert the table data to host native
+ * byte order.
+ *
+ * The dfa begins with a table set header, and is followed by the actual
+ * tables.
*/
#define YYTH_MAGIC 0x1B5E783D
-#define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */
struct table_set_header {
u32 th_magic; /* YYTH_MAGIC */
@@ -63,7 +68,7 @@ struct table_set_header {
#define YYTD_DATA32 4
#define YYTD_DATA64 8
-/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the
+/* ACCEPT & ACCEPT2 tables gets 6 dedicated flags, YYTD_DATAX define the
* first flags
*/
#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
@@ -122,6 +127,21 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
void aa_dfa_free_kref(struct kref *kref);
/**
+ * aa_get_dfa - increment refcount on dfa @p
+ * @dfa: dfa (MAYBE NULL)
+ *
+ * Returns: pointer to @dfa if @dfa is NULL will return NULL
+ * Requires: @dfa must be held with valid refcount when called
+ */
+static inline struct aa_dfa *aa_get_dfa(struct aa_dfa *dfa)
+{
+ if (dfa)
+ kref_get(&(dfa->count));
+
+ return dfa;
+}
+
+/**
* aa_put_dfa - put a dfa refcount
* @dfa: dfa to put refcount (MAYBE NULL)
*
diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h
new file mode 100644
index 0000000..a12b5b4
--- /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 <linux/fs.h>
+#include <linux/path.h>
+
+#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_MNT_CONT_MATCH 0x40
+
+#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
+
+int aa_remount(struct aa_label *label, struct path *path, unsigned long flags,
+ void *data);
+
+int aa_bind_mount(struct aa_label *label, struct path *path,
+ const char *old_name, unsigned long flags);
+
+
+int aa_mount_change_type(struct aa_label *label, struct path *path,
+ unsigned long flags);
+
+int aa_move_mount(struct aa_label *label, struct path *path,
+ const char *old_name);
+
+int aa_new_mount(struct aa_label *label, const char *dev_name,
+ struct path *path, const char *type, unsigned long flags,
+ void *data);
+
+int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags);
+
+int aa_pivotroot(struct aa_label *label, struct path *old_path,
+ struct path *new_path);
+
+#endif /* __AA_MOUNT_H */
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..4a5fae5
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,110 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2014 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 <net/sock.h>
+
+#include "apparmorfs.h"
+#include "label.h"
+#include "perms.h"
+#include "policy.h"
+
+#define AA_MAY_SEND AA_MAY_WRITE
+#define AA_MAY_RECEIVE AA_MAY_READ
+
+#define AA_MAY_SHUTDOWN AA_MAY_DELETE
+
+#define AA_MAY_CONNECT AA_MAY_OPEN
+#define AA_MAY_ACCEPT 0x00100000
+
+#define AA_MAY_BIND 0x00200000
+#define AA_MAY_LISTEN 0x00400000
+
+#define AA_MAY_SETOPT 0x01000000
+#define AA_MAY_GETOPT 0x02000000
+
+#define NET_PERMS_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
+ AA_MAY_SHUTDOWN | AA_MAY_BIND | AA_MAY_LISTEN | \
+ AA_MAY_CONNECT | AA_MAY_ACCEPT | AA_MAY_SETATTR | \
+ AA_MAY_GETATTR | AA_MAY_SETOPT | AA_MAY_GETOPT)
+
+#define NET_FS_PERMS (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \
+ AA_MAY_SHUTDOWN | AA_MAY_CONNECT | AA_MAY_RENAME |\
+ AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_CHMOD | \
+ AA_MAY_CHOWN | AA_MAY_CHGRP | AA_MAY_LOCK | \
+ AA_MAY_MPROT)
+
+#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \
+ AA_MAY_ACCEPT)
+struct aa_sk_cxt {
+ struct aa_label *label;
+ struct aa_label *peer;
+};
+
+#define SK_CXT(X) (X)->sk_security
+#define SOCK_CXT(X) SOCK_INODE(X)->i_security
+#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \
+ struct lsm_network_audit NAME ## _net = { .sk = (SK), \
+ .family = (F)}; \
+ DEFINE_AUDIT_DATA(NAME, \
+ (SK) ? LSM_AUDIT_DATA_NET : LSM_AUDIT_DATA_NONE,\
+ OP); \
+ NAME.u.net = &(NAME ## _net); \
+ aad(&NAME)->net.type = (T); \
+ aad(&NAME)->net.protocol = (P)
+
+/* 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[];
+
+void audit_net_cb(struct audit_buffer *ab, void *va);
+int aa_profile_af_perm(struct aa_profile *profile, int op, u16 family,
+ int type, int protocol, struct sock *sk);
+int aa_af_perm(struct aa_label *label, int op, u32 request, u16 family,
+ int type, int protocol, struct sock *sk);
+int aa_sock_perm(int op, u32 request, struct socket *sock);
+int aa_sock_create_perm(struct aa_label *label, int family, int type,
+ int protocol);
+int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_sock_listen_perm(struct socket *sock, int backlog);
+int aa_sock_accept_perm(struct socket *sock, struct socket *newsock);
+int aa_sock_msg_perm(int op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size);
+int aa_sock_opt_perm(int op, u32 request, struct socket *sock, int level,
+ int optname);
+int aa_sock_file_perm(struct aa_label *label, int op, u32 request,
+ struct socket *sock);
+
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
index 286ac75..09b47b8 100644
--- a/security/apparmor/include/path.h
+++ b/security/apparmor/include/path.h
@@ -18,15 +18,72 @@
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
+ PATH_SOCK_COND = 0x2,
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
- PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
+ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
};
-int aa_path_name(struct path *path, int flags, char **buffer,
- const char **name, const char **info);
+int aa_path_name(struct path *path, int flags, char *buffer,
+ const char **name, const char **info, const char *disconnect);
+
+#define MAX_PATH_BUFFERS 2
+
+/* Per cpu buffers used during mediation */
+/* preallocated buffers to use during path lookups */
+struct aa_buffers {
+ char *buf[MAX_PATH_BUFFERS];
+};
+
+#include <linux/percpu.h>
+#include <linux/preempt.h>
+
+DECLARE_PER_CPU(struct aa_buffers, aa_buffers);
+
+#define COUNT_ARGS(X...) COUNT_ARGS_HELPER ( , ##X ,9,8,7,6,5,4,3,2,1,0)
+#define COUNT_ARGS_HELPER(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,n,X...) n
+#define CONCAT(X, Y) X ## Y
+#define CONCAT_AFTER(X, Y) CONCAT(X, Y)
+
+#define ASSIGN(FN, X, N) do { (X) = FN(N); } while (0)
+#define EVAL1(FN, X) ASSIGN(FN, X, 0) /*X = FN(0)*/
+#define EVAL2(FN, X, Y...) ASSIGN(FN, X, 1); /*X = FN(1);*/ EVAL1(FN, Y)
+#define EVAL(FN, X...) CONCAT_AFTER(EVAL, COUNT_ARGS(X))(FN, X)
+
+#define for_each_cpu_buffer(I) for ((I) = 0; (I) < MAX_PATH_BUFFERS; (I)++)
+
+#ifdef CONFIG_DEBUG_PREEMPT
+#define AA_BUG_PREEMPT_ENABLED(X) AA_BUG(preempt_count() <= 0, X)
+#else
+#define AA_BUG_PREEMPT_ENABLED(X) /* nop */
+#endif
+
+#define __get_buffer(N) ({ \
+ struct aa_buffers *__cpu_var; \
+ AA_BUG_PREEMPT_ENABLED("__get_buffer without preempt disabled"); \
+ __cpu_var = &__get_cpu_var(aa_buffers); \
+ __cpu_var->buf[(N)]; })
+
+#define __get_buffers(X...) \
+do { \
+ EVAL(__get_buffer, X); \
+} while (0)
+
+#define __put_buffers(X, Y...) (void)&(X)
+
+#define get_buffers(X...) \
+do { \
+ preempt_disable(); \
+ __get_buffers(X); \
+} while (0)
+
+#define put_buffers(X, Y...) \
+do { \
+ __put_buffers(X, Y); \
+ preempt_enable(); \
+} while (0)
#endif /* __AA_PATH_H */
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
new file mode 100644
index 0000000..a8ee193
--- /dev/null
+++ b/security/apparmor/include/perms.h
@@ -0,0 +1,174 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic permission sets definitions.
+ *
+ * Copyright 2013 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_PERM_H
+#define __AA_PERM_H
+
+#include <linux/fs.h>
+#include "label.h"
+
+#define AA_MAY_EXEC MAY_EXEC
+#define AA_MAY_WRITE MAY_WRITE
+#define AA_MAY_READ MAY_READ
+#define AA_MAY_APPEND MAY_APPEND
+
+#define AA_MAY_CREATE 0x0010
+#define AA_MAY_DELETE 0x0020
+#define AA_MAY_OPEN 0x0040
+#define AA_MAY_RENAME 0x0080 /* pair */
+
+#define AA_MAY_SETATTR 0x0100 /* meta write */
+#define AA_MAY_GETATTR 0x0200 /* meta read */
+#define AA_MAY_SETCRED 0x0400 /* security cred/attr */
+#define AA_MAY_GETCRED 0x0800
+
+#define AA_MAY_CHMOD 0x1000 /* pair */
+#define AA_MAY_CHOWN 0x2000 /* pair */
+#define AA_MAY_CHGRP 0x4000 /* pair */
+#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */
+
+#define AA_EXEC_MMAP 0x00010000
+#define AA_MAY_MPROT 0x00020000 /* extend conditions */
+#define AA_MAY_LINK 0x00040000 /* pair */
+#define AA_MAY_SNAPSHOT 0x00080000 /* pair */
+
+#define AA_MAY_DELEGATE
+#define AA_CONT_MATCH 0x08000000
+
+#define AA_MAY_STACK 0x10000000
+#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */
+#define AA_MAY_CHANGE_PROFILE 0x40000000
+#define AA_MAY_CHANGEHAT 0x80000000
+
+#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
+
+
+#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \
+ AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \
+ AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND)
+
+#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \
+ AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \
+ AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \
+ AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \
+ AA_MAY_STACK | AA_MAY_ONEXEC | \
+ AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT)
+
+extern const char aa_file_perm_chrs[];
+extern const char *aa_file_perm_names[];
+
+
+struct aa_perms {
+ u32 allow;
+ u32 audit; /* set only when allow is set */
+
+ u32 deny; /* explicit deny, or conflict if allow also set */
+ u32 quiet; /* set only when ~allow | deny */
+ u32 kill; /* set only when ~allow | deny */
+ u32 stop; /* set only when ~allow | deny */
+
+ u32 complain; /* accumulates only used when ~allow & ~deny */
+ u32 cond; /* set only when ~allow and ~deny */
+
+ u32 hide; /* set only when ~allow | deny */
+ u32 prompt; /* accumulates only used when ~allow & ~deny */
+
+ /* Reserved:
+ * u32 subtree; / * set only when allow is set * /
+ */
+};
+
+#define ALL_PERMS_MASK 0xffffffff
+
+#define aa_perms_clear(X) memset((X), 0, sizeof(*(X)));
+#define aa_perms_all(X) \
+ do { \
+ aa_perms_clear(X); \
+ (X)->allow = ALL_PERMS_MASK; \
+ /* the following are only used for denials */ \
+ (X)->quiet = ALL_PERMS_MASK; \
+ (X)->hide = ALL_PERMS_MASK; \
+ } while (0)
+
+#define xcheck(FN1, FN2) \
+({ \
+ int e, error = FN1; \
+ e = FN2; \
+ if (e) \
+ error = e; \
+ error; \
+})
+
+
+/* TODO: update for labels pointing to labels instead of profiles
+* Note: this only works for profiles from a single namespace
+*/
+
+#define xcheck_profile_label(P, L, FN, args...) \
+({ \
+ struct aa_profile *__p2; \
+ fn_for_each((L), __p2, FN((P), __p2, args)); \
+})
+
+#define xcheck_ns_labels(L1, L2, FN, args...) \
+({ \
+ struct aa_profile *__p1; \
+ fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
+})
+
+/* todo: fix to handle multiple namespaces */
+#define xcheck_labels(L1, L2, FN, args...) \
+ xcheck_ns_labels((L1), (L2), FN, args)
+
+/* Do the cross check but applying FN at the profiles level */
+#define xcheck_labels_profiles(L1, L2, FN, args...) \
+ xcheck_ns_labels((L1), (L2), xcheck_profile_label, (FN), args)
+
+
+#define FINAL_CHECK true
+
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask);
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask);
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
+ u32 chrsmask, const char **names, u32 namesmask);
+void aa_apply_modes_to_perms(struct aa_profile *profile,
+ struct aa_perms *perms);
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
+ struct aa_perms *perms);
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
+void aa_profile_match_label(struct aa_profile *profile, const char *label,
+ int type, struct aa_perms *perms);
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
+ u32 request, int type, u32 *deny,
+ struct common_audit_data *sa);
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+ u32 request, struct common_audit_data *sa,
+ void (*cb) (struct audit_buffer *, void *));
+const char *aa_peer_name(struct aa_profile *peer);
+
+
+static inline int aa_xlabel_perm(struct aa_profile *profile,
+ struct aa_profile *target,
+ int type, u32 request, u32 reverse,
+ u32 * deny, struct common_audit_data *sa)
+{
+ /* TODO: ??? 2nd aa_profile_label_perm needs to reverse perms */
+ return xcheck(aa_profile_label_perm(profile, target, request, type,
+ deny, sa),
+ aa_profile_label_perm(target, profile, request /*??*/, type,
+ deny, sa));
+}
+
+
+#endif /* __AA_PERM_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index bda4569..da71e27f 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -27,20 +27,27 @@
#include "capability.h"
#include "domain.h"
#include "file.h"
+#include "label.h"
+#include "net.h"
#include "resource.h"
-extern const char *const profile_mode_names[];
-#define APPARMOR_NAMES_MAX_INDEX 3
+extern const char *aa_hidden_ns_name;
+extern const char *const aa_profile_mode_names[];
+#define APPARMOR_MODE_NAMES_MAX_INDEX 4
-#define COMPLAIN_MODE(_profile) \
- ((aa_g_profile_mode == APPARMOR_COMPLAIN) || \
- ((_profile)->mode == APPARMOR_COMPLAIN))
+#define PROFILE_MODE(_profile, _mode) \
+ ((aa_g_profile_mode == (_mode)) || \
+ ((_profile)->mode == (_mode)))
-#define KILL_MODE(_profile) \
- ((aa_g_profile_mode == APPARMOR_KILL) || \
- ((_profile)->mode == APPARMOR_KILL))
+#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
-#define PROFILE_IS_HAT(_profile) ((_profile)->flags & PFLAG_HAT)
+#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
+
+#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
+
+#define PROFILE_INVALID(_profile) ((_profile)->label.flags & FLAG_INVALID)
+
+#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
/*
* FIXME: currently need a clean way to replace and remove profiles as a
@@ -52,35 +59,21 @@ enum profile_mode {
APPARMOR_ENFORCE, /* enforce access rules */
APPARMOR_COMPLAIN, /* allow and log access violations */
APPARMOR_KILL, /* kill task on access violation */
+ APPARMOR_UNCONFINED, /* profile set to unconfined */
};
-enum profile_flags {
- PFLAG_HAT = 1, /* profile is a hat */
- PFLAG_UNCONFINED = 2, /* profile is an 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 - lower privs */
- PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
- PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
-
- /* These flags must correspond with PATH_flags */
- PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
-};
struct aa_profile;
/* struct aa_policy - common part of both namespaces and profiles
* @name: name of the object
- * @hname - The hierarchical name
- * @count: reference count of the obj
+ * @hname - The hierarchical name, NOTE: is .name of struct counted_str
* @list: list policy object is on
* @profiles: head of the profiles list contained in the object
*/
struct aa_policy {
- char *name;
- char *hname;
- struct kref count;
+ const char *name;
+ __counted char *hname;
struct list_head list;
struct list_head profiles;
};
@@ -105,6 +98,9 @@ struct aa_ns_acct {
* @acct: accounting for the namespace
* @unconfined: special unconfined profile for the namespace
* @sub_ns: list of namespaces under the current namespace.
+ * @uniq_null: uniq value used for null learning profiles
+ * @uniq_id: a unique id count for the profiles in the namespace
+ * @dents: dentries for the namespaces file entries in apparmorfs
*
* 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
@@ -123,10 +119,16 @@ struct aa_ns_acct {
struct aa_namespace {
struct aa_policy base;
struct aa_namespace *parent;
- rwlock_t lock;
+ struct mutex lock;
struct aa_ns_acct acct;
struct aa_profile *unconfined;
struct list_head sub_ns;
+ atomic_t uniq_null;
+ long uniq_id;
+ int level;
+ struct aa_labelset labels;
+
+ struct dentry *dents[AAFS_NS_SIZEOF];
};
/* struct aa_policydb - match engine for a policy
@@ -142,30 +144,33 @@ struct aa_policydb {
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
+ * @label - label this profile is an extension of
* @parent: parent of profile
* @ns: namespace the profile is in
- * @replacedby: is set to the profile that replaced this profile
* @rename: optional profile name that this profile renamed
+ * @attach: human readable attachment string
* @xmatch: optional extended matching for unconfined executables names
* @xmatch_len: 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
* @path_flags: flags controlling path generation behavior
+ * @disconnected: what to prepend if attach_disconnected is specified
* @size: the memory consumed by this profiles rules
* @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
*
+ * @dents: dentries for the profiles file entries in apparmorfs
+ * @dirname: name of the profile dir in apparmorfs
+ *
* The AppArmor profile contains the basic confinement data. Each profile
* has a name, and exists in a namespace. The @name and @exec_match are
* used to determine profile attachment against unconfined tasks. All other
* attachments are determined by 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.
+ * The @replacedby struct is write protected by the profile lock.
*
* Profiles have a hierarchy where hats and children profiles keep
* a reference to their parent.
@@ -176,81 +181,62 @@ struct aa_policydb {
*/
struct aa_profile {
struct aa_policy base;
- struct aa_profile *parent;
+ struct aa_label label;
+ struct aa_profile __rcu *parent;
struct aa_namespace *ns;
- struct aa_profile *replacedby;
const char *rename;
+ const char *attach;
struct aa_dfa *xmatch;
int xmatch_len;
- u32 sid;
enum audit_mode audit;
- enum profile_mode mode;
- u32 flags;
+ long mode;
u32 path_flags;
+ const char *disconnected;
int size;
struct aa_policydb policy;
struct aa_file_rules file;
struct aa_caps caps;
+ struct aa_net net;
struct aa_rlimit rlimits;
+
+ unsigned char *hash;
+ char *dirname;
+ struct dentry *dents[AAFS_PROF_SIZEOF];
};
extern struct aa_namespace *root_ns;
extern enum profile_mode aa_g_profile_mode;
+#define profiles_ns(P) ((P)->ns)
void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view);
const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child);
+void aa_free_namespace(struct aa_namespace *ns);
int aa_alloc_root_ns(void);
void aa_free_root_ns(void);
void aa_free_namespace_kref(struct kref *kref);
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
const char *name);
+struct aa_namespace *aa_findn_namespace(struct aa_namespace *root,
+ const char *name, size_t n);
-static inline struct aa_policy *aa_get_common(struct aa_policy *c)
-{
- if (c)
- kref_get(&c->count);
-
- return c;
-}
-
-/**
- * aa_get_namespace - increment references count on @ns
- * @ns: namespace to increment reference count of (MAYBE NULL)
- *
- * Returns: pointer to @ns, if @ns is NULL returns NULL
- * Requires: @ns must be held with valid refcount when called
- */
-static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
-{
- if (ns)
- kref_get(&(ns->base.count));
-
- return ns;
-}
-
-/**
- * aa_put_namespace - decrement refcount on @ns
- * @ns: namespace to put reference of
- *
- * Decrement reference count of @ns and if no longer in use free it
- */
-static inline void aa_put_namespace(struct aa_namespace *ns)
-{
- if (ns)
- kref_put(&ns->base.count, aa_free_namespace_kref);
-}
+struct aa_label *aa_setup_default_label(void);
struct aa_profile *aa_alloc_profile(const char *name);
struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
+void aa_free_profile(struct aa_profile *profile);
void aa_free_profile_kref(struct kref *kref);
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
+struct aa_profile *aa_lookupn_profile(struct aa_namespace *ns,
+ const char *hname, size_t n);
struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name);
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, char *fqname,
+ size_t n);
struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace);
@@ -259,25 +245,46 @@ ssize_t aa_remove_profiles(char *name, size_t size);
#define PROF_ADD 1
#define PROF_REPLACE 0
-#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED)
+#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
/**
- * aa_newest_version - find the newest version of @profile
- * @profile: the profile to check for newer versions of (NOT NULL)
+ * aa_get_newest_profile - simple wrapper fn to wrap the label version
+ * @p: profile (NOT NULL)
*
- * Returns: newest version of @profile, if @profile is the newest version
- * return @profile.
+ * Returns refcount to newest version of the profile (maybe @p)
*
- * 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.
+ * Requires: @p must be held with a valid refcount
*/
-static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)
+static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
+{
+ return labels_profile(aa_get_newest_label(&p->label));
+}
+
+#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)])
+/* safe version of POLICY_MEDIATES for full range input */
+static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile,
+ unsigned char class)
{
- while (profile->replacedby)
- profile = profile->replacedby;
+ if (profile->policy.dfa)
+ return aa_dfa_match_len(profile->policy.dfa,
+ profile->policy.start[0], &class, 1);
+ return 0;
+}
- return profile;
+static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile,
+ u16 AF) {
+ unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET);
+ u16 be_af = cpu_to_be16(AF);
+ if (!state)
+ return 0;
+ return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2);
+}
+
+
+static inline struct aa_profile *aa_deref_parent(struct aa_profile *p)
+{
+ return rcu_dereference_protected(p->parent,
+ mutex_is_locked(&p->ns->lock));
}
/**
@@ -290,19 +297,81 @@ static inline struct aa_profile *aa_newest_version(struct aa_profile *profile)
static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
{
if (p)
- kref_get(&(p->base.count));
+ kref_get(&(p->label.count));
return p;
}
/**
+ * aa_get_profile_not0 - increment refcount on profile @p found via lookup
+ * @p: profile (MAYBE NULL)
+ *
+ * Returns: pointer to @p if @p is NULL will return NULL
+ * Requires: @p must be held with valid refcount when called
+ */
+static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p)
+{
+ if (p && kref_get_not0(&p->label.count))
+ return p;
+
+ return NULL;
+}
+
+/**
+ * aa_get_profile_rcu - increment a refcount profile that can be replaced
+ * @p: pointer to profile that can be replaced (NOT NULL)
+ *
+ * Returns: pointer to a refcounted profile.
+ * else NULL if no profile
+ */
+static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p)
+{
+ struct aa_profile *c;
+
+ rcu_read_lock();
+ do {
+ c = rcu_dereference(*p);
+ } while (c && !kref_get_not0(&c->label.count));
+ rcu_read_unlock();
+
+ return c;
+}
+
+/**
* aa_put_profile - decrement refcount on profile @p
* @p: profile (MAYBE NULL)
*/
static inline void aa_put_profile(struct aa_profile *p)
{
if (p)
- kref_put(&p->base.count, aa_free_profile_kref);
+ kref_put(&p->label.count, aa_label_kref);
+}
+
+/**
+ * aa_get_namespace - increment references count on @ns
+ * @ns: namespace to increment reference count of (MAYBE NULL)
+ *
+ * Returns: pointer to @ns, if @ns is NULL returns NULL
+ * Requires: @ns must be held with valid refcount when called
+ */
+static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ aa_get_profile(ns->unconfined);
+
+ return ns;
+}
+
+/**
+ * aa_put_namespace - decrement refcount on @ns
+ * @ns: namespace to put reference of
+ *
+ * Decrement reference count of @ns and if no longer in use free it
+ */
+static inline void aa_put_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ aa_put_profile(ns->unconfined);
}
static inline int AUDIT_MODE(struct aa_profile *profile)
@@ -315,4 +384,30 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
bool aa_may_manage_policy(int op);
+
+#define LOCAL_VEC_ENTRIES 8
+#define DEFINE_PROFILE_VEC(V, T) \
+ struct aa_profile *(T)[LOCAL_VEC_ENTRIES]; \
+ struct aa_profile **(V)
+
+#define aa_setup_profile_vec(V, T, L) \
+({ \
+ if ((L) > LOCAL_VEC_ENTRIES) \
+ (V) = kmalloc(sizeof(struct aa_profile *) * (L), GFP_KERNEL);\
+ else \
+ (V) = (T); \
+ (V) ? 0 : -ENOMEM; \
+})
+
+static inline void aa_cleanup_profile_vec(struct aa_profile **vec, \
+ struct aa_profile **tmp, int len) \
+{ \
+ int i; \
+ for (i = 0; i < len; i++) \
+ aa_put_profile(vec[i]); \
+ if (vec != tmp) \
+ kfree(vec); \
+}
+
+
#endif /* __AA_POLICY_H */
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index a2dccca..c214fb8 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -15,6 +15,25 @@
#ifndef __POLICY_INTERFACE_H
#define __POLICY_INTERFACE_H
-struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns);
+#include <linux/list.h>
+
+struct aa_load_ent {
+ struct list_head list;
+ struct aa_profile *new;
+ struct aa_profile *old;
+ struct aa_profile *rename;
+};
+
+void aa_load_ent_free(struct aa_load_ent *ent);
+struct aa_load_ent *aa_load_ent_alloc(void);
+
+#define PACKED_FLAG_HAT 1
+
+#define PACKED_MODE_ENFORCE 0
+#define PACKED_MODE_COMPLAIN 1
+#define PACKED_MODE_KILL 2
+#define PACKED_MODE_UNCONFINED 3
+
+int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns);
#endif /* __POLICY_INTERFACE_H */
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
index 544aa6b..e4d2618 100644
--- a/security/apparmor/include/procattr.h
+++ b/security/apparmor/include/procattr.h
@@ -18,9 +18,8 @@
#define AA_DO_TEST 1
#define AA_ONEXEC 1
-int aa_getprocattr(struct aa_profile *profile, char **string);
+int aa_getprocattr(struct aa_label *label, char **string);
int aa_setprocattr_changehat(char *args, size_t size, int test);
int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test);
-int aa_setprocattr_permipc(char *fqname);
#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h
index d3f4cf0..69d3b8ad 100644
--- a/security/apparmor/include/resource.h
+++ b/security/apparmor/include/resource.h
@@ -37,10 +37,10 @@ struct aa_rlimit {
extern struct aa_fs_entry aa_fs_entry_rlimit[];
int aa_map_resource(int resource);
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *,
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *,
unsigned int resource, struct rlimit *new_rlim);
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
+void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
{
diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h
index 020db35..513ca0e 100644
--- a/security/apparmor/include/sid.h
+++ b/security/apparmor/include/sid.h
@@ -16,7 +16,9 @@
#include <linux/types.h>
-struct aa_profile;
+/* sid value that will not be allocated */
+#define AA_SID_INVALID 0
+#define AA_SID_ALLOC AA_SID_INVALID
u32 aa_alloc_sid(void);
void aa_free_sid(u32 sid);
diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h
new file mode 100644
index 0000000..8a3dfc5
--- /dev/null
+++ b/security/apparmor/include/sig_names.h
@@ -0,0 +1,95 @@
+#include <linux/signal.h>
+
+#define SIGUNKNOWN 0
+#define MAXMAPPED_SIG 35
+/* provide a mapping of arch signal to internal signal # for mediation
+ * those that are always an alias SIGCLD for SIGCLHD and SIGPOLL for SIGIO
+ * map to the same entry those that may/or may not get a separate entry
+ */
+static const int sig_map[MAXMAPPED_SIG] = {
+ [0] = MAXMAPPED_SIG, /* existance test */
+ [SIGHUP] = 1,
+ [SIGINT] = 2,
+ [SIGQUIT] = 3,
+ [SIGILL] = 4,
+ [SIGTRAP] = 5, /* -, 5, - */
+ [SIGABRT] = 6, /* SIGIOT: -, 6, - */
+ [SIGBUS] = 7, /* 10, 7, 10 */
+ [SIGFPE] = 8,
+ [SIGKILL] = 9,
+ [SIGUSR1] = 10, /* 30, 10, 16 */
+ [SIGSEGV] = 11,
+ [SIGUSR2] = 12, /* 31, 12, 17 */
+ [SIGPIPE] = 13,
+ [SIGALRM] = 14,
+ [SIGTERM] = 15,
+ [SIGSTKFLT] = 16, /* -, 16, - */
+ [SIGCHLD] = 17, /* 20, 17, 18. SIGCHLD -, -, 18 */
+ [SIGCONT] = 18, /* 19, 18, 25 */
+ [SIGSTOP] = 19, /* 17, 19, 23 */
+ [SIGTSTP] = 20, /* 18, 20, 24 */
+ [SIGTTIN] = 21, /* 21, 21, 26 */
+ [SIGTTOU] = 22, /* 22, 22, 27 */
+ [SIGURG] = 23, /* 16, 23, 21 */
+ [SIGXCPU] = 24, /* 24, 24, 30 */
+ [SIGXFSZ] = 25, /* 25, 25, 31 */
+ [SIGVTALRM] = 26, /* 26, 26, 28 */
+ [SIGPROF] = 27, /* 27, 27, 29 */
+ [SIGWINCH] = 28, /* 28, 28, 20 */
+ [SIGIO] = 29, /* SIGPOLL: 23, 29, 22 */
+ [SIGPWR] = 30, /* 29, 30, 19. SIGINFO 29, -, - */
+#ifdef SIGSYS
+ [SIGSYS] = 31, /* 12, 31, 12. often SIG LOST/UNUSED */
+#endif
+#ifdef SIGEMT
+ [SIGEMT] = 32, /* 7, - , 7 */
+#endif
+#if defined(SIGLOST) && SIGPWR != SIGLOST /* sparc */
+ [SIGLOST] = 33, /* unused on Linux */
+#endif
+#if defined(SIGLOST) && defined(SIGSYS) && SIGLOST != SIGSYS
+ [SIGUNUSED] = 34, /* -, 31, - */
+#endif
+};
+
+/* this table is ordered post sig_map[sig] mapping */
+static const char *const sig_names[MAXMAPPED_SIG + 1] = {
+ "unknown",
+ "hup",
+ "int",
+ "quit",
+ "ill",
+ "trap",
+ "abrt",
+ "bus",
+ "fpe",
+ "kill",
+ "usr1",
+ "segv",
+ "usr2",
+ "pipe",
+ "alrm",
+ "term",
+ "stkflt",
+ "chld",
+ "cont",
+ "stop",
+ "stp",
+ "ttin",
+ "ttou",
+ "urg",
+ "xcpu",
+ "xfsz",
+ "vtalrm",
+ "prof",
+ "winch",
+ "io",
+ "pwr",
+ "sys",
+ "emt",
+ "lost",
+ "unused",
+
+ "exists", /* always last existance test mapped to MAXMAPPED_SIG */
+};
+
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c
index cf1071b..eaf505d 100644
--- a/security/apparmor/ipc.c
+++ b/security/apparmor/ipc.c
@@ -4,7 +4,7 @@
* This file contains AppArmor ipc mediation
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2013 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
@@ -20,98 +20,201 @@
#include "include/context.h"
#include "include/policy.h"
#include "include/ipc.h"
+#include "include/sig_names.h"
+
+/**
+ * audit_ptrace_mask - convert mask to permission string
+ * @buffer: buffer to write string to (NOT NULL)
+ * @mask: permission mask to convert
+ */
+static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask)
+{
+ switch (mask) {
+ case MAY_READ:
+ audit_log_string(ab, "read");
+ break;
+ case MAY_WRITE:
+ audit_log_string(ab, "trace");
+ break;
+ case AA_MAY_BE_READ:
+ audit_log_string(ab, "readby");
+ break;
+ case AA_MAY_BE_TRACED:
+ audit_log_string(ab, "tracedby");
+ break;
+ }
+}
/* call back to audit ptrace fields */
-static void audit_cb(struct audit_buffer *ab, void *va)
+static void audit_ptrace_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
- audit_log_format(ab, " target=");
- audit_log_untrustedstring(ab, sa->aad->target);
+
+ if (aad(sa)->request & AA_PTRACE_PERM_MASK) {
+ audit_log_format(ab, " requested_mask=");
+ audit_ptrace_mask(ab, aad(sa)->request);
+
+ if (aad(sa)->denied & AA_PTRACE_PERM_MASK) {
+ audit_log_format(ab, " denied_mask=");
+ audit_ptrace_mask(ab, aad(sa)->denied);
+ }
+ }
+ audit_log_format(ab, " peer=");
+ audit_log_untrustedstring(ab, aad(sa)->target);
}
-/**
- * aa_audit_ptrace - do auditing for ptrace
- * @profile: profile being enforced (NOT NULL)
- * @target: profile being traced (NOT NULL)
- * @error: error condition
- *
- * Returns: %0 or error code
- */
-static int aa_audit_ptrace(struct aa_profile *profile,
- struct aa_profile *target, int error)
+/* TODO: conditionals */
+static int profile_ptrace_perm(struct aa_profile *profile,
+ struct aa_profile *peer, u32 request,
+ struct common_audit_data *sa)
{
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.op = OP_PTRACE;
- aad.target = target;
- aad.error = error;
-
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa,
- audit_cb);
+ struct aa_perms perms;
+
+ /* need because of peer in cross check */
+ if (profile_unconfined(profile) ||
+ !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE))
+ return 0;
+
+ aad(sa)->target = peer->base.hname;
+ aa_profile_match_label(profile, aa_peer_name(peer), AA_CLASS_PTRACE,
+ &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
+}
+
+static int cross_ptrace_perm(struct aa_profile *tracer,
+ struct aa_profile *tracee, u32 request,
+ struct common_audit_data *sa)
+{
+ if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
+ return xcheck(profile_ptrace_perm(tracer, tracee, request, sa),
+ profile_ptrace_perm(tracee, tracer,
+ request << PTRACE_PERM_SHIFT,
+ sa));
+ /* policy uses the old style capability check for ptrace */
+ if (profile_unconfined(tracer) || tracer == tracee)
+ return 0;
+
+ aad(sa)->label = &tracer->label;
+ aad(sa)->target = tracee->base.hname;
+ aad(sa)->request = 0;
+ aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE, 1);
+ return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb);
}
/**
* aa_may_ptrace - test if tracer task can trace the tracee
- * @tracer_task: task who will do the tracing (NOT NULL)
- * @tracer: profile of the task doing the tracing (NOT NULL)
- * @tracee: task to be traced
- * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH
+ * @tracer: label of the task doing the tracing (NOT NULL)
+ * @tracee: task label to be traced
+ * @request: permission request
*
* Returns: %0 else error code if permission denied or error
*/
-int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
- struct aa_profile *tracee, unsigned int mode)
+int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
+ u32 request)
{
- /* TODO: currently only based on capability, not extended ptrace
- * rules,
- * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
- */
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
- if (unconfined(tracer) || tracer == tracee)
- return 0;
- /* log this capability request */
- return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
+ return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm,
+ request, &sa);
+}
+
+
+static inline int map_signal_num(int sig)
+{
+ if (sig > SIGRTMAX)
+ return SIGUNKNOWN;
+ else if (sig >= SIGRTMIN)
+ return sig - SIGRTMIN + 128; /* rt sigs mapped to 128 */
+ else if (sig <= MAXMAPPED_SIG)
+ return sig_map[sig];
+ return SIGUNKNOWN;
}
/**
- * aa_ptrace - do ptrace permission check and auditing
- * @tracer: task doing the tracing (NOT NULL)
- * @tracee: task being traced (NOT NULL)
- * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH
- *
- * Returns: %0 else error code if permission denied or error
+ * audit_file_mask - convert mask to permission string
+ * @buffer: buffer to write string to (NOT NULL)
+ * @mask: permission mask to convert
+ */
+static void audit_signal_mask(struct audit_buffer *ab, u32 mask)
+{
+ if (mask & MAY_READ)
+ audit_log_string(ab, "receive");
+ if (mask & MAY_WRITE)
+ audit_log_string(ab, "send");
+}
+
+/**
+ * audit_cb - call back for signal specific audit fields
+ * @ab: audit_buffer (NOT NULL)
+ * @va: audit struct to audit values of (NOT NULL)
*/
-int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
- unsigned int mode)
+static void audit_signal_cb(struct audit_buffer *ab, void *va)
{
- /*
- * tracer can ptrace tracee when
- * - tracer is unconfined ||
- * - 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;
- /* cred released below */
- const struct cred *cred = get_task_cred(tracer);
- int error = 0;
- tracer_p = aa_cred_profile(cred);
-
- if (!unconfined(tracer_p)) {
- /* lcred released below */
- const struct cred *lcred = get_task_cred(tracee);
- struct aa_profile *tracee_p = aa_cred_profile(lcred);
-
- error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode);
- error = aa_audit_ptrace(tracer_p, tracee_p, error);
-
- put_cred(lcred);
+ struct common_audit_data *sa = va;
+
+ if (aad(sa)->request & AA_SIGNAL_PERM_MASK) {
+ audit_log_format(ab, " requested_mask=");
+ audit_signal_mask(ab, aad(sa)->request);
+ if (aad(sa)->denied & AA_SIGNAL_PERM_MASK) {
+ audit_log_format(ab, " denied_mask=");
+ audit_signal_mask(ab, aad(sa)->denied);
+ }
}
- put_cred(cred);
+ if (aad(sa)->signal <= MAXMAPPED_SIG)
+ audit_log_format(ab, " signal=%s", sig_names[aad(sa)->signal]);
+ else
+ audit_log_format(ab, " signal=rtmin+%d",
+ aad(sa)->signal - 128);
+ audit_log_format(ab, " peer=");
+ audit_log_untrustedstring(ab, aad(sa)->target);
+}
- return error;
+/* TODO: update to handle compound name&name2, conditionals */
+static void profile_match_signal(struct aa_profile *profile, const char *label,
+ int signal, struct aa_perms *perms)
+{
+ unsigned int state;
+ if (profile->policy.dfa) {
+ /* TODO: secondary cache check <profile, profile, perm> */
+ state = aa_dfa_next(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_SIGNAL],
+ signal);
+ state = aa_dfa_match(profile->policy.dfa, state, label);
+ aa_compute_perms(profile->policy.dfa, state, perms);
+ } else
+ memset(perms, 0, sizeof(*perms));
+}
+
+static int profile_signal_perm(struct aa_profile *profile,
+ struct aa_profile *peer, u32 request,
+ struct common_audit_data *sa)
+{
+ struct aa_perms perms;
+
+ if (profile_unconfined(profile) ||
+ !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL))
+ return 0;
+
+ aad(sa)->target = peer->base.hname;
+ profile_match_signal(profile, aa_peer_name(peer), aad(sa)->signal,
+ &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ return aa_check_perms(profile, &perms, request, sa, audit_signal_cb);
+}
+
+static int aa_signal_cross_perm(struct aa_profile *sender,
+ struct aa_profile *target,
+ struct common_audit_data *sa)
+{
+ return xcheck(profile_signal_perm(sender, target, MAY_WRITE, sa),
+ profile_signal_perm(target, sender, MAY_READ, sa));
+}
+
+int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig)
+{
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL);
+ aad(&sa)->signal = map_signal_num(sig);
+ return xcheck_labels_profiles(sender, target, aa_signal_cross_perm,
+ &sa);
}
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
new file mode 100644
index 0000000..311423e
--- /dev/null
+++ b/security/apparmor/label.c
@@ -0,0 +1,1844 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor label definitions
+ *
+ * Copyright 2013 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 <linux/audit.h>
+#include <linux/seq_file.h>
+
+#include "include/apparmor.h"
+#include "include/label.h"
+#include "include/policy.h"
+#include "include/sid.h"
+
+
+/*
+ * the aa_label represents the set of profiles confining an object
+ *
+ * Labels maintain a reference count to the set of pointers they reference
+ * Labels are ref counted by
+ * tasks and object via the security field/security context off the field
+ * code - will take a ref count on a label if it needs the label
+ * beyond what is possible with an rcu_read_lock.
+ * profiles - each profile is a label
+ * sids - a pinned sid will keep a refcount of the label it is
+ * referencing
+ * objects - inode, files, sockets, ...
+ *
+ * Labels are not ref counted by the label set, so they maybe removed and
+ * freed when no longer in use.
+ *
+ */
+
+static void free_replacedby(struct aa_replacedby *r)
+{
+ if (r) {
+ /* r->label will not updated any more as r is dead */
+ aa_put_label(rcu_dereference_protected(r->label, true));
+ kzfree(r);
+ }
+}
+
+void aa_free_replacedby_kref(struct kref *kref)
+{
+ struct aa_replacedby *r = container_of(kref, struct aa_replacedby,
+ count);
+ free_replacedby(r);
+}
+
+struct aa_replacedby *aa_alloc_replacedby(struct aa_label *l)
+{
+ struct aa_replacedby *r;
+
+ r = kzalloc(sizeof(struct aa_replacedby), GFP_KERNEL);
+ if (r) {
+ kref_init(&r->count);
+ rcu_assign_pointer(r->label, aa_get_label(l));
+ }
+ return r;
+}
+
+/* requires profile list write lock held */
+void __aa_update_replacedby(struct aa_label *orig, struct aa_label *new)
+{
+ struct aa_label *tmp;
+
+ AA_BUG(!orig);
+ AA_BUG(!new);
+ AA_BUG(!mutex_is_locked(&labels_ns(orig)->lock));
+
+ tmp = rcu_dereference_protected(orig->replacedby->label,
+ &labels_ns(orig)->lock);
+ rcu_assign_pointer(orig->replacedby->label, aa_get_label(new));
+ orig->flags |= FLAG_INVALID;
+ aa_put_label(tmp);
+}
+
+/* helper fn for label_for_each_confined */
+int aa_label_next_confined(struct aa_label *l, int i)
+{
+ AA_BUG(!l);
+ AA_BUG(i < 0);
+
+ for (; i < l->size; i++) {
+ if (!profile_unconfined(l->ent[i]))
+ return i;
+ }
+
+ return i;
+}
+
+#if 0
+static int label_profile_pos(struct aa_label *l, struct aa_profile *profile)
+{
+ struct aa_profile *p;
+ struct label_it i;
+
+ AA_BUG(!profile);
+ AA_BUG(!l);
+
+ label_for_each(i, l, p) {
+ if (p == profile)
+ return i.i;
+ }
+
+ return -1;
+}
+#endif
+
+#if 0
+static bool profile_in_label(struct aa_profile *profile, struct aa_label *l)
+{
+ return label_profile_pos(l, profile) != -1;
+}
+#endif
+
+static bool label_profiles_unconfined(struct aa_label *label)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+
+ AA_BUG(!label);
+
+ label_for_each(i, label, profile) {
+ if (!profile_unconfined(profile))
+ return false;
+ }
+
+ return true;
+}
+
+static int profile_cmp(struct aa_profile *a, struct aa_profile *b);
+/**
+ * aa_label_next_not_in_set - return the next profile of @sub not in @set
+ * @I: label iterator
+ * @set: label to test against
+ * @sub: label to if is subset of @set
+ *
+ * Returns: profile in @sub that is not in @set, with iterator set pos after
+ * else NULL if @sub is a subset of @set
+ */
+struct aa_profile * aa_label_next_not_in_set(struct label_it *I,
+ struct aa_label *set,
+ struct aa_label *sub)
+{
+ AA_BUG(!set);
+ AA_BUG(!I);
+ AA_BUG(I->i < 0);
+ AA_BUG(I->i > set->size);
+ AA_BUG(!sub);
+ AA_BUG(I->j < 0);
+ AA_BUG(I->j > sub->size);
+
+ while (I->j < sub->size && I->i < set->size) {
+ int res = profile_cmp(sub->ent[I->j], set->ent[I->i]);
+ if (res == 0) {
+ (I->j)++;
+ (I->i)++;
+ } else if (res > 0)
+ (I->i)++;
+ else
+ return sub->ent[(I->j)++];
+ }
+
+ if (I->j < sub->size)
+ return sub->ent[(I->j)++];
+
+ return NULL;
+}
+
+/**
+ * aa_label_is_subset - test if @sub is a subset of @set
+ * @set: label to test against
+ * @sub: label to test if is subset of @set
+ *
+ * Returns: true if @sub is subset of @set
+ * else false
+ */
+bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub)
+{
+ struct label_it i = { };
+
+ AA_BUG(!set);
+ AA_BUG(!sub);
+
+ if (sub == set)
+ return true;
+
+ return aa_label_next_not_in_set(&i, set, sub) == NULL;
+}
+
+void aa_label_destroy(struct aa_label *label)
+{
+ AA_BUG(!label);
+
+ if (label_invalid(label))
+ labelsetstats_dec(labels_set(label), invalid);
+
+ if (!label_isprofile(label)) {
+ struct aa_profile *profile;
+ struct label_it i;
+
+ aa_put_str(label->hname);
+
+ label_for_each(i, label, profile)
+ aa_put_profile(profile);
+ }
+
+ aa_free_sid(label->sid);
+ aa_put_replacedby(label->replacedby);
+}
+
+void aa_label_free(struct aa_label *label)
+{
+ if (!label)
+ return;
+
+ aa_label_destroy(label);
+ labelstats_inc(freed);
+ kzfree(label);
+}
+
+static void label_free_rcu(struct rcu_head *head)
+{
+ struct aa_label *l = container_of(head, struct aa_label, rcu);
+
+ if (l->flags & FLAG_NS_COUNT)
+ aa_free_namespace(labels_ns(l));
+ else if (label_isprofile(l))
+ aa_free_profile(labels_profile(l));
+ else
+ aa_label_free(l);
+}
+
+bool aa_label_remove(struct aa_labelset *ls, struct aa_label *label);
+void aa_label_kref(struct kref *kref)
+{
+ struct aa_label *l = container_of(kref, struct aa_label, count);
+ struct aa_namespace *ns = labels_ns(l);
+
+ if (!ns) {
+ /* never live, no rcu callback needed, just using the fn */
+ label_free_rcu(&l->rcu);
+ return;
+ }
+
+ (void) aa_label_remove(&ns->labels, l);
+
+ /* TODO: if compound label and not invalid add to reclaim cache */
+ call_rcu(&l->rcu, label_free_rcu);
+}
+
+bool aa_label_init(struct aa_label *label, int size)
+{
+ AA_BUG(!label);
+ AA_BUG(size < 1);
+
+ label->sid = aa_alloc_sid();
+ if (label->sid == AA_SID_INVALID)
+ return false;
+
+ label->size = size; /* doesn't include null */
+ label->ent[size] = NULL; /* null terminate */
+ kref_init(&label->count);
+ RB_CLEAR_NODE(&label->node);
+
+ return true;
+}
+
+/**
+ * aa_label_alloc - allocate a label with a profile vector of @size length
+ * @size: size of profile vector in the label
+ * @gfp: memory allocation type
+ *
+ * Returns: new label
+ * else NULL if failed
+ */
+struct aa_label *aa_label_alloc(int size, gfp_t gfp)
+{
+ struct aa_label *label;
+
+ AA_BUG(size < 1);
+
+ /* vector: size - 2 (size of array in label struct) + 1 for null */
+ label = kzalloc(sizeof(*label) + sizeof(struct aa_label *) * (size - 1),
+ gfp);
+ AA_DEBUG("%s (%p)\n", __func__, label);
+ if (!label)
+ goto fail;
+
+ if (!aa_label_init(label, size))
+ goto fail;
+
+ labelstats_inc(allocated);
+
+ return label;
+
+fail:
+ kfree(label);
+ labelstats_inc(failed);
+
+ return NULL;
+}
+
+static bool __aa_label_remove(struct aa_labelset *ls, struct aa_label *label)
+{
+ AA_BUG(!ls);
+ AA_BUG(!label);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(labels_set(label) != ls);
+
+ if (label_invalid(label))
+ labelstats_dec(invalid_intree);
+ else
+ __label_invalidate(label);
+
+ if (label->flags & FLAG_IN_TREE) {
+ labelsetstats_dec(ls, intree);
+ rb_erase(&label->node, &ls->root);
+ label->flags &= ~FLAG_IN_TREE;
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * aa_label_remove - remove a label from the labelset
+ * @ls: set to remove the label from
+ * @l: label to remove
+ *
+ * Returns: true if @l was removed from the tree
+ * else @l was not in tree so it could not be removed
+ */
+bool aa_label_remove(struct aa_labelset *ls, struct aa_label *l)
+{
+ unsigned long flags;
+ bool res;
+
+ write_lock_irqsave(&ls->lock, flags);
+ res = __aa_label_remove(ls, l);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return res;
+}
+
+#if 0
+/* don't use when using ptr comparisons because nodes should never be
+ * the same
+ */
+static bool __aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+ struct aa_label *new)
+{
+ AA_BUG(!ls);
+ AA_BUG(!old);
+ AA_BUG(!new);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(labels_set(old) != ls);
+ AA_BUG(new->flags & FLAG_IN_TREE);
+
+ if (label_invalid(old))
+ labelstats_dec(invalid_intree);
+ else
+ __label_invalidate(old);
+
+ if (old->flags & FLAG_IN_TREE) {
+ rb_replace_node(&old->node, &new->node, &ls->root);
+ old->flags &= ~FLAG_IN_TREE;
+ new->flags |= FLAG_IN_TREE;
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+static struct aa_label *__aa_label_insert(struct aa_labelset *ls,
+ struct aa_label *l);
+
+static struct aa_label *__aa_label_remove_and_insert(struct aa_labelset *ls,
+ struct aa_label *remove,
+ struct aa_label *insert)
+{
+ AA_BUG(!ls);
+ AA_BUG(!remove);
+ AA_BUG(!insert);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(labels_set(remove) != ls);
+ AA_BUG(insert->flags & FLAG_IN_TREE);
+
+ __aa_label_remove(ls, remove);
+ return __aa_label_insert(ls, insert);
+}
+
+struct aa_label *aa_label_remove_and_insert(struct aa_labelset *ls,
+ struct aa_label *remove,
+ struct aa_label *insert)
+{
+ unsigned long flags;
+ struct aa_label *l;
+
+ write_lock_irqsave(&ls->lock, flags);
+ l = aa_get_label(__aa_label_remove_and_insert(ls, remove, insert));
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return l;
+}
+
+/**
+ * aa_label_replace - replace a label @old with a new version @new
+ * @ls: labelset being manipulated
+ * @old: label to replace
+ * @new: label replacing @old
+ *
+ * Returns: true if @old was in tree and replaced
+ * else @old was not in tree, and @new was not inserted
+ */
+bool aa_label_replace(struct aa_labelset *ls, struct aa_label *old,
+ struct aa_label *new)
+{
+ struct aa_label *l;
+ unsigned long flags;
+ bool res;
+
+ write_lock_irqsave(&ls->lock, flags);
+ if (!(old->flags & FLAG_IN_TREE))
+ l = __aa_label_insert(ls, new);
+ else
+ l = __aa_label_remove_and_insert(ls, old, new);
+ res = (l == new);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return res;
+}
+
+static int ns_cmp(struct aa_namespace *a, struct aa_namespace *b)
+{
+ int res;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!a->base.name);
+ AA_BUG(!b->base.name);
+
+ if (a == b)
+ return 0;
+
+ res = a->level - b->level;
+ if (res)
+ return res;
+
+ return strcmp(a->base.name, b->base.name);
+}
+
+/**
+ * profile_cmp - profile comparision for set ordering
+ * @a: profile to compare (NOT NULL)
+ * @b: profile to compare (NOT NULL)
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
+{
+ int res;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!a->ns);
+ AA_BUG(!b->ns);
+ AA_BUG(!a->base.hname);
+ AA_BUG(!b->base.hname);
+
+ if (a == b || a->base.hname == b->base.hname)
+ return 0;
+ res = ns_cmp(a->ns, b->ns);
+ if (res)
+ return res;
+
+ return strcmp(a->base.hname, b->base.hname);
+}
+
+/**
+ * label_vec_cmp - label comparision for set ordering
+ * @a: label to compare (NOT NULL)
+ * @vec: vector of profiles to compare (NOT NULL)
+ * @n: length of @vec
+ *
+ * Returns: <0 if a < vec
+ * ==0 if a == vec
+ * >0 if a > vec
+ */
+static int label_vec_cmp(struct aa_label *a, struct aa_profile **vec, int n)
+{
+ int i;
+
+ AA_BUG(!a);
+ AA_BUG(!vec);
+ AA_BUG(!*vec);
+ AA_BUG(n <= 0);
+
+ for (i = 0; i < a->size && i < n; i++) {
+ int res = profile_cmp(a->ent[i], vec[i]);
+ if (res != 0)
+ return res;
+ }
+
+ return a->size - n;
+}
+
+/**
+ * label_cmp - label comparision for set ordering
+ * @a: label to compare (NOT NULL)
+ * @b: label to compare (NOT NULL)
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int label_cmp(struct aa_label *a, struct aa_label *b)
+{
+ AA_BUG(!b);
+
+ if (a == b)
+ return 0;
+
+ return label_vec_cmp(a, b->ent, b->size);
+}
+
+/**
+ * __aa_label_vec_find - find label that matches @vec in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @vec: vec of profiles to find matching label for (NOT NULL)
+ * @n: length of @vec
+ *
+ * Requires: @ls lock held
+ * caller to hold a valid ref on l
+ *
+ * Returns: unref counted @label if matching label is in tree
+ * else NULL if @vec equiv is not in tree
+ */
+static struct aa_label *__aa_label_vec_find(struct aa_labelset *ls,
+ struct aa_profile **vec, int n)
+{
+ struct rb_node *node;
+
+ AA_BUG(!ls);
+ AA_BUG(!vec);
+ AA_BUG(!*vec);
+ AA_BUG(n <= 0);
+
+ node = ls->root.rb_node;
+ while (node) {
+ struct aa_label *this = rb_entry(node, struct aa_label, node);
+ int result = label_vec_cmp(this, vec, n);
+
+ if (result > 0)
+ node = node->rb_left;
+ else if (result < 0)
+ node = node->rb_right;
+ else
+ return this;
+ }
+
+ return NULL;
+}
+
+/**
+ * __aa_label_find - find label @l in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @l: label to find (NOT NULL)
+ *
+ * Requires: @ls lock held
+ * caller to hold a valid ref on l
+ *
+ * Returns: unref counted @l if @l is in tree
+ * unref counted label that is equiv to @l in tree
+ * else NULL if @l or equiv is not in tree
+ */
+static struct aa_label *__aa_label_find(struct aa_labelset *ls,
+ struct aa_label *l)
+{
+ AA_BUG(!l);
+
+ return __aa_label_vec_find(ls, l->ent, l->size);
+}
+
+/**
+ * aa_label_vec_find - find label @l in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @vec: array of profiles to find equiv label for (NOT NULL)
+ * @n: length of @vec
+ *
+ * Returns: refcounted label if @vec equiv is in tree
+ * else NULL if @vec equiv is not in tree
+ */
+struct aa_label *aa_label_vec_find(struct aa_labelset *ls,
+ struct aa_profile **vec,
+ int n)
+{
+ struct aa_label *label;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+ AA_BUG(!vec);
+ AA_BUG(!*vec);
+ AA_BUG(n <= 0);
+
+ read_lock_irqsave(&ls->lock, flags);
+ label = aa_get_label(__aa_label_vec_find(ls, vec, n));
+ labelstats_inc(sread);
+ read_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/**
+ * aa_label_find - find label @l in label set
+ * @ls: set of labels to search (NOT NULL)
+ * @l: label to find (NOT NULL)
+ *
+ * Requires: caller to hold a valid ref on l
+ *
+ * Returns: refcounted @l if @l is in tree
+ * refcounted label that is equiv to @l in tree
+ * else NULL if @l or equiv is not in tree
+ */
+struct aa_label *aa_label_find(struct aa_labelset *ls, struct aa_label *l)
+{
+ AA_BUG(!l);
+
+ return aa_label_vec_find(ls, l->ent, l->size);
+}
+
+/**
+ * __aa_label_insert - attempt to insert @l into a label set
+ * @ls: set of labels to insert @l into (NOT NULL)
+ * @l: new label to insert (NOT NULL)
+ *
+ * Requires: @ls->lock
+ * caller to hold a valid ref on l
+ *
+ * Returns: @l if successful in inserting @l
+ * else ref counted equivalent label that is already in the set.
+ */
+static struct aa_label *__aa_label_insert(struct aa_labelset *ls,
+ struct aa_label *l)
+{
+ struct rb_node **new, *parent = NULL;
+
+ AA_BUG(!ls);
+ AA_BUG(!l);
+ AA_BUG(!write_is_locked(&ls->lock));
+ AA_BUG(l->flags & FLAG_IN_TREE);
+
+ /* Figure out where to put new node */
+ new = &ls->root.rb_node;
+ while (*new) {
+ struct aa_label *this = rb_entry(*new, struct aa_label, node);
+ int result = label_cmp(l, this);
+
+ parent = *new;
+ if (result == 0) {
+ labelsetstats_inc(ls, existing);
+ return this;
+ } else if (result < 0)
+ new = &((*new)->rb_left);
+ else /* (result > 0) */
+ new = &((*new)->rb_right);
+ }
+
+ /* Add new node and rebalance tree. */
+ rb_link_node(&l->node, parent, new);
+ rb_insert_color(&l->node, &ls->root);
+ l->flags |= FLAG_IN_TREE;
+ labelsetstats_inc(ls, insert);
+ labelsetstats_inc(ls, intree);
+
+ return l;
+}
+
+/**
+ * aa_label_insert - insert label @l into @ls or return existing label
+ * @ls - labelset to insert @l into
+ * @l - label to insert
+ *
+ * Requires: caller to hold a valid ref on l
+ *
+ * Returns: ref counted @l if successful in inserting @l
+ * else ref counted equivalent label that is already in the set
+ */
+struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l)
+{
+ struct aa_label *label;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+ AA_BUG(!l);
+
+ /* check if label exists before taking lock */
+ if (!label_invalid(l)) {
+ read_lock_irqsave(&ls->lock, flags);
+ label = aa_get_label(__aa_label_find(ls, l));
+ read_unlock_irqrestore(&ls->lock, flags);
+ labelstats_inc(fread);
+ if (label)
+ return label;
+ }
+
+ write_lock_irqsave(&ls->lock, flags);
+ label = aa_get_label(__aa_label_insert(ls, l));
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+struct aa_label *aa_label_vec_find_or_create(struct aa_labelset *ls,
+ struct aa_profile **vec, int len)
+{
+ struct aa_label *label = aa_label_vec_find(ls, vec, len);
+ if (label)
+ return label;
+
+ return aa_label_vec_merge(vec, len, GFP_KERNEL);
+}
+
+/**
+ * aa_label_next_in_merge - find the next profile when merging @a and @b
+ * @I: label iterator
+ * @a: label to merge
+ * @b: label to merge
+ *
+ * Returns: next profile
+ * else null if no more profiles
+ */
+struct aa_profile *aa_label_next_in_merge(struct label_it *I,
+ struct aa_label *a,
+ struct aa_label *b)
+{
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!I);
+ AA_BUG(I->i < 0);
+ AA_BUG(I->i > a->size);
+ AA_BUG(I->j < 0);
+ AA_BUG(I->j > b->size);
+
+ if (I->i < a->size) {
+ if (I->j < b->size) {
+ int res = profile_cmp(a->ent[I->i], b->ent[I->j]);
+ if (res > 0)
+ return b->ent[(I->j)++];
+ if (res == 0)
+ (I->j)++;
+ }
+
+ return a->ent[(I->i)++];
+ }
+
+ if (I->j < b->size)
+ return b->ent[(I->j)++];
+
+ return NULL;
+}
+
+/**
+ * label_merge_cmp - cmp of @a merging with @b against @z for set ordering
+ * @a: label to merge then compare (NOT NULL)
+ * @b: label to merge then compare (NOT NULL)
+ * @z: label to compare merge against (NOT NULL)
+ *
+ * Assumes: using the most recent versions of @a, @b, and @z
+ *
+ * Returns: <0 if a < b
+ * ==0 if a == b
+ * >0 if a > b
+ */
+static int label_merge_cmp(struct aa_label *a, struct aa_label *b,
+ struct aa_label *z)
+{
+ struct aa_profile *p = NULL;
+ struct label_it i = { };
+ int k;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+ AA_BUG(!z);
+
+ for (k = 0;
+ k < z->size && (p = aa_label_next_in_merge(&i, a, b));
+ k++) {
+ int res = profile_cmp(p, z->ent[k]);
+
+ if (res != 0)
+ return res;
+ }
+
+ if (p)
+ return 1;
+ else if (k < z->size)
+ return -1;
+ return 0;
+}
+
+#if 0
+/**
+ * label_merge_len - find the length of the merge of @a and @b
+ * @a: label to merge (NOT NULL)
+ * @b: label to merge (NOT NULL)
+ *
+ * Assumes: using newest versions of labels @a and @b
+ *
+ * Returns: length of a label vector for merge of @a and @b
+ */
+static int label_merge_len(struct aa_label *a, struct aa_label *b)
+{
+ int len = a->size + b->size;
+ int i, j;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ /* find entries in common and remove from count */
+ for (i = j = 0; i < a->size && j < b->size; ) {
+ int res = profile_cmp(a->ent[i], b->ent[j]);
+ if (res == 0) {
+ len--;
+ i++;
+ j++;
+ } else if (res < 0)
+ i++;
+ else
+ j++;
+ }
+
+ return len;
+}
+#endif
+
+/**
+ * aa_sort_and_merge_profiles - canonical sort and merge a list of profiles
+ * @n: number of refcounted profiles in the list (@n > 0)
+ * @ps: list of profiles to sort and merge
+ *
+ * Returns: the number of duplicates eliminated == references put
+ */
+static int aa_sort_and_merge_profiles(int n, struct aa_profile **ps)
+{
+ int i, dups = 0;
+
+ AA_BUG(n < 1);
+ AA_BUG(!ps);
+
+ /* label lists are usually small so just use insertion sort */
+ for (i = 1; i < n; i++) {
+ struct aa_profile *tmp = ps[i];
+ int pos, j;
+
+ for (pos = i - 1 - dups; pos >= 0; pos--) {
+ int res = profile_cmp(ps[pos], tmp);
+ if (res == 0) {
+ aa_put_profile(tmp);
+ dups++;
+ goto continue_outer;
+ } else if (res < 0)
+ break;
+ }
+ pos++;
+
+ for (j = i - dups; j > pos; j--)
+ ps[j] = ps[j - 1];
+ ps[pos] = tmp;
+ continue_outer:
+ ; /* sigh empty statement required after the label */
+ }
+
+ return dups;
+}
+
+/**
+ * __label_merge - create a new label by merging @a and @b
+ * @l: preallocated label to merge into (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Returns: ref counted label either l if merge is unique
+ * a if b is a subset of a
+ * b if a is a subset of b
+ *
+ * NOTE: will not use l if the merge results in l == a or b
+ *
+ * Must be used within labelset write lock to avoid racing with
+ * label invalidation.
+ */
+static struct aa_label *__label_merge(struct aa_label *l, struct aa_label *a,
+ struct aa_label *b)
+{
+ struct aa_profile *next;
+ struct label_it i;
+ int k = 0, invcount = 0;
+
+ AA_BUG(!a);
+ AA_BUG(a->size < 0);
+ AA_BUG(!b);
+ AA_BUG(b->size < 0);
+ AA_BUG(!l);
+ AA_BUG(l->size < a->size + b->size);
+
+ if (a == b)
+ return aa_get_label(a);
+
+ label_for_each_in_merge(i, a, b, next) {
+ if (PROFILE_INVALID(next)) {
+ l->ent[k] = aa_get_newest_profile(next);
+ if (next->label.replacedby !=
+ l->ent[k]->label.replacedby)
+ invcount++;
+ k++;
+ } else
+ l->ent[k++] = aa_get_profile(next);
+ }
+ /* set to actual size which is <= allocated len */
+ l->size = k;
+ l->ent[k] = NULL;
+
+ if (invcount) {
+ l->size -= aa_sort_and_merge_profiles(l->size, &l->ent[0]);
+ if (label_profiles_unconfined(l))
+ l->flags |= FLAG_UNCONFINED;
+ } else {
+ /* merge is same as at least one of the labels */
+ if (k == a->size)
+ return aa_get_label(a);
+ else if (k == b->size)
+ return aa_get_label(b);
+
+ l->flags |= a->flags & b->flags & FLAG_UNCONFINED;
+ }
+
+ return aa_get_label(l);
+}
+
+/**
+ * labelset_of_merge - find into which labelset a merged label should be inserted
+ * @a: label to merge and insert
+ * @b: label to merge and insert
+ *
+ * Returns: labelset that the merged label should be inserted into
+ */
+static struct aa_labelset *labelset_of_merge(struct aa_label *a, struct aa_label *b)
+{
+ struct aa_namespace *nsa = labels_ns(a);
+ struct aa_namespace *nsb = labels_ns(b);
+
+ if (ns_cmp(nsa, nsb) <= 0)
+ return &nsa->labels;
+ return &nsb->labels;
+}
+
+/**
+ * __aa_label_find_merge - find label that is equiv to merge of @a and @b
+ * @ls: set of labels to search (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Requires: read_lock held
+ *
+ * Returns: unref counted label that is equiv to merge of @a and @b
+ * else NULL if merge of @a and @b is not in set
+ */
+static struct aa_label *__aa_label_find_merge(struct aa_labelset *ls,
+ struct aa_label *a,
+ struct aa_label *b)
+{
+ struct rb_node *node;
+
+ AA_BUG(!ls);
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ if (a == b)
+ return __aa_label_find(ls, a);
+
+ node = ls->root.rb_node;
+ while (node) {
+ struct aa_label *this = container_of(node, struct aa_label,
+ node);
+ int result = label_merge_cmp(a, b, this);
+
+ if (result < 0)
+ node = node->rb_left;
+ else if (result > 0)
+ node = node->rb_right;
+ else
+ return this;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * __aa_label_find_merge - find label that is equiv to merge of @a and @b
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ *
+ * Requires: labels be fully constructed with a valid ns
+ *
+ * Returns: ref counted label that is equiv to merge of @a and @b
+ * else NULL if merge of @a and @b is not in set
+ */
+struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b)
+{
+ struct aa_labelset *ls;
+ struct aa_label *label, *ar = NULL, *br = NULL;
+ unsigned long flags;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ ls = labelset_of_merge(a, b);
+ read_lock_irqsave(&ls->lock, flags);
+ if (label_invalid(a))
+ a = ar = aa_get_newest_label(a);
+ if (label_invalid(b))
+ b = br = aa_get_newest_label(b);
+ label = aa_get_label(__aa_label_find_merge(ls, a, b));
+ read_unlock_irqrestore(&ls->lock, flags);
+ aa_put_label(ar);
+ aa_put_label(br);
+ labelsetstats_inc(ls, msread);
+
+ return label;
+}
+
+/**
+ * aa_label_merge - attempt to insert new merged label of @a and @b
+ * @ls: set of labels to insert label into (NOT NULL)
+ * @a: label to merge with @b (NOT NULL)
+ * @b: label to merge with @a (NOT NULL)
+ * @gfp: memory allocation type
+ *
+ * Requires: caller to hold valid refs on @a and @b
+ * labels be fully constructed with a valid ns
+ *
+ * Returns: ref counted new label if successful in inserting merge of a & b
+ * else ref counted equivalent label that is already in the set.
+ * else NULL if could not create label (-ENOMEM)
+ */
+struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
+ gfp_t gfp)
+{
+ struct aa_label *label = NULL;
+ struct aa_labelset *ls;
+ unsigned long flags;
+
+ AA_BUG(!a);
+ AA_BUG(!b);
+
+ if (a == b)
+ return aa_get_newest_label(a);
+
+ ls = labelset_of_merge(a, b);
+
+ /* TODO: enable when read side is lockless
+ * check if label exists before taking locks
+ if (!label_invalid(a) && !label_invalid(b))
+ label = aa_label_find_merge(a, b);
+ */
+
+ if (!label) {
+ struct aa_label *new, *l;
+
+ a = aa_get_newest_label(a);
+ b = aa_get_newest_label(b);
+
+ /* could use label_merge_len(a, b), but requires double
+ * comparison for small savings
+ */
+ new = aa_label_alloc(a->size + b->size, gfp);
+ if (!new)
+ return NULL;
+
+ write_lock_irqsave(&ls->lock, flags);
+ l = __label_merge(new, a, b);
+ if (l != new) {
+ /* new may not be fully setup so no put_label */
+ aa_label_free(new);
+ new = NULL;
+ }
+ if (!(l->flags & FLAG_IN_TREE))
+ label = aa_get_label(__aa_label_insert(ls, l));
+ write_unlock_irqrestore(&ls->lock, flags);
+ aa_put_label(new);
+ aa_put_label(l);
+ aa_put_label(a);
+ aa_put_label(b);
+ }
+
+ return label;
+}
+
+/* requires sort and merge done first */
+struct aa_label *aa_label_vec_merge(struct aa_profile **vec, int len,
+ gfp_t gfp)
+{
+ struct aa_label *label = NULL;
+ struct aa_labelset *ls;
+ unsigned long flags;
+ struct aa_label *new;
+ int i;
+
+ AA_BUG(!vec);
+
+ if (len == 1)
+ return aa_get_label(&vec[0]->label);
+
+ ls = labels_set(&vec[len - 1]->label);
+
+ /* TODO: enable when read side is lockless
+ * check if label exists before taking locks
+ */
+ new = aa_label_alloc(len, gfp);
+ if (!new)
+ return NULL;
+
+ write_lock_irqsave(&ls->lock, flags);
+ for (i = 0; i < len; i++) {
+ new->ent[i] = aa_get_profile(vec[i]);
+ label = __aa_label_insert(ls, new);
+ if (label != new) {
+ aa_get_label(label);
+ /* not fully constructed don't put */
+ aa_label_free(new);
+ }
+ }
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/**
+ * aa_update_label_name - update a label to have a stored name
+ * @ns: ns being viewed from (NOT NULL)
+ * @label: label to update (NOT NULL)
+ * @gfp: type of memory allocation
+ *
+ * Requires: labels_set(label) not locked in caller
+ *
+ * note: only updates the label name if it does not have a name already
+ * and if it is in the labelset
+ */
+bool aa_update_label_name(struct aa_namespace *ns, struct aa_label *label,
+ gfp_t gfp)
+{
+ struct aa_labelset *ls;
+ unsigned long flags;
+ char __counted *name;
+ bool res = false;
+
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ if (label->hname || labels_ns(label) != ns)
+ return res;
+
+ if (aa_label_acntsprint(&name, ns, label, false, gfp) == -1)
+ return res;
+
+ ls = labels_set(label);
+ write_lock_irqsave(&ls->lock, flags);
+ if (!label->hname && label->flags & FLAG_IN_TREE) {
+ label->hname = name;
+ res = true;
+ } else
+ aa_put_str(name);
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return res;
+}
+
+/* cached label name is present and visible
+ * @label->hname only exists if label is namespace hierachical */
+static inline bool label_name_visible(struct aa_namespace *ns,
+ struct aa_label *label)
+{
+ if (label->hname && labels_ns(label) == ns)
+ return true;
+
+ return false;
+}
+
+/* helper macro for snprint routines */
+#define update_for_len(total, len, size, str) \
+do { \
+ AA_BUG(len < 0); \
+ total += len; \
+ len = min(len, size); \
+ size -= len; \
+ str += len; \
+} while (0)
+
+/**
+ * aa_modename_snprint - print the mode name of a profile or label to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode of (NOT NULL)
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: will print every mode name visible (mode1)(mode2)(mode3)
+ * this is likely not what is desired for most interfaces
+ * use aa_mode_snprint to get the standard mode format
+ */
+static int aa_modename_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_label *label)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+ int total = 0;
+ size_t len;
+
+ label_for_each(i, label, profile) {
+ const char *modestr;
+ if (!aa_ns_visible(ns, profile->ns))
+ continue;
+ /* no mode for 'unconfined' */
+ if (profile_unconfined(profile) &&
+ profile == profile->ns->unconfined)
+ break;
+ modestr = aa_profile_mode_names[profile->mode];
+ len = snprintf(str, size, "(%s)", modestr);
+ update_for_len(total, len, size, str);
+ }
+
+ return total;
+}
+
+/**
+ * aa_modechr_snprint - print the mode chr of a profile or labels to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode chr of (NOT NULL)
+ *
+ * Returns: size of mode string written or would be written if larger than
+ * available buffer
+ *
+ * Note: will print the chr of every visible profile (123)
+ * this is likely not what is desired for most interfaces
+ * use aa_mode_snprint to get the standard mode format
+ */
+static int aa_modechr_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_label *label)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+ int total = 0;
+ size_t len;
+
+ len = snprintf(str, size, "(");
+ update_for_len(total, len, size, str);
+ label_for_each(i, label, profile) {
+ const char *modestr;
+ if (!aa_ns_visible(ns, profile->ns))
+ continue;
+ modestr = aa_profile_mode_names[profile->mode];
+ /* just the first char of the modestr */
+ len = snprintf(str, size, "%c", *modestr);
+ update_for_len(total, len, size, str);
+ }
+ len = snprintf(str, size, ")");
+ update_for_len(total, len, size, str);
+
+ return total;
+}
+
+/**
+ * aa_mode_snprint - print the mode of a profile or label to a buffer
+ * @str: buffer to write to (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to print the mode of (NOT NULL)
+ * @count: number of label entries to be printed (<= 0 if unknown)
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: dynamically switches between mode name, and mode char format as
+ * appropriate
+ * will not print anything if the label is not visible
+ */
+static int aa_mode_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_label *label, int count)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+
+ if (count <= 0) {
+ count = 0;
+ label_for_each(i, label, profile) {
+ if (aa_ns_visible(ns, profile->ns))
+ count++;
+ }
+ }
+
+ if (count == 0)
+ return 0;
+
+ if (count == 1)
+ return aa_modename_snprint(str, size, ns, label);
+
+ return aa_modechr_snprint(str, size, ns, label);
+}
+
+/**
+ * aa_snprint_profile - print a profile name to a buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @profile: profile to view (NOT NULL)
+ * @mode: whether to include the mode string
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: will not print anything if the profile is not visible
+ */
+int aa_profile_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_profile *profile, bool mode)
+{
+ const char *ns_name = aa_ns_name(ns, profile->ns);
+
+ AA_BUG(!str && size != 0);
+ AA_BUG(!ns);
+ AA_BUG(!profile);
+
+ if (!ns_name)
+ return 0;
+
+ if (mode && profile != profile->ns->unconfined) {
+ const char *modestr = aa_profile_mode_names[profile->mode];
+ if (strlen(ns_name))
+ return snprintf(str, size, ":%s://%s (%s)", ns_name,
+ profile->base.hname, modestr);
+ return snprintf(str, size, "%s (%s)", profile->base.hname,
+ modestr);
+ }
+
+ if (strlen(ns_name))
+ return snprintf(str, size, ":%s://%s", ns_name,
+ profile->base.hname);
+ return snprintf(str, size, "%s", profile->base.hname);
+}
+
+/**
+ * aa_label_snprint - print a label name to a string buffer
+ * @str: buffer to write to. (MAY BE NULL if @size == 0)
+ * @size: size of buffer
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to view (NOT NULL)
+ * @mode: whether to include the mode string
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ *
+ * Note: labels do not have to be strictly hierarchical to the ns as
+ * objects may be shared across different namespaces and thus
+ * pickup labeling from each ns. If a particular part of the
+ * label is not visible it will just be excluded. And if none
+ * of the label is visible "---" will be used.
+ */
+int aa_label_snprint(char *str, size_t size, struct aa_namespace *ns,
+ struct aa_label *label, bool mode)
+{
+ struct aa_profile *profile;
+ struct label_it i;
+ int count = 0, total = 0;
+ size_t len;
+
+ AA_BUG(!str && size != 0);
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ label_for_each(i, label, profile) {
+ if (aa_ns_visible(ns, profile->ns)) {
+ if (count > 0) {
+ len = snprintf(str, size, "//&");
+ update_for_len(total, len, size, str);
+ }
+ len = aa_profile_snprint(str, size, ns, profile, false);
+ update_for_len(total, len, size, str);
+ count++;
+ }
+ }
+
+ if (count == 0)
+ return snprintf(str, size, aa_hidden_ns_name);
+
+ /* count == 1 && ... is for backwards compat where the mode
+ * is not displayed for 'unconfined' in the current ns
+ */
+ if (mode &&
+ !(count == 1 && labels_ns(label) == ns &&
+ labels_profile(label) == ns->unconfined)) {
+ len = snprintf(str, size, " ");
+ update_for_len(total, len, size, str);
+ len = aa_mode_snprint(str, size, ns, label, count);
+ update_for_len(total, len, size, str);
+ }
+
+ return total;
+}
+#undef update_for_len
+
+/**
+ * aa_label_asprint - allocate a string buffer and print label into it
+ * @strp: Returns - the allocated buffer with the label name. (NOT NULL)
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to view (NOT NULL)
+ * @mode: whether to include the mode string
+ * @gfp: kernel memory allocation type
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ */
+int aa_label_asprint(char **strp, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp)
+{
+ int size;
+
+ AA_BUG(!strp);
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ size = aa_label_snprint(NULL, 0, ns, label, mode);
+ if (size < 0)
+ return size;
+
+ *strp = kmalloc(size + 1, gfp);
+ if (!*strp)
+ return -ENOMEM;
+ return aa_label_snprint(*strp, size + 1, ns, label, mode);
+}
+
+/**
+ * aa_label_acntsprint - allocate a __counted string buffer and print label
+ * @strp: buffer to write to. (MAY BE NULL if @size == 0)
+ * @ns: namespace profile is being viewed from (NOT NULL)
+ * @label: label to view (NOT NULL)
+ * @mode: whether to include the mode string
+ * @gfp: kernel memory allocation type
+ *
+ * Returns: size of name written or would be written if larger than
+ * available buffer
+ */
+int aa_label_acntsprint(char __counted **strp, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp)
+{
+ int size;
+
+ AA_BUG(!strp);
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ size = aa_label_snprint(NULL, 0, ns, label, mode);
+ if (size < 0)
+ return size;
+
+ *strp = aa_str_alloc(size + 1, gfp);
+ if (!*strp)
+ return -ENOMEM;
+ return aa_label_snprint(*strp, size + 1, ns, label, mode);
+}
+
+
+void aa_label_audit(struct audit_buffer *ab, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp)
+{
+ const char *str;
+ char *name = NULL;
+ int len;
+
+ AA_BUG(!ab);
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ if (label_name_visible(ns, label)) {
+ str = (char *) label->hname;
+ len = strlen(str);
+ } else {
+ labelstats_inc(audit_name_alloc);
+ len = aa_label_asprint(&name, ns, label, mode, gfp);
+ if (len == -1) {
+ labelstats_inc(audit_name_fail);
+ AA_DEBUG("label print error");
+ return;
+ }
+ str = name;
+ }
+
+ if (audit_string_contains_control(str, len))
+ audit_log_n_hex(ab, str, len);
+ else
+ audit_log_n_string(ab, str, len);
+
+ kfree(name);
+}
+
+void aa_label_seq_print(struct seq_file *f, struct aa_namespace *ns,
+ struct aa_label *label, bool mode, gfp_t gfp)
+{
+ AA_BUG(!f);
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ if (!label_name_visible(ns, label)) {
+ char *str;
+ int len;
+
+ labelstats_inc(seq_print_name_alloc);
+ len = aa_label_asprint(&str, ns, label, mode, gfp);
+ if (len == -1) {
+ labelstats_inc(seq_print_name_fail);
+ AA_DEBUG("label print error");
+ return;
+ }
+ seq_printf(f, "%s", str);
+ kfree(str);
+ } else
+ seq_printf(f, "%s", label->hname);
+}
+
+void aa_label_printk(struct aa_namespace *ns, struct aa_label *label, bool mode,
+ gfp_t gfp)
+{
+ char *str;
+ int len;
+
+ AA_BUG(!ns);
+ AA_BUG(!label);
+
+ if (!label_name_visible(ns, label)) {
+ labelstats_inc(printk_name_alloc);
+ len = aa_label_asprint(&str, ns, label, mode, gfp);
+ if (len == -1) {
+ labelstats_inc(printk_name_fail);
+ AA_DEBUG("label print error");
+ return;
+ }
+ printk("%s", str);
+ kfree(str);
+ } else
+ printk("%s", label->hname);
+}
+
+static int label_count_str_entries(const char *str)
+{
+ const char *split;
+ int count = 1;
+
+ AA_BUG(!str);
+
+ for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) {
+ count++;
+ str = split + 3;
+ }
+
+ return count;
+}
+
+/**
+ * aa_label_parse - parse, validate and convert a text string to a label
+ * @base: base label to use for lookups (NOT NULL)
+ * @str: null terminated text string (NOT NULL)
+ * @gfp: allocation type
+ * @create: true if should create compound labels if they don't exist
+ *
+ * Returns: the matching refcounted label if present
+ * else ERRPTR
+ */
+struct aa_label *aa_label_parse(struct aa_label *base, char *str, gfp_t gfp,
+ bool create)
+{
+ DEFINE_PROFILE_VEC(vec, tmp);
+ struct aa_label *l;
+ int i, len, error;
+ char *split;
+
+ AA_BUG(!base);
+ AA_BUG(!str);
+
+ len = label_count_str_entries(str);
+ error = aa_setup_profile_vec(vec, tmp, len);
+ if (error)
+ return ERR_PTR(error);
+
+ for (split = strstr(str, "//&"), i = 0; split && i < len; i++) {
+ vec[i] = aa_fqlookupn_profile(base, str, split - str);
+ if (!vec[i])
+ goto fail;
+ str = split + 3;
+ split = strstr(str, "//&");
+ }
+ vec[i] = aa_fqlookupn_profile(base, str, strlen(str));
+ if (!vec[i])
+ goto fail;
+ if (len == 1)
+ /* no need to free vec as len < LOCAL_VEC_ENTRIES */
+ return &vec[0]->label;
+
+ i = aa_sort_and_merge_profiles(len, vec);
+ len -= i;
+
+ if (create)
+ l = aa_label_vec_find_or_create(labels_set(base), vec, len);
+ else
+ l = aa_label_vec_find(labels_set(base), vec, len);
+ if (!l)
+ l = ERR_PTR(-ENOENT);
+
+out:
+ /* use adjusted len from after sort_and_merge, not original */
+ aa_cleanup_profile_vec(vec, tmp, len);
+ return l;
+
+fail:
+ l = ERR_PTR(-ENOENT);
+ goto out;
+}
+
+
+/**
+ * aa_labelset_destroy - remove all labels from the label set
+ * @ls: label set to cleanup (NOT NULL)
+ *
+ * Labels that are removed from the set may still exist beyond the set
+ * being destroyed depending on their reference counting
+ */
+void aa_labelset_destroy(struct aa_labelset *ls)
+{
+ struct rb_node *node;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+
+ write_lock_irqsave(&ls->lock, flags);
+ for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) {
+ struct aa_label *this = rb_entry(node, struct aa_label, node);
+ __aa_label_remove(ls, this);
+ }
+ write_unlock_irqrestore(&ls->lock, flags);
+}
+
+/*
+ * @ls: labelset to init (NOT NULL)
+ */
+void aa_labelset_init(struct aa_labelset *ls)
+{
+ AA_BUG(!ls);
+
+ rwlock_init(&ls->lock);
+ ls->root = RB_ROOT;
+ labelstats_init(&ls);
+}
+
+static struct aa_label *labelset_next_invalid(struct aa_labelset *ls)
+{
+ struct aa_label *label;
+ struct rb_node *node;
+ unsigned long flags;
+
+ AA_BUG(!ls);
+
+ read_lock_irqsave(&ls->lock, flags);
+
+ __labelset_for_each(ls, node) {
+ struct aa_profile *p;
+ struct label_it i;
+
+ label = rb_entry(node, struct aa_label, node);
+ if (label_invalid(label))
+ goto out;
+
+ label_for_each(i, label, p) {
+ if (PROFILE_INVALID(p))
+ goto out;
+ }
+ }
+ label = NULL;
+
+out:
+ aa_get_label(label);
+ read_unlock_irqrestore(&ls->lock, flags);
+
+ return label;
+}
+
+/**
+ * __label_update - insert updated version of @label into labelset
+ * @label - the label to update/repace
+ *
+ * Returns: new label that is up to date
+ * else NULL on failure
+ *
+ * Requires: @ns lock be held
+ *
+ * Note: worst case is the stale @label does not get updated and has
+ * to be updated at a later time.
+ */
+static struct aa_label *__label_update(struct aa_label *label)
+{
+ struct aa_label *l, *tmp;
+ struct aa_labelset *ls;
+ struct aa_profile *p;
+ struct label_it i;
+ unsigned long flags;
+ int invcount = 0;
+
+ AA_BUG(!label);
+ AA_BUG(!mutex_is_locked(&labels_ns(label)->lock));
+
+ l = aa_label_alloc(label->size, GFP_KERNEL);
+ if (!l)
+ return NULL;
+
+ if (!label->replacedby) {
+ struct aa_replacedby *r = aa_alloc_replacedby(l);
+ if (!r) {
+ aa_put_label(l);
+ return NULL;
+ }
+ /* only label update will set replacedby so ns lock is enough */
+ label->replacedby = r;
+ }
+
+ /* while holding the ns_lock will stop profile replacement, removal,
+ * and label updates, label merging and removal can be occuring
+ */
+
+ ls = labels_set(label);
+ write_lock_irqsave(&ls->lock, flags);
+ /* circular ref only broken by replace or remove */
+ l->replacedby = aa_get_replacedby(label->replacedby);
+ __aa_update_replacedby(label, l);
+
+ label_for_each(i, label, p) {
+ l->ent[i.i] = aa_get_newest_profile(p);
+ if (&l->ent[i.i]->label.replacedby != &p->label.replacedby)
+ invcount++;
+ }
+
+ /* updated label invalidated by being removed/renamed from labelset */
+ if (invcount) {
+ l->size -= aa_sort_and_merge_profiles(l->size, &l->ent[0]);
+ if (labels_set(label) == labels_set(l)) {
+ AA_BUG(__aa_label_remove_and_insert(labels_set(label), label, l) != l);
+ } else {
+ aa_label_remove(labels_set(label), label);
+ goto other_ls_insert;
+ }
+ } else {
+ AA_BUG(labels_ns(label) != labels_ns(l));
+ AA_BUG(__aa_label_remove_and_insert(labels_set(label), label, l) != l);
+ }
+ write_unlock_irqrestore(&ls->lock, flags);
+
+ return l;
+
+other_ls_insert:
+ write_unlock_irqrestore(&ls->lock, flags);
+ tmp = aa_label_insert(labels_set(l), l);
+ if (tmp != l) {
+ aa_put_label(l);
+ l = tmp;
+ }
+
+ return l;
+}
+
+/**
+ * __labelset_update - invalidate and update labels in @ns
+ * @ns: namespace to update and invalidate labels in (NOT NULL)
+ *
+ * Requires: @ns lock be held
+ *
+ * Walk the labelset ensuring that all labels are up to date and valid
+ * Any label that is outdated is replaced and by an updated version
+ * invalidated and removed from the tree.
+ *
+ * If failures happen due to memory pressures then stale labels will
+ * be left in place until the next pass.
+ */
+static void __labelset_update(struct aa_namespace *ns)
+{
+ struct aa_label *label;
+
+ AA_BUG(!ns);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ do {
+ label = labelset_next_invalid(&ns->labels);
+ if (label) {
+ struct aa_label *l;
+ l = __label_update(label);
+ aa_put_label(l);
+ aa_put_label(label);
+ }
+ } while (label);
+}
+
+/**
+ * __aa_labelset_invalidate_all - invalidate labels in @ns and below
+ * @ns: ns to start invalidation at (NOT NULL)
+ *
+ * Requires: @ns lock be held
+ *
+ * Invalidates labels based on @p in @ns and any children namespaces.
+*/
+void __aa_labelset_update_all(struct aa_namespace *ns)
+{
+ struct aa_namespace *child;
+
+ AA_BUG(!ns);
+ AA_BUG(!mutex_is_locked(&ns->lock));
+
+ __labelset_update(ns);
+
+ list_for_each_entry(child, &ns->sub_ns, base.list) {
+ mutex_lock(&child->lock);
+ __aa_labelset_update_all(child);
+ mutex_unlock(&child->lock);
+ }
+}
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index 7430298..1f810ef0 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -4,7 +4,7 @@
* This file contains basic common functions used in AppArmor
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * Copyright 2009-2013 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
@@ -12,14 +12,17 @@
* License.
*/
+#include <linux/ctype.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
-#include "include/audit.h"
#include "include/apparmor.h"
-
+#include "include/audit.h"
+#include "include/label.h"
+#include "include/perms.h"
+#include "include/policy.h"
/**
* aa_split_fqname - split a fqname into a profile and namespace name
@@ -45,8 +48,10 @@ char *aa_split_fqname(char *fqname, char **ns_name)
*ns_name = skip_spaces(&name[1]);
if (split) {
/* overwrite ':' with \0 */
- *split = 0;
- name = skip_spaces(split + 1);
+ *split++ = 0;
+ if (strncmp(split, "//", 2) == 0)
+ split += 2;
+ name = skip_spaces(split);
} else
/* a ns name without a following profile is allowed */
name = NULL;
@@ -58,32 +63,80 @@ char *aa_split_fqname(char *fqname, char **ns_name)
}
/**
+ * skipn_spaces - Removes leading whitespace from @str.
+ * @str: The string to be stripped.
+ *
+ * Returns a pointer to the first non-whitespace character in @str.
+ * if all whitespace will return NULL
+ */
+
+static char *skipn_spaces(const char *str, size_t n)
+{
+ for (;n && isspace(*str); --n)
+ ++str;
+ if (n)
+ return (char *)str;
+ return NULL;
+}
+
+char *aa_splitn_fqname(char *fqname, size_t n, char **ns_name, size_t *ns_len)
+{
+ char *end = fqname + n;
+ char *name = skipn_spaces(fqname, n);
+ if (!name)
+ return NULL;
+ *ns_name = NULL;
+ *ns_len = 0;
+ if (name[0] == ':') {
+ char *split = strnchr(name + 1, end - name - 1, ':');
+ *ns_name = skipn_spaces(&name[1], end - &name[1]);
+ if (!*ns_name)
+ return NULL;
+ if (split) {
+ *ns_len = split - *ns_name - 1;
+ if (*ns_len == 0)
+ *ns_name = NULL;
+ split++;
+ if (end - split > 1 && strncmp(split, "//", 2) == 0)
+ split += 2;
+ name = skipn_spaces(split, end - split);
+ } else {
+ /* a ns name without a following profile is allowed */
+ name = NULL;
+ *ns_len = end - *ns_name;
+ }
+ }
+ if (name && *name == 0)
+ name = NULL;
+
+ return name;
+}
+
+/**
* aa_info_message - log a none profile related status message
* @str: message to log
*/
void aa_info_message(const char *str)
{
if (audit_enabled) {
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.info = str;
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, 0);
+ aad(&sa)->info = str;
aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
}
printk(KERN_INFO "AppArmor: %s\n", str);
}
/**
- * kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
- * @size: size of allocation
+ * __aa_kvmalloc - do allocation preferring kmalloc but falling back to vmalloc
+ * @size: how many bytes of memory are required
+ * @flags: the type of memory to allocate (see kmalloc).
*
* Return: allocated buffer or NULL if failed
*
* It is possible that policy being loaded from the user is larger than
* what can be allocated by kmalloc, in those cases fall back to vmalloc.
*/
-void *kvmalloc(size_t size)
+void *__aa_kvmalloc(size_t size, gfp_t flags)
{
void *buffer = NULL;
@@ -92,46 +145,380 @@ void *kvmalloc(size_t size)
/* do not attempt kmalloc if we need more than 16 pages at once */
if (size <= (16*PAGE_SIZE))
- buffer = kmalloc(size, GFP_NOIO | __GFP_NOWARN);
+ buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN);
if (!buffer) {
- /* see kvfree for why size must be at least work_struct size
- * when allocated via vmalloc
- */
- if (size < sizeof(struct work_struct))
- size = sizeof(struct work_struct);
- buffer = vmalloc(size);
+ if (flags & __GFP_ZERO)
+ buffer = vzalloc(size);
+ else
+ buffer = vmalloc(size);
}
return buffer;
}
+
+__counted char *aa_str_alloc(int size, gfp_t gfp)
+{
+ struct counted_str *str;
+ str = kmalloc(sizeof(struct counted_str) + size, gfp);
+ if (!str)
+ return NULL;
+
+ kref_init(&str->count);
+ return str->name;
+}
+
+void aa_str_kref(struct kref *kref)
+{
+ kfree(container_of(kref, struct counted_str, count));
+}
+
+
+const char aa_file_perm_chrs[] = "xwracd km l ";
+const char *aa_file_perm_names[] = {
+ "exec",
+ "write",
+ "read",
+ "append",
+
+ "create",
+ "delete",
+ "open",
+ "rename",
+
+ "setattr",
+ "getattr",
+ "setcred",
+ "getcred",
+
+ "chmod",
+ "chown",
+ "chgrp",
+ "lock",
+
+ "mmap",
+ "mprot",
+ "link",
+ "snapshot",
+
+ "unknown",
+ "unknown",
+ "unknown",
+ "unknown",
+
+ "unknown",
+ "unknown",
+ "unknown",
+ "unknown",
+
+ "stack",
+ "change_onexec",
+ "change_profile",
+ "change_hat",
+};
+
/**
- * do_vfree - workqueue routine for freeing vmalloced memory
- * @work: data to be freed
- *
- * The work_struct is overlaid to the data being freed, as at the point
- * the work is scheduled the data is no longer valid, be its freeing
- * needs to be delayed until safe.
+ * aa_perm_mask_to_str - convert a perm mask to its short string
+ * @str: character buffer to store string in (at least 10 characters)
+ * @mask: permission mask to convert
*/
-static void do_vfree(struct work_struct *work)
+void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask)
+{
+ unsigned int i, perm = 1;
+ for (i = 0; i < 32; perm <<= 1, i++) {
+ if (mask & perm)
+ *str++ = chrs[i];
+ }
+ *str = '\0';
+}
+
+void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask)
+{
+ const char *fmt = "%s";
+ unsigned int i, perm = 1;
+ bool prev = false;
+ for (i = 0; i < 32; perm <<= 1, i++) {
+ if (mask & perm) {
+ audit_log_format(ab, fmt, names[i]);
+ if (!prev) {
+ prev = true;
+ fmt = " %s";
+ }
+ }
+ }
+}
+
+void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
+ u32 chrsmask, const char **names, u32 namesmask)
+{
+ char str[33];
+
+ audit_log_format(ab, "\"");
+ if ((mask & chrsmask) && chrs) {
+ aa_perm_mask_to_str(str, chrs, mask & chrsmask);
+ mask &= ~chrsmask;
+ audit_log_format(ab, "%s", str);
+ if (mask & namesmask)
+ audit_log_format(ab, " ");
+ }
+ if ((mask & namesmask) && names)
+ aa_audit_perm_names(ab, names, mask & namesmask);
+ audit_log_format(ab, "\"");
+}
+
+/**
+ * aa_audit_perms_cb - generic callback fn for auditing perms
+ * @ab: audit buffer (NOT NULL)
+ * @va: audit struct to audit values of (NOT NULL)
+ */
+static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
+{
+ struct common_audit_data *sa = va;
+
+ if (aad(sa)->request) {
+ audit_log_format(ab, " requested_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs,
+ PERMS_CHRS_MASK, aa_file_perm_names,
+ PERMS_NAMES_MASK);
+ }
+ if (aad(sa)->denied) {
+ audit_log_format(ab, "denied_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs,
+ PERMS_CHRS_MASK, aa_file_perm_names,
+ PERMS_NAMES_MASK);
+ }
+ audit_log_format(ab, " target=");
+ audit_log_untrustedstring(ab, aad(sa)->target);
+}
+
+void map_old_policy_perms(struct aa_dfa *dfa, unsigned int state,
+ struct aa_perms *perms)
{
- vfree(work);
+
}
/**
- * kvfree - free an allocation do by kvmalloc
- * @buffer: buffer to free (MAYBE_NULL)
+ * aa_apply_modes_to_perms - apply namespace and profile flags to perms
+ * @profile: that perms where computed from
+ * @perms: perms to apply mode modifiers to
*
- * Free a buffer allocated by kvmalloc
+ * TODO: split into profile and ns based flags for when accumulating perms
+ */
+void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
+{
+ switch (AUDIT_MODE(profile)) {
+ case AUDIT_ALL:
+ perms->audit = ALL_PERMS_MASK;
+ /* fall through */
+ case AUDIT_NOQUIET:
+ perms->quiet = 0;
+ break;
+ case AUDIT_QUIET:
+ perms->audit = 0;
+ /* fall through */
+ case AUDIT_QUIET_DENIED:
+ perms->quiet = ALL_PERMS_MASK;
+ break;
+ }
+
+ if (KILL_MODE(profile))
+ perms->kill = ALL_PERMS_MASK;
+ else if (COMPLAIN_MODE(profile))
+ perms->complain = ALL_PERMS_MASK;
+/* TODO:
+ else if (PROMPT_MODE(profile))
+ perms->prompt = ALL_PERMS_MASK;
+*/
+}
+
+static u32 map_other(u32 x)
+{
+ return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
+ ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
+ ((x & 0x60) << 19); /* SETOPT/GETOPT */
+}
+
+void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
+ struct aa_perms *perms)
+{
+ perms->deny = 0;
+ perms->kill = perms->stop = 0;
+ perms->complain = perms->cond = 0;
+ perms->hide = 0;
+ perms->prompt = 0;
+ perms->allow = dfa_user_allow(dfa, state);
+ perms->audit = dfa_user_audit(dfa, state);
+ perms->quiet = dfa_user_quiet(dfa, state);
+
+ /* for v5 perm mapping in the policydb, the other set is used
+ * to extend the general perm set
+ */
+ perms->allow |= map_other(dfa_other_allow(dfa, state));
+ perms->audit |= map_other(dfa_other_audit(dfa, state));
+ perms->quiet |= map_other(dfa_other_quiet(dfa, state));
+// perms->xindex = dfa_user_xindex(dfa, state);
+}
+
+/**
+ * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
+ * @accum - perms struct to accumulate into
+ * @addend - perms struct to add to @accum
*/
-void kvfree(void *buffer)
-{
- if (is_vmalloc_addr(buffer)) {
- /* Data is no longer valid so just use the allocated space
- * as the work_struct
- */
- struct work_struct *work = (struct work_struct *) buffer;
- INIT_WORK(work, do_vfree);
- schedule_work(work);
+void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~addend->deny;
+ accum->audit |= addend->audit & addend->allow;
+ accum->quiet &= addend->quiet & ~addend->allow;
+ accum->kill |= addend->kill & ~addend->allow;
+ accum->stop |= addend->stop & ~addend->allow;
+ accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
+ accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
+ accum->hide &= addend->hide & ~addend->allow;
+ accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
+}
+
+/**
+ * aa_perms_accum - accumulate perms, masking off overlapping perms
+ * @accum - perms struct to accumulate into
+ * @addend - perms struct to add to @accum
+ */
+void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~accum->deny;
+ accum->audit |= addend->audit & accum->allow;
+ accum->quiet &= addend->quiet & ~accum->allow;
+ accum->kill |= addend->kill & ~accum->allow;
+ accum->stop |= addend->stop & ~accum->allow;
+ accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
+ accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
+ accum->hide &= addend->hide & ~accum->allow;
+ accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
+}
+
+void aa_profile_match_label(struct aa_profile *profile, const char *label,
+ int type, struct aa_perms *perms)
+{
+ /* TODO: doesn't yet handle extended types */
+ unsigned int state;
+ if (profile->policy.dfa) {
+ state = aa_dfa_next(profile->policy.dfa,
+ profile->policy.start[AA_CLASS_LABEL],
+ type);
+ state = aa_dfa_match(profile->policy.dfa, state, label);
+ aa_compute_perms(profile->policy.dfa, state, perms);
} else
- kfree(buffer);
+ memset(perms, 0, sizeof(*perms));
+}
+
+
+int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
+ u32 request, int type, u32 *deny,
+ struct common_audit_data *sa)
+{
+ struct aa_perms perms;
+ aad(sa)->label = &profile->label;
+ aad(sa)->target = target;
+ aad(sa)->request = request;
+
+ aa_profile_match_label(profile, target->base.hname, type, &perms);
+ aa_apply_modes_to_perms(profile, &perms);
+ *deny |= request & perms.deny;
+ return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb);
+}
+
+/**
+ * aa_check_perms - do audit mode selection based on perms set
+ * @profile: profile being checked
+ * @perms: perms computed for the request
+ * @request: requested perms
+ * @deny: Returns: explicit deny set
+ * @sa: initialized audit structure (MAY BE NULL if not auditing)
+ * @cb: callback fn for tpye specific fields (MAY BE NULL)
+ *
+ * Returns: 0 if permission else error code
+ *
+ * Note: profile audit modes need to be set before calling by setting the
+ * perm masks appropriately.
+ *
+ * If not auditing then complain mode is not enabled and the
+ * error code will indicate whether there was an explicit deny
+ * with a positive value.
+ */
+int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+ u32 request, struct common_audit_data *sa,
+ void (*cb) (struct audit_buffer *, void *))
+{
+ int type, error;
+ bool stop = false;
+ u32 denied = request & (~perms->allow | perms->deny);
+ if (likely(!denied)) {
+ /* mask off perms that are not being force audited */
+ request &= perms->audit;
+ if (!request || !sa)
+ return 0;
+
+ type = AUDIT_APPARMOR_AUDIT;
+ error = 0;
+ } else {
+ error = -EACCES;
+
+ if (denied & perms->kill)
+ type = AUDIT_APPARMOR_KILL;
+ else if (denied == (denied & perms->complain))
+ type = AUDIT_APPARMOR_ALLOWED;
+ else
+ type = AUDIT_APPARMOR_DENIED;
+
+ if (denied & perms->stop)
+ stop = true;
+ if (denied == (denied & perms->hide))
+ error = -ENOENT;
+
+ denied &= ~perms->quiet;
+ if (type != AUDIT_APPARMOR_ALLOWED && (!sa || !denied))
+ return error;
+ }
+
+ if (sa) {
+ aad(sa)->label = &profile->label;
+ aad(sa)->request = request;
+ aad(sa)->denied = denied;
+ aad(sa)->error = error;
+ aa_audit_msg(type, sa, cb);
+ }
+
+ if (type == AUDIT_APPARMOR_ALLOWED)
+ error = 0;
+
+ return error;
+}
+
+const char *aa_imode_name(umode_t mode)
+{
+ switch(mode & S_IFMT) {
+ case S_IFSOCK:
+ return "sock";
+ case S_IFLNK:
+ return "link";
+ case S_IFREG:
+ return "reg";
+ case S_IFBLK:
+ return "blkdev";
+ case S_IFDIR:
+ return "dir";
+ case S_IFCHR:
+ return "chrdev";
+ case S_IFIFO:
+ return "fifo";
+ }
+ return "unknown";
+}
+
+const char *aa_peer_name(struct aa_profile *peer)
+{
+ if (profile_unconfined(peer))
+ return "unconfined";
+
+ return peer->base.hname;
}
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index b21830e..1b6a949 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -25,6 +25,7 @@
#include <linux/user_namespace.h>
#include <net/sock.h>
+#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/apparmorfs.h"
#include "include/audit.h"
@@ -32,24 +33,29 @@
#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"
+#include "include/mount.h"
/* Flag indicating whether initialization completed */
int apparmor_initialized __initdata;
+DEFINE_PER_CPU(struct aa_buffers, aa_buffers);
+
+
/*
* LSM hook functions
*/
/*
- * free the associated aa_task_cxt and put its profiles
+ * free the associated aa_task_cxt and put its labels
*/
static void apparmor_cred_free(struct cred *cred)
{
- aa_free_task_context(cred->security);
- cred->security = NULL;
+ aa_free_task_context(cred_cxt(cred));
+ cred_cxt(cred) = NULL;
}
/*
@@ -62,7 +68,7 @@ static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
if (!cxt)
return -ENOMEM;
- cred->security = cxt;
+ cred_cxt(cred) = cxt;
return 0;
}
@@ -77,8 +83,8 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
if (!cxt)
return -ENOMEM;
- aa_dup_task_context(cxt, old->security);
- new->security = cxt;
+ aa_dup_task_context(cxt, cred_cxt(old));
+ cred_cxt(new) = cxt;
return 0;
}
@@ -87,8 +93,8 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
*/
static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
{
- const struct aa_task_cxt *old_cxt = old->security;
- struct aa_task_cxt *new_cxt = new->security;
+ const struct aa_task_cxt *old_cxt = cred_cxt(old);
+ struct aa_task_cxt *new_cxt = cred_cxt(new);
aa_dup_task_context(new_cxt, old_cxt);
}
@@ -96,42 +102,62 @@ static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
static int apparmor_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
+ struct aa_label *tracer, *tracee;
int error = cap_ptrace_access_check(child, mode);
if (error)
return error;
- return aa_ptrace(current, child, mode);
+ tracer = aa_current_label();
+ tracee = aa_get_task_label(child);
+ error = aa_may_ptrace(tracer, tracee,
+ mode == PTRACE_MODE_READ ? AA_PTRACE_READ : AA_PTRACE_TRACE);
+ aa_put_label(tracee);
+ return error;
}
static int apparmor_ptrace_traceme(struct task_struct *parent)
{
+ struct aa_label *tracer, *tracee;
int error = cap_ptrace_traceme(parent);
if (error)
return error;
- return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+ tracee = aa_current_label();
+ tracer = aa_get_task_label(parent);
+ error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE);
+ aa_put_label(tracer);
+ return error;
}
/* 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;
+ struct aa_label *label;
const struct cred *cred;
rcu_read_lock();
cred = __task_cred(target);
- profile = aa_cred_profile(cred);
+ label = aa_get_newest_cred_label(cred);
*effective = cred->cap_effective;
*inheritable = cred->cap_inheritable;
*permitted = cred->cap_permitted;
- if (!unconfined(profile) && !COMPLAIN_MODE(profile)) {
- *effective = cap_intersect(*effective, profile->caps.allow);
- *permitted = cap_intersect(*permitted, profile->caps.allow);
+ if (!unconfined(label)) {
+ struct aa_profile *profile;
+ struct label_it i;
+ label_for_each_confined(i, label, profile) {
+ if (COMPLAIN_MODE(profile))
+ continue;
+ *effective = cap_intersect(*effective,
+ profile->caps.allow);
+ *permitted = cap_intersect(*permitted,
+ profile->caps.allow);
+ }
}
rcu_read_unlock();
+ aa_put_label(label);
return 0;
}
@@ -139,14 +165,17 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
int cap, int audit)
{
- struct aa_profile *profile;
+ struct aa_label *label;
/* cap_capable returns 0 on success, else -EPERM */
int error = cap_capable(cred, ns, cap, audit);
- if (!error) {
- profile = aa_cred_profile(cred);
- if (!unconfined(profile))
- error = aa_capable(current, profile, cap, audit);
- }
+ if (error)
+ return error;
+
+ label = aa_get_newest_cred_label(cred);
+ if (!unconfined(label))
+ error = aa_capable(label, cap, audit);
+ aa_put_label(label);
+
return error;
}
@@ -162,16 +191,36 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
static int common_perm(int op, struct path *path, u32 mask,
struct path_cond *cond)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
- profile = __aa_current_profile();
- if (!unconfined(profile))
- error = aa_path_perm(op, profile, path, 0, mask, cond);
+ label = aa_begin_current_label();
+ if (!unconfined(label))
+ error = aa_path_perm(op, label, path, 0, mask, cond);
+ aa_end_current_label(label);
return error;
}
+static int common_perm_cond(int op, struct path *path, u32 mask)
+{
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+
+ return common_perm(op, path, mask, &cond);
+}
+
+static void apparmor_inode_free_security(struct inode *inode)
+{
+ struct aa_label *cxt = inode_cxt(inode);
+
+ if (cxt) {
+ inode_cxt(inode) = NULL;
+ aa_put_label(cxt);
+ }
+}
+
/**
* common_perm_dir_dentry - common permission wrapper when path is dir, dentry
* @op: operation being checked
@@ -204,11 +253,8 @@ static int common_perm_mnt_dentry(int op, struct vfsmount *mnt,
struct dentry *dentry, u32 mask)
{
struct path path = { mnt, dentry };
- struct path_cond cond = { dentry->d_inode->i_uid,
- dentry->d_inode->i_mode
- };
- return common_perm(op, &path, mask, &cond);
+ return common_perm_cond(op, &path, mask);
}
/**
@@ -226,7 +272,7 @@ static int common_perm_rm(int op, struct path *dir,
struct inode *inode = dentry->d_inode;
struct path_cond cond = { };
- if (!inode || !dir->mnt || !mediated_filesystem(inode))
+ if (!inode || !dir->mnt || !path_mediated_fs(inode))
return 0;
cond.uid = inode->i_uid;
@@ -250,7 +296,7 @@ static int common_perm_create(int op, struct path *dir, struct dentry *dentry,
{
struct path_cond cond = { current_fsuid(), mode };
- if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
+ if (!dir->mnt || !path_mediated_fs(dir->dentry->d_inode))
return 0;
return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
@@ -281,15 +327,10 @@ static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
static int apparmor_path_truncate(struct path *path)
{
- 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))
+ if (!path->mnt || !path_mediated_fs(path->dentry->d_inode))
return 0;
- return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE,
- &cond);
+ return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR);
}
static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
@@ -302,42 +343,42 @@ static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
struct dentry *new_dentry)
{
- struct aa_profile *profile;
+ struct aa_label *label;
int error = 0;
- if (!mediated_filesystem(old_dentry->d_inode))
+ if (!path_mediated_fs(old_dentry->d_inode))
return 0;
- profile = aa_current_profile();
- if (!unconfined(profile))
- error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
+ label = aa_current_label();
+ if (!unconfined(label))
+ error = aa_path_link(label, 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;
+ struct aa_label *label;
int error = 0;
- if (!mediated_filesystem(old_dentry->d_inode))
+ if (!path_mediated_fs(old_dentry->d_inode))
return 0;
- profile = aa_current_profile();
- if (!unconfined(profile)) {
+ label = aa_current_label();
+ if (!unconfined(label)) {
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(OP_RENAME_SRC, profile, &old_path, 0,
- MAY_READ | AA_MAY_META_READ | MAY_WRITE |
- AA_MAY_META_WRITE | AA_MAY_DELETE,
+ error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0,
+ MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
+ AA_MAY_SETATTR | AA_MAY_DELETE,
&cond);
if (!error)
- error = aa_path_perm(OP_RENAME_DEST, profile, &new_path,
- 0, MAY_WRITE | AA_MAY_META_WRITE |
+ error = aa_path_perm(OP_RENAME_DEST, label, &new_path,
+ 0, MAY_WRITE | AA_MAY_SETATTR |
AA_MAY_CREATE, &cond);
}
@@ -346,40 +387,36 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
static int apparmor_path_chmod(struct path *path, umode_t mode)
{
- if (!mediated_filesystem(path->dentry->d_inode))
+ if (!path_mediated_fs(path->dentry->d_inode))
return 0;
- return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD);
+ return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD);
}
static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid)
{
- struct path_cond cond = { path->dentry->d_inode->i_uid,
- path->dentry->d_inode->i_mode
- };
-
- if (!mediated_filesystem(path->dentry->d_inode))
+ if (!path_mediated_fs(path->dentry->d_inode))
return 0;
- return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond);
+ return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN);
}
static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)
{
- if (!mediated_filesystem(dentry->d_inode))
+ if (!path_mediated_fs(dentry->d_inode))
return 0;
return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry,
- AA_MAY_META_READ);
+ AA_MAY_GETATTR);
}
static int apparmor_file_open(struct file *file, const struct cred *cred)
{
- struct aa_file_cxt *fcxt = file->f_security;
- struct aa_profile *profile;
+ struct aa_file_cxt *fcxt = file_cxt(file);
+ struct aa_label *label;
int error = 0;
- if (!mediated_filesystem(file_inode(file)))
+ if (!path_mediated_fs(file_inode(file)))
return 0;
/* If in exec, permission is handled by bprm hooks.
@@ -392,16 +429,17 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
return 0;
}
- profile = aa_cred_profile(cred);
- if (!unconfined(profile)) {
+ label = aa_get_newest_cred_label(cred);
+ if (!unconfined(label)) {
struct inode *inode = file_inode(file);
struct path_cond cond = { inode->i_uid, inode->i_mode };
- error = aa_path_perm(OP_OPEN, profile, &file->f_path, 0,
+ error = aa_path_perm(OP_OPEN, label, &file->f_path, 0,
aa_map_file_to_perms(file), &cond);
/* todo cache full allowed permissions set and state */
fcxt->allow = aa_map_file_to_perms(file);
}
+ aa_put_label(label);
return error;
}
@@ -409,8 +447,8 @@ static int apparmor_file_open(struct file *file, const struct cred *cred)
static int apparmor_file_alloc_security(struct file *file)
{
/* freed by apparmor_file_free_security */
- file->f_security = aa_alloc_file_context(GFP_KERNEL);
- if (!file->f_security)
+ file->f_security = aa_alloc_file_cxt(aa_current_label(), GFP_KERNEL);
+ if (!file_cxt(file))
return -ENOMEM;
return 0;
@@ -418,39 +456,26 @@ static int apparmor_file_alloc_security(struct file *file)
static void apparmor_file_free_security(struct file *file)
{
- struct aa_file_cxt *cxt = file->f_security;
-
- aa_free_file_context(cxt);
+ aa_free_file_cxt(file_cxt(file));
}
static int common_file_perm(int op, struct file *file, u32 mask)
{
- struct aa_file_cxt *fcxt = file->f_security;
- struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred);
+ struct aa_label *label;
int error = 0;
- BUG_ON(!fprofile);
-
- if (!file->f_path.mnt ||
- !mediated_filesystem(file_inode(file)))
- return 0;
-
- profile = __aa_current_profile();
-
- /* revalidate access, if task is unconfined, or the cached cred
- * doesn't match or if the request is for more permissions than
- * was granted.
- *
- * Note: the test for !unconfined(fprofile) is to handle file
- * delegation from unconfined tasks
- */
- if (!unconfined(profile) && !unconfined(fprofile) &&
- ((fprofile != profile) || (mask & ~fcxt->allow)))
- error = aa_file_perm(op, profile, file, mask);
+ label = aa_begin_current_label();
+ error = aa_file_perm(op, label, file, mask);
+ aa_end_current_label(label);
return error;
}
+static int apparmor_file_receive(struct file *file)
+{
+ return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file));
+}
+
static int apparmor_file_permission(struct file *file, int mask)
{
return common_file_perm(OP_FPERM, file, mask);
@@ -469,10 +494,9 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd)
static int common_mmap(int op, struct file *file, unsigned long prot,
unsigned long flags)
{
- struct dentry *dentry;
int mask = 0;
- if (!file || !file->f_security)
+ if (!file || !file_cxt(file))
return 0;
if (prot & PROT_READ)
@@ -486,7 +510,6 @@ static int common_mmap(int op, struct file *file, unsigned long prot,
if (prot & PROT_EXEC)
mask |= AA_EXEC_MMAP;
- dentry = file->f_path.dentry;
return common_file_perm(op, file, mask);
}
@@ -503,28 +526,86 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
}
+static int apparmor_sb_mount(const char *dev_name, struct path *path,
+ const char *type, unsigned long flags, void *data)
+{
+ struct aa_label *label;
+ int error = 0;
+
+ /* Discard magic */
+ if ((flags & MS_MGC_MSK) == MS_MGC_VAL)
+ flags &= ~MS_MGC_MSK;
+
+ flags &= ~AA_MS_IGNORE_MASK;
+
+ label = aa_begin_current_label();
+ if (!unconfined(label)) {
+ if (flags & MS_REMOUNT)
+ error = aa_remount(label, path, flags, data);
+ else if (flags & MS_BIND)
+ error = aa_bind_mount(label, path, dev_name, flags);
+ else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE |
+ MS_UNBINDABLE))
+ error = aa_mount_change_type(label, path, flags);
+ else if (flags & MS_MOVE)
+ error = aa_move_mount(label, path, dev_name);
+ else
+ error = aa_new_mount(label, dev_name, path, type,
+ flags, data);
+ }
+ aa_end_current_label(label);
+
+ return error;
+}
+
+static int apparmor_sb_umount(struct vfsmount *mnt, int flags)
+{
+ struct aa_label *label;
+ int error = 0;
+
+ label = aa_begin_current_label();
+ if (!unconfined(label))
+ error = aa_umount(label, mnt, flags);
+ aa_end_current_label(label);
+
+ return error;
+}
+
+static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path)
+{
+ struct aa_label *label;
+ int error = 0;
+
+ label = aa_begin_current_label();
+ if (!unconfined(label))
+ error = aa_pivotroot(label, old_path, new_path);
+ aa_end_current_label(label);
+
+ return error;
+}
+
static int apparmor_getprocattr(struct task_struct *task, char *name,
char **value)
{
int error = -ENOENT;
- struct aa_profile *profile;
/* released below */
const struct cred *cred = get_task_cred(task);
- struct aa_task_cxt *cxt = cred->security;
- profile = aa_cred_profile(cred);
+ struct aa_task_cxt *cxt = cred_cxt(cred);
+ struct aa_label *label = NULL;
if (strcmp(name, "current") == 0)
- error = aa_getprocattr(aa_newest_version(cxt->profile),
- value);
+ label = aa_get_newest_label(cxt->label);
else if (strcmp(name, "prev") == 0 && cxt->previous)
- error = aa_getprocattr(aa_newest_version(cxt->previous),
- value);
+ label = aa_get_newest_label(cxt->previous);
else if (strcmp(name, "exec") == 0 && cxt->onexec)
- error = aa_getprocattr(aa_newest_version(cxt->onexec),
- value);
+ label = aa_get_newest_label(cxt->onexec);
else
error = -EINVAL;
+ if (label)
+ error = aa_getprocattr(label, value);
+
+ aa_put_label(label);
put_cred(cred);
return error;
@@ -536,6 +617,7 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,
char *command, *args = value;
size_t arg_size;
int error;
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR);
if (size == 0)
return -EINVAL;
@@ -576,44 +658,473 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,
} else if (strcmp(command, "permprofile") == 0) {
error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
AA_DO_TEST);
- } else if (strcmp(command, "permipc") == 0) {
- error = aa_setprocattr_permipc(args);
- } else {
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.op = OP_SETPROCATTR;
- aad.info = name;
- aad.error = -EINVAL;
- return aa_audit(AUDIT_APPARMOR_DENIED,
- __aa_current_profile(), GFP_KERNEL,
- &sa, NULL);
- }
+ } else
+ goto fail;
} else if (strcmp(name, "exec") == 0) {
- error = aa_setprocattr_changeprofile(args, AA_ONEXEC,
- !AA_DO_TEST);
- } else {
+ if (strcmp(command, "exec") == 0)
+ error = aa_setprocattr_changeprofile(args, AA_ONEXEC,
+ !AA_DO_TEST);
+ else
+ goto fail;
+ } else
/* only support the "current" and "exec" process attributes */
return -EINVAL;
- }
+
if (!error)
error = size;
return error;
+
+fail:
+ aad(&sa)->label = aa_current_label();
+ aad(&sa)->info = name;
+ aad(&sa)->error = -EINVAL;
+ aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
+ return -EINVAL;
+}
+
+/**
+ * apparmor_bprm_committing_creds - do task cleanup on committing new creds
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ struct aa_label *label = aa_current_raw_label();
+ struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred);
+
+ /* bail out if unconfined or not changing profile */
+ if ((new_cxt->label->replacedby == label->replacedby) ||
+ (unconfined(new_cxt->label)))
+ return;
+
+ aa_inherit_files(bprm->cred, current->files);
+
+ current->pdeath_signal = 0;
+
+ /* reset soft limits and set hard limits for the new label */
+ __aa_transition_rlimits(label, new_cxt->label);
+}
+
+/**
+ * apparmor_bprm_commited_cred - do cleanup after new creds committed
+ * @bprm: binprm for the exec (NOT NULL)
+ */
+void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+{
+ /* TODO: cleanup signals - ipc mediation */
+ return;
}
static int apparmor_task_setrlimit(struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
- struct aa_profile *profile = __aa_current_profile();
+ struct aa_label *label = aa_begin_current_label();
int error = 0;
- if (!unconfined(profile))
- error = aa_task_setrlimit(profile, task, resource, new_rlim);
+ if (!unconfined(label))
+ error = aa_task_setrlimit(label, task, resource, new_rlim);
+ aa_end_current_label(label);
+
+ return error;
+}
+
+/**
+ * apparmor_sk_alloc_security - allocate and attach the sk_security field
+ */
+static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags)
+{
+ struct aa_sk_cxt *cxt;
+
+ cxt = kzalloc(sizeof(*cxt), flags);
+ if (!cxt)
+ return -ENOMEM;
+
+ SK_CXT(sk) = cxt;
+ //??? set local too current???
+
+ return 0;
+}
+
+/**
+ * apparmor_sk_free_security - free the sk_security field
+ */
+static void apparmor_sk_free_security(struct sock *sk)
+{
+ struct aa_sk_cxt *cxt = SK_CXT(sk);
+
+ SK_CXT(sk) = NULL;
+ aa_put_label(cxt->label);
+ aa_put_label(cxt->peer);
+ kfree(cxt);
+}
+
+/**
+ * apparmor_clone_security - clone the sk_security field
+ */
+static void apparmor_sk_clone_security(const struct sock *sk,
+ struct sock *newsk)
+{
+ struct aa_sk_cxt *cxt = SK_CXT(sk);
+ struct aa_sk_cxt *new = SK_CXT(newsk);
+
+ new->label = aa_get_label(cxt->label);
+ new->peer = aa_get_label(cxt->peer);
+}
+
+/**
+ * apparmor_unix_stream_connect - check perms before making unix domain conn
+ *
+ * peer is locked when this hook is called
+ */
+static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk,
+ struct sock *newsk)
+{
+ struct aa_sk_cxt *sk_cxt = SK_CXT(sk);
+ struct aa_sk_cxt *peer_cxt = SK_CXT(peer_sk);
+ struct aa_sk_cxt *new_cxt = SK_CXT(newsk);
+ struct aa_label *label;
+ int error;
+
+ label = aa_begin_current_label();
+ error = aa_unix_peer_perm(label, OP_CONNECT,
+ (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE),
+ sk, peer_sk, NULL);
+ if (!UNIX_FS(peer_sk)) {
+ last_error(error,
+ aa_unix_peer_perm(peer_cxt->label, OP_CONNECT,
+ (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE),
+ peer_sk, sk, label));
+ }
+ aa_end_current_label(label);
+
+ if (error)
+ return error;
+
+ /* label newsk if it wasn't labeled in post_create. Normally this
+ * would be done in sock_graft, but because we are directly looking
+ * at the peer_sk to obtain peer_labeling for unix socks this
+ * does not work
+ */
+ if (!new_cxt->label)
+ new_cxt->label = aa_get_label(peer_cxt->label);
+
+ /* Cross reference the peer labels for SO_PEERSEC */
+ if (new_cxt->peer)
+ aa_put_label(new_cxt->peer);
+
+ if (sk_cxt->peer)
+ aa_put_label(sk_cxt->peer);
+
+ new_cxt->peer = aa_get_label(sk_cxt->label);
+ sk_cxt->peer = aa_get_label(peer_cxt->label);
+
+ return 0;
+}
+
+/**
+ * apparmor_unix_may_send - check perms before conn or sending unix dgrams
+ *
+ * other is locked when this hook is called
+ *
+ * dgram connect calls may_send, peer setup but path not copied?????
+ */
+static int apparmor_unix_may_send(struct socket *sock, struct socket *peer)
+{
+ struct aa_sk_cxt *peer_cxt = SK_CXT(peer->sk);
+ struct aa_label *label = aa_begin_current_label();
+ int error;
+
+ error = xcheck(aa_unix_peer_perm(label, OP_SENDMSG, AA_MAY_SEND,
+ sock->sk, peer->sk, NULL),
+ aa_unix_peer_perm(peer_cxt->label, OP_SENDMSG, AA_MAY_RECEIVE,
+ peer->sk, sock->sk, label));
+ aa_end_current_label(label);
return error;
}
+/**
+ * apparmor_socket_create - check perms before creating a new socket
+ */
+static int apparmor_socket_create(int family, int type, int protocol, int kern)
+{
+ struct aa_label *label;
+
+ label = aa_current_label();
+ if (kern || unconfined(label))
+ return 0;
+
+ return aa_sock_create_perm(label, family, type, protocol);
+}
+
+/**
+ * apparmor_socket_post_create - setup the per-socket security struct
+ *
+ * Note:
+ * - kernel sockets currently labeled unconfined but we may want to
+ * move to a special kernel label
+ * - socket may not have sk here if created with sock_create_lite or
+ * sock_alloc. These should be accept cases which will be handled in
+ * sock_graft.
+ */
+static int apparmor_socket_post_create(struct socket *sock, int family,
+ int type, int protocol, int kern)
+{
+ struct aa_label *label;
+
+ if (kern)
+ label = aa_get_label(&current_ns()->unconfined->label);
+ else
+ label = aa_get_label(aa_current_label());
+
+ if (sock->sk) {
+ struct aa_sk_cxt *cxt = SK_CXT(sock->sk);
+ aa_put_label(cxt->label);
+ cxt->label = aa_get_label(label);
+ }
+ aa_put_label(label);
+
+ return 0;
+}
+
+/**
+ * apparmor_socket_bind - check perms before bind addr to socket
+ */
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ return aa_sock_bind_perm(sock, address, addrlen);
+}
+
+/**
+ * apparmor_socket_connect - check perms before connecting @sock to @address
+ */
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ return aa_sock_connect_perm(sock, address, addrlen);
+}
+
+/**
+ * apparmor_socket_list - check perms before allowing listen
+ */
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ return aa_sock_listen_perm(sock, backlog);
+}
+
+/**
+ * apparmor_socket_accept - check perms before accepting a new connection.
+ *
+ * Note: while @newsock is created and has some information, the accept
+ * has not been done.
+ */
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ return aa_sock_accept_perm(sock, newsock);
+}
+
+/**
+ * apparmor_socket_sendmsg - check perms before sending msg to another socket
+ */
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ int error = aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size);
+ if (!error) {
+ /* TODO: setup delegation on scm rights
+ see smack for AF_INET, AF_INET6 */
+ ;
+ }
+
+ return error;
+}
+
+/**
+ * apparmor_socket_recvmsg - check perms before receiving a message
+ */
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ return aa_sock_msg_perm(OP_RECVMSG, AA_MAY_RECEIVE, sock, msg, size);
+}
+
+/**
+ * apparmor_socket_getsockname - check perms before getting the local address
+ */
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ return aa_sock_perm(OP_GETSOCKNAME, AA_MAY_GETATTR, sock);
+}
+
+/**
+ * apparmor_socket_getpeername - check perms before getting remote address
+ */
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ return aa_sock_perm(OP_GETPEERNAME, AA_MAY_GETATTR, sock);
+}
+
+/**
+ * apparmor_getsockopt - check perms before getting socket options
+ */
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ return aa_sock_opt_perm(OP_GETSOCKOPT, AA_MAY_GETOPT, sock,
+ level, optname);
+}
+
+/**
+ * apparmor_setsockopt - check perms before setting socket options
+ */
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ return aa_sock_opt_perm(OP_SETSOCKOPT, AA_MAY_SETOPT, sock,
+ level, optname);
+}
+
+/**
+ * apparmor_socket_shutdown - check perms before shutting down @sock conn
+ */
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock);
+}
+
+/**
+ * apparmor_socket_sock_recv_skb - check perms before associating skb to sk
+ *
+ * Note: can not sleep maybe called with locks held
+
+dont want protocol specific in __skb_recv_datagram()
+to deny an incoming connection socket_sock_rcv_skb()
+
+ */
+static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
+{
+ /* TODO: */
+ return 0;
+}
+
+
+static struct aa_label *sk_peer_label(struct sock *sk)
+{
+ struct sock *peer_sk;
+ struct aa_sk_cxt *cxt = SK_CXT(sk);
+
+ if (cxt->peer)
+ return cxt->peer;
+
+ if (sk->sk_family != PF_UNIX)
+ return ERR_PTR(-ENOPROTOOPT);
+
+ /* check for sockpair peering which does not go through
+ * security_unix_stream_connect
+ */
+ peer_sk = unix_peer(sk);
+ if (peer_sk) {
+ cxt = SK_CXT(peer_sk);
+ if (cxt->label)
+ return cxt->label;
+ }
+
+ return ERR_PTR(-ENOPROTOOPT);
+}
+
+/**
+ * apparmor_socket_getpeersec_stream - get security context of peer
+ *
+ * Note: for tcp only valid if using ipsec or cipso on lan
+ */
+static int apparmor_socket_getpeersec_stream(struct socket *sock,
+ char __user *optval,
+ int __user *optlen, unsigned len)
+{
+ char *name;
+ int slen, error = 0;
+ struct aa_label *label = aa_current_label();
+ struct aa_label *peer = sk_peer_label(sock->sk);
+
+ if (IS_ERR(peer))
+ return PTR_ERR(peer);
+
+ slen = aa_label_asprint(&name, labels_ns(label), peer, true, GFP_KERNEL);
+ /* don't include terminating \0 in slen, it breaks some apps */
+ if (slen < 0) {
+ error = -ENOMEM;
+ } else {
+ if (slen > len) {
+ error = -ERANGE;
+ } else if (copy_to_user(optval, name, slen)) {
+ error = -EFAULT;
+ goto out;
+ }
+ if (put_user(slen, optlen))
+ error = -EFAULT;
+ out:
+ kfree(name);
+
+ }
+
+ return error;
+}
+
+/**
+ * apparmor_socket_getpeersec_dgram - get security label of packet
+ * @sock: the peer socket
+ * @skb: packet data
+ * @secid: pointer to where to put the secid of the packet
+ *
+ * Sets the netlabel socket state on sk from parent
+ */
+static int apparmor_socket_getpeersec_dgram(struct socket *sock,
+ struct sk_buff *skb, u32 *secid)
+
+{
+ /* TODO: requires secid support, and netlabel */
+ return -ENOPROTOOPT;
+}
+
+/**
+ * apparmor_sock_graft - Initialize newly created socket
+ * @sk: child sock
+ * @parent: parent socket
+ *
+ * Note: could set off of SOCK_CXT(parent) but need to track inode and we can
+ * just set sk security information off of current creating process label
+ * Labeling of sk for accept case - probably should be sock based
+ * instead of task, because of the case where an implicitly labeled
+ * socket is shared by different tasks.
+ */
+static void apparmor_sock_graft(struct sock *sk, struct socket *parent)
+{
+ struct aa_sk_cxt *cxt = SK_CXT(sk);
+ if (!cxt->label)
+ cxt->label = aa_get_current_label();
+}
+
+static int apparmor_task_kill(struct task_struct *target, struct siginfo *info,
+ int sig, u32 secid)
+{
+ struct aa_label *cl, *tl;
+ int error;
+
+ if (secid)
+ /* TODO: after secid to label mapping is done.
+ * Dealing with USB IO specific behavior
+ */
+ return 0;
+ cl = aa_begin_current_label();
+ tl = aa_get_task_label(target);
+ error = aa_may_signal(cl, tl, sig);
+ aa_put_label(tl);
+ aa_end_current_label(cl);
+
+ return error;
+}
+
+
static struct security_operations apparmor_ops = {
.name = "apparmor",
@@ -622,6 +1133,12 @@ static struct security_operations apparmor_ops = {
.capget = apparmor_capget,
.capable = apparmor_capable,
+ .inode_free_security = apparmor_inode_free_security,
+
+ .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,
@@ -635,6 +1152,7 @@ static struct security_operations apparmor_ops = {
.inode_getattr = apparmor_inode_getattr,
.file_open = apparmor_file_open,
+ .file_receive = apparmor_file_receive,
.file_permission = apparmor_file_permission,
.file_alloc_security = apparmor_file_alloc_security,
.file_free_security = apparmor_file_free_security,
@@ -646,6 +1164,31 @@ static struct security_operations apparmor_ops = {
.getprocattr = apparmor_getprocattr,
.setprocattr = apparmor_setprocattr,
+ .sk_alloc_security = apparmor_sk_alloc_security,
+ .sk_free_security = apparmor_sk_free_security,
+ .sk_clone_security = apparmor_sk_clone_security,
+
+ .unix_stream_connect = apparmor_unix_stream_connect,
+ .unix_may_send = apparmor_unix_may_send,
+
+ .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,
+ .socket_sock_rcv_skb = apparmor_socket_sock_rcv_skb,
+ .socket_getpeersec_stream = apparmor_socket_getpeersec_stream,
+ .socket_getpeersec_dgram = apparmor_socket_getpeersec_dgram,
+ .sock_graft = apparmor_sock_graft,
+
.cred_alloc_blank = apparmor_cred_alloc_blank,
.cred_free = apparmor_cred_free,
.cred_prepare = apparmor_cred_prepare,
@@ -657,6 +1200,7 @@ static struct security_operations apparmor_ops = {
.bprm_secureexec = apparmor_bprm_secureexec,
.task_setrlimit = apparmor_task_setrlimit,
+ .task_kill = apparmor_task_kill,
};
/*
@@ -667,6 +1211,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp);
static int param_get_aabool(char *buffer, const struct kernel_param *kp);
#define param_check_aabool param_check_bool
static struct kernel_param_ops param_ops_aabool = {
+ .flags = KERNEL_PARAM_OPS_FL_NOARG,
.set = param_set_aabool,
.get = param_get_aabool
};
@@ -683,6 +1228,7 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp);
#define param_check_aalockpolicy param_check_bool
static struct kernel_param_ops param_ops_aalockpolicy = {
+ .flags = KERNEL_PARAM_OPS_FL_NOARG,
.set = param_set_aalockpolicy,
.get = param_get_aalockpolicy
};
@@ -743,12 +1289,17 @@ module_param_named(paranoid_load, aa_g_paranoid_load, aabool,
/* Boot time disable flag */
static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
-module_param_named(enabled, apparmor_enabled, aabool, S_IRUSR);
+module_param_named(enabled, apparmor_enabled, bool, S_IRUGO);
+
+/* Boot time to set use of default or unconfined as initial profile */
+bool aa_g_unconfined_init = CONFIG_SECURITY_APPARMOR_UNCONFINED_INIT;
+module_param_named(unconfined, aa_g_unconfined_init, bool, S_IRUSR);
+
static int __init apparmor_enabled_setup(char *str)
{
unsigned long enabled;
- int error = strict_strtoul(str, 0, &enabled);
+ int error = kstrtoul(str, 0, &enabled);
if (!error)
apparmor_enabled = enabled ? 1 : 0;
return 1;
@@ -842,7 +1393,7 @@ static int param_get_mode(char *buffer, struct kernel_param *kp)
if (!apparmor_enabled)
return -EINVAL;
- return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]);
+ return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]);
}
static int param_set_mode(const char *val, struct kernel_param *kp)
@@ -857,8 +1408,8 @@ static int param_set_mode(const char *val, struct kernel_param *kp)
if (!val)
return -EINVAL;
- for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) {
- if (strcmp(val, profile_mode_names[i]) == 0) {
+ for (i = 0; i < APPARMOR_MODE_NAMES_MAX_INDEX; i++) {
+ if (strcmp(val, aa_profile_mode_names[i]) == 0) {
aa_g_profile_mode = i;
return 0;
}
@@ -873,8 +1424,6 @@ static int param_set_mode(const char *val, struct kernel_param *kp)
/**
* set_init_cxt - set a task context and profile on the first task.
- *
- * TODO: allow setting an alternate profile than unconfined
*/
static int __init set_init_cxt(void)
{
@@ -885,8 +1434,52 @@ static int __init set_init_cxt(void)
if (!cxt)
return -ENOMEM;
- cxt->profile = aa_get_profile(root_ns->unconfined);
- cred->security = cxt;
+ if (!aa_g_unconfined_init) {
+ cxt->label = aa_setup_default_label();
+ if (!cxt->label) {
+ aa_free_task_context(cxt);
+ return -ENOMEM;
+ }
+ /* fs setup of default is done in aa_create_aafs() */
+ } else
+ cxt->label = aa_get_label(&root_ns->unconfined->label);
+ cred_cxt(cred) = cxt;
+
+ return 0;
+}
+
+static void destroy_buffers(void)
+{
+ u32 i, j;
+
+ for_each_possible_cpu(i) {
+ for_each_cpu_buffer(j) {
+ kfree(per_cpu(aa_buffers, i).buf[j]);
+ per_cpu(aa_buffers, i).buf[j] = NULL;
+ }
+ }
+}
+
+static int __init alloc_buffers(void)
+{
+ u32 i, j;
+
+ for_each_possible_cpu(i) {
+ for_each_cpu_buffer(j) {
+ char *buffer;
+ if (cpu_to_node(i) > num_online_nodes())
+ /* fallback to kmalloc for offline nodes */
+ buffer = kmalloc(aa_g_path_max, GFP_KERNEL);
+ else
+ buffer = kmalloc_node(aa_g_path_max, GFP_KERNEL,
+ cpu_to_node(i));
+ if (!buffer) {
+ destroy_buffers();
+ return -ENOMEM;
+ }
+ per_cpu(aa_buffers, i).buf[j] = buffer;
+ }
+ }
return 0;
}
@@ -907,6 +1500,12 @@ static int __init apparmor_init(void)
goto alloc_out;
}
+ error = alloc_buffers();
+ if (error) {
+ AA_ERROR("Unable to allocate work buffers\n");
+ goto buffers_out;
+ }
+
error = set_init_cxt();
if (error) {
AA_ERROR("Failed to set context on init task\n");
@@ -915,8 +1514,11 @@ static int __init apparmor_init(void)
error = register_security(&apparmor_ops);
if (error) {
+ struct cred *cred = (struct cred *)current->real_cred;
+ aa_free_task_context(cred_cxt(cred));
+ cred_cxt(cred) = NULL;
AA_ERROR("Unable to register AppArmor\n");
- goto set_init_cxt_out;
+ goto register_security_out;
}
/* Report that AppArmor successfully initialized */
@@ -930,12 +1532,12 @@ static int __init apparmor_init(void)
return error;
-set_init_cxt_out:
- aa_free_task_context(current->real_cred->security);
-
register_security_out:
aa_free_root_ns();
+buffers_out:
+ destroy_buffers();
+
alloc_out:
aa_destroy_aafs();
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
index 90971a8..727eb42 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -4,7 +4,7 @@
* This file contains AppArmor dfa based regular expression matching engine
*
* Copyright (C) 1998-2008 Novell/SUSE
- * Copyright 2009-2010 Canonical Ltd.
+ * 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
@@ -23,6 +23,8 @@
#include "include/apparmor.h"
#include "include/match.h"
+#define base_idx(X) ((X) & 0xffffff)
+
/**
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
* @blob: data to unpack (NOT NULL)
@@ -30,7 +32,7 @@
*
* Returns: pointer to table else NULL on failure
*
- * NOTE: must be freed by kvfree (not kmalloc)
+ * NOTE: must be freed by kvfree (not kfree)
*/
static struct table_header *unpack_table(char *blob, size_t bsize)
{
@@ -57,7 +59,7 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
if (bsize < tsize)
goto out;
- table = kvmalloc(tsize);
+ table = kvzalloc(tsize);
if (table) {
*table = th;
if (th.td_flags == YYTD_DATA8)
@@ -137,8 +139,7 @@ static int verify_dfa(struct aa_dfa *dfa, int flags)
for (i = 0; i < state_count; i++) {
if (DEFAULT_TABLE(dfa)[i] >= state_count)
goto out;
- /* TODO: do check that DEF state recursion terminates */
- if (BASE_TABLE(dfa)[i] + 255 >= trans_count) {
+ if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) {
printk(KERN_ERR "AppArmor DFA next/check upper "
"bounds error\n");
goto out;
@@ -314,7 +315,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
for (; len; len--) {
- pos = base[state] + equiv[(u8) *str++];
+ pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
@@ -323,7 +324,7 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
} else {
/* default is direct to next state */
for (; len; len--) {
- pos = base[state] + (u8) *str++;
+ pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
@@ -364,7 +365,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
while (*str) {
- pos = base[state] + equiv[(u8) *str++];
+ pos = base_idx(base[state]) + equiv[(u8) *str++];
if (check[pos] == state)
state = next[pos];
else
@@ -373,7 +374,7 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
} else {
/* default is direct to next state */
while (*str) {
- pos = base[state] + (u8) *str++;
+ pos = base_idx(base[state]) + (u8) *str++;
if (check[pos] == state)
state = next[pos];
else
@@ -409,14 +410,14 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
u8 *equiv = EQUIV_TABLE(dfa);
/* default is direct to next state */
- pos = base[state] + equiv[(u8) c];
+ pos = base_idx(base[state]) + equiv[(u8) c];
if (check[pos] == state)
state = next[pos];
else
state = def[state];
} else {
/* default is direct to next state */
- pos = base[state] + (u8) c;
+ pos = base_idx(base[state]) + (u8) c;
if (check[pos] == state)
state = next[pos];
else
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
new file mode 100644
index 0000000..ee6229df
--- /dev/null
+++ b/security/apparmor/mount.c
@@ -0,0 +1,703 @@
+/*
+ * 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 <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+
+#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 (aad(sa)->mnt.type) {
+ audit_log_format(ab, " fstype=");
+ audit_log_untrustedstring(ab, aad(sa)->mnt.type);
+ }
+ if (aad(sa)->mnt.src_name) {
+ audit_log_format(ab, " srcname=");
+ audit_log_untrustedstring(ab, aad(sa)->mnt.src_name);
+ }
+ if (aad(sa)->mnt.trans) {
+ audit_log_format(ab, " trans=");
+ audit_log_untrustedstring(ab, aad(sa)->mnt.trans);
+ }
+ if (aad(sa)->mnt.flags || aad(sa)->op == OP_MOUNT) {
+ audit_log_format(ab, " flags=\"");
+ audit_mnt_flags(ab, aad(sa)->mnt.flags);
+ audit_log_format(ab, "\"");
+ }
+ if (aad(sa)->mnt.data) {
+ audit_log_format(ab, " options=");
+ audit_log_untrustedstring(ab, aad(sa)->mnt.data);
+ }
+}
+
+/**
+ * audit_mount - handle the auditing of mount operations
+ * @profile: the profile being enforced (NOT NULL)
+ * @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, 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;
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
+
+ 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;
+ }
+
+ aad(&sa)->name = name;
+ aad(&sa)->mnt.src_name = src_name;
+ aad(&sa)->mnt.type = type;
+ aad(&sa)->mnt.trans = trans;
+ aad(&sa)->mnt.flags = flags;
+ if (data && (perms->audit & AA_AUDIT_DATA))
+ aad(&sa)->mnt.data = data;
+ aad(&sa)->info = info;
+ aad(&sa)->error = error;
+
+ return aa_audit(audit_type, profile, &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 *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_MNT_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 = NULL;
+ int pos, error = -EACCES;
+
+ if (!profile->policy.dfa)
+ goto audit;
+
+ 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];
+ goto audit;
+ }
+ error = 0;
+
+audit:
+ return audit_mount(profile, OP_MOUNT, mntpnt, devname, type, NULL,
+ flags, data, AA_MAY_MOUNT, &perms, info, error);
+}
+
+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_label *label, struct path *path, unsigned long flags,
+ void *data)
+{
+ struct aa_profile *profile;
+ const char *name, *info = NULL;
+ char *buffer = NULL;
+ bool binary;
+ int error;
+
+ binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
+
+ get_buffers(buffer);
+ error = aa_path_name(path, path_flags(labels_profile(label), path),
+ buffer, &name, &info,
+ labels_profile(label)->disconnected);
+ if (error) {
+ error = audit_mount(labels_profile(label), OP_MOUNT, name, NULL,
+ NULL, NULL, flags, data, AA_MAY_MOUNT,
+ &nullperms, info, error);
+ goto out;
+ }
+
+ error = fn_for_each_confined(label, profile,
+ match_mnt(profile, name, NULL, NULL, flags, data,
+ binary));
+
+out:
+ put_buffers(buffer);
+
+ return error;
+}
+
+int aa_bind_mount(struct aa_label *label, struct path *path,
+ const char *dev_name, unsigned long flags)
+{
+ struct aa_profile *profile;
+ 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;
+
+ get_buffers(buffer, old_buffer);
+ error = aa_path_name(path, path_flags(labels_profile(label), path), buffer, &name,
+ &info, labels_profile(label)->disconnected);
+ if (error)
+ goto error;
+
+ error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path);
+ if (error)
+ goto error;
+
+ error = aa_path_name(&old_path, path_flags(labels_profile(label),
+ &old_path),
+ old_buffer, &old_name, &info,
+ labels_profile(label)->disconnected);
+ path_put(&old_path);
+ if (error)
+ goto error;
+
+ error = fn_for_each_confined(label, profile,
+ match_mnt(profile, name, old_name, NULL, flags, NULL,
+ false));
+
+out:
+ put_buffers(buffer, old_buffer);
+
+ return error;
+
+error:
+ error = fn_for_each(label, profile,
+ audit_mount(profile, OP_MOUNT, name, old_name, NULL,
+ NULL, flags, NULL, AA_MAY_MOUNT, &nullperms,
+ info, error));
+ goto out;
+}
+
+int aa_mount_change_type(struct aa_label *label, struct path *path,
+ unsigned long flags)
+{
+ struct aa_profile *profile;
+ 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);
+
+ get_buffers(buffer);
+ error = aa_path_name(path, path_flags(labels_profile(label), path),
+ buffer, &name, &info,
+ labels_profile(label)->disconnected);
+ if (error) {
+ error = fn_for_each(label, profile,
+ audit_mount(profile, OP_MOUNT, name, NULL,
+ NULL, NULL, flags, NULL,
+ AA_MAY_MOUNT, &nullperms, info,
+ error));
+ goto out;
+ }
+
+ error = fn_for_each_confined(label, profile,
+ match_mnt(profile, name, NULL, NULL, flags, NULL,
+ false));
+
+out:
+ put_buffers(buffer);
+
+ return error;
+}
+
+int aa_move_mount(struct aa_label *label, struct path *path,
+ const char *orig_name)
+{
+ struct aa_profile *profile;
+ 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;
+
+ get_buffers(buffer, old_buffer);
+ error = aa_path_name(path, path_flags(labels_profile(label), path),
+ buffer, &name, &info,
+ labels_profile(label)->disconnected);
+ if (error)
+ goto error;
+
+ error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path);
+ if (error)
+ goto error;
+
+ error = aa_path_name(&old_path, path_flags(labels_profile(label),
+ &old_path),
+ old_buffer, &old_name, &info,
+ labels_profile(label)->disconnected);
+ path_put(&old_path);
+ if (error)
+ goto error;
+
+ error = fn_for_each_confined(label, profile,
+ match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL,
+ false));
+
+out:
+ put_buffers(buffer, old_buffer);
+
+ return error;
+
+error:
+ error = fn_for_each(label, profile,
+ audit_mount(profile, OP_MOUNT, name, old_name, NULL,
+ NULL, MS_MOVE, NULL, AA_MAY_MOUNT,
+ &nullperms, info, error));
+ goto out;
+}
+
+int aa_new_mount(struct aa_label *label, const char *orig_dev_name,
+ struct path *path, const char *type, unsigned long flags,
+ void *data)
+{
+ struct aa_profile *profile;
+ char *buffer = NULL, *dev_buffer = NULL;
+ const char *name = NULL, *dev_name = NULL, *info = NULL;
+ bool binary = true;
+ int error;
+
+ dev_name = orig_dev_name;
+ get_buffers(buffer, dev_buffer);
+ 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 error;
+
+ error = aa_path_name(&dev_path,
+ path_flags(labels_profile(label),
+ &dev_path),
+ dev_buffer, &dev_name, &info,
+ labels_profile(label)->disconnected);
+ path_put(&dev_path);
+ if (error)
+ goto error;
+ }
+ }
+
+ error = aa_path_name(path, path_flags(labels_profile(label), path),
+ buffer, &name, &info,
+ labels_profile(label)->disconnected);
+ if (error)
+ goto error;
+
+ error = fn_for_each_confined(label, profile,
+ match_mnt(profile, name, dev_name, type, flags, data,
+ binary));
+
+cleanup:
+ put_buffers(buffer, dev_buffer);
+
+out:
+ return error;
+
+error:
+ error = fn_for_each(label, profile,
+ audit_mount(labels_profile(label), OP_MOUNT, name,
+ dev_name, type, NULL, flags, data,
+ AA_MAY_MOUNT, &nullperms, info, error));
+ goto cleanup;
+}
+
+static int profile_umount(struct aa_profile *profile, const char *name)
+{
+ struct file_perms perms = { };
+ const char *info = NULL;
+ int e = 0;
+
+ if (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)
+ e = -EACCES;
+ } else
+ e = -EACCES;
+ return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
+ AA_MAY_UMOUNT, &perms, info, e);
+}
+
+int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
+{
+ struct aa_profile *profile;
+ char *buffer = NULL;
+ const char *name, *info = NULL;
+ int error;
+
+ struct path path = { mnt, mnt->mnt_root };
+ get_buffers(buffer);
+ error = aa_path_name(&path, path_flags(labels_profile(label), &path),
+ buffer, &name, &info,
+ labels_profile(label)->disconnected);
+ if (error) {
+ error = fn_for_each(label, profile,
+ audit_mount(profile, OP_UMOUNT, name, NULL,
+ NULL, NULL, 0, NULL, AA_MAY_UMOUNT,
+ &nullperms, info, error));
+ goto out;
+ }
+
+ error = fn_for_each_confined(label, profile,
+ profile_umount(profile, name));
+
+out:
+ put_buffers(buffer);
+
+ return error;
+}
+
+static int profile_pivotroot(struct aa_profile *profile, const char *new_name,
+ const char *old_name, struct aa_profile **trans)
+{
+ struct aa_profile *target = NULL;
+ struct file_perms perms = { };
+ const char *info = NULL;
+ int error = -EACCES;
+
+ /* TODO: actual domain transition computation for multiple
+ * profiles
+ */
+ 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
+ *trans = target;
+ } else
+ error = 0;
+ }
+ }
+
+ error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
+ NULL, target ? target->base.name : NULL,
+ 0, NULL, AA_MAY_PIVOTROOT, &perms, info,
+ error);
+ if (!*trans)
+ aa_put_profile(target);
+
+ return error;
+}
+
+int aa_pivotroot(struct aa_label *label, struct path *old_path,
+ struct path *new_path)
+{
+ struct aa_profile *profile, *target = NULL;
+ char *old_buffer = NULL, *new_buffer = NULL;
+ const char *old_name, *new_name = NULL, *info = NULL;
+ int error;
+
+ get_buffers(old_buffer, new_buffer);
+ error = aa_path_name(old_path, path_flags(labels_profile(label),
+ old_path),
+ old_buffer, &old_name, &info,
+ labels_profile(label)->disconnected);
+ if (error)
+ goto error;
+
+ error = aa_path_name(new_path, path_flags(labels_profile(label),
+ new_path),
+ new_buffer, &new_name, &info,
+ labels_profile(label)->disconnected);
+ if (error)
+ goto error;
+
+ error = fn_for_each(label, profile,
+ profile_pivotroot(profile, new_name, old_name,
+ &target));
+out:
+ put_buffers(old_buffer, new_buffer);
+
+ if (target)
+ error = aa_replace_current_label(&target->label);
+
+ return error;
+
+error:
+ error = fn_for_each(label, profile,
+ audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
+ NULL, NULL,
+ 0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
+ error));
+ goto out;
+}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..e1deffb
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,397 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2014 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/af_unix.h"
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/label.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),
+ AA_FS_FILE_BOOLEAN("af_unix", 1),
+ { }
+};
+
+static const char *net_mask_names[] = {
+ "unknown",
+ "send",
+ "receive",
+ "unknown",
+
+ "create",
+ "shutdown",
+ "connect",
+ "unknown",
+
+ "setattr",
+ "getattr",
+ "setcred",
+ "getcred",
+
+ "chmod",
+ "chown",
+ "chgrp",
+ "lock",
+
+ "mmap",
+ "mprot",
+ "unknown",
+ "unknown",
+
+ "accept",
+ "bind",
+ "listen",
+ "unknown",
+
+ "setopt",
+ "getopt",
+ "unknown",
+ "unknown",
+
+ "unknown",
+ "unknown",
+ "unknown",
+ "unknown",
+};
+
+static void audit_unix_addr(struct audit_buffer *ab, const char *str,
+ struct sockaddr_un *addr, int addrlen)
+{
+ int len = unix_addr_len(addrlen);
+
+ if (!addr || len <= 0) {
+ audit_log_format(ab, " %s=none", str);
+ } else if (addr->sun_path[0]) {
+ audit_log_format(ab, " %s=", str);
+ audit_log_untrustedstring(ab, addr->sun_path);
+ } else {
+ audit_log_format(ab, " %s=\"@", str);
+ if (audit_string_contains_control(&addr->sun_path[1], len - 1))
+ audit_log_n_hex(ab, &addr->sun_path[1], len - 1);
+ else
+ audit_log_format(ab, "%.*s", len - 1,
+ &addr->sun_path[1]);
+ audit_log_format(ab, "\"");
+ }
+}
+
+static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str,
+ struct sock *sk)
+{
+ struct unix_sock *u = unix_sk(sk);
+ if (u && u->addr)
+ audit_unix_addr(ab, str, u->addr->name, u->addr->len);
+ else
+ audit_unix_addr(ab, str, NULL, 0);
+}
+
+/* audit callback for net specific fields */
+void audit_net_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[aad(sa)->net.type]) {
+ audit_log_string(ab, sock_type_names[aad(sa)->net.type]);
+ } else {
+ audit_log_format(ab, "\"unknown(%d)\"", aad(sa)->net.type);
+ }
+ audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol);
+
+ if (aad(sa)->request & NET_PERMS_MASK) {
+ audit_log_format(ab, " requested_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->request, NULL, 0,
+ net_mask_names, NET_PERMS_MASK);
+
+ if (aad(sa)->denied & NET_PERMS_MASK) {
+ audit_log_format(ab, " denied_mask=");
+ aa_audit_perm_mask(ab, aad(sa)->denied, NULL, 0,
+ net_mask_names, NET_PERMS_MASK);
+ }
+ }
+ if (sa->u.net->family == AF_UNIX) {
+ if ((aad(sa)->request & ~NET_PEER_MASK) && aad(sa)->net.addr)
+ audit_unix_addr(ab, "addr",
+ unix_addr(aad(sa)->net.addr),
+ aad(sa)->net.addrlen);
+ else
+ audit_unix_sk_addr(ab, "addr", sa->u.net->sk);
+ if (aad(sa)->request & NET_PEER_MASK) {
+ if (aad(sa)->net.addr)
+ audit_unix_addr(ab, "peer_addr",
+ unix_addr(aad(sa)->net.addr),
+ aad(sa)->net.addrlen);
+ else
+ audit_unix_sk_addr(ab, "peer_addr",
+ aad(sa)->net.peer_sk);
+ }
+ }
+ if (aad(sa)->target) {
+ audit_log_format(ab, " peer=");
+ audit_log_untrustedstring(ab, aad(sa)->target);
+ }
+}
+
+/**
+ * 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;
+ DEFINE_AUDIT_NET(sa, op, sk, family, type, protocol);
+ aad(&sa)->error = error;
+
+ if (likely(!aad(&sa)->error)) {
+ u16 audit_mask = profile->net.audit[sa.u.net->family];
+ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << aad(&sa)->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 << aad(&sa)->net.type);
+
+ 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 : aad(&sa)->error;
+ }
+
+ return aa_audit(audit_type, profile, &sa, audit_net_cb);
+}
+
+static inline int aa_af_mask_perm(struct aa_profile *profile, u16 family,
+ int type)
+{
+ u16 family_mask;
+
+ AA_BUG(family >= AF_MAX);
+ AA_BUG(type < 0 && type >= SOCK_MAX);
+
+ if (profile_unconfined(profile))
+ return 0;
+
+ family_mask = profile->net.allow[family];
+ return (family_mask & (1 << type)) ? 0 : -EACCES;
+
+}
+
+/* Generic af perm */
+int aa_profile_af_perm(struct aa_profile *profile, int op, u16 family,
+ int type, int protocol, struct sock *sk)
+{
+ int error = aa_af_mask_perm(profile, family, type);
+
+ return audit_net(profile, op, family, type, protocol, sk, error);
+}
+
+int aa_af_perm(struct aa_label *label, int op, u32 request, u16 family,
+ int type, int protocol, struct sock *sk)
+{
+ struct aa_profile *profile;
+
+ return fn_for_each_confined(label, profile,
+ aa_profile_af_perm(profile, op, family, type, protocol,
+ sk));
+}
+
+static int aa_label_sk_perm(struct aa_label *label, int op, u32 request,
+ struct sock *sk)
+{
+ struct aa_profile *profile;
+
+ AA_BUG(!label);
+ AA_BUG(!sk);
+
+ if (unconfined(label))
+ return 0;
+
+ return fn_for_each_confined(label, profile,
+ aa_profile_af_perm(profile, op, sk->sk_family,
+ sk->sk_type, sk->sk_protocol,
+ sk));
+
+}
+
+static int aa_sk_perm(int op, u32 request, struct sock *sk)
+{
+ struct aa_label *label;
+
+ AA_BUG(!sk);
+ AA_BUG(in_interrupt());
+
+ /* TODO: switch to begin_current_label ???? */
+ label = aa_current_label();
+ return aa_label_sk_perm(label, op, request, sk);
+}
+
+#define af_select(FAMILY, FN, DEF_FN) \
+({ \
+ int __e; \
+ switch ((FAMILY)) { \
+ case AF_UNIX: \
+ __e = aa_unix_ ## FN; \
+ break; \
+ default: \
+ __e = DEF_FN; \
+ } \
+ __e; \
+})
+
+/* TODO: push into lsm.c ???? */
+
+/* revaliation, get/set attr, shutdown */
+int aa_sock_perm(int op, u32 request, struct socket *sock)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ sock_perm(op, request, sock),
+ aa_sk_perm(op, request, sock->sk));
+}
+
+int aa_sock_create_perm(struct aa_label *label, int family, int type,
+ int protocol)
+{
+ AA_BUG(!label);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(family,
+ create_perm(label, family, type, protocol),
+ aa_af_perm(label, OP_CREATE, AA_MAY_CREATE, family,
+ type, protocol, NULL));
+}
+
+int aa_sock_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(!address);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ bind_perm(sock, address, addrlen),
+ aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk));
+}
+
+int aa_sock_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(!address);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ connect_perm(sock, address, addrlen),
+ aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk));
+}
+
+int aa_sock_listen_perm(struct socket *sock, int backlog)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ listen_perm(sock, backlog),
+ aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk));
+}
+
+/* ability of sock to connect, not peer address binding */
+int aa_sock_accept_perm(struct socket *sock, struct socket *newsock)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(!newsock);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ accept_perm(sock, newsock),
+ aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk));
+}
+
+/* sendmsg, recvmsg */
+int aa_sock_msg_perm(int op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(!msg);
+ /* TODO: .... */
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ msg_perm(op, request, sock, msg, size),
+ aa_sk_perm(op, request, sock->sk));
+}
+
+/* revaliation, get/set attr, opt */
+int aa_sock_opt_perm(int op, u32 request, struct socket *sock, int level,
+ int optname)
+{
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+ AA_BUG(in_interrupt());
+
+ return af_select(sock->sk->sk_family,
+ opt_perm(op, request, sock, level, optname),
+ aa_sk_perm(op, request, sock->sk));
+}
+
+int aa_sock_file_perm(struct aa_label *label, int op, u32 request,
+ struct socket *sock)
+{
+ AA_BUG(!label);
+ AA_BUG(!sock);
+ AA_BUG(!sock->sk);
+
+ return af_select(sock->sk->sk_family,
+ file_perm(label, op, request, sock),
+ aa_label_sk_perm(label, op, request, sock->sk));
+}
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
index e91ffee..bcdf7bb 100644
--- a/security/apparmor/path.c
+++ b/security/apparmor/path.c
@@ -25,7 +25,6 @@
#include "include/path.h"
#include "include/policy.h"
-
/* modified from dcache.c */
static int prepend(char **buffer, int buflen, const char *str, int namelen)
{
@@ -39,13 +38,50 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
#define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
+/* If the path is not connected to the expected root,
+ * check if it is a sysctl and handle specially else remove any
+ * leading / that __d_path may have returned.
+ * Unless
+ * specifically directed to connect the path,
+ * OR
+ * if in a chroot and doing chroot relative paths and the path
+ * resolves to the namespace root (would be connected outside
+ * of chroot) and specifically directed to connect paths to
+ * namespace root.
+ */
+static int disconnect(struct path *path, char *buf, char **name, int flags,
+ const char *disconnected)
+{
+ int error = 0;
+
+ if (!(flags & PATH_CONNECT_PATH) &&
+ !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
+ our_mnt(path->mnt))) {
+ /* disconnected path, don't return pathname starting
+ * with '/'
+ */
+ error = -EACCES;
+ if (**name == '/')
+ *name = *name + 1;
+ } else {
+ if (**name != '/')
+ /* CONNECT_PATH with missing root */
+ error = prepend(name, *name - buf, "/", 1);
+ if (!error && disconnected)
+ error = prepend(name, *name - buf, disconnected,
+ strlen(disconnected));
+ }
+
+ return error;
+}
+
/**
* d_namespace_path - lookup a name associated with a given path
* @path: path to lookup (NOT NULL)
* @buf: buffer to store path to (NOT NULL)
- * @buflen: length of @buf
* @name: Returns - pointer for start of path name with in @buf (NOT NULL)
* @flags: flags controlling path lookup
+ * @disconnected: string to prefix to disconnected paths
*
* Handle path name lookup.
*
@@ -53,12 +89,14 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
* When no error the path name is returned in @name which points to
* to a position in @buf
*/
-static int d_namespace_path(struct path *path, char *buf, int buflen,
- char **name, int flags)
+static int d_namespace_path(struct path *path, char *buf, char **name,
+ int flags, const char *disconnected)
{
char *res;
int error = 0;
int connected = 1;
+ int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
+ int buflen = aa_g_path_max - isdir;
if (path->mnt->mnt_flags & MNT_INTERNAL) {
/* it's not mounted anywhere */
@@ -73,9 +111,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
/* TODO: convert over to using a per namespace
* control instead of hard coded /proc
*/
- return prepend(name, *name - buf, "/proc", 5);
- }
- return 0;
+ error = prepend(name, *name - buf, "/proc", 5);
+ goto out;
+ } else
+ error = disconnect(path, buf, name, flags,
+ disconnected);
+ goto out;
}
/* resolve paths relative to chroot?*/
@@ -94,8 +135,11 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
* be returned.
*/
if (!res || IS_ERR(res)) {
- if (PTR_ERR(res) == -ENAMETOOLONG)
- return -ENAMETOOLONG;
+ if (PTR_ERR(res) == -ENAMETOOLONG) {
+ error = -ENAMETOOLONG;
+ *name = buf;
+ goto out;
+ }
connected = 0;
res = dentry_path_raw(path->dentry, buf, buflen);
if (IS_ERR(res)) {
@@ -120,78 +164,28 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
goto out;
}
- /* If the path is not connected to the expected root,
- * check if it is a sysctl and handle specially else remove any
- * leading / that __d_path may have returned.
- * Unless
- * specifically directed to connect the path,
- * OR
- * if in a chroot and doing chroot relative paths and the path
- * resolves to the namespace root (would be connected outside
- * of chroot) and specifically directed to connect paths to
- * namespace root.
- */
- if (!connected) {
- if (!(flags & PATH_CONNECT_PATH) &&
- !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
- our_mnt(path->mnt))) {
- /* disconnected path, don't return pathname starting
- * with '/'
- */
- error = -EACCES;
- if (*res == '/')
- *name = res + 1;
- }
- }
+ if (!connected)
+ error = disconnect(path, buf, name, flags, disconnected);
out:
- return error;
-}
-
-/**
- * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended
- * @path: path to get name for (NOT NULL)
- * @flags: flags controlling path lookup
- * @buffer: buffer to put name in (NOT NULL)
- * @size: size of buffer
- * @name: Returns - contains position of path name in @buffer (NOT NULL)
- *
- * Returns: %0 else error on failure
- */
-static int get_name_to_buffer(struct path *path, int flags, char *buffer,
- int size, char **name, const char **info)
-{
- int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
- int error = d_namespace_path(path, buffer, size - adjust, name, flags);
-
- if (!error && (flags & PATH_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], "/");
-
- if (info && error) {
- if (error == -ENOENT)
- *info = "Failed name lookup - deleted entry";
- else if (error == -ESTALE)
- *info = "Failed name lookup - disconnected path";
- else if (error == -ENAMETOOLONG)
- *info = "Failed name lookup - name too long";
- else
- *info = "Failed name lookup";
- }
+ /*
+ * Append "/" to the pathname. The root directory is a special
+ * case; it already ends in slash.
+ */
+ if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
+ strcpy(&buf[aa_g_path_max - 2], "/");
return error;
}
/**
- * aa_path_name - compute the pathname of a file
+ * aa_path_name - get the pathname to a buffer ensure dir / is appended
* @path: path the file (NOT NULL)
* @flags: flags controlling path name generation
- * @buffer: buffer that aa_get_name() allocated (NOT NULL)
+ * @buffer: buffer to put name in (NOT NULL)
* @name: Returns - the generated path name if !error (NOT NULL)
* @info: Returns - information on why the path lookup failed (MAYBE NULL)
+ * @disconnected: string to prepend to disconnected paths
*
* @name is a pointer to the beginning of the pathname (which usually differs
* from the beginning of the buffer), or NULL. If there is an error @name
@@ -204,33 +198,24 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer,
*
* Returns: %0 else error code if could retrieve name
*/
-int aa_path_name(struct path *path, int flags, char **buffer, const char **name,
- const char **info)
+int aa_path_name(struct path *path, int flags, char *buffer, const char **name,
+ const char **info, const char *disconnected)
{
- char *buf, *str = NULL;
- int size = 256;
- int error;
-
- *name = NULL;
- *buffer = NULL;
- for (;;) {
- /* freed by caller */
- buf = kmalloc(size, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- error = get_name_to_buffer(path, flags, buf, size, &str, info);
- if (error != -ENAMETOOLONG)
- break;
-
- kfree(buf);
- size <<= 1;
- if (size > aa_g_path_max)
- return -ENAMETOOLONG;
- *info = NULL;
+ char *str = NULL;
+ int error = d_namespace_path(path, buffer, &str, flags, disconnected);
+
+
+ if (info && error) {
+ if (error == -ENOENT)
+ *info = "Failed name lookup - deleted entry";
+ else if (error == -EACCES)
+ *info = "Failed name lookup - disconnected path";
+ else if (error == -ENAMETOOLONG)
+ *info = "Failed name lookup - name too long";
+ else
+ *info = "Failed name lookup";
}
- *buffer = buf;
- *name = str;
+ *name = str;
return error;
}
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 8132003..7a9d4c8 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -82,21 +82,25 @@
#include "include/context.h"
#include "include/file.h"
#include "include/ipc.h"
+#include "include/label.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
#include "include/resource.h"
-#include "include/sid.h"
/* root profile namespace */
struct aa_namespace *root_ns;
-const char *const profile_mode_names[] = {
+/* Note: mode names must be unique in the first character because of
+ * modechrs used to print modes on compound labels on some interfaces
+ */
+const char *const aa_profile_mode_names[] = {
"enforce",
"complain",
"kill",
+ "unconfined",
};
/**
@@ -128,21 +132,26 @@ static const char *hname_tail(const char *hname)
static bool policy_init(struct aa_policy *policy, const char *prefix,
const char *name)
{
+ char *hname;
+
/* freed by policy_free */
if (prefix) {
- policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3,
- GFP_KERNEL);
- if (policy->hname)
- sprintf(policy->hname, "%s//%s", prefix, name);
- } else
- policy->hname = kstrdup(name, GFP_KERNEL);
- if (!policy->hname)
+ hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3,
+ GFP_KERNEL);
+ if (hname)
+ sprintf(hname, "%s//%s", prefix, name);
+ } else {
+ hname = aa_str_alloc(strlen(name) + 1, GFP_KERNEL);
+ if (hname)
+ strcpy(hname, name);
+ }
+ if (!hname)
return 0;
+ policy->hname = hname;
/* base.name is a substring of fqname */
policy->name = (char *)hname_tail(policy->hname);
INIT_LIST_HEAD(&policy->list);
INIT_LIST_HEAD(&policy->profiles);
- kref_init(&policy->count);
return 1;
}
@@ -154,20 +163,20 @@ static bool policy_init(struct aa_policy *policy, const char *prefix,
static void policy_destroy(struct aa_policy *policy)
{
/* still contains profiles -- invalid */
- if (!list_empty(&policy->profiles)) {
+ if (on_list_rcu(&policy->profiles)) {
AA_ERROR("%s: internal error, "
"policy '%s' still contains profiles\n",
__func__, policy->name);
BUG();
}
- if (!list_empty(&policy->list)) {
+ if (on_list_rcu(&policy->list)) {
AA_ERROR("%s: internal error, policy '%s' still on list\n",
__func__, policy->name);
BUG();
}
/* don't free name as its a subset of hname */
- kzfree(policy->hname);
+ aa_put_str(policy->hname);
}
/**
@@ -175,7 +184,7 @@ static void policy_destroy(struct aa_policy *policy)
* @head: list to search (NOT NULL)
* @name: name to search for (NOT NULL)
*
- * Requires: correct locks for the @head list be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted policy that match @name or NULL if not found
*/
@@ -183,7 +192,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name)
{
struct aa_policy *policy;
- list_for_each_entry(policy, head, list) {
+ list_for_each_entry_rcu(policy, head, list) {
if (!strcmp(policy->name, name))
return policy;
}
@@ -196,7 +205,7 @@ static struct aa_policy *__policy_find(struct list_head *head, const char *name)
* @str: string to search for (NOT NULL)
* @len: length of match required
*
- * Requires: correct locks for the @head list be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted policy that match @str or NULL if not found
*
@@ -208,7 +217,7 @@ static struct aa_policy *__policy_strn_find(struct list_head *head,
{
struct aa_policy *policy;
- list_for_each_entry(policy, head, list) {
+ list_for_each_entry_rcu(policy, head, list) {
if (aa_strneq(policy->name, str, len))
return policy;
}
@@ -220,7 +229,7 @@ static struct aa_policy *__policy_strn_find(struct list_head *head,
* Routines for AppArmor namespaces
*/
-static const char *hidden_ns_name = "---";
+const char *aa_hidden_ns_name = "---";
/**
* aa_ns_visible - test if @view is visible from @curr
* @curr: namespace to treat as the parent (NOT NULL)
@@ -262,7 +271,7 @@ const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view)
*/
return view->base.hname + strlen(curr->base.hname) + 2;
} else
- return hidden_ns_name;
+ return aa_hidden_ns_name;
}
/**
@@ -285,26 +294,32 @@ static struct aa_namespace *alloc_namespace(const char *prefix,
goto fail_ns;
INIT_LIST_HEAD(&ns->sub_ns);
- rwlock_init(&ns->lock);
+ mutex_init(&ns->lock);
/* released by free_namespace */
ns->unconfined = aa_alloc_profile("unconfined");
if (!ns->unconfined)
goto fail_unconfined;
+ ns->unconfined->label.replacedby = aa_alloc_replacedby(NULL);
+ if (!ns->unconfined->label.replacedby)
+ goto fail_replacedby;
- ns->unconfined->sid = aa_alloc_sid();
- ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR |
- PFLAG_IMMUTABLE;
+ ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
+ FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
+ ns->unconfined->mode = APPARMOR_UNCONFINED;
- /*
- * released by free_namespace, however __remove_namespace breaks
- * the cyclic references (ns->unconfined, and unconfined->ns) and
- * replaces with refs to parent namespace unconfined
- */
- ns->unconfined->ns = aa_get_namespace(ns);
+ /* ns and ns->unconfined share ns->unconfined refcount */
+ ns->unconfined->ns = ns;
+
+ atomic_set(&ns->uniq_null, 0);
+
+ aa_labelset_init(&ns->labels);
return ns;
+fail_replacedby:
+ aa_free_profile(ns->unconfined);
+
fail_unconfined:
kzfree(ns->base.hname);
fail_ns:
@@ -313,49 +328,62 @@ fail_ns:
}
/**
- * free_namespace - free a profile namespace
+ * aa_free_namespace - free a profile namespace
* @ns: the namespace to free (MAYBE NULL)
*
* Requires: All references to the namespace must have been put, if the
* namespace was referenced by a profile confining a task,
*/
-static void free_namespace(struct aa_namespace *ns)
+void aa_free_namespace(struct aa_namespace *ns)
{
if (!ns)
return;
policy_destroy(&ns->base);
+ aa_labelset_destroy(&ns->labels);
aa_put_namespace(ns->parent);
- if (ns->unconfined && ns->unconfined->ns == ns)
- ns->unconfined->ns = NULL;
-
- aa_put_profile(ns->unconfined);
+ ns->unconfined->ns = NULL;
+ aa_free_profile(ns->unconfined);
kzfree(ns);
}
/**
- * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
- * @kr: kref callback for freeing of a namespace (NOT NULL)
+ * __aa_findn_namespace - find a namespace on a list by @name
+ * @head: list to search for namespace on (NOT NULL)
+ * @name: name of namespace to look for (NOT NULL)
+ * @n: length of @name
+ * Returns: unrefcounted namespace
+ *
+ * Requires: rcu_read_lock be held
*/
-void aa_free_namespace_kref(struct kref *kref)
+static struct aa_namespace *__aa_findn_namespace(struct list_head *head,
+ const char *name, size_t n)
{
- free_namespace(container_of(kref, struct aa_namespace, base.count));
+ return (struct aa_namespace *)__policy_strn_find(head, name, n);
}
/**
- * __aa_find_namespace - find a namespace on a list by @name
- * @head: list to search for namespace on (NOT NULL)
- * @name: name of namespace to look for (NOT NULL)
+ * aa_find_namespace - look up a profile namespace on the namespace list
+ * @root: namespace to search in (NOT NULL)
+ * @name: name of namespace to find (NOT NULL)
+ * @n: length of @name
*
- * Returns: unrefcounted namespace
+ * Returns: a refcounted namespace on the list, or NULL if no namespace
+ * called @name exists.
*
- * Requires: ns lock be held
+ * refcount released by caller
*/
-static struct aa_namespace *__aa_find_namespace(struct list_head *head,
- const char *name)
+struct aa_namespace *aa_findn_namespace(struct aa_namespace *root,
+ const char *name, size_t n)
{
- return (struct aa_namespace *)__policy_find(head, name);
+ struct aa_namespace *ns = NULL;
+
+ rcu_read_lock();
+ ns = aa_get_namespace(__aa_findn_namespace(&root->sub_ns, name, n));
+ rcu_read_unlock();
+
+ return ns;
}
/**
@@ -371,13 +399,7 @@ static struct aa_namespace *__aa_find_namespace(struct list_head *head,
struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
const char *name)
{
- struct aa_namespace *ns = NULL;
-
- read_lock(&root->lock);
- ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
- read_unlock(&root->lock);
-
- return ns;
+ return aa_findn_namespace(root, name, strlen(name));
}
/**
@@ -390,9 +412,9 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)
{
struct aa_namespace *ns, *root;
- root = aa_current_profile()->ns;
+ root = labels_ns(aa_current_label());
- write_lock(&root->lock);
+ mutex_lock(&root->lock);
/* if name isn't specified the profile is loaded to the current ns */
if (!name) {
@@ -403,40 +425,34 @@ static struct aa_namespace *aa_prepare_namespace(const char *name)
/* try and find the specified ns and if it doesn't exist create it */
/* released by caller */
- ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
+ ns = aa_get_namespace(__aa_findn_namespace(&root->sub_ns, name,
+ strlen(name)));
if (!ns) {
- /* namespace not found */
- struct aa_namespace *new_ns;
- write_unlock(&root->lock);
- new_ns = alloc_namespace(root->base.hname, name);
- if (!new_ns)
- return NULL;
- write_lock(&root->lock);
- /* test for race when new_ns was allocated */
- ns = __aa_find_namespace(&root->sub_ns, name);
- if (!ns) {
- /* add parent ref */
- new_ns->parent = aa_get_namespace(root);
-
- list_add(&new_ns->base.list, &root->sub_ns);
- /* add list ref */
- ns = aa_get_namespace(new_ns);
- } else {
- /* raced so free the new one */
- free_namespace(new_ns);
- /* get reference on namespace */
- aa_get_namespace(ns);
+ ns = alloc_namespace(root->base.hname, name);
+ if (!ns)
+ goto out;
+ if (__aa_fs_namespace_mkdir(ns, ns_subns_dir(root), name)) {
+ AA_ERROR("Failed to create interface for ns %s\n",
+ ns->base.name);
+ aa_free_namespace(ns);
+ ns = NULL;
+ goto out;
}
+ ns->parent = aa_get_namespace(root);
+ ns->level = root->level + 1;
+ list_add_rcu(&ns->base.list, &root->sub_ns);
+ /* add list ref */
+ aa_get_namespace(ns);
}
out:
- write_unlock(&root->lock);
+ mutex_unlock(&root->lock);
/* return ref */
return ns;
}
/**
- * __list_add_profile - add a profile to a list
+ * __add_profile - add a profiles to list and label tree
* @list: list to add it to (NOT NULL)
* @profile: the profile to add (NOT NULL)
*
@@ -444,12 +460,21 @@ out:
*
* Requires: namespace lock be held, or list not be shared
*/
-static void __list_add_profile(struct list_head *list,
- struct aa_profile *profile)
+static void __add_profile(struct list_head *list, struct aa_profile *profile)
{
- list_add(&profile->base.list, list);
+ struct aa_label *l;
+
+ AA_BUG(!list);
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
+
+ list_add_rcu(&profile->base.list, list);
/* get list reference */
aa_get_profile(profile);
+ l = aa_label_insert(&profile->ns->labels, &profile->label);
+ AA_BUG(l != &profile->label);
+ aa_put_label(l);
}
/**
@@ -466,50 +491,12 @@ static void __list_add_profile(struct list_head *list,
*/
static void __list_remove_profile(struct aa_profile *profile)
{
- list_del_init(&profile->base.list);
- if (!(profile->flags & PFLAG_NO_LIST_REF))
- /* release list reference */
- aa_put_profile(profile);
-}
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
-/**
- * __replace_profile - replace @old with @new on a list
- * @old: profile to be replaced (NOT NULL)
- * @new: profile to replace @old with (NOT NULL)
- *
- * Will duplicate and refcount elements that @new inherits from @old
- * and will inherit @old children.
- *
- * refcount @new for list, put @old list refcount
- *
- * Requires: namespace list lock be held, or list not be shared
- */
-static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
-{
- struct aa_policy *policy;
- struct aa_profile *child, *tmp;
-
- if (old->parent)
- policy = &old->parent->base;
- else
- policy = &old->ns->base;
-
- /* released when @new is freed */
- new->parent = aa_get_profile(old->parent);
- new->ns = aa_get_namespace(old->ns);
- new->sid = old->sid;
- __list_add_profile(&policy->profiles, new);
- /* inherit children */
- list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) {
- aa_put_profile(child->parent);
- child->parent = aa_get_profile(new);
- /* list refcount transferred to @new*/
- list_move(&child->base.list, &new->base.profiles);
- }
-
- /* released by free_profile */
- old->replacedby = aa_get_profile(new);
- __list_remove_profile(old);
+ list_del_rcu(&profile->base.list);
+ aa_put_profile(profile);
}
static void __profile_list_release(struct list_head *head);
@@ -522,10 +509,17 @@ static void __profile_list_release(struct list_head *head);
*/
static void __remove_profile(struct aa_profile *profile)
{
+ AA_BUG(!profile);
+ AA_BUG(!profile->ns);
+ AA_BUG(!mutex_is_locked(&profile->ns->lock));
+
/* release any children lists first */
__profile_list_release(&profile->base.profiles);
/* released by free_profile */
- profile->replacedby = aa_get_profile(profile->ns->unconfined);
+ aa_label_remove(&profile->ns->labels, &profile->label);
+ __aa_update_replacedby(&profile->label,
+ &profile->ns->unconfined->label);
+ __aa_fs_profile_rmdir(profile);
__list_remove_profile(profile);
}
@@ -553,14 +547,18 @@ static void destroy_namespace(struct aa_namespace *ns)
if (!ns)
return;
- write_lock(&ns->lock);
+ mutex_lock(&ns->lock);
/* release all profiles in this namespace */
__profile_list_release(&ns->base.profiles);
/* release all sub namespaces */
__ns_list_release(&ns->sub_ns);
- write_unlock(&ns->lock);
+ if (ns->parent)
+ __aa_update_replacedby(&ns->unconfined->label,
+ &ns->parent->unconfined->label);
+ __aa_fs_namespace_rmdir(ns);
+ mutex_unlock(&ns->lock);
}
/**
@@ -571,25 +569,9 @@ static void destroy_namespace(struct aa_namespace *ns)
*/
static void __remove_namespace(struct aa_namespace *ns)
{
- struct aa_profile *unconfined = ns->unconfined;
-
/* remove ns from namespace list */
- list_del_init(&ns->base.list);
-
- /*
- * break the ns, unconfined profile cyclic reference and forward
- * all new unconfined profiles requests to the parent namespace
- * This will result in all confined tasks that have a profile
- * being removed, inheriting the parent->unconfined profile.
- */
- if (ns->parent)
- ns->unconfined = aa_get_profile(ns->parent->unconfined);
-
+ list_del_rcu(&ns->base.list);
destroy_namespace(ns);
-
- /* release original ns->unconfined ref */
- aa_put_profile(unconfined);
- /* release ns->base.list ref, from removal above */
aa_put_namespace(ns);
}
@@ -635,6 +617,44 @@ void __init aa_free_root_ns(void)
aa_put_namespace(ns);
}
+
+/**
+ * aa_free_profile - free a profile
+ * @profile: the profile to free (MAYBE NULL)
+ *
+ * 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_profile() will
+ * be called from an rcu callback routine, so we must not sleep here.
+ */
+void aa_free_profile(struct aa_profile *profile)
+{
+ AA_DEBUG("%s(%p)\n", __func__, profile);
+
+ if (!profile)
+ return;
+
+ /* free children profiles */
+ policy_destroy(&profile->base);
+ aa_put_profile(rcu_access_pointer(profile->parent));
+
+ aa_put_namespace(profile->ns);
+ kzfree(profile->rename);
+
+ aa_free_file_rules(&profile->file);
+ aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
+ aa_free_rlimit_rules(&profile->rlimits);
+
+ kzfree(profile->dirname);
+ aa_put_dfa(profile->xmatch);
+ aa_put_dfa(profile->policy.dfa);
+
+ kzfree(profile->hash);
+ kzfree(profile);
+}
+
/**
* aa_alloc_profile - allocate, initialize and return a new profile
* @hname: name of the profile (NOT NULL)
@@ -650,13 +670,21 @@ struct aa_profile *aa_alloc_profile(const char *hname)
if (!profile)
return NULL;
- if (!policy_init(&profile->base, NULL, hname)) {
- kzfree(profile);
- return NULL;
- }
+ if (!policy_init(&profile->base, NULL, hname))
+ goto fail;
+ if (!aa_label_init(&profile->label, 1))
+ goto fail;
+ profile->label.hname = profile->base.hname;
+ profile->label.flags |= FLAG_PROFILE;
+ profile->label.ent[0] = profile;
/* refcount released by caller */
return profile;
+
+fail:
+ kzfree(profile);
+
+ return NULL;
}
/**
@@ -665,7 +693,7 @@ struct aa_profile *aa_alloc_profile(const char *hname)
* @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.
+ * the profile is unique and follows the format of parent//null-<uniq>.
*
* 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
@@ -677,117 +705,68 @@ struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat)
{
struct aa_profile *profile = NULL;
char *name;
- u32 sid = aa_alloc_sid();
+ int uniq = atomic_inc_return(&parent->ns->uniq_null);
/* freed below */
name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL);
if (!name)
goto fail;
- sprintf(name, "%s//null-%x", parent->base.hname, sid);
+ sprintf(name, "%s//null-%x", parent->base.hname, uniq);
profile = aa_alloc_profile(name);
kfree(name);
if (!profile)
goto fail;
- profile->sid = sid;
+ profile->label.replacedby = aa_alloc_replacedby(NULL);
+ if (!profile->label.replacedby)
+ goto fail;
+
profile->mode = APPARMOR_COMPLAIN;
- profile->flags = PFLAG_NULL;
+ profile->label.flags |= FLAG_NULL;
if (hat)
- profile->flags |= PFLAG_HAT;
+ profile->label.flags |= FLAG_HAT;
/* released on free_profile */
- profile->parent = aa_get_profile(parent);
+ rcu_assign_pointer(profile->parent, aa_get_profile(parent));
profile->ns = aa_get_namespace(parent->ns);
- write_lock(&profile->ns->lock);
- __list_add_profile(&parent->base.profiles, profile);
- write_unlock(&profile->ns->lock);
+ mutex_lock(&profile->ns->lock);
+ __add_profile(&parent->base.profiles, profile);
+ mutex_unlock(&profile->ns->lock);
/* refcount released by caller */
return profile;
fail:
- aa_free_sid(sid);
+ aa_free_profile(profile);
return NULL;
}
/**
- * free_profile - free a profile
- * @profile: the profile to free (MAYBE NULL)
- *
- * 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_profile() will
- * be called from an rcu callback routine, so we must not sleep here.
+ * aa_setup_default_label - create the initial default label
*/
-static void free_profile(struct aa_profile *profile)
+struct aa_label *aa_setup_default_label(void)
{
- struct aa_profile *p;
-
- AA_DEBUG("%s(%p)\n", __func__, profile);
-
+ struct aa_profile *profile = aa_alloc_profile("default");
if (!profile)
- return;
-
- if (!list_empty(&profile->base.list)) {
- AA_ERROR("%s: internal error, "
- "profile '%s' still on ns list\n",
- __func__, profile->base.name);
- BUG();
- }
-
- /* free children profiles */
- policy_destroy(&profile->base);
- aa_put_profile(profile->parent);
+ return NULL;
- aa_put_namespace(profile->ns);
- kzfree(profile->rename);
+ /* the default profile pretends to be unconfined until it is replaced */
+ profile->label.flags |= FLAG_IX_ON_NAME_ERROR | FLAG_UNCONFINED;
+ profile->mode = APPARMOR_UNCONFINED;
- aa_free_file_rules(&profile->file);
- aa_free_cap_rules(&profile->caps);
- aa_free_rlimit_rules(&profile->rlimits);
+ profile->ns = aa_get_namespace(root_ns);
- aa_free_sid(profile->sid);
- aa_put_dfa(profile->xmatch);
- aa_put_dfa(profile->policy.dfa);
-
- /* put the profile reference for replacedby, but not via
- * put_profile(kref_put).
- * replacedby can form a long chain that can result in cascading
- * frees that blows the stack because kref_put makes a nested fn
- * call (it looks like recursion, with free_profile calling
- * free_profile) for each profile in the chain lp#1056078.
- */
- for (p = profile->replacedby; p; ) {
- if (atomic_dec_and_test(&p->base.count.refcount)) {
- /* no more refs on p, grab its replacedby */
- struct aa_profile *next = p->replacedby;
- /* break the chain */
- p->replacedby = NULL;
- /* now free p, chain is broken */
- free_profile(p);
-
- /* follow up with next profile in the chain */
- p = next;
- } else
- break;
+ /* replacedby being set needed by fs interface */
+ profile->label.replacedby = aa_alloc_replacedby(&profile->label);
+ if (!profile->label.replacedby) {
+ aa_free_profile(profile);
+ return NULL;
}
+ __add_profile(&root_ns->base.profiles, profile);
- kzfree(profile);
-}
-
-/**
- * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile)
- * @kr: kref callback for freeing of a profile (NOT NULL)
- */
-void aa_free_profile_kref(struct kref *kref)
-{
- struct aa_profile *p = container_of(kref, struct aa_profile,
- base.count);
-
- free_profile(p);
+ return &profile->label;
}
/* TODO: profile accounting - setup in remove */
@@ -797,7 +776,7 @@ void aa_free_profile_kref(struct kref *kref)
* @head: list to search (NOT NULL)
* @name: name of profile (NOT NULL)
*
- * Requires: ns lock protecting list be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted profile ptr, or NULL if not found
*/
@@ -812,7 +791,7 @@ static struct aa_profile *__find_child(struct list_head *head, const char *name)
* @name: name of profile (NOT NULL)
* @len: length of @name substring to match
*
- * Requires: ns lock protecting list be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted profile ptr, or NULL if not found
*/
@@ -833,9 +812,9 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
{
struct aa_profile *profile;
- read_lock(&parent->ns->lock);
+ rcu_read_lock();
profile = aa_get_profile(__find_child(&parent->base.profiles, name));
- read_unlock(&parent->ns->lock);
+ rcu_read_unlock();
/* refcount released by caller */
return profile;
@@ -850,7 +829,7 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
* that matches hname does not need to exist, in general this
* is used to load a new profile.
*
- * Requires: ns->lock be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted policy or NULL if not found
*/
@@ -878,61 +857,99 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns,
}
/**
- * __lookup_profile - lookup the profile matching @hname
+ * __lookupn_profile - lookup the profile matching @hname
* @base: base list to start looking up profile name from (NOT NULL)
* @hname: hierarchical profile name (NOT NULL)
+ * @n: length of @hname
*
- * Requires: ns->lock be held
+ * Requires: rcu_read_lock be held
*
* Returns: unrefcounted profile pointer or NULL if not found
*
* Do a relative name lookup, recursing through profile tree.
*/
-static struct aa_profile *__lookup_profile(struct aa_policy *base,
- const char *hname)
+static struct aa_profile *__lookupn_profile(struct aa_policy *base,
+ const char *hname, size_t n)
{
struct aa_profile *profile = NULL;
- char *split;
+ const char *split, *name = hname;
- for (split = strstr(hname, "//"); split;) {
- profile = __strn_find_child(&base->profiles, hname,
- split - hname);
+ for (split = strstr(hname, "//"); split && (split - hname <= n);) {
+ profile = __strn_find_child(&base->profiles, name,
+ split - name);
if (!profile)
return NULL;
base = &profile->base;
- hname = split + 2;
- split = strstr(hname, "//");
+ name = split + 2;
+ split = strstr(name, "//");
}
- profile = __find_child(&base->profiles, hname);
+ if (name - hname <= n)
+ return __strn_find_child(&base->profiles, name,
+ n - (name - hname));
+ return NULL;
+}
- return profile;
+static struct aa_profile *__lookup_profile(struct aa_policy *base,
+ const char *hname)
+{
+ return __lookupn_profile(base, hname, strlen(hname));
}
/**
* aa_lookup_profile - find a profile by its full or partial name
* @ns: the namespace to start from (NOT NULL)
* @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL)
+ * @n: size of @hname
*
* Returns: refcounted profile or NULL if not found
*/
-struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname)
+struct aa_profile *aa_lookupn_profile(struct aa_namespace *ns,
+ const char *hname, size_t n)
{
struct aa_profile *profile;
- read_lock(&ns->lock);
- profile = aa_get_profile(__lookup_profile(&ns->base, hname));
- read_unlock(&ns->lock);
+ rcu_read_lock();
+ do {
+ profile = __lookupn_profile(&ns->base, hname, n);
+ } while (profile && !aa_get_profile_not0(profile));
+ rcu_read_unlock();
/* the unconfined profile is not in the regular profile list */
- if (!profile && strcmp(hname, "unconfined") == 0)
- profile = aa_get_profile(ns->unconfined);
+ if (!profile && strncmp(hname, "unconfined", n) == 0)
+ profile = aa_get_newest_profile(ns->unconfined);
/* refcount released by caller */
return profile;
}
+struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname)
+{
+ return aa_lookupn_profile(ns, hname, strlen(hname));
+}
+
+struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, char *fqname,
+ size_t n)
+{
+ struct aa_profile *profile;
+ struct aa_namespace *ns;
+ char *name, *ns_name;
+ size_t ns_len;
+
+ name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len);
+ if (ns_name) {
+ ns = aa_findn_namespace(labels_ns(base), ns_name, ns_len);
+ if (!ns)
+ return NULL;
+ } else
+ ns = aa_get_namespace(labels_ns(base));
+ profile = aa_lookupn_profile(ns, name, n - (name - fqname));
+ aa_put_namespace(ns);
+
+ return profile;
+}
+
/**
* replacement_allowed - test to see if replacement is allowed
* @profile: profile to test if it can be replaced (MAYBE NULL)
@@ -945,7 +962,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
const char **info)
{
if (profile) {
- if (profile->flags & PFLAG_IMMUTABLE) {
+ if (profile->label.flags & FLAG_IMMUTIBLE) {
*info = "cannot replace immutible profile";
return -EPERM;
} else if (noreplace) {
@@ -957,49 +974,25 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
}
/**
- * __add_new_profile - simple wrapper around __list_add_profile
- * @ns: namespace that profile is being added to (NOT NULL)
- * @policy: the policy container to add the profile to (NOT NULL)
- * @profile: profile to add (NOT NULL)
- *
- * add a profile to a list and do other required basic allocations
- */
-static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy,
- struct aa_profile *profile)
-{
- if (policy != &ns->base)
- /* released on profile replacement or free_profile */
- profile->parent = aa_get_profile((struct aa_profile *) policy);
- __list_add_profile(&policy->profiles, profile);
- /* released on free_profile */
- profile->sid = aa_alloc_sid();
- profile->ns = aa_get_namespace(ns);
-}
-
-/**
* aa_audit_policy - Do auditing of policy changes
* @op: policy operation being performed
- * @gfp: memory allocation flags
* @name: name of profile being manipulated (NOT NULL)
* @info: any extra information to be audited (MAYBE NULL)
* @error: error code
*
* Returns: the error to be returned after audit is done
*/
-static int audit_policy(int op, gfp_t gfp, const char *name, const char *info,
+static int audit_policy(int op, const char *name, const char *info,
int error)
{
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.op = op;
- aad.name = name;
- aad.info = info;
- aad.error = error;
-
- return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp,
- &sa, NULL);
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
+ // aad(&sa)->op = op;
+ aad(&sa)->name = name;
+ aad(&sa)->info = info;
+ aad(&sa)->error = error;
+
+ return aa_audit(AUDIT_APPARMOR_STATUS,
+ labels_profile(aa_current_raw_label()), &sa, NULL);
}
/**
@@ -1012,18 +1005,161 @@ bool aa_may_manage_policy(int op)
{
/* check if loading policy is locked out */
if (aa_g_lock_policy) {
- audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES);
+ audit_policy(op, NULL, "policy_locked", -EACCES);
return 0;
}
if (!capable(CAP_MAC_ADMIN)) {
- audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES);
+ audit_policy(op, NULL, "not policy admin", -EACCES);
return 0;
}
return 1;
}
+static struct aa_profile *__list_lookup_parent(struct list_head *lh,
+ struct aa_profile *profile)
+{
+ const char *base = hname_tail(profile->base.hname);
+ long len = base - profile->base.hname;
+ struct aa_load_ent *ent;
+
+ /* parent won't have trailing // so remove from len */
+ if (len <= 2)
+ return NULL;
+ len -= 2;
+
+ list_for_each_entry(ent, lh, list) {
+ if (ent->new == profile)
+ continue;
+ if (strncmp(ent->new->base.hname, profile->base.hname, len) ==
+ 0 && ent->new->base.hname[len] == 0)
+ return ent->new;
+ }
+
+ return NULL;
+}
+
+/**
+ * __replace_profile - replace @old with @new on a list
+ * @old: profile to be replaced (NOT NULL)
+ * @new: profile to replace @old with (NOT NULL)
+ * @share_replacedby: transfer @old->replacedby to @new
+ *
+ * Will duplicate and refcount elements that @new inherits from @old
+ * and will inherit @old children.
+ *
+ * refcount @new for list, put @old list refcount
+ *
+ * Requires: namespace list lock be held, or list not be shared
+ */
+static void __replace_profile(struct aa_profile *old, struct aa_profile *new,
+ bool share_replacedby)
+{
+ struct aa_profile *child, *tmp;
+
+ if (!list_empty(&old->base.profiles)) {
+ LIST_HEAD(lh);
+ list_splice_init_rcu(&old->base.profiles, &lh, synchronize_rcu);
+
+ list_for_each_entry_safe(child, tmp, &lh, base.list) {
+ struct aa_profile *p;
+
+ list_del_init(&child->base.list);
+ p = __find_child(&new->base.profiles, child->base.name);
+ if (p) {
+ /* @p replaces @child */
+ __replace_profile(child, p, share_replacedby);
+ continue;
+ }
+
+ /* inherit @child and its children */
+ /* TODO: update hname of inherited children */
+ /* list refcount transferred to @new */
+ p = aa_deref_parent(child);
+ rcu_assign_pointer(child->parent, aa_get_profile(new));
+ list_add_rcu(&child->base.list, &new->base.profiles);
+ aa_put_profile(p);
+ }
+ }
+
+ if (!rcu_access_pointer(new->parent)) {
+ struct aa_profile *parent = aa_deref_parent(old);
+ rcu_assign_pointer(new->parent, aa_get_profile(parent));
+ }
+ __aa_update_replacedby(&old->label, &new->label);
+ if (share_replacedby)
+ new->label.replacedby = aa_get_replacedby(old->label.replacedby);
+ else if (!rcu_access_pointer(new->label.replacedby->label))
+ /* aafs interface uses replacedby */
+ rcu_assign_pointer(new->label.replacedby->label,
+ aa_get_label(&new->label));
+ __aa_fs_profile_migrate_dents(old, new);
+
+ if (list_empty(&new->base.list)) {
+ /* new is not on a list already */
+ list_replace_rcu(&old->base.list, &new->base.list);
+ aa_get_profile(new);
+ aa_put_profile(old);
+ } else
+ __list_remove_profile(old);
+}
+
+/**
+ * __lookup_replace - lookup replacement information for a profile
+ * @ns - namespace the lookup occurs in
+ * @hname - name of profile to lookup
+ * @noreplace - true if not replacing an existing profile
+ * @p - Returns: profile to be replaced
+ * @info - Returns: info string on why lookup failed
+ *
+ * Returns: profile to replace (no ref) on success else ptr error
+ */
+static int __lookup_replace(struct aa_namespace *ns, const char *hname,
+ bool noreplace, struct aa_profile **p,
+ const char **info)
+{
+ *p = aa_get_profile(__lookup_profile(&ns->base, hname));
+ if (*p) {
+ int error = replacement_allowed(*p, noreplace, info);
+ if (error) {
+ *info = "profile can not be replaced";
+ return error;
+ }
+ }
+
+ return 0;
+}
+
+static void share_name(struct aa_profile *old, struct aa_profile *new)
+{
+ aa_put_str(new->base.hname);
+ aa_get_str(old->base.hname);
+ new->base.hname = old->base.hname;
+ new->base.name = old->base.name;
+ new->label.hname = old->label.hname;
+}
+
+/* Update to newest version of parent after previous replacements
+ * Returns: unrefcount newest version of parent
+ */
+static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
+{
+ struct aa_profile *parent, *newest;
+ parent = rcu_dereference_protected(new->parent,
+ mutex_is_locked(&new->ns->lock));
+ newest = aa_get_newest_profile(parent);
+
+ /* parent replaced in this atomic set? */
+ if (newest != parent) {
+ aa_put_profile(parent);
+ rcu_assign_pointer(new->parent, newest);
+ } else
+ aa_put_profile(newest);
+
+ return newest;
+}
+
/**
* aa_replace_profiles - replace profile(s) on the profile list
* @udata: serialized data stream (NOT NULL)
@@ -1038,21 +1174,17 @@ bool aa_may_manage_policy(int op)
*/
ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
{
- struct aa_policy *policy;
- struct aa_profile *old_profile = NULL, *new_profile = NULL;
- struct aa_profile *rename_profile = NULL;
- struct aa_namespace *ns = NULL;
const char *ns_name, *name = NULL, *info = NULL;
+ struct aa_namespace *ns = NULL;
+ struct aa_load_ent *ent, *tmp;
int op = OP_PROF_REPL;
ssize_t error;
+ LIST_HEAD(lh);
/* released below */
- new_profile = aa_unpack(udata, size, &ns_name);
- if (IS_ERR(new_profile)) {
- error = PTR_ERR(new_profile);
- new_profile = NULL;
- goto fail;
- }
+ error = aa_unpack(udata, size, &lh, &ns_name);
+ if (error)
+ goto out;
/* released below */
ns = aa_prepare_namespace(ns_name);
@@ -1063,77 +1195,143 @@ ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace)
goto fail;
}
- name = new_profile->base.hname;
+ mutex_lock(&ns->lock);
+ /* setup parent and ns info */
+ list_for_each_entry(ent, &lh, list) {
+ struct aa_policy *policy;
+
+ name = ent->new->base.hname;
+ error = __lookup_replace(ns, ent->new->base.hname, noreplace,
+ &ent->old, &info);
+ if (error)
+ goto fail_lock;
+
+ if (ent->new->rename) {
+ error = __lookup_replace(ns, ent->new->rename,
+ noreplace, &ent->rename,
+ &info);
+ if (error)
+ goto fail_lock;
+ }
- write_lock(&ns->lock);
- /* no ref on policy only use inside lock */
- policy = __lookup_parent(ns, new_profile->base.hname);
+ /* released when @new is freed */
+ ent->new->ns = aa_get_namespace(ns);
- if (!policy) {
- info = "parent does not exist";
- error = -ENOENT;
- goto audit;
+ if (ent->old || ent->rename)
+ continue;
+
+ /* no ref on policy only use inside lock */
+ policy = __lookup_parent(ns, ent->new->base.hname);
+ if (!policy) {
+ struct aa_profile *p;
+ p = __list_lookup_parent(&lh, ent->new);
+ if (!p) {
+ error = -ENOENT;
+ info = "parent does not exist";
+ name = ent->new->base.hname;
+ goto fail_lock;
+ }
+ rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
+ } else if (policy != &ns->base) {
+ /* released on profile replacement or free_profile */
+ struct aa_profile *p = (struct aa_profile *) policy;
+ rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
+ }
}
- old_profile = __find_child(&policy->profiles, new_profile->base.name);
- /* released below */
- aa_get_profile(old_profile);
+ /* create new fs entries for introspection if needed */
+ list_for_each_entry(ent, &lh, list) {
+ struct aa_replacedby *r;
+ if (ent->old) {
+ /* inherit old interface files */
- if (new_profile->rename) {
- rename_profile = __lookup_profile(&ns->base,
- new_profile->rename);
- /* released below */
- aa_get_profile(rename_profile);
+ /* if (ent->rename)
+ TODO: support rename */
+ /* } else if (ent->rename) {
+ TODO: support rename */
+ } else {
+ struct dentry *parent;
+ r = aa_alloc_replacedby(NULL);
+ if (!r) {
+ info = "failed to create";
+ error = -ENOMEM;
+ goto fail_lock;
+ }
+ ent->new->label.replacedby = r;
+
+ if (rcu_access_pointer(ent->new->parent)) {
+ struct aa_profile *p;
+ p = aa_deref_parent(ent->new);
+ parent = prof_child_dir(p);
+ } else
+ parent = ns_subprofs_dir(ent->new->ns);
+ error = __aa_fs_profile_mkdir(ent->new, parent);
+ }
- if (!rename_profile) {
- info = "profile to rename does not exist";
- name = new_profile->rename;
- error = -ENOENT;
- goto audit;
+ if (error) {
+ info = "failed to create";
+ goto fail_lock;
}
}
- error = replacement_allowed(old_profile, noreplace, &info);
- if (error)
- goto audit;
-
- error = replacement_allowed(rename_profile, noreplace, &info);
- if (error)
- goto audit;
-
-audit:
- if (!old_profile && !rename_profile)
- op = OP_PROF_LOAD;
-
- error = audit_policy(op, GFP_ATOMIC, name, info, error);
-
- if (!error) {
- if (rename_profile)
- __replace_profile(rename_profile, new_profile);
- if (old_profile) {
- /* when there are both rename and old profiles
- * inherit old profiles sid
- */
- if (rename_profile)
- aa_free_sid(new_profile->sid);
- __replace_profile(old_profile, new_profile);
+ /* Done with checks that may fail - do actual replacement */
+ list_for_each_entry_safe(ent, tmp, &lh, list) {
+ list_del_init(&ent->list);
+ op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
+
+ audit_policy(op, ent->new->base.name, NULL, error);
+
+ if (ent->old) {
+ share_name(ent->old, ent->new);
+ __replace_profile(ent->old, ent->new, 1);
+ aa_label_replace(&ns->labels, &ent->old->label,
+ &ent->new->label);
+ if (ent->rename) {
+ /* aafs interface uses replacedby */
+ rcu_assign_pointer(ent->new->label.replacedby->label,
+ aa_get_label(&ent->new->label));
+ __replace_profile(ent->rename, ent->new, 0);
+ }
+ } else if (ent->rename) {
+ /* aafs interface uses replacedby */
+ rcu_assign_pointer(ent->new->label.replacedby->label,
+ aa_get_label(&ent->new->label));
+ } else {
+ struct list_head *lh;
+ if (rcu_access_pointer(ent->new->parent)) {
+ struct aa_profile *parent;
+ parent = update_to_newest_parent(ent->new);
+ lh = &parent->base.profiles;
+ } else
+ lh = &ns->base.profiles;
+
+ /* aafs interface uses replacedby */
+ rcu_assign_pointer(ent->new->label.replacedby->label,
+ aa_get_label(&ent->new->label));
+ __add_profile(lh, ent->new);
}
- if (!(old_profile || rename_profile))
- __add_new_profile(ns, policy, new_profile);
+ aa_load_ent_free(ent);
}
- write_unlock(&ns->lock);
+ __aa_labelset_update_all(ns);
+ mutex_unlock(&ns->lock);
out:
aa_put_namespace(ns);
- aa_put_profile(rename_profile);
- aa_put_profile(old_profile);
- aa_put_profile(new_profile);
+
if (error)
return error;
return size;
+fail_lock:
+ mutex_unlock(&ns->lock);
fail:
- error = audit_policy(op, GFP_KERNEL, name, info, error);
+ error = audit_policy(op, name, info, error);
+
+ list_for_each_entry_safe(ent, tmp, &lh, list) {
+ list_del_init(&ent->list);
+ aa_load_ent_free(ent);
+ }
+
goto out;
}
@@ -1162,19 +1360,17 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
goto fail;
}
- root = aa_current_profile()->ns;
+ root = labels_ns(aa_current_label());
if (fqname[0] == ':') {
char *ns_name;
name = aa_split_fqname(fqname, &ns_name);
- if (ns_name) {
- /* released below */
- ns = aa_find_namespace(root, ns_name);
- if (!ns) {
- info = "namespace does not exist";
- error = -ENOENT;
- goto fail;
- }
+ /* released below */
+ ns = aa_find_namespace(root, ns_name);
+ if (!ns) {
+ info = "namespace does not exist";
+ error = -ENOENT;
+ goto fail;
}
} else
/* released below */
@@ -1182,12 +1378,12 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
if (!name) {
/* remove namespace - can only happen if fqname[0] == ':' */
- write_lock(&ns->parent->lock);
+ mutex_lock(&ns->parent->lock);
__remove_namespace(ns);
- write_unlock(&ns->parent->lock);
+ mutex_unlock(&ns->parent->lock);
} else {
/* remove profile */
- write_lock(&ns->lock);
+ mutex_lock(&ns->lock);
profile = aa_get_profile(__lookup_profile(&ns->base, name));
if (!profile) {
error = -ENOENT;
@@ -1196,20 +1392,21 @@ ssize_t aa_remove_profiles(char *fqname, size_t size)
}
name = profile->base.hname;
__remove_profile(profile);
- write_unlock(&ns->lock);
+ __aa_labelset_update_all(ns);
+ mutex_unlock(&ns->lock);
}
/* don't fail removal if audit fails */
- (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error);
+ (void) audit_policy(OP_PROF_RM, name, info, error);
aa_put_namespace(ns);
aa_put_profile(profile);
return size;
fail_ns_lock:
- write_unlock(&ns->lock);
+ mutex_unlock(&ns->lock);
aa_put_namespace(ns);
fail:
- (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error);
+ (void) audit_policy(OP_PROF_RM, name, info, error);
return error;
}
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 329b1fd..188d36e 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -24,10 +24,19 @@
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/context.h"
+#include "include/crypto.h"
#include "include/match.h"
+#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
-#include "include/sid.h"
+
+#define K_ABI_MASK 0x3ff
+#define FORCE_COMPLAIN_FLAG 0x800
+#define VERSION_CMP(OP, X, Y) (((X) & K_ABI_MASK) OP ((Y) & K_ABI_MASK))
+
+#define v5 5 /* base version */
+#define v6 6 /* per entry policydb mediation check */
+#define v7 7 /* full network masking */
/*
* The AppArmor interface treats data as a type byte followed by the
@@ -70,13 +79,13 @@ struct aa_ext {
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
- if (sa->aad->iface.target) {
- struct aa_profile *name = sa->aad->iface.target;
+ if (aad(sa)->target) {
+ const struct aa_profile *name = aad(sa)->target;
audit_log_format(ab, " name=");
audit_log_untrustedstring(ab, name->base.hname);
}
- if (sa->aad->iface.pos)
- audit_log_format(ab, " offset=%ld", sa->aad->iface.pos);
+ if (aad(sa)->iface.pos)
+ audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
}
/**
@@ -92,20 +101,16 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_iface(struct aa_profile *new, const char *name,
const char *info, struct aa_ext *e, int error)
{
- struct aa_profile *profile = __aa_current_profile();
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
+ struct aa_profile *profile = labels_profile(aa_current_raw_label());
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, 0);
if (e)
- aad.iface.pos = e->pos - e->start;
- aad.iface.target = new;
- aad.name = name;
- aad.info = info;
- aad.error = error;
-
- return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &sa,
- audit_cb);
+ aad(&sa)->iface.pos = e->pos - e->start;
+ aad(&sa)->target = new;
+ aad(&sa)->name = name;
+ aad(&sa)->info = info;
+ aad(&sa)->error = error;
+
+ return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
}
/* test if read will be in packed data bounds */
@@ -193,6 +198,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)) {
@@ -290,6 +308,9 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
return res;
}
+#define DFA_VALID_PERM_MASK 0xffffffff
+#define DFA_VALID_PERM2_MASK 0xffffffff
+
/**
* verify_accept - verify the accept tables of a dfa
* @dfa: dfa to verify accept tables of (NOT NULL)
@@ -331,8 +352,10 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
/*
* The dfa is aligned with in the blob to 8 bytes
* from the beginning of the stream.
+ * alignment adjust needed by dfa unpack
*/
- size_t sz = blob - (char *)e->start;
+ size_t sz = blob - (char *) e->start -
+ ((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz;
int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
TO_ACCEPT2_FLAG(YYTD_DATA32);
@@ -471,6 +494,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;
@@ -488,6 +512,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
/* profile renaming is optional */
(void) unpack_str(e, &profile->rename, "rename");
+ /* attachment string is optional */
+ (void) unpack_str(e, &profile->attach, "attach");
+
/* xmatch is optional and may be NULL */
profile->xmatch = unpack_dfa(e);
if (IS_ERR(profile->xmatch)) {
@@ -502,17 +529,24 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
profile->xmatch_len = tmp;
}
+ /* disconnected attachment string is optional */
+ (void) unpack_str(e, &profile->disconnected, "disconnected");
+
/* per profile debug flags (complain, audit) */
if (!unpack_nameX(e, AA_STRUCT, "flags"))
goto fail;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
- if (tmp)
- profile->flags |= PFLAG_HAT;
+ if (tmp & PACKED_FLAG_HAT)
+ profile->label.flags |= FLAG_HAT;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
- if (tmp)
+ if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
profile->mode = APPARMOR_COMPLAIN;
+ else if (tmp == PACKED_MODE_KILL)
+ profile->mode = APPARMOR_KILL;
+ else if (tmp == PACKED_MODE_UNCONFINED)
+ profile->mode = APPARMOR_UNCONFINED;
if (!unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp)
@@ -522,11 +556,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
goto fail;
/* path_flags is optional */
- if (unpack_u32(e, &profile->path_flags, "path_flags"))
- profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
- else
+ if (!unpack_u32(e, &profile->path_flags, "path_flags"))
/* set a default value if path_flags field is not present */
- profile->path_flags = PFLAG_MEDIATE_DELETED;
+ profile->path_flags = PATH_MEDIATE_DELETED;
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
goto fail;
@@ -564,6 +596,37 @@ 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;
+ }
+ if (VERSION_CMP(<, e->version, v7)) {
+ /* old policy always allowed these too */
+ 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);
@@ -592,12 +655,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e)
error = PTR_ERR(profile->file.dfa);
profile->file.dfa = NULL;
goto fail;
+ } else if (profile->file.dfa) {
+ if (!unpack_u32(e, &profile->file.start, "dfa_start"))
+ /* default start state */
+ profile->file.start = DFA_START;
+ } else if (profile->policy.dfa &&
+ profile->policy.start[AA_CLASS_FILE]) {
+ profile->file.dfa = aa_get_dfa(profile->policy.dfa);
+ profile->file.start = profile->policy.start[AA_CLASS_FILE];
}
- if (!unpack_u32(e, &profile->file.start, "dfa_start"))
- /* default start state */
- profile->file.start = DFA_START;
-
if (!unpack_trans_table(e, profile))
goto fail;
@@ -612,7 +679,7 @@ fail:
else if (!name)
name = "unknown";
audit_iface(profile, name, "failed to unpack profile", e, error);
- aa_put_profile(profile);
+ aa_free_profile(profile);
return ERR_PTR(error);
}
@@ -620,29 +687,43 @@ fail:
/**
* verify_head - unpack serialized stream header
* @e: serialized data read head (NOT NULL)
+ * @required: whether the header is required or optional
* @ns: Returns - namespace if one is specified else NULL (NOT NULL)
*
* Returns: error or 0 if header is good
*/
-static int verify_header(struct aa_ext *e, const char **ns)
+static int verify_header(struct aa_ext *e, int required, const char **ns)
{
int error = -EPROTONOSUPPORT;
+ const char *name = NULL;
+ *ns = NULL;
+
/* get the interface version */
if (!unpack_u32(e, &e->version, "version")) {
- audit_iface(NULL, NULL, "invalid profile format", e, error);
- return error;
+ if (required) {
+ audit_iface(NULL, NULL, "invalid profile format", e,
+ error);
+ return error;
+ }
}
- /* check that the interface version is currently supported */
- if (e->version != 5) {
- audit_iface(NULL, NULL, "unsupported interface version", e,
- error);
+ /* Check that the interface version is currently supported.
+ * if not specified use previous version
+ * Mask off everything that is not kernel abi version
+ */
+ if (VERSION_CMP(<, e->version, v5) && VERSION_CMP(>, e->version, v7)) {
+ audit_iface(NULL, NULL, "unsupported interface version",
+ e, error);
return error;
}
/* read the namespace if present */
- if (!unpack_str(e, ns, "namespace"))
- *ns = NULL;
+ if (unpack_str(e, &name, "namespace")) {
+ if (*ns && strcmp(*ns, name))
+ audit_iface(NULL, NULL, "invalid ns change", e, error);
+ else if (!*ns)
+ *ns = name;
+ }
return 0;
}
@@ -691,18 +772,40 @@ static int verify_profile(struct aa_profile *profile)
return 0;
}
+void aa_load_ent_free(struct aa_load_ent *ent)
+{
+ if (ent) {
+ aa_put_profile(ent->rename);
+ aa_put_profile(ent->old);
+ aa_put_profile(ent->new);
+ kzfree(ent);
+ }
+}
+
+struct aa_load_ent *aa_load_ent_alloc(void)
+{
+ struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL);
+ if (ent)
+ INIT_LIST_HEAD(&ent->list);
+ return ent;
+}
+
/**
- * aa_unpack - unpack packed binary profile data loaded from user space
+ * aa_unpack - unpack packed binary profile(s) data loaded from user space
* @udata: user data copied to kmem (NOT NULL)
* @size: the size of the user data
+ * @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
*
- * Unpack user data and return refcounted allocated profile or ERR_PTR
+ * Unpack user data and return refcounted allocated profile(s) stored in
+ * @lh in order of discovery, with the list chain stored in base.list
+ * or error
*
- * Returns: profile else error pointer if fails to unpack
+ * Returns: profile(s) on @lh else error pointer if fails to unpack
*/
-struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
+int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
{
+ struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
int error;
struct aa_ext e = {
@@ -711,20 +814,49 @@ struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns)
.pos = udata,
};
- error = verify_header(&e, ns);
- if (error)
- return ERR_PTR(error);
+ *ns = NULL;
+ while (e.pos < e.end) {
+ void *start;
+ error = verify_header(&e, e.pos == e.start, ns);
+ if (error)
+ goto fail;
+
+ start = e.pos;
+ profile = unpack_profile(&e);
+ if (IS_ERR(profile)) {
+ error = PTR_ERR(profile);
+ goto fail;
+ }
+
+ error = verify_profile(profile);
+ if (error)
+ goto fail_profile;
+
+ error = aa_calc_profile_hash(profile, e.version, start,
+ e.pos - start);
+ if (error)
+ goto fail_profile;
+
+ ent = aa_load_ent_alloc();
+ if (!ent) {
+ error = -ENOMEM;
+ goto fail_profile;
+ }
+
+ ent->new = profile;
+ list_add_tail(&ent->list, lh);
+ }
+
+ return 0;
- profile = unpack_profile(&e);
- if (IS_ERR(profile))
- return profile;
+fail_profile:
+ aa_put_profile(profile);
- error = verify_profile(profile);
- if (error) {
- aa_put_profile(profile);
- profile = ERR_PTR(error);
+fail:
+ list_for_each_entry_safe(ent, tmp, lh, list) {
+ list_del_init(&ent->list);
+ aa_load_ent_free(ent);
}
- /* return refcount */
- return profile;
+ return error;
}
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
index 1b41c54..079130a 100644
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -33,50 +33,29 @@
*
* Returns: size of string placed in @string else error code on failure
*/
-int aa_getprocattr(struct aa_profile *profile, char **string)
+int aa_getprocattr(struct aa_label *label, char **string)
{
- char *str;
- int len = 0, mode_len = 0, ns_len = 0, name_len;
- const char *mode_str = profile_mode_names[profile->mode];
- const char *ns_name = NULL;
- struct aa_namespace *ns = profile->ns;
- struct aa_namespace *current_ns = __aa_current_profile()->ns;
- char *s;
+ struct aa_namespace *ns = labels_ns(label);
+ struct aa_namespace *current_ns = labels_ns(aa_current_label());
+ int len;
if (!aa_ns_visible(current_ns, ns))
return -EACCES;
- ns_name = aa_ns_name(current_ns, ns);
- ns_len = strlen(ns_name);
+ len = aa_label_snprint(NULL, 0, current_ns, label, true);
+ AA_BUG(len < 0);
- /* if the visible ns_name is > 0 increase size for : :// seperator */
- if (ns_len)
- ns_len += 4;
-
- /* unconfined profiles don't have a mode string appended */
- if (!unconfined(profile))
- mode_len = strlen(mode_str) + 3; /* + 3 for _() */
-
- name_len = strlen(profile->base.hname);
- len = mode_len + ns_len + name_len + 1; /* + 1 for \n */
- s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */
- if (!str)
+ *string = kmalloc(len + 2, GFP_KERNEL);
+ if (!*string)
return -ENOMEM;
- if (ns_len) {
- /* skip over prefix current_ns->base.hname and separating // */
- sprintf(s, ":%s://", ns_name);
- s += ns_len;
- }
- if (unconfined(profile))
- /* mode string not being appended */
- sprintf(s, "%s\n", profile->base.hname);
- else
- sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
- *string = str;
-
- /* NOTE: len does not include \0 of string, not saved as part of file */
- return len;
+ len = aa_label_snprint(*string, len + 2, current_ns, label, true);
+ if (len < 0)
+ return len;
+ (*string)[len] = '\n';
+ (*string)[len + 1] = 0;
+
+ return len + 1;
}
/**
@@ -138,12 +117,13 @@ int aa_setprocattr_changehat(char *args, size_t size, int test)
for (count = 0; (hat < end) && count < 16; ++count) {
char *next = hat + strlen(hat) + 1;
hats[count] = hat;
+ AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
+ , __func__, current->pid, token, count, hat);
hat = next;
}
- }
-
- AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
- __func__, token, hat ? hat : NULL);
+ } else
+ AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
+ __func__, current->pid, token, count, "<NULL>");
return aa_change_hat(hats, count, token, test);
}
@@ -163,9 +143,3 @@ int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test)
name = aa_split_fqname(fqname, &ns_name);
return aa_change_profile(ns_name, name, onexec, test);
}
-
-int aa_setprocattr_permipc(char *fqname)
-{
- /* TODO: add ipc permission querying */
- return -ENOTSUPP;
-}
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
index e1f3d7e..add0cfe 100644
--- a/security/apparmor/resource.c
+++ b/security/apparmor/resource.c
@@ -15,6 +15,7 @@
#include <linux/audit.h>
#include "include/audit.h"
+#include "include/context.h"
#include "include/resource.h"
#include "include/policy.h"
@@ -34,7 +35,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
struct common_audit_data *sa = va;
audit_log_format(ab, " rlimit=%s value=%lu",
- rlim_names[sa->aad->rlim.rlim], sa->aad->rlim.max);
+ rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max);
}
/**
@@ -49,17 +50,11 @@ static void audit_cb(struct audit_buffer *ab, void *va)
static int audit_resource(struct aa_profile *profile, unsigned int resource,
unsigned long value, int error)
{
- struct common_audit_data sa;
- struct apparmor_audit_data aad = {0,};
-
- sa.type = LSM_AUDIT_DATA_NONE;
- sa.aad = &aad;
- aad.op = OP_SETRLIMIT,
- aad.rlim.rlim = resource;
- aad.rlim.max = value;
- aad.error = error;
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa,
- audit_cb);
+ DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
+ aad(&sa)->rlim.rlim = resource;
+ aad(&sa)->rlim.max = value;
+ aad(&sa)->error = error;
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
}
/**
@@ -76,9 +71,19 @@ int aa_map_resource(int resource)
return rlim_map[resource];
}
+static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ int e = 0;
+ if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
+ profile->rlimits.limits[resource].rlim_max)
+ e = -EACCES;
+ return audit_resource(profile, resource, new_rlim->rlim_max, e);
+}
+
/**
* aa_task_setrlimit - test permission to set an rlimit
- * @profile - profile confining the task (NOT NULL)
+ * @label - label confining the task (NOT NULL)
* @task - task the resource is being set on
* @resource - the resource being set
* @new_rlim - the new resource limit (NOT NULL)
@@ -87,59 +92,81 @@ int aa_map_resource(int resource)
*
* Returns: 0 or error code if setting resource failed
*/
-int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *task,
+int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
+ struct aa_profile *profile;
+ struct aa_label *task_label;
int error = 0;
+ rcu_read_lock();
+ task_label = aa_get_newest_cred_label(__task_cred(task));
+ rcu_read_unlock();
+
/* TODO: extend resource control to handle other (non current)
- * processes. AppArmor rules currently have the implicit assumption
- * that the task is setting the resource of the current process
+ * profiles. AppArmor rules currently have the implicit assumption
+ * that the task is setting the resource of a task confined with
+ * the same profile.
*/
- if ((task != current->group_leader) ||
- (profile->rlimits.mask & (1 << resource) &&
- new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max))
- error = -EACCES;
-
- return audit_resource(profile, resource, new_rlim->rlim_max, error);
+ if (label != task_label)
+ error = fn_for_each(label, profile,
+ audit_resource(profile, resource,
+ new_rlim->rlim_max, EACCES));
+ else
+ error = fn_for_each_confined(label, profile,
+ profile_setrlimit(profile, resource, new_rlim));
+ aa_put_label(task_label);
+
+ return error;
}
/**
* __aa_transition_rlimits - apply new profile rlimits
- * @old: old profile on task (NOT NULL)
- * @new: new profile with rlimits to apply (NOT NULL)
+ * @old_l: old label on task (NOT NULL)
+ * @new_l: new label with rlimits to apply (NOT NULL)
*/
-void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
+void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
{
unsigned int mask = 0;
struct rlimit *rlim, *initrlim;
- int i;
+ struct aa_profile *old, *new;
+ struct label_it i;
+
+ old = labels_profile(old_l);
+ new = labels_profile(new_l);
- /* for any rlimits the profile controlled reset the soft limit
- * to the less of the tasks hard limit and the init tasks soft limit
+ /* for any rlimits the profile controlled, reset the soft limit
+ * to the lesser of the tasks hard limit and the init tasks soft limit
*/
- if (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);
+ label_for_each_confined(i, old_l, old) {
+ if (old->rlimits.mask) {
+ int j;
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
+ mask <<= 1) {
+ if (old->rlimits.mask & mask) {
+ rlim = current->signal->rlim + j;
+ initrlim = init_task.signal->rlim + j;
+ rlim->rlim_cur = min(rlim->rlim_max,
+ initrlim->rlim_cur);
+ }
}
}
}
/* set any new hard limits as dictated by the new profile */
- if (!new->rlimits.mask)
- return;
- for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
- if (!(new->rlimits.mask & mask))
+ label_for_each_confined(i, new_l, new) {
+ int j;
+ if (!new->rlimits.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);
+ for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
+ if (!(new->rlimits.mask & mask))
+ continue;
+
+ rlim = current->signal->rlim + j;
+ rlim->rlim_max = min(rlim->rlim_max,
+ new->rlimits.limits[j].rlim_max);
+ /* soft limit should not exceed hard limit */
+ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
+ }
}
}