From 3337d214399265412d0f166bd31309dfa9512414 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 8 Apr 2025 19:57:41 -0500 Subject: [PATCH] Updated AppArmor with a newer backported AppArmor release by UBports --- security/apparmor/.gitignore | 1 + security/apparmor/Kconfig | 60 + security/apparmor/Makefile | 50 +- security/apparmor/af_unix.c | 631 +++++++ security/apparmor/apparmorfs.c | 923 ++++++++++- security/apparmor/audit.c | 89 +- security/apparmor/capability.c | 62 +- security/apparmor/context.c | 122 +- security/apparmor/crypto.c | 95 ++ security/apparmor/domain.c | 198 +-- security/apparmor/file.c | 553 ++++-- security/apparmor/include/af_unix.h | 121 ++ security/apparmor/include/apparmor.h | 82 +- security/apparmor/include/apparmorfs.h | 42 + security/apparmor/include/audit.h | 74 +- security/apparmor/include/capability.h | 13 +- security/apparmor/include/context.h | 224 ++- security/apparmor/include/crypto.h | 36 + security/apparmor/include/domain.h | 2 + security/apparmor/include/file.h | 106 +- security/apparmor/include/ipc.h | 22 +- security/apparmor/include/label.h | 419 +++++ security/apparmor/include/match.h | 36 +- security/apparmor/include/mount.h | 54 + security/apparmor/include/net.h | 110 ++ security/apparmor/include/path.h | 63 +- security/apparmor/include/perms.h | 174 ++ security/apparmor/include/policy.h | 299 ++-- security/apparmor/include/policy_unpack.h | 21 +- security/apparmor/include/procattr.h | 3 +- security/apparmor/include/resource.h | 4 +- security/apparmor/include/sid.h | 4 +- security/apparmor/include/sig_names.h | 95 ++ security/apparmor/ipc.c | 255 ++- security/apparmor/label.c | 1844 +++++++++++++++++++++ security/apparmor/lib.c | 474 +++++- security/apparmor/lsm.c | 882 ++++++++-- security/apparmor/match.c | 23 +- security/apparmor/mount.c | 703 ++++++++ security/apparmor/net.c | 397 +++++ security/apparmor/path.c | 173 +- security/apparmor/policy.c | 927 +++++++---- security/apparmor/policy_unpack.c | 243 ++- security/apparmor/procattr.c | 64 +- security/apparmor/resource.c | 117 +- 45 files changed, 9343 insertions(+), 1547 deletions(-) create mode 100644 security/apparmor/af_unix.c create mode 100644 security/apparmor/crypto.c create mode 100644 security/apparmor/include/af_unix.h create mode 100644 security/apparmor/include/crypto.h create mode 100644 security/apparmor/include/label.h create mode 100644 security/apparmor/include/mount.h create mode 100644 security/apparmor/include/net.h create mode 100644 security/apparmor/include/perms.h create mode 100644 security/apparmor/include/sig_names.h create mode 100644 security/apparmor/label.c create mode 100644 security/apparmor/mount.c create mode 100644 security/apparmor/net.c diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70d..d5b291e9 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 9b9013b2..794d66d2 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -29,3 +29,63 @@ 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 "enable introspection of sha1 hashes for loaded profiles" + depends on SECURITY_APPARMOR + depends on CRYPTO + select CRYPTO_SHA1 + default y + + help + This option selects whether introspection of loaded policy + is available to userspace via the apparmor filesystem. + +config SECURITY_APPARMOR_HASH_DEFAULT + bool "Enable policy hash introspection by default" + depends on SECURITY_APPARMOR_HASH + default y + + help + This option selects whether sha1 hashing of loaded policy + is enabled by default. The generation of sha1 hashes for + loaded policy provide system administrators a quick way + to verify that policy in the kernel matches what is expected, + however it can slow down policy load on some devices. In + these cases policy hashing can be disabled by default and + enabled only if needed. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 5706b74c..69ce26e2 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 00000000..62e7fd14 --- /dev/null +++ b/security/apparmor/af_unix.c @@ -0,0 +1,631 @@ +/* + * 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 + +#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; + + mask &= NET_FS_PERMS; + 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 + * can only fall back to original sun_path request + */ + return fn_for_each_confined(label, profile, + ((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ? + __aa_path_perm(op, profile, + u->addr->name->sun_path, mask, + &cond, flags, &perms) : + aa_audit_file(profile, &nullperms, op, mask, + u->addr->name->sun_path, NULL, + cond.uid, "Failed name lookup - " + "deleted entry", -EACCES)); + } 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, &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 16c15ec6..f86f56e8 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -12,21 +12,66 @@ * License. */ +#include #include #include #include #include #include +#include #include #include +#include +#include #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" #include "include/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 @@ -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, "\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 3ae28db5..62b9dd71 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); + audit_log_untrustedstring(ab, aad(sa)->name); } - - if (sa->aad->tsk) { - audit_log_format(ab, " pid=%d comm=", tsk->pid); - audit_log_untrustedstring(ab, tsk->comm); - } - } /** @@ -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 887a5e94..0b289931 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 8a9b5027..475682c5 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 00000000..532471d0 --- /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 + +#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 9aaa4e72..09eb1a99 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 = {}; @@ -634,11 +612,13 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) /* 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 (task_no_new_privs(current) && !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,26 @@ 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 (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 fdaa50cb..29e8c01e 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -12,8 +12,14 @@ * License. */ +#include +#include +#include + +#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 (!sa.aad->fs.request) - return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + if (!aad(&sa)->request) + return COMPLAIN_MODE(profile) ? 0 : aad(&sa)->error; } - sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; - return aa_audit(type, profile, gfp, &sa, file_audit_cb); + 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; +} + +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; + } + + 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; + int e = -EACCES; - 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; - - 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; - return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, - request, &cond); + 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->f_path.dentry)) { + 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; +} + +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 00000000..ccffd23d --- /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 + +#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) && !addr_unix_abstract_name(unix_addr(A)->sun_path)) + +#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(" "); + 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 : ""); print_sk(sock); \ + printk(" other %s:", other_cxt && other_cxt->label && other_cxt->label->hname ? other_cxt->label->hname : ""); print_sk(other); \ + printk(" new %s", new_cxt && new_cxt->label && new_cxt->label->hname ? new_cxt->label->hname : ""); 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 40aedd9f..7dbf8b48 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 #include #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,17 +41,23 @@ #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; extern bool aa_g_audit_header; extern bool aa_g_debug; +extern bool aa_g_hash_policy; 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 +70,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 +87,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 +136,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 dentry *dentry) { - return !(inode->i_sb->s_flags & MS_NOUSER); + return !(dentry->d_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 7ea4769f..0ce350d2 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 69d8cae6..b0e50746 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -22,12 +22,10 @@ #include #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 c24d2959..e01d97fa 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 -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 a9cbee4d..a8f3ef6d 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/context.h @@ -19,56 +19,29 @@ #include #include +#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 - * - * 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. - */ -struct aa_file_cxt { - u16 allow; -}; - -/** - * aa_alloc_file_context - allocate file_cxt - * @gfp: gfp flags for allocation - * - * Returns: file_cxt or NULL on failure - */ -static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) -{ - return kzalloc(sizeof(struct aa_file_cxt), gfp); -} - -/** - * aa_free_file_context - free a file_cxt - * @cxt: file_cxt to free (MAYBE_NULL) - */ -static inline void aa_free_file_context(struct aa_file_cxt *cxt) -{ - if (cxt) - kzfree(cxt); -} +#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 - * @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 + * @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 * - * Contains the task's current profile (which could change due to + * 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_task_cxt { - struct aa_profile *profile; - struct aa_profile *onexec; - struct aa_profile *previous; + struct aa_label *label; + struct aa_label *onexec; + struct aa_label *previous; u64 token; }; @@ -76,10 +49,51 @@ 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); +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_cred_raw_label - obtain cred's label + * @cred: cred to obtain label from (NOT NULL) + * + * Returns: confining label + * + * does NOT increment reference count + */ +static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) +{ + struct aa_task_cxt *cxt = cred_cxt(cred); + BUG_ON(!cxt || !cxt->label); + return cxt->label; +} + +/** + * 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 struct aa_label *aa_get_newest_cred_label(const struct cred *cred) +{ + return aa_get_newest_label(aa_cred_raw_label(cred)); +} + +/** + * __aa_task_raw_label - retrieve another task's label + * @task: task to query (NOT NULL) + * + * Returns: @task's label without incrementing its ref count + * + * If @task != current needs to be called in RCU safe critical section + */ +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 1; + return !unconfined(__aa_task_raw_label(task)); } /** - * aa_cred_profile - obtain cred's profiles - * @cred: cred to obtain profiles from (NOT NULL) + * aa_current_raw_label - find the current tasks confining label * - * Returns: confining profile - * - * does NOT increment reference count - */ -static inline struct aa_profile *aa_cred_profile(const struct cred *cred) -{ - struct aa_task_cxt *cxt = cred->security; - BUG_ON(!cxt || !cxt->profile); - return aa_newest_version(cxt->profile); -} - -/** - * __aa_current_profile - find the current tasks confining profile - * - * 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 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. + * of the label so it is safe to call when inside of locks. */ -static inline struct aa_profile *__aa_current_profile(void) +static inline struct aa_label *aa_current_raw_label(void) { - return aa_cred_profile(current_cred()); + return aa_cred_raw_label(current_cred()); } /** - * aa_current_profile - find the current tasks confining profile and do updates + * aa_get_current_label - get the 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 update the tasks cred structure if the profile has been + * 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_label *aa_get_current_label(void) +{ + struct aa_label *l = aa_current_raw_label(); + + if (label_invalid(l)) + return aa_get_newest_label(l); + return aa_get_label(l); +} + +/** + * aa_begin_current_label - find newest version of the current tasks label + * + * Returns: newest version of confining label (NOT NULL) + * + * 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 void aa_end_current_label(struct aa_label *label) +{ + if (label != aa_current_raw_label()) + aa_put_label(label); +} + +/** + * aa_current_label - find the current tasks confining label and update it + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * 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_cred()->security; - struct aa_profile *profile; - BUG_ON(!cxt || !cxt->profile); + const struct aa_task_cxt *cxt = current_cxt(); + struct aa_label *label; + BUG_ON(!cxt || !cxt->label); - 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); + 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 profile; + 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) +{ + 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 00000000..dc418e50 --- /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 de04464f..a3f70c58 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 967b2ded..83f70000 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 + #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..a786685b 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 00000000..071eabe2 --- /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 +#include +#include +#include + +#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 775843e7..31228454 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 -#include #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) @@ -121,6 +126,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 00000000..a12b5b44 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,54 @@ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2012 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#ifndef __AA_MOUNT_H +#define __AA_MOUNT_H + +#include +#include + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_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 00000000..4a5fae50 --- /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 + +#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 286ac75d..79c48d46 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 +#include + +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 = this_cpu_ptr(&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 00000000..a8ee193c --- /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 +#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 bda4569f..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,49 +181,172 @@ 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) +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); +ssize_t aa_remove_profiles(char *name, size_t size); + +#define PROF_ADD 1 +#define PROF_REPLACE 0 + +#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) + +/** + * aa_get_newest_profile - simple wrapper fn to wrap the label version + * @p: profile (NOT NULL) + * + * Returns refcount to newest version of the profile (maybe @p) + * + * Requires: @p must be held with a valid refcount + */ +static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) { - if (c) - kref_get(&c->count); + 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) +{ + if (profile->policy.dfa) + return aa_dfa_match_len(profile->policy.dfa, + profile->policy.start[0], &class, 1); + return 0; +} + +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)); +} + +/** + * aa_get_profile - increment refcount on profile @p + * @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(struct aa_profile *p) +{ + if (p) + 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->label.count, aa_label_kref); +} + /** * aa_get_namespace - increment references count on @ns * @ns: namespace to increment reference count of (MAYBE NULL) @@ -229,7 +357,7 @@ static inline struct aa_policy *aa_get_common(struct aa_policy *c) static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) { if (ns) - kref_get(&(ns->base.count)); + aa_get_profile(ns->unconfined); return ns; } @@ -243,66 +371,7 @@ static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) static inline void aa_put_namespace(struct aa_namespace *ns) { if (ns) - kref_put(&ns->base.count, aa_free_namespace_kref); -} - -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_kref(struct kref *kref); -struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); -struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); -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); -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) - -/** - * aa_newest_version - find the newest version of @profile - * @profile: the profile to check for newer versions of (NOT NULL) - * - * Returns: newest version of @profile, if @profile is the newest version - * return @profile. - * - * 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. - */ -static inline struct aa_profile *aa_newest_version(struct aa_profile *profile) -{ - while (profile->replacedby) - profile = profile->replacedby; - - return profile; -} - -/** - * aa_get_profile - increment refcount on profile @p - * @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(struct aa_profile *p) -{ - if (p) - kref_get(&(p->base.count)); - - return p; -} - -/** - * 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); + 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 a2dcccac..c214fb88 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 + +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 544aa6b7..e4d26187 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 d3f4cf02..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 020db35c..513ca0e4 100644 --- a/security/apparmor/include/sid.h +++ b/security/apparmor/include/sid.h @@ -16,7 +16,9 @@ #include -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 00000000..8a3dfc50 --- /dev/null +++ b/security/apparmor/include/sig_names.h @@ -0,0 +1,95 @@ +#include + +#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 cf1071b1..eaf505d3 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" - -/* call back to audit ptrace fields */ -static void audit_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); -} +#include "include/sig_names.h" /** - * 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 + * audit_ptrace_mask - convert mask to permission string + * @buffer: buffer to write string to (NOT NULL) + * @mask: permission mask to convert */ -static int aa_audit_ptrace(struct aa_profile *profile, - struct aa_profile *target, int error) +static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask) { - 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; + 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; + } +} - return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, - audit_cb); +/* call back to audit ptrace fields */ +static void audit_ptrace_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + 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); +} + +/* TODO: conditionals */ +static int profile_ptrace_perm(struct aa_profile *profile, + struct aa_profile *peer, u32 request, + struct common_audit_data *sa) +{ + 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 */ -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode) +static void audit_signal_mask(struct audit_buffer *ab, u32 mask) { - /* - * 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); - } - put_cred(cred); - - return error; + 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) + */ +static void audit_signal_cb(struct audit_buffer *ab, void *va) +{ + 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); + } + } + 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); +} + +/* 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 */ + 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 00000000..311423ed --- /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 +#include + +#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 74302981..f4033f72 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 #include #include #include #include -#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; @@ -57,6 +62,56 @@ char *aa_split_fqname(char *fqname, char **ns_name) return 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 @@ -64,26 +119,24 @@ char *aa_split_fqname(char *fqname, char **ns_name) 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,379 @@ 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; } -/** - * 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. - */ -static void do_vfree(struct work_struct *work) +__counted char *aa_str_alloc(int size, gfp_t gfp) { - vfree(work); + 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", +}; + +/** + * 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 + */ +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, "\""); } /** - * kvfree - free an allocation do by kvmalloc - * @buffer: buffer to free (MAYBE_NULL) - * - * Free a buffer allocated by kvmalloc + * aa_audit_perms_cb - generic callback fn for auditing perms + * @ab: audit buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) */ -void kvfree(void *buffer) +static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) { - 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); - } else - kfree(buffer); + 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) +{ + +} + +/** + * 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 + * + * 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 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 + 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 b21830ec..7665c8b4 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -25,6 +25,7 @@ #include #include +#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(dentry)) 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)) 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)) 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)) 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)) 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)) 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)) 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)) 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->f_path.dentry)) 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(¤t_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, }; /* @@ -702,6 +1246,10 @@ enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; module_param_call(mode, param_set_mode, param_get_mode, &aa_g_profile_mode, S_IRUSR | S_IWUSR); +/* whether policy verification hashing is enabled */ +bool aa_g_hash_policy = CONFIG_SECURITY_APPARMOR_HASH_DEFAULT; +module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUSR | S_IWUSR); + /* Debug mode */ bool aa_g_debug; module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); @@ -743,12 +1291,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 +1395,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 +1410,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 +1426,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 +1436,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 +1502,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 +1516,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 +1534,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 90971a8c..727eb420 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 00000000..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 +#include +#include + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/context.h" +#include "include/domain.h" +#include "include/file.h" +#include "include/match.h" +#include "include/mount.h" +#include "include/path.h" +#include "include/policy.h" + + +static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags) +{ + if (flags & MS_RDONLY) + audit_log_format(ab, "ro"); + else + audit_log_format(ab, "rw"); + if (flags & MS_NOSUID) + audit_log_format(ab, ", nosuid"); + if (flags & MS_NODEV) + audit_log_format(ab, ", nodev"); + if (flags & MS_NOEXEC) + audit_log_format(ab, ", noexec"); + if (flags & MS_SYNCHRONOUS) + audit_log_format(ab, ", sync"); + if (flags & MS_REMOUNT) + audit_log_format(ab, ", remount"); + if (flags & MS_MANDLOCK) + audit_log_format(ab, ", mand"); + if (flags & MS_DIRSYNC) + audit_log_format(ab, ", dirsync"); + if (flags & MS_NOATIME) + audit_log_format(ab, ", noatime"); + if (flags & MS_NODIRATIME) + audit_log_format(ab, ", nodiratime"); + if (flags & MS_BIND) + audit_log_format(ab, flags & MS_REC ? ", rbind" : ", bind"); + if (flags & MS_MOVE) + audit_log_format(ab, ", move"); + if (flags & MS_SILENT) + audit_log_format(ab, ", silent"); + if (flags & MS_POSIXACL) + audit_log_format(ab, ", acl"); + if (flags & MS_UNBINDABLE) + audit_log_format(ab, flags & MS_REC ? ", runbindable" : + ", unbindable"); + if (flags & MS_PRIVATE) + audit_log_format(ab, flags & MS_REC ? ", rprivate" : + ", private"); + if (flags & MS_SLAVE) + audit_log_format(ab, flags & MS_REC ? ", rslave" : + ", slave"); + if (flags & MS_SHARED) + audit_log_format(ab, flags & MS_REC ? ", rshared" : + ", shared"); + if (flags & MS_RELATIME) + audit_log_format(ab, ", relatime"); + if (flags & MS_I_VERSION) + audit_log_format(ab, ", iversion"); + if (flags & MS_STRICTATIME) + audit_log_format(ab, ", strictatime"); + if (flags & MS_NOUSER) + audit_log_format(ab, ", nouser"); +} + +/** + * audit_cb - call back for mount specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) + */ +static void audit_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + + if (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 00000000..e1deffb0 --- /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 e91ffee8..6cea2a6a 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)) { @@ -114,84 +158,34 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, * security_path hooks as a deleted dentry except without an inode * allocated. */ - if (d_unlinked(path->dentry) && path->dentry->d_inode && + if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && !(flags & PATH_MEDIATE_DELETED)) { error = -ENOENT; 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; + char *str = NULL; + int error = d_namespace_path(path, buffer, &str, flags, disconnected); - *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; + 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 81320038..7a9d4c8e 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-. * * 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; + return NULL; - if (!list_empty(&profile->base.list)) { - AA_ERROR("%s: internal error, " - "profile '%s' still on ns list\n", - __func__, profile->base.name); - BUG(); + /* 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; + + profile->ns = aa_get_namespace(root_ns); + + /* 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); - /* free children profiles */ - policy_destroy(&profile->base); - aa_put_profile(profile->parent); - - aa_put_namespace(profile->ns); - kzfree(profile->rename); - - aa_free_file_rules(&profile->file); - aa_free_cap_rules(&profile->caps); - aa_free_rlimit_rules(&profile->rlimits); - - 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; - } - - 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) { @@ -956,50 +973,26 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, return 0; } -/** - * __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; + 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, __aa_current_profile(), gfp, - &sa, NULL); + 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; - write_lock(&ns->lock); - /* no ref on policy only use inside lock */ - policy = __lookup_parent(ns, new_profile->base.hname); + name = ent->new->base.hname; + error = __lookup_replace(ns, ent->new->base.hname, noreplace, + &ent->old, &info); + if (error) + goto fail_lock; - if (!policy) { - info = "parent does not exist"; - error = -ENOENT; - goto audit; - } + if (ent->new->rename) { + error = __lookup_replace(ns, ent->new->rename, + noreplace, &ent->rename, + &info); + if (error) + goto fail_lock; + } - old_profile = __find_child(&policy->profiles, new_profile->base.name); - /* released below */ - aa_get_profile(old_profile); + /* released when @new is freed */ + ent->new->ns = aa_get_namespace(ns); - if (new_profile->rename) { - rename_profile = __lookup_profile(&ns->base, - new_profile->rename); - /* released below */ - aa_get_profile(rename_profile); + if (ent->old || ent->rename) + continue; - if (!rename_profile) { - info = "profile to rename does not exist"; - name = new_profile->rename; - error = -ENOENT; - goto audit; + /* 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)); } } - error = replacement_allowed(old_profile, noreplace, &info); - if (error) - goto audit; + /* 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 */ - error = replacement_allowed(rename_profile, noreplace, &info); - if (error) - goto audit; + /* 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; -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); + 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 (error) { + info = "failed to create"; + goto fail_lock; } - if (!(old_profile || rename_profile)) - __add_new_profile(ns, policy, new_profile); } - write_unlock(&ns->lock); + + /* 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); + } + aa_load_ent_free(ent); + } + __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 329b1fd3..7f63b67d 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; + 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, GFP_KERNEL, &sa, - audit_cb); + 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,50 @@ 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; - profile = unpack_profile(&e); - if (IS_ERR(profile)) - return profile; + start = e.pos; + profile = unpack_profile(&e); + if (IS_ERR(profile)) { + error = PTR_ERR(profile); + goto fail; + } - error = verify_profile(profile); - if (error) { - aa_put_profile(profile); - profile = ERR_PTR(error); + error = verify_profile(profile); + if (error) + goto fail_profile; + + if (aa_g_hash_policy) + 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 refcount */ - return profile; + return 0; + +fail_profile: + aa_put_profile(profile); + +fail: + list_for_each_entry_safe(ent, tmp, lh, list) { + list_del_init(&ent->list); + aa_load_ent_free(ent); + } + + return error; } diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 1b41c542..079130a9 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; + len = aa_label_snprint(*string, len + 2, current_ns, label, true); + if (len < 0) + return len; + (*string)[len] = '\n'; + (*string)[len + 1] = 0; - /* NOTE: len does not include \0 of string, not saved as part of file */ - return len; + 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, ""); 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 e1f3d7ef..add0cfe6 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -15,6 +15,7 @@ #include #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; - /* 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 - */ - if ((task != current->group_leader) || - (profile->rlimits.mask & (1 << resource) && - new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) - error = -EACCES; + rcu_read_lock(); + task_label = aa_get_newest_cred_label(__task_cred(task)); + rcu_read_unlock(); - return audit_resource(profile, resource, new_rlim->rlim_max, error); + /* TODO: extend resource control to handle other (non current) + * profiles. AppArmor rules currently have the implicit assumption + * that the task is setting the resource of a task confined with + * the same profile. + */ + 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; - /* for any rlimits the profile controlled reset the soft limit - * to the less of the tasks hard limit and the init tasks soft limit + old = labels_profile(old_l); + new = labels_profile(new_l); + + /* 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; + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { + if (!(new->rlimits.mask & mask)) + continue; - rlim = current->signal->rlim + i; - rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[i].rlim_max); - /* soft limit should not exceed hard limit */ - rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + 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); + } } }