diff --git a/include/linux/security.h b/include/linux/security.h index 7caf520e6233..73c24e28d88d 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -1595,6 +1595,19 @@ static inline void security_audit_rule_free(void *lsmrule) #ifdef CONFIG_SECURITYFS + +extern int securityfs_pin_fs(void); + +extern int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, + const struct file_operations *fops, + const struct inode_operations *iops); + +extern struct dentry *securityfs_create_dentry(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops, + const struct inode_operations *iops); + extern struct dentry *securityfs_create_file(const char *name, umode_t mode, struct dentry *parent, void *data, const struct file_operations *fops); diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70d72b8..d5b291e94264 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 232469baa94f..ae38e60a5ac7 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -30,14 +30,62 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE If you are unsure how to answer this question, answer 1. +config SECURITY_APPARMOR_STATS + bool "enable debug statistics" + depends on SECURITY_APPARMOR + select APPARMOR_LABEL_STATS + default n + help + This enables keeping statistics on various internal structures + and functions in apparmor. + + If you are unsure how to answer this question, answer N. + +config SECURITY_APPARMOR_UNCONFINED_INIT + bool "Set init to unconfined on boot" + depends on SECURITY_APPARMOR + default y + help + This option determines policy behavior during early boot by + placing the init process in the unconfined state, or the + 'default' profile. + + This option determines policy behavior during early boot by + placing the init process in the unconfined state, or the + 'default' profile. + + 'Y' means init and its children are not confined, unless the + init process is re-execed after a policy load; loaded policy + will only apply to processes started after the load. + + 'N' means init and its children are confined in a profile + named 'default', which can be replaced later and thus + provide for confinement for processes started early at boot, + though not confined during early boot. + + If you are unsure how to answer this question, answer Y. + config SECURITY_APPARMOR_HASH - bool "SHA1 hash of loaded profiles" + bool "Enable introspection of sha1 hashes for loaded profiles" depends on SECURITY_APPARMOR select CRYPTO select CRYPTO_SHA1 default y help - This option selects whether sha1 hashing is done against loaded - profiles and exported for inspection to user space via the apparmor - filesystem. + 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 d693df874818..d85425e62ca5 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,11 +4,45 @@ 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 \ + policy_ns.o backport.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 @@ -61,6 +95,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 @@ -68,3 +103,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 000000000000..757df1ade9a0 --- /dev/null +++ b/security/apparmor/af_unix.c @@ -0,0 +1,643 @@ +/* + * 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 struct sock *aa_sock(struct unix_sock *u) +{ + return &u->sk; +} + +static inline int unix_fs_perm(const char *op, u32 mask, struct aa_label *label, + struct unix_sock *u, int flags) +{ + AA_BUG(!label); + AA_BUG(!u); + AA_BUG(!UNIX_FS(aa_sock(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 aa_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 + */ + struct aa_sk_ctx *ctx = SK_CTX(&u->sk); + if (ctx->path.dentry) + return aa_path_perm(op, label, &ctx->path, flags, mask, + &cond); + 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, + 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)->peer = &peer->label; + + 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; + DEFINE_AUDIT_NET(sa, OP_CREATE, NULL, family, type, protocol); + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + + if ((state = PROFILE_MEDIATES_AF(profile, AF_UNIX))) { + 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, &sa, AA_MAY_CREATE, family, type); +} + +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, const char *op, + u32 request, struct sock *sk) +{ + unsigned int state; + DEFINE_AUDIT_SK(sa, op, sk); + + 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) { + state = match_to_sk(profile, state, unix_sk(sk), + &aad(&sa)->info); + return do_perms(profile, state, request, &sa); + } + + return aa_profile_af_sk_perm(profile, &sa, request, sk); +} + +int aa_unix_label_sk_perm(struct aa_label *label, const char *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, const char *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(const char *op, u32 request, struct socket *sock) +{ + struct aa_label *label = aa_begin_current_label(DO_UPDATE); + int error = unix_label_sock_perm(label, op, request, sock); + aa_end_current_label(label); + + return error; +} + +static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, + struct sockaddr *addr, int addrlen) +{ + unsigned int state; + DEFINE_AUDIT_SK(sa, OP_BIND, sk); + + 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 */ + 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_sk_perm(profile, &sa, AA_MAY_BIND, sk); +} + +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + struct aa_profile *profile; + struct aa_label *label = aa_begin_current_label(DO_UPDATE); + int error = 0; + + /* fs bind is handled by mknod */ + if (!(unconfined(label) || unix_addr_fs(address, addrlen))) + error = fn_for_each_confined(label, profile, + profile_bind_perm(profile, sock->sk, address, + addrlen)); + aa_end_current_label(label); + + return error; +} + +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; + DEFINE_AUDIT_SK(sa, OP_LISTEN, sk); + + 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); + + 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_sk_perm(profile, &sa, AA_MAY_LISTEN, sk); +} + +int aa_unix_listen_perm(struct socket *sock, int backlog) +{ + struct aa_profile *profile; + struct aa_label *label = aa_begin_current_label(DO_UPDATE); + int error = 0; + + if (!(unconfined(label) || UNIX_FS(sock->sk))) + error = fn_for_each_confined(label, profile, + profile_listen_perm(profile, sock->sk, + backlog)); + aa_end_current_label(label); + + return error; +} + + +static inline int profile_accept_perm(struct aa_profile *profile, + struct sock *sk, + struct sock *newsk) +{ + unsigned int state; + DEFINE_AUDIT_SK(sa, OP_ACCEPT, sk); + + 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) { + 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_sk_perm(profile, &sa, AA_MAY_ACCEPT, 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_begin_current_label(DO_UPDATE); + int error = 0; + + if (!(unconfined(label) || UNIX_FS(sock->sk))) + error = fn_for_each_confined(label, profile, + profile_accept_perm(profile, sock->sk, + newsock->sk)); + aa_end_current_label(label); + + return error; +} + + +/* 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(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + return 0; +} + + +static int profile_opt_perm(struct aa_profile *profile, const char *op, u32 request, + struct sock *sk, int level, int optname) +{ + unsigned int state; + DEFINE_AUDIT_SK(sa, op, sk); + + 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); + + 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_sk_perm(profile, &sa, request, sk); +} + +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname) +{ + struct aa_profile *profile; + struct aa_label *label = aa_begin_current_label(DO_UPDATE); + int error = 0; + + if (!(unconfined(label) || UNIX_FS(sock->sk))) + error = fn_for_each_confined(label, profile, + profile_opt_perm(profile, op, request, + sock->sk, level, optname)); + aa_end_current_label(label); + + return error; +} + +/* null peer_label is allowed, in which case the peer_sk label is used */ +static int profile_peer_perm(struct aa_profile *profile, const char *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_ctx *peer_ctx = SK_CTX(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_ctx->label; + return fn_for_each_in_ns(peer_label, peerp, + match_label(profile, peerp, state, request, + sa)); + } + + return aa_profile_af_sk_perm(profile, sa, request, sk); +} + +/** + * + * Requires: lock held on both @sk and @peer_sk + */ +int aa_unix_peer_perm(struct aa_label *label, const char *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(aa_sock(peeru))) + return unix_fs_perm(op, request, label, peeru, 0); + else if (UNIX_FS(aa_sock(u))) + return unix_fs_perm(op, request, label, u, 0); + else { + struct aa_profile *profile; + DEFINE_AUDIT_SK(sa, op, sk); + 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, const char *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_ctx *pctx = SK_CTX(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(pctx->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 9068369f8a1b..10ccfa3d0928 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -18,17 +18,26 @@ #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_ns.h" +#include "include/label.h" #include "include/policy.h" #include "include/resource.h" +#include "include/label.h" +#include "include/lib.h" +#include "include/policy_unpack.h" /** * aa_mangle_name - mangle a profile name to std profile layout form @@ -37,7 +46,7 @@ * * Returns: length of mangled name */ -static int mangle_name(char *name, char *target) +static int mangle_name(const char *name, char *target) { char *t = target; @@ -71,7 +80,6 @@ static int mangle_name(char *name, char *target) /** * aa_simple_write_to_buffer - common routine for getting policy from user - * @op: operation doing the user buffer copy * @userbuf: user buffer to copy data from (NOT NULL) * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) * @copy_size: size of data to copy from user buffer @@ -80,11 +88,12 @@ static int mangle_name(char *name, char *target) * Returns: kernel buffer containing copy of user buffer data or an * ERR_PTR on failure. */ -static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, - size_t alloc_size, size_t copy_size, - loff_t *pos) +static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, + size_t copy_size, + loff_t *pos) { - char *data; + struct aa_loaddata *data; BUG_ON(copy_size > alloc_size); @@ -92,19 +101,16 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, /* only writes from pos 0, that is complete writes */ return ERR_PTR(-ESPIPE); - /* - * Don't allow profile load/replace/remove from profiles that don't - * have CAP_MAC_ADMIN - */ - if (!aa_may_manage_policy(op)) - return ERR_PTR(-EACCES); - /* freed by caller to simple_write_to_buffer */ - data = kvmalloc(alloc_size); + data = kvmalloc(sizeof(*data) + alloc_size); if (data == NULL) return ERR_PTR(-ENOMEM); + kref_init(&data->count); + data->size = copy_size; + data->hash = NULL; + data->abi = 0; - if (copy_from_user(data, userbuf, copy_size)) { + if (copy_from_user(data->data, userbuf, copy_size)) { kvfree(data); return ERR_PTR(-EFAULT); } @@ -112,21 +118,41 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, return data; } +static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, + loff_t *pos, struct aa_ns *ns) +{ + struct aa_label *label; + ssize_t error; + struct aa_loaddata *data; + + label = aa_begin_current_label(DO_UPDATE); + + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(label, ns, mask); + if (error) + return error; + + data = aa_simple_write_to_buffer(buf, size, size, pos); + error = PTR_ERR(data); + if (!IS_ERR(data)) { + error = aa_replace_profiles(ns ? ns : labels_ns(label), label, + mask, data); + aa_put_loaddata(data); + } + aa_end_current_label(label); + + return error; +} /* .load file hook fn to load policy */ static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; - ssize_t error; - - data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); - - error = PTR_ERR(data); - if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_ADD); - kvfree(data); - } + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); + aa_put_ns(ns); return error; } @@ -140,15 +166,10 @@ static const struct file_operations aa_fs_profile_load = { static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; - ssize_t error; - - data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); - error = PTR_ERR(data); - if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_REPLACE); - kvfree(data); - } + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, + buf, size, pos, ns); + aa_put_ns(ns); return error; } @@ -162,22 +183,35 @@ static const struct file_operations aa_fs_profile_replace = { static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; + struct aa_loaddata *data; + struct aa_label *label; ssize_t error; + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + + label = aa_begin_current_label(DO_UPDATE); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY); + if (error) + goto out; /* * aa_remove_profile needs a null terminated string so 1 extra * byte is allocated and the copied data is null terminated. */ - data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); + data = aa_simple_write_to_buffer(buf, size + 1, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - data[size] = 0; - error = aa_remove_profiles(data, size); - kvfree(data); + data->data[size] = 0; + error = aa_remove_profiles(ns ? ns : labels_ns(label), label, + data->data, size); + aa_put_loaddata(data); } - + out: + aa_end_current_label(label); + aa_put_ns(ns); return error; } @@ -186,6 +220,262 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; + +/** + * query_data - queries a policy and writes its data to buf + * @buf: the resulting data is stored here (NOT NULL) + * @buf_len: size of buf + * @query: query string used to retrieve data + * @query_len: size of query including second NUL byte + * + * 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 "