diff options
Diffstat (limited to 'security/apparmor')
57 files changed, 10593 insertions, 3198 deletions
diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore index 9cdec70d72b8..6d1eb1c15c18 100644 --- a/security/apparmor/.gitignore +++ b/security/apparmor/.gitignore @@ -1,5 +1,4 @@ -# -# Generated include files -# +# SPDX-License-Identifier: GPL-2.0-only +net_names.h capability_names.h rlim_names.h diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index b6b68a7750ce..1e3bd44643da 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only config SECURITY_APPARMOR bool "AppArmor support" depends on SECURITY && NET @@ -14,45 +15,6 @@ config SECURITY_APPARMOR If you are unsure how to answer this question, answer N. -config SECURITY_APPARMOR_BOOTPARAM_VALUE - int "AppArmor boot parameter default value" - depends on SECURITY_APPARMOR - range 0 1 - default 1 - help - This option sets the default value for the kernel parameter - 'apparmor', which allows AppArmor to be enabled or disabled - at boot. If this option is set to 0 (zero), the AppArmor - kernel parameter will default to 0, disabling AppArmor at - boot. If this option is set to 1 (one), the AppArmor - kernel parameter will default to 1, enabling AppArmor at - boot. - - If you are unsure how to answer this question, answer 1. - -config SECURITY_APPARMOR_HASH - 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 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. - config SECURITY_APPARMOR_DEBUG bool "Build AppArmor with debug code" depends on SECURITY_APPARMOR @@ -81,3 +43,80 @@ config SECURITY_APPARMOR_DEBUG_MESSAGES Set the default value of the apparmor.debug kernel parameter. When enabled, various debug messages will be logged to the kernel message buffer. + +config SECURITY_APPARMOR_INTROSPECT_POLICY + bool "Allow loaded policy to be introspected" + depends on SECURITY_APPARMOR + default y + help + This option selects whether introspection of loaded policy + is available to userspace via the apparmor filesystem. This + adds to kernel memory usage. It is required for introspection + of loaded policy, and check point and restore support. It + can be disabled for embedded systems where reducing memory and + cpu is paramount. + +config SECURITY_APPARMOR_HASH + bool "Enable introspection of sha256 hashes for loaded profiles" + depends on SECURITY_APPARMOR_INTROSPECT_POLICY + select CRYPTO_LIB_SHA256 + default y + help + This option selects whether introspection of loaded policy + hashes is available to userspace via the apparmor + filesystem. This option provides a light weight means of + checking loaded policy. This option adds to policy load + time and can be disabled for small embedded systems. + +config SECURITY_APPARMOR_HASH_DEFAULT + bool "Enable policy hash introspection by default" + depends on SECURITY_APPARMOR_HASH + default y + help + This option selects whether sha256 hashing of loaded policy + is enabled by default. The generation of sha256 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. + +config SECURITY_APPARMOR_EXPORT_BINARY + bool "Allow exporting the raw binary policy" + depends on SECURITY_APPARMOR_INTROSPECT_POLICY + select ZSTD_COMPRESS + select ZSTD_DECOMPRESS + default y + help + This option allows reading back binary policy as it was loaded. + It increases the amount of kernel memory needed by policy and + also increases policy load time. This option is required for + checkpoint and restore support, and debugging of loaded policy. + +config SECURITY_APPARMOR_PARANOID_LOAD + bool "Perform full verification of loaded policy" + depends on SECURITY_APPARMOR + default y + help + This options allows controlling whether apparmor does a full + verification of loaded policy. This should not be disabled + except for embedded systems where the image is read only, + includes policy, and has some form of integrity check. + Disabling the check will speed up policy loads. + +config SECURITY_APPARMOR_KUNIT_TEST + tristate "Build KUnit tests for policy_unpack.c" if !KUNIT_ALL_TESTS + depends on KUNIT && SECURITY_APPARMOR + default KUNIT_ALL_TESTS + help + This builds the AppArmor KUnit tests. + + KUnit tests run during boot and output the results to the debug log + in TAP format (https://testanything.org/). Only useful for kernel devs + running KUnit test harness and are not for inclusion into a + production build. + + For more information on KUnit and unit tests in general please refer + to the KUnit documentation in Documentation/dev-tools/kunit/. + + If unsure, say N. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index a16b195274de..12fb419714c0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -1,14 +1,52 @@ +# SPDX-License-Identifier: GPL-2.0 # Makefile for AppArmor Linux Security Module # obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o -apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ +apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o secid.o file.o policy_ns.o label.o + resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ + policy_compat.o af_unix.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o -clean-files := capability_names.h rlim_names.h +obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o +apparmor_policy_unpack_test-objs += policy_unpack_test.o +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_SFS_AF_MASK "local inet" +quiet_cmd_make-af = GEN $@ +cmd_make-af = echo "static const char *const 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 "};" >> $@ ;\ + printf '%s' '\#define AA_SFS_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 *const 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 +99,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 +107,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..ac0f4be791ec --- /dev/null +++ b/security/apparmor/af_unix.c @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include <linux/fs.h> +#include <net/tcp_states.h> + +#include "include/audit.h" +#include "include/af_unix.h" +#include "include/apparmor.h" +#include "include/file.h" +#include "include/label.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/cred.h" + + +static inline struct sock *aa_unix_sk(struct unix_sock *u) +{ + return &u->sk; +} + +static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, + struct aa_label *label, const struct path *path) +{ + AA_BUG(!label); + AA_BUG(!path); + + if (unconfined(label) || !label_mediates(label, AA_CLASS_FILE)) + return 0; + + mask &= NET_FS_PERMS; + /* if !u->path.dentry socket is being shutdown - implicit delegation + * until obj delegation is supported + */ + if (path->dentry) { + /* the sunpath may not be valid for this ns so use the path */ + struct inode *inode = path->dentry->d_inode; + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt), inode); + struct path_cond cond = { + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, + }; + + return aa_path_perm(op, subj_cred, label, path, + PATH_SOCK_COND, mask, &cond); + } /* else implicitly delegated */ + + return 0; +} + +/* match_addr special constants */ +#define ABSTRACT_ADDR "\x00" /* abstract socket addr */ +#define ANONYMOUS_ADDR "\x01" /* anonymous endpoint, no addr */ +#define DISCONNECTED_ADDR "\x02" /* addr is another namespace */ +#define SHUTDOWN_ADDR "\x03" /* path addr is shutdown and cleared */ +#define FS_ADDR "/" /* path addr in fs */ + +static aa_state_t match_addr(struct aa_dfa *dfa, aa_state_t state, + struct sockaddr_un *addr, int addrlen) +{ + if (addr) + /* include leading \0 */ + state = aa_dfa_match_len(dfa, state, addr->sun_path, + unix_addr_len(addrlen)); + else + state = aa_dfa_match_len(dfa, state, ANONYMOUS_ADDR, 1); + /* todo: could change to out of band for cleaner separation */ + state = aa_dfa_null_transition(dfa, state); + + return state; +} + +static aa_state_t match_to_local(struct aa_policydb *policy, + aa_state_t state, u32 request, + int type, int protocol, + struct sockaddr_un *addr, int addrlen, + struct aa_perms **p, + const char **info) +{ + state = aa_match_to_prot(policy, state, request, PF_UNIX, type, + protocol, NULL, info); + if (state) { + state = match_addr(policy->dfa, state, addr, addrlen); + if (state) { + /* todo: local label matching */ + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + *info = "failed local label match"; + } else { + *info = "failed local address match"; + } + } + + return state; +} + +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen) +{ + struct unix_address *addr; + + /* memory barrier is sufficient see note in net/unix/af_unix.c */ + addr = smp_load_acquire(&u->addr); + if (addr) { + *addrlen = addr->len; + return addr->name; + } + *addrlen = 0; + return NULL; +} + +static aa_state_t match_to_sk(struct aa_policydb *policy, + aa_state_t state, u32 request, + struct unix_sock *u, struct aa_perms **p, + const char **info) +{ + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); + + return match_to_local(policy, state, request, u->sk.sk_type, + u->sk.sk_protocol, addr, addrlen, p, info); +} + +#define CMD_ADDR 1 +#define CMD_LISTEN 2 +#define CMD_OPT 4 + +static aa_state_t match_to_cmd(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + char cmd, struct aa_perms **p, + const char **info) +{ + AA_BUG(!p); + + state = match_to_sk(policy, state, request, u, p, info); + if (state && !*p) { + state = aa_dfa_match_len(policy->dfa, state, &cmd, 1); + if (!state) + *info = "failed cmd selection match"; + } + + return state; +} + +static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + struct sockaddr_un *peer_addr, int peer_addrlen, + struct aa_perms **p, const char **info) +{ + AA_BUG(!p); + + state = match_to_cmd(policy, state, request, u, CMD_ADDR, p, info); + if (state && !*p) { + state = match_addr(policy->dfa, state, peer_addr, peer_addrlen); + if (!state) + *info = "failed peer address match"; + } + + return state; +} + +static aa_state_t match_label(struct aa_profile *profile, + struct aa_ruleset *rule, aa_state_t state, + u32 request, struct aa_profile *peer, + struct aa_perms *p, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!peer); + + ad->peer = &peer->label; + + if (state && !p) { + state = aa_dfa_match(rule->policy->dfa, state, + peer->base.hname); + if (!state) + ad->info = "failed peer label match"; + + } + + return aa_do_perms(profile, rule->policy, state, request, p, ad); +} + + +/* 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. + * v8 - same as v7 + */ +static int profile_create_perm(struct aa_profile *profile, int family, + int type, int protocol, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, + PF_UNIX, type, protocol, NULL, + &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_CREATE, + NULL, ad); + } + + return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, + protocol); +} + +static int profile_sk_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request, struct sock *sk, const struct path *path) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, + &unix_sk(sk)->path); + + state = match_to_sk(rules->policy, state, request, unix_sk(sk), + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen)) + /* under v7-9 fs hook handles bind */ + return 0; + /* bind for abstract socket */ + state = match_to_local(rules->policy, state, AA_MAY_BIND, + sk->sk_type, sk->sk_protocol, + unix_addr(ad->net.addr), + ad->net.addrlen, + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_BIND, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); +} + +static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, + int backlog, struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + __be16 b = cpu_to_be16(backlog); + + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_LISTEN, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_cmd(rules->policy, state, AA_MAY_LISTEN, + unix_sk(sk), CMD_LISTEN, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed listen backlog match"; + } + return aa_do_perms(profile, rules->policy, state, AA_MAY_LISTEN, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); +} + +static int profile_accept_perm(struct aa_profile *profile, + struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_ACCEPT, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT, + unix_sk(sk), &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_ACCEPT, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); +} + +static int profile_opt_perm(struct aa_profile *profile, u32 request, + struct sock *sk, int optname, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + __be16 b = cpu_to_be16(optname); + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + + state = match_to_cmd(rules->policy, state, request, unix_sk(sk), + CMD_OPT, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed sockopt match"; + } + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* null peer_label is allowed, in which case the peer_sk label is used */ +static int profile_peer_perm(struct aa_profile *profile, u32 request, + struct sock *sk, const struct path *path, + struct sockaddr_un *peer_addr, + int peer_addrlen, const struct path *peer_path, + struct aa_label *peer_label, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + AA_BUG(!sk); + AA_BUG(!peer_label); + AA_BUG(!ad); + + state = RULE_MEDIATES_v9NET(rules); + if (state) { + struct aa_profile *peerp; + + if (peer_path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, peer_path); + else if (path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, path); + state = match_to_peer(rules->policy, state, request, + unix_sk(sk), + peer_addr, peer_addrlen, &p, &ad->info); + + return fn_for_each_in_ns(peer_label, peerp, + match_label(profile, rules, state, request, + peerp, p, ad)); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* -------------------------------- */ + +int aa_unix_create_perm(struct aa_label *label, int family, int type, + int protocol) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, + type, protocol); + + return fn_for_each_confined(label, profile, + profile_create_perm(profile, family, type, + protocol, &ad)); + } + + return 0; +} + +static int aa_unix_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, + const char *op, u32 request, struct sock *sk, + const struct path *path) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + return fn_for_each_confined(label, profile, + profile_sk_perm(profile, &ad, request, sk, + path)); + } + return 0; +} + +/* revalidation, get/set attr, shutdown */ +int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) +{ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_unix_label_sk_perm(current_cred(), label, op, + request, sock->sk, + is_unix_fs(sock->sk) ? &unix_sk(sock->sk)->path : NULL); + end_current_label_crit_section(label); + + return error; +} + +static int valid_addr(struct sockaddr *addr, int addr_len) +{ + struct sockaddr_un *sunaddr = unix_addr(addr); + + /* addr_len == offsetof(struct sockaddr_un, sun_path) is autobind */ + if (addr_len < offsetof(struct sockaddr_un, sun_path) || + addr_len > sizeof(*sunaddr)) + return -EINVAL; + return 0; +} + +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + error = valid_addr(addr, addrlen); + if (error) + return error; + + label = begin_current_label_crit_section(); + /* fs bind is handled by mknod */ + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); + + ad.net.addr = unix_addr(addr); + ad.net.addrlen = addrlen; + + error = fn_for_each_confined(label, profile, + profile_bind_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +/* + * unix connections are covered by the + * - unix_stream_connect (stream) and unix_may_send hooks (dgram) + * - fs connect is handled by open + * This is just here to document this is not needed for af_unix + * +int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} +*/ + +int aa_unix_listen_perm(struct socket *sock, int backlog) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_listen_perm(profile, sock->sk, + backlog, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* 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; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_accept_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* + * dgram handled by unix_may_sendmsg, right to send on stream done at connect + * could do per msg unix_stream here, but connect + socket transfer is + * sufficient. This is just here to document this is not needed for af_unix + * + * sendmsg, recvmsg +int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + return 0; +} +*/ + +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; + int error = 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_opt_perm(profile, request, sock->sk, + optname, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +static int unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, const struct path *path, + struct sockaddr_un *peer_addr, int peer_addrlen, + const struct path *peer_path, struct aa_label *peer_label) +{ + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + ad.net.peer.addr = peer_addr; + ad.net.peer.addrlen = peer_addrlen; + + return fn_for_each_confined(label, profile, + profile_peer_perm(profile, request, sk, path, + peer_addr, peer_addrlen, peer_path, + peer_label, &ad)); +} + +/** + * + * Requires: lock held on both @sk and @peer_sk + * called by unix_stream_connect, unix_may_send + */ +int aa_unix_peer_perm(const struct cred *subj_cred, + 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); + int plen; + struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), &plen); + + AA_BUG(!label); + AA_BUG(!sk); + AA_BUG(!peer_sk); + AA_BUG(!peer_label); + + return unix_peer_perm(subj_cred, label, op, request, sk, + is_unix_fs(sk) ? &u->path : NULL, + paddr, plen, + is_unix_fs(peer_sk) ? &peeru->path : NULL, + peer_label); +} + +/* sk_plabel for comparison only */ +static void update_sk_ctx(struct sock *sk, struct aa_label *label, + struct aa_label *plabel) +{ + struct aa_label *l, *old; + struct aa_sk_ctx *ctx = aa_sock(sk); + bool update_sk; + + rcu_read_lock(); + update_sk = (plabel && + (plabel != rcu_access_pointer(ctx->peer_lastupdate) || + !aa_label_is_subset(plabel, rcu_dereference(ctx->peer)))) || + !__aa_subj_label_is_cached(label, rcu_dereference(ctx->label)); + rcu_read_unlock(); + if (!update_sk) + return; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->label, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + } + if (plabel && rcu_access_pointer(ctx->peer_lastupdate) != plabel) { + old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock)); + + if (old == plabel) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + } else if (aa_label_is_subset(plabel, old)) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + rcu_assign_pointer(ctx->peer, aa_get_label(plabel)); + aa_put_label(old); + } /* else race or a subset - don't update */ + } + spin_unlock(&unix_sk(sk)->lock); +} + +static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx, + struct aa_label *label) +{ + struct aa_label *l, *old; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->peer, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->peer, l); + aa_put_label(old); + } else + aa_put_label(l); + } + spin_unlock(&unix_sk(sk)->lock); +} + +/* This fn is only checked if something has changed in the security + * boundaries. Otherwise cached info off file is sufficient + */ +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file) +{ + struct socket *sock = (struct socket *) file->private_data; + struct sockaddr_un *addr, *peer_addr; + int addrlen, peer_addrlen; + struct aa_label *plabel = NULL; + struct sock *peer_sk = NULL; + u32 sk_req = request & ~NET_PEER_MASK; + struct path path; + bool is_sk_fs; + int error = 0; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(sock->sk->sk_family != PF_UNIX); + + /* investigate only using lock via unix_peer_get() + * addr only needs the memory barrier, but need to investigate + * path + */ + unix_state_lock(sock->sk); + peer_sk = unix_peer(sock->sk); + if (peer_sk) + sock_hold(peer_sk); + + is_sk_fs = is_unix_fs(sock->sk); + addr = aa_sunaddr(unix_sk(sock->sk), &addrlen); + path = unix_sk(sock->sk)->path; + unix_state_unlock(sock->sk); + + if (is_sk_fs && peer_sk) + sk_req = request; + if (sk_req) { + error = aa_unix_label_sk_perm(subj_cred, label, op, + sk_req, sock->sk, + is_sk_fs ? &path : NULL); + } + if (!peer_sk) + goto out; + + peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen); + + struct path peer_path; + + peer_path = unix_sk(peer_sk)->path; + if (!is_sk_fs && is_unix_fs(peer_sk)) { + last_error(error, + unix_fs_perm(op, request, subj_cred, label, + is_unix_fs(peer_sk) ? &peer_path : NULL)); + } else if (!is_sk_fs) { + struct aa_label *plabel; + struct aa_sk_ctx *pctx = aa_sock(peer_sk); + + rcu_read_lock(); + plabel = aa_get_label_rcu(&pctx->label); + rcu_read_unlock(); + /* no fs check of aa_unix_peer_perm because conditions above + * ensure they will never be done + */ + last_error(error, + xcheck(unix_peer_perm(subj_cred, label, op, + MAY_READ | MAY_WRITE, sock->sk, + is_sk_fs ? &path : NULL, + peer_addr, peer_addrlen, + is_unix_fs(peer_sk) ? + &peer_path : NULL, + plabel), + unix_peer_perm(file->f_cred, plabel, op, + MAY_READ | MAY_WRITE, peer_sk, + is_unix_fs(peer_sk) ? + &peer_path : NULL, + addr, addrlen, + is_sk_fs ? &path : NULL, + label))); + if (!error && !__aa_subj_label_is_cached(plabel, label)) + update_peer_ctx(peer_sk, pctx, label); + } + sock_put(peer_sk); + +out: + + /* update peer cache to latest successful perm check */ + if (error == 0) + update_sk_ctx(sock->sk, label, plabel); + aa_put_label(plabel); + + return error; +} + diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 853c2ec8e0c9..907bd2667e28 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,17 +6,12 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/ctype.h> #include <linux/security.h> #include <linux/vmalloc.h> -#include <linux/module.h> +#include <linux/init.h> #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/mount.h> @@ -23,28 +19,31 @@ #include <linux/capability.h> #include <linux/rcupdate.h> #include <linux/fs.h> +#include <linux/fs_context.h> #include <linux/poll.h> +#include <linux/zstd.h> #include <uapi/linux/major.h> #include <uapi/linux/magic.h> #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" -#include "include/context.h" +#include "include/cred.h" #include "include/crypto.h" -#include "include/policy_ns.h" +#include "include/ipc.h" #include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" #include "include/policy_unpack.h" +#include "include/task.h" /* * The apparmor filesystem interface used for policy load and introspection * The interface is split into two main components based on their function * a securityfs component: * used for static files that are always available, and which allows - * userspace to specificy the location of the security filesystem. + * userspace to specify the location of the security filesystem. * * fns and data are prefixed with * aa_sfs_ @@ -68,8 +67,39 @@ * support fns */ +struct rawdata_f_data { + struct aa_loaddata *loaddata; +}; + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1) + +static void rawdata_f_data_free(struct rawdata_f_data *private) +{ + if (!private) + return; + + aa_put_loaddata(private->loaddata); + kvfree(private); +} + +static struct rawdata_f_data *rawdata_f_data_alloc(size_t size) +{ + struct rawdata_f_data *ret; + + if (size > SIZE_MAX - sizeof(*ret)) + return ERR_PTR(-EINVAL); + + ret = kvzalloc(sizeof(*ret) + size, GFP_KERNEL); + if (!ret) + return ERR_PTR(-ENOMEM); + + return ret; +} +#endif + /** - * aa_mangle_name - mangle a profile name to std profile layout form + * 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) * @@ -119,27 +149,24 @@ static int aafs_count; static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) { - struct inode *inode = d_inode(dentry); - - seq_printf(seq, "%s:[%lu]", AAFS_NAME, inode->i_ino); + seq_printf(seq, "%s:[%lu]", AAFS_NAME, d_inode(dentry)->i_ino); return 0; } -static void aafs_evict_inode(struct inode *inode) +static void aafs_free_inode(struct inode *inode) { - truncate_inode_pages_final(&inode->i_data); - clear_inode(inode); if (S_ISLNK(inode->i_mode)) kfree(inode->i_link); + free_inode_nonrcu(inode); } static const struct super_operations aafs_super_ops = { .statfs = simple_statfs, - .evict_inode = aafs_evict_inode, + .free_inode = aafs_free_inode, .show_path = aafs_show_path, }; -static int fill_super(struct super_block *sb, void *data, int silent) +static int apparmorfs_fill_super(struct super_block *sb, struct fs_context *fc) { static struct tree_descr files[] = { {""} }; int error; @@ -152,23 +179,32 @@ static int fill_super(struct super_block *sb, void *data, int silent) return 0; } -static struct dentry *aafs_mount(struct file_system_type *fs_type, - int flags, const char *dev_name, void *data) +static int apparmorfs_get_tree(struct fs_context *fc) { - return mount_single(fs_type, flags, data, fill_super); + return get_tree_single(fc, apparmorfs_fill_super); +} + +static const struct fs_context_operations apparmorfs_context_ops = { + .get_tree = apparmorfs_get_tree, +}; + +static int apparmorfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &apparmorfs_context_ops; + return 0; } static struct file_system_type aafs_ops = { .owner = THIS_MODULE, .name = AAFS_NAME, - .mount = aafs_mount, + .init_fs_context = apparmorfs_init_fs_context, .kill_sb = kill_anon_super, }; /** * __aafs_setup_d_inode - basic inode setup for apparmorfs * @dir: parent directory for the dentry - * @dentry: dentry we are seting the inode up for + * @dentry: dentry we are setting the inode up for * @mode: permissions the file should have * @data: data to store on inode.i_private, available in open() * @link: if symlink, symlink target string @@ -190,7 +226,7 @@ static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, inode->i_ino = get_next_ino(); inode->i_mode = mode; - inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); + simple_inode_init_ts(inode); inode->i_private = data; if (S_ISDIR(mode)) { inode->i_op = iops ? iops : &simple_dir_inode_operations; @@ -247,9 +283,11 @@ static struct dentry *aafs_create(const char *name, umode_t mode, dir = d_inode(parent); inode_lock(dir); - dentry = lookup_one_len(name, parent, strlen(name)); - if (IS_ERR(dentry)) + dentry = lookup_noperm(&QSTR(name), parent); + if (IS_ERR(dentry)) { + error = PTR_ERR(dentry); goto fail_lock; + } if (d_really_is_positive(dentry)) { error = -EEXIST; @@ -306,37 +344,6 @@ static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) } /** - * aafs_create_symlink - create a symlink in the apparmorfs filesystem - * @name: name of dentry to create - * @parent: parent directory for this dentry - * @target: if symlink, symlink target string - * @iops: struct of inode_operations that should be used - * - * If @target parameter is %NULL, then the @iops parameter needs to be - * setup to handle .readlink and .get_link inode_operations. - */ -static struct dentry *aafs_create_symlink(const char *name, - struct dentry *parent, - const char *target, - const struct inode_operations *iops) -{ - struct dentry *dent; - char *link = NULL; - - if (target) { - link = kstrdup(target, GFP_KERNEL); - if (!link) - return ERR_PTR(-ENOMEM); - } - dent = aafs_create(name, S_IFLNK | 0444, parent, NULL, link, NULL, - iops); - if (IS_ERR(dent)) - kfree(link); - - return dent; -} - -/** * aafs_remove - removes a file or directory from the apparmorfs filesystem * * @dentry: dentry of the file/directory/symlink to removed. @@ -348,16 +355,22 @@ static void aafs_remove(struct dentry *dentry) if (!dentry || IS_ERR(dentry)) return; + /* ->d_parent is stable as rename is not supported */ dir = d_inode(dentry->d_parent); - inode_lock(dir); - if (simple_positive(dentry)) { - if (d_is_dir(dentry)) - simple_rmdir(dir, dentry); - else - simple_unlink(dir, dentry); - dput(dentry); + dentry = start_removing_dentry(dentry->d_parent, dentry); + if (!IS_ERR(dentry) && simple_positive(dentry)) { + if (d_is_dir(dentry)) { + if (!WARN_ON(!simple_empty(dentry))) { + __simple_rmdir(dir, dentry); + dput(dentry); + } + } else { + __simple_unlink(dir, dentry); + dput(dentry); + } + d_delete(dentry); } - inode_unlock(dir); + end_removing(dentry); simple_release_fs(&aafs_mnt, &aafs_count); } @@ -396,7 +409,7 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, data->size = copy_size; if (copy_from_user(data->data, userbuf, copy_size)) { - kvfree(data); + aa_put_loaddata(data); return ERR_PTR(-EFAULT); } @@ -415,9 +428,9 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(label, ns, mask); + error = aa_may_manage_policy(current_cred(), label, ns, mask); if (error) - return error; + goto end_section; data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); @@ -425,6 +438,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, error = aa_replace_profiles(ns, label, mask, data); aa_put_loaddata(data); } +end_section: end_current_label_crit_section(label); return error; @@ -477,7 +491,8 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, /* high level check about policy management - fine grained in * below after unpack */ - error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY); + error = aa_may_manage_policy(current_cred(), label, ns, + AA_MAY_REMOVE_POLICY); if (error) goto out; @@ -530,7 +545,7 @@ static ssize_t ns_revision_read(struct file *file, char __user *buf, long last_read; int avail; - mutex_lock(&rev->ns->lock); + mutex_lock_nested(&rev->ns->lock, rev->ns->level); last_read = rev->last_read; if (last_read == rev->ns->revision) { mutex_unlock(&rev->ns->lock); @@ -540,7 +555,7 @@ static ssize_t ns_revision_read(struct file *file, char __user *buf, last_read != READ_ONCE(rev->ns->revision))) return -ERESTARTSYS; - mutex_lock(&rev->ns->lock); + mutex_lock_nested(&rev->ns->lock, rev->ns->level); } avail = sprintf(buffer, "%ld\n", rev->ns->revision); @@ -568,16 +583,16 @@ static int ns_revision_open(struct inode *inode, struct file *file) return 0; } -static unsigned int ns_revision_poll(struct file *file, poll_table *pt) +static __poll_t ns_revision_poll(struct file *file, poll_table *pt) { struct aa_revision *rev = file->private_data; - unsigned int mask = 0; + __poll_t mask = 0; if (rev) { - mutex_lock(&rev->ns->lock); + mutex_lock_nested(&rev->ns->lock, rev->ns->level); poll_wait(file, &rev->ns->wait, pt); if (rev->last_read < rev->ns->revision) - mask |= POLLIN | POLLRDNORM; + mask |= EPOLLIN | EPOLLRDNORM; mutex_unlock(&rev->ns->lock); } @@ -586,7 +601,7 @@ static unsigned int ns_revision_poll(struct file *file, poll_table *pt) void __aa_bump_ns_revision(struct aa_ns *ns) { - ns->revision++; + WRITE_ONCE(ns->revision, READ_ONCE(ns->revision) + 1); wake_up_interruptible(&ns->wait); } @@ -602,32 +617,38 @@ static const struct file_operations aa_fs_ns_revision_fops = { static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, const char *match_str, size_t match_len) { - struct aa_perms tmp; - struct aa_dfa *dfa; - unsigned int state = 0; + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms tmp = { }; + aa_state_t state = DFA_NOMATCH; if (profile_unconfined(profile)) return; - if (profile->file.dfa && *match_str == AA_CLASS_FILE) { - dfa = profile->file.dfa; - state = aa_dfa_match_len(dfa, profile->file.start, + if (rules->file->dfa && *match_str == AA_CLASS_FILE) { + state = aa_dfa_match_len(rules->file->dfa, + rules->file->start[AA_CLASS_FILE], match_str + 1, match_len - 1); - tmp = nullperms; if (state) { struct path_cond cond = { }; - tmp = aa_compute_fperms(dfa, state, &cond); + tmp = *(aa_lookup_condperms(current_fsuid(), + rules->file, state, &cond)); } - } else if (profile->policy.dfa) { - if (!PROFILE_MEDIATES_SAFE(profile, *match_str)) + } else if (rules->policy->dfa) { + if (!RULE_MEDIATES(rules, *match_str)) return; /* no change to current perms */ - dfa = profile->policy.dfa; - state = aa_dfa_match_len(dfa, profile->policy.start[0], + /* old user space does not correctly detect dbus mediation + * support so we may get dbus policy and requests when + * the abi doesn't support it. This can cause mediation + * regressions, so explicitly test for this situation. + */ + if (*match_str == AA_CLASS_DBUS && + !RULE_MEDIATES_v9NET(rules)) + return; /* no change to current perms */ + state = aa_dfa_match_len(rules->policy->dfa, + rules->policy->start[0], match_str, match_len); if (state) - aa_compute_perms(dfa, state, &tmp); - else - tmp = nullperms; + tmp = *aa_lookup_perms(rules->policy, state); } aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum_raw(perms, &tmp); @@ -805,12 +826,10 @@ static ssize_t query_label(char *buf, size_t buf_len, struct multi_transaction { struct kref count; ssize_t size; - char data[0]; + char data[]; }; #define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction)) -/* TODO: replace with per file lock */ -static DEFINE_SPINLOCK(multi_transaction_lock); static void multi_transaction_kref(struct kref *kref) { @@ -844,10 +863,10 @@ static void multi_transaction_set(struct file *file, AA_BUG(n > MULTI_TRANSACTION_LIMIT); new->size = n; - spin_lock(&multi_transaction_lock); + spin_lock(&file->f_lock); old = (struct multi_transaction *) file->private_data; file->private_data = new; - spin_unlock(&multi_transaction_lock); + spin_unlock(&file->f_lock); put_multi_transaction(old); } @@ -864,8 +883,10 @@ static struct multi_transaction *multi_transaction_new(struct file *file, if (!t) return ERR_PTR(-ENOMEM); kref_init(&t->count); - if (copy_from_user(t->data, buf, size)) + if (copy_from_user(t->data, buf, size)) { + put_multi_transaction(t); return ERR_PTR(-EFAULT); + } return t; } @@ -876,9 +897,10 @@ static ssize_t multi_transaction_read(struct file *file, char __user *buf, struct multi_transaction *t; ssize_t ret; - spin_lock(&multi_transaction_lock); + spin_lock(&file->f_lock); t = get_multi_transaction(file->private_data); - spin_unlock(&multi_transaction_lock); + spin_unlock(&file->f_lock); + if (!t) return 0; @@ -988,7 +1010,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) switch (fs_file->v_type) { case AA_SFS_TYPE_BOOLEAN: - seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(fs_file->v.boolean)); break; case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); @@ -997,7 +1019,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: - /* Ignore unpritable entry types. */ + /* Ignore unprintable entry types. */ break; } @@ -1085,9 +1107,9 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v) struct aa_proxy *proxy = seq->private; struct aa_label *label = aa_get_label_rcu(&proxy->label); struct aa_profile *profile = labels_profile(label); - if (profile->attach) - seq_printf(seq, "%s\n", profile->attach); - else if (profile->xmatch) + if (profile->attach.xmatch_str) + seq_printf(seq, "%s\n", profile->attach.xmatch_str); + else if (profile->attach.xmatch->dfa) seq_puts(seq, "<unknown>\n"); else seq_printf(seq, "%s\n", profile->base.name); @@ -1143,7 +1165,7 @@ static int seq_ns_stacked_show(struct seq_file *seq, void *v) struct aa_label *label; label = begin_current_label_crit_section(); - seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(label->size > 1)); end_current_label_crit_section(label); return 0; @@ -1166,7 +1188,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) } } - seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(count > 1)); end_current_label_crit_section(label); return 0; @@ -1186,22 +1208,34 @@ static int seq_ns_level_show(struct seq_file *seq, void *v) static int seq_ns_name_show(struct seq_file *seq, void *v) { struct aa_label *label = begin_current_label_crit_section(); - - seq_printf(seq, "%s\n", aa_ns_name(labels_ns(label), - labels_ns(label), true)); + seq_printf(seq, "%s\n", labels_ns(label)->base.name); end_current_label_crit_section(label); return 0; } +static int seq_ns_compress_min_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%d\n", AA_MIN_CLEVEL); + return 0; +} + +static int seq_ns_compress_max_show(struct seq_file *seq, void *v) +{ + seq_printf(seq, "%d\n", AA_MAX_CLEVEL); + return 0; +} + SEQ_NS_FOPS(stacked); SEQ_NS_FOPS(nsstacked); SEQ_NS_FOPS(level); SEQ_NS_FOPS(name); +SEQ_NS_FOPS(compress_min); +SEQ_NS_FOPS(compress_max); /* policy/raw_data/ * file ops */ - +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY #define SEQ_RAWDATA_FOPS(NAME) \ static int seq_rawdata_ ##NAME ##_open(struct inode *inode, struct file *file)\ { \ @@ -1278,36 +1312,110 @@ static int seq_rawdata_hash_show(struct seq_file *seq, void *v) return 0; } +static int seq_rawdata_compressed_size_show(struct seq_file *seq, void *v) +{ + struct aa_loaddata *data = seq->private; + + seq_printf(seq, "%zu\n", data->compressed_size); + + return 0; +} + SEQ_RAWDATA_FOPS(abi); SEQ_RAWDATA_FOPS(revision); SEQ_RAWDATA_FOPS(hash); +SEQ_RAWDATA_FOPS(compressed_size); + +static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen) +{ + if (slen < dlen) { + const size_t wksp_len = zstd_dctx_workspace_bound(); + zstd_dctx *ctx; + void *wksp; + size_t out_len; + int ret = 0; + + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; + } + ctx = zstd_init_dctx(wksp, wksp_len); + if (ctx == NULL) { + ret = -ENOMEM; + goto cleanup; + } + out_len = zstd_decompress_dctx(ctx, dst, dlen, src, slen); + if (zstd_is_error(out_len)) { + ret = -EINVAL; + goto cleanup; + } +cleanup: + kvfree(wksp); + return ret; + } + + if (dlen < slen) + return -EINVAL; + memcpy(dst, src, slen); + return 0; +} static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { - struct aa_loaddata *rawdata = file->private_data; + struct rawdata_f_data *private = file->private_data; - return simple_read_from_buffer(buf, size, ppos, rawdata->data, - rawdata->size); + return simple_read_from_buffer(buf, size, ppos, + RAWDATA_F_DATA_BUF(private), + private->loaddata->size); } static int rawdata_release(struct inode *inode, struct file *file) { - aa_put_loaddata(file->private_data); + rawdata_f_data_free(file->private_data); return 0; } static int rawdata_open(struct inode *inode, struct file *file) { - if (!policy_view_capable(NULL)) + int error; + struct aa_loaddata *loaddata; + struct rawdata_f_data *private; + + if (!aa_current_policy_view_capable(NULL)) return -EACCES; - file->private_data = __aa_get_loaddata(inode->i_private); - if (!file->private_data) + + loaddata = __aa_get_loaddata(inode->i_private); + if (!loaddata) /* lost race: this entry is being reaped */ return -ENOENT; + private = rawdata_f_data_alloc(loaddata->size); + if (IS_ERR(private)) { + error = PTR_ERR(private); + goto fail_private_alloc; + } + + private->loaddata = loaddata; + + error = decompress_zstd(loaddata->data, loaddata->compressed_size, + RAWDATA_F_DATA_BUF(private), + loaddata->size); + if (error) + goto fail_decompress; + + file->private_data = private; return 0; + +fail_decompress: + rawdata_f_data_free(private); + return error; + +fail_private_alloc: + aa_put_loaddata(loaddata); + return error; } static const struct file_operations rawdata_fops = { @@ -1379,13 +1487,20 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) rawdata->dents[AAFS_LOADDATA_REVISION] = dent; if (aa_g_hash_policy) { - dent = aafs_create_file("sha1", S_IFREG | 0444, dir, + dent = aafs_create_file("sha256", S_IFREG | 0444, dir, rawdata, &seq_rawdata_hash_fops); if (IS_ERR(dent)) goto fail; rawdata->dents[AAFS_LOADDATA_HASH] = dent; } + dent = aafs_create_file("compressed_size", S_IFREG | 0444, dir, + rawdata, + &seq_rawdata_compressed_size_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_COMPRESSED_SIZE] = dent; + dent = aafs_create_file("raw_data", S_IFREG | 0444, dir, rawdata, &rawdata_fops); if (IS_ERR(dent)) @@ -1404,10 +1519,12 @@ fail: return PTR_ERR(dent); } +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + /** fns to setup dynamic per profile/namespace files **/ -/** +/* * * Requires: @profile->ns->lock held */ @@ -1434,7 +1551,7 @@ void __aafs_profile_rmdir(struct aa_profile *profile) } } -/** +/* * * Requires: @old->ns->lock held */ @@ -1443,10 +1560,18 @@ void __aafs_profile_migrate_dents(struct aa_profile *old, { 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(new->dents[i]->d_inode); + if (new->dents[i]) { + struct inode *inode = d_inode(new->dents[i]); + + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + } old->dents[i] = NULL; } } @@ -1465,6 +1590,7 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name, return dent; } +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY static int profile_depth(struct aa_profile *profile) { int depth = 0; @@ -1477,26 +1603,92 @@ static int profile_depth(struct aa_profile *profile) return depth; } -static int gen_symlink_name(char *buffer, size_t bsize, int depth, - const char *dirname, const char *fname) +static char *gen_symlink_name(int depth, const char *dirname, const char *fname) { + char *buffer, *s; int error; + int size = depth * 6 + strlen(dirname) + strlen(fname) + 11; + + s = buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return ERR_PTR(-ENOMEM); for (; depth > 0; depth--) { - if (bsize < 7) - return -ENAMETOOLONG; - strcpy(buffer, "../../"); - buffer += 6; - bsize -= 6; + strcpy(s, "../../"); + s += 6; + size -= 6; } - error = snprintf(buffer, bsize, "raw_data/%s/%s", dirname, fname); - if (error >= bsize || error < 0) - return -ENAMETOOLONG; + error = snprintf(s, size, "raw_data/%s/%s", dirname, fname); + if (error >= size || error < 0) { + kfree(buffer); + return ERR_PTR(-ENAMETOOLONG); + } - return 0; + return buffer; } +static const char *rawdata_get_link_base(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done, + const char *name) +{ + struct aa_proxy *proxy = inode->i_private; + struct aa_label *label; + struct aa_profile *profile; + char *target; + int depth; + + if (!dentry) + return ERR_PTR(-ECHILD); + + label = aa_get_label_rcu(&proxy->label); + profile = labels_profile(label); + depth = profile_depth(profile); + target = gen_symlink_name(depth, profile->rawdata->name, name); + aa_put_label(label); + + if (IS_ERR(target)) + return target; + + set_delayed_call(done, kfree_link, target); + + return target; +} + +static const char *rawdata_get_link_sha256(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + return rawdata_get_link_base(dentry, inode, done, "sha256"); +} + +static const char *rawdata_get_link_abi(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + return rawdata_get_link_base(dentry, inode, done, "abi"); +} + +static const char *rawdata_get_link_data(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + return rawdata_get_link_base(dentry, inode, done, "raw_data"); +} + +static const struct inode_operations rawdata_link_sha256_iops = { + .get_link = rawdata_get_link_sha256, +}; + +static const struct inode_operations rawdata_link_abi_iops = { + .get_link = rawdata_get_link_abi, +}; +static const struct inode_operations rawdata_link_data_iops = { + .get_link = rawdata_get_link_data, +}; +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + /* * Requires: @profile->ns->lock held */ @@ -1506,10 +1698,17 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) 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); + if (!dent) { + error = -ENOENT; + goto fail2; + } /* adding to parent that previously didn't have children */ dent = aafs_create_dir("profiles", dent); if (IS_ERR(dent)) @@ -1556,44 +1755,41 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->dents[AAFS_PROF_ATTACH] = dent; if (profile->hash) { - dent = create_profile_file(dir, "sha1", profile, + dent = create_profile_file(dir, "sha256", profile, &seq_profile_hash_fops); if (IS_ERR(dent)) goto fail; profile->dents[AAFS_PROF_HASH] = dent; } +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY if (profile->rawdata) { - char target[64]; - int depth = profile_depth(profile); - - error = gen_symlink_name(target, sizeof(target), depth, - profile->rawdata->name, "sha1"); - if (error < 0) - goto fail2; - dent = aafs_create_symlink("raw_sha1", dir, target, NULL); - if (IS_ERR(dent)) - goto fail; - profile->dents[AAFS_PROF_RAW_HASH] = dent; - - error = gen_symlink_name(target, sizeof(target), depth, - profile->rawdata->name, "abi"); - if (error < 0) - goto fail2; - dent = aafs_create_symlink("raw_abi", dir, target, NULL); + if (aa_g_hash_policy) { + dent = aafs_create("raw_sha256", S_IFLNK | 0444, dir, + profile->label.proxy, NULL, NULL, + &rawdata_link_sha256_iops); + if (IS_ERR(dent)) + goto fail; + aa_get_proxy(profile->label.proxy); + profile->dents[AAFS_PROF_RAW_HASH] = dent; + } + dent = aafs_create("raw_abi", S_IFLNK | 0444, dir, + profile->label.proxy, NULL, NULL, + &rawdata_link_abi_iops); if (IS_ERR(dent)) goto fail; + aa_get_proxy(profile->label.proxy); profile->dents[AAFS_PROF_RAW_ABI] = dent; - error = gen_symlink_name(target, sizeof(target), depth, - profile->rawdata->name, "raw_data"); - if (error < 0) - goto fail2; - dent = aafs_create_symlink("raw_data", dir, target, NULL); + dent = aafs_create("raw_data", S_IFLNK | 0444, dir, + profile->label.proxy, NULL, NULL, + &rawdata_link_data_iops); if (IS_ERR(dent)) goto fail; + aa_get_proxy(profile->label.proxy); profile->dents[AAFS_PROF_RAW_DATA] = dent; } +#endif /*CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ list_for_each_entry(child, &profile->base.profiles, base.list) { error = __aafs_profile_mkdir(child, prof_child_dir(profile)); @@ -1612,7 +1808,8 @@ fail2: return error; } -static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) +static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; /* TODO: improve permission check */ @@ -1620,10 +1817,11 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) int error; label = begin_current_label_crit_section(); - error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + error = aa_may_manage_policy(current_cred(), label, NULL, + AA_MAY_LOAD_POLICY); end_current_label_crit_section(label); if (error) - return error; + return ERR_PTR(error); parent = aa_get_ns(dir->i_private); AA_BUG(d_inode(ns_subns_dir(parent)) != dir); @@ -1633,7 +1831,7 @@ static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode) */ inode_unlock(dir); error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); - mutex_lock(&parent->lock); + mutex_lock_nested(&parent->lock, parent->level); inode_lock_nested(dir, I_MUTEX_PARENT); if (error) goto out; @@ -1658,7 +1856,7 @@ out: mutex_unlock(&parent->lock); aa_put_ns(parent); - return error; + return ERR_PTR(error); } static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) @@ -1669,12 +1867,13 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) int error; label = begin_current_label_crit_section(); - error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY); + error = aa_may_manage_policy(current_cred(), label, NULL, + AA_MAY_LOAD_POLICY); end_current_label_crit_section(label); if (error) return error; - parent = aa_get_ns(dir->i_private); + parent = aa_get_ns(dir->i_private); /* rmdir calls the generic securityfs functions to remove files * from the apparmor dir. It is up to the apparmor ns locking * to avoid races. @@ -1682,7 +1881,7 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) inode_unlock(dir); inode_unlock(dentry->d_inode); - mutex_lock(&parent->lock); + mutex_lock_nested(&parent->lock, parent->level); ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name, dentry->d_name.len)); if (!ns) { @@ -1719,7 +1918,7 @@ static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) __aa_fs_remove_rawdata(ent); } -/** +/* * * Requires: @ns->lock held */ @@ -1731,12 +1930,13 @@ void __aafs_ns_rmdir(struct aa_ns *ns) if (!ns) return; + AA_BUG(!mutex_is_locked(&ns->lock)); list_for_each_entry(child, &ns->base.profiles, base.list) __aafs_profile_rmdir(child); list_for_each_entry(sub, &ns->sub_ns, base.list) { - mutex_lock(&sub->lock); + mutex_lock_nested(&sub->lock, sub->level); __aafs_ns_rmdir(sub); mutex_unlock(&sub->lock); } @@ -1866,7 +2066,7 @@ int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, /* subnamespaces */ list_for_each_entry(sub, &ns->sub_ns, base.list) { - mutex_lock(&sub->lock); + mutex_lock_nested(&sub->lock, sub->level); error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL); mutex_unlock(&sub->lock); if (error) @@ -1884,9 +2084,6 @@ fail2: return error; } - -#define list_entry_is_head(pos, head, member) (&pos->member == (head)) - /** * __next_ns - find the next namespace to list * @root: root namespace to stop search at (NOT NULL) @@ -1903,10 +2100,14 @@ static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) { struct aa_ns *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); + mutex_lock_nested(&next->lock, next->level); return next; } @@ -1916,7 +2117,7 @@ static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) mutex_unlock(&ns->lock); next = list_next_entry(ns, base.list); if (!list_entry_is_head(next, &parent->sub_ns, base.list)) { - mutex_lock(&next->lock); + mutex_lock_nested(&next->lock, next->level); return next; } ns = parent; @@ -1937,6 +2138,9 @@ static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) static struct aa_profile *__first_profile(struct aa_ns *root, struct aa_ns *ns) { + AA_BUG(!root); + AA_BUG(ns && !mutex_is_locked(&ns->lock)); + for (; ns; ns = __next_ns(root, ns)) { if (!list_empty(&ns->base.profiles)) return list_first_entry(&ns->base.profiles, @@ -1947,7 +2151,7 @@ static struct aa_profile *__first_profile(struct aa_ns *root, /** * __next_profile - step to the next profile in a profile tree - * @profile: current profile in tree (NOT NULL) + * @p: current profile in tree (NOT NULL) * * Perform a depth first traversal on the profile tree in a namespace * @@ -1959,6 +2163,8 @@ static struct aa_profile *__next_profile(struct aa_profile *p) struct aa_profile *parent; struct aa_ns *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), @@ -2019,7 +2225,7 @@ static void *p_start(struct seq_file *f, loff_t *pos) f->private = root; /* find the first profile */ - mutex_lock(&root->lock); + mutex_lock_nested(&root->lock, root->level); profile = __first_profile(root, root); /* skip to position */ @@ -2051,7 +2257,7 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos) /** * p_stop - stop depth first traversal * @f: seq_file we are filling - * @p: the last profile writen + * @p: the last profile written * * Release all locking done by p_start/p_next on namespace tree */ @@ -2096,7 +2302,7 @@ static const struct seq_operations aa_sfs_profiles_op = { static int profiles_open(struct inode *inode, struct file *file) { - if (!policy_view_capable(NULL)) + if (!aa_current_policy_view_capable(NULL)) return -EACCES; return seq_open(file, &aa_sfs_profiles_op); @@ -2127,33 +2333,74 @@ static struct aa_sfs_entry aa_sfs_entry_ptrace[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_signal[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_SIG_MASK), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_attach[] = { + AA_SFS_FILE_BOOLEAN("xattr", 1), + { } +}; static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("change_hat", 1), AA_SFS_FILE_BOOLEAN("change_hatv", 1), + AA_SFS_FILE_BOOLEAN("unconfined_allowed_children", 1), AA_SFS_FILE_BOOLEAN("change_onexec", 1), AA_SFS_FILE_BOOLEAN("change_profile", 1), AA_SFS_FILE_BOOLEAN("stack", 1), AA_SFS_FILE_BOOLEAN("fix_binfmt_elf_mmap", 1), + AA_SFS_FILE_BOOLEAN("post_nnp_subset", 1), + AA_SFS_FILE_BOOLEAN("computed_longest_left", 1), + AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach), + AA_SFS_FILE_BOOLEAN("disconnected.path", 1), + AA_SFS_FILE_BOOLEAN("kill.signal", 1), AA_SFS_FILE_STRING("version", "1.2"), { } }; +static struct aa_sfs_entry aa_sfs_entry_unconfined[] = { + AA_SFS_FILE_BOOLEAN("change_profile", 1), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_versions[] = { AA_SFS_FILE_BOOLEAN("v5", 1), AA_SFS_FILE_BOOLEAN("v6", 1), AA_SFS_FILE_BOOLEAN("v7", 1), + AA_SFS_FILE_BOOLEAN("v8", 1), + AA_SFS_FILE_BOOLEAN("v9", 1), { } }; +#define PERMS32STR "allow deny subtree cond kill complain prompt audit quiet hide xindex tag label" static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_DIR("versions", aa_sfs_entry_versions), AA_SFS_FILE_BOOLEAN("set_load", 1), + /* number of out of band transitions supported */ + AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), + AA_SFS_FILE_U64("permstable32_version", 3), + AA_SFS_FILE_STRING("permstable32", PERMS32STR), + AA_SFS_FILE_U64("state32", 1), + AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_mount[] = { + AA_SFS_FILE_STRING("mask", "mount umount pivot_root"), + AA_SFS_FILE_STRING("move_mount", "detached"), { } }; static struct aa_sfs_entry aa_sfs_entry_ns[] = { AA_SFS_FILE_BOOLEAN("profile", 1), - AA_SFS_FILE_BOOLEAN("pivot_root", 1), + AA_SFS_FILE_BOOLEAN("pivot_root", 0), + AA_SFS_FILE_STRING("mask", "userns_create"), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_dbus[] = { + AA_SFS_FILE_STRING("mask", "acquire send receive"), { } }; @@ -2168,26 +2415,40 @@ static struct aa_sfs_entry aa_sfs_entry_query[] = { AA_SFS_DIR("label", aa_sfs_entry_query_label), { } }; + +static struct aa_sfs_entry aa_sfs_entry_io_uring[] = { + AA_SFS_FILE_STRING("mask", "sqpoll override_creds"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("policy", aa_sfs_entry_policy), AA_SFS_DIR("domain", aa_sfs_entry_domain), AA_SFS_DIR("file", aa_sfs_entry_file), + AA_SFS_DIR("network_v8", aa_sfs_entry_network), + AA_SFS_DIR("network_v9", aa_sfs_entry_networkv9), + AA_SFS_DIR("mount", aa_sfs_entry_mount), AA_SFS_DIR("namespaces", aa_sfs_entry_ns), AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), AA_SFS_DIR("rlimit", aa_sfs_entry_rlimit), AA_SFS_DIR("caps", aa_sfs_entry_caps), AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), + AA_SFS_DIR("signal", aa_sfs_entry_signal), + AA_SFS_DIR("dbus", aa_sfs_entry_dbus), AA_SFS_DIR("query", aa_sfs_entry_query), + AA_SFS_DIR("io_uring", aa_sfs_entry_io_uring), { } }; static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { - AA_SFS_FILE_FOPS(".access", 0640, &aa_sfs_access), + AA_SFS_FILE_FOPS(".access", 0666, &aa_sfs_access), AA_SFS_FILE_FOPS(".stacked", 0444, &seq_ns_stacked_fops), AA_SFS_FILE_FOPS(".ns_stacked", 0444, &seq_ns_nsstacked_fops), - AA_SFS_FILE_FOPS(".ns_level", 0666, &seq_ns_level_fops), - AA_SFS_FILE_FOPS(".ns_name", 0640, &seq_ns_name_fops), - AA_SFS_FILE_FOPS("profiles", 0440, &aa_sfs_profiles_fops), + AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops), + AA_SFS_FILE_FOPS(".ns_name", 0444, &seq_ns_name_fops), + AA_SFS_FILE_FOPS("profiles", 0444, &aa_sfs_profiles_fops), + AA_SFS_FILE_FOPS("raw_data_compression_level_min", 0444, &seq_ns_compress_min_fops), + AA_SFS_FILE_FOPS("raw_data_compression_level_max", 0444, &seq_ns_compress_max_fops), AA_SFS_DIR("features", aa_sfs_entry_features), { } }; @@ -2312,7 +2573,7 @@ static int aa_mk_null_file(struct dentry *parent) return error; inode_lock(d_inode(parent)); - dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME)); + dentry = lookup_noperm(&QSTR(NULL_FILE_NAME), parent); if (IS_ERR(dentry)) { error = PTR_ERR(dentry); goto out; @@ -2325,7 +2586,7 @@ static int aa_mk_null_file(struct dentry *parent) 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(inode); + simple_inode_init_ts(inode); init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); d_instantiate(dentry, inode); @@ -2350,41 +2611,32 @@ static const char *policy_get_link(struct dentry *dentry, { struct aa_ns *ns; struct path path; + int error; if (!dentry) return ERR_PTR(-ECHILD); + ns = aa_get_current_ns(); path.mnt = mntget(aafs_mnt); path.dentry = dget(ns_dir(ns)); - nd_jump_link(&path); + error = nd_jump_link(&path); aa_put_ns(ns); - return NULL; -} - -static int ns_get_name(char *buf, size_t size, struct aa_ns *ns, - struct inode *inode) -{ - int res = snprintf(buf, size, "%s:[%lu]", AAFS_NAME, inode->i_ino); - - if (res < 0 || res >= size) - res = -ENOENT; - - return res; + return ERR_PTR(error); } static int policy_readlink(struct dentry *dentry, char __user *buffer, int buflen) { - struct aa_ns *ns; char name[32]; int res; - ns = aa_get_current_ns(); - res = ns_get_name(name, sizeof(name), ns, d_inode(dentry)); - if (res >= 0) - res = readlink_copy(buffer, buflen, name); - aa_put_ns(ns); + res = snprintf(name, sizeof(name), "%s:[%lu]", AAFS_NAME, + d_inode(dentry)->i_ino); + if (res > 0 && res < sizeof(name)) + res = readlink_copy(buffer, buflen, name, strlen(name)); + else + res = -ENOENT; return res; } @@ -2402,7 +2654,7 @@ static const struct inode_operations policy_link_iops = { * * Returns: error on failure */ -static int __init aa_create_aafs(void) +int __init aa_create_aafs(void) { struct dentry *dent; int error; @@ -2419,7 +2671,7 @@ static int __init aa_create_aafs(void) aafs_mnt = kern_mount(&aafs_ops); if (IS_ERR(aafs_mnt)) panic("can't set apparmorfs up\n"); - aafs_mnt->mnt_sb->s_flags &= ~MS_NOUSER; + aafs_mnt->mnt_sb->s_flags &= ~SB_NOUSER; /* Populate fs tree. */ error = entry_create_dir(&aa_sfs_entry, NULL); @@ -2428,38 +2680,30 @@ static int __init aa_create_aafs(void) dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_load); - if (IS_ERR(dent)) { - error = PTR_ERR(dent); - goto error; - } + if (IS_ERR(dent)) + goto dent_error; ns_subload(root_ns) = dent; dent = securityfs_create_file(".replace", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_replace); - if (IS_ERR(dent)) { - error = PTR_ERR(dent); - goto error; - } + if (IS_ERR(dent)) + goto dent_error; ns_subreplace(root_ns) = dent; dent = securityfs_create_file(".remove", 0666, aa_sfs_entry.dentry, NULL, &aa_fs_profile_remove); - if (IS_ERR(dent)) { - error = PTR_ERR(dent); - goto error; - } + if (IS_ERR(dent)) + goto dent_error; ns_subremove(root_ns) = dent; dent = securityfs_create_file("revision", 0444, aa_sfs_entry.dentry, NULL, &aa_fs_ns_revision_fops); - if (IS_ERR(dent)) { - error = PTR_ERR(dent); - goto error; - } + if (IS_ERR(dent)) + goto dent_error; ns_subrevision(root_ns) = dent; /* policy tree referenced by magic policy symlink */ - mutex_lock(&root_ns->lock); + mutex_lock_nested(&root_ns->lock, root_ns->level); error = __aafs_ns_mkdir(root_ns, aafs_mnt->mnt_root, ".policy", aafs_mnt->mnt_root); mutex_unlock(&root_ns->lock); @@ -2469,10 +2713,8 @@ static int __init aa_create_aafs(void) /* magic symlink similar to nsfs redirects based on task policy */ dent = securityfs_create_symlink("policy", aa_sfs_entry.dentry, NULL, &policy_link_iops); - if (IS_ERR(dent)) { - error = PTR_ERR(dent); - goto error; - } + if (IS_ERR(dent)) + goto dent_error; error = aa_mk_null_file(aa_sfs_entry.dentry); if (error) @@ -2484,10 +2726,10 @@ static int __init aa_create_aafs(void) aa_info_message("AppArmor Filesystem Enabled"); return 0; +dent_error: + error = PTR_ERR(dent); error: aa_destroy_aafs(); AA_ERROR("Error creating AppArmor securityfs\n"); return error; } - -fs_initcall(aa_create_aafs); diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 8f9ecac7f8de..ac89602aa2d9 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/audit.h> @@ -19,7 +15,7 @@ #include "include/audit.h" #include "include/policy.h" #include "include/policy_ns.h" - +#include "include/secid.h" const char *const audit_mode_names[] = { "normal", @@ -40,6 +36,43 @@ static const char *const aa_audit_type[] = { "AUTO" }; +static const char *const aa_class_names[] = { + "none", + "unknown", + "file", + "cap", + "net", + "rlimits", + "domain", + "mount", + "unknown", + "ptrace", + "signal", + "xmatch", + "unknown", + "unknown", + "net", + "unknown", + "label", + "posix_mqueue", + "io_uring", + "module", + "lsm", + "namespace", + "io_uring", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "unknown", + "X", + "dbus", +}; + + /* * Currently AppArmor auditing is fed straight into the audit framework. * @@ -50,35 +83,38 @@ static const char *const aa_audit_type[] = { */ /** - * audit_base - core AppArmor function. + * audit_pre() - core AppArmor function. * @ab: audit buffer to fill (NOT NULL) - * @ca: audit structure containing data to audit (NOT NULL) + * @va: audit structure containing data to audit (NOT NULL) * - * Record common AppArmor audit data from @sa + * Record common AppArmor audit data from @va */ -static void audit_pre(struct audit_buffer *ab, void *ca) +static void audit_pre(struct audit_buffer *ab, void *va) { - struct common_audit_data *sa = ca; + struct apparmor_audit_data *ad = aad_of_va(va); if (aa_g_audit_header) { - audit_log_format(ab, "apparmor="); - audit_log_string(ab, aa_audit_type[aad(sa)->type]); + audit_log_format(ab, "apparmor=\"%s\"", + aa_audit_type[ad->type]); } - if (aad(sa)->op) { - audit_log_format(ab, " operation="); - audit_log_string(ab, aad(sa)->op); - } + if (ad->op) + audit_log_format(ab, " operation=\"%s\"", ad->op); - if (aad(sa)->info) { - audit_log_format(ab, " info="); - audit_log_string(ab, aad(sa)->info); - if (aad(sa)->error) - audit_log_format(ab, " error=%d", aad(sa)->error); + if (ad->class) + audit_log_format(ab, " class=\"%s\"", + ad->class <= AA_CLASS_LAST ? + aa_class_names[ad->class] : + "unknown"); + + if (ad->info) { + audit_log_format(ab, " info=\"%s\"", ad->info); + if (ad->error) + audit_log_format(ab, " error=%d", ad->error); } - if (aad(sa)->label) { - struct aa_label *label = aad(sa)->label; + if (ad->subj_label) { + struct aa_label *label = ad->subj_label; if (label_isprofile(label)) { struct aa_profile *profile = labels_profile(label); @@ -97,42 +133,44 @@ static void audit_pre(struct audit_buffer *ab, void *ca) } } - if (aad(sa)->name) { + if (ad->name) { audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, aad(sa)->name); + audit_log_untrustedstring(ab, ad->name); } } /** * aa_audit_msg - Log a message to the audit subsystem - * @sa: audit event structure (NOT NULL) + * @type: audit type for the message + * @ad: audit event structure (NOT NULL) * @cb: optional callback fn for type specific fields (MAYBE NULL) */ -void aa_audit_msg(int type, struct common_audit_data *sa, +void aa_audit_msg(int type, struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)) { - aad(sa)->type = type; - common_lsm_audit(sa, audit_pre, cb); + ad->type = type; + common_lsm_audit(&ad->common, audit_pre, cb); } /** * 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) - * @sa: audit event (NOT NULL) + * @ad: audit event (NOT NULL) * @cb: optional callback fn for type specific fields (MAYBE NULL) * * Handle default message switching based off of audit mode flags * * Returns: error on failure */ -int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, +int aa_audit(int type, struct aa_profile *profile, + struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)) { AA_BUG(!profile); if (type == AUDIT_APPARMOR_AUTO) { - if (likely(!aad(sa)->error)) { + if (likely(!ad->error)) { if (AUDIT_MODE(profile) != AUDIT_ALL) return 0; type = AUDIT_APPARMOR_AUDIT; @@ -143,23 +181,111 @@ int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, } if (AUDIT_MODE(profile) == AUDIT_QUIET || (type == AUDIT_APPARMOR_DENIED && - AUDIT_MODE(profile) == AUDIT_QUIET)) - return aad(sa)->error; + AUDIT_MODE(profile) == AUDIT_QUIET_DENIED)) + return ad->error; if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) type = AUDIT_APPARMOR_KILL; - aad(sa)->label = &profile->label; + ad->subj_label = &profile->label; + + aa_audit_msg(type, ad, cb); + + if (ad->type == AUDIT_APPARMOR_KILL) + (void)send_sig_info(profile->signal, NULL, + ad->common.type == LSM_AUDIT_DATA_TASK && + ad->common.u.tsk ? ad->common.u.tsk : current); + + if (ad->type == AUDIT_APPARMOR_ALLOWED) + return complain_error(ad->error); + + return ad->error; +} + +struct aa_audit_rule { + struct aa_label *label; +}; + +void aa_audit_rule_free(void *vrule) +{ + struct aa_audit_rule *rule = vrule; + + if (rule) { + if (!IS_ERR(rule->label)) + aa_put_label(rule->label); + kfree(rule); + } +} + +int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp) +{ + struct aa_audit_rule *rule; + + switch (field) { + case AUDIT_SUBJ_ROLE: + if (op != Audit_equal && op != Audit_not_equal) + return -EINVAL; + break; + default: + return -EINVAL; + } - aa_audit_msg(type, sa, cb); + rule = kzalloc(sizeof(struct aa_audit_rule), gfp); - if (aad(sa)->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(SIGKILL, NULL, - sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ? - sa->u.tsk : current); + if (!rule) + return -ENOMEM; - if (aad(sa)->type == AUDIT_APPARMOR_ALLOWED) - return complain_error(aad(sa)->error); + /* Currently rules are treated as coming from the root ns */ + rule->label = aa_label_parse(&root_ns->unconfined->label, rulestr, + gfp, true, false); + if (IS_ERR(rule->label)) { + int err = PTR_ERR(rule->label); + aa_audit_rule_free(rule); + return err; + } + + *vrule = rule; + return 0; +} - return aad(sa)->error; +int aa_audit_rule_known(struct audit_krule *rule) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + + switch (f->type) { + case AUDIT_SUBJ_ROLE: + return 1; + } + } + + return 0; +} + +int aa_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, void *vrule) +{ + struct aa_audit_rule *rule = vrule; + struct aa_label *label; + int found = 0; + + label = prop->apparmor.label; + + if (!label) + return -ENOENT; + + if (aa_label_is_subset(label, rule->label)) + found = 1; + + switch (field) { + case AUDIT_SUBJ_ROLE: + switch (op) { + case Audit_equal: + return found; + case Audit_not_equal: + return !found; + } + } + return 0; } diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 67e347192a55..b9ea6bc45c1a 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,21 +6,17 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/capability.h> #include <linux/errno.h> #include <linux/gfp.h> #include <linux/security.h> +#include <linux/timekeeping.h> #include "include/apparmor.h" #include "include/capability.h" -#include "include/context.h" +#include "include/cred.h" #include "include/policy.h" #include "include/audit.h" @@ -30,20 +27,22 @@ struct aa_sfs_entry aa_sfs_entry_caps[] = { AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), + AA_SFS_FILE_BOOLEAN("extended", 1), { } }; struct audit_cache { - struct aa_profile *profile; - kernel_cap_t caps; + const struct cred *ad_subj_cred; + /* Capabilities go from 0 to CAP_LAST_CAP */ + u64 ktime_ns_expiration[CAP_LAST_CAP+1]; }; static DEFINE_PER_CPU(struct audit_cache, audit_cache); /** * audit_cb - call back for capability components of audit struct - * @ab - audit buffer (NOT NULL) - * @va - audit struct to audit data from (NOT NULL) + * @ab: audit buffer (NOT NULL) + * @va: audit struct to audit data from (NOT NULL) */ static void audit_cb(struct audit_buffer *ab, void *va) { @@ -55,7 +54,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) /** * audit_caps - audit a capability - * @sa: audit data + * @ad: audit data * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested * @error: error code returned by test @@ -63,26 +62,29 @@ static void audit_cb(struct audit_buffer *ab, void *va) * Do auditing of capability and handle, audit/complain/kill modes switching * and duplicate message elimination. * - * Returns: 0 or sa->error on success, error code on failure + * Returns: 0 or ad->error on success, error code on failure */ -static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, +static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile, int cap, int error) { + const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */ + + struct aa_ruleset *rules = profile->label.rules[0]; struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; - aad(sa)->error = error; + ad->error = error; if (likely(!error)) { /* test if auditing is being forced */ if (likely((AUDIT_MODE(profile) != AUDIT_ALL) && - !cap_raised(profile->caps.audit, cap))) + !cap_raised(rules->caps.audit, cap))) return 0; type = AUDIT_APPARMOR_AUDIT; } else if (KILL_MODE(profile) || - cap_raised(profile->caps.kill, cap)) { + cap_raised(rules->caps.kill, cap)) { type = AUDIT_APPARMOR_KILL; - } else if (cap_raised(profile->caps.quiet, cap) && + } else if (cap_raised(rules->caps.quiet, cap) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) { /* quiet auditing */ @@ -91,72 +93,131 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile, /* Do simple duplicate message elimination */ ent = &get_cpu_var(audit_cache); - if (profile == ent->profile && cap_raised(ent->caps, cap)) { + /* If the capability was never raised the timestamp check would also catch that */ + if (ad->subj_cred == ent->ad_subj_cred && ktime_get_ns() <= ent->ktime_ns_expiration[cap]) { put_cpu_var(audit_cache); if (COMPLAIN_MODE(profile)) return complain_error(error); return error; } else { - aa_put_profile(ent->profile); - ent->profile = aa_get_profile(profile); - cap_raise(ent->caps, cap); + put_cred(ent->ad_subj_cred); + ent->ad_subj_cred = get_cred(ad->subj_cred); + ent->ktime_ns_expiration[cap] = ktime_get_ns() + AUDIT_CACHE_TIMEOUT_NS; } put_cpu_var(audit_cache); - return aa_audit(type, profile, sa, audit_cb); + return aa_audit(type, profile, ad, 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 - * @audit: whether an audit record should be generated - * @sa: audit data (MAY BE NULL indicating no auditing) + * @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated + * @ad: audit data (NOT NULL) * * Returns: 0 if allowed else -EPERM */ -static int profile_capable(struct aa_profile *profile, int cap, int audit, - struct common_audit_data *sa) +static int profile_capable(struct aa_profile *profile, int cap, + unsigned int opts, struct apparmor_audit_data *ad) { + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; int error; - if (cap_raised(profile->caps.allow, cap) && - !cap_raised(profile->caps.denied, cap)) + state = RULE_MEDIATES(rules, ad->class); + if (state) { + struct aa_perms perms = { }; + u32 request; + + /* caps broken into 256 x 32 bit permission chunks */ + state = aa_dfa_next(rules->policy->dfa, state, cap >> 5); + request = 1 << (cap & 0x1f); + perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &perms); + + if (opts & CAP_OPT_NOAUDIT) { + if (perms.complain & request) + ad->info = "optional: no audit"; + else + ad = NULL; + } + return aa_check_perms(profile, &perms, request, ad, + audit_cb); + } + + /* fallback to old caps mediation that doesn't support conditionals */ + if (cap_raised(rules->caps.allow, cap) && + !cap_raised(rules->caps.denied, cap)) error = 0; else error = -EPERM; - if (audit == SECURITY_CAP_NOAUDIT) { + if (opts & CAP_OPT_NOAUDIT) { if (!COMPLAIN_MODE(profile)) return error; /* audit the cap request in complain mode but note that it * should be optional. */ - aad(sa)->info = "optional: no audit"; + ad->info = "optional: no audit"; } - return audit_caps(sa, profile, cap, error); + return audit_caps(ad, profile, cap, error); } /** * aa_capable - test permission to use capability + * @subj_cred: cred we are testing capability against * @label: label being tested for capability (NOT NULL) * @cap: capability to be tested - * @audit: whether an audit record should be generated + * @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated * * Look up capability in profile capability set. * * Returns: 0 on success, or else an error code. */ -int aa_capable(struct aa_label *label, int cap, int audit) +int aa_capable(const struct cred *subj_cred, struct aa_label *label, + int cap, unsigned int opts) { struct aa_profile *profile; int error = 0; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE); - sa.u.cap = cap; + ad.subj_cred = subj_cred; + ad.common.u.cap = cap; error = fn_for_each_confined(label, profile, - profile_capable(profile, cap, audit, &sa)); + profile_capable(profile, cap, opts, &ad)); return error; } + +kernel_cap_t aa_profile_capget(struct aa_profile *profile) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + + state = RULE_MEDIATES(rules, AA_CLASS_CAP); + if (state) { + kernel_cap_t caps = CAP_EMPTY_SET; + int i; + + /* caps broken into up to 256, 32 bit permission chunks */ + for (i = 0; i < (CAP_LAST_CAP >> 5); i++) { + struct aa_perms perms = { }; + aa_state_t tmp; + + tmp = aa_dfa_next(rules->policy->dfa, state, i); + perms = *aa_lookup_perms(rules->policy, tmp); + aa_apply_modes_to_perms(profile, &perms); + caps.val |= ((u64)(perms.allow)) << (i * 5); + caps.val |= ((u64)(perms.complain)) << (i * 5); + } + return caps; + } + + /* fallback to old caps */ + if (COMPLAIN_MODE(profile)) + return CAP_FULL_SET; + + return rules->caps.allow; +} diff --git a/security/apparmor/context.c b/security/apparmor/context.c deleted file mode 100644 index c95f1ac6190b..000000000000 --- a/security/apparmor/context.c +++ /dev/null @@ -1,228 +0,0 @@ -/* - * AppArmor security module - * - * This file contains AppArmor functions used to manipulate object security - * contexts. - * - * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * - * AppArmor sets confinement on every task, via the the aa_task_ctx and - * the aa_task_ctx.label, both of which are required and are not allowed - * to be NULL. The aa_task_ctx is not reference counted and is unique - * to each cred (which is reference count). The label pointed to by - * the task_ctx is reference counted. - * - * TODO - * If a task uses change_hat it currently does not return to the old - * cred or task context but instead creates a new one. Ideally the task - * should return to the previous cred if it has not been modified. - * - */ - -#include "include/context.h" -#include "include/policy.h" - -/** - * aa_alloc_task_context - allocate a new task_ctx - * @flags: gfp flags for allocation - * - * Returns: allocated buffer or NULL on failure - */ -struct aa_task_ctx *aa_alloc_task_context(gfp_t flags) -{ - return kzalloc(sizeof(struct aa_task_ctx), flags); -} - -/** - * aa_free_task_context - free a task_ctx - * @ctx: task_ctx to free (MAYBE NULL) - */ -void aa_free_task_context(struct aa_task_ctx *ctx) -{ - if (ctx) { - aa_put_label(ctx->label); - aa_put_label(ctx->previous); - aa_put_label(ctx->onexec); - - kzfree(ctx); - } -} - -/** - * aa_dup_task_context - duplicate a task context, incrementing reference counts - * @new: a blank task context (NOT NULL) - * @old: the task context to copy (NOT NULL) - */ -void aa_dup_task_context(struct aa_task_ctx *new, const struct aa_task_ctx *old) -{ - *new = *old; - aa_get_label(new->label); - aa_get_label(new->previous); - aa_get_label(new->onexec); -} - -/** - * 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_label(struct aa_label *label) -{ - struct aa_task_ctx *ctx = current_ctx(); - struct cred *new; - AA_BUG(!label); - - if (ctx->label == label) - return 0; - - if (current_cred() != current_real_cred()) - return -EBUSY; - - new = prepare_creds(); - if (!new) - return -ENOMEM; - - ctx = cred_ctx(new); - if (unconfined(label) || (labels_ns(ctx->label) != labels_ns(label))) - /* if switching to unconfined or a different label namespace - * clear out context state - */ - aa_clear_task_ctx_trans(ctx); - - /* - * be careful switching ctx->profile, when racing replacement it - * is possible that ctx->profile->proxy->profile is the reference - * keeping @profile valid, so make sure to get its reference before - * dropping the reference on ctx->profile - */ - aa_get_label(label); - aa_put_label(ctx->label); - ctx->label = label; - - commit_creds(new); - return 0; -} - -/** - * aa_set_current_onexec - set the tasks change_profile to happen onexec - * @label: system label to set at exec (MAYBE NULL to clear value) - * @stack: whether stacking should be done - * Returns: 0 or error on failure - */ -int aa_set_current_onexec(struct aa_label *label, bool stack) -{ - struct aa_task_ctx *ctx; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - - ctx = cred_ctx(new); - aa_get_label(label); - aa_clear_task_ctx_trans(ctx); - ctx->onexec = label; - ctx->token = stack; - - commit_creds(new); - return 0; -} - -/** - * aa_set_current_hat - set the current tasks hat - * @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 - * validate the token to match. - * - * Returns: 0 or error on failure - */ -int aa_set_current_hat(struct aa_label *label, u64 token) -{ - struct aa_task_ctx *ctx; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - AA_BUG(!label); - - ctx = cred_ctx(new); - if (!ctx->previous) { - /* transfer refcount */ - ctx->previous = ctx->label; - ctx->token = token; - } else if (ctx->token == token) { - aa_put_label(ctx->label); - } else { - /* previous_profile && ctx->token != token */ - abort_creds(new); - return -EACCES; - } - ctx->label = aa_get_newest_label(label); - /* clear exec on switching context */ - aa_put_label(ctx->onexec); - ctx->onexec = NULL; - - commit_creds(new); - return 0; -} - -/** - * 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 label. The token - * must match the stored token value. - * - * Returns: 0 or error of failure - */ -int aa_restore_previous_label(u64 token) -{ - struct aa_task_ctx *ctx; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - - ctx = cred_ctx(new); - if (ctx->token != token) { - abort_creds(new); - return -EACCES; - } - /* ignore restores when there is no saved label */ - if (!ctx->previous) { - abort_creds(new); - return 0; - } - - aa_put_label(ctx->label); - ctx->label = aa_get_newest_label(ctx->previous); - AA_BUG(!ctx->label); - /* clear exec && prev information when restoring to previous context */ - aa_clear_task_ctx_trans(ctx); - - commit_creds(new); - return 0; -} diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c index 136f2a047836..d8a7bde94d79 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,125 +6,56 @@ * * Copyright 2013 Canonical Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * * Fns to provide a checksum of policy that has been loaded this can be * compared to userspace policy compiles to check loaded policy is what * it should be. */ -#include <crypto/hash.h> +#include <crypto/sha2.h> #include "include/apparmor.h" #include "include/crypto.h" -static unsigned int apparmor_hash_size; - -static struct crypto_shash *apparmor_tfm; - unsigned int aa_hash_size(void) { - return apparmor_hash_size; + return SHA256_DIGEST_SIZE; } char *aa_calc_hash(void *data, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); - char *hash = NULL; - int error = -ENOMEM; + char *hash; - if (!apparmor_tfm) - return NULL; - - hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!hash) - goto fail; - - desc->tfm = apparmor_tfm; - desc->flags = 0; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) data, len); - if (error) - goto fail; - error = crypto_shash_final(desc, hash); - if (error) - goto fail; + return ERR_PTR(-ENOMEM); + sha256(data, len, hash); return hash; - -fail: - kfree(hash); - - return ERR_PTR(error); } int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); - int error = -ENOMEM; + struct sha256_ctx sctx; __le32 le32_version = cpu_to_le32(version); if (!aa_g_hash_policy) return 0; - if (!apparmor_tfm) - return 0; - - profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!profile->hash) - goto fail; - - desc->tfm = apparmor_tfm; - desc->flags = 0; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) &le32_version, 4); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) start, len); - if (error) - goto fail; - error = crypto_shash_final(desc, profile->hash); - if (error) - goto fail; + return -ENOMEM; + sha256_init(&sctx); + sha256_update(&sctx, (u8 *)&le32_version, 4); + sha256_update(&sctx, (u8 *)start, len); + sha256_final(&sctx, profile->hash); return 0; - -fail: - kfree(profile->hash); - profile->hash = NULL; - - return error; } -static int __init init_profile_hash(void) +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"); - + if (apparmor_initialized) + aa_info_message("AppArmor sha256 policy hashing enabled"); return 0; } - -late_initcall(init_profile_hash); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d0594446ae3f..267da82afb14 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,24 +6,20 @@ * * Copyright (C) 2002-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/errno.h> -#include <linux/fdtable.h> +#include <linux/fs.h> #include <linux/file.h> #include <linux/mount.h> #include <linux/syscalls.h> -#include <linux/tracehook.h> #include <linux/personality.h> +#include <linux/xattr.h> +#include <linux/user_namespace.h> #include "include/audit.h" #include "include/apparmorfs.h" -#include "include/context.h" +#include "include/cred.h" #include "include/domain.h" #include "include/file.h" #include "include/ipc.h" @@ -31,26 +28,15 @@ #include "include/policy.h" #include "include/policy_ns.h" -/** - * aa_free_domain_entries - free entries in a domain table - * @domain: the domain table to free (MAYBE NULL) - */ -void aa_free_domain_entries(struct aa_domain *domain) -{ - int i; - if (domain) { - if (!domain->table) - return; - - for (i = 0; i < domain->size; i++) - kzfree(domain->table[i]); - kzfree(domain->table); - domain->table = NULL; - } -} +static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments"; +static const char * const CONFLICTING_ATTACH_STR_IX = + "conflicting profile attachments - ix fallback"; +static const char * const CONFLICTING_ATTACH_STR_UX = + "conflicting profile attachments - ux fallback"; /** * may_change_ptraced_domain - check if can change profile on ptraced task + * @to_cred: cred of task changing domain * @to_label: profile to change to (NOT NULL) * @info: message if there is an error * @@ -59,28 +45,34 @@ void aa_free_domain_entries(struct aa_domain *domain) * * Returns: %0 or error if change not allowed */ -static int may_change_ptraced_domain(struct aa_label *to_label, +static int may_change_ptraced_domain(const struct cred *to_cred, + struct aa_label *to_label, const char **info) { struct task_struct *tracer; struct aa_label *tracerl = NULL; + const struct cred *tracer_cred = NULL; + int error = 0; rcu_read_lock(); tracer = ptrace_parent(current); - if (tracer) + if (tracer) { /* released below */ tracerl = aa_get_task_label(tracer); - + tracer_cred = get_task_cred(tracer); + } /* not ptraced */ if (!tracer || unconfined(tracerl)) goto out; - error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH); + error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label, + PTRACE_MODE_ATTACH); out: rcu_read_unlock(); aa_put_label(tracerl); + put_cred(tracer_cred); if (error) *info = "ptrace prevents transition"; @@ -90,30 +82,31 @@ out: /**** TODO: dedup to aa_label_match - needs perm and dfa, merging * specifically this is an exact copy of aa_label_match except * aa_compute_perms is replaced with aa_compute_fperms - * and policy.dfa with file.dfa + * and policy->dfa with file->dfa ****/ /* match a profile and its associated ns component if needed * Assumes visibility test has already been done. * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline unsigned int match_component(struct aa_profile *profile, - struct aa_profile *tp, - bool stack, unsigned int state) +static inline aa_state_t match_component(struct aa_profile *profile, + struct aa_profile *tp, + bool stack, aa_state_t state) { + struct aa_ruleset *rules = profile->label.rules[0]; const char *ns_name; if (stack) - state = aa_dfa_match(profile->file.dfa, state, "&"); + state = aa_dfa_match(rules->file->dfa, state, "&"); if (profile->ns == tp->ns) - return aa_dfa_match(profile->file.dfa, state, tp->base.hname); + return aa_dfa_match(rules->file->dfa, state, tp->base.hname); /* try matching with namespace name and then profile */ ns_name = aa_ns_name(profile->ns, tp->ns, true); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - state = aa_dfa_match(profile->file.dfa, state, ns_name); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - return aa_dfa_match(profile->file.dfa, state, tp->base.hname); + state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); + state = aa_dfa_match(rules->file->dfa, state, ns_name); + state = aa_dfa_match_len(rules->file->dfa, state, ":", 1); + return aa_dfa_match(rules->file->dfa, state, tp->base.hname); } /** @@ -121,7 +114,7 @@ static inline unsigned int match_component(struct aa_profile *profile, * @profile: profile to find perms for * @label: label to check access permissions for * @stack: whether this is a stacking request - * @start: state to start match in + * @state: state to start match in * @subns: whether to do permission checks on components in a subns * @request: permissions to request * @perms: perms struct to set @@ -134,9 +127,10 @@ static inline unsigned int match_component(struct aa_profile *profile, */ static int label_compound_match(struct aa_profile *profile, struct aa_label *label, bool stack, - unsigned int state, bool subns, u32 request, + aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; @@ -159,12 +153,13 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(profile->file.dfa, state, "//&"); + state = aa_dfa_match(rules->file->dfa, state, "//&"); state = match_component(profile, tp, false, state); if (!state) goto fail; } - *perms = aa_compute_fperms(profile->file.dfa, state, &cond); + *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -194,14 +189,15 @@ fail: */ static int label_components_match(struct aa_profile *profile, struct aa_label *label, bool stack, - unsigned int start, bool subns, u32 request, + aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct aa_perms tmp; struct path_cond cond = { }; - unsigned int state = 0; + aa_state_t state = 0; /* find first subcomponent to test */ label_for_each(i, label, tp) { @@ -217,7 +213,8 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -226,7 +223,8 @@ next: state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = aa_compute_fperms(profile->file.dfa, state, &cond); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -254,7 +252,7 @@ fail: * Returns: the state the match finished in, may be the none matching state */ static int label_match(struct aa_profile *profile, struct aa_label *label, - bool stack, unsigned int state, bool subns, u32 request, + bool stack, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { int error; @@ -279,6 +277,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, * @stack: whether this is a stacking request * @request: requested perms * @start: state to start matching in + * @perms: Returns computed perms (NOT NULL) * * * Returns: permission set @@ -288,7 +287,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label, */ static int change_profile_perms(struct aa_profile *profile, struct aa_label *target, bool stack, - u32 request, unsigned int start, + u32 request, aa_state_t start, struct aa_perms *perms) { if (profile_unconfined(profile)) { @@ -302,9 +301,81 @@ static int change_profile_perms(struct aa_profile *profile, } /** - * __attach_match_ - find an attachment match - * @name - to match against (NOT NULL) - * @head - profile list to walk (NOT NULL) + * aa_xattrs_match - check whether a file matches the xattrs defined in profile + * @bprm: binprm struct for the process to validate + * @profile: profile to match against (NOT NULL) + * @state: state to start match in + * + * Returns: number of extended attributes that matched, or < 0 on error + */ +static int aa_xattrs_match(const struct linux_binprm *bprm, + struct aa_profile *profile, aa_state_t state) +{ + int i; + struct dentry *d; + char *value = NULL; + struct aa_attachment *attach = &profile->attach; + int size, value_size = 0, ret = attach->xattr_count; + + if (!bprm || !attach->xattr_count) + return 0; + might_sleep(); + + /* transition from exec match to xattr set */ + state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); + d = bprm->file->f_path.dentry; + + for (i = 0; i < attach->xattr_count; i++) { + size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], + &value, value_size, GFP_KERNEL); + if (size >= 0) { + struct aa_perms *perms; + + /* + * Check the xattr presence before value. This ensure + * that not present xattr can be distinguished from a 0 + * length value or rule that matches any value + */ + state = aa_dfa_null_transition(attach->xmatch->dfa, + state); + /* Check xattr value */ + state = aa_dfa_match_len(attach->xmatch->dfa, state, + value, size); + perms = aa_lookup_perms(attach->xmatch, state); + if (!(perms->allow & MAY_EXEC)) { + ret = -EINVAL; + goto out; + } + } + /* transition to next element */ + state = aa_dfa_outofband_transition(attach->xmatch->dfa, state); + if (size < 0) { + /* + * No xattr match, so verify if transition to + * next element was valid. IFF so the xattr + * was optional. + */ + if (!state) { + ret = -EINVAL; + goto out; + } + /* don't count missing optional xattr as matched */ + ret--; + } + } + +out: + kfree(value); + return ret; +} + +/** + * find_attach - do attachment search for unconfined processes + * @bprm: binprm structure of transitioning task + * @ns: the current namespace (NOT NULL) + * @head: profile list to walk (NOT NULL) + * @name: to match against (NOT NULL) + * @info: info message if there was an error (NOT NULL) * * Do a linear search on the profiles in the list. There is a matching * preference where an exact match is preferred over a name which uses @@ -313,52 +384,120 @@ static int change_profile_perms(struct aa_profile *profile, * * Requires: @head not be shared or have appropriate locks held * - * Returns: profile or NULL if no match found + * Returns: label or NULL if no match found */ -static struct aa_profile *__attach_match(const char *name, - struct list_head *head) +static struct aa_label *find_attach(const struct linux_binprm *bprm, + struct aa_ns *ns, struct list_head *head, + const char *name, const char **info) { - int len = 0; + int candidate_len = 0, candidate_xattrs = 0; + bool conflict = false; struct aa_profile *profile, *candidate = NULL; + AA_BUG(!name); + AA_BUG(!head); + + rcu_read_lock(); +restart: list_for_each_entry_rcu(profile, head, base.list) { - if (profile->label.flags & FLAG_NULL) + struct aa_attachment *attach = &profile->attach; + + if (profile->label.flags & FLAG_NULL && + &profile->label == ns_unconfined(profile->ns)) continue; - if (profile->xmatch && profile->xmatch_len > len) { - unsigned int state = aa_dfa_match(profile->xmatch, - DFA_START, name); - u32 perm = dfa_user_allow(profile->xmatch, state); + + /* Find the "best" matching profile. Profiles must + * match the path and extended attributes (if any) + * associated with the file. A more specific path + * match will be preferred over a less specific one, + * and a match with more matching extended attributes + * will be preferred over one with fewer. If the best + * match has both the same level of path specificity + * and the same number of matching extended attributes + * as another profile, signal a conflict and refuse to + * match. + */ + if (attach->xmatch->dfa) { + unsigned int count; + aa_state_t state; + struct aa_perms *perms; + + state = aa_dfa_leftmatch(attach->xmatch->dfa, + attach->xmatch->start[AA_CLASS_XMATCH], + name, &count); + perms = aa_lookup_perms(attach->xmatch, state); /* any accepting state means a valid match. */ - if (perm & MAY_EXEC) { + if (perms->allow & MAY_EXEC) { + int ret = 0; + + if (count < candidate_len) + continue; + + if (bprm && attach->xattr_count) { + long rev = READ_ONCE(ns->revision); + + if (!aa_get_profile_not0(profile)) + goto restart; + rcu_read_unlock(); + ret = aa_xattrs_match(bprm, profile, + state); + rcu_read_lock(); + aa_put_profile(profile); + if (rev != + READ_ONCE(ns->revision)) + /* policy changed */ + goto restart; + /* + * Fail matching if the xattrs don't + * match + */ + if (ret < 0) + continue; + } + /* + * TODO: allow for more flexible best match + * + * The new match isn't more specific + * than the current best match + */ + if (count == candidate_len && + ret <= candidate_xattrs) { + /* Match is equivalent, so conflict */ + if (ret == candidate_xattrs) + conflict = true; + continue; + } + + /* Either the same length with more matching + * xattrs, or a longer match + */ candidate = profile; - len = profile->xmatch_len; + candidate_len = max(count, attach->xmatch_len); + candidate_xattrs = ret; + conflict = false; } - } else if (!strcmp(profile->base.name, name)) - /* exact non-re match, no more searching required */ - return profile; + } else if (!strcmp(profile->base.name, name)) { + /* + * old exact non-re match, without conditionals such + * as xattrs. no more searching required + */ + candidate = profile; + goto out; + } } - return candidate; -} - -/** - * find_attach - do attachment search for unconfined processes - * @ns: the current namespace (NOT NULL) - * @list: list to search (NOT NULL) - * @name: the executable name to match against (NOT NULL) - * - * Returns: label or NULL if no match found - */ -static struct aa_label *find_attach(struct aa_ns *ns, struct list_head *list, - const char *name) -{ - struct aa_profile *profile; + if (!candidate || conflict) { + if (conflict) + *info = CONFLICTING_ATTACH_STR; + rcu_read_unlock(); + return NULL; + } - rcu_read_lock(); - profile = aa_get_profile(__attach_match(name, list)); +out: + candidate = aa_get_newest_profile(candidate); rcu_read_unlock(); - return profile ? &profile->label : NULL; + return &candidate->label; } static const char *next_name(int xtype, const char *name) @@ -373,13 +512,16 @@ static const char *next_name(int xtype, const char *name) * @name: returns: name tested to find label (NOT NULL) * * Returns: refcounted label, or NULL on failure (MAYBE NULL) + * @name will always be set with the last name tried */ -static struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, - const char **name) +struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name) { + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; + const char *next; AA_BUG(!name); @@ -387,47 +529,54 @@ static struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, /* TODO: move lookup parsing to unpack time so this is a straight * index into the resultant label */ - for (*name = profile->file.trans.table[index]; !label && *name; - *name = next_name(xtype, *name)) { + for (next = rules->file->trans.table[index]; next; + next = next_name(xtype, next)) { + const char *lookup = (*next == '&') ? next + 1 : next; + *name = next; if (xindex & AA_X_CHILD) { - struct aa_profile *new_profile; - /* release by caller */ - new_profile = aa_find_child(profile, *name); - if (new_profile) - label = &new_profile->label; + /* TODO: switich to parse to get stack of child */ + struct aa_profile *new = aa_find_child(profile, lookup); + + if (new) + /* release by caller */ + return &new->label; continue; } - label = aa_label_parse(&profile->label, *name, GFP_ATOMIC, + label = aa_label_parse(&profile->label, lookup, GFP_KERNEL, true, false); - if (IS_ERR(label)) - label = NULL; + if (!IS_ERR_OR_NULL(label)) + /* release by caller */ + return label; } - /* released by caller */ - - return label; + return NULL; } /** * x_to_label - get target label for a given xindex * @profile: current profile (NOT NULL) + * @bprm: binprm structure of transitioning task * @name: name to lookup (NOT NULL) * @xindex: index into x transition table * @lookupname: returns: name used in lookup if one was specified (NOT NULL) + * @info: info message if there was an error (NOT NULL) * * find label for a transition index * * Returns: refcounted label or NULL if not found available */ static struct aa_label *x_to_label(struct aa_profile *profile, + const struct linux_binprm *bprm, const char *name, u32 xindex, const char **lookupname, const char **info) { struct aa_label *new = NULL; + struct aa_label *stack = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; - const char *stack = NULL; + /* Used for info checks during fallback handling */ + const char *old_info = NULL; switch (xtype) { case AA_X_NONE: @@ -436,38 +585,60 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; case AA_X_TABLE: /* TODO: fix when perm mapping done at unload */ - stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK]; - if (*stack != '&') { - /* released by caller */ - new = x_table_lookup(profile, xindex, lookupname); - stack = NULL; + /* released by caller + * if null for both stack and direct want to try fallback + */ + new = x_table_lookup(profile, xindex, lookupname); + if (!new || **lookupname != '&') break; - } - /* fall through to X_NAME */ + stack = new; + new = NULL; + fallthrough; /* to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new = find_attach(ns, &profile->base.profiles, - name); + new = find_attach(bprm, ns, &profile->base.profiles, + name, info); else /* released by caller */ - new = find_attach(ns, &ns->base.profiles, - name); + new = find_attach(bprm, ns, &ns->base.profiles, + name, info); *lookupname = name; break; } + /* fallback transition check */ if (!new) { if (xindex & AA_X_INHERIT) { /* (p|c|n)ix - don't change profile but do * use the newest version */ - *info = "ix fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_IX; + } else { + old_info = *info; + *info = "ix fallback"; + } /* no profile && no error */ new = aa_get_newest_label(&profile->label); } else if (xindex & AA_X_UNCONFINED) { new = aa_get_newest_label(ns_unconfined(profile->ns)); - *info = "ux fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_UX; + } else { + old_info = *info; + *info = "ux fallback"; + } + } + /* We set old_info on the code paths above where overwriting + * could have happened, so now check if info was set by + * find_attach as well (i.e. whether we actually overwrote) + * and warn accordingly. + */ + if (old_info && old_info != CONFLICTING_ATTACH_STR) { + pr_warn_ratelimited( + "AppArmor: find_attach (from profile %s) audit info \"%s\" dropped", + profile->base.hname, old_info); } } @@ -475,24 +646,27 @@ static struct aa_label *x_to_label(struct aa_profile *profile, /* base the stack on post domain transition */ struct aa_label *base = new; - new = aa_label_parse(base, stack, GFP_ATOMIC, true, false); - if (IS_ERR(new)) - new = NULL; + new = aa_label_merge(base, stack, GFP_KERNEL); + /* null on error */ aa_put_label(base); } + aa_put_label(stack); /* released by caller */ return new; } -static struct aa_label *profile_transition(struct aa_profile *profile, +static struct aa_label *profile_transition(const struct cred *subj_cred, + struct aa_profile *profile, const struct linux_binprm *bprm, char *buffer, struct path_cond *cond, bool *secure_exec) { + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *new = NULL; + struct aa_profile *new_profile = NULL; const char *info = NULL, *name = NULL, *target = NULL; - unsigned int state = profile->file.start; + aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; bool nonewprivs = false; int error = 0; @@ -506,7 +680,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; new = aa_get_newest_label(&profile->label); } @@ -515,35 +689,68 @@ static struct aa_label *profile_transition(struct aa_profile *profile, } if (profile_unconfined(profile)) { - new = find_attach(profile->ns, &profile->ns->base.profiles, - name); + new = find_attach(bprm, profile->ns, + &profile->ns->base.profiles, name, &info); + /* info set -> something unusual that we should report + * Currently this is only conflicting attachments, but other + * infos added in the future should also be logged by default + * and only excluded on a case-by-case basis + */ + if (info) { + /* Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + perms.audit |= MAY_EXEC; + perms.allow |= MAY_EXEC; + /* Don't cause error if auditing fails */ + (void) aa_audit_file(subj_cred, profile, &perms, + OP_EXEC, MAY_EXEC, name, target, new, cond->uid, + info, error); + } if (new) { - AA_DEBUG("unconfined attached to new label"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label"); return new; } - AA_DEBUG("unconfined exec no attachment"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment"); return aa_get_newest_label(&profile->label); } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, state, name, cond, &perms); + state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new = x_to_label(profile, name, perms.xindex, &target, &info); + new = x_to_label(profile, bprm, name, perms.xindex, &target, + &info); if (new && new->proxy == profile->label.proxy && info) { + /* Force audit on conflicting attachment fallback + * Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + if (info == CONFLICTING_ATTACH_STR_IX + || info == CONFLICTING_ATTACH_STR_UX) + perms.audit |= MAY_EXEC; /* hack ix fallback - improve how this is detected */ goto audit; } else if (!new) { - error = -EACCES; + if (info) { + pr_warn_ratelimited( + "AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition", + __func__, profile->base.hname, info); + } info = "profile transition not found"; - /* remove MAY_EXEC to audit as failure */ + /* remove MAY_EXEC to audit as failure or complaint */ perms.allow &= ~MAY_EXEC; + if (COMPLAIN_MODE(profile)) { + /* create null profile instead of failing */ + goto create_learning_profile; + } + error = -EACCES; } } else if (COMPLAIN_MODE(profile)) { +create_learning_profile: /* no exec permission - learning mode */ - struct aa_profile *new_profile = aa_new_null_profile(profile, - false, name, - GFP_ATOMIC); + new_profile = aa_new_learning_profile(profile, false, name, + GFP_KERNEL); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; @@ -559,35 +766,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile, if (!new) goto audit; - /* Policy has specified a domain transitions. if no_new_privs and - * confined and not transitioning to the current domain fail. - * - * NOTE: Domain transitions from unconfined and to stritly stacked - * subsets are allowed even when no_new_privs is set because this - * aways results in a further reduction of permissions. - */ - if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && - !profile_unconfined(profile) && - !aa_label_is_subset(new, &profile->label)) { - error = -EPERM; - info = "no new privs"; - nonewprivs = true; - perms.allow &= ~MAY_EXEC; - goto audit; - } if (!(perms.xindex & AA_X_UNSAFE)) { if (DEBUG_ON) { - dbg_printk("apparmor: scrubbing environment variables" - " for %s profile=", name); - aa_label_printk(new, GFP_ATOMIC); + dbg_printk("apparmor: setting AT_SECURE for %s profile=", + name); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } *secure_exec = true; } audit: - aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new, + aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name, + target, new, cond->uid, info, error); if (!new || nonewprivs) { aa_put_label(new); @@ -597,12 +789,14 @@ audit: return new; } -static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, +static int profile_onexec(const struct cred *subj_cred, + struct aa_profile *profile, struct aa_label *onexec, bool stack, const struct linux_binprm *bprm, char *buffer, struct path_cond *cond, bool *secure_exec) { - unsigned int state = profile->file.start; + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; int error = -EACCES; @@ -616,7 +810,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, /* change_profile on exec already granted */ /* * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results + * even when no_new_privs is set because this always results * in a further reduction of permissions. */ return 0; @@ -627,7 +821,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; } xname = bprm->filename; @@ -635,7 +829,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, } /* find exec permissions for name */ - state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); + state = aa_str_perms(rules->file, state, xname, cond, &perms); if (!(perms.allow & AA_MAY_ONEXEC)) { info = "no change_onexec valid for executable"; goto audit; @@ -644,47 +838,34 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, * onexec permission is linked to exec with a standard pairing * exec\0change_profile */ - state = aa_dfa_null_transition(profile->file.dfa, state); + state = aa_dfa_null_transition(rules->file->dfa, state); error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC, state, &perms); if (error) { perms.allow &= ~AA_MAY_ONEXEC; goto audit; } - /* Policy has specified a domain transitions. if no_new_privs and - * confined and not transitioning to the current domain fail. - * - * NOTE: Domain transitions from unconfined and to stritly stacked - * subsets are allowed even when no_new_privs is set because this - * aways results in a further reduction of permissions. - */ - if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && - !profile_unconfined(profile) && - !aa_label_is_subset(onexec, &profile->label)) { - error = -EPERM; - info = "no new privs"; - perms.allow &= ~AA_MAY_ONEXEC; - goto audit; - } if (!(perms.xindex & AA_X_UNSAFE)) { if (DEBUG_ON) { - dbg_printk("apparmor: scrubbing environment " - "variables for %s label=", xname); - aa_label_printk(onexec, GFP_ATOMIC); + dbg_printk("apparmor: setting AT_SECURE for %s label=", + xname); + aa_label_printk(onexec, GFP_KERNEL); dbg_printk("\n"); } *secure_exec = true; } audit: - return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname, + return aa_audit_file(subj_cred, profile, &perms, OP_EXEC, + AA_MAY_ONEXEC, xname, NULL, onexec, cond->uid, info, error); } /* ensure none ns domain transitions are correctly applied with onexec */ -static struct aa_label *handle_onexec(struct aa_label *label, +static struct aa_label *handle_onexec(const struct cred *subj_cred, + struct aa_label *label, struct aa_label *onexec, bool stack, const struct linux_binprm *bprm, char *buffer, struct path_cond *cond, @@ -699,37 +880,26 @@ static struct aa_label *handle_onexec(struct aa_label *label, AA_BUG(!bprm); AA_BUG(!buffer); - if (!stack) { - error = fn_for_each_in_ns(label, profile, - profile_onexec(profile, onexec, stack, - bprm, buffer, cond, unsafe)); - if (error) - return ERR_PTR(error); - new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, - aa_get_newest_label(onexec), - profile_transition(profile, bprm, buffer, - cond, unsafe)); - - } else { - /* TODO: determine how much we want to losen this */ - error = fn_for_each_in_ns(label, profile, - profile_onexec(profile, onexec, stack, bprm, - buffer, cond, unsafe)); - if (error) - return ERR_PTR(error); - new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, - aa_label_merge(&profile->label, onexec, - GFP_ATOMIC), - profile_transition(profile, bprm, buffer, - cond, unsafe)); - } + /* TODO: determine how much we want to loosen this */ + error = fn_for_each_in_ns(label, profile, + profile_onexec(subj_cred, profile, onexec, stack, + bprm, buffer, cond, unsafe)); + if (error) + return ERR_PTR(error); + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + stack ? aa_label_merge(&profile->label, onexec, + GFP_KERNEL) + : aa_get_newest_label(onexec), + profile_transition(subj_cred, profile, bprm, + buffer, cond, unsafe)); if (new) return new; /* TODO: get rid of GLOBAL_ROOT_UID */ error = fn_for_each_in_ns(label, profile, - aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, + aa_audit_file(subj_cred, profile, &nullperms, + OP_CHANGE_ONEXEC, AA_MAY_ONEXEC, bprm->filename, NULL, onexec, GLOBAL_ROOT_UID, "failed to build target label", -ENOMEM)); @@ -737,44 +907,63 @@ static struct aa_label *handle_onexec(struct aa_label *label, } /** - * apparmor_bprm_set_creds - set the new creds on the bprm struct + * apparmor_bprm_creds_for_exec - Update the new creds on the bprm struct * @bprm: binprm for the exec (NOT NULL) * * Returns: %0 or error on failure * * TODO: once the other paths are done see if we can't refactor into a fn */ -int apparmor_bprm_set_creds(struct linux_binprm *bprm) +int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) { struct aa_task_ctx *ctx; struct aa_label *label, *new = NULL; + const struct cred *subj_cred; struct aa_profile *profile; char *buffer = NULL; const char *info = NULL; int error = 0; bool unsafe = false; + vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file), + file_inode(bprm->file)); struct path_cond cond = { - file_inode(bprm->file)->i_uid, + vfsuid_into_kuid(vfsuid), file_inode(bprm->file)->i_mode }; - if (bprm->cred_prepared) - return 0; - - ctx = cred_ctx(bprm->cred); + subj_cred = current_cred(); + ctx = task_ctx(current); + AA_BUG(!cred_label(bprm->cred)); AA_BUG(!ctx); - label = aa_get_newest_label(ctx->label); + label = aa_get_newest_label(cred_label(bprm->cred)); + + /* + * Detect no new privs being set, and store the label it + * occurred under. Ideally this would happen when nnp + * is set but there isn't a good way to do that yet. + * + * Testing for unconfined must be done before the subset test + */ + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && + !ctx->nnp) + ctx->nnp = aa_get_label(label); /* buffer freed below, name is pointer into buffer */ - get_buffers(buffer); + buffer = aa_get_buffer(false); + if (!buffer) { + error = -ENOMEM; + goto done; + } + /* Test for onexec first as onexec override other x transitions. */ if (ctx->onexec) - new = handle_onexec(label, ctx->onexec, ctx->token, + new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token, bprm, buffer, &cond, &unsafe); else - new = fn_label_build(label, profile, GFP_ATOMIC, - profile_transition(profile, bprm, buffer, + new = fn_label_build(label, profile, GFP_KERNEL, + profile_transition(subj_cred, profile, bprm, + buffer, &cond, &unsafe)); AA_BUG(!new); @@ -786,7 +975,21 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) goto done; } - /* TODO: Add ns level no_new_privs subset test */ + /* Policy has specified a domain transitions. If no_new_privs and + * confined ensure the transition is to confinement that is subset + * of the confinement when the task entered no new privs. + * + * NOTE: Domain transitions from unconfined and to stacked + * subsets are allowed even when no_new_privs is set because this + * always results in a further reduction of permissions. + */ + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && + !unconfined(label) && + !aa_label_is_unconfined_subset(new, ctx->nnp)) { + error = -EPERM; + info = "no new privs"; + goto audit; + } if (bprm->unsafe & LSM_UNSAFE_SHARE) { /* FIXME: currently don't mediate shared state */ @@ -795,71 +998,51 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { /* TODO: test needs to be profile of label to new */ - error = may_change_ptraced_domain(new, &info); + error = may_change_ptraced_domain(bprm->cred, new, &info); if (error) goto audit; } if (unsafe) { if (DEBUG_ON) { - dbg_printk("scrubbing environment variables for %s " - "label=", bprm->filename); - aa_label_printk(new, GFP_ATOMIC); + dbg_printk("setting AT_SECURE for %s label=", + bprm->filename); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } - bprm->unsafe |= AA_SECURE_X_NEEDED; + bprm->secureexec = 1; } if (label->proxy != new->proxy) { /* when transitioning clear unsafe personality bits */ if (DEBUG_ON) { - dbg_printk("apparmor: clearing unsafe personality " - "bits. %s label=", bprm->filename); - aa_label_printk(new, GFP_ATOMIC); + dbg_printk("apparmor: clearing unsafe personality bits. %s label=", + bprm->filename); + aa_label_printk(new, GFP_KERNEL); dbg_printk("\n"); } bprm->per_clear |= PER_CLEAR_ON_SETID; } - aa_put_label(ctx->label); - /* transfer reference, released when ctx is freed */ - ctx->label = new; + aa_put_label(cred_label(bprm->cred)); + /* transfer reference, released when cred is freed */ + set_cred_label(bprm->cred, new); done: - /* clear out temporary/transitional state from the context */ - aa_clear_task_ctx_trans(ctx); - aa_put_label(label); - put_buffers(buffer); + aa_put_buffer(buffer); return error; audit: error = fn_for_each(label, profile, - aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, + aa_audit_file(current_cred(), profile, &nullperms, + OP_EXEC, MAY_EXEC, bprm->filename, NULL, new, - file_inode(bprm->file)->i_uid, info, - error)); + vfsuid_into_kuid(vfsuid), info, error)); aa_put_label(new); goto done; } -/** - * apparmor_bprm_secureexec - determine if secureexec is needed - * @bprm: binprm for exec (NOT NULL) - * - * Returns: %1 if secureexec is needed else %0 - */ -int apparmor_bprm_secureexec(struct linux_binprm *bprm) -{ - /* the decision to use secure exec is computed in set_creds - * and stored in bprm->unsafe. - */ - if (bprm->unsafe & AA_SECURE_X_NEEDED) - return 1; - - return 0; -} - /* * Functions for self directed profile change */ @@ -869,7 +1052,8 @@ int apparmor_bprm_secureexec(struct linux_binprm *bprm) * * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL */ -static struct aa_label *build_change_hat(struct aa_profile *profile, +static struct aa_label *build_change_hat(const struct cred *subj_cred, + struct aa_profile *profile, const char *name, bool sibling) { struct aa_profile *root, *hat = NULL; @@ -890,8 +1074,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile, if (!hat) { error = -ENOENT; if (COMPLAIN_MODE(profile)) { - hat = aa_new_null_profile(profile, true, name, - GFP_KERNEL); + hat = aa_new_learning_profile(profile, true, name, + GFP_KERNEL); if (!hat) { info = "failed null profile create"; error = -ENOMEM; @@ -901,9 +1085,10 @@ static struct aa_label *build_change_hat(struct aa_profile *profile, aa_put_profile(root); audit: - aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, + aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, name, hat ? hat->base.hname : NULL, - hat ? &hat->label : NULL, GLOBAL_ROOT_UID, NULL, + hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info, error); if (!hat || (error && error != -ENOENT)) return ERR_PTR(error); @@ -917,7 +1102,8 @@ audit: * * Returns: label for hat transition or ERR_PTR. Does not return NULL */ -static struct aa_label *change_hat(struct aa_label *label, const char *hats[], +static struct aa_label *change_hat(const struct cred *subj_cred, + struct aa_label *label, const char *hats[], int count, int flags) { struct aa_profile *profile, *root, *hat = NULL; @@ -993,7 +1179,8 @@ fail: */ /* TODO: get rid of GLOBAL_ROOT_UID */ if (count > 1 || COMPLAIN_MODE(profile)) { - aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, + aa_audit_file(subj_cred, profile, &nullperms, + OP_CHANGE_HAT, AA_MAY_CHANGEHAT, name, NULL, NULL, GLOBAL_ROOT_UID, info, error); } @@ -1002,7 +1189,8 @@ fail: build: new = fn_label_build_in_ns(label, profile, GFP_KERNEL, - build_change_hat(profile, name, sibling), + build_change_hat(subj_cred, profile, name, + sibling), aa_get_label(&profile->label)); if (!new) { info = "label build failed"; @@ -1032,39 +1220,51 @@ build: */ int aa_change_hat(const char *hats[], int count, u64 token, int flags) { - const struct cred *cred; - struct aa_task_ctx *ctx; + const struct cred *subj_cred; + struct aa_task_ctx *ctx = task_ctx(current); struct aa_label *label, *previous, *new = NULL, *target = NULL; struct aa_profile *profile; struct aa_perms perms = {}; const char *info = NULL; int error = 0; - /* - * Fail explicitly requested domain transitions if no_new_privs. - * There is no exception for unconfined as change_hat is not - * available. - */ - if (task_no_new_privs(current)) { - /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); - return -EPERM; - } - /* released below */ - cred = get_current_cred(); - ctx = cred_ctx(cred); - label = aa_get_newest_cred_label(cred); + subj_cred = get_current_cred(); + label = aa_get_newest_cred_label(subj_cred); previous = aa_get_newest_label(ctx->previous); + /* + * Detect no new privs being set, and store the label it + * occurred under. Ideally this would happen when nnp + * is set but there isn't a good way to do that yet. + * + * Testing for unconfined must be done before the subset test + */ + if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) + ctx->nnp = aa_get_label(label); + + /* return -EPERM when unconfined doesn't have children to avoid + * changing the traditional error code for unconfined. + */ if (unconfined(label)) { - info = "unconfined can not change_hat"; - error = -EPERM; - goto fail; + struct label_it i; + bool empty = true; + + rcu_read_lock(); + label_for_each_in_ns(i, labels_ns(label), label, profile) { + empty &= list_empty(&profile->base.profiles); + } + rcu_read_unlock(); + + if (empty) { + info = "unconfined can not change_hat"; + error = -EPERM; + goto fail; + } } if (count) { - new = change_hat(label, hats, count, flags); + new = change_hat(subj_cred, label, hats, count, flags); AA_BUG(!new); if (IS_ERR(new)) { error = PTR_ERR(new); @@ -1073,10 +1273,24 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) goto out; } - error = may_change_ptraced_domain(new, &info); + /* target cred is the same as current except new label */ + error = may_change_ptraced_domain(subj_cred, new, &info); if (error) goto fail; + /* + * no new privs prevents domain transitions that would + * reduce restrictions. + */ + if (task_no_new_privs(current) && !unconfined(label) && + !aa_label_is_unconfined_subset(new, ctx->nnp)) { + /* not an apparmor denial per se, so don't log it */ + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); + error = -EPERM; + goto out; + } + if (flags & AA_CHANGE_TEST) goto out; @@ -1086,6 +1300,19 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) /* kill task in case of brute force attacks */ goto kill; } else if (previous && !(flags & AA_CHANGE_TEST)) { + /* + * no new privs prevents domain transitions that would + * reduce restrictions. + */ + if (task_no_new_privs(current) && !unconfined(label) && + !aa_label_is_unconfined_subset(previous, ctx->nnp)) { + /* not an apparmor denial per se, so don't log it */ + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); + error = -EPERM; + goto out; + } + /* Return to saved label. Kill task if restore fails * to avoid brute force attacks */ @@ -1102,7 +1329,7 @@ out: aa_put_label(new); aa_put_label(previous); aa_put_label(label); - put_cred(cred); + put_cred(subj_cred); return error; @@ -1112,7 +1339,7 @@ kill: fail: fn_for_each_in_ns(label, profile, - aa_audit_file(profile, &perms, OP_CHANGE_HAT, + aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, NULL, target, GLOBAL_ROOT_UID, info, error)); @@ -1121,43 +1348,33 @@ fail: static int change_profile_perms_wrapper(const char *op, const char *name, + const struct cred *subj_cred, struct aa_profile *profile, struct aa_label *target, bool stack, u32 request, struct aa_perms *perms) { + struct aa_ruleset *rules = profile->label.rules[0]; const char *info = NULL; int error = 0; - /* - * Fail explicitly requested domain transitions when no_new_privs - * and not unconfined OR the transition results in a stack on - * the current label. - * Stacking domain transitions and transitions from unconfined are - * allowed even when no_new_privs is set because this aways results - * in a reduction of permissions. - */ - if (task_no_new_privs(current) && !stack && - !profile_unconfined(profile) && - !aa_label_is_subset(target, &profile->label)) { - info = "no new privs"; - error = -EPERM; - } - if (!error) error = change_profile_perms(profile, target, stack, request, - profile->file.start, perms); + rules->file->start[AA_CLASS_FILE], + perms); if (error) - error = aa_audit_file(profile, perms, op, request, name, + error = aa_audit_file(subj_cred, profile, perms, op, request, + name, NULL, target, GLOBAL_ROOT_UID, info, error); return error; } +static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking"; + /** * aa_change_profile - perform a one-way profile transition * @fqname: name of profile may include namespace (NOT NULL) - * @onexec: whether this transition is to take place immediately or at exec * @flags: flags affecting change behavior * * Change to new profile @name. Unlike with hats, there is no way @@ -1176,12 +1393,27 @@ int aa_change_profile(const char *fqname, int flags) const char *info = NULL; const char *auditname = fqname; /* retain leading & if stack */ bool stack = flags & AA_CHANGE_STACK; + struct aa_task_ctx *ctx = task_ctx(current); + const struct cred *subj_cred = get_current_cred(); int error = 0; char *op; u32 request; + label = aa_get_current_label(); + + /* + * Detect no new privs being set, and store the label it + * occurred under. Ideally this would happen when nnp + * is set but there isn't a good way to do that yet. + * + * Testing for unconfined must be done before the subset test + */ + if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) + ctx->nnp = aa_get_label(label); + if (!fqname || !*fqname) { - AA_DEBUG("no profile name"); + aa_put_label(label); + AA_DEBUG(DEBUG_DOMAIN, "no profile name"); return -EINVAL; } @@ -1199,7 +1431,27 @@ int aa_change_profile(const char *fqname, int flags) op = OP_CHANGE_PROFILE; } - label = aa_get_current_label(); + /* This should move to a per profile test. Requires pushing build + * into callback + */ + if (!stack && unconfined(label) && + label == &labels_ns(label)->unconfined->label && + aa_unprivileged_unconfined_restricted && + /* TODO: refactor so this check is a fn */ + cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE, + CAP_OPT_NOAUDIT)) { + /* regardless of the request in this case apparmor + * stacks against unconfined so admin set policy can't be + * by-passed + */ + stack = true; + perms.audit = request; + (void) fn_for_each_in_ns(label, profile, + aa_audit_file(subj_cred, profile, &perms, op, + request, auditname, NULL, target, + GLOBAL_ROOT_UID, stack_msg, 0)); + perms.audit = 0; + } if (*fqname == '&') { stack = true; @@ -1221,8 +1473,8 @@ int aa_change_profile(const char *fqname, int flags) !COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */ - tprofile = aa_new_null_profile(labels_profile(label), false, - fqname, GFP_KERNEL); + tprofile = aa_new_learning_profile(labels_profile(label), false, + fqname, GFP_KERNEL); if (!tprofile) { info = "failed null profile create"; error = -ENOMEM; @@ -1242,6 +1494,7 @@ int aa_change_profile(const char *fqname, int flags) */ error = fn_for_each_in_ns(label, profile, change_profile_perms_wrapper(op, auditname, + subj_cred, profile, target, stack, request, &perms)); if (error) @@ -1252,7 +1505,7 @@ int aa_change_profile(const char *fqname, int flags) check: /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(target, &info); + error = may_change_ptraced_domain(subj_cred, target, &info); if (error && !fn_for_each_in_ns(label, profile, COMPLAIN_MODE(profile))) goto audit; @@ -1267,29 +1520,54 @@ check: if (flags & AA_CHANGE_TEST) goto out; + /* stacking is always a subset, so only check the nonstack case */ + if (!stack) { + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + aa_get_label(target), + aa_get_label(&profile->label)); + /* + * no new privs prevents domain transitions that would + * reduce restrictions. + */ + if (task_no_new_privs(current) && !unconfined(label) && + !aa_label_is_unconfined_subset(new, ctx->nnp)) { + /* not an apparmor denial per se, so don't log it */ + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); + error = -EPERM; + goto out; + } + } + if (!(flags & AA_CHANGE_ONEXEC)) { /* only transition profiles in the current ns */ if (stack) new = aa_label_merge(label, target, GFP_KERNEL); - else - new = fn_label_build_in_ns(label, profile, GFP_KERNEL, - aa_get_label(target), - aa_get_label(&profile->label)); if (IS_ERR_OR_NULL(new)) { info = "failed to build target label"; - error = PTR_ERR(new); + if (!new) + error = -ENOMEM; + else + error = PTR_ERR(new); new = NULL; perms.allow = 0; goto audit; } error = aa_replace_current_label(new); - } else + } else { + if (new) { + aa_put_label(new); + new = NULL; + } + /* full transition will be built in exec path */ - error = aa_set_current_onexec(target, stack); + aa_set_current_onexec(target, stack); + } audit: error = fn_for_each_in_ns(label, profile, - aa_audit_file(profile, &perms, op, request, auditname, + aa_audit_file(subj_cred, + profile, &perms, op, request, auditname, NULL, new ? new : target, GLOBAL_ROOT_UID, info, error)); @@ -1297,6 +1575,7 @@ out: aa_put_label(new); aa_put_label(target); aa_put_label(label); + put_cred(subj_cred); return error; } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 3382518b87fa..c75820402878 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,22 +6,21 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/tty.h> #include <linux/fdtable.h> #include <linux/file.h> +#include <linux/fs.h> +#include <linux/mount.h> +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/audit.h" -#include "include/context.h" +#include "include/cred.h" #include "include/file.h" #include "include/match.h" +#include "include/net.h" #include "include/path.h" #include "include/policy.h" #include "include/label.h" @@ -38,19 +38,6 @@ static u32 map_mask_to_chr_mask(u32 mask) } /** - * audit_file_mask - convert mask to permission string - * @buffer: buffer to write string to (NOT NULL) - * @mask: permission mask to convert - */ -static void audit_file_mask(struct audit_buffer *ab, u32 mask) -{ - char str[10]; - - aa_perm_mask_to_str(str, aa_file_perm_chrs, map_mask_to_chr_mask(mask)); - audit_log_string(ab, str); -} - -/** * file_audit_cb - call back for file specific audit fields * @ab: audit_buffer (NOT NULL) * @va: audit struct to audit values of (NOT NULL) @@ -58,35 +45,40 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) static void file_audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; - kuid_t fsuid = current_fsuid(); + struct apparmor_audit_data *ad = aad(sa); + kuid_t fsuid = ad->subj_cred ? ad->subj_cred->fsuid : current_fsuid(); + char str[10]; - if (aad(sa)->request & AA_AUDIT_FILE_MASK) { - audit_log_format(ab, " requested_mask="); - audit_file_mask(ab, aad(sa)->request); + if (ad->request & AA_AUDIT_FILE_MASK) { + aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs, + map_mask_to_chr_mask(ad->request)); + audit_log_format(ab, " requested_mask=\"%s\"", str); } - if (aad(sa)->denied & AA_AUDIT_FILE_MASK) { - audit_log_format(ab, " denied_mask="); - audit_file_mask(ab, aad(sa)->denied); + if (ad->denied & AA_AUDIT_FILE_MASK) { + aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs, + map_mask_to_chr_mask(ad->denied)); + audit_log_format(ab, " denied_mask=\"%s\"", str); } - if (aad(sa)->request & AA_AUDIT_FILE_MASK) { + if (ad->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, aad(sa)->fs.ouid)); + from_kuid(&init_user_ns, ad->fs.ouid)); } - if (aad(sa)->peer) { + if (ad->peer) { audit_log_format(ab, " target="); - aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, - FLAG_VIEW_SUBNS, GFP_ATOMIC); - } else if (aad(sa)->fs.target) { + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, + FLAG_VIEW_SUBNS, GFP_KERNEL); + } else if (ad->fs.target) { audit_log_format(ab, " target="); - audit_log_untrustedstring(ab, aad(sa)->fs.target); + audit_log_untrustedstring(ab, ad->fs.target); } } /** * aa_audit_file - handle the auditing of file operations + * @subj_cred: cred of the subject * @profile: the profile being enforced (NOT NULL) * @perms: the permissions computed for the request (NOT NULL) * @op: operation being mediated @@ -100,72 +92,61 @@ 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 aa_perms *perms, +int aa_audit_file(const struct cred *subj_cred, + struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error) { int type = AUDIT_APPARMOR_AUTO; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op); - - sa.u.tsk = NULL; - aad(&sa)->request = request; - aad(&sa)->name = name; - aad(&sa)->fs.target = target; - aad(&sa)->peer = tlabel; - aad(&sa)->fs.ouid = ouid; - aad(&sa)->info = info; - aad(&sa)->error = error; - sa.u.tsk = NULL; - - if (likely(!aad(&sa)->error)) { + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op); + + ad.subj_cred = subj_cred; + ad.request = request; + ad.name = name; + ad.fs.target = target; + ad.peer = tlabel; + ad.fs.ouid = ouid; + ad.info = info; + ad.error = error; + ad.common.u.tsk = NULL; + + if (likely(!ad.error)) { u32 mask = perms->audit; if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL)) mask = 0xffff; /* mask off perms that are not being force audited */ - aad(&sa)->request &= mask; + ad.request &= mask; - if (likely(!aad(&sa)->request)) + if (likely(!ad.request)) return 0; type = AUDIT_APPARMOR_AUDIT; } else { /* only report permissions that were denied */ - aad(&sa)->request = aad(&sa)->request & ~perms->allow; - AA_BUG(!aad(&sa)->request); + ad.request = ad.request & ~perms->allow; + AA_BUG(!ad.request); - if (aad(&sa)->request & perms->kill) + if (ad.request & perms->kill) type = AUDIT_APPARMOR_KILL; /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((aad(&sa)->request & perms->quiet) && + if ((ad.request & perms->quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - aad(&sa)->request &= ~perms->quiet; + ad.request &= ~perms->quiet; - if (!aad(&sa)->request) - return aad(&sa)->error; + if (!ad.request) + return ad.error; } - aad(&sa)->denied = aad(&sa)->request & ~perms->allow; - return aa_audit(type, profile, &sa, file_audit_cb); + ad.denied = ad.request & ~perms->allow; + return aa_audit(type, profile, &ad, 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) && d_backing_inode(dentry)->i_nlink == 0) - return 1; - return 0; -} - -static int path_name(const char *op, struct aa_label *label, +static int path_name(const char *op, const struct cred *subj_cred, + struct aa_label *label, const struct path *path, int flags, char *buffer, const char **name, struct path_cond *cond, u32 request) { @@ -177,7 +158,8 @@ static int path_name(const char *op, struct aa_label *label, labels_profile(label)->disconnected); if (error) { fn_for_each_confined(label, profile, - aa_audit_file(profile, &nullperms, op, request, *name, + aa_audit_file(subj_cred, + profile, &nullperms, op, request, *name, NULL, NULL, cond->uid, info, error)); return error; } @@ -185,120 +167,80 @@ static int path_name(const char *op, struct aa_label *label, return 0; } +struct aa_perms default_perms = {}; /** - * map_old_perms - map old file perms layout to the new layout - * @old: permission set in old mapping - * - * Returns: new permission mapping - */ -static u32 map_old_perms(u32 old) -{ - u32 new = old & 0xf; - if (old & MAY_READ) - new |= AA_MAY_GETATTR | AA_MAY_OPEN; - if (old & MAY_WRITE) - new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | - AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; - if (old & 0x10) - new |= AA_MAY_LINK; - /* the old mapping lock and link_subset flags where overlaid - * and use was determined by part of a pair that they were in - */ - if (old & 0x20) - new |= AA_MAY_LOCK | AA_LINK_SUBSET; - if (old & 0x40) /* AA_EXEC_MMAP */ - new |= AA_EXEC_MMAP; - - return new; -} - -/** - * aa_compute_fperms - convert dfa compressed perms to internal perms - * @dfa: dfa to compute perms for (NOT NULL) + * aa_lookup_condperms - convert dfa compressed perms to internal perms + * @subj_uid: uid to use for subject owner test + * @rules: the aa_policydb to lookup perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) * - * TODO: convert from dfa + state to permission entry, do computation conversion - * at load time. + * TODO: convert from dfa + state to permission entry * - * Returns: computed permission set + * Returns: a pointer to a file permission set */ -struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond) +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules, + aa_state_t state, struct path_cond *cond) { - struct aa_perms perms; + unsigned int index = ACCEPT_TABLE(rules->dfa)[state]; - /* FIXME: change over to new dfa format - * currently file perms are encoded in the dfa, new format - * splits the permissions from the dfa. This mapping can be - * done at profile load - */ - perms.deny = 0; - perms.kill = perms.stop = 0; - perms.complain = perms.cond = 0; - perms.hide = 0; - perms.prompt = 0; - - if (uid_eq(current_fsuid(), cond->uid)) { - perms.allow = map_old_perms(dfa_user_allow(dfa, state)); - perms.audit = map_old_perms(dfa_user_audit(dfa, state)); - perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); - perms.xindex = dfa_user_xindex(dfa, state); - } else { - perms.allow = map_old_perms(dfa_other_allow(dfa, state)); - perms.audit = map_old_perms(dfa_other_audit(dfa, state)); - perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); - perms.xindex = dfa_other_xindex(dfa, state); - } - perms.allow |= AA_MAY_GETATTR; + if (!(rules->perms)) + return &default_perms; - /* change_profile wasn't determined by ownership in old mapping */ - if (ACCEPT_TABLE(dfa)[state] & 0x80000000) - perms.allow |= AA_MAY_CHANGE_PROFILE; - if (ACCEPT_TABLE(dfa)[state] & 0x40000000) - perms.allow |= AA_MAY_ONEXEC; + if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) { + if (uid_eq(subj_uid, cond->uid)) + return &(rules->perms[index]); + return &(rules->perms[index + 1]); + } - return perms; + return &(rules->perms[index]); } /** * aa_str_perms - find permission that match @name - * @dfa: to match against (MAYBE NULL) - * @state: state to start matching in + * @file_rules: the aa_policydb to match against (NOT NULL) + * @start: state to start matching in * @name: string to match against dfa (NOT NULL) * @cond: conditions to consider for permission set computation (NOT NULL) * @perms: Returns - the permissions found when matching @name * * Returns: the final state in @dfa when beginning @start and walking @name */ -unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, - const char *name, struct path_cond *cond, - struct aa_perms *perms) +aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, + const char *name, struct path_cond *cond, + struct aa_perms *perms) { - unsigned int state; - state = aa_dfa_match(dfa, start, name); - *perms = aa_compute_fperms(dfa, state, cond); + aa_state_t state; + state = aa_dfa_match(file_rules->dfa, start, name); + *perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state, + cond)); return state; } -int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, u32 request, struct path_cond *cond, int flags, struct aa_perms *perms) { + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; - if (profile_unconfined(profile)) + if (profile_unconfined(profile) || + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) return 0; - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms); + aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], + name, cond, perms); if (request & ~perms->allow) e = -EACCES; - return aa_audit_file(profile, perms, op, request, name, NULL, NULL, + return aa_audit_file(subj_cred, + profile, perms, op, request, name, NULL, NULL, cond->uid, NULL, e); } -static int profile_path_perm(const char *op, struct aa_profile *profile, +static int profile_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const struct path *path, char *buffer, u32 request, struct path_cond *cond, int flags, struct aa_perms *perms) @@ -309,18 +251,19 @@ static int profile_path_perm(const char *op, struct aa_profile *profile, if (profile_unconfined(profile)) return 0; - error = path_name(op, &profile->label, path, + error = path_name(op, subj_cred, &profile->label, path, flags | profile->path_flags, buffer, &name, cond, request); if (error) return error; - return __aa_path_perm(op, profile, name, request, cond, flags, - perms); + return __aa_path_perm(op, subj_cred, profile, name, request, cond, + flags, perms); } /** * aa_path_perm - do permissions check & audit for @path * @op: operation being checked + * @subj_cred: subject cred * @label: profile being enforced (NOT NULL) * @path: path to check permissions of (NOT NULL) * @flags: any additional path flags beyond what the profile specifies @@ -329,7 +272,8 @@ static int profile_path_perm(const char *op, struct aa_profile *profile, * * Returns: %0 else error if access denied or other error */ -int aa_path_perm(const char *op, struct aa_label *label, +int aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond) { @@ -340,12 +284,14 @@ int aa_path_perm(const char *op, struct aa_label *label, flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - get_buffers(buffer); + buffer = aa_get_buffer(false); + if (!buffer) + return -ENOMEM; error = fn_for_each_confined(label, profile, - profile_path_perm(op, profile, path, buffer, request, - cond, flags, &perms)); + profile_path_perm(op, subj_cred, profile, path, buffer, + request, cond, flags, &perms)); - put_buffers(buffer); + aa_put_buffer(buffer); return error; } @@ -359,51 +305,56 @@ int aa_path_perm(const char *op, struct aa_label *label, * this is done as part of the subset test, where a hardlink must have * a subset of permissions that the target has. * - * Returns: %1 if subset else %0 + * Returns: true if subset else false */ static inline bool xindex_is_subset(u32 link, u32 target) { if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) || ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE))) - return 0; + return false; - return 1; + return true; } -static int profile_path_link(struct aa_profile *profile, +static int profile_path_link(const struct cred *subj_cred, + struct aa_profile *profile, const struct path *link, char *buffer, const struct path *target, char *buffer2, struct path_cond *cond) { + struct aa_ruleset *rules = profile->label.rules[0]; const char *lname, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; u32 request = AA_MAY_LINK; - unsigned int state; + aa_state_t state; int error; - error = path_name(OP_LINK, &profile->label, link, profile->path_flags, + error = path_name(OP_LINK, subj_cred, &profile->label, link, + profile->path_flags, buffer, &lname, cond, AA_MAY_LINK); if (error) goto audit; /* buffer2 freed below, tname is pointer in buffer2 */ - error = path_name(OP_LINK, &profile->label, target, profile->path_flags, + error = path_name(OP_LINK, subj_cred, &profile->label, target, + profile->path_flags, buffer2, &tname, cond, AA_MAY_LINK); 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, + state = aa_str_perms(rules->file, + rules->file->start[AA_CLASS_FILE], lname, 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); + state = aa_dfa_null_transition(rules->file->dfa, state); + aa_str_perms(rules->file, state, tname, cond, &perms); /* force audit/quiet masks for link are stored in the second entry * in the link pair. @@ -425,8 +376,8 @@ static int profile_path_link(struct aa_profile *profile, /* 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, - &perms); + aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], + tname, cond, &perms); /* AA_MAY_LINK is not considered in the subset test */ request = lperms.allow & ~AA_MAY_LINK; @@ -447,12 +398,14 @@ done_tests: error = 0; audit: - return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, + return aa_audit_file(subj_cred, + profile, &lperms, OP_LINK, request, lname, tname, NULL, cond->uid, info, error); } /** * aa_path_link - Handle hard link permission check + * @subj_cred: subject cred * @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) @@ -469,26 +422,35 @@ audit: * * Returns: %0 if allowed else error */ -int aa_path_link(struct aa_label *label, struct dentry *old_dentry, +int aa_path_link(const struct cred *subj_cred, + struct aa_label *label, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; + struct inode *inode = d_backing_inode(old_dentry); + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode); struct path_cond cond = { - d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, }; char *buffer = NULL, *buffer2 = NULL; struct aa_profile *profile; int error; /* buffer freed below, lname is pointer in buffer */ - get_buffers(buffer, buffer2); - error = fn_for_each_confined(label, profile, - profile_path_link(profile, &link, buffer, &target, - buffer2, &cond)); - put_buffers(buffer, buffer2); + buffer = aa_get_buffer(false); + buffer2 = aa_get_buffer(false); + error = -ENOMEM; + if (!buffer || !buffer2) + goto out; + error = fn_for_each_confined(label, profile, + profile_path_link(subj_cred, profile, &link, buffer, + &target, buffer2, &cond)); +out: + aa_put_buffer(buffer); + aa_put_buffer(buffer2); return error; } @@ -500,7 +462,7 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, /* update caching of label on file_ctx */ spin_lock(&fctx->lock); old = rcu_dereference_protected(fctx->label, - spin_is_locked(&fctx->lock)); + lockdep_is_held(&fctx->lock)); l = aa_label_merge(old, label, GFP_ATOMIC); if (l) { if (l != old) { @@ -513,14 +475,17 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, spin_unlock(&fctx->lock); } -static int __file_path_perm(const char *op, struct aa_label *label, +static int __file_path_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, struct aa_label *flabel, struct file *file, - u32 request, u32 denied) + u32 request, u32 denied, bool in_atomic) { struct aa_profile *profile; struct aa_perms perms = {}; + vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(file), + file_inode(file)); struct path_cond cond = { - .uid = file_inode(file)->i_uid, + .uid = vfsuid_into_kuid(vfsuid), .mode = file_inode(file)->i_mode }; char *buffer; @@ -532,11 +497,14 @@ static int __file_path_perm(const char *op, struct aa_label *label, return 0; flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); - get_buffers(buffer); + buffer = aa_get_buffer(in_atomic); + if (!buffer) + return -ENOMEM; /* check every profile in task label not in current cache */ error = fn_for_each_not_in_set(flabel, label, profile, - profile_path_perm(op, profile, &file->f_path, buffer, + profile_path_perm(op, subj_cred, profile, + &file->f_path, buffer, request, &cond, flags, &perms)); if (denied && !error) { /* @@ -549,34 +517,93 @@ static int __file_path_perm(const char *op, struct aa_label *label, */ if (label == flabel) error = fn_for_each(label, profile, - profile_path_perm(op, profile, &file->f_path, + profile_path_perm(op, subj_cred, + profile, &file->f_path, buffer, request, &cond, flags, &perms)); else error = fn_for_each_not_in_set(label, flabel, profile, - profile_path_perm(op, profile, &file->f_path, + profile_path_perm(op, subj_cred, + profile, &file->f_path, buffer, request, &cond, flags, &perms)); } if (!error) update_file_ctx(file_ctx(file), label, request); - put_buffers(buffer); + aa_put_buffer(buffer); + + return error; +} + +static int __file_sock_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, + struct aa_label *flabel, struct file *file, + u32 request, u32 denied) +{ + int error; + + /* 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(subj_cred, label, op, request, file); + 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(subj_cred, flabel, op, + request, file)); + } + if (!error) + update_file_ctx(file_ctx(file), label, request); return error; } +/* for now separate fn to indicate semantics of the check */ +static bool __file_is_delegated(struct aa_label *obj_label) +{ + return unconfined(obj_label); +} + +static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, + u32 request) +{ + struct socket *sock = (struct socket *) file->private_data; + + lockdep_assert_in_rcu_read_lock(); + + if (!S_ISSOCK(file_inode(file)->i_mode)) + return false; + if (request & NET_PEER_MASK) + return false; + if (sock->sk->sk_family == PF_UNIX) { + struct aa_sk_ctx *ctx = aa_sock(sock->sk); + + if (rcu_access_pointer(ctx->peer) != + rcu_access_pointer(ctx->peer_lastupdate)) + return true; + return !__aa_subj_label_is_cached(rcu_dereference(ctx->label), + label); + } + return false; +} + /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked + * @subj_cred: subject cred * @label: label being enforced (NOT NULL) * @file: file to revalidate access permissions on (NOT NULL) * @request: requested permissions + * @in_atomic: whether allocations need to be done in atomic context * * Returns: %0 if access allowed else error */ -int aa_file_perm(const char *op, struct aa_label *label, struct file *file, - u32 request) +int aa_file_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, struct file *file, + u32 request, bool in_atomic) { struct aa_file_ctx *fctx; struct aa_label *flabel; @@ -600,23 +627,31 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file, * delegation from unconfined tasks */ denied = request & ~fctx->allow; - if (unconfined(label) || unconfined(flabel) || - (!denied && aa_label_is_subset(flabel, label))) + if (unconfined(label) || __file_is_delegated(flabel) || + __unix_needs_revalidation(file, label, request) || + (!denied && __aa_subj_label_is_cached(label, flabel))) { + rcu_read_unlock(); goto done; + } - /* TODO: label cross check */ + /* slow path - revalidate access */ + flabel = aa_get_newest_label(flabel); + rcu_read_unlock(); - if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) - error = __file_path_perm(op, label, flabel, file, request, - denied); + if (path_mediated_fs(file->f_path.dentry)) + error = __file_path_perm(op, subj_cred, label, flabel, file, + request, denied, in_atomic); -done: - rcu_read_unlock(); + else if (S_ISSOCK(file_inode(file)->i_mode)) + error = __file_sock_perm(op, subj_cred, label, flabel, file, + request, denied); + aa_put_label(flabel); +done: return error; } -static void revalidate_tty(struct aa_label *label) +static void revalidate_tty(const struct cred *subj_cred, struct aa_label *label) { struct tty_struct *tty; int drop_tty = 0; @@ -634,7 +669,8 @@ static void revalidate_tty(struct aa_label *label) struct tty_file_private, list); file = file_priv->file; - if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE)) + if (aa_file_perm(OP_INHERIT, subj_cred, label, file, + MAY_READ | MAY_WRITE, IN_ATOMIC)) drop_tty = 1; } spin_unlock(&tty->files_lock); @@ -644,11 +680,17 @@ static void revalidate_tty(struct aa_label *label) no_tty(); } +struct cred_label { + const struct cred *cred; + struct aa_label *label; +}; + static int match_file(const void *p, struct file *file, unsigned int fd) { - struct aa_label *label = (struct aa_label *)p; + struct cred_label *cl = (struct cred_label *)p; - if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file))) + if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file, + aa_map_file_to_perms(file), IN_ATOMIC)) return fd + 1; return 0; } @@ -658,13 +700,17 @@ static int match_file(const void *p, struct file *file, unsigned int fd) void aa_inherit_files(const struct cred *cred, struct files_struct *files) { struct aa_label *label = aa_get_newest_cred_label(cred); + struct cred_label cl = { + .cred = cred, + .label = label, + }; struct file *devnull = NULL; unsigned int n; - revalidate_tty(label); + revalidate_tty(cred, label); /* Revalidate access to inherited open files. */ - n = iterate_fd(files, 0, match_file, label); + n = iterate_fd(files, 0, match_file, &cl); if (!n) /* none found? */ goto out; @@ -674,7 +720,7 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files) /* replace all the matching ones with this */ do { replace_fd(n - 1, devnull, 0); - } while ((n = iterate_fd(files, n, match_file, label)) != 0); + } while ((n = iterate_fd(files, n, match_file, &cl)) != 0); if (devnull) fput(devnull); out: diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h new file mode 100644 index 000000000000..4a62e600d82b --- /dev/null +++ b/security/apparmor/include/af_unix.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ +#ifndef __AA_AF_UNIX_H + +#include <net/af_unix.h> + +#include "label.h" + +#define unix_addr(A) ((struct sockaddr_un *)(A)) +#define unix_addr_len(L) ((L) - sizeof(sa_family_t)) +#define unix_peer(sk) (unix_sk(sk)->peer) +#define is_unix_addr_abstract_name(B) ((B)[0] == 0) +#define is_unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0) +#define is_unix_addr_fs(A, L) (!is_unix_addr_anon(A, L) && \ + !is_unix_addr_abstract_name(unix_addr(A)->sun_path)) + +#define is_unix_anonymous(U) (!unix_sk(U)->addr) +#define is_unix_fs(U) (!is_unix_anonymous(U) && \ + unix_sk(U)->addr->name->sun_path[0]) +#define is_unix_connected(S) ((S)->state == SS_CONNECTED) + + +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen); +int aa_unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label); +int aa_unix_sock_perm(const char *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(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size); +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname); +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file); + +#endif /* __AA_AF_UNIX_H */ diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index aaf893f4e4f5..cc6e3df1bc62 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2017 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __APPARMOR_H @@ -20,26 +16,53 @@ /* * Class of mediation types in the AppArmor policy db */ -#define AA_CLASS_ENTRY 0 +#define AA_CLASS_NONE 0 #define AA_CLASS_UNKNOWN 1 #define AA_CLASS_FILE 2 #define AA_CLASS_CAP 3 -#define AA_CLASS_NET 4 +#define AA_CLASS_DEPRECATED 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_XMATCH 11 +#define AA_CLASS_NET 14 +#define AA_CLASS_NETV9 15 #define AA_CLASS_LABEL 16 +#define AA_CLASS_POSIX_MQUEUE 17 +#define AA_CLASS_MODULE 19 +#define AA_CLASS_DISPLAY_LSM 20 +#define AA_CLASS_NS 21 +#define AA_CLASS_IO_URING 22 + +#define AA_CLASS_X 31 +#define AA_CLASS_DBUS 32 -#define AA_CLASS_LAST AA_CLASS_LABEL +/* NOTE: if AA_CLASS_LAST > 63 need to update label->mediates */ +#define AA_CLASS_LAST AA_CLASS_DBUS /* 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 int aa_g_debug; extern bool aa_g_hash_policy; +extern bool aa_g_export_binary; +extern int aa_g_rawdata_compression_level; extern bool aa_g_lock_policy; extern bool aa_g_logsyscall; extern bool aa_g_paranoid_load; extern unsigned int aa_g_path_max; +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +#define AA_MIN_CLEVEL zstd_min_clevel() +#define AA_MAX_CLEVEL zstd_max_clevel() +#define AA_DEFAULT_CLEVEL ZSTD_CLEVEL_DEFAULT +#else +#define AA_MIN_CLEVEL 0 +#define AA_MAX_CLEVEL 0 +#define AA_DEFAULT_CLEVEL 0 +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + + #endif /* __APPARMOR_H */ diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index bd689114bf93..dd580594dfb7 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_APPARMORFS_H @@ -108,6 +104,8 @@ enum aafs_prof_type { #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) +int aa_create_aafs(void); + void __aa_bump_ns_revision(struct aa_ns *ns); void __aafs_profile_rmdir(struct aa_profile *profile); void __aafs_profile_migrate_dents(struct aa_profile *old, @@ -118,7 +116,21 @@ int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, struct dentry *dent); struct aa_loaddata; + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata); int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata); +#else +static inline void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata) +{ + /* empty stub */ +} + +static inline int __aa_fs_create_rawdata(struct aa_ns *ns, + struct aa_loaddata *rawdata) +{ + return 0; +} +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index c68839a44351..1a71a94ea19c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_AUDIT_H @@ -71,6 +67,10 @@ enum audit_type { #define OP_FMPROT "file_mprotect" #define OP_INHERIT "file_inherit" +#define OP_PIVOTROOT "pivotroot" +#define OP_MOUNT "mount" +#define OP_UMOUNT "umount" + #define OP_CREATE "create" #define OP_POST_CREATE "post_create" #define OP_BIND "bind" @@ -86,6 +86,7 @@ enum audit_type { #define OP_SHUTDOWN "socket_shutdown" #define OP_PTRACE "ptrace" +#define OP_SIGNAL "signal" #define OP_EXEC "exec" @@ -102,12 +103,18 @@ enum audit_type { #define OP_PROF_LOAD "profile_load" #define OP_PROF_RM "profile_remove" +#define OP_USERNS_CREATE "userns_create" + +#define OP_URING_OVERRIDE "uring_override" +#define OP_URING_SQPOLL "uring_sqpoll" struct apparmor_audit_data { int error; int type; + u16 class; const char *op; - struct aa_label *label; + const struct cred *subj_cred; + struct aa_label *subj_label; const char *name; const char *info; u32 request; @@ -116,45 +123,75 @@ struct apparmor_audit_data { /* these entries require a custom callback fn */ struct { struct aa_label *peer; - struct { - const char *target; - kuid_t ouid; - } fs; + union { + struct { + const char *target; + kuid_t ouid; + } fs; + struct { + int rlim; + unsigned long max; + } rlim; + struct { + int signal; + int unmappedsig; + }; + struct { + int type, protocol; + void *addr; + int addrlen; + struct { + void *addr; + int addrlen; + } peer; + } net; + }; }; struct { - const char *name; - long pos; + struct aa_profile *profile; const char *ns; + long pos; } iface; struct { - int rlim; - unsigned long max; - } rlim; + const char *src_name; + const char *type; + const char *trans; + const char *data; + unsigned long flags; + } mnt; + struct { + struct aa_label *target; + } uring; }; + + struct common_audit_data common; }; /* macros for dealing with apparmor_audit_data structure */ -#define aad(SA) ((SA)->apparmor_audit_data) -#define DEFINE_AUDIT_DATA(NAME, T, X) \ +#define aad(SA) (container_of(SA, struct apparmor_audit_data, common)) +#define aad_of_va(VA) aad((struct common_audit_data *)(VA)) + +#define DEFINE_AUDIT_DATA(NAME, T, C, 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, + struct apparmor_audit_data NAME = { \ + .class = (C), \ + .op = (X), \ + .common.type = (T), \ + .common.u.tsk = NULL, \ + .common.apparmor_audit_data = &NAME, \ + }; + +void aa_audit_msg(int type, struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)); -int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa, +int aa_audit(int type, struct aa_profile *profile, + struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)); -#define aa_audit_error(ERROR, SA, CB) \ +#define aa_audit_error(ERROR, AD, CB) \ ({ \ - aad((SA))->error = (ERROR); \ - aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \ - aad((SA))->error; \ + (AD)->error = (ERROR); \ + aa_audit_msg(AUDIT_APPARMOR_ERROR, (AD), (CB)); \ + (AD)->error; \ }) @@ -165,4 +202,9 @@ static inline int complain_error(int error) return error; } +void aa_audit_rule_free(void *vrule); +int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp); +int aa_audit_rule_known(struct audit_krule *rule); +int aa_audit_rule_match(struct lsm_prop *prop, u32 field, u32 op, void *vrule); + #endif /* __AA_AUDIT_H */ diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index e0304e2aeb7f..1ddcec2d1160 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * 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 - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_CAPABILITY_H @@ -40,7 +36,9 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; -int aa_capable(struct aa_label *label, int cap, int audit); +kernel_cap_t aa_profile_capget(struct aa_profile *profile); +int aa_capable(const struct cred *subj_cred, struct aa_label *label, + int cap, unsigned int opts); static inline void aa_free_cap_rules(struct aa_caps *caps) { diff --git a/security/apparmor/include/context.h b/security/apparmor/include/cred.h index 6ae07e9aaa17..b028e4c13b6f 100644 --- a/security/apparmor/include/context.h +++ b/security/apparmor/include/cred.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_CONTEXT_H @@ -21,39 +17,24 @@ #include "label.h" #include "policy_ns.h" +#include "task.h" -#define cred_ctx(X) ((X)->security) -#define current_ctx() cred_ctx(current_cred()) +static inline struct aa_label *cred_label(const struct cred *cred) +{ + struct aa_label **blob = cred->security + apparmor_blob_sizes.lbs_cred; -/** - * struct aa_task_ctx - primary label for confined tasks - * @label: the current label (NOT NULL) - * @exec: label to transition to on next exec (MAYBE NULL) - * @previous: label the task may return to (MAYBE NULL) - * @token: magic value the task must know for returning to @previous - * - * 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_ctx { - struct aa_label *label; - struct aa_label *onexec; - struct aa_label *previous; - u64 token; -}; - -struct aa_task_ctx *aa_alloc_task_context(gfp_t flags); -void aa_free_task_context(struct aa_task_ctx *ctx); -void aa_dup_task_context(struct aa_task_ctx *new, - const struct aa_task_ctx *old); -int aa_replace_current_label(struct aa_label *label); -int aa_set_current_onexec(struct aa_label *label, bool stack); -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_BUG(!blob); + return *blob; +} +static inline void set_cred_label(const struct cred *cred, + struct aa_label *label) +{ + struct aa_label **blob = cred->security + apparmor_blob_sizes.lbs_cred; + + AA_BUG(!blob); + *blob = label; +} /** * aa_cred_raw_label - obtain cred's label @@ -65,10 +46,10 @@ struct aa_label *aa_get_task_label(struct task_struct *task); */ static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) { - struct aa_task_ctx *ctx = cred_ctx(cred); + struct aa_label *label = cred_label(cred); - AA_BUG(!ctx || !ctx->label); - return ctx->label; + AA_BUG(!label); + return label; } /** @@ -82,28 +63,24 @@ 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) +static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred, + bool *needput) { - return aa_cred_raw_label(__task_cred(task)); + struct aa_label *l = aa_cred_raw_label(cred); + + if (unlikely(label_is_stale(l))) { + *needput = true; + return aa_get_newest_label(l); + } + + *needput = false; + return l; } -/** - * __aa_task_is_confined - determine if @task has any confinement - * @task: task to check confinement of (NOT NULL) - * - * If @task != current needs to be called in RCU safe critical section - */ -static inline bool __aa_task_is_confined(struct task_struct *task) +static inline void aa_put_label_condref(struct aa_label *l, bool needput) { - return !unconfined(__aa_task_raw_label(task)); + if (unlikely(needput)) + aa_put_label(l); } /** @@ -137,10 +114,22 @@ static inline struct aa_label *aa_get_current_label(void) return aa_get_label(l); } -#define __end_current_label_crit_section(X) end_current_label_crit_section(X) +/** + * __end_current_label_crit_section - end crit section begun with __begin_... + * @label: label obtained from __begin_current_label_crit_section + * @needput: output: bool set by __begin_current_label_crit_section + * + * Returns: label to use for this crit section + */ +static inline void __end_current_label_crit_section(struct aa_label *label, + bool needput) +{ + if (unlikely(needput)) + aa_put_label(label); +} /** - * end_label_crit_section - put a reference found with begin_current_label.. + * end_current_label_crit_section - put a reference found with begin_current_label.. * @label: label reference to put * * Should only be used with a reference obtained with @@ -155,6 +144,7 @@ static inline void end_current_label_crit_section(struct aa_label *label) /** * __begin_current_label_crit_section - current's confining label + * @needput: store whether the label needs to be put when ending crit section * * Returns: up to date confining label or the ns unconfined label (NOT NULL) * @@ -165,13 +155,16 @@ static inline void end_current_label_crit_section(struct aa_label *label) * critical section between __begin_current_label_crit_section() .. * __end_current_label_crit_section() */ -static inline struct aa_label *__begin_current_label_crit_section(void) +static inline struct aa_label *__begin_current_label_crit_section(bool *needput) { struct aa_label *label = aa_current_raw_label(); - if (label_is_stale(label)) - label = aa_get_newest_label(label); + if (label_is_stale(label)) { + *needput = true; + return aa_get_newest_label(label); + } + *needput = false; return label; } @@ -191,6 +184,8 @@ static inline struct aa_label *begin_current_label_crit_section(void) { struct aa_label *label = aa_current_raw_label(); + might_sleep(); + if (label_is_stale(label)) { label = aa_get_newest_label(label); if (aa_replace_current_label(label) == 0) @@ -205,25 +200,13 @@ static inline struct aa_ns *aa_get_current_ns(void) { struct aa_label *label; struct aa_ns *ns; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); ns = aa_get_ns(labels_ns(label)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return ns; } -/** - * aa_clear_task_ctx_trans - clear transition tracking info from the ctx - * @ctx: task context to clear (NOT NULL) - */ -static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx) -{ - aa_put_label(ctx->previous); - aa_put_label(ctx->onexec); - ctx->previous = NULL; - ctx->onexec = NULL; - ctx->token = 0; -} - #endif /* __AA_CONTEXT_H */ diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h index c1469f8db174..f3ffd388cc58 100644 --- a/security/apparmor/include/crypto.h +++ b/security/apparmor/include/crypto.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * 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 @@ -17,6 +13,7 @@ #include "policy.h" #ifdef CONFIG_SECURITY_APPARMOR_HASH +int init_profile_hash(void); unsigned int aa_hash_size(void); char *aa_calc_hash(void *data, size_t len); int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index bab5810b6e9a..77f9a0ed0f04 100644 --- a/security/apparmor/include/domain.h +++ b/security/apparmor/include/domain.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,34 +6,27 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/binfmts.h> #include <linux/types.h> +#include "label.h" + #ifndef __AA_DOMAIN_H #define __AA_DOMAIN_H -struct aa_domain { - int size; - char **table; -}; - #define AA_CHANGE_NOFLAGS 0 #define AA_CHANGE_TEST 1 #define AA_CHANGE_CHILD 2 #define AA_CHANGE_ONEXEC 4 #define AA_CHANGE_STACK 8 -int apparmor_bprm_set_creds(struct linux_binprm *bprm); -int apparmor_bprm_secureexec(struct linux_binprm *bprm); +struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name); + +int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm); -void aa_free_domain_entries(struct aa_domain *domain); int aa_change_hat(const char *hats[], int count, u64 token, int flags); int aa_change_profile(const char *fqname, int flags); diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 001e40073ff9..ef60f99bc5ae 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_FILE_H @@ -21,6 +17,7 @@ #include "match.h" #include "perms.h" +struct aa_policydb; struct aa_profile; struct path; @@ -32,7 +29,10 @@ struct path; AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) -#define file_ctx(X) ((struct aa_file_ctx *)(X)->f_security) +static inline struct aa_file_ctx *file_ctx(struct file *file) +{ + return file->f_security + apparmor_blob_sizes.lbs_file; +} /* struct aa_file_ctx - the AppArmor context the file was opened in * @lock: lock to update the ctx @@ -45,64 +45,23 @@ struct aa_file_ctx { u32 allow; }; -/** - * aa_alloc_file_ctx - allocate file_ctx - * @label: initial label of task creating the file - * @gfp: gfp flags for allocation - * - * Returns: file_ctx or NULL on failure - */ -static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label, - gfp_t gfp) -{ - struct aa_file_ctx *ctx; - - ctx = kzalloc(sizeof(struct aa_file_ctx), gfp); - if (ctx) { - spin_lock_init(&ctx->lock); - rcu_assign_pointer(ctx->label, aa_get_label(label)); - } - return ctx; -} - -/** - * aa_free_file_ctx - free a file_ctx - * @ctx: file_ctx to free (MAYBE_NULL) - */ -static inline void aa_free_file_ctx(struct aa_file_ctx *ctx) -{ - if (ctx) { - aa_put_label(rcu_access_pointer(ctx->label)); - kzfree(ctx); - } -} - -static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx) -{ - return aa_get_label_rcu(&ctx->label); -} - /* * The xindex is broken into 3 parts * - index - an index into either the exec name table or the variable table * - exec type - which determines how the executable name and index are used * - flags - which modify how the destination name is applied */ -#define AA_X_INDEX_MASK 0x03ff +#define AA_X_INDEX_MASK AA_INDEX_MASK -#define AA_X_TYPE_MASK 0x0c00 -#define AA_X_TYPE_SHIFT 10 -#define AA_X_NONE 0x0000 -#define AA_X_NAME 0x0400 /* use executable name px */ -#define AA_X_TABLE 0x0800 /* use a specified name ->n# */ +#define AA_X_TYPE_MASK 0x0c000000 +#define AA_X_NONE AA_INDEX_NONE +#define AA_X_NAME 0x04000000 /* use executable name px */ +#define AA_X_TABLE 0x08000000 /* use a specified name ->n# */ -#define AA_X_UNSAFE 0x1000 -#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */ -#define AA_X_INHERIT 0x4000 -#define AA_X_UNCONFINED 0x8000 - -/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */ -#define AA_SECURE_X_NEEDED 0x8000 +#define AA_X_UNSAFE 0x10000000 +#define AA_X_CHILD 0x20000000 +#define AA_X_INHERIT 0x40000000 +#define AA_X_UNCONFINED 0x80000000 /* need to make conditional which ones are being set */ struct path_cond { @@ -112,107 +71,40 @@ struct path_cond { #define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill) -/* FIXME: split perms from dfa and match this to description - * also add delegation info. - */ -static inline u16 dfa_map_xindex(u16 mask) -{ - u16 old_index = (mask >> 10) & 0xf; - u16 index = 0; - - if (mask & 0x100) - index |= AA_X_UNSAFE; - if (mask & 0x200) - index |= AA_X_INHERIT; - if (mask & 0x80) - index |= AA_X_UNCONFINED; - - if (old_index == 1) { - index |= AA_X_UNCONFINED; - } else if (old_index == 2) { - index |= AA_X_NAME; - } else if (old_index == 3) { - index |= AA_X_NAME | AA_X_CHILD; - } else if (old_index) { - index |= AA_X_TABLE; - index |= old_index - 4; - } - - return index; -} - -/* - * map old dfa inline permissions to new format - */ -#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ - ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) -#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) -#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) -#define dfa_user_xindex(dfa, state) \ - (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) - -#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ - 0x7f) | \ - ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) -#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) -#define dfa_other_quiet(dfa, state) \ - ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) -#define dfa_other_xindex(dfa, state) \ - dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) - -int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, +int aa_audit_file(const struct cred *cred, + struct aa_profile *profile, struct aa_perms *perms, const char *op, u32 request, const char *name, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); -/** - * struct aa_file_rules - components used for file rule permissions - * @dfa: dfa to match path names and conditionals against - * @perms: permission table indexed by the matched state accept entry of @dfa - * @trans: transition table for indexed by named x transitions - * - * File permission are determined by matching a path against @dfa and then - * then using the value of the accept entry for the matching state as - * an index into @perms. If a named exec transition is required it is - * looked up in the transition table. - */ -struct aa_file_rules { - unsigned int start; - struct aa_dfa *dfa; - /* struct perms perms; */ - struct aa_domain trans; - /* TODO: add delegate table */ -}; - -struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state, - struct path_cond *cond); -unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, - const char *name, struct path_cond *cond, - struct aa_perms *perms); - -int __aa_path_perm(const char *op, struct aa_profile *profile, - const char *name, u32 request, struct path_cond *cond, - int flags, struct aa_perms *perms); -int aa_path_perm(const char *op, struct aa_label *label, - const struct path *path, int flags, u32 request, - struct path_cond *cond); - -int aa_path_link(struct aa_label *label, struct dentry *old_dentry, - const struct path *new_dir, struct dentry *new_dentry); - -int aa_file_perm(const char *op, struct aa_label *label, struct file *file, - u32 request); +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, + struct aa_policydb *file_rules, + aa_state_t state, struct path_cond *cond); +aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, + const char *name, struct path_cond *cond, + struct aa_perms *perms); + +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms); +int aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + int flags, u32 request, struct path_cond *cond); + +int aa_path_link(const struct cred *subj_cred, struct aa_label *label, + struct dentry *old_dentry, const struct path *new_dir, + struct dentry *new_dentry); + +int aa_file_perm(const char *op, const struct cred *subj_cred, + struct aa_label *label, struct file *file, + u32 request, bool in_atomic); 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); -} /** - * aa_map_file_perms - map file flags to AppArmor permissions + * aa_map_file_to_perms - map file flags to AppArmor permissions * @file: open file to map flags to AppArmor permissions * * Returns: apparmor permission set for the file diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index 656fdb81c8a0..323dd071afe9 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2017 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_IPC_H @@ -17,18 +13,11 @@ #include <linux/sched.h> -struct aa_profile; - -#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 - -#define AA_PTRACE_PERM_MASK (AA_PTRACE_READ | AA_PTRACE_TRACE | \ - AA_MAY_BE_READ | AA_MAY_BE_TRACED) +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 -int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee, - u32 request); +int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender, + const struct cred *target_cred, struct aa_label *target, + int sig); #endif /* __AA_IPC_H */ diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 9a283b722755..c0812dbc1b5b 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor label definitions * * Copyright 2017 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 @@ -23,6 +19,7 @@ #include "lib.h" struct aa_ns; +struct aa_ruleset; #define LOCAL_VEC_ENTRIES 8 #define DEFINE_VEC(T, V) \ @@ -81,10 +78,6 @@ struct aa_labelset { #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 */ @@ -100,6 +93,8 @@ enum label_flags { FLAG_STALE = 0x800, /* replaced/removed */ FLAG_RENAMED = 0x1000, /* label has renaming in it */ FLAG_REVOKED = 0x2000, /* label has revocation in it */ + FLAG_DEBUG1 = 0x4000, + FLAG_DEBUG2 = 0x8000, /* These flags must correspond with PATH_flags */ /* TODO: add new path flags */ @@ -115,7 +110,7 @@ struct label_it { int i, j; }; -/* struct aa_label - lazy labeling struct +/* struct aa_label_base - base info of label * @count: ref count of active users * @node: rbtree position * @rcu: rcu callback struct @@ -124,7 +119,10 @@ struct label_it { * @flags: stale and other flags - values may change under label set lock * @secid: secid that references this label * @size: number of entries in @ent[] - * @ent: set of profiles for label, actual size determined by @size + * @mediates: bitmask for label_mediates + * profile: label vec when embedded in a profile FLAG_PROFILE is set + * rules: variable length rules in a profile FLAG_PROFILE is set + * vec: vector of profiles comprising the compound label */ struct aa_label { struct kref count; @@ -135,7 +133,18 @@ struct aa_label { long flags; u32 secid; int size; - struct aa_profile *vec[]; + u64 mediates; + union { + struct { + /* only used is the label is a profile, size of + * rules[] is determined by the profile + * profile[1] is poison or null as guard + */ + struct aa_profile *profile[2]; + DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules); + }; + DECLARE_FLEX_ARRAY(struct aa_profile *, vec); + }; }; #define last_error(E, FN) \ @@ -152,6 +161,7 @@ do { \ #define __label_make_stale(X) ((X)->flags |= FLAG_STALE) #define labels_ns(X) (vec_ns(&((X)->vec[0]), (X)->size)) #define labels_set(X) (&labels_ns(X)->labels) +#define labels_view(X) labels_ns(X) #define labels_profile(X) ((X)->vec[(X)->size - 1]) @@ -165,31 +175,7 @@ int aa_label_next_confined(struct aa_label *l, int i); #define label_for_each_cont(I, L, P) \ for (++((I).i); ((P) = (L)->vec[(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) - -/* 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)->vec[(I).i]) && ((P2) = (L2)->vec[(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; \ -}) /* for each profile that is enforcing confinement in a label */ #define label_for_each_confined(I, L, P) \ @@ -260,31 +246,30 @@ for ((I).i = (I).j = 0; \ #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; \ -}) +static inline bool label_mediates(struct aa_label *L, unsigned char C) +{ + return (L)->mediates & (((u64) 1) << (C)); +} +static inline bool label_mediates_safe(struct aa_label *L, unsigned char C) +{ + if (C > AA_CLASS_LAST) + return false; + return label_mediates(L, C); +} void aa_labelset_destroy(struct aa_labelset *ls); void aa_labelset_init(struct aa_labelset *ls); void __aa_labelset_update_subtree(struct aa_ns *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); +bool aa_label_init(struct aa_label *label, int size, gfp_t gfp); struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp); bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub); +bool aa_label_is_unconfined_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); @@ -294,8 +279,6 @@ bool aa_label_replace(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_label *l); - struct aa_profile *aa_label_next_in_merge(struct label_it *I, struct aa_label *a, struct aa_label *b); @@ -310,6 +293,7 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp); #define FLAG_SHOW_MODE 1 #define FLAG_VIEW_SUBNS 2 #define FLAG_HIDDEN_UNCONFINED 4 +#define FLAG_ABS_ROOT 8 int aa_label_snxprint(char *str, size_t size, struct aa_ns *view, struct aa_label *label, int flags); int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, @@ -322,18 +306,45 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, struct aa_label *label, int flags, gfp_t gfp); void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, gfp_t gfp); -void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp); -void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp); void aa_label_printk(struct aa_label *label, gfp_t gfp); +struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str, + size_t n, gfp_t gfp, bool create, + bool force_stack); struct aa_label *aa_label_parse(struct aa_label *base, const char *str, gfp_t gfp, bool create, bool force_stack); +static inline const char *aa_label_strn_split(const char *str, int n) +{ + const char *pos; + aa_state_t state; + + state = aa_dfa_matchn_until(stacksplitdfa, DFA_START, str, n, &pos); + if (!ACCEPT_TABLE(stacksplitdfa)[state]) + return NULL; + + return pos - 3; +} + +static inline const char *aa_label_str_split(const char *str) +{ + const char *pos; + aa_state_t state; + + state = aa_dfa_match_until(stacksplitdfa, DFA_START, str, &pos); + if (!ACCEPT_TABLE(stacksplitdfa)[state]) + return NULL; + + return pos - 3; +} + + struct aa_perms; -int aa_label_match(struct aa_profile *profile, struct aa_label *label, - unsigned int state, bool subns, u32 request, - struct aa_perms *perms); +struct aa_ruleset; +int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, + struct aa_label *label, aa_state_t state, bool subns, + u32 request, struct aa_perms *perms); /** @@ -418,6 +429,13 @@ static inline void aa_put_label(struct aa_label *l) kref_put(&l->count, aa_label_kref); } +/* wrapper fn to indicate semantics of the check */ +static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label, + struct aa_label *obj_label) +{ + return aa_label_is_subset(obj_label, subj_label); +} + struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp); void aa_proxy_kref(struct kref *kref); diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 436b3a722357..444197075fd6 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor lib definitions * * 2017 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_LIB_H @@ -16,43 +12,64 @@ #include <linux/slab.h> #include <linux/fs.h> +#include <linux/lsm_hooks.h> #include "match.h" -/* Provide our own test for whether a write lock is held for asserts - * this is because on none SMP systems write_can_lock will always - * resolve to true, which is what you want for code making decisions - * based on it, but wrong for asserts checking that the lock is held - */ -#ifdef CONFIG_SMP -#define write_is_locked(X) !write_can_lock(X) -#else -#define write_is_locked(X) (1) -#endif /* CONFIG_SMP */ +extern struct aa_dfa *stacksplitdfa; /* - * DEBUG remains global (no per profile flag) since it is mostly used in sysctl - * which is not related to profile accesses. + * split individual debug cases out in preparation for finer grained + * debug controls in the future. */ - -#define DEBUG_ON (aa_g_debug) #define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args) -#define AA_DEBUG(fmt, args...) \ + +#define DEBUG_NONE 0 +#define DEBUG_LABEL_ABS_ROOT 1 +#define DEBUG_LABEL 2 +#define DEBUG_DOMAIN 4 +#define DEBUG_POLICY 8 +#define DEBUG_INTERFACE 0x10 + +#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */ +#define DEBUG_PARSE_ERROR (-1) + +#define DEBUG_ON (aa_g_debug != DEBUG_NONE) +#define DEBUG_ABS_ROOT (aa_g_debug & DEBUG_LABEL_ABS_ROOT) + +#define AA_DEBUG(opt, fmt, args...) \ do { \ - if (DEBUG_ON) \ - pr_debug_ratelimited("AppArmor: " fmt, ##args); \ + if (aa_g_debug & opt) \ + pr_warn_ratelimited("%s: " fmt, __func__, ##args); \ } while (0) +#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \ +do { \ + if ((LAB)->flags & FLAG_DEBUG1) \ + AA_DEBUG(X, fmt, args); \ +} while (0) #define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X) -#define AA_BUG(X, args...) AA_BUG_FMT((X), "" args) +#define AA_BUG(X, args...) \ + do { \ + _Pragma("GCC diagnostic ignored \"-Wformat-zero-length\""); \ + AA_BUG_FMT((X), "" args); \ + _Pragma("GCC diagnostic warning \"-Wformat-zero-length\""); \ + } while (0) #ifdef CONFIG_SECURITY_APPARMOR_DEBUG_ASSERTS #define AA_BUG_FMT(X, fmt, args...) \ WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __func__, ##args) #else -#define AA_BUG_FMT(X, fmt, args...) +#define AA_BUG_FMT(X, fmt, args...) \ + do { \ + BUILD_BUG_ON_INVALID(X); \ + no_printk(fmt, ##args); \ + } while (0) #endif +int aa_parse_debug_params(const char *str); +int aa_print_debug_params(char *buffer); + #define AA_ERROR(fmt, args...) \ pr_err_ratelimited("AppArmor: " fmt, ##args) @@ -61,11 +78,13 @@ extern int apparmor_initialized; /* fn's in lib */ const char *skipn_spaces(const char *str, size_t n); -char *aa_split_fqname(char *args, char **ns_name); const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, size_t *ns_len); void aa_info_message(const char *str); +/* Security blob offsets */ +extern struct lsm_blob_sizes apparmor_blob_sizes; + /** * aa_strneq - compare null terminated @str to a non null terminated substring * @str: a null terminated string @@ -88,8 +107,8 @@ static inline bool aa_strneq(const char *str, const char *sub, int len) * character which is not used in standard matching and is only * used to separate pairs. */ -static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, - unsigned int start) +static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa, + aa_state_t start) { /* the null transition only needs the string's null terminator byte */ return aa_dfa_next(dfa, start, 0); @@ -97,9 +116,16 @@ static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, static inline bool path_mediated_fs(struct dentry *dentry) { - return !(dentry->d_sb->s_flags & MS_NOUSER); + return !(dentry->d_sb->s_flags & SB_NOUSER); } +struct aa_str_table { + int size; + char **table; +}; + +void aa_free_str_table(struct aa_str_table *table); +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp); struct counted_str { struct kref count; @@ -145,7 +171,7 @@ struct aa_policy { /** * basename - find the last component of an hname - * @name: hname to find the base profile name component of (NOT NULL) + * @hname: hname to find the base profile name component of (NOT NULL) * * Returns: the tail (base profile name) name component of an hname */ @@ -227,7 +253,7 @@ void aa_policy_destroy(struct aa_policy *policy); */ #define fn_label_build(L, P, GFP, FN) \ ({ \ - __label__ __cleanup, __done; \ + __label__ __do_cleanup, __done; \ struct aa_label *__new_; \ \ if ((L)->size > 1) { \ @@ -245,7 +271,7 @@ void aa_policy_destroy(struct aa_policy *policy); __new_ = (FN); \ AA_BUG(!__new_); \ if (IS_ERR(__new_)) \ - goto __cleanup; \ + goto __do_cleanup; \ __lvec[__j++] = __new_; \ } \ for (__j = __count = 0; __j < (L)->size; __j++) \ @@ -267,7 +293,7 @@ void aa_policy_destroy(struct aa_policy *policy); vec_cleanup(profile, __pvec, __count); \ } else \ __new_ = NULL; \ -__cleanup: \ +__do_cleanup: \ vec_cleanup(label, __lvec, (L)->size); \ } else { \ (P) = labels_profile(L); \ @@ -275,7 +301,7 @@ __cleanup: \ } \ __done: \ if (!__new_) \ - AA_DEBUG("label build failed\n"); \ + AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ (__new_); \ }) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index add4c6726558..1fbe82f5021b 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2012 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_MATCH_H @@ -21,7 +17,7 @@ #define DFA_START 1 -/** +/* * 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 @@ -40,6 +36,11 @@ */ #define YYTH_MAGIC 0x1B5E783D +#define YYTH_FLAG_DIFF_ENCODE 1 +#define YYTH_FLAG_OOB_TRANS 2 +#define YYTH_FLAGS (YYTH_FLAG_DIFF_ENCODE | YYTH_FLAG_OOB_TRANS) + +#define MAX_OOB_SUPPORTED 1 struct table_set_header { u32 th_magic; /* YYTH_MAGIC */ @@ -86,10 +87,12 @@ struct table_header { char td_data[]; }; -#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF]->td_data)) +#define TABLE_DATAU16(TABLE) ((u16 *)((TABLE)->td_data)) +#define TABLE_DATAU32(TABLE) ((u32 *)((TABLE)->td_data)) +#define DEFAULT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_DEF]->td_data)) #define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data)) -#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data)) -#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data)) +#define NEXT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_NXT]->td_data)) +#define CHECK_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_CHK]->td_data)) #define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data)) #define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data)) #define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data)) @@ -97,11 +100,10 @@ struct table_header { struct aa_dfa { struct kref count; u16 flags; + u32 max_oob; struct table_header *tables[YYTD_ID_TSIZE]; }; -extern struct aa_dfa *nulldfa; - #define byte_to_byte(X) (X) #define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \ @@ -119,19 +121,38 @@ static inline size_t table_size(size_t len, size_t el_size) return ALIGN(sizeof(struct table_header) + len * el_size, 8); } -int aa_setup_dfa_engine(void); -void aa_teardown_dfa_engine(void); +#define aa_state_t unsigned int struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags); -unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, - const char *str, int len); -unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, - const char *str); -unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, - const char c); +aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, + const char *str, int len); +aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, + const char *str); +aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c); +aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state); +aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, const char **retpos); +aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, int n, const char **retpos); void aa_dfa_free_kref(struct kref *kref); +/* This needs to be a power of 2 */ +#define WB_HISTORY_SIZE 32 +struct match_workbuf { + unsigned int pos; + unsigned int len; + aa_state_t history[WB_HISTORY_SIZE]; +}; +#define DEFINE_MATCH_WB(N) \ +struct match_workbuf N = { \ + .pos = 0, \ + .len = 0, \ +} + +aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, + const char *str, unsigned int *count); + /** * aa_get_dfa - increment refcount on dfa @p * @dfa: dfa (MAYBE NULL) @@ -159,4 +180,11 @@ static inline void aa_put_dfa(struct aa_dfa *dfa) kref_put(&dfa->count, aa_dfa_free_kref); } +#define MATCH_FLAG_DIFF_ENCODE 0x80000000 +#define MARK_DIFF_ENCODE 0x40000000 +#define MATCH_FLAG_OOB_TRANSITION 0x20000000 +#define MATCH_FLAGS_MASK 0xff000000 +#define MATCH_FLAGS_VALID (MATCH_FLAG_DIFF_ENCODE | MATCH_FLAG_OOB_TRANSITION) +#define MATCH_FLAGS_INVALID (MATCH_FLAGS_MASK & ~MATCH_FLAGS_VALID) + #endif /* __AA_MATCH_H */ diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h new file mode 100644 index 000000000000..46834f828179 --- /dev/null +++ b/security/apparmor/include/mount.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor file mediation function definitions. + * + * Copyright 2017 Canonical Ltd. + */ + +#ifndef __AA_MOUNT_H +#define __AA_MOUNT_H + +#include <linux/fs.h> +#include <linux/path.h> + +#include "domain.h" +#include "policy.h" + +/* mount perms */ +#define AA_MAY_PIVOTROOT 0x01 +#define AA_MAY_MOUNT 0x02 +#define AA_MAY_UMOUNT 0x04 +#define AA_AUDIT_DATA 0x40 +#define AA_MNT_CONT_MATCH 0x40 + +#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) + +int aa_remount(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + unsigned long flags, void *data); + +int aa_bind_mount(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + const char *old_name, unsigned long flags); + + +int aa_mount_change_type(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + unsigned long flags); + +int aa_move_mount_old(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + const char *old_name); +int aa_move_mount(const struct cred *subj_cred, + struct aa_label *label, const struct path *from_path, + const struct path *to_path); + +int aa_new_mount(const struct cred *subj_cred, + struct aa_label *label, const char *dev_name, + const struct path *path, const char *type, unsigned long flags, + void *data); + +int aa_umount(const struct cred *subj_cred, + struct aa_label *label, struct vfsmount *mnt, int flags); + +int aa_pivotroot(const struct cred *subj_cred, + struct aa_label *label, const struct path *old_path, + const 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 000000000000..0d0b0ce42723 --- /dev/null +++ b/security/apparmor/include/net.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor network mediation definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2017 Canonical Ltd. + */ + +#ifndef __AA_NET_H +#define __AA_NET_H + +#include <net/sock.h> +#include <linux/path.h> + +#include "apparmorfs.h" +#include "label.h" +#include "perms.h" +#include "policy.h" + +#define AA_MAY_SEND AA_MAY_WRITE +#define AA_MAY_RECEIVE AA_MAY_READ + +#define AA_MAY_SHUTDOWN AA_MAY_DELETE + +#define AA_MAY_CONNECT AA_MAY_OPEN +#define AA_MAY_ACCEPT 0x00100000 + +#define AA_MAY_BIND 0x00200000 +#define AA_MAY_LISTEN 0x00400000 + +#define AA_MAY_SETOPT 0x01000000 +#define AA_MAY_GETOPT 0x02000000 + +#define NET_PERMS_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \ + AA_MAY_SHUTDOWN | AA_MAY_BIND | AA_MAY_LISTEN | \ + AA_MAY_CONNECT | AA_MAY_ACCEPT | AA_MAY_SETATTR | \ + AA_MAY_GETATTR | AA_MAY_SETOPT | AA_MAY_GETOPT) + +#define NET_FS_PERMS (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CREATE | \ + AA_MAY_SHUTDOWN | AA_MAY_CONNECT | AA_MAY_RENAME |\ + AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_CHMOD | \ + AA_MAY_CHOWN | AA_MAY_CHGRP | AA_MAY_LOCK | \ + AA_MAY_MPROT) + +#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \ + AA_MAY_ACCEPT) +struct aa_sk_ctx { + struct aa_label __rcu *label; + struct aa_label __rcu *peer; + struct aa_label __rcu *peer_lastupdate; /* ptr cmp only, no deref */ +}; + +static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) +{ + return sk->sk_security + apparmor_blob_sizes.lbs_sock; +} + +#define DEFINE_AUDIT_NET(NAME, OP, CRED, SK, F, T, P) \ + struct lsm_network_audit NAME ## _net = { .sk = (SK), \ + .family = (F)}; \ + DEFINE_AUDIT_DATA(NAME, \ + ((SK) && (F) != AF_UNIX) ? LSM_AUDIT_DATA_NET : \ + LSM_AUDIT_DATA_NONE, \ + AA_CLASS_NET, \ + OP); \ + NAME.common.u.net = &(NAME ## _net); \ + NAME.subj_cred = (CRED); \ + NAME.net.type = (T); \ + NAME.net.protocol = (P) + +#define DEFINE_AUDIT_SK(NAME, OP, CRED, SK) \ + DEFINE_AUDIT_NET(NAME, OP, CRED, SK, (SK)->sk_family, (SK)->sk_type, \ + (SK)->sk_protocol) + + +struct aa_secmark { + u8 audit; + u8 deny; + u32 secid; + char *label; +}; + +extern struct aa_sfs_entry aa_sfs_entry_network[]; +extern struct aa_sfs_entry aa_sfs_entry_networkv9[]; + +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, struct aa_perms *p, + struct apparmor_audit_data *ad); +/* passing in state returned by XXX_mediates_AF() */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 af, int type, int protocol, + struct aa_perms **p, const char **info); +void audit_net_cb(struct audit_buffer *ab, void *va); +int aa_profile_af_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request, u16 family, int type, int protocol); +int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, u16 family, + int type, int protocol); +static inline int aa_profile_af_sk_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request, + struct sock *sk) +{ + return aa_profile_af_perm(profile, ad, request, sk->sk_family, + sk->sk_type, sk->sk_protocol); +} +int aa_sk_perm(const char *op, u32 request, struct sock *sk); + +int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, + struct file *file); + +int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, + u32 secid, const struct sock *sk); + +#endif /* __AA_NET_H */ diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 05fb3305671e..8bb915d48dc7 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,82 +6,28 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_PATH_H #define __AA_PATH_H - 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_DELEGATE_DELETED = 0x10000, /* delegate deleted files */ + PATH_MEDIATE_DELETED = 0x20000, /* mediate deleted paths */ }; int aa_path_name(const struct path *path, int flags, char *buffer, const char **name, const char **info, const char *disconnected); -#define MAX_PATH_BUFFERS 2 - -/* Per cpu buffers used during mediation */ -/* preallocated buffers to use during path lookups */ -struct aa_buffers { - char *buf[MAX_PATH_BUFFERS]; -}; - -#include <linux/percpu.h> -#include <linux/preempt.h> - -DECLARE_PER_CPU(struct aa_buffers, aa_buffers); - -#define COUNT_ARGS(X...) COUNT_ARGS_HELPER(, ##X, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) -#define COUNT_ARGS_HELPER(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, n, X...) n -#define CONCAT(X, Y) X ## Y -#define CONCAT_AFTER(X, Y) CONCAT(X, Y) - -#define ASSIGN(FN, X, N) ((X) = FN(N)) -#define EVAL1(FN, X) ASSIGN(FN, X, 0) /*X = FN(0)*/ -#define EVAL2(FN, X, Y...) do { ASSIGN(FN, X, 1); EVAL1(FN, Y); } while (0) -#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...) EVAL(__get_buffer, X) - -#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) +#define IN_ATOMIC true +char *aa_get_buffer(bool in_atomic); +void aa_put_buffer(char *buf); #endif /* __AA_PATH_H */ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index 2b27bb79aec4..37a3781b99a0 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -1,14 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor basic permission sets definitions. * * Copyright 2017 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 @@ -52,6 +48,9 @@ #define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ +#define AA_MAY_CREATE_SQPOLL AA_MAY_CREATE +#define AA_MAY_OVERRIDE_CRED AA_MAY_APPEND +#define AA_URING_PERM_MASK (AA_MAY_OVERRIDE_CRED | AA_MAY_CREATE_SQPOLL) #define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \ AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \ @@ -69,29 +68,90 @@ 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 subtree; /* allow perm on full subtree only when allow is set */ u32 cond; /* set only when ~allow and ~deny */ - u32 hide; /* set only when ~allow | deny */ + u32 kill; /* set only when ~allow | deny */ + u32 complain; /* accumulates only used when ~allow & ~deny */ u32 prompt; /* accumulates only used when ~allow & ~deny */ - /* Reserved: - * u32 subtree; / * set only when allow is set * / - */ - u16 xindex; + u32 audit; /* set only when allow is set */ + u32 quiet; /* set only when ~allow | deny */ + u32 hide; /* set only when ~allow | deny */ + + + u32 xindex; + u32 tag; /* tag string index, if present */ + u32 label; /* label string index, if present */ }; +/* + * Indexes are broken into a 24 bit index and 8 bit flag. + * For the index to be valid there must be a value in the flag + */ +#define AA_INDEX_MASK 0x00ffffff +#define AA_INDEX_FLAG_MASK 0xff000000 +#define AA_INDEX_NONE 0 + #define ALL_PERMS_MASK 0xffffffff extern struct aa_perms nullperms; extern struct aa_perms allperms; +/** + * 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 + */ +static inline 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->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; + accum->subtree |= addend->subtree & ~addend->deny; + + if (!accum->xindex) + accum->xindex = addend->xindex; + if (!accum->tag) + accum->tag = addend->tag; + if (!accum->label) + accum->label = addend->label; +} + +/** + * aa_perms_accum - accumulate perms, masking off overlapping perms + * @accum: perms struct to accumulate into + * @addend: perms struct to add to @accum + */ +static inline 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->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; + accum->subtree &= addend->subtree & ~accum->deny; + + if (!accum->xindex) + accum->xindex = addend->xindex; + if (!accum->tag) + accum->tag = addend->tag; + if (!accum->label) + accum->label = addend->label; +} #define xcheck(FN1, FN2) \ ({ \ @@ -133,23 +193,27 @@ extern struct aa_perms allperms; #define xcheck_labels_profiles(L1, L2, FN, args...) \ xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args) +#define xcheck_labels(L1, L2, P, FN1, FN2) \ + xcheck(fn_for_each((L1), (P), (FN1)), fn_for_each((L2), (P), (FN2))) + + +extern struct aa_perms default_perms; + -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_perm_mask_to_str(char *str, size_t str_size, const char *chrs, + u32 mask); +void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, + u32 mask); void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, - u32 chrsmask, const char **names, u32 namesmask); + u32 chrsmask, const char * const *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, struct aa_label *label, +void aa_profile_match_label(struct aa_profile *profile, + struct aa_ruleset *rules, struct aa_label *label, int type, u32 request, 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, + u32 request, struct apparmor_audit_data *ad, void (*cb)(struct audit_buffer *, void *)); #endif /* __AA_PERM_H */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 17fe41a9cac3..4c50875c9d13 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_POLICY_H @@ -30,6 +26,7 @@ #include "file.h" #include "lib.h" #include "label.h" +#include "net.h" #include "perms.h" #include "resource.h" @@ -37,6 +34,7 @@ struct aa_ns; extern int unprivileged_userns_apparmor_policy; +extern int aa_unprivileged_unconfined_restricted; extern const char *const aa_profile_mode_names[]; #define APPARMOR_MODE_NAMES_MAX_INDEX 4 @@ -47,14 +45,25 @@ extern const char *const aa_profile_mode_names[]; #define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN) +#define USER_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_USER) + #define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL) #define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT) +#define CHECK_DEBUG1(_profile) ((_profile)->label.flags & FLAG_DEBUG1) + +#define CHECK_DEBUG2(_profile) ((_profile)->label.flags & FLAG_DEBUG2) + #define profile_is_stale(_profile) (label_is_stale(&(_profile)->label)) #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) +/* flags in the dfa accept2 table */ +enum dfa_accept_flags { + ACCEPT_FLAG_OWNER = 1, +}; + /* * FIXME: currently need a clean way to replace and remove profiles as a * set. It should be done at the namespace level. @@ -66,20 +75,72 @@ enum profile_mode { APPARMOR_COMPLAIN, /* allow and log access violations */ APPARMOR_KILL, /* kill task on access violation */ APPARMOR_UNCONFINED, /* profile set to unconfined */ + APPARMOR_USER, /* modified complain mode to userspace */ }; /* struct aa_policydb - match engine for a policy + * count: refcount for the pdb * dfa: dfa pattern match + * perms: table of permissions + * strs: table of strings, index by x * start: set of start states for the different classes of data */ struct aa_policydb { - /* Generic policy DFA specific rule types will be subsections of it */ + struct kref count; struct aa_dfa *dfa; - unsigned int start[AA_CLASS_LAST + 1]; - + struct { + struct aa_perms *perms; + u32 size; + }; + struct aa_str_table trans; + aa_state_t start[AA_CLASS_LAST + 1]; }; +extern struct aa_policydb *nullpdb; + +struct aa_policydb *aa_alloc_pdb(gfp_t gfp); +void aa_pdb_free_kref(struct kref *kref); + +/** + * aa_get_pdb - increment refcount on @pdb + * @pdb: policydb (MAYBE NULL) + * + * Returns: pointer to @pdb if @pdb is NULL will return NULL + * Requires: @pdb must be held with valid refcount when called + */ +static inline struct aa_policydb *aa_get_pdb(struct aa_policydb *pdb) +{ + if (pdb) + kref_get(&(pdb->count)); + + return pdb; +} + +/** + * aa_put_pdb - put a pdb refcount + * @pdb: pdb to put refcount (MAYBE NULL) + * + * Requires: if @pdb != NULL that a valid refcount be held + */ +static inline void aa_put_pdb(struct aa_policydb *pdb) +{ + if (pdb) + kref_put(&pdb->count, aa_pdb_free_kref); +} + +/* lookup perm that doesn't have and object conditional */ +static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, + aa_state_t state) +{ + unsigned int index = ACCEPT_TABLE(policy->dfa)[state]; + + if (!(policy->perms)) + return &default_perms; + + return &(policy->perms[index]); +} + /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes @@ -93,29 +154,70 @@ struct aa_data { struct rhash_head head; }; +/* struct aa_ruleset - data covering mediation rules + * @list: list the rule is on + * @size: the memory consumed by this ruleset + * @policy: general match rules governing policy + * @file: The set of rules governing basic file access and domain transitions + * @caps: capabilities for the profile + * @rlimits: rlimits for the profile + * @secmark_count: number of secmark entries + * @secmark: secmark label match info + */ +struct aa_ruleset { + int size; + + /* TODO: merge policy and file */ + struct aa_policydb *policy; + struct aa_policydb *file; + struct aa_caps caps; + + struct aa_rlimit rlimits; + + int secmark_count; + struct aa_secmark *secmark; +}; + + +/* struct aa_attachment - data and rules for a profiles attachment + * @list: + * @xmatch_str: human readable attachment string + * @xmatch: optional extended matching for unconfined executables names + * @xmatch_len: xmatch prefix len, used to determine xmatch priority + * @xattr_count: number of xattrs in table + * @xattrs: table of xattrs + */ +struct aa_attachment { + const char *xmatch_str; + struct aa_policydb *xmatch; + unsigned int xmatch_len; + int xattr_count; + char **xattrs; +}; /* 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 * @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 + * * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior + * @signal: the signal that should be used when kill is used * @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 - * @rlimits: rlimits for the profile + * @attach: attachment rules for the profile + * @rules: rules to be enforced * + * learning_cache: the accesses learned in complain mode + * raw_data: rawdata of the loaded profile policy + * hash: cryptographic hash of the profile * @dents: dentries for the profiles file entries in apparmorfs * @dirname: name of the profile dir in apparmorfs + * @dents: set of dentries associated with the profile * @data: hashtable for free-form policy aa_data + * @label - label this profile is an extension of + * @rules - label with the rule vec on its end * * The AppArmor profile contains the basic confinement data. Each profile * has a name, and exists in a namespace. The @name and @exec_match are @@ -136,25 +238,22 @@ struct aa_profile { struct aa_ns *ns; const char *rename; - const char *attach; - struct aa_dfa *xmatch; - int xmatch_len; enum audit_mode audit; long mode; u32 path_flags; + int signal; const char *disconnected; - int size; - struct aa_policydb policy; - struct aa_file_rules file; - struct aa_caps caps; - struct aa_rlimit rlimits; + struct aa_attachment attach; struct aa_loaddata *rawdata; unsigned char *hash; char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + + int n_rules; + /* special - variable length must be last entry in profile */ struct aa_label label; }; @@ -167,23 +266,19 @@ extern enum profile_mode aa_g_profile_mode; #define profiles_ns(P) ((P)->ns) #define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) -void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); - - -void aa_free_proxy_kref(struct kref *kref); +struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp); struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy, gfp_t gfp); -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp); +struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, + gfp_t gfp); +struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, + const char *base, gfp_t gfp); 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_ns *ns, const char *hname, size_t n); -struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name); struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n); -struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, u32 mask, struct aa_loaddata *udata); @@ -191,9 +286,6 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); -#define PROF_ADD 1 -#define PROF_REPLACE 0 - #define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) /** @@ -209,15 +301,48 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) return labels_profile(aa_get_newest_label(&p->label)); } -#define PROFILE_MEDIATES(P, T) ((P)->policy.start[(T)]) -/* safe version of POLICY_MEDIATES for full range input */ -static inline unsigned int PROFILE_MEDIATES_SAFE(struct aa_profile *profile, - unsigned char class) +static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, + unsigned char class) +{ + if (class <= AA_CLASS_LAST) + return rules->policy->start[class]; + else + return aa_dfa_match_len(rules->policy->dfa, + rules->policy->start[0], &class, 1); +} + +static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules) +{ + return RULE_MEDIATES(rules, AA_CLASS_NETV9); +} + +static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) +{ + /* can not use RULE_MEDIATE_v9AF here, because AF match fail + * can not be distiguished from class match fail, and we only + * fallback to checking older class on class match failure + */ + aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9); + + /* fallback and check v7/8 if v9 is NOT mediated */ + if (!state) + state = RULE_MEDIATES(rules, AA_CLASS_NET); + + return state; +} + + +void aa_compute_profile_mediates(struct aa_profile *profile); +static inline bool profile_mediates(struct aa_profile *profile, + unsigned char class) +{ + return label_mediates(&profile->label, class); +} + +static inline bool 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; + return label_mediates_safe(&profile->label, class); } /** @@ -288,9 +413,14 @@ static inline int AUDIT_MODE(struct aa_profile *profile) return profile->audit; } -bool policy_view_capable(struct aa_ns *ns); -bool policy_admin_capable(struct aa_ns *ns); -int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, +bool aa_policy_view_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns); +bool aa_policy_admin_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns); +int aa_may_manage_policy(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns, u32 mask); +bool aa_current_policy_view_capable(struct aa_ns *ns); +bool aa_current_policy_admin_capable(struct aa_ns *ns); #endif /* __AA_POLICY_H */ diff --git a/security/apparmor/include/policy_compat.h b/security/apparmor/include/policy_compat.h new file mode 100644 index 000000000000..af0e174332df --- /dev/null +++ b/security/apparmor/include/policy_compat.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * Code to provide backwards compatibility with older policy versions, + * by converting/mapping older policy formats into the newer internal + * formats. + * + * Copyright 2022 Canonical Ltd. + */ + +#ifndef __POLICY_COMPAT_H +#define __POLICY_COMPAT_H + +#include "policy.h" + +#define K_ABI_MASK 0x3ff +#define FORCE_COMPLAIN_FLAG 0x800 +#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK)) +#define VERSION_LE(X, Y) (((X) & K_ABI_MASK) <= ((Y) & K_ABI_MASK)) +#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK)) + +#define v5 5 /* base version */ +#define v6 6 /* per entry policydb mediation check */ +#define v7 7 +#define v8 8 /* full network masking */ +#define v9 9 /* xbits are used as permission bits in policydb */ + +int aa_compat_map_xmatch(struct aa_policydb *policy); +int aa_compat_map_policy(struct aa_policydb *policy, u32 version); +int aa_compat_map_file(struct aa_policydb *policy); + +#endif /* __POLICY_COMPAT_H */ diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h index 9605f18624e2..d646070fd966 100644 --- a/security/apparmor/include/policy_ns.h +++ b/security/apparmor/include/policy_ns.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2017 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_NAMESPACE_H @@ -78,6 +74,7 @@ struct aa_ns { struct dentry *dents[AAFS_NS_SIZEOF]; }; +extern struct aa_label *kernel_t; extern struct aa_ns *root_ns; extern const char *aa_hidden_ns_name; @@ -89,10 +86,7 @@ const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns); void aa_free_ns(struct aa_ns *ns); int aa_alloc_root_ns(void); void aa_free_root_ns(void); -void aa_free_ns_kref(struct kref *kref); -struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); -struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n); struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n); struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, @@ -154,15 +148,4 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head, return __aa_findn_ns(head, name, strlen(name)); } -static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base, - const char *hname) -{ - return __aa_lookupn_ns(base, hname, strlen(hname)); -} - -static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name) -{ - return aa_lookupn_ns(view, name, strlen(name)); -} - #endif /* AA_NAMESPACE_H */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index be6cd69ac319..a6f4611ee50c 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __POLICY_INTERFACE_H @@ -20,6 +16,7 @@ #include <linux/dcache.h> #include <linux/workqueue.h> + struct aa_load_ent { struct list_head list; struct aa_profile *new; @@ -32,11 +29,14 @@ 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_FLAG_DEBUG1 2 +#define PACKED_FLAG_DEBUG2 4 #define PACKED_MODE_ENFORCE 0 #define PACKED_MODE_COMPLAIN 1 #define PACKED_MODE_KILL 2 #define PACKED_MODE_UNCONFINED 3 +#define PACKED_MODE_USER 4 struct aa_ns; @@ -45,11 +45,49 @@ enum { AAFS_LOADDATA_REVISION, AAFS_LOADDATA_HASH, AAFS_LOADDATA_DATA, + AAFS_LOADDATA_COMPRESSED_SIZE, AAFS_LOADDATA_DIR, /* must be last actual entry */ AAFS_LOADDATA_NDENTS /* count of entries */ }; /* + * The AppArmor interface treats data as a type byte followed by the + * actual data. The interface has the notion of a named entry + * which has a name (AA_NAME typecode followed by name string) followed by + * the entries typecode and data. Named types allow for optional + * elements and extensions to be added and tested for without breaking + * backwards compatibility. + */ + +enum aa_code { + AA_U8, + AA_U16, + AA_U32, + AA_U64, + AA_NAME, /* same as string except it is items name */ + AA_STRING, + AA_BLOB, + AA_STRUCT, + AA_STRUCTEND, + AA_LIST, + AA_LISTEND, + AA_ARRAY, + AA_ARRAYEND, +}; + +/* + * aa_ext is the read of the buffer containing the serialized profile. The + * data is copied into a kernel buffer in apparmorfs and then handed off to + * the unpack routines. + */ +struct aa_ext { + void *start; + void *end; + void *pos; /* pointer to current position in the buffer */ + u32 version; +}; + +/* * struct aa_loaddata - buffer of policy raw_data set * * there is no loaddata ref for being on ns list, nor a ref from @@ -65,12 +103,17 @@ struct aa_loaddata { struct dentry *dents[AAFS_LOADDATA_NDENTS]; struct aa_ns *ns; char *name; - size_t size; + size_t size; /* the original size of the payload */ + size_t compressed_size; /* the compressed size of the payload */ long revision; /* the ns policy revision this caused */ int abi; unsigned char *hash; - char data[]; + /* Pointer to payload. If @compressed_size > 0, then this is the + * compressed version of the payload, else it is the uncompressed + * version (with the size indicated by @size). + */ + char *data; }; int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); @@ -122,4 +165,17 @@ static inline void aa_put_loaddata(struct aa_loaddata *data) kref_put(&data->count, aa_loaddata_kref); } +#if IS_ENABLED(CONFIG_KUNIT) +bool aa_inbounds(struct aa_ext *e, size_t size); +size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk); +bool aa_unpack_X(struct aa_ext *e, enum aa_code code); +bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name); +bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name); +bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name); +bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size); +size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name); +int aa_unpack_str(struct aa_ext *e, const char **string, const char *name); +int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name); +#endif + #endif /* __POLICY_INTERFACE_H */ diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h index c8fd99c9357d..03dbfdb2f2c0 100644 --- a/security/apparmor/include/procattr.h +++ b/security/apparmor/include/procattr.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,17 +6,12 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_PROCATTR_H #define __AA_PROCATTR_H -int aa_getprocattr(struct aa_label *label, char **string); +int aa_getprocattr(struct aa_label *label, char **string, bool newline); int aa_setprocattr_changehat(char *args, size_t size, int flags); #endif /* __AA_PROCATTR_H */ diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h index 76f1586c9adb..ad2c0da8e64f 100644 --- a/security/apparmor/include/resource.h +++ b/security/apparmor/include/resource.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #ifndef __AA_RESOURCE_H @@ -37,7 +33,8 @@ struct aa_rlimit { extern struct aa_sfs_entry aa_sfs_entry_rlimit[]; int aa_map_resource(int resource); -int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, +int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label, + struct task_struct *task, unsigned int resource, struct rlimit *new_rlim); void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new); diff --git a/security/apparmor/include/secid.h b/security/apparmor/include/secid.h index 95ed86a0f1e2..6025d3849cf8 100644 --- a/security/apparmor/include/secid.h +++ b/security/apparmor/include/secid.h @@ -1,26 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor security identifier (secid) definitions * - * Copyright 2009-2010 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. + * Copyright 2009-2018 Canonical Ltd. */ #ifndef __AA_SECID_H #define __AA_SECID_H +#include <linux/slab.h> #include <linux/types.h> +struct aa_label; + /* secid value that will not be allocated */ #define AA_SECID_INVALID 0 -#define AA_SECID_ALLOC AA_SECID_INVALID -u32 aa_alloc_secid(void); +/* secid value that matches any other secid */ +#define AA_SECID_WILDCARD 1 + +/* sysctl to enable displaying mode when converting secid to secctx */ +extern int apparmor_display_secid_mode; + +struct aa_label *aa_secid_to_label(u32 secid); +int apparmor_secid_to_secctx(u32 secid, struct lsm_context *cp); +int apparmor_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp); +int apparmor_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid); +void apparmor_release_secctx(struct lsm_context *cp); + + +int aa_alloc_secid(struct aa_label *label, gfp_t gfp); void aa_free_secid(u32 secid); #endif /* __AA_SECID_H */ diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h new file mode 100644 index 000000000000..c772668cdc62 --- /dev/null +++ b/security/apparmor/include/sig_names.h @@ -0,0 +1,97 @@ +#include <linux/signal.h> +#include "signal.h" + +/* 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, /* existence 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, +#ifdef SIGSTKFLT + [SIGSTKFLT] = 16, /* -, 16, - */ +#endif + [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(SIGUNUSED) && \ + 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_SIGNAME] = { + "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 existence test mapped to MAXMAPPED_SIG */ +}; + diff --git a/security/apparmor/include/signal.h b/security/apparmor/include/signal.h new file mode 100644 index 000000000000..729763fa7ce6 --- /dev/null +++ b/security/apparmor/include/signal.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation function definitions. + * + * Copyright 2023 Canonical Ltd. + */ + +#ifndef __AA_SIGNAL_H +#define __AA_SIGNAL_H + +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 + +#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1) +#define SIGRT_BASE 128 + +#endif /* __AA_SIGNAL_H */ diff --git a/security/apparmor/include/task.h b/security/apparmor/include/task.h new file mode 100644 index 000000000000..b1aaaf60fa8b --- /dev/null +++ b/security/apparmor/include/task.h @@ -0,0 +1,105 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor task related definitions and mediation + * + * Copyright 2017 Canonical Ltd. + */ + +#ifndef __AA_TASK_H +#define __AA_TASK_H + +static inline struct aa_task_ctx *task_ctx(struct task_struct *task) +{ + return task->security + apparmor_blob_sizes.lbs_task; +} + +/* + * struct aa_task_ctx - information for current task label change + * @nnp: snapshot of label at time of no_new_privs + * @onexec: profile to transition to on next exec (MAY BE NULL) + * @previous: profile the task may return to (MAY BE NULL) + * @token: magic value the task must know for returning to @previous_profile + */ +struct aa_task_ctx { + struct aa_label *nnp; + struct aa_label *onexec; + struct aa_label *previous; + u64 token; +}; + +int aa_replace_current_label(struct aa_label *label); +void aa_set_current_onexec(struct aa_label *label, bool stack); +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_free_task_ctx - free a task_ctx + * @ctx: task_ctx to free (MAYBE NULL) + */ +static inline void aa_free_task_ctx(struct aa_task_ctx *ctx) +{ + if (ctx) { + aa_put_label(ctx->nnp); + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); + } +} + +/** + * aa_dup_task_ctx - duplicate a task context, incrementing reference counts + * @new: a blank task context (NOT NULL) + * @old: the task context to copy (NOT NULL) + */ +static inline void aa_dup_task_ctx(struct aa_task_ctx *new, + const struct aa_task_ctx *old) +{ + *new = *old; + aa_get_label(new->nnp); + aa_get_label(new->previous); + aa_get_label(new->onexec); +} + +/** + * aa_clear_task_ctx_trans - clear transition tracking info from the ctx + * @ctx: task context to clear (NOT NULL) + */ +static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx) +{ + AA_BUG(!ctx); + + aa_put_label(ctx->previous); + aa_put_label(ctx->onexec); + ctx->previous = NULL; + ctx->onexec = NULL; + ctx->token = 0; +} + +#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 + +#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_SFS_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(const struct cred *tracer_cred, struct aa_label *tracer, + const struct cred *tracee_cred, struct aa_label *tracee, + u32 request); + + + +#define AA_USERNS_CREATE 8 + +int aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, u32 request); + +#endif /* __AA_TASK_H */ diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 11e66b5bbc42..df5712cea685 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,119 +6,111 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2017 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/gfp.h> -#include <linux/ptrace.h> #include "include/audit.h" #include "include/capability.h" -#include "include/context.h" +#include "include/cred.h" #include "include/policy.h" #include "include/ipc.h" +#include "include/sig_names.h" + + +static inline int map_signal_num(int sig) +{ + if (sig > SIGRTMAX) + return SIGUNKNOWN; + else if (sig >= SIGRTMIN) + return sig - SIGRTMIN + SIGRT_BASE; + else if (sig < MAXMAPPED_SIG) + return sig_map[sig]; + return SIGUNKNOWN; +} /** - * audit_ptrace_mask - convert mask to permission string - * @buffer: buffer to write string to (NOT NULL) + * audit_signal_mask - convert mask to permission string * @mask: permission mask to convert + * + * Returns: pointer to static string */ -static void audit_ptrace_mask(struct audit_buffer *ab, u32 mask) +static const char *audit_signal_mask(u32 mask) { - switch (mask) { - case MAY_READ: - audit_log_string(ab, "read"); - break; - case MAY_WRITE: - audit_log_string(ab, "trace"); - break; - case AA_MAY_BE_READ: - audit_log_string(ab, "readby"); - break; - case AA_MAY_BE_TRACED: - audit_log_string(ab, "tracedby"); - break; - } + if (mask & MAY_READ) + return "receive"; + if (mask & MAY_WRITE) + return "send"; + return ""; } -/* call back to audit ptrace fields */ -static void audit_ptrace_cb(struct audit_buffer *ab, void *va) +/** + * audit_signal_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_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); + struct apparmor_audit_data *ad = aad(sa); + + if (ad->request & AA_SIGNAL_PERM_MASK) { + audit_log_format(ab, " requested_mask=\"%s\"", + audit_signal_mask(ad->request)); + if (ad->denied & AA_SIGNAL_PERM_MASK) { + audit_log_format(ab, " denied_mask=\"%s\"", + audit_signal_mask(ad->denied)); } } + if (ad->signal == SIGUNKNOWN) + audit_log_format(ab, "signal=unknown(%d)", + ad->unmappedsig); + else if (ad->signal < MAXMAPPED_SIGNAME) + audit_log_format(ab, " signal=%s", sig_names[ad->signal]); + else + audit_log_format(ab, " signal=rtmin+%d", + ad->signal - SIGRT_BASE); audit_log_format(ab, " peer="); - aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, FLAGS_NONE, GFP_ATOMIC); } -/* TODO: conditionals */ -static int profile_ptrace_perm(struct aa_profile *profile, - struct aa_profile *peer, u32 request, - struct common_audit_data *sa) +static int profile_signal_perm(const struct cred *cred, + struct aa_profile *profile, + struct aa_label *peer, u32 request, + struct apparmor_audit_data *ad) { - struct aa_perms perms = { }; + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms perms; + aa_state_t state; - /* need because of peer in cross check */ - if (profile_unconfined(profile) || - !PROFILE_MEDIATES(profile, AA_CLASS_PTRACE)) + if (profile_unconfined(profile)) return 0; - aad(sa)->peer = &peer->label; - aa_profile_match_label(profile, &peer->label, AA_CLASS_PTRACE, request, - &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) + ad->subj_cred = cred; + ad->peer = peer; + /* TODO: secondary cache check <profile, profile, perm> */ + state = RULE_MEDIATES(rules, AA_CLASS_SIGNAL); + if (!state) return 0; - - aad(sa)->label = &tracer->label; - aad(sa)->peer = &tracee->label; - 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); + state = aa_dfa_next(rules->policy->dfa, state, ad->signal); + aa_label_match(profile, rules, peer, state, false, request, &perms); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, ad, audit_signal_cb); } -/** - * aa_may_ptrace - test if tracer task can trace the tracee - * @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 aa_label *tracer, struct aa_label *tracee, - u32 request) +int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender, + const struct cred *target_cred, struct aa_label *target, + int sig) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE); - - return xcheck_labels_profiles(tracer, tracee, cross_ptrace_perm, - request, &sa); + struct aa_profile *profile; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL); + + ad.signal = map_signal_num(sig); + ad.unmappedsig = sig; + return xcheck_labels(sender, target, profile, + profile_signal_perm(subj_cred, profile, target, + MAY_WRITE, &ad), + profile_signal_perm(target_cred, profile, sender, + MAY_READ, &ad)); } - - diff --git a/security/apparmor/label.c b/security/apparmor/label.c index e052eaba1cf6..913678f199c3 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * * This file contains AppArmor label definitions * * Copyright 2017 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/audit.h> @@ -16,7 +12,7 @@ #include <linux/sort.h> #include "include/apparmor.h" -#include "include/context.h" +#include "include/cred.h" #include "include/label.h" #include "include/policy.h" #include "include/secid.h" @@ -49,7 +45,7 @@ static void free_proxy(struct aa_proxy *proxy) /* p->label will not updated any more as p is dead */ aa_put_label(rcu_dereference_protected(proxy->label, true)); memset(proxy, 0, sizeof(*proxy)); - proxy->label = (struct aa_label *) PROXY_POISON; + RCU_INIT_POINTER(proxy->label, (struct aa_label *)PROXY_POISON); kfree(proxy); } } @@ -80,7 +76,7 @@ void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) AA_BUG(!orig); AA_BUG(!new); - AA_BUG(!write_is_locked(&labels_set(orig)->lock)); + lockdep_assert_held_write(&labels_set(orig)->lock); tmp = rcu_dereference_protected(orig->proxy->label, &labels_ns(orig)->lock); @@ -128,7 +124,7 @@ static int ns_cmp(struct aa_ns *a, struct aa_ns *b) } /** - * profile_cmp - profile comparision for set ordering + * profile_cmp - profile comparison for set ordering * @a: profile to compare (NOT NULL) * @b: profile to compare (NOT NULL) * @@ -157,14 +153,15 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b) } /** - * 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 + * vec_cmp - label comparison for set ordering + * @a: aa_profile to compare (NOT NULL) + * @an: length of @a + * @b: aa_profile to compare (NOT NULL) + * @bn: length of @b + * + * Returns: <0 if @a < @b + * ==0 if @a == @b + * >0 if @a > @b */ static int vec_cmp(struct aa_profile **a, int an, struct aa_profile **b, int bn) { @@ -201,18 +198,25 @@ static bool vec_is_stale(struct aa_profile **vec, int n) return false; } -static bool vec_unconfined(struct aa_profile **vec, int n) +static void accum_label_info(struct aa_label *new) { + long u = FLAG_UNCONFINED; int i; - AA_BUG(!vec); + AA_BUG(!new); - for (i = 0; i < n; i++) { - if (!profile_unconfined(vec[i])) - return false; - } + /* size == 1 is a profile and flags must be set as part of creation */ + if (new->size == 1) + return; - return true; + for (i = 0; i < new->size; i++) { + u |= new->vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | + FLAG_STALE); + if (!(u & new->vec[i]->label.flags & FLAG_UNCONFINED)) + u &= ~FLAG_UNCONFINED; + new->mediates |= new->vec[i]->label.mediates; + } + new->flags |= u; } static int sort_cmp(const void *a, const void *b) @@ -257,6 +261,7 @@ static inline int unique(struct aa_profile **vec, int n) * aa_vec_unique - canonical sort and unique a list of profiles * @n: number of refcounted profiles in the list (@n > 0) * @vec: list of profiles to sort and merge + * @flags: null terminator flags of @vec * * Returns: the number of duplicates eliminated == references put * @@ -313,10 +318,8 @@ out: } -static void label_destroy(struct aa_label *label) +void aa_label_destroy(struct aa_label *label) { - struct aa_label *tmp; - AA_BUG(!label); if (!label_isprofile(label)) { @@ -332,16 +335,13 @@ static void label_destroy(struct aa_label *label) } } - if (rcu_dereference_protected(label->proxy->label, true) == label) - rcu_assign_pointer(label->proxy->label, NULL); - + if (label->proxy) { + if (rcu_dereference_protected(label->proxy->label, true) == label) + rcu_assign_pointer(label->proxy->label, NULL); + aa_put_proxy(label->proxy); + } aa_free_secid(label->secid); - tmp = rcu_dereference_protected(label->proxy->label, true); - if (tmp == label) - rcu_assign_pointer(label->proxy->label, NULL); - - aa_put_proxy(label->proxy); label->proxy = (struct aa_proxy *) PROXY_POISON + 1; } @@ -350,7 +350,7 @@ void aa_label_free(struct aa_label *label) if (!label) return; - label_destroy(label); + aa_label_destroy(label); kfree(label); } @@ -402,13 +402,12 @@ static void label_free_or_put_new(struct aa_label *label, struct aa_label *new) aa_put_label(new); } -bool aa_label_init(struct aa_label *label, int size) +bool aa_label_init(struct aa_label *label, int size, gfp_t gfp) { AA_BUG(!label); AA_BUG(size < 1); - label->secid = aa_alloc_secid(); - if (label->secid == AA_SECID_INVALID) + if (aa_alloc_secid(label, gfp) < 0) return false; label->size = size; /* doesn't include null */ @@ -435,13 +434,12 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) AA_BUG(size < 1); /* + 1 for null terminator entry on vec */ - new = kzalloc(sizeof(*new) + sizeof(struct aa_profile *) * (size + 1), - gfp); - AA_DEBUG("%s (%p)\n", __func__, new); + new = kzalloc(struct_size(new, vec, size + 1), gfp); + AA_DEBUG(DEBUG_LABEL, "%s (%p)\n", __func__, new); if (!new) goto fail; - if (!aa_label_init(new, size)) + if (!aa_label_init(new, size, gfp)) goto fail; if (!proxy) { @@ -463,7 +461,7 @@ fail: /** - * label_cmp - label comparision for set ordering + * label_cmp - label comparison for set ordering * @a: label to compare (NOT NULL) * @b: label to compare (NOT NULL) * @@ -496,7 +494,7 @@ int aa_label_next_confined(struct aa_label *label, int i) } /** - * aa_label_next_not_in_set - return the next profile of @sub not in @set + * __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 @@ -555,11 +553,44 @@ bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) return __aa_label_next_not_in_set(&i, set, sub) == NULL; } +/** + * aa_label_is_unconfined_subset - test if @sub is a subset of @set + * @set: label to test against + * @sub: label to test if is subset of @set + * + * This checks for subset but taking into account unconfined. IF + * @sub contains an unconfined profile that does not have a matching + * unconfined in @set then this will not cause the test to fail. + * Conversely we don't care about an unconfined in @set that is not in + * @sub + * + * Returns: true if @sub is special_subset of @set + * else false + */ +bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub) +{ + struct label_it i = { }; + struct aa_profile *p; + + AA_BUG(!set); + AA_BUG(!sub); + + if (sub == set) + return true; + + do { + p = __aa_label_next_not_in_set(&i, set, sub); + if (p && !profile_unconfined(p)) + break; + } while (p); + + return p == NULL; +} /** * __label_remove - remove @label from the label set - * @l: label to remove + * @label: label to remove * @new: label to redirect to * * Requires: labels_set(@label)->lock write_lock @@ -571,7 +602,7 @@ static bool __label_remove(struct aa_label *label, struct aa_label *new) AA_BUG(!ls); AA_BUG(!label); - AA_BUG(!write_is_locked(&ls->lock)); + lockdep_assert_held_write(&ls->lock); if (new) __aa_proxy_redirect(label, new); @@ -608,7 +639,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new) AA_BUG(!ls); AA_BUG(!old); AA_BUG(!new); - AA_BUG(!write_is_locked(&ls->lock)); + lockdep_assert_held_write(&ls->lock); AA_BUG(new->flags & FLAG_IN_TREE); if (!label_is_stale(old)) @@ -618,6 +649,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new) rb_replace_node(&old->node, &new->node, &ls->root); old->flags &= ~FLAG_IN_TREE; new->flags |= FLAG_IN_TREE; + accum_label_info(new); return true; } @@ -645,7 +677,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls, AA_BUG(!ls); AA_BUG(!label); AA_BUG(labels_set(label) != ls); - AA_BUG(!write_is_locked(&ls->lock)); + lockdep_assert_held_write(&ls->lock); AA_BUG(label->flags & FLAG_IN_TREE); /* Figure out where to put new node */ @@ -678,6 +710,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls, rb_link_node(&label->node, parent, new); rb_insert_color(&label->node, &ls->root); label->flags |= FLAG_IN_TREE; + accum_label_info(label); return aa_get_label(label); } @@ -872,28 +905,11 @@ struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, return vec_create_and_insert_label(vec, len, gfp); } -/** - * aa_label_find - find label @label in label set - * @label: label to find (NOT NULL) - * - * Requires: caller to hold a valid ref on l - * - * Returns: refcounted @label if @label is in tree - * refcounted label that is equiv to @label in tree - * else NULL if @label or equiv is not in tree - */ -struct aa_label *aa_label_find(struct aa_label *label) -{ - AA_BUG(!label); - - return vec_find(label->vec, label->size); -} - /** * aa_label_insert - insert label @label into @ls or return existing label - * @ls - labelset to insert @label into - * @label - label to insert + * @ls: labelset to insert @label into + * @label: label to insert * * Requires: caller to hold a valid ref on @label * @@ -1075,8 +1091,6 @@ static struct aa_label *label_merge_insert(struct aa_label *new, else if (k == b->size) return aa_get_label(b); } - if (vec_unconfined(new->vec, new->size)) - new->flags |= FLAG_UNCONFINED; ls = labels_set(new); write_lock_irqsave(&ls->lock, flags); label = __label_insert(labels_set(new), new, false); @@ -1180,7 +1194,6 @@ struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) /** * 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 @@ -1232,39 +1245,35 @@ out: return label; } -static inline bool label_is_visible(struct aa_profile *profile, - struct aa_label *label) -{ - return aa_ns_visible(profile->ns, labels_ns(label), true); -} - /* match a profile and its associated ns component if needed * Assumes visibility test has already been done. * If a subns profile is not to be matched should be prescreened with * visibility test. */ -static inline unsigned int match_component(struct aa_profile *profile, - struct aa_profile *tp, - unsigned int state) +static inline aa_state_t match_component(struct aa_profile *profile, + struct aa_ruleset *rules, + struct aa_profile *tp, + aa_state_t state) { const char *ns_name; if (profile->ns == tp->ns) - return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); + return aa_dfa_match(rules->policy->dfa, state, tp->base.hname); /* try matching with namespace name and then profile */ ns_name = aa_ns_name(profile->ns, tp->ns, true); - state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); - state = aa_dfa_match(profile->policy.dfa, state, ns_name); - state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1); - return aa_dfa_match(profile->policy.dfa, state, tp->base.hname); + state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1); + state = aa_dfa_match(rules->policy->dfa, state, ns_name); + state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1); + return aa_dfa_match(rules->policy->dfa, state, tp->base.hname); } /** * label_compound_match - find perms for full compound label * @profile: profile to find perms for + * @rules: ruleset to search * @label: label to check access permissions for - * @start: state to start match in + * @state: state to start match in * @subns: whether to do permission checks on components in a subns * @request: permissions to request * @perms: perms struct to set @@ -1276,8 +1285,9 @@ static inline unsigned int match_component(struct aa_profile *profile, * check to be stacked. */ static int label_compound_match(struct aa_profile *profile, + struct aa_ruleset *rules, struct aa_label *label, - unsigned int state, bool subns, u32 request, + aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { struct aa_profile *tp; @@ -1287,7 +1297,7 @@ static int label_compound_match(struct aa_profile *profile, label_for_each(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, state); + state = match_component(profile, rules, tp, state); if (!state) goto fail; goto next; @@ -1301,12 +1311,12 @@ next: label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = aa_dfa_match(profile->policy.dfa, state, "//&"); - state = match_component(profile, tp, state); + state = aa_dfa_match(rules->policy->dfa, state, "//&"); + state = match_component(profile, rules, tp, state); if (!state) goto fail; } - aa_compute_perms(profile->policy.dfa, state, perms); + *perms = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -1321,6 +1331,7 @@ fail: /** * label_components_match - find perms for all subcomponents of a label * @profile: profile to find perms for + * @rules: ruleset to search * @label: label to check access permissions for * @start: state to start match in * @subns: whether to do permission checks on components in a subns @@ -1334,20 +1345,21 @@ fail: * check to be stacked. */ static int label_components_match(struct aa_profile *profile, - struct aa_label *label, unsigned int start, + struct aa_ruleset *rules, + struct aa_label *label, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { struct aa_profile *tp; struct label_it i; struct aa_perms tmp; - unsigned int state = 0; + aa_state_t state = 0; /* find first subcomponent to test */ label_for_each(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, start); + state = match_component(profile, rules, tp, start); if (!state) goto fail; goto next; @@ -1357,16 +1369,16 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - aa_compute_perms(profile->policy.dfa, state, &tmp); + tmp = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, subns)) continue; - state = match_component(profile, tp, start); + state = match_component(profile, rules, tp, start); if (!state) goto fail; - aa_compute_perms(profile->policy.dfa, state, &tmp); + tmp = *aa_lookup_perms(rules->policy, state); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } @@ -1384,6 +1396,7 @@ fail: /** * aa_label_match - do a multi-component label match * @profile: profile to match against (NOT NULL) + * @rules: ruleset to search * @label: label to match (NOT NULL) * @state: state to start in * @subns: whether to match subns components @@ -1392,18 +1405,18 @@ fail: * * Returns: the state the match finished in, may be the none matching state */ -int aa_label_match(struct aa_profile *profile, struct aa_label *label, - unsigned int state, bool subns, u32 request, - struct aa_perms *perms) +int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules, + struct aa_label *label, aa_state_t state, bool subns, + u32 request, struct aa_perms *perms) { - int error = label_compound_match(profile, label, state, subns, request, - perms); + int error = label_compound_match(profile, rules, label, state, subns, + request, perms); if (!error) return error; *perms = allperms; - return label_components_match(profile, label, state, subns, request, - perms); + return label_components_match(profile, rules, label, state, subns, + request, perms); } @@ -1431,7 +1444,7 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) if (label->hname || labels_ns(label) != ns) return res; - if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) == -1) + if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) < 0) return res; ls = labels_set(label); @@ -1448,11 +1461,13 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) /* * cached label name is present and visible - * @label->hname only exists if label is namespace hierachical + * @label->hname only exists if label is namespace hierarchical */ -static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label) +static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label, + int flags) { - if (label->hname && labels_ns(label) == ns) + if (label->hname && (!ns || labels_ns(label) == ns) && + !(flags & ~FLAG_SHOW_MODE)) return true; return false; @@ -1461,11 +1476,13 @@ static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label) /* helper macro for snprint routines */ #define update_for_len(total, len, size, str) \ do { \ + size_t ulen = len; \ + \ AA_BUG(len < 0); \ - total += len; \ - len = min(len, size); \ - size -= len; \ - str += len; \ + total += ulen; \ + ulen = min(ulen, size); \ + size -= ulen; \ + str += ulen; \ } while (0) /** @@ -1495,7 +1512,7 @@ static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view, view = profiles_ns(profile); if (view != profile->ns && - (!prev_ns || (prev_ns && *prev_ns != profile->ns))) { + (!prev_ns || (*prev_ns != profile->ns))) { if (prev_ns) *prev_ns = profile->ns; ns_name = aa_ns_name(view, profile->ns, @@ -1532,13 +1549,13 @@ static const char *label_modename(struct aa_ns *ns, struct aa_label *label, label_for_each(i, label, profile) { if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { - if (profile->mode == APPARMOR_UNCONFINED) + count++; + if (profile == profile->ns->unconfined) /* special case unconfined so stacks with * unconfined don't report as mixed. ie. * profile_foo//&:ns1:unconfined (mixed) */ continue; - count++; if (mode == -1) mode = profile->mode; else if (mode != profile->mode) @@ -1600,13 +1617,18 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, struct aa_ns *prev_ns = NULL; struct label_it i; int count = 0, total = 0; - size_t len; + ssize_t len; AA_BUG(!str && size != 0); AA_BUG(!label); - if (!ns) + if (DEBUG_ABS_ROOT && (flags & FLAG_ABS_ROOT)) { + ns = root_ns; + len = snprintf(str, size, "_"); + update_for_len(total, len, size, str); + } else if (!ns) { ns = labels_ns(label); + } label_for_each(i, label, profile) { if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { @@ -1672,7 +1694,7 @@ int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, /** * aa_label_acntsxprint - allocate a __counted string buffer and print label - * @strp: buffer to write to. (MAY BE NULL if @size == 0) + * @strp: buffer to write to. * @ns: namespace profile is being viewed from * @label: label to view (NOT NULL) * @flags: flags controlling what label info is printed @@ -1710,13 +1732,11 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, AA_BUG(!ab); AA_BUG(!label); - if (!ns) - ns = labels_ns(label); - - if (!use_label_hname(ns, label) || display_mode(ns, label, flags)) { + if (!use_label_hname(ns, label, flags) || + display_mode(ns, label, flags)) { len = aa_label_asxprint(&name, ns, label, flags, gfp); - if (len == -1) { - AA_DEBUG("label print error"); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } str = name; @@ -1738,25 +1758,22 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, AA_BUG(!f); AA_BUG(!label); - if (!ns) - ns = labels_ns(label); - - if (!use_label_hname(ns, label)) { + if (!use_label_hname(ns, label, flags)) { char *str; int len; len = aa_label_asxprint(&str, ns, label, flags, gfp); - if (len == -1) { - AA_DEBUG("label print error"); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } - seq_printf(f, "%s", str); + seq_puts(f, str); kfree(str); } else if (display_mode(ns, label, flags)) seq_printf(f, "%s (%s)", label->hname, label_modename(ns, label, flags)); else - seq_printf(f, "%s", label->hname); + seq_puts(f, label->hname); } void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, @@ -1764,16 +1781,13 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, { AA_BUG(!label); - if (!ns) - ns = labels_ns(label); - - if (!use_label_hname(ns, label)) { + if (!use_label_hname(ns, label, flags)) { char *str; int len; len = aa_label_asxprint(&str, ns, label, flags, gfp); - if (len == -1) { - AA_DEBUG("label print error"); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } pr_info("%s", str); @@ -1785,22 +1799,6 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, pr_info("%s", label->hname); } -void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp) -{ - struct aa_ns *ns = aa_get_current_ns(); - - aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp); - aa_put_ns(ns); -} - -void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp) -{ - struct aa_ns *ns = aa_get_current_ns(); - - aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp); - aa_put_ns(ns); -} - void aa_label_printk(struct aa_label *label, gfp_t gfp) { struct aa_ns *ns = aa_get_current_ns(); @@ -1809,14 +1807,17 @@ void aa_label_printk(struct aa_label *label, gfp_t gfp) aa_put_ns(ns); } -static int label_count_str_entries(const char *str) +static int label_count_strn_entries(const char *str, size_t n) { + const char *end = str + n; const char *split; int count = 1; AA_BUG(!str); - for (split = strstr(str, "//&"); split; split = strstr(str, "//&")) { + for (split = aa_label_strn_split(str, end - str); + split; + split = aa_label_strn_split(str, end - str)) { count++; str = split + 3; } @@ -1844,9 +1845,10 @@ static struct aa_profile *fqlookupn_profile(struct aa_label *base, } /** - * aa_label_parse - parse, validate and convert a text string to a label + * aa_label_strn_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) + * @n: length of str to parse, will stop at \0 if encountered before n * @gfp: allocation type * @create: true if should create compound labels if they don't exist * @force_stack: true if should stack even if no leading & @@ -1854,19 +1856,25 @@ static struct aa_profile *fqlookupn_profile(struct aa_label *base, * Returns: the matching refcounted label if present * else ERRPTR */ -struct aa_label *aa_label_parse(struct aa_label *base, const char *str, - gfp_t gfp, bool create, bool force_stack) +struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str, + size_t n, gfp_t gfp, bool create, + bool force_stack) { DEFINE_VEC(profile, vec); struct aa_label *label, *currbase = base; int i, len, stack = 0, error; - char *split; + const char *end = str + n; + const char *split; AA_BUG(!base); AA_BUG(!str); - str = skip_spaces(str); - len = label_count_str_entries(str); + str = skipn_spaces(str, n); + if (str == NULL || (DEBUG_ABS_ROOT && *str == '_' && + base != &root_ns->unconfined->label)) + return ERR_PTR(-EINVAL); + + len = label_count_strn_entries(str, end - str); if (*str == '&' || force_stack) { /* stack on top of base */ stack = base->size; @@ -1874,6 +1882,7 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str, if (*str == '&') str++; } + error = vec_setup(profile, vec, len, gfp); if (error) return ERR_PTR(error); @@ -1881,7 +1890,8 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str, for (i = 0; i < stack; i++) vec[i] = aa_get_profile(base->vec[i]); - for (split = strstr(str, "//&"), i = stack; split && i < len; i++) { + for (split = aa_label_strn_split(str, end - str), i = stack; + split && i < len; i++) { vec[i] = fqlookupn_profile(base, currbase, str, split - str); if (!vec[i]) goto fail; @@ -1892,11 +1902,11 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str, if (vec[i]->ns != labels_ns(currbase)) currbase = &vec[i]->label; str = split + 3; - split = strstr(str, "//&"); + split = aa_label_strn_split(str, end - str); } /* last element doesn't have a split */ if (i < len) { - vec[i] = fqlookupn_profile(base, currbase, str, strlen(str)); + vec[i] = fqlookupn_profile(base, currbase, str, end - str); if (!vec[i]) goto fail; } @@ -1928,6 +1938,12 @@ fail: goto out; } +struct aa_label *aa_label_parse(struct aa_label *base, const char *str, + gfp_t gfp, bool create, bool force_stack) +{ + return aa_label_strn_parse(base, str, strlen(str), gfp, create, + force_stack); +} /** * aa_labelset_destroy - remove all labels from the label set @@ -1995,7 +2011,7 @@ out: /** * __label_update - insert updated version of @label into labelset - * @label - the label to update/repace + * @label: the label to update/replace * * Returns: new label that is up to date * else NULL on failure @@ -2096,7 +2112,7 @@ static void __labelset_update(struct aa_ns *ns) } /** - * __aa_labelset_udate_subtree - update all labels with a stale component + * __aa_labelset_update_subtree - update all labels with a stale component * @ns: ns to start update at (NOT NULL) * * Requires: @ns lock be held @@ -2113,7 +2129,7 @@ void __aa_labelset_update_subtree(struct aa_ns *ns) __labelset_update(ns); list_for_each_entry(child, &ns->sub_ns, base.list) { - mutex_lock(&child->lock); + mutex_lock_nested(&child->lock, child->level); __aa_labelset_update_subtree(child); mutex_unlock(&child->lock); } diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 08ca26bcca77..82dbb97ad406 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/ctype.h> @@ -29,47 +25,144 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; +struct val_table_ent { + const char *str; + int value; +}; + +static struct val_table_ent debug_values_table[] = { + { "N", DEBUG_NONE }, + { "none", DEBUG_NONE }, + { "n", DEBUG_NONE }, + { "0", DEBUG_NONE }, + { "all", DEBUG_ALL }, + { "Y", DEBUG_ALL }, + { "y", DEBUG_ALL }, + { "1", DEBUG_ALL }, + { "abs_root", DEBUG_LABEL_ABS_ROOT }, + { "label", DEBUG_LABEL }, + { "domain", DEBUG_DOMAIN }, + { "policy", DEBUG_POLICY }, + { "interface", DEBUG_INTERFACE }, + { NULL, 0 } +}; + +static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, + const char *name, size_t len) +{ + struct val_table_ent *entry; + + for (entry = table; entry->str != NULL; entry++) { + if (strncmp(entry->str, name, len) == 0 && + strlen(entry->str) == len) + return entry; + } + return NULL; +} + +int aa_parse_debug_params(const char *str) +{ + struct val_table_ent *ent; + const char *next; + int val = 0; + + do { + size_t n = strcspn(str, "\r\n,"); + + next = str + n; + ent = val_table_find_ent(debug_values_table, str, next - str); + if (ent) + val |= ent->value; + else + AA_DEBUG(DEBUG_INTERFACE, "unknown debug type '%.*s'", + (int)(next - str), str); + str = next + 1; + } while (*next != 0); + return val; +} + /** - * aa_split_fqname - split a fqname into a profile and namespace name - * @fqname: a full qualified name in namespace profile format (NOT NULL) - * @ns_name: pointer to portion of the string containing the ns name (NOT NULL) - * - * Returns: profile name or NULL if one is not specified - * - * Split a namespace name from a profile name (see policy.c for naming - * description). If a portion of the name is missing it returns NULL for - * that portion. - * - * NOTE: may modify the @fqname string. The pointers returned point - * into the @fqname string. + * val_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @size: size of the @str buffer + * @table: NUL-terminated character buffer of permission characters (NOT NULL) + * @mask: permission mask to convert */ -char *aa_split_fqname(char *fqname, char **ns_name) +static int val_mask_to_str(char *str, size_t size, + const struct val_table_ent *table, u32 mask) { - char *name = strim(fqname); - - *ns_name = NULL; - if (name[0] == ':') { - char *split = strchr(&name[1], ':'); - *ns_name = skip_spaces(&name[1]); - if (split) { - /* overwrite ':' with \0 */ - *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; + const struct val_table_ent *ent; + int total = 0; + + for (ent = table; ent->str; ent++) { + if (ent->value && (ent->value & mask) == ent->value) { + int len = scnprintf(str, size, "%s%s", total ? "," : "", + ent->str); + size -= len; + str += len; + total += len; + mask &= ~ent->value; + } } - if (name && *name == 0) - name = NULL; - return name; + return total; +} + +int aa_print_debug_params(char *buffer) +{ + if (!aa_g_debug) + return sprintf(buffer, "N"); + return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table, + aa_g_debug); +} + +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp) +{ + char **n; + int i; + + if (t->size == newsize) + return true; + n = kcalloc(newsize, sizeof(*n), gfp); + if (!n) + return false; + for (i = 0; i < min(t->size, newsize); i++) + n[i] = t->table[i]; + for (; i < t->size; i++) + kfree_sensitive(t->table[i]); + if (newsize > t->size) + memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n)); + kfree_sensitive(t->table); + t->table = n; + t->size = newsize; + + return true; +} + +/** + * aa_free_str_table - free entries str table + * @t: the string table to free (MAYBE NULL) + */ +void aa_free_str_table(struct aa_str_table *t) +{ + int i; + + if (t) { + if (!t->table) + return; + + for (i = 0; i < t->size; i++) + kfree_sensitive(t->table[i]); + kfree_sensitive(t->table); + t->table = NULL; + t->size = 0; + } } /** * skipn_spaces - Removes leading whitespace from @str. * @str: The string to be stripped. + * @n: length of str to parse, will stop at \0 if encountered before n * * Returns a pointer to the first non-whitespace character in @str. * if all whitespace will return NULL @@ -90,10 +183,12 @@ const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, const char *end = fqname + n; const char *name = skipn_spaces(fqname, n); - if (!name) - return NULL; *ns_name = NULL; *ns_len = 0; + + if (!name) + return NULL; + if (name[0] == ':') { char *split = strnchr(&name[1], end - &name[1], ':'); *ns_name = skipn_spaces(&name[1], end - &name[1]); @@ -126,10 +221,10 @@ const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, void aa_info_message(const char *str) { if (audit_enabled) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); - aad(&sa)->info = str; - aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); + ad.info = str; + aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, NULL); } printk(KERN_INFO "AppArmor: %s\n", str); } @@ -138,7 +233,7 @@ __counted char *aa_str_alloc(int size, gfp_t gfp) { struct counted_str *str; - str = kmalloc(sizeof(struct counted_str) + size, gfp); + str = kmalloc(struct_size(str, name, size), gfp); if (!str) return NULL; @@ -198,20 +293,30 @@ const char *aa_file_perm_names[] = { /** * aa_perm_mask_to_str - convert a perm mask to its short string * @str: character buffer to store string in (at least 10 characters) + * @str_size: size of the @str buffer + * @chrs: NUL-terminated character buffer of permission characters * @mask: permission mask to convert */ -void aa_perm_mask_to_str(char *str, const char *chrs, u32 mask) +void aa_perm_mask_to_str(char *str, size_t str_size, const char *chrs, u32 mask) { unsigned int i, perm = 1; + size_t num_chrs = strlen(chrs); + + for (i = 0; i < num_chrs; perm <<= 1, i++) { + if (mask & perm) { + /* Ensure that one byte is left for NUL-termination */ + if (WARN_ON_ONCE(str_size <= 1)) + break; - for (i = 0; i < 32; perm <<= 1, i++) { - if (mask & perm) *str++ = chrs[i]; + str_size--; + } } *str = '\0'; } -void aa_audit_perm_names(struct audit_buffer *ab, const char **names, u32 mask) +void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, + u32 mask) { const char *fmt = "%s"; unsigned int i, perm = 1; @@ -229,13 +334,13 @@ 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) + u32 chrsmask, const char * const *names, u32 namesmask) { char str[33]; audit_log_format(ab, "\""); if ((mask & chrsmask) && chrs) { - aa_perm_mask_to_str(str, chrs, mask & chrsmask); + aa_perm_mask_to_str(str, sizeof(str), chrs, mask & chrsmask); mask &= ~chrsmask; audit_log_format(ab, "%s", str); if (mask & namesmask) @@ -247,32 +352,6 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, } /** - * aa_audit_perms_cb - generic callback fn for auditing perms - * @ab: audit buffer (NOT NULL) - * @va: audit struct to audit values of (NOT NULL) - */ -static void aa_audit_perms_cb(struct audit_buffer *ab, void *va) -{ - struct common_audit_data *sa = va; - - if (aad(sa)->request) { - audit_log_format(ab, " requested_mask="); - aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs, - PERMS_CHRS_MASK, aa_file_perm_names, - PERMS_NAMES_MASK); - } - if (aad(sa)->denied) { - audit_log_format(ab, "denied_mask="); - aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs, - PERMS_CHRS_MASK, aa_file_perm_names, - PERMS_NAMES_MASK); - } - audit_log_format(ab, " peer="); - aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, - FLAGS_NONE, GFP_ATOMIC); -} - -/** * 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 @@ -284,13 +363,13 @@ 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 */ + fallthrough; case AUDIT_NOQUIET: perms->quiet = 0; break; case AUDIT_QUIET: perms->audit = 0; - /* fall through */ + fallthrough; case AUDIT_QUIET_DENIED: perms->quiet = ALL_PERMS_MASK; break; @@ -300,117 +379,32 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) 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); + else if (USER_MODE(profile)) + perms->prompt = ALL_PERMS_MASK; } -/** - * 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, struct aa_label *label, +void aa_profile_match_label(struct aa_profile *profile, + struct aa_ruleset *rules, + struct aa_label *label, int type, u32 request, struct aa_perms *perms) { /* TODO: doesn't yet handle extended types */ - unsigned int state; + aa_state_t state; - state = aa_dfa_next(profile->policy.dfa, - profile->policy.start[AA_CLASS_LABEL], + state = aa_dfa_next(rules->policy->dfa, + rules->policy->start[AA_CLASS_LABEL], type); - aa_label_match(profile, label, state, false, request, perms); + aa_label_match(profile, rules, label, state, false, request, perms); } -/* currently unused */ -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)->peer = &target->label; - aad(sa)->request = request; - - aa_profile_match_label(profile, &target->label, type, request, &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) + * @ad: initialized audit structure (MAY BE NULL if not auditing) + * @cb: callback fn for type specific fields (MAY BE NULL) * * Returns: 0 if permission else error code * @@ -422,17 +416,16 @@ int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target, * with a positive value. */ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, - u32 request, struct common_audit_data *sa, + u32 request, struct apparmor_audit_data *ad, 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) + if (!request || !ad) return 0; type = AUDIT_APPARMOR_AUDIT; @@ -447,22 +440,20 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, else type = AUDIT_APPARMOR_DENIED; - if (denied & perms->stop) - stop = true; if (denied == (denied & perms->hide)) error = -ENOENT; denied &= ~perms->quiet; - if (!sa || !denied) + if (!ad || !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 (ad) { + ad->subj_label = &profile->label; + ad->request = request; + ad->denied = denied; + ad->error = error; + aa_audit_msg(type, ad, cb); } if (type == AUDIT_APPARMOR_ALLOWED) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 867bcd154c7e..a87cd60ed206 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/lsm_hooks.h> @@ -23,39 +19,64 @@ #include <linux/sysctl.h> #include <linux/audit.h> #include <linux/user_namespace.h> -#include <linux/kmemleak.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/zstd.h> #include <net/sock.h> +#include <uapi/linux/mount.h> +#include <uapi/linux/lsm.h> +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" #include "include/capability.h" -#include "include/context.h" +#include "include/cred.h" +#include "include/crypto.h" #include "include/file.h" #include "include/ipc.h" +#include "include/net.h" #include "include/path.h" #include "include/label.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/procattr.h" +#include "include/mount.h" +#include "include/secid.h" /* Flag indicating whether initialization completed */ int apparmor_initialized; -DEFINE_PER_CPU(struct aa_buffers, aa_buffers); +union aa_buffer { + struct list_head list; + DECLARE_FLEX_ARRAY(char, buffer); +}; + +struct aa_local_cache { + unsigned int hold; + unsigned int count; + struct list_head head; +}; + +#define RESERVE_COUNT 2 +static int reserve_count = RESERVE_COUNT; +static int buffer_count; +static LIST_HEAD(aa_global_buffers); +static DEFINE_SPINLOCK(aa_buffers_lock); +static DEFINE_PER_CPU(struct aa_local_cache, aa_local_buffers); /* * LSM hook functions */ /* - * free the associated aa_task_ctx and put its labels + * put the associated labels */ static void apparmor_cred_free(struct cred *cred) { - aa_free_task_context(cred_ctx(cred)); - cred_ctx(cred) = NULL; + aa_put_label(cred_label(cred)); + set_cred_label(cred, NULL); } /* @@ -63,30 +84,17 @@ static void apparmor_cred_free(struct cred *cred) */ static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp) { - /* freed by apparmor_cred_free */ - struct aa_task_ctx *ctx = aa_alloc_task_context(gfp); - - if (!ctx) - return -ENOMEM; - - cred_ctx(cred) = ctx; + set_cred_label(cred, NULL); return 0; } /* - * prepare new aa_task_ctx for modification by prepare_cred block + * prepare new cred label for modification by prepare_cred block */ static int apparmor_cred_prepare(struct cred *new, const struct cred *old, gfp_t gfp) { - /* freed by apparmor_cred_free */ - struct aa_task_ctx *ctx = aa_alloc_task_context(gfp); - - if (!ctx) - return -ENOMEM; - - aa_dup_task_context(ctx, cred_ctx(old)); - cred_ctx(new) = ctx; + set_cred_label(new, aa_get_newest_label(cred_label(old))); return 0; } @@ -95,24 +103,41 @@ 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_ctx *old_ctx = cred_ctx(old); - struct aa_task_ctx *new_ctx = cred_ctx(new); + set_cred_label(new, aa_get_newest_label(cred_label(old))); +} + +static void apparmor_task_free(struct task_struct *task) +{ + + aa_free_task_ctx(task_ctx(task)); +} + +static int apparmor_task_alloc(struct task_struct *task, + u64 clone_flags) +{ + struct aa_task_ctx *new = task_ctx(task); - aa_dup_task_context(new_ctx, old_ctx); + aa_dup_task_ctx(new, task_ctx(current)); + + return 0; } static int apparmor_ptrace_access_check(struct task_struct *child, unsigned int mode) { struct aa_label *tracer, *tracee; + const struct cred *cred; int error; - - tracer = begin_current_label_crit_section(); - 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); - end_current_label_crit_section(tracer); + bool needput; + + cred = get_task_cred(child); + tracee = cred_label(cred); /* ref count on cred */ + tracer = __begin_current_label_crit_section(&needput); + error = aa_may_ptrace(current_cred(), tracer, cred, tracee, + (mode & PTRACE_MODE_READ) ? AA_PTRACE_READ + : AA_PTRACE_TRACE); + __end_current_label_crit_section(tracer, needput); + put_cred(cred); return error; } @@ -120,19 +145,23 @@ static int apparmor_ptrace_access_check(struct task_struct *child, static int apparmor_ptrace_traceme(struct task_struct *parent) { struct aa_label *tracer, *tracee; + const struct cred *cred; int error; + bool needput; - tracee = begin_current_label_crit_section(); - tracer = aa_get_task_label(parent); - error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE); - aa_put_label(tracer); - end_current_label_crit_section(tracee); + tracee = __begin_current_label_crit_section(&needput); + cred = get_task_cred(parent); + tracer = cred_label(cred); /* ref count on cred */ + error = aa_may_ptrace(cred, tracer, current_cred(), tracee, + AA_PTRACE_TRACE); + put_cred(cred); + __end_current_label_crit_section(tracee, needput); return error; } /* Derived from security/commoncap.c:cap_capget */ -static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, +static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effective, kernel_cap_t *inheritable, kernel_cap_t *permitted) { struct aa_label *label; @@ -151,12 +180,11 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective, 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); + kernel_cap_t allowed; + + allowed = aa_profile_capget(profile); + *effective = cap_intersect(*effective, allowed); + *permitted = cap_intersect(*permitted, allowed); } } rcu_read_unlock(); @@ -166,14 +194,14 @@ 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) + int cap, unsigned int opts) { struct aa_label *label; int error = 0; label = aa_get_newest_cred_label(cred); if (!unconfined(label)) - error = aa_capable(label, cap, audit); + error = aa_capable(cred, label, cap, opts); aa_put_label(label); return error; @@ -193,11 +221,13 @@ static int common_perm(const char *op, const struct path *path, u32 mask, { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) - error = aa_path_perm(op, label, path, 0, mask, cond); - __end_current_label_crit_section(label); + error = aa_path_perm(op, current_cred(), label, path, 0, mask, + cond); + __end_current_label_crit_section(label, needput); return error; } @@ -212,8 +242,11 @@ static int common_perm(const char *op, const struct path *path, u32 mask, */ static int common_perm_cond(const char *op, const struct path *path, u32 mask) { - struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, - d_backing_inode(path->dentry)->i_mode + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt), + d_backing_inode(path->dentry)); + struct path_cond cond = { + vfsuid_into_kuid(vfsuid), + d_backing_inode(path->dentry)->i_mode }; if (!path_mediated_fs(path->dentry)) @@ -255,11 +288,13 @@ static int common_perm_rm(const char *op, const struct path *dir, { struct inode *inode = d_backing_inode(dentry); struct path_cond cond = { }; + vfsuid_t vfsuid; if (!inode || !path_mediated_fs(dentry)) return 0; - cond.uid = inode->i_uid; + vfsuid = i_uid_into_vfsuid(mnt_idmap(dir->mnt), inode); + cond.uid = vfsuid_into_kuid(vfsuid); cond.mode = inode->i_mode; return common_perm_dir_dentry(op, dir, dentry, mask, &cond); @@ -314,6 +349,11 @@ static int apparmor_path_truncate(const struct path *path) return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); } +static int apparmor_file_truncate(struct file *file) +{ + return apparmor_path_truncate(&file->f_path); +} + static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, const char *old_name) { @@ -332,37 +372,67 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_ label = begin_current_label_crit_section(); if (!unconfined(label)) - error = aa_path_link(label, old_dentry, new_dir, new_dentry); + error = aa_path_link(current_cred(), label, old_dentry, new_dir, + new_dentry); end_current_label_crit_section(label); return error; } static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, - const struct path *new_dir, struct dentry *new_dentry) + const struct path *new_dir, struct dentry *new_dentry, + const unsigned int flags) { struct aa_label *label; int error = 0; if (!path_mediated_fs(old_dentry)) return 0; + if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry)) + return 0; label = begin_current_label_crit_section(); if (!unconfined(label)) { + struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); + vfsuid_t vfsuid; struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; struct path new_path = { .mnt = new_dir->mnt, .dentry = new_dentry }; - struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode + struct path_cond cond = { + .mode = d_backing_inode(old_dentry)->i_mode }; + vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(old_dentry)); + cond.uid = vfsuid_into_kuid(vfsuid); + + if (flags & RENAME_EXCHANGE) { + struct path_cond cond_exchange = { + .mode = d_backing_inode(new_dentry)->i_mode, + }; + vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(old_dentry)); + cond_exchange.uid = vfsuid_into_kuid(vfsuid); + + error = aa_path_perm(OP_RENAME_SRC, current_cred(), + label, &new_path, 0, + MAY_READ | AA_MAY_GETATTR | MAY_WRITE | + AA_MAY_SETATTR | AA_MAY_DELETE, + &cond_exchange); + if (!error) + error = aa_path_perm(OP_RENAME_DEST, current_cred(), + label, &old_path, + 0, MAY_WRITE | AA_MAY_SETATTR | + AA_MAY_CREATE, &cond_exchange); + } - 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, label, &new_path, + error = aa_path_perm(OP_RENAME_SRC, current_cred(), + 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, current_cred(), + label, &new_path, 0, MAY_WRITE | AA_MAY_SETATTR | AA_MAY_CREATE, &cond); @@ -387,11 +457,12 @@ static int apparmor_inode_getattr(const struct path *path) return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR); } -static int apparmor_file_open(struct file *file, const struct cred *cred) +static int apparmor_file_open(struct file *file) { struct aa_file_ctx *fctx = file_ctx(file); struct aa_label *label; int error = 0; + bool needput; if (!path_mediated_fs(file->f_path.dentry)) return 0; @@ -400,70 +471,82 @@ static int apparmor_file_open(struct file *file, const struct cred *cred) * Cache permissions granted by the previous exec check, with * implicit read and executable mmap which are required to * actually execute the image. + * + * Illogically, FMODE_EXEC is in f_flags, not f_mode. */ - if (current->in_execve) { + if (file->f_flags & __FMODE_EXEC) { fctx->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; return 0; } - label = aa_get_newest_cred_label(cred); + label = aa_get_newest_cred_label_condref(file->f_cred, &needput); if (!unconfined(label)) { + struct mnt_idmap *idmap = file_mnt_idmap(file); struct inode *inode = file_inode(file); - struct path_cond cond = { inode->i_uid, inode->i_mode }; + vfsuid_t vfsuid; + struct path_cond cond = { + .mode = inode->i_mode, + }; + vfsuid = i_uid_into_vfsuid(idmap, inode); + cond.uid = vfsuid_into_kuid(vfsuid); - error = aa_path_perm(OP_OPEN, label, &file->f_path, 0, + error = aa_path_perm(OP_OPEN, file->f_cred, + label, &file->f_path, 0, aa_map_file_to_perms(file), &cond); /* todo cache full allowed permissions set and state */ fctx->allow = aa_map_file_to_perms(file); } - aa_put_label(label); + aa_put_label_condref(label, needput); return error; } static int apparmor_file_alloc_security(struct file *file) { - int error = 0; - - /* freed by apparmor_file_free_security */ + struct aa_file_ctx *ctx = file_ctx(file); struct aa_label *label = begin_current_label_crit_section(); - file->f_security = aa_alloc_file_ctx(label, GFP_KERNEL); - if (!file_ctx(file)) - error = -ENOMEM; - end_current_label_crit_section(label); - return error; + spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + end_current_label_crit_section(label); + return 0; } static void apparmor_file_free_security(struct file *file) { - aa_free_file_ctx(file_ctx(file)); + struct aa_file_ctx *ctx = file_ctx(file); + + if (ctx) + aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(const char *op, struct file *file, u32 mask) +static int common_file_perm(const char *op, struct file *file, u32 mask, + bool in_atomic) { struct aa_label *label; int error = 0; + bool needput; /* don't reaudit files closed during inheritance */ - if (file->f_path.dentry == aa_null.dentry) + if (unlikely(file->f_path.dentry == aa_null.dentry)) return -EACCES; - label = __begin_current_label_crit_section(); - error = aa_file_perm(op, label, file, mask); - __end_current_label_crit_section(label); + label = __begin_current_label_crit_section(&needput); + error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic); + __end_current_label_crit_section(label, needput); return error; } static int apparmor_file_receive(struct file *file) { - return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); + return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file), + false); } static int apparmor_file_permission(struct file *file, int mask) { - return common_file_perm(OP_FPERM, file, mask); + return common_file_perm(OP_FPERM, file, mask, false); } static int apparmor_file_lock(struct file *file, unsigned int cmd) @@ -473,11 +556,11 @@ static int apparmor_file_lock(struct file *file, unsigned int cmd) if (cmd == F_WRLCK) mask |= MAY_WRITE; - return common_file_perm(OP_FLOCK, file, mask); + return common_file_perm(OP_FLOCK, file, mask, false); } static int common_mmap(const char *op, struct file *file, unsigned long prot, - unsigned long flags) + unsigned long flags, bool in_atomic) { int mask = 0; @@ -495,33 +578,265 @@ static int common_mmap(const char *op, struct file *file, unsigned long prot, if (prot & PROT_EXEC) mask |= AA_EXEC_MMAP; - return common_file_perm(op, file, mask); + return common_file_perm(op, file, mask, in_atomic); } static int apparmor_mmap_file(struct file *file, unsigned long reqprot, unsigned long prot, unsigned long flags) { - return common_mmap(OP_FMMAP, file, prot, flags); + return common_mmap(OP_FMMAP, file, prot, flags, GFP_ATOMIC); } static int apparmor_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, unsigned long prot) { return common_mmap(OP_FMPROT, vma->vm_file, prot, - !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); + !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0, + false); +} + +#ifdef CONFIG_IO_URING +static const char *audit_uring_mask(u32 mask) +{ + if (mask & AA_MAY_CREATE_SQPOLL) + return "sqpoll"; + if (mask & AA_MAY_OVERRIDE_CRED) + return "override_creds"; + return ""; +} + +static void audit_uring_cb(struct audit_buffer *ab, void *va) +{ + struct apparmor_audit_data *ad = aad_of_va(va); + + if (ad->request & AA_URING_PERM_MASK) { + audit_log_format(ab, " requested=\"%s\"", + audit_uring_mask(ad->request)); + if (ad->denied & AA_URING_PERM_MASK) { + audit_log_format(ab, " denied=\"%s\"", + audit_uring_mask(ad->denied)); + } + } + if (ad->uring.target) { + audit_log_format(ab, " tcontext="); + aa_label_xaudit(ab, labels_ns(ad->subj_label), + ad->uring.target, + FLAGS_NONE, GFP_ATOMIC); + } +} + +static int profile_uring(struct aa_profile *profile, u32 request, + struct aa_label *new, int cap, + struct apparmor_audit_data *ad) +{ + unsigned int state; + struct aa_ruleset *rules; + int error = 0; + + AA_BUG(!profile); + + rules = profile->label.rules[0]; + state = RULE_MEDIATES(rules, AA_CLASS_IO_URING); + if (state) { + struct aa_perms perms = { }; + + if (new) { + aa_label_match(profile, rules, new, state, + false, request, &perms); + } else { + perms = *aa_lookup_perms(rules->policy, state); + } + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, request, ad, + audit_uring_cb); + } + + return error; } -static int apparmor_getprocattr(struct task_struct *task, char *name, +/** + * apparmor_uring_override_creds - check the requested cred override + * @new: the target creds + * + * Check to see if the current task is allowed to override it's credentials + * to service an io_uring operation. + */ +static int apparmor_uring_override_creds(const struct cred *new) +{ + struct aa_profile *profile; + struct aa_label *label; + int error; + bool needput; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, + OP_URING_OVERRIDE); + + ad.uring.target = cred_label(new); + label = __begin_current_label_crit_section(&needput); + error = fn_for_each(label, profile, + profile_uring(profile, AA_MAY_OVERRIDE_CRED, + cred_label(new), CAP_SYS_ADMIN, &ad)); + __end_current_label_crit_section(label, needput); + + return error; +} + +/** + * apparmor_uring_sqpoll - check if a io_uring polling thread can be created + * + * Check to see if the current task is allowed to create a new io_uring + * kernel polling thread. + */ +static int apparmor_uring_sqpoll(void) +{ + struct aa_profile *profile; + struct aa_label *label; + int error; + bool needput; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, + OP_URING_SQPOLL); + + label = __begin_current_label_crit_section(&needput); + error = fn_for_each(label, profile, + profile_uring(profile, AA_MAY_CREATE_SQPOLL, + NULL, CAP_SYS_ADMIN, &ad)); + __end_current_label_crit_section(label, needput); + + return error; +} +#endif /* CONFIG_IO_URING */ + +static int apparmor_sb_mount(const char *dev_name, const struct path *path, + const char *type, unsigned long flags, void *data) +{ + struct aa_label *label; + int error = 0; + bool needput; + + /* Discard magic */ + if ((flags & MS_MGC_MSK) == MS_MGC_VAL) + flags &= ~MS_MGC_MSK; + + flags &= ~AA_MS_IGNORE_MASK; + + label = __begin_current_label_crit_section(&needput); + if (!unconfined(label)) { + if (flags & MS_REMOUNT) + error = aa_remount(current_cred(), label, path, flags, + data); + else if (flags & MS_BIND) + error = aa_bind_mount(current_cred(), label, path, + dev_name, flags); + else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE)) + error = aa_mount_change_type(current_cred(), label, + path, flags); + else if (flags & MS_MOVE) + error = aa_move_mount_old(current_cred(), label, path, + dev_name); + else + error = aa_new_mount(current_cred(), label, dev_name, + path, type, flags, data); + } + __end_current_label_crit_section(label, needput); + + return error; +} + +static int apparmor_move_mount(const struct path *from_path, + const struct path *to_path) +{ + struct aa_label *label; + int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); + if (!unconfined(label)) + error = aa_move_mount(current_cred(), label, from_path, + to_path); + __end_current_label_crit_section(label, needput); + + return error; +} + +static int apparmor_sb_umount(struct vfsmount *mnt, int flags) +{ + struct aa_label *label; + int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); + if (!unconfined(label)) + error = aa_umount(current_cred(), label, mnt, flags); + __end_current_label_crit_section(label, needput); + + return error; +} + +static int apparmor_sb_pivotroot(const struct path *old_path, + const struct path *new_path) +{ + struct aa_label *label; + int error = 0; + + label = aa_get_current_label(); + if (!unconfined(label)) + error = aa_pivotroot(current_cred(), label, old_path, new_path); + aa_put_label(label); + + return error; +} + +static int apparmor_getselfattr(unsigned int attr, struct lsm_ctx __user *lx, + u32 *size, u32 flags) +{ + int error = -ENOENT; + struct aa_task_ctx *ctx = task_ctx(current); + struct aa_label *label = NULL; + char *value = NULL; + + switch (attr) { + case LSM_ATTR_CURRENT: + label = aa_get_newest_label(cred_label(current_cred())); + break; + case LSM_ATTR_PREV: + if (ctx->previous) + label = aa_get_newest_label(ctx->previous); + break; + case LSM_ATTR_EXEC: + if (ctx->onexec) + label = aa_get_newest_label(ctx->onexec); + break; + default: + error = -EOPNOTSUPP; + break; + } + + if (label) { + error = aa_getprocattr(label, &value, false); + if (error > 0) + error = lsm_fill_user_ctx(lx, size, value, error, + LSM_ID_APPARMOR, 0); + kfree(value); + } + + aa_put_label(label); + + if (error < 0) + return error; + return 1; +} + +static int apparmor_getprocattr(struct task_struct *task, const char *name, char **value) { int error = -ENOENT; /* released below */ const struct cred *cred = get_task_cred(task); - struct aa_task_ctx *ctx = cred_ctx(cred); + struct aa_task_ctx *ctx = task_ctx(current); struct aa_label *label = NULL; if (strcmp(name, "current") == 0) - label = aa_get_newest_label(ctx->label); + label = aa_get_newest_label(cred_label(cred)); else if (strcmp(name, "prev") == 0 && ctx->previous) label = aa_get_newest_label(ctx->previous); else if (strcmp(name, "exec") == 0 && ctx->onexec) @@ -530,7 +845,7 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, error = -EINVAL; if (label) - error = aa_getprocattr(label, value); + error = aa_getprocattr(label, value, true); aa_put_label(label); put_cred(cred); @@ -538,13 +853,13 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, return error; } -static int apparmor_setprocattr(const char *name, void *value, - size_t size) +static int do_setattr(u64 attr, void *value, size_t size) { char *command, *largs = NULL, *args = value; size_t arg_size; int error; - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, + OP_SETPROCATTR); if (size == 0) return -EINVAL; @@ -569,7 +884,7 @@ static int apparmor_setprocattr(const char *name, void *value, goto out; arg_size = size - (args - (largs ? largs : (char *) value)); - if (strcmp(name, "current") == 0) { + if (attr == LSM_ATTR_CURRENT) { if (strcmp(command, "changehat") == 0) { error = aa_setprocattr_changehat(args, arg_size, AA_CHANGE_NOFLAGS); @@ -584,7 +899,7 @@ static int apparmor_setprocattr(const char *name, void *value, error = aa_change_profile(args, AA_CHANGE_STACK); } else goto fail; - } else if (strcmp(name, "exec") == 0) { + } else if (attr == LSM_ATTR_EXEC) { if (strcmp(command, "exec") == 0) error = aa_change_profile(args, AA_CHANGE_ONEXEC); else if (strcmp(command, "stack") == 0) @@ -603,26 +918,55 @@ out: return error; fail: - aad(&sa)->label = begin_current_label_crit_section(); - aad(&sa)->info = name; - aad(&sa)->error = error = -EINVAL; - aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); - end_current_label_crit_section(aad(&sa)->label); + ad.subj_label = begin_current_label_crit_section(); + if (attr == LSM_ATTR_CURRENT) + ad.info = "current"; + else if (attr == LSM_ATTR_EXEC) + ad.info = "exec"; + else + ad.info = "invalid"; + ad.error = error = -EINVAL; + aa_audit_msg(AUDIT_APPARMOR_DENIED, &ad, NULL); + end_current_label_crit_section(ad.subj_label); goto out; } +static int apparmor_setselfattr(unsigned int attr, struct lsm_ctx *ctx, + u32 size, u32 flags) +{ + int rc; + + if (attr != LSM_ATTR_CURRENT && attr != LSM_ATTR_EXEC) + return -EOPNOTSUPP; + + rc = do_setattr(attr, ctx->ctx, ctx->ctx_len); + if (rc > 0) + return 0; + return rc; +} + +static int apparmor_setprocattr(const char *name, void *value, + size_t size) +{ + int attr = lsm_name_to_attr(name); + + if (attr) + return do_setattr(attr, value, size); + return -EINVAL; +} + /** * apparmor_bprm_committing_creds - do task cleanup on committing new creds * @bprm: binprm for the exec (NOT NULL) */ -static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) +static void apparmor_bprm_committing_creds(const struct linux_binprm *bprm) { struct aa_label *label = aa_current_raw_label(); - struct aa_task_ctx *new_ctx = cred_ctx(bprm->cred); + struct aa_label *new_label = cred_label(bprm->cred); /* bail out if unconfined or not changing profile */ - if ((new_ctx->label->proxy == label->proxy) || - (unconfined(new_ctx->label))) + if ((new_label->proxy == label->proxy) || + (unconfined(new_label))) return; aa_inherit_files(bprm->cred, current->files); @@ -630,38 +974,712 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm) current->pdeath_signal = 0; /* reset soft limits and set hard limits for the new label */ - __aa_transition_rlimits(label, new_ctx->label); + __aa_transition_rlimits(label, new_label); } /** - * apparmor_bprm_committed_cred - do cleanup after new creds committed + * apparmor_bprm_committed_creds() - do cleanup after new creds committed * @bprm: binprm for the exec (NOT NULL) */ -static void apparmor_bprm_committed_creds(struct linux_binprm *bprm) +static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm) { - /* TODO: cleanup signals - ipc mediation */ + /* clear out temporary/transitional state from the context */ + aa_clear_task_ctx_trans(task_ctx(current)); + return; } +static void apparmor_current_getlsmprop_subj(struct lsm_prop *prop) +{ + struct aa_label *label; + bool needput; + + label = __begin_current_label_crit_section(&needput); + prop->apparmor.label = label; + __end_current_label_crit_section(label, needput); +} + +static void apparmor_task_getlsmprop_obj(struct task_struct *p, + struct lsm_prop *prop) +{ + struct aa_label *label = aa_get_task_label(p); + + prop->apparmor.label = label; + aa_put_label(label); +} + static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_label *label = __begin_current_label_crit_section(); + struct aa_label *label; int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) - error = aa_task_setrlimit(label, task, resource, new_rlim); - __end_current_label_crit_section(label); + error = aa_task_setrlimit(current_cred(), label, task, + resource, new_rlim); + __end_current_label_crit_section(label, needput); + + return error; +} + +static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo *info, + int sig, const struct cred *cred) +{ + const struct cred *tc; + struct aa_label *cl, *tl; + int error; + bool needput; + + tc = get_task_cred(target); + tl = aa_get_newest_cred_label(tc); + if (cred) { + /* + * Dealing with USB IO specific behavior + */ + cl = aa_get_newest_cred_label(cred); + error = aa_may_signal(cred, cl, tc, tl, sig); + aa_put_label(cl); + } else { + cl = __begin_current_label_crit_section(&needput); + error = aa_may_signal(current_cred(), cl, tc, tl, sig); + __end_current_label_crit_section(cl, needput); + } + aa_put_label(tl); + put_cred(tc); + + return error; +} + +static int apparmor_userns_create(const struct cred *cred) +{ + struct aa_label *label; + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_NS, + OP_USERNS_CREATE); + + ad.subj_cred = current_cred(); + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + error = fn_for_each(label, profile, + aa_profile_ns_perm(profile, &ad, + AA_USERNS_CREATE)); + } + end_current_label_crit_section(label); + + return error; +} + +static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t gfp) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_label *label; + bool needput; + + label = __begin_current_label_crit_section(&needput); + //spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + rcu_assign_pointer(ctx->peer, NULL); + rcu_assign_pointer(ctx->peer_lastupdate, NULL); + __end_current_label_crit_section(label, needput); + return 0; +} + +static void apparmor_sk_free_security(struct sock *sk) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + + /* dead these won't be updated any more */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + aa_put_label(rcu_dereference_protected(ctx->peer, true)); + aa_put_label(rcu_dereference_protected(ctx->peer_lastupdate, true)); +} + +/** + * apparmor_sk_clone_security - clone the sk_security field + * @sk: sock to have security cloned + * @newsk: sock getting clone + */ +static void apparmor_sk_clone_security(const struct sock *sk, + struct sock *newsk) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_sk_ctx *new = aa_sock(newsk); + + /* not actually in use yet */ + if (rcu_access_pointer(ctx->label) != rcu_access_pointer(new->label)) { + aa_put_label(rcu_dereference_protected(new->label, true)); + rcu_assign_pointer(new->label, aa_get_label_rcu(&ctx->label)); + } + + if (rcu_access_pointer(ctx->peer) != rcu_access_pointer(new->peer)) { + aa_put_label(rcu_dereference_protected(new->peer, true)); + rcu_assign_pointer(new->peer, aa_get_label_rcu(&ctx->peer)); + } + + if (rcu_access_pointer(ctx->peer_lastupdate) != rcu_access_pointer(new->peer_lastupdate)) { + aa_put_label(rcu_dereference_protected(new->peer_lastupdate, true)); + rcu_assign_pointer(new->peer_lastupdate, + aa_get_label_rcu(&ctx->peer_lastupdate)); + } +} + +static int unix_connect_perm(const struct cred *cred, struct aa_label *label, + struct sock *sk, struct sock *peer_sk) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + int error; + + error = aa_unix_peer_perm(cred, label, OP_CONNECT, + (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), + sk, peer_sk, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock))); + if (!is_unix_fs(peer_sk)) { + last_error(error, + aa_unix_peer_perm(cred, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock)), + OP_CONNECT, + (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), + peer_sk, sk, label)); + } + + return error; +} + +/* lockdep check in unix_connect_perm - push sks here to check */ +static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, + struct aa_sk_ctx *peer_ctx) +{ + /* Cross reference the peer labels for SO_PEERSEC */ + struct aa_label *label = rcu_dereference_protected(sk_ctx->label, true); + + aa_get_label(label); + aa_put_label(rcu_dereference_protected(peer_ctx->peer, + true)); + rcu_assign_pointer(peer_ctx->peer, label); /* transfer cnt */ + + label = aa_get_label(rcu_dereference_protected(peer_ctx->label, + true)); + //spin_unlock(&peer_ctx->lock); + + //spin_lock(&sk_ctx->lock); + aa_put_label(rcu_dereference_protected(sk_ctx->peer, + true)); + aa_put_label(rcu_dereference_protected(sk_ctx->peer_lastupdate, + true)); + + rcu_assign_pointer(sk_ctx->peer, aa_get_label(label)); + rcu_assign_pointer(sk_ctx->peer_lastupdate, label); /* transfer cnt */ + //spin_unlock(&sk_ctx->lock); +} + +/** + * apparmor_unix_stream_connect - check perms before making unix domain conn + * @sk: sk attempting to connect + * @peer_sk: sk that is accepting the connection + * @newsk: new sk created for this connection + * peer is locked when this hook is called + * + * Return: + * 0 if connection is permitted + * error code on denial or failure + */ +static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, + struct sock *newsk) +{ + struct aa_sk_ctx *sk_ctx = aa_sock(sk); + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + struct aa_sk_ctx *new_ctx = aa_sock(newsk); + struct aa_label *label; + int error; + bool needput; + + label = __begin_current_label_crit_section(&needput); + error = unix_connect_perm(current_cred(), label, sk, peer_sk); + __end_current_label_crit_section(label, needput); + + if (error) + return error; + + /* newsk doesn't go through post_create, but does go through + * security_sk_alloc() + */ + rcu_assign_pointer(new_ctx->label, + aa_get_label(rcu_dereference_protected(peer_ctx->label, + true))); + + /* Cross reference the peer labels for SO_PEERSEC */ + unix_connect_peers(sk_ctx, new_ctx); + + return 0; +} + +/** + * apparmor_unix_may_send - check perms before conn or sending unix dgrams + * @sock: socket sending the message + * @peer: socket message is being send to + * + * Performs bidirectional permission checks for Unix domain socket communication: + * 1. Verifies sender has AA_MAY_SEND to target socket + * 2. Verifies receiver has AA_MAY_RECEIVE from source socket + * + * sock and peer are locked when this hook is called + * called by: dgram_connect peer setup but path not copied to newsk + * + * Return: + * 0 if transmission is permitted + * error code on denial or failure + */ +static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk); + struct aa_label *label; + int error; + bool needput; + + label = __begin_current_label_crit_section(&needput); + error = xcheck(aa_unix_peer_perm(current_cred(), + label, OP_SENDMSG, AA_MAY_SEND, + sock->sk, peer->sk, + rcu_dereference_protected(peer_ctx->label, + true)), + aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, + rcu_dereference_protected(peer_ctx->label, + true), + OP_SENDMSG, AA_MAY_RECEIVE, peer->sk, + sock->sk, label)); + __end_current_label_crit_section(label, needput); + + return error; +} + +static int apparmor_socket_create(int family, int type, int protocol, int kern) +{ + struct aa_label *label; + int error = 0; + + AA_BUG(in_interrupt()); + + if (kern) + return 0; + + label = begin_current_label_crit_section(); + if (!unconfined(label)) { + if (family == PF_UNIX) + error = aa_unix_create_perm(label, family, type, + protocol); + else + error = aa_af_perm(current_cred(), label, OP_CREATE, + AA_MAY_CREATE, family, type, + protocol); + } + end_current_label_crit_section(label); + + return error; +} + +/** + * apparmor_socket_post_create - setup the per-socket security struct + * @sock: socket that is being setup + * @family: family of socket being created + * @type: type of the socket + * @protocol: protocol of the socket + * @kern: socket is a special kernel socket + * + * Note: + * - kernel sockets labeled kernel_t used to use unconfined + * - 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(kernel_t); + } else + label = aa_get_current_label(); + + if (sock->sk) { + struct aa_sk_ctx *ctx = aa_sock(sock->sk); + + /* still not live */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + } + aa_put_label(label); + + return 0; +} + +static int apparmor_socket_socketpair(struct socket *socka, + struct socket *sockb) +{ + struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); + struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); + struct aa_label *label; + + /* socks not live yet - initial values set in sk_alloc */ + label = begin_current_label_crit_section(); + if (rcu_access_pointer(a_ctx->label) != label) { + AA_BUG("a_ctx != label"); + aa_put_label(rcu_dereference_protected(a_ctx->label, true)); + rcu_assign_pointer(a_ctx->label, aa_get_label(label)); + } + if (rcu_access_pointer(b_ctx->label) != label) { + AA_BUG("b_ctx != label"); + aa_put_label(rcu_dereference_protected(b_ctx->label, true)); + rcu_assign_pointer(b_ctx->label, aa_get_label(label)); + } + + if (socka->sk->sk_family == PF_UNIX) { + /* unix socket pairs by-pass unix_stream_connect */ + unix_connect_peers(a_ctx, b_ctx); + } + end_current_label_crit_section(label); + + return 0; +} + +/** + * apparmor_socket_bind - check perms before bind addr to socket + * @sock: socket to bind the address to (must be non-NULL) + * @address: address that is being bound (must be non-NULL) + * @addrlen: length of @address + * + * Performs security checks before allowing a socket to bind to an address. + * Handles Unix domain sockets specially through aa_unix_bind_perm(). + * For other socket families, uses generic permission check via aa_sk_perm(). + * + * Return: + * 0 if binding is permitted + * error code on denial or invalid parameters + */ +static int apparmor_socket_bind(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(!address); + AA_BUG(in_interrupt()); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_bind_perm(sock, address, addrlen); + return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); +} + +static int apparmor_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(!address); + AA_BUG(in_interrupt()); + + /* PF_UNIX goes through unix_stream_connect && unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; + return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); +} + +static int apparmor_socket_listen(struct socket *sock, int backlog) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(in_interrupt()); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_listen_perm(sock, backlog); + return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); +} + +/* + * 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) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(!newsock); + AA_BUG(in_interrupt()); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_accept_perm(sock, newsock); + return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); +} + +static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(!msg); + AA_BUG(in_interrupt()); + + /* PF_UNIX goes through unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; + return aa_sk_perm(op, request, sock->sk); +} + +static int apparmor_socket_sendmsg(struct socket *sock, + struct msghdr *msg, int size) +{ + return aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size); +} + +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); +} + +/* revaliation, get/set attr, shutdown */ +static int aa_sock_perm(const char *op, u32 request, struct socket *sock) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(in_interrupt()); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_sock_perm(op, request, sock); + return aa_sk_perm(op, request, sock->sk); +} + +static int apparmor_socket_getsockname(struct socket *sock) +{ + return aa_sock_perm(OP_GETSOCKNAME, AA_MAY_GETATTR, sock); +} + +static int apparmor_socket_getpeername(struct socket *sock) +{ + return aa_sock_perm(OP_GETPEERNAME, AA_MAY_GETATTR, sock); +} + +/* revaliation, get/set attr, opt */ +static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, + int level, int optname) +{ + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(in_interrupt()); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_opt_perm(op, request, sock, level, optname); + return aa_sk_perm(op, request, sock->sk); +} + +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); +} + +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); +} + +static int apparmor_socket_shutdown(struct socket *sock, int how) +{ + return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock); +} + +#ifdef CONFIG_NETWORK_SECMARK +/** + * apparmor_socket_sock_rcv_skb - check perms before associating skb to sk + * @sk: sk to associate @skb with + * @skb: skb to check for perms + * + * Note: can not sleep may be 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) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + int error; + + if (!skb->secmark) + return 0; + + /* + * If reach here before socket_post_create hook is called, in which + * case label is null, drop the packet. + */ + if (!rcu_access_pointer(ctx->label)) + return -EACCES; + + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, + AA_MAY_RECEIVE, skb->secmark, sk); + rcu_read_unlock(); return error; } +#endif + + +static struct aa_label *sk_peer_get_label(struct sock *sk) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_label *label = ERR_PTR(-ENOPROTOOPT); + + if (rcu_access_pointer(ctx->peer)) + return aa_get_label_rcu(&ctx->peer); -static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { + if (sk->sk_family != PF_UNIX) + return ERR_PTR(-ENOPROTOOPT); + + return label; +} + +/** + * apparmor_socket_getpeersec_stream - get security context of peer + * @sock: socket that we are trying to get the peer context of + * @optval: output - buffer to copy peer name to + * @optlen: output - size of copied name in @optval + * @len: size of @optval buffer + * Returns: 0 on success, -errno of failure + * + * Note: for tcp only valid if using ipsec or cipso on lan + */ +static int apparmor_socket_getpeersec_stream(struct socket *sock, + sockptr_t optval, sockptr_t optlen, + unsigned int len) +{ + char *name = NULL; + int slen, error = 0; + struct aa_label *label; + struct aa_label *peer; + + peer = sk_peer_get_label(sock->sk); + if (IS_ERR(peer)) { + error = PTR_ERR(peer); + goto done; + } + label = begin_current_label_crit_section(); + slen = aa_label_asxprint(&name, labels_ns(label), peer, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); + /* don't include terminating \0 in slen, it breaks some apps */ + if (slen < 0) { + error = -ENOMEM; + goto done_put; + } + if (slen > len) { + error = -ERANGE; + goto done_len; + } + + if (copy_to_sockptr(optval, name, slen)) + error = -EFAULT; +done_len: + if (copy_to_sockptr(optlen, &slen, sizeof(slen))) + error = -EFAULT; + +done_put: + end_current_label_crit_section(label); + aa_put_label(peer); +done: + 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 */ + return -ENOPROTOOPT; +} + +/** + * apparmor_sock_graft - Initialize newly created socket + * @sk: child sock + * @parent: parent socket + * + * Note: could set off of SOCK_CTX(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_ctx *ctx = aa_sock(sk); + + /* setup - not live */ + if (!rcu_access_pointer(ctx->label)) + rcu_assign_pointer(ctx->label, aa_get_current_label()); +} + +#ifdef CONFIG_NETWORK_SECMARK +static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + int error; + + if (!skb->secmark) + return 0; + + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_CONNECT, + AA_MAY_CONNECT, skb->secmark, sk); + rcu_read_unlock(); + + return error; +} +#endif + +/* + * The cred blob is a pointer to, not an instance of, an aa_label. + */ +struct lsm_blob_sizes apparmor_blob_sizes __ro_after_init = { + .lbs_cred = sizeof(struct aa_label *), + .lbs_file = sizeof(struct aa_file_ctx), + .lbs_task = sizeof(struct aa_task_ctx), + .lbs_sock = sizeof(struct aa_sk_ctx), +}; + +static const struct lsm_id apparmor_lsmid = { + .name = "apparmor", + .id = LSM_ID_APPARMOR, +}; + +static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(ptrace_access_check, apparmor_ptrace_access_check), LSM_HOOK_INIT(ptrace_traceme, apparmor_ptrace_traceme), LSM_HOOK_INIT(capget, apparmor_capget), LSM_HOOK_INIT(capable, apparmor_capable), + LSM_HOOK_INIT(move_mount, apparmor_move_mount), + LSM_HOOK_INIT(sb_mount, apparmor_sb_mount), + LSM_HOOK_INIT(sb_umount, apparmor_sb_umount), + LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot), + LSM_HOOK_INIT(path_link, apparmor_path_link), LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), @@ -682,21 +1700,80 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(mmap_file, apparmor_mmap_file), LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect), LSM_HOOK_INIT(file_lock, apparmor_file_lock), + LSM_HOOK_INIT(file_truncate, apparmor_file_truncate), + LSM_HOOK_INIT(getselfattr, apparmor_getselfattr), + LSM_HOOK_INIT(setselfattr, apparmor_setselfattr), LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security), + LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), + LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), + + LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send), + + LSM_HOOK_INIT(socket_create, apparmor_socket_create), + LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create), + LSM_HOOK_INIT(socket_socketpair, apparmor_socket_socketpair), + LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), + LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), + LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), + LSM_HOOK_INIT(socket_accept, apparmor_socket_accept), + LSM_HOOK_INIT(socket_sendmsg, apparmor_socket_sendmsg), + LSM_HOOK_INIT(socket_recvmsg, apparmor_socket_recvmsg), + LSM_HOOK_INIT(socket_getsockname, apparmor_socket_getsockname), + LSM_HOOK_INIT(socket_getpeername, apparmor_socket_getpeername), + LSM_HOOK_INIT(socket_getsockopt, apparmor_socket_getsockopt), + LSM_HOOK_INIT(socket_setsockopt, apparmor_socket_setsockopt), + LSM_HOOK_INIT(socket_shutdown, apparmor_socket_shutdown), +#ifdef CONFIG_NETWORK_SECMARK + LSM_HOOK_INIT(socket_sock_rcv_skb, apparmor_socket_sock_rcv_skb), +#endif + LSM_HOOK_INIT(socket_getpeersec_stream, + apparmor_socket_getpeersec_stream), + LSM_HOOK_INIT(socket_getpeersec_dgram, + apparmor_socket_getpeersec_dgram), + LSM_HOOK_INIT(sock_graft, apparmor_sock_graft), +#ifdef CONFIG_NETWORK_SECMARK + LSM_HOOK_INIT(inet_conn_request, apparmor_inet_conn_request), +#endif + LSM_HOOK_INIT(cred_alloc_blank, apparmor_cred_alloc_blank), LSM_HOOK_INIT(cred_free, apparmor_cred_free), LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare), LSM_HOOK_INIT(cred_transfer, apparmor_cred_transfer), - LSM_HOOK_INIT(bprm_set_creds, apparmor_bprm_set_creds), + LSM_HOOK_INIT(bprm_creds_for_exec, apparmor_bprm_creds_for_exec), LSM_HOOK_INIT(bprm_committing_creds, apparmor_bprm_committing_creds), LSM_HOOK_INIT(bprm_committed_creds, apparmor_bprm_committed_creds), - LSM_HOOK_INIT(bprm_secureexec, apparmor_bprm_secureexec), + LSM_HOOK_INIT(task_free, apparmor_task_free), + LSM_HOOK_INIT(task_alloc, apparmor_task_alloc), + LSM_HOOK_INIT(current_getlsmprop_subj, + apparmor_current_getlsmprop_subj), + LSM_HOOK_INIT(task_getlsmprop_obj, apparmor_task_getlsmprop_obj), LSM_HOOK_INIT(task_setrlimit, apparmor_task_setrlimit), + LSM_HOOK_INIT(task_kill, apparmor_task_kill), + LSM_HOOK_INIT(userns_create, apparmor_userns_create), + +#ifdef CONFIG_AUDIT + LSM_HOOK_INIT(audit_rule_init, aa_audit_rule_init), + LSM_HOOK_INIT(audit_rule_known, aa_audit_rule_known), + LSM_HOOK_INIT(audit_rule_match, aa_audit_rule_match), + LSM_HOOK_INIT(audit_rule_free, aa_audit_rule_free), +#endif + + LSM_HOOK_INIT(secid_to_secctx, apparmor_secid_to_secctx), + LSM_HOOK_INIT(lsmprop_to_secctx, apparmor_lsmprop_to_secctx), + LSM_HOOK_INIT(secctx_to_secid, apparmor_secctx_to_secid), + LSM_HOOK_INIT(release_secctx, apparmor_release_secctx), + +#ifdef CONFIG_IO_URING + LSM_HOOK_INIT(uring_override_creds, apparmor_uring_override_creds), + LSM_HOOK_INIT(uring_sqpoll, apparmor_uring_sqpoll), +#endif }; /* @@ -720,6 +1797,16 @@ static const struct kernel_param_ops param_ops_aauint = { .get = param_get_aauint }; +static int param_set_aacompressionlevel(const char *val, + const struct kernel_param *kp); +static int param_get_aacompressionlevel(char *buffer, + const struct kernel_param *kp); +#define param_check_aacompressionlevel param_check_int +static const struct kernel_param_ops param_ops_aacompressionlevel = { + .set = param_set_aacompressionlevel, + .get = param_get_aacompressionlevel +}; + static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp); static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp); #define param_check_aalockpolicy param_check_bool @@ -729,11 +1816,14 @@ static const struct kernel_param_ops param_ops_aalockpolicy = { .get = param_get_aalockpolicy }; -static int param_set_audit(const char *val, struct kernel_param *kp); -static int param_get_audit(char *buffer, struct kernel_param *kp); +static int param_set_debug(const char *val, const struct kernel_param *kp); +static int param_get_debug(char *buffer, const struct kernel_param *kp); -static int param_set_mode(const char *val, struct kernel_param *kp); -static int param_get_mode(char *buffer, struct kernel_param *kp); +static int param_set_audit(const char *val, const struct kernel_param *kp); +static int param_get_audit(char *buffer, const struct kernel_param *kp); + +static int param_set_mode(const char *val, const struct kernel_param *kp); +static int param_get_mode(char *buffer, const struct kernel_param *kp); /* Flag values, also controllable via /sys/module/apparmor/parameters * We define special types as we want to do additional mediation. @@ -750,9 +1840,21 @@ bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT); module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUSR | S_IWUSR); #endif +/* whether policy exactly as loaded is retained for debug and checkpointing */ +bool aa_g_export_binary = IS_ENABLED(CONFIG_SECURITY_APPARMOR_EXPORT_BINARY); +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +module_param_named(export_binary, aa_g_export_binary, aabool, 0600); +#endif + +/* policy loaddata compression level */ +int aa_g_rawdata_compression_level = AA_DEFAULT_CLEVEL; +module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, + aacompressionlevel, 0400); + /* Debug mode */ -bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES); -module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); +int aa_g_debug; +module_param_call(debug, param_set_debug, param_get_debug, + &aa_g_debug, 0600); /* Audit mode */ enum audit_mode aa_g_audit; @@ -762,7 +1864,7 @@ module_param_call(audit, param_set_audit, param_get_audit, /* Determines if audit header is included in audited messages. This * provides more context if the audit daemon is not running */ -bool aa_g_audit_header = 1; +bool aa_g_audit_header = true; module_param_named(audit_header, aa_g_audit_header, aabool, S_IRUSR | S_IWUSR); @@ -787,12 +1889,19 @@ module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR); * DEPRECATED: read only as strict checking of load is always done now * that none root users (user namespaces) can load policy. */ -bool aa_g_paranoid_load = 1; +bool aa_g_paranoid_load = IS_ENABLED(CONFIG_SECURITY_APPARMOR_PARANOID_LOAD); module_param_named(paranoid_load, aa_g_paranoid_load, aabool, S_IRUGO); +static int param_get_aaintbool(char *buffer, const struct kernel_param *kp); +static int param_set_aaintbool(const char *val, const struct kernel_param *kp); +#define param_check_aaintbool param_check_int +static const struct kernel_param_ops param_ops_aaintbool = { + .set = param_set_aaintbool, + .get = param_get_aaintbool +}; /* Boot time disable flag */ -static bool apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE; -module_param_named(enabled, apparmor_enabled, bool, S_IRUGO); +static int apparmor_enabled __ro_after_init = 1; +module_param_named(enabled, apparmor_enabled, aaintbool, 0444); static int __init apparmor_enabled_setup(char *str) { @@ -810,7 +1919,7 @@ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; return param_set_bool(val, kp); } @@ -819,7 +1928,7 @@ static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_view_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return param_get_bool(buffer, kp); } @@ -828,7 +1937,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; return param_set_bool(val, kp); } @@ -837,7 +1946,7 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_view_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return param_get_bool(buffer, kp); } @@ -853,6 +1962,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) return -EPERM; error = param_set_uint(val, kp); + aa_g_path_max = max_t(uint32_t, aa_g_path_max, sizeof(union aa_buffer)); pr_info("AppArmor: buffer size set to %d bytes\n", aa_g_path_max); return error; @@ -862,21 +1972,119 @@ static int param_get_aauint(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_view_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return param_get_uint(buffer, kp); } -static int param_get_audit(char *buffer, struct kernel_param *kp) +/* Can only be set before AppArmor is initialized (i.e. on boot cmdline). */ +static int param_set_aaintbool(const char *val, const struct kernel_param *kp) +{ + struct kernel_param kp_local; + bool value; + int error; + + if (apparmor_initialized) + return -EPERM; + + /* Create local copy, with arg pointing to bool type. */ + value = !!*((int *)kp->arg); + memcpy(&kp_local, kp, sizeof(kp_local)); + kp_local.arg = &value; + + error = param_set_bool(val, &kp_local); + if (!error) + *((int *)kp->arg) = *((bool *)kp_local.arg); + return error; +} + +/* + * To avoid changing /sys/module/apparmor/parameters/enabled from Y/N to + * 1/0, this converts the "int that is actually bool" back to bool for + * display in the /sys filesystem, while keeping it "int" for the LSM + * infrastructure. + */ +static int param_get_aaintbool(char *buffer, const struct kernel_param *kp) { + struct kernel_param kp_local; + bool value; + + /* Create local copy, with arg pointing to bool type. */ + value = !!*((int *)kp->arg); + memcpy(&kp_local, kp, sizeof(kp_local)); + kp_local.arg = &value; + + return param_get_bool(buffer, &kp_local); +} + +static int param_set_aacompressionlevel(const char *val, + const struct kernel_param *kp) +{ + int error; + if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_view_capable(NULL)) + if (apparmor_initialized) + return -EPERM; + + error = param_set_int(val, kp); + + aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level, + AA_MIN_CLEVEL, AA_MAX_CLEVEL); + pr_info("AppArmor: policy rawdata compression level set to %d\n", + aa_g_rawdata_compression_level); + + return error; +} + +static int param_get_aacompressionlevel(char *buffer, + const struct kernel_param *kp) +{ + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) + return -EPERM; + return param_get_int(buffer, kp); +} + +static int param_get_debug(char *buffer, const struct kernel_param *kp) +{ + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) + return -EPERM; + return aa_print_debug_params(buffer); +} + +static int param_set_debug(const char *val, const struct kernel_param *kp) +{ + int i; + + if (!apparmor_enabled) + return -EINVAL; + if (!val) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) + return -EPERM; + + i = aa_parse_debug_params(val); + if (i == DEBUG_PARSE_ERROR) + return -EINVAL; + + aa_g_debug = i; + return 0; +} + +static int param_get_audit(char *buffer, const struct kernel_param *kp) +{ + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); } -static int param_set_audit(const char *val, struct kernel_param *kp) +static int param_set_audit(const char *val, const struct kernel_param *kp) { int i; @@ -884,30 +2092,28 @@ static int param_set_audit(const char *val, struct kernel_param *kp) return -EINVAL; if (!val) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - for (i = 0; i < AUDIT_MAX_INDEX; i++) { - if (strcmp(val, audit_mode_names[i]) == 0) { - aa_g_audit = i; - return 0; - } - } + i = match_string(audit_mode_names, AUDIT_MAX_INDEX, val); + if (i < 0) + return -EINVAL; - return -EINVAL; + aa_g_audit = i; + return 0; } -static int param_get_mode(char *buffer, struct kernel_param *kp) +static int param_get_mode(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) return -EINVAL; - if (apparmor_initialized && !policy_view_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return sprintf(buffer, "%s", aa_profile_mode_names[aa_g_profile_mode]); } -static int param_set_mode(const char *val, struct kernel_param *kp) +static int param_set_mode(const char *val, const struct kernel_param *kp) { int i; @@ -915,17 +2121,112 @@ static int param_set_mode(const char *val, struct kernel_param *kp) return -EINVAL; if (!val) return -EINVAL; - if (apparmor_initialized && !policy_admin_capable(NULL)) + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - 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; + i = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX, + val); + if (i < 0) + return -EINVAL; + + aa_g_profile_mode = i; + return 0; +} + +char *aa_get_buffer(bool in_atomic) +{ + union aa_buffer *aa_buf; + struct aa_local_cache *cache; + bool try_again = true; + gfp_t flags = (GFP_KERNEL | __GFP_RETRY_MAYFAIL | __GFP_NOWARN); + + /* use per cpu cached buffers first */ + cache = get_cpu_ptr(&aa_local_buffers); + if (!list_empty(&cache->head)) { + aa_buf = list_first_entry(&cache->head, union aa_buffer, list); + list_del(&aa_buf->list); + cache->hold--; + cache->count--; + put_cpu_ptr(&aa_local_buffers); + return &aa_buf->buffer[0]; + } + put_cpu_ptr(&aa_local_buffers); + + if (!spin_trylock(&aa_buffers_lock)) { + cache = get_cpu_ptr(&aa_local_buffers); + cache->hold += 1; + put_cpu_ptr(&aa_local_buffers); + spin_lock(&aa_buffers_lock); + } else { + cache = get_cpu_ptr(&aa_local_buffers); + put_cpu_ptr(&aa_local_buffers); + } +retry: + if (buffer_count > reserve_count || + (in_atomic && !list_empty(&aa_global_buffers))) { + aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, + list); + list_del(&aa_buf->list); + buffer_count--; + spin_unlock(&aa_buffers_lock); + return aa_buf->buffer; + } + if (in_atomic) { + /* + * out of reserve buffers and in atomic context so increase + * how many buffers to keep in reserve + */ + reserve_count++; + flags = GFP_ATOMIC; + } + spin_unlock(&aa_buffers_lock); + + if (!in_atomic) + might_sleep(); + aa_buf = kmalloc(aa_g_path_max, flags); + if (!aa_buf) { + if (try_again) { + try_again = false; + spin_lock(&aa_buffers_lock); + goto retry; } + pr_warn_once("AppArmor: Failed to allocate a memory buffer.\n"); + return NULL; } + return aa_buf->buffer; +} - return -EINVAL; +void aa_put_buffer(char *buf) +{ + union aa_buffer *aa_buf; + struct aa_local_cache *cache; + + if (!buf) + return; + aa_buf = container_of(buf, union aa_buffer, buffer[0]); + + cache = get_cpu_ptr(&aa_local_buffers); + if (!cache->hold) { + put_cpu_ptr(&aa_local_buffers); + + if (spin_trylock(&aa_buffers_lock)) { + /* put back on global list */ + list_add(&aa_buf->list, &aa_global_buffers); + buffer_count++; + spin_unlock(&aa_buffers_lock); + cache = get_cpu_ptr(&aa_local_buffers); + put_cpu_ptr(&aa_local_buffers); + return; + } + /* contention on global list, fallback to percpu */ + cache = get_cpu_ptr(&aa_local_buffers); + cache->hold += 1; + } + + /* cache in percpu list */ + list_add(&aa_buf->list, &cache->head); + cache->count++; + put_cpu_ptr(&aa_local_buffers); } /* @@ -939,61 +2240,74 @@ static int param_set_mode(const char *val, struct kernel_param *kp) */ static int __init set_init_ctx(void) { - struct cred *cred = (struct cred *)current->real_cred; - struct aa_task_ctx *ctx; + struct cred *cred = (__force struct cred *)current->real_cred; - ctx = aa_alloc_task_context(GFP_KERNEL); - if (!ctx) - return -ENOMEM; - - ctx->label = aa_get_label(ns_unconfined(root_ns)); - cred_ctx(cred) = ctx; + set_cred_label(cred, aa_get_label(ns_unconfined(root_ns))); 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; - } + union aa_buffer *aa_buf; + + spin_lock(&aa_buffers_lock); + while (!list_empty(&aa_global_buffers)) { + aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer, + list); + list_del(&aa_buf->list); + spin_unlock(&aa_buffers_lock); + kfree(aa_buf); + spin_lock(&aa_buffers_lock); } + spin_unlock(&aa_buffers_lock); } static int __init alloc_buffers(void) { - u32 i, j; + union aa_buffer *aa_buf; + int i, num; + /* + * per cpu set of cached allocated buffers used to help reduce + * lock contention + */ 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; - } + per_cpu(aa_local_buffers, i).hold = 0; + per_cpu(aa_local_buffers, i).count = 0; + INIT_LIST_HEAD(&per_cpu(aa_local_buffers, i).head); } + /* + * A function may require two buffers at once. Usually the buffers are + * used for a short period of time and are shared. On UP kernel buffers + * two should be enough, with more CPUs it is possible that more + * buffers will be used simultaneously. The preallocated pool may grow. + * This preallocation has also the side-effect that AppArmor will be + * disabled early at boot if aa_g_path_max is extremely high. + */ + if (num_online_cpus() > 1) + num = 4 + RESERVE_COUNT; + else + num = 2 + RESERVE_COUNT; + + for (i = 0; i < num; i++) { + aa_buf = kmalloc(aa_g_path_max, GFP_KERNEL | + __GFP_RETRY_MAYFAIL | __GFP_NOWARN); + if (!aa_buf) { + destroy_buffers(); + return -ENOMEM; + } + aa_put_buffer(aa_buf->buffer); + } return 0; } #ifdef CONFIG_SYSCTL -static int apparmor_dointvec(struct ctl_table *table, int write, - void __user *buffer, size_t *lenp, loff_t *ppos) +static int apparmor_dointvec(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) { - if (!policy_admin_capable(NULL)) + if (!aa_current_policy_admin_capable(NULL)) return -EPERM; if (!apparmor_enabled) return -EINVAL; @@ -1001,12 +2315,8 @@ static int apparmor_dointvec(struct ctl_table *table, int write, return proc_dointvec(table, write, buffer, lenp, ppos); } -static struct ctl_path apparmor_sysctl_path[] = { - { .procname = "kernel", }, - { } -}; - -static struct ctl_table apparmor_sysctl_table[] = { +static const struct ctl_table apparmor_sysctl_table[] = { +#ifdef CONFIG_USER_NS { .procname = "unprivileged_userns_apparmor_policy", .data = &unprivileged_userns_apparmor_policy, @@ -1014,13 +2324,26 @@ static struct ctl_table apparmor_sysctl_table[] = { .mode = 0600, .proc_handler = apparmor_dointvec, }, - { } +#endif /* CONFIG_USER_NS */ + { + .procname = "apparmor_display_secid_mode", + .data = &apparmor_display_secid_mode, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = apparmor_dointvec, + }, + { + .procname = "apparmor_restrict_unprivileged_unconfined", + .data = &aa_unprivileged_unconfined_restricted, + .maxlen = sizeof(int), + .mode = 0600, + .proc_handler = apparmor_dointvec, + }, }; static int __init apparmor_init_sysctl(void) { - return register_sysctl_paths(apparmor_sysctl_path, - apparmor_sysctl_table) ? 0 : -ENOMEM; + return register_sysctl("kernel", apparmor_sysctl_table) ? 0 : -ENOMEM; } #else static inline int apparmor_init_sysctl(void) @@ -1029,15 +2352,149 @@ static inline int apparmor_init_sysctl(void) } #endif /* CONFIG_SYSCTL */ -static int __init apparmor_init(void) +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) +static unsigned int apparmor_ip_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) { + struct aa_sk_ctx *ctx; + struct sock *sk; int error; - if (!apparmor_enabled || !security_module_enable("apparmor")) { - aa_info_message("AppArmor disabled by boot time parameter"); - apparmor_enabled = 0; + if (!skb->secmark) + return NF_ACCEPT; + + sk = skb_to_full_sk(skb); + if (sk == NULL) + return NF_ACCEPT; + + ctx = aa_sock(sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, + AA_MAY_SEND, skb->secmark, sk); + rcu_read_unlock(); + if (!error) + return NF_ACCEPT; + + return NF_DROP_ERR(-ECONNREFUSED); + +} + +static const struct nf_hook_ops apparmor_nf_ops[] = { + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, +#if IS_ENABLED(CONFIG_IPV6) + { + .hook = apparmor_ip_postroute, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, +#endif +}; + +static int __net_init apparmor_nf_register(struct net *net) +{ + return nf_register_net_hooks(net, apparmor_nf_ops, + ARRAY_SIZE(apparmor_nf_ops)); +} + +static void __net_exit apparmor_nf_unregister(struct net *net) +{ + nf_unregister_net_hooks(net, apparmor_nf_ops, + ARRAY_SIZE(apparmor_nf_ops)); +} + +static struct pernet_operations apparmor_net_ops = { + .init = apparmor_nf_register, + .exit = apparmor_nf_unregister, +}; + +static int __init apparmor_nf_ip_init(void) +{ + int err; + + if (!apparmor_enabled) return 0; + + err = register_pernet_subsys(&apparmor_net_ops); + if (err) + panic("Apparmor: register_pernet_subsys: error %d\n", err); + + return 0; +} +#endif + +static char nulldfa_src[] __aligned(8) = { + #include "nulldfa.in" +}; +static struct aa_dfa *nulldfa; + +static char stacksplitdfa_src[] __aligned(8) = { + #include "stacksplitdfa.in" +}; +struct aa_dfa *stacksplitdfa; +struct aa_policydb *nullpdb; + +static int __init aa_setup_dfa_engine(void) +{ + int error = -ENOMEM; + + nullpdb = aa_alloc_pdb(GFP_KERNEL); + if (!nullpdb) + return -ENOMEM; + + nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src), + TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32)); + if (IS_ERR(nulldfa)) { + error = PTR_ERR(nulldfa); + goto fail; } + nullpdb->dfa = aa_get_dfa(nulldfa); + nullpdb->perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL); + if (!nullpdb->perms) + goto fail; + nullpdb->size = 2; + + stacksplitdfa = aa_dfa_unpack(stacksplitdfa_src, + sizeof(stacksplitdfa_src), + TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32)); + if (IS_ERR(stacksplitdfa)) { + error = PTR_ERR(stacksplitdfa); + goto fail; + } + + return 0; + +fail: + aa_put_pdb(nullpdb); + aa_put_dfa(nulldfa); + nullpdb = NULL; + nulldfa = NULL; + stacksplitdfa = NULL; + + return error; +} + +static void __init aa_teardown_dfa_engine(void) +{ + aa_put_dfa(stacksplitdfa); + aa_put_dfa(nulldfa); + aa_put_pdb(nullpdb); + nullpdb = NULL; + stacksplitdfa = NULL; + nulldfa = NULL; +} + +static int __init apparmor_init(void) +{ + int error; error = aa_setup_dfa_engine(); if (error) { @@ -1061,7 +2518,7 @@ static int __init apparmor_init(void) error = alloc_buffers(); if (error) { AA_ERROR("Unable to allocate work buffers\n"); - goto buffers_out; + goto alloc_out; } error = set_init_ctx(); @@ -1071,7 +2528,10 @@ static int __init apparmor_init(void) goto buffers_out; } security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks), - "apparmor"); + &apparmor_lsmid); + + /* Inform the audit system that secctx is used */ + audit_cfg_lsm(&apparmor_lsmid, AUDIT_CFG_LSM_SECCTX_SUBJECT); /* Report that AppArmor successfully initialized */ apparmor_initialized = 1; @@ -1086,13 +2546,25 @@ static int __init apparmor_init(void) buffers_out: destroy_buffers(); - alloc_out: aa_destroy_aafs(); aa_teardown_dfa_engine(); - apparmor_enabled = 0; + apparmor_enabled = false; return error; } -security_initcall(apparmor_init); +DEFINE_LSM(apparmor) = { + .id = &apparmor_lsmid, + .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, + .enabled = &apparmor_enabled, + .blobs = &apparmor_blob_sizes, + .init = apparmor_init, + .initcall_fs = aa_create_aafs, +#if defined(CONFIG_NETFILTER) && defined(CONFIG_NETWORK_SECMARK) + .initcall_device = apparmor_nf_ip_init, +#endif +#ifdef CONFIG_SECURITY_APPARMOR_HASH + .initcall_late = init_profile_hash, +#endif +}; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 72c604350e80..c5a91600842a 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2012 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/errno.h> @@ -25,33 +21,6 @@ #define base_idx(X) ((X) & 0xffffff) -static char nulldfa_src[] = { - #include "nulldfa.in" -}; -struct aa_dfa *nulldfa; - -int aa_setup_dfa_engine(void) -{ - int error; - - nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src), - TO_ACCEPT1_FLAG(YYTD_DATA32) | - TO_ACCEPT2_FLAG(YYTD_DATA32)); - if (!IS_ERR(nulldfa)) - return 0; - - error = PTR_ERR(nulldfa); - nulldfa = NULL; - - return error; -} - -void aa_teardown_dfa_engine(void) -{ - aa_put_dfa(nulldfa); - nulldfa = NULL; -} - /** * unpack_table - unpack a dfa table (one of accept, default, base, next check) * @blob: data to unpack (NOT NULL) @@ -84,6 +53,9 @@ static struct table_header *unpack_table(char *blob, size_t bsize) th.td_flags == YYTD_DATA8)) goto out; + /* if we have a table it must have some entries */ + if (th.td_lolen == 0) + goto out; tsize = table_size(th.td_lolen, th.td_flags); if (bsize < tsize) goto out; @@ -119,8 +91,8 @@ fail: } /** - * verify_dfa - verify that transitions and states in the tables are in bounds. - * @dfa: dfa to test (NOT NULL) + * verify_table_headers - verify that the tables headers are as expected + * @tables: array of dfa tables to check (NOT NULL) * @flags: flags controlling what type of accept table are acceptable * * Assumes dfa has gone through the first pass verification done by unpacking @@ -128,64 +100,120 @@ fail: * * Returns: %0 else error code on failure to verify */ -static int verify_dfa(struct aa_dfa *dfa, int flags) +static int verify_table_headers(struct table_header **tables, int flags) { - size_t i, state_count, trans_count; + size_t state_count, trans_count; int error = -EPROTO; /* check that required tables exist */ - if (!(dfa->tables[YYTD_ID_DEF] && - dfa->tables[YYTD_ID_BASE] && - dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK])) + if (!(tables[YYTD_ID_DEF] && tables[YYTD_ID_BASE] && + tables[YYTD_ID_NXT] && tables[YYTD_ID_CHK])) goto out; /* accept.size == default.size == base.size */ - state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + state_count = tables[YYTD_ID_BASE]->td_lolen; if (ACCEPT1_FLAGS(flags)) { - if (!dfa->tables[YYTD_ID_ACCEPT]) + if (!tables[YYTD_ID_ACCEPT]) goto out; - if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen) + if (state_count != tables[YYTD_ID_ACCEPT]->td_lolen) goto out; } if (ACCEPT2_FLAGS(flags)) { - if (!dfa->tables[YYTD_ID_ACCEPT2]) + if (!tables[YYTD_ID_ACCEPT2]) goto out; - if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen) + if (state_count != tables[YYTD_ID_ACCEPT2]->td_lolen) goto out; } - if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen) + if (state_count != tables[YYTD_ID_DEF]->td_lolen) goto out; /* next.size == chk.size */ - trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; - if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen) + trans_count = tables[YYTD_ID_NXT]->td_lolen; + if (trans_count != tables[YYTD_ID_CHK]->td_lolen) goto out; /* if equivalence classes then its table size must be 256 */ - if (dfa->tables[YYTD_ID_EC] && - dfa->tables[YYTD_ID_EC]->td_lolen != 256) + if (tables[YYTD_ID_EC] && tables[YYTD_ID_EC]->td_lolen != 256) goto out; - if (flags & DFA_FLAG_VERIFY_STATES) { - for (i = 0; i < state_count; i++) { - if (DEFAULT_TABLE(dfa)[i] >= state_count) - goto out; - if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) { - printk(KERN_ERR "AppArmor DFA next/check upper " - "bounds error\n"); + error = 0; +out: + return error; +} + +/** + * verify_dfa - verify that transitions and states in the tables are in bounds. + * @dfa: dfa to test (NOT NULL) + * + * Assumes dfa has gone through the first pass verification done by unpacking + * NOTE: this does not valid accept table values + * + * Returns: %0 else error code on failure to verify + */ +static int verify_dfa(struct aa_dfa *dfa) +{ + size_t i, state_count, trans_count; + int error = -EPROTO; + + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen; + if (state_count == 0) + goto out; + for (i = 0; i < state_count; i++) { + if (!(BASE_TABLE(dfa)[i] & MATCH_FLAG_DIFF_ENCODE) && + (DEFAULT_TABLE(dfa)[i] >= state_count)) + goto out; + if (BASE_TABLE(dfa)[i] & MATCH_FLAGS_INVALID) { + pr_err("AppArmor DFA state with invalid match flags"); + goto out; + } + if ((BASE_TABLE(dfa)[i] & MATCH_FLAG_DIFF_ENCODE)) { + if (!(dfa->flags & YYTH_FLAG_DIFF_ENCODE)) { + pr_err("AppArmor DFA diff encoded transition state without header flag"); goto out; } } - - for (i = 0; i < trans_count; i++) { - if (NEXT_TABLE(dfa)[i] >= state_count) + if ((BASE_TABLE(dfa)[i] & MATCH_FLAG_OOB_TRANSITION)) { + if (base_idx(BASE_TABLE(dfa)[i]) < dfa->max_oob) { + pr_err("AppArmor DFA out of bad transition out of range"); goto out; - if (CHECK_TABLE(dfa)[i] >= state_count) + } + if (!(dfa->flags & YYTH_FLAG_OOB_TRANS)) { + pr_err("AppArmor DFA out of bad transition state without header flag"); goto out; + } + } + if (base_idx(BASE_TABLE(dfa)[i]) + 255 >= trans_count) { + pr_err("AppArmor DFA next/check upper bounds error\n"); + goto out; } } + for (i = 0; i < trans_count; i++) { + if (NEXT_TABLE(dfa)[i] >= state_count) + goto out; + if (CHECK_TABLE(dfa)[i] >= state_count) + goto out; + } + + /* Now that all the other tables are verified, verify diffencoding */ + for (i = 0; i < state_count; i++) { + size_t j, k; + + for (j = i; + (BASE_TABLE(dfa)[j] & MATCH_FLAG_DIFF_ENCODE) && + !(BASE_TABLE(dfa)[j] & MARK_DIFF_ENCODE); + j = k) { + k = DEFAULT_TABLE(dfa)[j]; + if (j == k) + goto out; + if (k < j) + break; /* already verified */ + BASE_TABLE(dfa)[j] |= MARK_DIFF_ENCODE; + } + } error = 0; + out: return error; } @@ -211,7 +239,7 @@ static void dfa_free(struct aa_dfa *dfa) /** * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa) - * @kr: kref callback for freeing of a dfa (NOT NULL) + * @kref: kref callback for freeing of a dfa (NOT NULL) */ void aa_dfa_free_kref(struct kref *kref) { @@ -219,6 +247,42 @@ void aa_dfa_free_kref(struct kref *kref) dfa_free(dfa); } + + +/** + * remap_data16_to_data32 - remap u16 @old table to a u32 based table + * @old: table to remap + * + * Returns: new table with u32 entries instead of u16. + * + * Note: will free @old so caller does not have to + */ +static struct table_header *remap_data16_to_data32(struct table_header *old) +{ + struct table_header *new; + size_t tsize; + u32 i; + + tsize = table_size(old->td_lolen, YYTD_DATA32); + new = kvzalloc(tsize, GFP_KERNEL); + if (!new) { + kvfree(old); + return NULL; + } + new->td_id = old->td_id; + new->td_flags = YYTD_DATA32; + new->td_lolen = old->td_lolen; + + for (i = 0; i < old->td_lolen; i++) + TABLE_DATAU32(new)[i] = (u32) TABLE_DATAU16(old)[i]; + + kvfree(old); + if (is_vmalloc_addr(new)) + vm_unmap_aliases(); + + return new; +} + /** * aa_dfa_unpack - unpack the binary tables of a serialized dfa * @blob: aligned serialized stream of data to unpack (NOT NULL) @@ -257,6 +321,23 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) goto fail; dfa->flags = ntohs(*(__be16 *) (data + 12)); + if (dfa->flags & ~(YYTH_FLAGS)) + goto fail; + + /* + * TODO: needed for dfa to support more than 1 oob + * if (dfa->flags & YYTH_FLAGS_OOB_TRANS) { + * if (hsize < 16 + 4) + * goto fail; + * dfa->max_oob = ntol(*(__be32 *) (data + 16)); + * if (dfa->max <= MAX_OOB_SUPPORTED) { + * pr_err("AppArmor DFA OOB greater than supported\n"); + * goto fail; + * } + * } + */ + dfa->max_oob = 1; + data += hsize; size -= hsize; @@ -281,8 +362,10 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) case YYTD_ID_DEF: case YYTD_ID_NXT: case YYTD_ID_CHK: - if (table->td_flags != YYTD_DATA16) + if (!(table->td_flags == YYTD_DATA16 || + table->td_flags == YYTD_DATA32)) { goto fail; + } break; case YYTD_ID_EC: if (table->td_flags != YYTD_DATA8) @@ -297,13 +380,35 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) dfa->tables[table->td_id] = table; data += table_size(table->td_lolen, table->td_flags); size -= table_size(table->td_lolen, table->td_flags); + + /* + * this remapping has to be done after incrementing data above + * for now straight remap, later have dfa support both + */ + switch (table->td_id) { + case YYTD_ID_DEF: + case YYTD_ID_NXT: + case YYTD_ID_CHK: + if (table->td_flags == YYTD_DATA16) { + table = remap_data16_to_data32(table); + if (!table) + goto fail; + } + dfa->tables[table->td_id] = table; + break; + } table = NULL; } - - error = verify_dfa(dfa, flags); + error = verify_table_headers(dfa->tables, flags); if (error) goto fail; + if (flags & DFA_FLAG_VERIFY_STATES) { + error = verify_dfa(dfa); + if (error) + goto fail; + } + return dfa; fail: @@ -312,6 +417,20 @@ fail: return ERR_PTR(error); } +#define match_char(state, def, base, next, check, C) \ +do { \ + u32 b = (base)[(state)]; \ + unsigned int pos = base_idx(b) + (C); \ + if ((check)[pos] != (state)) { \ + (state) = (def)[(state)]; \ + if (b & MATCH_FLAG_DIFF_ENCODE) \ + continue; \ + break; \ + } \ + (state) = (next)[pos]; \ + break; \ +} while (1) + /** * aa_dfa_match_len - traverse @dfa to find state @str stops at * @dfa: the dfa to match @str against (NOT NULL) @@ -328,132 +447,356 @@ fail: * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start, - const char *str, int len) +aa_state_t aa_dfa_match_len(struct aa_dfa *dfa, aa_state_t start, + const char *str, int len) +{ + u32 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + aa_state_t state = start; + + if (state == DFA_NOMATCH) + return DFA_NOMATCH; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + for (; len; len--) + match_char(state, def, base, next, check, + equiv[(u8) *str++]); + } else { + /* default is direct to next state */ + for (; len; len--) + match_char(state, def, base, next, check, (u8) *str++); + } + + return state; +} + +/** + * aa_dfa_match - traverse @dfa to find state @str stops at + * @dfa: the dfa to match @str against (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * + * aa_dfa_match will match @str against the dfa and return the state it + * finished matching in. The final state can be used to look up the accepting + * label, or as the start state of a continuing match. + * + * Returns: final state reached after input is consumed + */ +aa_state_t aa_dfa_match(struct aa_dfa *dfa, aa_state_t start, const char *str) { - u16 *def = DEFAULT_TABLE(dfa); + u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); - u16 *next = NEXT_TABLE(dfa); - u16 *check = CHECK_TABLE(dfa); - unsigned int state = start, pos; + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + aa_state_t state = start; - if (state == 0) - return 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { /* Equivalence class table defined */ u8 *equiv = EQUIV_TABLE(dfa); /* default is direct to next state */ - for (; len; len--) { + while (*str) + match_char(state, def, base, next, check, + equiv[(u8) *str++]); + } else { + /* default is direct to next state */ + while (*str) + match_char(state, def, base, next, check, (u8) *str++); + } + + return state; +} + +/** + * aa_dfa_next - step one character to the next state in the dfa + * @dfa: the dfa to traverse (NOT NULL) + * @state: the state to start in + * @c: the input character to transition on + * + * aa_dfa_match will step through the dfa by one input character @c + * + * Returns: state reach after input @c + */ +aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c) +{ + u32 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + match_char(state, def, base, next, check, equiv[(u8) c]); + } else + match_char(state, def, base, next, check, (u8) c); + + return state; +} + +aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state) +{ + u32 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + u32 b = (base)[(state)]; + + if (!(b & MATCH_FLAG_OOB_TRANSITION)) + return DFA_NOMATCH; + + /* No Equivalence class remapping for outofband transitions */ + match_char(state, def, base, next, check, -1); + + return state; +} + +/** + * aa_dfa_match_until - traverse @dfa until accept state or end of input + * @dfa: the dfa to match @str against (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * @retpos: first character in str after match OR end of string + * + * aa_dfa_match will match @str against the dfa and return the state it + * finished matching in. The final state can be used to look up the accepting + * label, or as the start state of a continuing match. + * + * Returns: final state reached after input is consumed + */ +aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, const char **retpos) +{ + u32 *def = DEFAULT_TABLE(dfa); + u32 *base = BASE_TABLE(dfa); + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + u32 *accept = ACCEPT_TABLE(dfa); + aa_state_t state = start, pos; + + if (state == DFA_NOMATCH) + return DFA_NOMATCH; + + /* current state is <state>, matching character *str */ + if (dfa->tables[YYTD_ID_EC]) { + /* Equivalence class table defined */ + u8 *equiv = EQUIV_TABLE(dfa); + /* default is direct to next state */ + while (*str) { pos = base_idx(base[state]) + equiv[(u8) *str++]; if (check[pos] == state) state = next[pos]; else state = def[state]; + if (accept[state]) + break; } } else { /* default is direct to next state */ - for (; len; len--) { + while (*str) { pos = base_idx(base[state]) + (u8) *str++; if (check[pos] == state) state = next[pos]; else state = def[state]; + if (accept[state]) + break; } } + *retpos = str; return state; } /** - * aa_dfa_match - traverse @dfa to find state @str stops at + * aa_dfa_matchn_until - traverse @dfa until accept or @n bytes consumed * @dfa: the dfa to match @str against (NOT NULL) * @start: the state of the dfa to start matching in - * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * @str: the string of bytes to match against the dfa (NOT NULL) + * @n: length of the string of bytes to match + * @retpos: first character in str after match OR str + n * - * aa_dfa_match will match @str against the dfa and return the state it + * aa_dfa_match_len will match @str against the dfa and return the state it * finished matching in. The final state can be used to look up the accepting * label, or as the start state of a continuing match. * + * This function will happily match again the 0 byte and only finishes + * when @n input is consumed. + * * Returns: final state reached after input is consumed */ -unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start, - const char *str) +aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, + const char *str, int n, const char **retpos) { - u16 *def = DEFAULT_TABLE(dfa); + u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); - u16 *next = NEXT_TABLE(dfa); - u16 *check = CHECK_TABLE(dfa); - unsigned int state = start, pos; + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + u32 *accept = ACCEPT_TABLE(dfa); + aa_state_t state = start, pos; - if (state == 0) - return 0; + *retpos = NULL; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { /* Equivalence class table defined */ u8 *equiv = EQUIV_TABLE(dfa); /* default is direct to next state */ - while (*str) { + for (; n; n--) { pos = base_idx(base[state]) + equiv[(u8) *str++]; if (check[pos] == state) state = next[pos]; else state = def[state]; + if (accept[state]) + break; } } else { /* default is direct to next state */ - while (*str) { + for (; n; n--) { pos = base_idx(base[state]) + (u8) *str++; if (check[pos] == state) state = next[pos]; else state = def[state]; + if (accept[state]) + break; } } + *retpos = str; return state; } -/** - * aa_dfa_next - step one character to the next state in the dfa - * @dfa: the dfa to tranverse (NOT NULL) - * @state: the state to start in - * @c: the input character to transition on - * - * aa_dfa_match will step through the dfa by one input character @c - * - * Returns: state reach after input @c - */ -unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state, - const char c) +#define inc_wb_pos(wb) \ +do { \ + BUILD_BUG_ON_NOT_POWER_OF_2(WB_HISTORY_SIZE); \ + wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \ + wb->len = (wb->len + 1) > WB_HISTORY_SIZE ? WB_HISTORY_SIZE : \ + wb->len + 1; \ +} while (0) + +/* For DFAs that don't support extended tagging of states */ +/* adjust is only set if is_loop returns true */ +static bool is_loop(struct match_workbuf *wb, aa_state_t state, + unsigned int *adjust) +{ + int pos = wb->pos; + int i; + + if (wb->history[pos] < state) + return false; + + for (i = 0; i < wb->len; i++) { + if (wb->history[pos] == state) { + *adjust = i; + return true; + } + /* -1 wraps to WB_HISTORY_SIZE - 1 */ + pos = (pos - 1) & (WB_HISTORY_SIZE - 1); + } + + return false; +} + +static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, + const char *str, struct match_workbuf *wb, + unsigned int *count) { - u16 *def = DEFAULT_TABLE(dfa); + u32 *def = DEFAULT_TABLE(dfa); u32 *base = BASE_TABLE(dfa); - u16 *next = NEXT_TABLE(dfa); - u16 *check = CHECK_TABLE(dfa); - unsigned int pos; + u32 *next = NEXT_TABLE(dfa); + u32 *check = CHECK_TABLE(dfa); + aa_state_t state = start, pos; + + AA_BUG(!dfa); + AA_BUG(!str); + AA_BUG(!wb); + AA_BUG(!count); + + *count = 0; + if (state == DFA_NOMATCH) + return DFA_NOMATCH; /* current state is <state>, matching character *str */ if (dfa->tables[YYTD_ID_EC]) { /* Equivalence class table defined */ u8 *equiv = EQUIV_TABLE(dfa); /* default is direct to next state */ + while (*str) { + unsigned int adjust; - pos = base_idx(base[state]) + equiv[(u8) c]; - if (check[pos] == state) - state = next[pos]; - else - state = def[state]; + wb->history[wb->pos] = state; + pos = base_idx(base[state]) + equiv[(u8) *str++]; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + if (is_loop(wb, state, &adjust)) { + state = aa_dfa_match(dfa, state, str); + *count -= adjust; + goto out; + } + inc_wb_pos(wb); + (*count)++; + } } else { /* default is direct to next state */ - pos = base_idx(base[state]) + (u8) c; - if (check[pos] == state) - state = next[pos]; - else - state = def[state]; + while (*str) { + unsigned int adjust; + + wb->history[wb->pos] = state; + pos = base_idx(base[state]) + (u8) *str++; + if (check[pos] == state) + state = next[pos]; + else + state = def[state]; + if (is_loop(wb, state, &adjust)) { + state = aa_dfa_match(dfa, state, str); + *count -= adjust; + goto out; + } + inc_wb_pos(wb); + (*count)++; + } } +out: + if (!state) + *count = 0; return state; } + +/** + * aa_dfa_leftmatch - traverse @dfa to find state @str stops at + * @dfa: the dfa to match @str against (NOT NULL) + * @start: the state of the dfa to start matching in + * @str: the null terminated string of bytes to match against the dfa (NOT NULL) + * @count: current count of longest left. + * + * aa_dfa_match will match @str against the dfa and return the state it + * finished matching in. The final state can be used to look up the accepting + * label, or as the start state of a continuing match. + * + * Returns: final state reached after input is consumed + */ +aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start, + const char *str, unsigned int *count) +{ + DEFINE_MATCH_WB(wb); + + /* TODO: match for extended state dfas */ + + return leftmatch_fb(dfa, start, str, &wb, count); +} diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c new file mode 100644 index 000000000000..523570aa1a5a --- /dev/null +++ b/security/apparmor/mount.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor mediation of files + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2017 Canonical Ltd. + */ + +#include <linux/fs.h> +#include <linux/mount.h> +#include <linux/namei.h> +#include <uapi/linux/mount.h> + +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/cred.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_NOSYMFOLLOW) + audit_log_format(ab, ", nosymfollow"); + 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; + struct apparmor_audit_data *ad = aad(sa); + + if (ad->mnt.type) { + audit_log_format(ab, " fstype="); + audit_log_untrustedstring(ab, ad->mnt.type); + } + if (ad->mnt.src_name) { + audit_log_format(ab, " srcname="); + audit_log_untrustedstring(ab, ad->mnt.src_name); + } + if (ad->mnt.trans) { + audit_log_format(ab, " trans="); + audit_log_untrustedstring(ab, ad->mnt.trans); + } + if (ad->mnt.flags) { + audit_log_format(ab, " flags=\""); + audit_mnt_flags(ab, ad->mnt.flags); + audit_log_format(ab, "\""); + } + if (ad->mnt.data) { + audit_log_format(ab, " options="); + audit_log_untrustedstring(ab, ad->mnt.data); + } +} + +/** + * audit_mount - handle the auditing of mount operations + * @subj_cred: cred of the subject + * @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 independent 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(const struct cred *subj_cred, + struct aa_profile *profile, const char *op, + const char *name, const char *src_name, + const char *type, const char *trans, + unsigned long flags, const void *data, u32 request, + struct aa_perms *perms, const char *info, int error) +{ + int audit_type = AUDIT_APPARMOR_AUTO; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, 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 error; + } + + ad.subj_cred = subj_cred; + ad.name = name; + ad.mnt.src_name = src_name; + ad.mnt.type = type; + ad.mnt.trans = trans; + ad.mnt.flags = flags; + if (data && (perms->audit & AA_AUDIT_DATA)) + ad.mnt.data = data; + ad.info = info; + ad.error = error; + + return aa_audit(audit_type, profile, &ad, 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 aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t 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; +} + +static const char * const mnt_info_table[] = { + "match succeeded", + "failed mntpnt match", + "failed srcname match", + "failed type match", + "failed flags match", + "failed data match", + "failed perms check" +}; + +/* + * 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_policydb *policy, aa_state_t start, + const char *mntpnt, const char *devname, + const char *type, unsigned long flags, + void *data, bool binary, struct aa_perms *perms) +{ + aa_state_t state; + + AA_BUG(!policy); + AA_BUG(!policy->dfa); + AA_BUG(!policy->perms); + AA_BUG(!perms); + + state = aa_dfa_match(policy->dfa, start, mntpnt); + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + return 1; + + if (devname) + state = aa_dfa_match(policy->dfa, state, devname); + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + return 2; + + if (type) + state = aa_dfa_match(policy->dfa, state, type); + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + return 3; + + state = match_mnt_flags(policy->dfa, state, flags); + if (!state) + return 4; + *perms = *aa_lookup_perms(policy, 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(policy->dfa, state); + if (!state) + return 4; + + state = aa_dfa_match(policy->dfa, state, data); + if (!state) + return 5; + *perms = *aa_lookup_perms(policy, state); + if (perms->allow & AA_MAY_MOUNT) + return 0; + } + + /* failed at perms check, don't confuse with flags match */ + return 6; +} + + +static int path_flags(struct aa_profile *profile, const struct path *path) +{ + AA_BUG(!profile); + AA_BUG(!path); + + return profile->path_flags | + (S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0); +} + +/** + * match_mnt_path_str - handle path matching for mount + * @subj_cred: cred of confined subject + * @profile: the confining profile + * @mntpath: for the mntpnt (NOT NULL) + * @buffer: buffer to be used to lookup mntpath + * @devname: string for the devname/src_name (MAY BE NULL OR ERRPTR) + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * @devinfo: error str if (IS_ERR(@devname)) + * + * Returns: 0 on success else error + */ +static int match_mnt_path_str(const struct cred *subj_cred, + struct aa_profile *profile, + const struct path *mntpath, char *buffer, + const char *devname, const char *type, + unsigned long flags, void *data, bool binary, + const char *devinfo) +{ + struct aa_perms perms = { }; + const char *mntpnt = NULL, *info = NULL; + struct aa_ruleset *rules = profile->label.rules[0]; + int pos, error; + + AA_BUG(!profile); + AA_BUG(!mntpath); + AA_BUG(!buffer); + + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) + return 0; + + error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer, + &mntpnt, &info, profile->disconnected); + if (error) + goto audit; + if (IS_ERR(devname)) { + error = PTR_ERR(devname); + devname = NULL; + info = devinfo; + goto audit; + } + + error = -EACCES; + pos = do_match_mnt(rules->policy, + rules->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(subj_cred, profile, OP_MOUNT, mntpnt, devname, + type, NULL, + flags, data, AA_MAY_MOUNT, &perms, info, error); +} + +/** + * match_mnt - handle path matching for mount + * @subj_cred: cred of the subject + * @profile: the confining profile + * @path: for the mntpnt (NOT NULL) + * @buffer: buffer to be used to lookup mntpath + * @devpath: path devname/src_name (MAYBE NULL) + * @devbuffer: buffer to be used to lookup devname/src_name + * @type: string for the dev type (MAYBE NULL) + * @flags: mount flags to match + * @data: fs mount data (MAYBE NULL) + * @binary: whether @data is binary + * + * Returns: 0 on success else error + */ +static int match_mnt(const struct cred *subj_cred, + struct aa_profile *profile, const struct path *path, + char *buffer, const struct path *devpath, char *devbuffer, + const char *type, unsigned long flags, void *data, + bool binary) +{ + const char *devname = NULL, *info = NULL; + struct aa_ruleset *rules = profile->label.rules[0]; + int error = -EACCES; + + AA_BUG(!profile); + AA_BUG(devpath && !devbuffer); + + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) + return 0; + + if (devpath) { + error = aa_path_name(devpath, path_flags(profile, devpath), + devbuffer, &devname, &info, + profile->disconnected); + if (error) + devname = ERR_PTR(error); + } + + return match_mnt_path_str(subj_cred, profile, path, buffer, devname, + type, flags, data, binary, info); +} + +int aa_remount(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + unsigned long flags, void *data) +{ + struct aa_profile *profile; + char *buffer = NULL; + bool binary; + int error; + + AA_BUG(!label); + AA_BUG(!path); + + binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; + + buffer = aa_get_buffer(false); + if (!buffer) + return -ENOMEM; + error = fn_for_each_confined(label, profile, + match_mnt(subj_cred, profile, path, buffer, NULL, + NULL, NULL, + flags, data, binary)); + aa_put_buffer(buffer); + + return error; +} + +int aa_bind_mount(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + const char *dev_name, unsigned long flags) +{ + struct aa_profile *profile; + char *buffer = NULL, *old_buffer = NULL; + struct path old_path; + int error; + + AA_BUG(!label); + AA_BUG(!path); + + if (!dev_name || !*dev_name) + return -EINVAL; + + flags &= MS_REC | MS_BIND; + + error = kern_path(dev_name, LOOKUP_FOLLOW|LOOKUP_AUTOMOUNT, &old_path); + if (error) + return error; + + buffer = aa_get_buffer(false); + old_buffer = aa_get_buffer(false); + error = -ENOMEM; + if (!buffer || !old_buffer) + goto out; + + error = fn_for_each_confined(label, profile, + match_mnt(subj_cred, profile, path, buffer, &old_path, + old_buffer, NULL, flags, NULL, false)); +out: + aa_put_buffer(buffer); + aa_put_buffer(old_buffer); + path_put(&old_path); + + return error; +} + +int aa_mount_change_type(const struct cred *subj_cred, + struct aa_label *label, const struct path *path, + unsigned long flags) +{ + struct aa_profile *profile; + char *buffer = NULL; + int error; + + AA_BUG(!label); + AA_BUG(!path); + + /* These are the flags allowed by do_change_type() */ + flags &= (MS_REC | MS_SILENT | MS_SHARED | MS_PRIVATE | MS_SLAVE | + MS_UNBINDABLE); + + buffer = aa_get_buffer(false); + if (!buffer) + return -ENOMEM; + error = fn_for_each_confined(label, profile, + match_mnt(subj_cred, profile, path, buffer, NULL, + NULL, NULL, + flags, NULL, false)); + aa_put_buffer(buffer); + + return error; +} + +int aa_move_mount(const struct cred *subj_cred, + struct aa_label *label, const struct path *from_path, + const struct path *to_path) +{ + struct aa_profile *profile; + char *to_buffer = NULL, *from_buffer = NULL; + int error; + + AA_BUG(!label); + AA_BUG(!from_path); + AA_BUG(!to_path); + + to_buffer = aa_get_buffer(false); + from_buffer = aa_get_buffer(false); + error = -ENOMEM; + if (!to_buffer || !from_buffer) + goto out; + + if (!our_mnt(from_path->mnt)) + /* moving a mount detached from the namespace */ + from_path = NULL; + error = fn_for_each_confined(label, profile, + match_mnt(subj_cred, profile, to_path, to_buffer, + from_path, from_buffer, + NULL, MS_MOVE, NULL, false)); +out: + aa_put_buffer(to_buffer); + aa_put_buffer(from_buffer); + + return error; +} + +int aa_move_mount_old(const struct cred *subj_cred, struct aa_label *label, + const struct path *path, const char *orig_name) +{ + struct path old_path; + int error; + + if (!orig_name || !*orig_name) + return -EINVAL; + error = kern_path(orig_name, LOOKUP_FOLLOW, &old_path); + if (error) + return error; + + error = aa_move_mount(subj_cred, label, &old_path, path); + path_put(&old_path); + + return error; +} + +int aa_new_mount(const struct cred *subj_cred, struct aa_label *label, + const char *dev_name, const struct path *path, + const char *type, unsigned long flags, void *data) +{ + struct aa_profile *profile; + char *buffer = NULL, *dev_buffer = NULL; + bool binary = true; + int error; + int requires_dev = 0; + struct path tmp_path, *dev_path = NULL; + + AA_BUG(!label); + AA_BUG(!path); + + if (type) { + struct file_system_type *fstype; + + 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) { + if (!dev_name || !*dev_name) + return -ENOENT; + + error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path); + if (error) + return error; + dev_path = &tmp_path; + } + } + + buffer = aa_get_buffer(false); + if (!buffer) { + error = -ENOMEM; + goto out; + } + if (dev_path) { + dev_buffer = aa_get_buffer(false); + if (!dev_buffer) { + error = -ENOMEM; + goto out; + } + error = fn_for_each_confined(label, profile, + match_mnt(subj_cred, profile, path, buffer, + dev_path, dev_buffer, + type, flags, data, binary)); + } else { + error = fn_for_each_confined(label, profile, + match_mnt_path_str(subj_cred, profile, path, + buffer, dev_name, + type, flags, data, binary, NULL)); + } + +out: + aa_put_buffer(buffer); + aa_put_buffer(dev_buffer); + if (dev_path) + path_put(dev_path); + + return error; +} + +static int profile_umount(const struct cred *subj_cred, + struct aa_profile *profile, const struct path *path, + char *buffer) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms perms = { }; + const char *name = NULL, *info = NULL; + aa_state_t state; + int error; + + AA_BUG(!profile); + AA_BUG(!path); + + if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT)) + return 0; + + error = aa_path_name(path, path_flags(profile, path), buffer, &name, + &info, profile->disconnected); + if (error) + goto audit; + + state = aa_dfa_match(rules->policy->dfa, + rules->policy->start[AA_CLASS_MOUNT], + name); + perms = *aa_lookup_perms(rules->policy, state); + if (AA_MAY_UMOUNT & ~perms.allow) + error = -EACCES; + +audit: + return audit_mount(subj_cred, profile, OP_UMOUNT, name, NULL, NULL, + NULL, 0, NULL, + AA_MAY_UMOUNT, &perms, info, error); +} + +int aa_umount(const struct cred *subj_cred, struct aa_label *label, + struct vfsmount *mnt, int flags) +{ + struct aa_profile *profile; + char *buffer = NULL; + int error; + struct path path = { .mnt = mnt, .dentry = mnt->mnt_root }; + + AA_BUG(!label); + AA_BUG(!mnt); + + buffer = aa_get_buffer(false); + if (!buffer) + return -ENOMEM; + + error = fn_for_each_confined(label, profile, + profile_umount(subj_cred, profile, &path, buffer)); + aa_put_buffer(buffer); + + return error; +} + +/* helper fn for transition on pivotroot + * + * Returns: label for transition or ERR_PTR. Does not return NULL + */ +static struct aa_label *build_pivotroot(const struct cred *subj_cred, + struct aa_profile *profile, + const struct path *new_path, + char *new_buffer, + const struct path *old_path, + char *old_buffer) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + const char *old_name, *new_name = NULL, *info = NULL; + const char *trans_name = NULL; + struct aa_perms perms = { }; + aa_state_t state; + int error; + + AA_BUG(!profile); + AA_BUG(!new_path); + AA_BUG(!old_path); + + if (profile_unconfined(profile) || + !RULE_MEDIATES(rules, AA_CLASS_MOUNT)) + return aa_get_newest_label(&profile->label); + + error = aa_path_name(old_path, path_flags(profile, old_path), + old_buffer, &old_name, &info, + profile->disconnected); + if (error) + goto audit; + error = aa_path_name(new_path, path_flags(profile, new_path), + new_buffer, &new_name, &info, + profile->disconnected); + if (error) + goto audit; + + error = -EACCES; + state = aa_dfa_match(rules->policy->dfa, + rules->policy->start[AA_CLASS_MOUNT], + new_name); + state = aa_dfa_null_transition(rules->policy->dfa, state); + state = aa_dfa_match(rules->policy->dfa, state, old_name); + perms = *aa_lookup_perms(rules->policy, state); + + if (AA_MAY_PIVOTROOT & perms.allow) + error = 0; + +audit: + error = audit_mount(subj_cred, profile, OP_PIVOTROOT, new_name, + old_name, + NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, + &perms, info, error); + if (error) + return ERR_PTR(error); + + return aa_get_newest_label(&profile->label); +} + +int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, + const struct path *old_path, + const struct path *new_path) +{ + struct aa_profile *profile; + struct aa_label *target = NULL; + char *old_buffer = NULL, *new_buffer = NULL, *info = NULL; + int error; + + AA_BUG(!label); + AA_BUG(!old_path); + AA_BUG(!new_path); + + old_buffer = aa_get_buffer(false); + new_buffer = aa_get_buffer(false); + error = -ENOMEM; + if (!old_buffer || !new_buffer) + goto out; + target = fn_label_build(label, profile, GFP_KERNEL, + build_pivotroot(subj_cred, profile, new_path, + new_buffer, + old_path, old_buffer)); + if (!target) { + info = "label build failed"; + error = -ENOMEM; + goto fail; + } else if (!IS_ERR(target)) { + error = aa_replace_current_label(target); + if (error) { + /* TODO: audit target */ + aa_put_label(target); + goto out; + } + aa_put_label(target); + } else + /* already audited error */ + error = PTR_ERR(target); +out: + aa_put_buffer(old_buffer); + aa_put_buffer(new_buffer); + + return error; + +fail: + /* TODO: add back in auditing of new_name and old_name */ + error = fn_for_each(label, profile, + audit_mount(subj_cred, profile, OP_PIVOTROOT, + NULL /*new_name */, + NULL /* 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 000000000000..45cf25605c34 --- /dev/null +++ b/security/apparmor/net.c @@ -0,0 +1,403 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor network mediation + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2017 Canonical Ltd. + */ + +#include "include/af_unix.h" +#include "include/apparmor.h" +#include "include/audit.h" +#include "include/cred.h" +#include "include/label.h" +#include "include/net.h" +#include "include/policy.h" +#include "include/secid.h" + +#include "net_names.h" + + +struct aa_sfs_entry aa_sfs_entry_network[] = { + AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), + { } +}; + +struct aa_sfs_entry aa_sfs_entry_networkv9[] = { + AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), + AA_SFS_FILE_BOOLEAN("af_unix", 1), + { } +}; + +static const char * const 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, + const struct sock *sk) +{ + const struct unix_sock *u = unix_sk(sk); + + if (u && u->addr) { + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); + + audit_unix_addr(ab, str, addr, addrlen); + } 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; + struct apparmor_audit_data *ad = aad(sa); + + if (address_family_names[ad->common.u.net->family]) + audit_log_format(ab, " family=\"%s\"", + address_family_names[ad->common.u.net->family]); + else + audit_log_format(ab, " family=\"unknown(%d)\"", + ad->common.u.net->family); + if (sock_type_names[ad->net.type]) + audit_log_format(ab, " sock_type=\"%s\"", + sock_type_names[ad->net.type]); + else + audit_log_format(ab, " sock_type=\"unknown(%d)\"", + ad->net.type); + audit_log_format(ab, " protocol=%d", ad->net.protocol); + + if (ad->request & NET_PERMS_MASK) { + audit_log_format(ab, " requested_mask="); + aa_audit_perm_mask(ab, ad->request, NULL, 0, + net_mask_names, NET_PERMS_MASK); + + if (ad->denied & NET_PERMS_MASK) { + audit_log_format(ab, " denied_mask="); + aa_audit_perm_mask(ab, ad->denied, NULL, 0, + net_mask_names, NET_PERMS_MASK); + } + } + if (ad->common.u.net->family == PF_UNIX) { + if (ad->net.addr || !ad->common.u.net->sk) + audit_unix_addr(ab, "addr", + unix_addr(ad->net.addr), + ad->net.addrlen); + else + audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk); + if (ad->request & NET_PEER_MASK) { + audit_unix_addr(ab, "peer_addr", + unix_addr(ad->net.peer.addr), + ad->net.peer.addrlen); + } + } + if (ad->peer) { + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, + FLAGS_NONE, GFP_ATOMIC); + } +} + +/* standard permission lookup pattern - supports early bailout */ +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, + struct aa_perms *p, struct apparmor_audit_data *ad) +{ + struct aa_perms perms; + + AA_BUG(!profile); + AA_BUG(!policy); + + + if (state || !p) + p = aa_lookup_perms(policy, state); + perms = *p; + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, ad, + audit_net_cb); +} + +/* only continue match if + * insufficient current perms at current state + * indicates there are more perms in later state + * Returns: perms struct if early match + */ +static struct aa_perms *early_match(struct aa_policydb *policy, + aa_state_t state, u32 request) +{ + struct aa_perms *p; + + p = aa_lookup_perms(policy, state); + if (((p->allow & request) != request) && (p->allow & AA_CONT_MATCH)) + return NULL; + return p; +} + +static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, + u16 data) +{ + __be16 buffer = cpu_to_be16(data); + + return aa_dfa_match_len(dfa, state, (char *) &buffer, 2); +} + +/** + * aa_match_to_prot - match the af, type, protocol triplet + * @policy: policy being matched + * @state: state to start in + * @request: permissions being requested, ignored if @p == NULL + * @af: socket address family + * @type: socket type + * @protocol: socket protocol + * @p: output - pointer to permission associated with match + * @info: output - pointer to string describing failure + * + * RETURNS: state match stopped in. + * + * If @(p) is assigned a value the returned state will be the + * corresponding state. Will not set @p on failure or if match completes + * only if an early match occurs + */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 af, int type, int protocol, + struct aa_perms **p, const char **info) +{ + state = aa_dfa_match_be16(policy->dfa, state, (u16)af); + if (!state) { + *info = "failed af match"; + return state; + } + state = aa_dfa_match_be16(policy->dfa, state, (u16)type); + if (state) { + if (p) + *p = early_match(policy, state, request); + if (!p || !*p) { + state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol); + if (!state) + *info = "failed protocol match"; + } + } else { + *info = "failed type match"; + } + + return state; +} + +/* Generic af perm */ +int aa_profile_af_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, u32 request, u16 family, + int type, int protocol) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(family >= AF_MAX); + AA_BUG(type < 0 || type >= SOCK_MAX); + AA_BUG(profile_unconfined(profile)); + + if (profile_unconfined(profile)) + return 0; + state = RULE_MEDIATES_NET(rules); + if (!state) + return 0; + state = aa_match_to_prot(rules->policy, state, request, family, type, + protocol, &p, &ad->info); + return aa_do_perms(profile, rules->policy, state, request, p, ad); +} + +int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, u16 family, int type, int protocol) +{ + struct aa_profile *profile; + DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol); + + return fn_for_each_confined(label, profile, + aa_profile_af_perm(profile, &ad, request, family, + type, protocol)); +} + +static int aa_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, + const char *op, u32 request, + struct sock *sk) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + int error = 0; + + AA_BUG(!label); + AA_BUG(!sk); + + if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + ad.subj_cred = subj_cred; + error = fn_for_each_confined(label, profile, + aa_profile_af_sk_perm(profile, &ad, request, sk)); + } + + return error; +} + +int aa_sk_perm(const char *op, u32 request, struct sock *sk) +{ + struct aa_label *label; + int error; + + AA_BUG(!sk); + AA_BUG(in_interrupt()); + + /* TODO: switch to begin_current_label ???? */ + label = begin_current_label_crit_section(); + error = aa_label_sk_perm(current_cred(), label, op, request, sk); + end_current_label_crit_section(label); + + return error; +} + + +int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file) +{ + struct socket *sock = (struct socket *) file->private_data; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_file_perm(subj_cred, label, op, request, file); + return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); +} + +#ifdef CONFIG_NETWORK_SECMARK +static int apparmor_secmark_init(struct aa_secmark *secmark) +{ + struct aa_label *label; + + if (secmark->label[0] == '*') { + secmark->secid = AA_SECID_WILDCARD; + return 0; + } + + label = aa_label_strn_parse(&root_ns->unconfined->label, + secmark->label, strlen(secmark->label), + GFP_ATOMIC, false, false); + + if (IS_ERR(label)) + return PTR_ERR(label); + + secmark->secid = label->secid; + + return 0; +} + +static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, + struct apparmor_audit_data *ad) +{ + int i, ret; + struct aa_perms perms = { }; + struct aa_ruleset *rules = profile->label.rules[0]; + + if (rules->secmark_count == 0) + return 0; + + for (i = 0; i < rules->secmark_count; i++) { + if (!rules->secmark[i].secid) { + ret = apparmor_secmark_init(&rules->secmark[i]); + if (ret) + return ret; + } + + if (rules->secmark[i].secid == secid || + rules->secmark[i].secid == AA_SECID_WILDCARD) { + if (rules->secmark[i].deny) + perms.deny = ALL_PERMS_MASK; + else + perms.allow = ALL_PERMS_MASK; + + if (rules->secmark[i].audit) + perms.audit = ALL_PERMS_MASK; + } + } + + aa_apply_modes_to_perms(profile, &perms); + + return aa_check_perms(profile, &perms, request, ad, audit_net_cb); +} + +int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, + u32 secid, const struct sock *sk) +{ + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, NULL, sk); + + return fn_for_each_confined(label, profile, + aa_secmark_perm(profile, request, secid, + &ad)); +} +#endif diff --git a/security/apparmor/nulldfa.in b/security/apparmor/nulldfa.in index 3cb38022902e..095f42a24cbc 100644 --- a/security/apparmor/nulldfa.in +++ b/security/apparmor/nulldfa.in @@ -1 +1,107 @@ -0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x04, 0x90, 0x00, 0x00, 0x6E, 0x6F, 0x74, 0x66, 0x6C, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x04, +0x90, 0x00, 0x00, 0x6E, 0x6F, 0x74, 0x66, 0x6C, 0x65, 0x78, 0x00, +0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00 diff --git a/security/apparmor/path.c b/security/apparmor/path.c index 9d5de1d05be4..d6c74c357ffd 100644 --- a/security/apparmor/path.c +++ b/security/apparmor/path.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,11 +6,6 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/magic.h> @@ -87,7 +83,7 @@ static int disconnect(const struct path *path, char *buf, char **name, * * Returns: %0 else error code if path lookup fails * When no error the path name is returned in @name which points to - * to a position in @buf + * a position in @buf */ static int d_namespace_path(const struct path *path, char *buf, char **name, int flags, const char *disconnected) @@ -134,7 +130,7 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, /* handle error conditions - and still allow a partial path to * be returned. */ - if (!res || IS_ERR(res)) { + if (IS_ERR_OR_NULL(res)) { if (PTR_ERR(res) == -ENAMETOOLONG) { error = -ENAMETOOLONG; *name = buf; @@ -146,7 +142,7 @@ static int d_namespace_path(const struct path *path, char *buf, char **name, error = PTR_ERR(res); *name = buf; goto out; - }; + } } else if (!our_mnt(path->mnt)) connected = 0; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 244ea4a4a8f0..50d5345ff5cb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -6,12 +7,6 @@ * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * * AppArmor policy is based around profiles, which contain the rules a * task is confined by. Every task in the system has a profile attached * to it determined either by matching "unconfined" tasks against the @@ -82,7 +77,7 @@ #include "include/apparmor.h" #include "include/capability.h" -#include "include/context.h" +#include "include/cred.h" #include "include/file.h" #include "include/ipc.h" #include "include/match.h" @@ -93,15 +88,52 @@ #include "include/resource.h" int unprivileged_userns_apparmor_policy = 1; +int aa_unprivileged_unconfined_restricted; const char *const aa_profile_mode_names[] = { "enforce", "complain", "kill", "unconfined", + "user", }; +static void aa_free_pdb(struct aa_policydb *pdb) +{ + if (pdb) { + aa_put_dfa(pdb->dfa); + kvfree(pdb->perms); + aa_free_str_table(&pdb->trans); + kfree(pdb); + } +} + +/** + * aa_pdb_free_kref - free aa_policydb by kref (called by aa_put_pdb) + * @kref: kref callback for freeing of a dfa (NOT NULL) + */ +void aa_pdb_free_kref(struct kref *kref) +{ + struct aa_policydb *pdb = container_of(kref, struct aa_policydb, count); + + aa_free_pdb(pdb); +} + + +struct aa_policydb *aa_alloc_pdb(gfp_t gfp) +{ + struct aa_policydb *pdb = kzalloc(sizeof(struct aa_policydb), gfp); + + if (!pdb) + return NULL; + + kref_init(&pdb->count); + + return pdb; +} + + /** * __add_profile - add a profiles to list and label tree * @list: list to add it to (NOT NULL) @@ -192,9 +224,46 @@ static void aa_free_data(void *ptr, void *arg) { struct aa_data *data = ptr; - kzfree(data->data); - kzfree(data->key); - kzfree(data); + kvfree_sensitive(data->data, data->size); + kfree_sensitive(data->key); + kfree_sensitive(data); +} + +static void free_attachment(struct aa_attachment *attach) +{ + int i; + + for (i = 0; i < attach->xattr_count; i++) + kfree_sensitive(attach->xattrs[i]); + kfree_sensitive(attach->xattrs); + aa_put_pdb(attach->xmatch); +} + +static void free_ruleset(struct aa_ruleset *rules) +{ + int i; + + if (!rules) + return; + + aa_put_pdb(rules->file); + aa_put_pdb(rules->policy); + aa_free_cap_rules(&rules->caps); + aa_free_rlimit_rules(&rules->rlimits); + + for (i = 0; i < rules->secmark_count; i++) + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); + kfree_sensitive(rules); +} + +struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) +{ + struct aa_ruleset *rules; + + rules = kzalloc(sizeof(*rules), gfp); + + return rules; } /** @@ -211,7 +280,7 @@ void aa_free_profile(struct aa_profile *profile) { struct rhashtable *rht; - AA_DEBUG("%s(%p)\n", __func__, profile); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile); if (!profile) return; @@ -221,32 +290,38 @@ void aa_free_profile(struct aa_profile *profile) aa_put_profile(rcu_access_pointer(profile->parent)); aa_put_ns(profile->ns); - kzfree(profile->rename); + kfree_sensitive(profile->rename); + kfree_sensitive(profile->disconnected); - aa_free_file_rules(&profile->file); - aa_free_cap_rules(&profile->caps); - aa_free_rlimit_rules(&profile->rlimits); + free_attachment(&profile->attach); + + /* + * at this point there are no tasks that can have a reference + * to rules + */ + for (int i = 0; i < profile->n_rules; i++) + free_ruleset(profile->label.rules[i]); - kzfree(profile->dirname); - aa_put_dfa(profile->xmatch); - aa_put_dfa(profile->policy.dfa); + kfree_sensitive(profile->dirname); if (profile->data) { rht = profile->data; profile->data = NULL; rhashtable_free_and_destroy(rht, aa_free_data, NULL); - kzfree(rht); + kfree_sensitive(rht); } - kzfree(profile->hash); + kfree_sensitive(profile->hash); aa_put_loaddata(profile->rawdata); + aa_label_destroy(&profile->label); - kzfree(profile); + kfree_sensitive(profile); } /** * aa_alloc_profile - allocate, initialize and return a new profile * @hname: name of the profile (NOT NULL) + * @proxy: proxy to use OR null if to allocate a new one * @gfp: allocation type * * Returns: refcount profile or NULL on failure @@ -256,16 +331,24 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, { struct aa_profile *profile; - /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(sizeof(*profile) + sizeof(struct aa_profile *) * 2, - gfp); + /* freed by free_profile - usually through aa_put_profile + * this adds space for a single ruleset in the rules section of the + * label + */ + profile = kzalloc(struct_size(profile, label.rules, 1), gfp); if (!profile) return NULL; if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; - if (!aa_label_init(&profile->label, 1)) + if (!aa_label_init(&profile->label, 1, gfp)) + goto fail; + + /* allocate the first ruleset, but leave it empty */ + profile->label.rules[0] = aa_alloc_ruleset(gfp); + if (!profile->label.rules[0]) goto fail; + profile->n_rules = 1; /* update being set needed by fs interface */ if (!proxy) { @@ -280,6 +363,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, profile->label.flags |= FLAG_PROFILE; profile->label.vec[0] = profile; + profile->signal = SIGKILL; /* refcount released by caller */ return profile; @@ -289,83 +373,39 @@ fail: return NULL; } -/** - * aa_new_null_profile - create or find a null-X learning profile - * @parent: profile that caused this profile to be created (NOT NULL) - * @hat: true if the null- learning profile is a hat - * @base: name to base the null profile off of - * @gfp: type of allocation - * - * Find/Create a null- complain mode profile used in learning mode. The - * name of the profile is unique and follows the format of parent//null-XXX. - * where XXX is based on the @name or if that fails or is not supplied - * a unique number - * - * null profiles are added to the profile list but the list does not - * hold a count on them so that they are automatically released when - * not in use. - * - * Returns: new refcounted profile else NULL on failure - */ -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat, - const char *base, gfp_t gfp) +static inline bool ANY_RULE_MEDIATES(struct aa_profile *profile, + unsigned char class) { - struct aa_profile *profile; - char *name; + int i; - AA_BUG(!parent); - - if (base) { - name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base), - gfp); - if (name) { - sprintf(name, "%s//null-%s", parent->base.hname, base); - goto name; - } - /* fall through to try shorter uniq */ + for (i = 0; i < profile->n_rules; i++) { + if (RULE_MEDIATES(profile->label.rules[i], class)) + return true; } + return false; +} - name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp); - if (!name) - return NULL; - sprintf(name, "%s//null-%x", parent->base.hname, - atomic_inc_return(&parent->ns->uniq_null)); - -name: - /* lookup to see if this is a dup creation */ - profile = aa_find_child(parent, basename(name)); - if (profile) - goto out; - - profile = aa_alloc_profile(name, NULL, gfp); - if (!profile) - goto fail; - - profile->mode = APPARMOR_COMPLAIN; - profile->label.flags |= FLAG_NULL; - if (hat) - profile->label.flags |= FLAG_HAT; - profile->path_flags = parent->path_flags; - - /* released on free_profile */ - rcu_assign_pointer(profile->parent, aa_get_profile(parent)); - profile->ns = aa_get_ns(parent->ns); - profile->file.dfa = aa_get_dfa(nulldfa); - profile->policy.dfa = aa_get_dfa(nulldfa); - - mutex_lock(&profile->ns->lock); - __add_profile(&parent->base.profiles, profile); - mutex_unlock(&profile->ns->lock); +/* set of rules that are mediated by unconfined */ +static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 }; - /* refcount released by caller */ -out: - kfree(name); +/* must be called after profile rulesets and start information is setup */ +void aa_compute_profile_mediates(struct aa_profile *profile) +{ + int c; - return profile; + if (profile_unconfined(profile)) { + int *pos; -fail: - aa_free_profile(profile); - return NULL; + for (pos = unconfined_mediates; *pos; pos++) { + if (ANY_RULE_MEDIATES(profile, *pos)) + profile->label.mediates |= ((u64) 1) << AA_CLASS_NS; + } + return; + } + for (c = 0; c <= AA_CLASS_LAST; c++) { + if (ANY_RULE_MEDIATES(profile, c)) + profile->label.mediates |= ((u64) 1) << c; + } } /* TODO: profile accounting - setup in remove */ @@ -458,6 +498,55 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns, } /** + * __create_missing_ancestors - create place holders for missing ancestors + * @ns: namespace to lookup profile in (NOT NULL) + * @hname: hierarchical profile name to find parent of (NOT NULL) + * @gfp: type of allocation. + * + * Requires: ns mutex lock held + * + * Return: unrefcounted parent policy on success or %NULL if error creating + * place holder profiles. + */ +static struct aa_policy *__create_missing_ancestors(struct aa_ns *ns, + const char *hname, + gfp_t gfp) +{ + struct aa_policy *policy; + struct aa_profile *parent, *profile = NULL; + char *split; + + AA_BUG(!ns); + AA_BUG(!hname); + + policy = &ns->base; + + for (split = strstr(hname, "//"); split;) { + parent = profile; + profile = __strn_find_child(&policy->profiles, hname, + split - hname); + if (!profile) { + const char *name = kstrndup(hname, split - hname, + gfp); + if (!name) + return NULL; + profile = aa_alloc_null(parent, name, gfp); + kfree(name); + if (!profile) + return NULL; + if (!parent) + profile->ns = aa_get_ns(ns); + } + policy = &profile->base; + hname = split + 2; + split = strstr(hname, "//"); + } + if (!profile) + return &ns->base; + return &profile->base; +} + +/** * __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) @@ -499,7 +588,7 @@ static struct aa_profile *__lookup_profile(struct aa_policy *base, } /** - * aa_lookup_profile - find a profile by its full or partial name + * aa_lookupn_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 @@ -525,11 +614,6 @@ struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, return profile; } -struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname) -{ - return aa_lookupn_profile(ns, hname, strlen(hname)); -} - struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, const char *fqname, size_t n) { @@ -558,6 +642,116 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, return profile; } + +struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, + gfp_t gfp) +{ + struct aa_profile *profile; + struct aa_ruleset *rules; + + profile = aa_alloc_profile(name, NULL, gfp); + if (!profile) + return NULL; + + /* TODO: ideally we should inherit abi from parent */ + profile->label.flags |= FLAG_NULL; + profile->attach.xmatch = aa_get_pdb(nullpdb); + rules = profile->label.rules[0]; + rules->file = aa_get_pdb(nullpdb); + rules->policy = aa_get_pdb(nullpdb); + aa_compute_profile_mediates(profile); + + if (parent) { + profile->path_flags = parent->path_flags; + /* override/inherit what is mediated from parent */ + profile->label.mediates = parent->label.mediates; + /* released on free_profile */ + rcu_assign_pointer(profile->parent, aa_get_profile(parent)); + profile->ns = aa_get_ns(parent->ns); + } + + return profile; +} + +/** + * aa_new_learning_profile - create or find a null-X learning profile + * @parent: profile that caused this profile to be created (NOT NULL) + * @hat: true if the null- learning profile is a hat + * @base: name to base the null profile off of + * @gfp: type of allocation + * + * Find/Create a null- complain mode profile used in learning mode. The + * name of the profile is unique and follows the format of parent//null-XXX. + * where XXX is based on the @name or if that fails or is not supplied + * a unique number + * + * null profiles are added to the profile list but the list does not + * hold a count on them so that they are automatically released when + * not in use. + * + * Returns: new refcounted profile else NULL on failure + */ +struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat, + const char *base, gfp_t gfp) +{ + struct aa_profile *p, *profile; + const char *bname; + char *name = NULL; + + AA_BUG(!parent); + + if (base) { + name = kmalloc(strlen(parent->base.hname) + 8 + strlen(base), + gfp); + if (name) { + sprintf(name, "%s//null-%s", parent->base.hname, base); + goto name; + } + /* fall through to try shorter uniq */ + } + + name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, gfp); + if (!name) + return NULL; + sprintf(name, "%s//null-%x", parent->base.hname, + atomic_inc_return(&parent->ns->uniq_null)); + +name: + /* lookup to see if this is a dup creation */ + bname = basename(name); + profile = aa_find_child(parent, bname); + if (profile) + goto out; + + profile = aa_alloc_null(parent, name, gfp); + if (!profile) + goto fail; + profile->mode = APPARMOR_COMPLAIN; + if (hat) + profile->label.flags |= FLAG_HAT; + + mutex_lock_nested(&profile->ns->lock, profile->ns->level); + p = __find_child(&parent->base.profiles, bname); + if (p) { + aa_free_profile(profile); + profile = aa_get_profile(p); + } else { + __add_profile(&parent->base.profiles, profile); + } + mutex_unlock(&profile->ns->lock); + + /* refcount released by caller */ +out: + kfree(name); + + return profile; + +fail: + kfree(name); + aa_free_profile(profile); + return NULL; +} + /** * replacement_allowed - test to see if replacement is allowed * @profile: profile to test if it can be replaced (MAYBE NULL) @@ -571,7 +765,7 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, { if (profile) { if (profile->label.flags & FLAG_IMMUTIBLE) { - *info = "cannot replace immutible profile"; + *info = "cannot replace immutable profile"; return -EPERM; } else if (noreplace) { *info = "profile already exists"; @@ -585,16 +779,17 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); - if (aad(sa)->iface.ns) { + if (ad->iface.ns) { audit_log_format(ab, " ns="); - audit_log_untrustedstring(ab, aad(sa)->iface.ns); + audit_log_untrustedstring(ab, ad->iface.ns); } } /** * audit_policy - Do auditing of policy changes - * @label: label to check if it can manage policy + * @subj_label: label to check if it can manage policy * @op: policy operation being performed * @ns_name: name of namespace being manipulated * @name: name of profile being manipulated (NOT NULL) @@ -603,35 +798,56 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: the error to be returned after audit is done */ -static int audit_policy(struct aa_label *label, const char *op, +static int audit_policy(struct aa_label *subj_label, const char *op, const char *ns_name, const char *name, const char *info, int error) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, op); - aad(&sa)->iface.ns = ns_name; - aad(&sa)->name = name; - aad(&sa)->info = info; - aad(&sa)->error = error; - aad(&sa)->label = label; + ad.iface.ns = ns_name; + ad.name = name; + ad.info = info; + ad.error = error; + ad.subj_label = subj_label; - aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb); + aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, audit_cb); return error; } +/* don't call out to other LSMs in the stack for apparmor policy admin + * permissions + */ +static int policy_ns_capable(const struct cred *subj_cred, + struct aa_label *label, + struct user_namespace *userns, int cap) +{ + int err; + + /* check for MAC_ADMIN cap in cred */ + err = cap_capable(subj_cred, userns, cap, CAP_OPT_NONE); + if (!err) + err = aa_capable(subj_cred, label, cap, CAP_OPT_NONE); + + return err; +} + /** - * policy_view_capable - check if viewing policy in at @ns is allowed - * ns: namespace being viewed by current task (may be NULL) + * aa_policy_view_capable - check if viewing policy in at @ns is allowed + * @subj_cred: cred of subject + * @label: label that is trying to view policy in ns + * @ns: namespace being viewed by @label (may be NULL if @label's ns) + * * Returns: true if viewing policy is allowed * * If @ns is NULL then the namespace being viewed is assumed to be the * tasks current namespace. */ -bool policy_view_capable(struct aa_ns *ns) +bool aa_policy_view_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns) { - struct user_namespace *user_ns = current_user_ns(); - struct aa_ns *view_ns = aa_get_current_ns(); + struct user_namespace *user_ns = subj_cred->user_ns; + struct aa_ns *view_ns = labels_view(label); bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) || in_egroup_p(make_kgid(user_ns, 0)); bool response = false; @@ -643,30 +859,59 @@ bool policy_view_capable(struct aa_ns *ns) (unprivileged_userns_apparmor_policy != 0 && user_ns->level == view_ns->level))) response = true; - aa_put_ns(view_ns); return response; } -bool policy_admin_capable(struct aa_ns *ns) +bool aa_policy_admin_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns) { - struct user_namespace *user_ns = current_user_ns(); - bool capable = ns_capable(user_ns, CAP_MAC_ADMIN); + struct user_namespace *user_ns = subj_cred->user_ns; + bool capable = policy_ns_capable(subj_cred, label, user_ns, + CAP_MAC_ADMIN) == 0; - AA_DEBUG("cap_mac_admin? %d\n", capable); - AA_DEBUG("policy locked? %d\n", aa_g_lock_policy); + AA_DEBUG(DEBUG_POLICY, "cap_mac_admin? %d\n", capable); + AA_DEBUG(DEBUG_POLICY, "policy locked? %d\n", aa_g_lock_policy); - return policy_view_capable(ns) && capable && !aa_g_lock_policy; + return aa_policy_view_capable(subj_cred, label, ns) && capable && + !aa_g_lock_policy; +} + +bool aa_current_policy_view_capable(struct aa_ns *ns) +{ + struct aa_label *label; + bool needput, res; + + label = __begin_current_label_crit_section(&needput); + res = aa_policy_view_capable(current_cred(), label, ns); + __end_current_label_crit_section(label, needput); + + return res; +} + +bool aa_current_policy_admin_capable(struct aa_ns *ns) +{ + struct aa_label *label; + bool needput, res; + + label = __begin_current_label_crit_section(&needput); + res = aa_policy_admin_capable(current_cred(), label, ns); + __end_current_label_crit_section(label, needput); + + return res; } /** * aa_may_manage_policy - can the current task manage policy + * @subj_cred: subjects cred * @label: label to check if it can manage policy - * @op: the policy manipulation operation being done + * @ns: namespace being managed by @label (may be NULL if @label's ns) + * @mask: contains the policy manipulation operation being done * * Returns: 0 if the task is allowed to manipulate policy else error */ -int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask) +int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label, + struct aa_ns *ns, u32 mask) { const char *op; @@ -682,7 +927,7 @@ int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask) return audit_policy(label, op, NULL, NULL, "policy_locked", -EACCES); - if (!policy_admin_capable(ns)) + if (!aa_policy_admin_capable(subj_cred, label, ns)) return audit_policy(label, op, NULL, NULL, "not policy admin", -EACCES); @@ -717,7 +962,6 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh, * __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_proxy: transfer @old->proxy to @new * * Will duplicate and refcount elements that @new inherits from @old * and will inherit @old children. @@ -774,11 +1018,11 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new) /** * __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 + * @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 */ @@ -836,15 +1080,16 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) * @udata: serialized data stream (NOT NULL) * * unpack and replace a profile on the profile list and uses of that profile - * by any aa_task_ctx. If the profile does not exist on the profile list - * it is added. + * by any task creds via invalidating the old version of the profile, which + * tasks will notice to update their own cred. If the profile does not exist + * on the profile list it is added. * * Returns: size of data consumed else error code on failure. */ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, u32 mask, struct aa_loaddata *udata) { - const char *ns_name, *info = NULL; + const char *ns_name = NULL, *info = NULL; struct aa_ns *ns = NULL; struct aa_load_ent *ent, *tmp; struct aa_loaddata *rawdata_ent; @@ -860,7 +1105,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, goto out; /* ensure that profiles are all for the same ns - * TODO: update locking to remove this constaint. All profiles in + * TODO: update locking to remove this constraint. All profiles in * the load set must succeed as a set or the load will * fail. Sort ent list and take ns locks in hierarchy order */ @@ -897,26 +1142,30 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, } else ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label)); - mutex_lock(&ns->lock); + mutex_lock_nested(&ns->lock, ns->level); /* check for duplicate rawdata blobs: space and file dedup */ - list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) { - if (aa_rawdata_eq(rawdata_ent, udata)) { - struct aa_loaddata *tmp; - - tmp = __aa_get_loaddata(rawdata_ent); - /* check we didn't fail the race */ - if (tmp) { - aa_put_loaddata(udata); - udata = tmp; - break; + if (!list_empty(&ns->rawdata_list)) { + list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) { + if (aa_rawdata_eq(rawdata_ent, udata)) { + struct aa_loaddata *tmp; + + tmp = __aa_get_loaddata(rawdata_ent); + /* check we didn't fail the race */ + if (tmp) { + aa_put_loaddata(udata); + udata = tmp; + break; + } } } } /* setup parent and ns info */ list_for_each_entry(ent, &lh, list) { struct aa_policy *policy; + struct aa_profile *p; - ent->new->rawdata = aa_get_loaddata(udata); + if (aa_g_export_binary) + ent->new->rawdata = aa_get_loaddata(udata); error = __lookup_replace(ns, ent->new->base.hname, !(mask & AA_MAY_REPLACE_POLICY), &ent->old, &info); @@ -938,25 +1187,42 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, continue; /* no ref on policy only use inside lock */ + p = NULL; policy = __lookup_parent(ns, ent->new->base.hname); if (!policy) { - struct aa_profile *p; + /* first check for parent in the load set */ p = __list_lookup_parent(&lh, ent->new); if (!p) { - error = -ENOENT; - info = "parent does not exist"; - goto fail_lock; + /* + * fill in missing parent with null + * profile that doesn't have + * permissions. This allows for + * individual profile loading where + * the child is loaded before the + * parent, and outside of the current + * atomic set. This unfortunately can + * happen with some userspaces. The + * null profile will be replaced once + * the parent is loaded. + */ + policy = __create_missing_ancestors(ns, + ent->new->base.hname, + GFP_KERNEL); + if (!policy) { + error = -ENOENT; + info = "parent does not exist"; + 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)); } + if (!p && policy != &ns->base) + /* released on profile replacement or free_profile */ + p = (struct aa_profile *) policy; + rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); } /* create new fs entries for introspection if needed */ - if (!udata->dents[AAFS_LOADDATA_DIR]) { + if (!udata->dents[AAFS_LOADDATA_DIR] && aa_g_export_binary) { error = __aa_fs_create_rawdata(ns, udata); if (error) { info = "failed to create raw_data dir and files"; @@ -984,16 +1250,21 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, /* Done with checks that may fail - do actual replacement */ __aa_bump_ns_revision(ns); - __aa_loaddata_update(udata, ns->revision); + if (aa_g_export_binary) + __aa_loaddata_update(udata, ns->revision); 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; - if (ent->old && ent->old->rawdata == ent->new->rawdata) { + if (ent->old && ent->old->rawdata == ent->new->rawdata && + ent->new->rawdata) { /* dedup actual profile replacement */ audit_policy(label, op, ns_name, ent->new->base.hname, "same as current profile, skipping", error); + /* break refcount cycle with proxy. */ + aa_put_proxy(ent->new->label.proxy); + ent->new->label.proxy = NULL; goto skip; } @@ -1028,6 +1299,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, out: aa_put_ns(ns); aa_put_loaddata(udata); + kfree(ns_name); if (error) return error; @@ -1071,7 +1343,7 @@ fail: * Remove a profile or sub namespace from the current namespace, so that * they can not be found anymore and mark them as replaced by unconfined * - * NOTE: removing confinement does not restore rlimits to preconfinemnet values + * NOTE: removing confinement does not restore rlimits to preconfinement values * * Returns: size of data consume else error code if fails */ @@ -1108,13 +1380,13 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ - mutex_lock(&ns->parent->lock); - __aa_remove_ns(ns); + mutex_lock_nested(&ns->parent->lock, ns->parent->level); __aa_bump_ns_revision(ns); + __aa_remove_ns(ns); mutex_unlock(&ns->parent->lock); } else { /* remove profile */ - mutex_lock(&ns->lock); + mutex_lock_nested(&ns->lock, ns->level); profile = aa_get_profile(__lookup_profile(&ns->base, name)); if (!profile) { error = -ENOENT; @@ -1122,9 +1394,9 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, goto fail_ns_lock; } name = profile->base.hname; + __aa_bump_ns_revision(ns); __remove_profile(profile); __aa_labelset_update_subtree(ns); - __aa_bump_ns_revision(ns); mutex_unlock(&ns->lock); } diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c new file mode 100644 index 000000000000..cfc2207e5a12 --- /dev/null +++ b/security/apparmor/policy_compat.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor functions for unpacking policy loaded + * from userspace. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2022 Canonical Ltd. + * + * Code to provide backwards compatibility with older policy versions, + * by converting/mapping older policy formats into the newer internal + * formats. + */ + +#include <linux/ctype.h> +#include <linux/errno.h> + +#include "include/lib.h" +#include "include/policy_unpack.h" +#include "include/policy_compat.h" + +/* remap old accept table embedded permissions to separate permission table */ +static u32 dfa_map_xindex(u16 mask) +{ + u16 old_index = (mask >> 10) & 0xf; + u32 index = 0; + + if (mask & 0x100) + index |= AA_X_UNSAFE; + if (mask & 0x200) + index |= AA_X_INHERIT; + if (mask & 0x80) + index |= AA_X_UNCONFINED; + + if (old_index == 1) { + index |= AA_X_UNCONFINED; + } else if (old_index == 2) { + index |= AA_X_NAME; + } else if (old_index == 3) { + index |= AA_X_NAME | AA_X_CHILD; + } else if (old_index) { + index |= AA_X_TABLE; + index |= old_index - 4; + } + + return index; +} + +/* + * map old dfa inline permissions to new format + */ +#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f) +#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f) +#define dfa_user_xindex(dfa, state) \ + (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff)) + +#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \ + 0x7f) | \ + ((ACCEPT_TABLE(dfa)[state]) & 0x80000000)) +#define dfa_other_xbits(dfa, state) \ + ((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f) +#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f) +#define dfa_other_quiet(dfa, state) \ + ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f) +#define dfa_other_xindex(dfa, state) \ + dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff) + +/** + * map_old_perms - map old file perms layout to the new layout + * @old: permission set in old mapping + * + * Returns: new permission mapping + */ +static u32 map_old_perms(u32 old) +{ + u32 new = old & 0xf; + + if (old & MAY_READ) + new |= AA_MAY_GETATTR | AA_MAY_OPEN; + if (old & MAY_WRITE) + new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE | + AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN; + if (old & 0x10) + new |= AA_MAY_LINK; + /* the old mapping lock and link_subset flags where overlaid + * and use was determined by part of a pair that they were in + */ + if (old & 0x20) + new |= AA_MAY_LOCK | AA_LINK_SUBSET; + if (old & 0x40) /* AA_EXEC_MMAP */ + new |= AA_EXEC_MMAP; + + return new; +} + +static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa, + aa_state_t state) +{ + perms->allow |= AA_MAY_GETATTR; + + /* change_profile wasn't determined by ownership in old mapping */ + if (ACCEPT_TABLE(dfa)[state] & 0x80000000) + perms->allow |= AA_MAY_CHANGE_PROFILE; + if (ACCEPT_TABLE(dfa)[state] & 0x40000000) + perms->allow |= AA_MAY_ONEXEC; +} + +static struct aa_perms compute_fperms_user(struct aa_dfa *dfa, + aa_state_t state) +{ + struct aa_perms perms = { }; + + perms.allow = map_old_perms(dfa_user_allow(dfa, state)); + perms.audit = map_old_perms(dfa_user_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); + perms.xindex = dfa_user_xindex(dfa, state); + + compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +static struct aa_perms compute_fperms_other(struct aa_dfa *dfa, + aa_state_t state) +{ + struct aa_perms perms = { }; + + perms.allow = map_old_perms(dfa_other_allow(dfa, state)); + perms.audit = map_old_perms(dfa_other_audit(dfa, state)); + perms.quiet = map_old_perms(dfa_other_quiet(dfa, state)); + perms.xindex = dfa_other_xindex(dfa, state); + + compute_fperms_allow(&perms, dfa, state); + + return perms; +} + +/** + * compute_fperms - convert dfa compressed perms to internal perms and store + * them so they can be retrieved later. + * @dfa: a dfa using fperms to remap to internal permissions + * @size: Returns the permission table size + * + * Returns: remapped perm table + */ +static struct aa_perms *compute_fperms(struct aa_dfa *dfa, + u32 *size) +{ + aa_state_t state; + unsigned int state_count; + struct aa_perms *table; + + AA_BUG(!dfa); + + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL); + if (!table) + return NULL; + *size = state_count * 2; + + for (state = 0; state < state_count; state++) { + table[state * 2] = compute_fperms_user(dfa, state); + table[state * 2 + 1] = compute_fperms_other(dfa, state); + } + + return table; +} + +static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch, + u32 *size) +{ + struct aa_perms *perms; + int state; + int state_count; + + AA_BUG(!xmatch); + + state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + if (!perms) + return NULL; + *size = state_count; + + /* zero init so skip the trap state (state == 0) */ + for (state = 1; state < state_count; state++) + perms[state].allow = dfa_user_allow(xmatch, state); + + return perms; +} + +static u32 map_other(u32 x) +{ + return ((x & 0x3) << 8) | /* SETATTR/GETATTR */ + ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */ + ((x & 0x60) << 19); /* SETOPT/GETOPT */ +} + +static u32 map_xbits(u32 x) +{ + return ((x & 0x1) << 7) | + ((x & 0x7e) << 9); +} + +static struct aa_perms compute_perms_entry(struct aa_dfa *dfa, + aa_state_t state, + u32 version) +{ + struct aa_perms perms = { }; + + perms.allow = dfa_user_allow(dfa, state); + perms.audit = dfa_user_audit(dfa, state); + perms.quiet = dfa_user_quiet(dfa, state); + + /* + * This mapping is convulated due to history. + * v1-v4: only file perms, which are handled by compute_fperms + * v5: added policydb which dropped user conditional to gain new + * perm bits, but had to map around the xbits because the + * userspace compiler was still munging them. + * v9: adds using the xbits in policydb because the compiler now + * supports treating policydb permission bits different. + * Unfortunately there is no way to force auditing on the + * perms represented by the xbits + */ + perms.allow |= map_other(dfa_other_allow(dfa, state)); + if (VERSION_LE(version, v8)) + perms.allow |= AA_MAY_LOCK; + else + perms.allow |= map_xbits(dfa_user_xbits(dfa, state)); + + /* + * for v5-v9 perm mapping in the policydb, the other set is used + * to extend the general perm set + */ + perms.audit |= map_other(dfa_other_audit(dfa, state)); + perms.quiet |= map_other(dfa_other_quiet(dfa, state)); + if (VERSION_GT(version, v8)) + perms.quiet |= map_xbits(dfa_other_xbits(dfa, state)); + + return perms; +} + +static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version, + u32 *size) +{ + unsigned int state; + unsigned int state_count; + struct aa_perms *table; + + AA_BUG(!dfa); + + state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + /* DFAs are restricted from having a state_count of less than 2 */ + table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL); + if (!table) + return NULL; + *size = state_count; + + /* zero init so skip the trap state (state == 0) */ + for (state = 1; state < state_count; state++) + table[state] = compute_perms_entry(dfa, state, version); + + return table; +} + +/** + * remap_dfa_accept - remap old dfa accept table to be an index + * @dfa: dfa to do the remapping on + * @factor: scaling factor for the index conversion. + * + * Used in conjunction with compute_Xperms, it converts old style perms + * that are encoded in the dfa accept tables to the new style where + * there is a permission table and the accept table is an index into + * the permission table. + */ +static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor) +{ + unsigned int state; + unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen; + + AA_BUG(!dfa); + + for (state = 0; state < state_count; state++) { + ACCEPT_TABLE(dfa)[state] = state * factor; + ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0; + } +} + +/* TODO: merge different dfa mappings into single map_policy fn */ +int aa_compat_map_xmatch(struct aa_policydb *policy) +{ + policy->perms = compute_xmatch_perms(policy->dfa, &policy->size); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 1); + + return 0; +} + +int aa_compat_map_policy(struct aa_policydb *policy, u32 version) +{ + policy->perms = compute_perms(policy->dfa, version, &policy->size); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 1); + + return 0; +} + +int aa_compat_map_file(struct aa_policydb *policy) +{ + policy->perms = compute_fperms(policy->dfa, &policy->size); + if (!policy->perms) + return -ENOMEM; + + remap_dfa_accept(policy->dfa, 2); + + return 0; +} diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 351d3bab3a3d..64783ca3b0f2 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -6,11 +7,6 @@ * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2017 Canonical Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * * AppArmor policy namespaces, allow for different sets of policies * to be loaded for tasks within the namespace. */ @@ -21,11 +17,14 @@ #include <linux/string.h> #include "include/apparmor.h" -#include "include/context.h" +#include "include/cred.h" #include "include/policy_ns.h" #include "include/label.h" #include "include/policy.h" +/* kernel label */ +struct aa_label *kernel_t; + /* root profile namespace */ struct aa_ns *root_ns; const char *aa_hidden_ns_name = "---"; @@ -55,10 +54,10 @@ bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns) } /** - * aa_na_name - Find the ns name to display for @view from @curr - * @curr - current namespace (NOT NULL) - * @view - namespace attempting to view (NOT NULL) - * @subns - are subns visible + * aa_ns_name - Find the ns name to display for @view from @curr + * @curr: current namespace (NOT NULL) + * @view: namespace attempting to view (NOT NULL) + * @subns: are subns visible * * Returns: name of @view visible from @curr */ @@ -81,6 +80,21 @@ const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) return aa_hidden_ns_name; } +static struct aa_profile *alloc_unconfined(const char *name) +{ + struct aa_profile *profile; + + profile = aa_alloc_null(NULL, name, GFP_KERNEL); + if (!profile) + return NULL; + + profile->label.flags |= FLAG_IX_ON_NAME_ERROR | + FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; + profile->mode = APPARMOR_UNCONFINED; + + return profile; +} + /** * alloc_ns - allocate, initialize and return a new namespace * @prefix: parent namespace name (MAYBE NULL) @@ -93,7 +107,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) struct aa_ns *ns; ns = kzalloc(sizeof(*ns), GFP_KERNEL); - AA_DEBUG("%s(%p)\n", __func__, ns); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns); if (!ns) return NULL; if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) @@ -105,14 +119,9 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) init_waitqueue_head(&ns->wait); /* released by aa_free_ns() */ - ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL); + ns->unconfined = alloc_unconfined("unconfined"); if (!ns->unconfined) goto fail_unconfined; - - ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR | - FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED; - ns->unconfined->mode = APPARMOR_UNCONFINED; - /* ns and ns->unconfined share ns->unconfined refcount */ ns->unconfined->ns = ns; @@ -123,9 +132,9 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) return ns; fail_unconfined: - kzfree(ns->base.hname); + aa_policy_destroy(&ns->base); fail_ns: - kzfree(ns); + kfree_sensitive(ns); return NULL; } @@ -147,49 +156,12 @@ void aa_free_ns(struct aa_ns *ns) ns->unconfined->ns = NULL; aa_free_profile(ns->unconfined); - kzfree(ns); -} - -/** - * aa_findn_ns - 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: a refcounted namespace on the list, or NULL if no namespace - * called @name exists. - * - * refcount released by caller - */ -struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n) -{ - struct aa_ns *ns = NULL; - - rcu_read_lock(); - ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n)); - rcu_read_unlock(); - - return ns; -} - -/** - * aa_find_ns - look up a profile namespace on the namespace list - * @root: namespace to search in (NOT NULL) - * @name: name of namespace to find (NOT NULL) - * - * Returns: a refcounted namespace on the list, or NULL if no namespace - * called @name exists. - * - * refcount released by caller - */ -struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) -{ - return aa_findn_ns(root, name, strlen(name)); + kfree_sensitive(ns); } /** * __aa_lookupn_ns - lookup the namespace matching @hname - * @base: base list to start looking up profile name from (NOT NULL) + * @view: namespace to search in (NOT NULL) * @hname: hierarchical ns name (NOT NULL) * @n: length of @hname * @@ -253,8 +225,9 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, ns = alloc_ns(parent->base.hname, name); if (!ns) - return NULL; - mutex_lock(&ns->lock); + return ERR_PTR(-ENOMEM); + ns->level = parent->level + 1; + mutex_lock_nested(&ns->lock, ns->level); error = __aafs_ns_mkdir(ns, ns_subns_dir(parent), name, dir); if (error) { AA_ERROR("Failed to create interface for ns %s\n", @@ -264,7 +237,6 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, return ERR_PTR(error); } ns->parent = aa_get_ns(parent); - ns->level = parent->level + 1; list_add_rcu(&ns->base.list, &parent->sub_ns); /* add list ref */ aa_get_ns(ns); @@ -274,7 +246,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, } /** - * aa_create_ns - create an ns, fail if it already exists + * __aa_find_or_create_ns - create an ns, fail if it already exists * @parent: the parent of the namespace being created * @name: the name of the namespace * @dir: if not null the dir to put the ns entries in @@ -311,7 +283,7 @@ struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name) { struct aa_ns *ns; - mutex_lock(&parent->lock); + mutex_lock_nested(&parent->lock, parent->level); /* try and find the specified ns and if it doesn't exist create it */ /* released by caller */ ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); @@ -334,7 +306,7 @@ static void destroy_ns(struct aa_ns *ns) if (!ns) return; - mutex_lock(&ns->lock); + mutex_lock_nested(&ns->lock, ns->level); /* release all profiles in this namespace */ __aa_profile_list_release(&ns->base.profiles); @@ -390,11 +362,22 @@ static void __ns_list_release(struct list_head *head) */ int __init aa_alloc_root_ns(void) { + struct aa_profile *kernel_p; + /* released by aa_free_root_ns - used as list ref*/ root_ns = alloc_ns(NULL, "root"); if (!root_ns) return -ENOMEM; + kernel_p = alloc_unconfined("kernel_t"); + if (!kernel_p) { + destroy_ns(root_ns); + aa_free_ns(root_ns); + return -ENOMEM; + } + kernel_t = &kernel_p->label; + root_ns->unconfined->ns = aa_get_ns(root_ns); + return 0; } @@ -407,6 +390,7 @@ void __init aa_free_root_ns(void) root_ns = NULL; + aa_label_free(kernel_t); destroy_ns(ns); aa_put_ns(ns); } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index c600f4dd1783..7523971e37d9 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -7,90 +8,45 @@ * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * * AppArmor uses a serialized binary format for loading policy. To find * policy format documentation see Documentation/admin-guide/LSM/apparmor.rst * All policy is validated before it is used. */ -#include <asm/unaligned.h> +#include <linux/unaligned.h> +#include <kunit/visibility.h> #include <linux/ctype.h> #include <linux/errno.h> +#include <linux/zstd.h> #include "include/apparmor.h" #include "include/audit.h" -#include "include/context.h" +#include "include/cred.h" #include "include/crypto.h" +#include "include/file.h" #include "include/match.h" #include "include/path.h" #include "include/policy.h" #include "include/policy_unpack.h" - -#define K_ABI_MASK 0x3ff -#define FORCE_COMPLAIN_FLAG 0x800 -#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK)) -#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((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 - * actual data. The interface has the notion of a a named entry - * which has a name (AA_NAME typecode followed by name string) followed by - * the entries typecode and data. Named types allow for optional - * elements and extensions to be added and tested for without breaking - * backwards compatibility. - */ - -enum aa_code { - AA_U8, - AA_U16, - AA_U32, - AA_U64, - AA_NAME, /* same as string except it is items name */ - AA_STRING, - AA_BLOB, - AA_STRUCT, - AA_STRUCTEND, - AA_LIST, - AA_LISTEND, - AA_ARRAY, - AA_ARRAYEND, -}; - -/* - * aa_ext is the read of the buffer containing the serialized profile. The - * data is copied into a kernel buffer in apparmorfs and then handed off to - * the unpack routines. - */ -struct aa_ext { - void *start; - void *end; - void *pos; /* pointer to current position in the buffer */ - u32 version; -}; +#include "include/policy_compat.h" +#include "include/signal.h" /* audit callback for unpack fields */ static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); - if (aad(sa)->iface.ns) { + if (ad->iface.ns) { audit_log_format(ab, " ns="); - audit_log_untrustedstring(ab, aad(sa)->iface.ns); + audit_log_untrustedstring(ab, ad->iface.ns); } - if (aad(sa)->iface.name) { + if (ad->name) { audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, aad(sa)->iface.name); + audit_log_untrustedstring(ab, ad->name); } - if (aad(sa)->iface.pos) - audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos); + if (ad->iface.pos) + audit_log_format(ab, " offset=%ld", ad->iface.pos); } /** @@ -109,42 +65,48 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, int error) { struct aa_profile *profile = labels_profile(aa_current_raw_label()); - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); if (e) - aad(&sa)->iface.pos = e->pos - e->start; - aad(&sa)->iface.ns = ns_name; + ad.iface.pos = e->pos - e->start; + ad.iface.ns = ns_name; if (new) - aad(&sa)->iface.name = new->base.hname; + ad.name = new->base.hname; else - aad(&sa)->iface.name = name; - aad(&sa)->info = info; - aad(&sa)->error = error; + ad.name = name; + ad.info = info; + ad.error = error; - return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); + return aa_audit(AUDIT_APPARMOR_STATUS, profile, &ad, audit_cb); } void __aa_loaddata_update(struct aa_loaddata *data, long revision) { AA_BUG(!data); AA_BUG(!data->ns); - AA_BUG(!data->dents[AAFS_LOADDATA_REVISION]); AA_BUG(!mutex_is_locked(&data->ns->lock)); AA_BUG(data->revision > revision); data->revision = revision; - d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime = - current_time(d_inode(data->dents[AAFS_LOADDATA_DIR])); - d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime = - current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION])); + if ((data->dents[AAFS_LOADDATA_REVISION])) { + struct inode *inode; + + inode = d_inode(data->dents[AAFS_LOADDATA_DIR]); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + + inode = d_inode(data->dents[AAFS_LOADDATA_REVISION]); + inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); + } } bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r) { if (l->size != r->size) return false; + if (l->compressed_size != r->compressed_size) + return false; if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0) return false; - return memcmp(l->data, r->data, r->size) == 0; + return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0; } /* @@ -157,15 +119,16 @@ static void do_loaddata_free(struct work_struct *work) struct aa_ns *ns = aa_get_ns(d->ns); if (ns) { - mutex_lock(&ns->lock); + mutex_lock_nested(&ns->lock, ns->level); __aa_fs_remove_rawdata(d); mutex_unlock(&ns->lock); aa_put_ns(ns); } - kzfree(d->hash); - kfree(d->name); - kvfree(d); + kfree_sensitive(d->hash); + kfree_sensitive(d->name); + kvfree(d->data); + kfree_sensitive(d); } void aa_loaddata_kref(struct kref *kref) @@ -180,10 +143,16 @@ void aa_loaddata_kref(struct kref *kref) struct aa_loaddata *aa_loaddata_alloc(size_t size) { - struct aa_loaddata *d = kvzalloc(sizeof(*d) + size, GFP_KERNEL); + struct aa_loaddata *d; + d = kzalloc(sizeof(*d), GFP_KERNEL); if (d == NULL) return ERR_PTR(-ENOMEM); + d->data = kvzalloc(size, GFP_KERNEL); + if (!d->data) { + kfree(d); + return ERR_PTR(-ENOMEM); + } kref_init(&d->count); INIT_LIST_HEAD(&d->list); @@ -191,46 +160,54 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size) } /* test if read will be in packed data bounds */ -static bool inbounds(struct aa_ext *e, size_t size) +VISIBLE_IF_KUNIT bool aa_inbounds(struct aa_ext *e, size_t size) { return (size <= e->end - e->pos); } +EXPORT_SYMBOL_IF_KUNIT(aa_inbounds); /** - * aa_u16_chunck - test and do bounds checking for a u16 size based chunk + * aa_unpack_u16_chunk - test and do bounds checking for a u16 size based chunk * @e: serialized data read head (NOT NULL) * @chunk: start address for chunk of data (NOT NULL) * * Returns: the size of chunk found with the read head at the end of the chunk. */ -static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk) +VISIBLE_IF_KUNIT size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk) { size_t size = 0; + void *pos = e->pos; - if (!inbounds(e, sizeof(u16))) - return 0; + if (!aa_inbounds(e, sizeof(u16))) + goto fail; size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); e->pos += sizeof(__le16); - if (!inbounds(e, size)) - return 0; + if (!aa_inbounds(e, size)) + goto fail; *chunk = e->pos; e->pos += size; return size; + +fail: + e->pos = pos; + return 0; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u16_chunk); /* unpack control byte */ -static bool unpack_X(struct aa_ext *e, enum aa_code code) +VISIBLE_IF_KUNIT bool aa_unpack_X(struct aa_ext *e, enum aa_code code) { - if (!inbounds(e, 1)) - return 0; + if (!aa_inbounds(e, 1)) + return false; if (*(u8 *) e->pos != code) - return 0; + return false; e->pos++; - return 1; + return true; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_X); /** - * unpack_nameX - check is the next element is of type X with a name of @name + * aa_unpack_nameX - check is the next element is of type X with a name of @name * @e: serialized data extent information (NOT NULL) * @code: type code * @name: name to match to the serialized element. (MAYBE NULL) @@ -240,12 +217,12 @@ static bool unpack_X(struct aa_ext *e, enum aa_code code) * name element in the stream. If @name is NULL any name element will be * skipped and only the typecode will be tested. * - * Returns 1 on success (both type code and name tests match) and the read + * Returns true on success (both type code and name tests match) and the read * head is advanced past the headers * - * Returns: 0 if either match fails, the read head does not move + * Returns: false if either match fails, the read head does not move */ -static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) +VISIBLE_IF_KUNIT bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) { /* * May need to reset pos if name or type doesn't match @@ -255,11 +232,11 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) * Check for presence of a tagname, and if present name size * AA_NAME tag value is a u16. */ - if (unpack_X(e, AA_NAME)) { + if (aa_unpack_X(e, AA_NAME)) { char *tag = NULL; - size_t size = unpack_u16_chunk(e, &tag); + size_t size = aa_unpack_u16_chunk(e, &tag); /* if a name is specified it must match. otherwise skip tag */ - if (name && (!size || strcmp(name, tag))) + if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag))) goto fail; } else if (name) { /* if a name is specified and there is no name tag fail */ @@ -267,97 +244,161 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) } /* now check if type code matches */ - if (unpack_X(e, code)) - return 1; + if (aa_unpack_X(e, code)) + return true; fail: e->pos = pos; - return 0; + return false; +} +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_nameX); + +static bool unpack_u8(struct aa_ext *e, u8 *data, const char *name) +{ + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_U8, name)) { + if (!aa_inbounds(e, sizeof(u8))) + goto fail; + if (data) + *data = *((u8 *)e->pos); + e->pos += sizeof(u8); + return true; + } + +fail: + e->pos = pos; + return false; } -static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name) +VISIBLE_IF_KUNIT bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name) { - if (unpack_nameX(e, AA_U32, name)) { - if (!inbounds(e, sizeof(u32))) - return 0; + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_U32, name)) { + if (!aa_inbounds(e, sizeof(u32))) + goto fail; if (data) *data = le32_to_cpu(get_unaligned((__le32 *) e->pos)); e->pos += sizeof(u32); - return 1; + return true; } - return 0; + +fail: + e->pos = pos; + return false; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u32); -static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name) +VISIBLE_IF_KUNIT bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name) { - if (unpack_nameX(e, AA_U64, name)) { - if (!inbounds(e, sizeof(u64))) - return 0; + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_U64, name)) { + if (!aa_inbounds(e, sizeof(u64))) + goto fail; if (data) *data = le64_to_cpu(get_unaligned((__le64 *) e->pos)); e->pos += sizeof(u64); - return 1; + return true; } - return 0; + +fail: + e->pos = pos; + return false; +} +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64); + +static bool aa_unpack_cap_low(struct aa_ext *e, kernel_cap_t *data, const char *name) +{ + u32 val; + + if (!aa_unpack_u32(e, &val, name)) + return false; + data->val = val; + return true; } -static size_t unpack_array(struct aa_ext *e, const char *name) +static bool aa_unpack_cap_high(struct aa_ext *e, kernel_cap_t *data, const char *name) { - if (unpack_nameX(e, AA_ARRAY, name)) { - int size; - if (!inbounds(e, sizeof(u16))) - return 0; - size = (int)le16_to_cpu(get_unaligned((__le16 *) e->pos)); + u32 val; + + if (!aa_unpack_u32(e, &val, name)) + return false; + data->val = (u32)data->val | ((u64)val << 32); + return true; +} + +VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size) +{ + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_ARRAY, name)) { + if (!aa_inbounds(e, sizeof(u16))) + goto fail; + *size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); e->pos += sizeof(u16); - return size; + return true; } - return 0; + +fail: + e->pos = pos; + return false; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array); -static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name) +VISIBLE_IF_KUNIT size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name) { - if (unpack_nameX(e, AA_BLOB, name)) { + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_BLOB, name)) { u32 size; - if (!inbounds(e, sizeof(u32))) - return 0; + if (!aa_inbounds(e, sizeof(u32))) + goto fail; size = le32_to_cpu(get_unaligned((__le32 *) e->pos)); e->pos += sizeof(u32); - if (inbounds(e, (size_t) size)) { + if (aa_inbounds(e, (size_t) size)) { *blob = e->pos; e->pos += size; return size; } } + +fail: + e->pos = pos; return 0; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_blob); -static int unpack_str(struct aa_ext *e, const char **string, const char *name) +VISIBLE_IF_KUNIT int aa_unpack_str(struct aa_ext *e, const char **string, const char *name) { char *src_str; size_t size = 0; void *pos = e->pos; *string = NULL; - if (unpack_nameX(e, AA_STRING, name)) { - size = unpack_u16_chunk(e, &src_str); + if (aa_unpack_nameX(e, AA_STRING, name)) { + size = aa_unpack_u16_chunk(e, &src_str); if (size) { /* strings are null terminated, length is size - 1 */ if (src_str[size - 1] != 0) goto fail; *string = src_str; + + return size; } } - return size; fail: e->pos = pos; return 0; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_str); -static int unpack_strdup(struct aa_ext *e, char **string, const char *name) +VISIBLE_IF_KUNIT int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name) { const char *tmp; void *pos = e->pos; - int res = unpack_str(e, &tmp, name); + int res = aa_unpack_str(e, &tmp, name); *string = NULL; if (!res) @@ -371,47 +412,23 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name) return res; } +EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup); -#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) - * @flags: flags governing dfa - * - * Returns: 1 if valid accept tables else 0 if error - */ -static bool verify_accept(struct aa_dfa *dfa, int flags) -{ - int i; - - /* verify accept permissions */ - for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { - int mode = ACCEPT_TABLE(dfa)[i]; - - if (mode & ~DFA_VALID_PERM_MASK) - return 0; - - if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK) - return 0; - } - return 1; -} /** * unpack_dfa - unpack a file rule dfa * @e: serialized data extent information (NOT NULL) + * @flags: dfa flags to check * * returns dfa or ERR_PTR or NULL if no dfa */ -static struct aa_dfa *unpack_dfa(struct aa_ext *e) +static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) { char *blob = NULL; size_t size; struct aa_dfa *dfa = NULL; - size = unpack_blob(e, &blob, "aadfa"); + size = aa_unpack_blob(e, &blob, "aadfa"); if (size) { /* * The dfa is aligned with in the blob to 8 bytes @@ -421,58 +438,58 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e) 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) | DFA_FLAG_VERIFY_STATES; + if (aa_g_paranoid_load) + flags |= DFA_FLAG_VERIFY_STATES; dfa = aa_dfa_unpack(blob + pad, size - pad, flags); if (IS_ERR(dfa)) return dfa; - if (!verify_accept(dfa, flags)) - goto fail; } return dfa; - -fail: - aa_put_dfa(dfa); - return ERR_PTR(-EPROTO); } /** * unpack_trans_table - unpack a profile transition table * @e: serialized data extent information (NOT NULL) - * @profile: profile to add the accept table to (NOT NULL) + * @strs: str table to unpack to (NOT NULL) * - * Returns: 1 if table successfully unpacked + * Returns: true if table successfully unpacked or not present */ -static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) +static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) { - void *pos = e->pos; + void *saved_pos = e->pos; + char **table = NULL; /* exec table is optional */ - if (unpack_nameX(e, AA_STRUCT, "xtable")) { - int i, size; - - size = unpack_array(e, NULL); - /* currently 4 exec bits and entries 0-3 are reserved iupcx */ - if (size > 16 - 4) + if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) { + u16 size; + int i; + + if (!aa_unpack_array(e, NULL, &size)) + /* + * Note: index into trans table array is a max + * of 2^24, but unpack array can only unpack + * an array of 2^16 in size atm so no need + * for size check here + */ goto fail; - profile->file.trans.table = kzalloc(sizeof(char *) * size, - GFP_KERNEL); - if (!profile->file.trans.table) + table = kcalloc(size, sizeof(char *), GFP_KERNEL); + if (!table) goto fail; - profile->file.trans.size = size; + strs->table = table; + strs->size = size; for (i = 0; i < size; i++) { char *str; - int c, j, pos, size2 = unpack_strdup(e, &str, NULL); - /* unpack_strdup verifies that the last character is + int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL); + /* aa_unpack_strdup verifies that the last character is * null termination byte. */ if (!size2) goto fail; - profile->file.trans.table[i] = str; + table[i] = str; /* verify that name doesn't start with space */ if (isspace(*str)) goto fail; @@ -490,7 +507,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) goto fail; /* beginning with : requires an embedded \0, * verify that exactly 1 internal \0 exists - * trailing \0 already verified by unpack_strdup + * trailing \0 already verified by aa_unpack_strdup * * convert \0 back to : for label_parse */ @@ -502,60 +519,304 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile) /* fail - all other cases with embedded \0 */ goto fail; } - if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + return true; + +fail: + aa_free_str_table(strs); + e->pos = saved_pos; + return false; +} + +static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) +{ + void *pos = e->pos; + + if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) { + u16 size; + int i; + + if (!aa_unpack_array(e, NULL, &size)) + goto fail; + profile->attach.xattr_count = size; + profile->attach.xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); + if (!profile->attach.xattrs) + goto fail; + for (i = 0; i < size; i++) { + if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL)) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - return 1; + + return true; fail: - aa_free_domain_entries(&profile->file.trans); e->pos = pos; - return 0; + return false; } -static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) +static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) +{ + void *pos = e->pos; + u16 size; + int i; + + if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) { + if (!aa_unpack_array(e, NULL, &size)) + goto fail; + + rules->secmark = kcalloc(size, sizeof(struct aa_secmark), + GFP_KERNEL); + if (!rules->secmark) + goto fail; + + rules->secmark_count = size; + + for (i = 0; i < size; i++) { + if (!unpack_u8(e, &rules->secmark[i].audit, NULL)) + goto fail; + if (!unpack_u8(e, &rules->secmark[i].deny, NULL)) + goto fail; + if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL)) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } + + return true; + +fail: + if (rules->secmark) { + for (i = 0; i < size; i++) + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); + rules->secmark_count = 0; + rules->secmark = NULL; + } + + e->pos = pos; + return false; +} + +static bool unpack_rlimits(struct aa_ext *e, struct aa_ruleset *rules) { void *pos = e->pos; /* rlimits are optional */ - if (unpack_nameX(e, AA_STRUCT, "rlimits")) { - int i, size; + if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) { + u16 size; + int i; u32 tmp = 0; - if (!unpack_u32(e, &tmp, NULL)) + if (!aa_unpack_u32(e, &tmp, NULL)) goto fail; - profile->rlimits.mask = tmp; + rules->rlimits.mask = tmp; - size = unpack_array(e, NULL); - if (size > RLIM_NLIMITS) + if (!aa_unpack_array(e, NULL, &size) || + size > RLIM_NLIMITS) goto fail; for (i = 0; i < size; i++) { u64 tmp2 = 0; int a = aa_map_resource(i); - if (!unpack_u64(e, &tmp2, NULL)) + if (!aa_unpack_u64(e, &tmp2, NULL)) goto fail; - profile->rlimits.limits[a].rlim_max = tmp2; + rules->rlimits.limits[a].rlim_max = tmp2; } - if (!unpack_nameX(e, AA_ARRAYEND, NULL)) + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - return 1; + return true; fail: e->pos = pos; - return 0; + return false; +} + +static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) +{ + u32 reserved; + + if (version != 1) + return false; + + /* reserved entry is for later expansion, discard for now */ + return aa_unpack_u32(e, &reserved, NULL) && + aa_unpack_u32(e, &perm->allow, NULL) && + aa_unpack_u32(e, &perm->deny, NULL) && + aa_unpack_u32(e, &perm->subtree, NULL) && + aa_unpack_u32(e, &perm->cond, NULL) && + aa_unpack_u32(e, &perm->kill, NULL) && + aa_unpack_u32(e, &perm->complain, NULL) && + aa_unpack_u32(e, &perm->prompt, NULL) && + aa_unpack_u32(e, &perm->audit, NULL) && + aa_unpack_u32(e, &perm->quiet, NULL) && + aa_unpack_u32(e, &perm->hide, NULL) && + aa_unpack_u32(e, &perm->xindex, NULL) && + aa_unpack_u32(e, &perm->tag, NULL) && + aa_unpack_u32(e, &perm->label, NULL); +} + +static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) +{ + void *pos = e->pos; + u16 size = 0; + + AA_BUG(!perms); + /* + * policy perms are optional, in which case perms are embedded + * in the dfa accept table + */ + if (aa_unpack_nameX(e, AA_STRUCT, "perms")) { + int i; + u32 version; + + if (!aa_unpack_u32(e, &version, "version")) + goto fail_reset; + if (!aa_unpack_array(e, NULL, &size)) + goto fail_reset; + *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); + if (!*perms) + goto fail_reset; + for (i = 0; i < size; i++) { + if (!unpack_perm(e, version, &(*perms)[i])) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + } else + *perms = NULL; + + return size; + +fail: + kfree(*perms); +fail_reset: + e->pos = pos; + return -EPROTO; } -static void *kvmemdup(const void *src, size_t len) +static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, + bool required_dfa, bool required_trans, + const char **info) { - void *p = kvmalloc(len, GFP_KERNEL); + struct aa_policydb *pdb; + void *pos = e->pos; + int i, flags, error = -EPROTO; + ssize_t size; + u32 version = 0; + + pdb = aa_alloc_pdb(GFP_KERNEL); + if (!pdb) + return -ENOMEM; + + size = unpack_perms_table(e, &pdb->perms); + if (size < 0) { + error = size; + pdb->perms = NULL; + *info = "failed to unpack - perms"; + goto fail; + } + pdb->size = size; + + if (pdb->perms) { + /* perms table present accept is index */ + flags = TO_ACCEPT1_FLAG(YYTD_DATA32); + if (aa_unpack_u32(e, &version, "permsv") && version > 2) + /* accept2 used for dfa flags */ + flags |= TO_ACCEPT2_FLAG(YYTD_DATA32); + } else { + /* packed perms in accept1 and accept2 */ + flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | + TO_ACCEPT2_FLAG(YYTD_DATA32); + } + + pdb->dfa = unpack_dfa(e, flags); + if (IS_ERR(pdb->dfa)) { + error = PTR_ERR(pdb->dfa); + pdb->dfa = NULL; + *info = "failed to unpack - dfa"; + goto fail; + } else if (!pdb->dfa) { + if (required_dfa) { + *info = "missing required dfa"; + goto fail; + } + } else { + /* + * only unpack the following if a dfa is present + * + * sadly start was given different names for file and policydb + * but since it is optional we can try both + */ + if (!aa_unpack_u32(e, &pdb->start[0], "start")) + /* default start state */ + pdb->start[0] = DFA_START; + if (!aa_unpack_u32(e, &pdb->start[AA_CLASS_FILE], "dfa_start")) { + /* default start state for xmatch and file dfa */ + pdb->start[AA_CLASS_FILE] = DFA_START; + } /* setup class index */ + for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) { + pdb->start[i] = aa_dfa_next(pdb->dfa, pdb->start[0], + i); + } + } + + /* accept2 is in some cases being allocated, even with perms */ + if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + /* add dfa flags table missing in v2 */ + u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen; + u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags; + size_t tsize = table_size(noents, tdflags); + + pdb->dfa->tables[YYTD_ID_ACCEPT2] = kvzalloc(tsize, GFP_KERNEL); + if (!pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + *info = "failed to alloc dfa flags table"; + goto out; + } + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents; + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags; + } + /* + * Unfortunately due to a bug in earlier userspaces, a + * transition table may be present even when the dfa is + * not. For compatibility reasons unpack and discard. + */ + if (!unpack_trans_table(e, &pdb->trans) && required_trans) { + *info = "failed to unpack profile transition table"; + goto fail; + } + + if (!pdb->dfa && pdb->trans.table) + aa_free_str_table(&pdb->trans); + + /* TODO: + * - move compat mapping here, requires dfa merging first + * - move verify here, it has to be done after compat mappings + * - move free of unneeded trans table here, has to be done + * after perm mapping. + */ +out: + *policy = pdb; + return 0; - if (p) - memcpy(p, src, len); - return p; +fail: + aa_put_pdb(pdb); + e->pos = pos; + return error; } static u32 strhash(const void *data, u32 len, u32 seed) @@ -576,184 +837,249 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) /** * unpack_profile - unpack a serialized profile * @e: serialized data extent information (NOT NULL) + * @ns_name: pointer of newly allocated copy of %NULL in case of error * * NOTE: unpack profile sets audit struct if there is a failure */ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) { + struct aa_ruleset *rules; struct aa_profile *profile = NULL; const char *tmpname, *tmpns = NULL, *name = NULL; + const char *info = "failed to unpack profile"; size_t ns_len; struct rhashtable_params params = { 0 }; - char *key = NULL; + char *key = NULL, *disconnected = NULL; struct aa_data *data; - int i, error = -EPROTO; + int error = -EPROTO; kernel_cap_t tmpcap; u32 tmp; *ns_name = NULL; /* check that we have the right struct being passed */ - if (!unpack_nameX(e, AA_STRUCT, "profile")) + if (!aa_unpack_nameX(e, AA_STRUCT, "profile")) goto fail; - if (!unpack_str(e, &name, NULL)) + if (!aa_unpack_str(e, &name, NULL)) goto fail; if (*name == '\0') goto fail; tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len); if (tmpns) { + if (!tmpname) { + info = "empty profile name"; + goto fail; + } *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL); - if (!*ns_name) + if (!*ns_name) { + info = "out of memory"; + error = -ENOMEM; goto fail; + } name = tmpname; } profile = aa_alloc_profile(name, NULL, GFP_KERNEL); - if (!profile) - return ERR_PTR(-ENOMEM); + if (!profile) { + info = "out of memory"; + error = -ENOMEM; + goto fail; + } + rules = profile->label.rules[0]; /* profile renaming is optional */ - (void) unpack_str(e, &profile->rename, "rename"); + (void) aa_unpack_str(e, &profile->rename, "rename"); /* attachment string is optional */ - (void) unpack_str(e, &profile->attach, "attach"); + (void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach"); /* xmatch is optional and may be NULL */ - profile->xmatch = unpack_dfa(e); - if (IS_ERR(profile->xmatch)) { - error = PTR_ERR(profile->xmatch); - profile->xmatch = NULL; + error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info); + if (error) { + info = "bad xmatch"; goto fail; } - /* xmatch_len is not optional if xmatch is set */ - if (profile->xmatch) { - if (!unpack_u32(e, &tmp, NULL)) + + /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ + if (profile->attach.xmatch->dfa) { + if (!aa_unpack_u32(e, &tmp, NULL)) { + info = "missing xmatch len"; goto fail; - profile->xmatch_len = tmp; + } + profile->attach.xmatch_len = tmp; + profile->attach.xmatch->start[AA_CLASS_XMATCH] = DFA_START; + if (!profile->attach.xmatch->perms) { + error = aa_compat_map_xmatch(profile->attach.xmatch); + if (error) { + info = "failed to convert xmatch permission table"; + goto fail; + } + } } /* disconnected attachment string is optional */ - (void) unpack_str(e, &profile->disconnected, "disconnected"); + (void) aa_unpack_strdup(e, &disconnected, "disconnected"); + profile->disconnected = disconnected; + /* optional */ + (void) aa_unpack_u32(e, &profile->signal, "kill"); + if (profile->signal < 1 || profile->signal > MAXMAPPED_SIG) { + info = "profile kill.signal invalid value"; + goto fail; + } /* per profile debug flags (complain, audit) */ - if (!unpack_nameX(e, AA_STRUCT, "flags")) + if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) { + info = "profile missing flags"; goto fail; - if (!unpack_u32(e, &tmp, NULL)) + } + info = "failed to unpack profile flags"; + if (!aa_unpack_u32(e, &tmp, NULL)) goto fail; if (tmp & PACKED_FLAG_HAT) profile->label.flags |= FLAG_HAT; - if (!unpack_u32(e, &tmp, NULL)) + if (tmp & PACKED_FLAG_DEBUG1) + profile->label.flags |= FLAG_DEBUG1; + if (tmp & PACKED_FLAG_DEBUG2) + profile->label.flags |= FLAG_DEBUG2; + if (!aa_unpack_u32(e, &tmp, NULL)) goto fail; - if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) + if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) { profile->mode = APPARMOR_COMPLAIN; - else if (tmp == PACKED_MODE_KILL) + } else if (tmp == PACKED_MODE_ENFORCE) { + profile->mode = APPARMOR_ENFORCE; + } else if (tmp == PACKED_MODE_KILL) { profile->mode = APPARMOR_KILL; - else if (tmp == PACKED_MODE_UNCONFINED) + } else if (tmp == PACKED_MODE_UNCONFINED) { profile->mode = APPARMOR_UNCONFINED; - if (!unpack_u32(e, &tmp, NULL)) + profile->label.flags |= FLAG_UNCONFINED; + } else if (tmp == PACKED_MODE_USER) { + profile->mode = APPARMOR_USER; + } else { + goto fail; + } + if (!aa_unpack_u32(e, &tmp, NULL)) goto fail; if (tmp) profile->audit = AUDIT_ALL; - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; /* path_flags is optional */ - if (unpack_u32(e, &profile->path_flags, "path_flags")) + if (aa_unpack_u32(e, &profile->path_flags, "path_flags")) profile->path_flags |= profile->label.flags & PATH_MEDIATE_DELETED; else /* set a default value if path_flags field is not present */ profile->path_flags = PATH_MEDIATE_DELETED; - if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL)) + info = "failed to unpack profile capabilities"; + if (!aa_unpack_cap_low(e, &rules->caps.allow, NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL)) + if (!aa_unpack_cap_low(e, &rules->caps.audit, NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL)) + if (!aa_unpack_cap_low(e, &rules->caps.quiet, NULL)) goto fail; - if (!unpack_u32(e, &tmpcap.cap[0], NULL)) + if (!aa_unpack_cap_low(e, &tmpcap, NULL)) goto fail; - if (unpack_nameX(e, AA_STRUCT, "caps64")) { + info = "failed to unpack upper profile capabilities"; + if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) { /* optional upper half of 64 bit caps */ - if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL)) + if (!aa_unpack_cap_high(e, &rules->caps.allow, NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL)) + if (!aa_unpack_cap_high(e, &rules->caps.audit, NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL)) + if (!aa_unpack_cap_high(e, &rules->caps.quiet, NULL)) goto fail; - if (!unpack_u32(e, &(tmpcap.cap[1]), NULL)) + if (!aa_unpack_cap_high(e, &tmpcap, NULL)) goto fail; - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - if (unpack_nameX(e, AA_STRUCT, "capsx")) { + info = "failed to unpack extended profile capabilities"; + if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) { /* optional extended caps mediation mask */ - if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL)) + if (!aa_unpack_cap_low(e, &rules->caps.extended, NULL)) goto fail; - if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL)) + if (!aa_unpack_cap_high(e, &rules->caps.extended, NULL)) goto fail; - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - if (!unpack_rlimits(e, profile)) + if (!unpack_xattrs(e, profile)) { + info = "failed to unpack profile xattrs"; + goto fail; + } + + if (!unpack_rlimits(e, rules)) { + info = "failed to unpack profile rlimits"; goto fail; + } - if (unpack_nameX(e, AA_STRUCT, "policydb")) { + if (!unpack_secmark(e, rules)) { + info = "failed to unpack profile secmark rules"; + goto fail; + } + + if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) { /* generic policy dfa - optional and may be NULL */ - profile->policy.dfa = unpack_dfa(e); - if (IS_ERR(profile->policy.dfa)) { - error = PTR_ERR(profile->policy.dfa); - profile->policy.dfa = NULL; + info = "failed to unpack policydb"; + error = unpack_pdb(e, &rules->policy, true, false, + &info); + if (error) goto fail; - } else if (!profile->policy.dfa) { - error = -EPROTO; + /* Fixup: drop when we get rid of start array */ + if (aa_dfa_next(rules->policy->dfa, rules->policy->start[0], + AA_CLASS_FILE)) + rules->policy->start[AA_CLASS_FILE] = + aa_dfa_next(rules->policy->dfa, + rules->policy->start[0], + AA_CLASS_FILE); + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; + if (!rules->policy->perms) { + error = aa_compat_map_policy(rules->policy, + e->version); + if (error) { + info = "failed to remap policydb permission table"; + goto fail; + } } - if (!unpack_u32(e, &profile->policy.start[0], "start")) - /* default start state */ - profile->policy.start[0] = DFA_START; - /* setup class index */ - for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) { - profile->policy.start[i] = - aa_dfa_next(profile->policy.dfa, - profile->policy.start[0], - i); - } - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) - goto fail; - } else - profile->policy.dfa = aa_get_dfa(nulldfa); - + } else { + rules->policy = aa_get_pdb(nullpdb); + } /* get file rules */ - profile->file.dfa = unpack_dfa(e); - if (IS_ERR(profile->file.dfa)) { - 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]; - } else - profile->file.dfa = aa_get_dfa(nulldfa); - - if (!unpack_trans_table(e, profile)) + error = unpack_pdb(e, &rules->file, false, true, &info); + if (error) { goto fail; - - if (unpack_nameX(e, AA_STRUCT, "data")) { + } else if (rules->file->dfa) { + if (!rules->file->perms) { + error = aa_compat_map_file(rules->file); + if (error) { + info = "failed to remap file permission table"; + goto fail; + } + } + } else if (rules->policy->dfa && + rules->policy->start[AA_CLASS_FILE]) { + aa_put_pdb(rules->file); + rules->file = aa_get_pdb(rules->policy); + } else { + aa_put_pdb(rules->file); + rules->file = aa_get_pdb(nullpdb); + } + error = -EPROTO; + if (aa_unpack_nameX(e, AA_STRUCT, "data")) { + info = "out of memory"; profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); - if (!profile->data) + if (!profile->data) { + error = -ENOMEM; goto fail; - + } params.nelem_hint = 3; params.key_len = sizeof(void *); params.key_offset = offsetof(struct aa_data, key); @@ -761,52 +1087,74 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) params.hashfn = strhash; params.obj_cmpfn = datacmp; - if (rhashtable_init(profile->data, ¶ms)) + if (rhashtable_init(profile->data, ¶ms)) { + info = "failed to init key, value hash table"; goto fail; + } - while (unpack_strdup(e, &key, NULL)) { + while (aa_unpack_strdup(e, &key, NULL)) { data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) { - kzfree(key); + kfree_sensitive(key); + error = -ENOMEM; goto fail; } data->key = key; - data->size = unpack_blob(e, &data->data, NULL); - data->data = kvmemdup(data->data, data->size); + data->size = aa_unpack_blob(e, &data->data, NULL); + data->data = kvmemdup(data->data, data->size, GFP_KERNEL); if (data->size && !data->data) { - kzfree(data->key); - kzfree(data); + kfree_sensitive(data->key); + kfree_sensitive(data); + error = -ENOMEM; goto fail; } - rhashtable_insert_fast(profile->data, &data->head, - profile->data->p); + if (rhashtable_insert_fast(profile->data, &data->head, + profile->data->p)) { + kvfree_sensitive(data->data, data->size); + kfree_sensitive(data->key); + kfree_sensitive(data); + info = "failed to insert data to table"; + goto fail; + } } - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) { + info = "failed to unpack end of key, value data table"; goto fail; + } } - if (!unpack_nameX(e, AA_STRUCTEND, NULL)) + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) { + info = "failed to unpack end of profile"; goto fail; + } + + aa_compute_profile_mediates(profile); return profile; fail: + if (error == 0) + /* default error covers most cases */ + error = -EPROTO; + if (*ns_name) { + kfree(*ns_name); + *ns_name = NULL; + } if (profile) name = NULL; else if (!name) name = "unknown"; - audit_iface(profile, NULL, name, "failed to unpack profile", e, - error); + audit_iface(profile, NULL, name, info, e, error); aa_free_profile(profile); return ERR_PTR(error); } /** - * verify_head - unpack serialized stream header + * verify_header - 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) @@ -820,7 +1168,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) *ns = NULL; /* get the interface version */ - if (!unpack_u32(e, &e->version, "version")) { + if (!aa_unpack_u32(e, &e->version, "version")) { if (required) { audit_iface(NULL, NULL, NULL, "invalid profile format", e, error); @@ -832,50 +1180,100 @@ static int verify_header(struct aa_ext *e, int required, const char **ns) * if not specified use previous version * Mask off everything that is not kernel abi version */ - if (VERSION_LT(e->version, v5) && VERSION_GT(e->version, v7)) { + if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) { audit_iface(NULL, NULL, NULL, "unsupported interface version", e, error); return error; } /* read the namespace if present */ - if (unpack_str(e, &name, "namespace")) { + if (aa_unpack_str(e, &name, "namespace")) { if (*name == '\0') { audit_iface(NULL, NULL, NULL, "invalid namespace name", e, error); return error; } - if (*ns && strcmp(*ns, name)) + if (*ns && strcmp(*ns, name)) { audit_iface(NULL, NULL, NULL, "invalid ns change", e, error); - else if (!*ns) - *ns = name; + } else if (!*ns) { + *ns = kstrdup(name, GFP_KERNEL); + if (!*ns) + return -ENOMEM; + } } return 0; } -static bool verify_xindex(int xindex, int table_size) +/** + * verify_dfa_accept_index - verify accept indexes are in range of perms table + * @dfa: the dfa to check accept indexes are in range + * @table_size: the permission table size the indexes should be within + */ +static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size) +{ + int i; + for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { + if (ACCEPT_TABLE(dfa)[i] >= table_size) + return false; + } + return true; +} + +static bool verify_perm(struct aa_perms *perm) { - int index, xtype; - xtype = xindex & AA_X_TYPE_MASK; - index = xindex & AA_X_INDEX_MASK; - if (xtype == AA_X_TABLE && index >= table_size) - return 0; - return 1; + /* TODO: allow option to just force the perms into a valid state */ + if (perm->allow & perm->deny) + return false; + if (perm->subtree & ~perm->allow) + return false; + if (perm->cond & (perm->allow | perm->deny)) + return false; + if (perm->kill & perm->allow) + return false; + if (perm->complain & (perm->allow | perm->deny)) + return false; + if (perm->prompt & (perm->allow | perm->deny)) + return false; + if (perm->complain & perm->prompt) + return false; + if (perm->hide & perm->allow) + return false; + + return true; } -/* verify dfa xindexes are in range of transition tables */ -static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) +static bool verify_perms(struct aa_policydb *pdb) { int i; - for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { - if (!verify_xindex(dfa_user_xindex(dfa, i), table_size)) - return 0; - if (!verify_xindex(dfa_other_xindex(dfa, i), table_size)) - return 0; + int xidx, xmax = -1; + + for (i = 0; i < pdb->size; i++) { + if (!verify_perm(&pdb->perms[i])) + return false; + /* verify indexes into str table */ + if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + xidx = pdb->perms[i].xindex & AA_X_INDEX_MASK; + if (xidx >= pdb->trans.size) + return false; + if (xmax < xidx) + xmax = xidx; + } + if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->trans.size) + return false; + if (pdb->perms[i].label && + pdb->perms[i].label >= pdb->trans.size) + return false; } - return 1; + /* deal with incorrectly constructed string tables */ + if (xmax == -1) { + aa_free_str_table(&pdb->trans); + } else if (pdb->trans.size > xmax + 1) { + if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL)) + return false; + } + return true; } /** @@ -883,14 +1281,44 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) * @profile: profile to verify (NOT NULL) * * Returns: 0 if passes verification else error + * + * This verification is post any unpack mapping or changes */ static int verify_profile(struct aa_profile *profile) { - if (profile->file.dfa && - !verify_dfa_xindex(profile->file.dfa, - profile->file.trans.size)) { - audit_iface(profile, NULL, NULL, "Invalid named transition", - NULL, -EPROTO); + struct aa_ruleset *rules = profile->label.rules[0]; + + if (!rules) + return 0; + + if (rules->file->dfa && !verify_dfa_accept_index(rules->file->dfa, + rules->file->size)) { + audit_iface(profile, NULL, NULL, + "Unpack: file Invalid named transition", NULL, + -EPROTO); + return -EPROTO; + } + if (rules->policy->dfa && + !verify_dfa_accept_index(rules->policy->dfa, rules->policy->size)) { + audit_iface(profile, NULL, NULL, + "Unpack: policy Invalid named transition", NULL, + -EPROTO); + return -EPROTO; + } + + if (!verify_perms(rules->file)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perms(rules->policy)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); + return -EPROTO; + } + if (!verify_perms(profile->attach.xmatch)) { + audit_iface(profile, NULL, NULL, + "Unpack: Invalid perm index", NULL, -EPROTO); return -EPROTO; } @@ -904,7 +1332,7 @@ void aa_load_ent_free(struct aa_load_ent *ent) aa_put_profile(ent->old); aa_put_profile(ent->new); kfree(ent->ns_name); - kzfree(ent); + kfree_sensitive(ent); } } @@ -916,6 +1344,103 @@ struct aa_load_ent *aa_load_ent_alloc(void) return ent; } +static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen) +{ +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY + const zstd_parameters params = + zstd_get_params(aa_g_rawdata_compression_level, slen); + const size_t wksp_len = zstd_cctx_workspace_bound(¶ms.cParams); + void *wksp = NULL; + zstd_cctx *ctx = NULL; + size_t out_len = zstd_compress_bound(slen); + void *out = NULL; + int ret = 0; + + out = kvzalloc(out_len, GFP_KERNEL); + if (!out) { + ret = -ENOMEM; + goto cleanup; + } + + wksp = kvzalloc(wksp_len, GFP_KERNEL); + if (!wksp) { + ret = -ENOMEM; + goto cleanup; + } + + ctx = zstd_init_cctx(wksp, wksp_len); + if (!ctx) { + ret = -EINVAL; + goto cleanup; + } + + out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); + if (zstd_is_error(out_len) || out_len >= slen) { + ret = -EINVAL; + goto cleanup; + } + + if (is_vmalloc_addr(out)) { + *dst = kvzalloc(out_len, GFP_KERNEL); + if (*dst) { + memcpy(*dst, out, out_len); + kvfree(out); + out = NULL; + } + } else { + /* + * If the staging buffer was kmalloc'd, then using krealloc is + * probably going to be faster. The destination buffer will + * always be smaller, so it's just shrunk, avoiding a memcpy + */ + *dst = krealloc(out, out_len, GFP_KERNEL); + } + + if (!*dst) { + ret = -ENOMEM; + goto cleanup; + } + + *dlen = out_len; + +cleanup: + if (ret) { + kvfree(out); + *dst = NULL; + } + + kvfree(wksp); + return ret; +#else + *dlen = slen; + return 0; +#endif +} + +static int compress_loaddata(struct aa_loaddata *data) +{ + AA_BUG(data->compressed_size > 0); + + /* + * Shortcut the no compression case, else we increase the amount of + * storage required by a small amount + */ + if (aa_g_rawdata_compression_level != 0) { + void *udata = data->data; + int error = compress_zstd(udata, data->size, &data->data, + &data->compressed_size); + if (error) { + data->compressed_size = data->size; + return error; + } + if (udata != data->data) + kvfree(udata); + } else + data->compressed_size = data->size; + + return 0; +} + /** * aa_unpack - unpack packed binary profile(s) data loaded from user space * @udata: user data copied to kmem (NOT NULL) @@ -933,6 +1458,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, { struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; + char *ns_name = NULL; int error; struct aa_ext e = { .start = udata->data, @@ -942,7 +1468,6 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, *ns = NULL; while (e.pos < e.end) { - char *ns_name = NULL; void *start; error = verify_header(&e, e.pos == e.start, ns); if (error) @@ -973,6 +1498,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, ent->new = profile; ent->ns_name = ns_name; + ns_name = NULL; list_add_tail(&ent->list, lh); } udata->abi = e.version & K_ABI_MASK; @@ -984,9 +1510,16 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, goto fail; } } + + if (aa_g_export_binary) { + error = compress_loaddata(udata); + if (error) + goto fail; + } return 0; fail_profile: + kfree(ns_name); aa_put_profile(profile); fail: diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c new file mode 100644 index 000000000000..cf18744dafe2 --- /dev/null +++ b/security/apparmor/policy_unpack_test.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for AppArmor's policy unpack. + */ + +#include <kunit/test.h> +#include <kunit/visibility.h> + +#include "include/policy.h" +#include "include/policy_unpack.h" + +#include <linux/unaligned.h> + +#define TEST_STRING_NAME "TEST_STRING" +#define TEST_STRING_DATA "testing" +#define TEST_STRING_BUF_OFFSET \ + (3 + strlen(TEST_STRING_NAME) + 1) + +#define TEST_U32_NAME "U32_TEST" +#define TEST_U32_DATA ((u32)0x01020304) +#define TEST_NAMED_U32_BUF_OFFSET \ + (TEST_STRING_BUF_OFFSET + 3 + strlen(TEST_STRING_DATA) + 1) +#define TEST_U32_BUF_OFFSET \ + (TEST_NAMED_U32_BUF_OFFSET + 3 + strlen(TEST_U32_NAME) + 1) + +#define TEST_U16_OFFSET (TEST_U32_BUF_OFFSET + 3) +#define TEST_U16_DATA ((u16)(TEST_U32_DATA >> 16)) + +#define TEST_U64_NAME "U64_TEST" +#define TEST_U64_DATA ((u64)0x0102030405060708) +#define TEST_NAMED_U64_BUF_OFFSET (TEST_U32_BUF_OFFSET + sizeof(u32) + 1) +#define TEST_U64_BUF_OFFSET \ + (TEST_NAMED_U64_BUF_OFFSET + 3 + strlen(TEST_U64_NAME) + 1) + +#define TEST_BLOB_NAME "BLOB_TEST" +#define TEST_BLOB_DATA "\xde\xad\x00\xbe\xef" +#define TEST_BLOB_DATA_SIZE (ARRAY_SIZE(TEST_BLOB_DATA)) +#define TEST_NAMED_BLOB_BUF_OFFSET (TEST_U64_BUF_OFFSET + sizeof(u64) + 1) +#define TEST_BLOB_BUF_OFFSET \ + (TEST_NAMED_BLOB_BUF_OFFSET + 3 + strlen(TEST_BLOB_NAME) + 1) + +#define TEST_ARRAY_NAME "ARRAY_TEST" +#define TEST_ARRAY_SIZE 16 +#define TEST_NAMED_ARRAY_BUF_OFFSET \ + (TEST_BLOB_BUF_OFFSET + 5 + TEST_BLOB_DATA_SIZE) +#define TEST_ARRAY_BUF_OFFSET \ + (TEST_NAMED_ARRAY_BUF_OFFSET + 3 + strlen(TEST_ARRAY_NAME) + 1) + +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); + +struct policy_unpack_fixture { + struct aa_ext *e; + size_t e_size; +}; + +static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf, + struct kunit *test, size_t buf_size) +{ + char *buf; + struct aa_ext *e; + + buf = kunit_kzalloc(test, buf_size, GFP_USER); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, buf); + + e = kunit_kmalloc(test, sizeof(*e), GFP_USER); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, e); + + e->start = buf; + e->end = e->start + buf_size; + e->pos = e->start; + + *buf = AA_NAME; + *(buf + 1) = strlen(TEST_STRING_NAME) + 1; + strscpy(buf + 3, TEST_STRING_NAME, e->end - (void *)(buf + 3)); + + buf = e->start + TEST_STRING_BUF_OFFSET; + *buf = AA_STRING; + *(buf + 1) = strlen(TEST_STRING_DATA) + 1; + strscpy(buf + 3, TEST_STRING_DATA, e->end - (void *)(buf + 3)); + buf = e->start + TEST_NAMED_U32_BUF_OFFSET; + *buf = AA_NAME; + *(buf + 1) = strlen(TEST_U32_NAME) + 1; + strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3)); + *(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32; + put_unaligned_le32(TEST_U32_DATA, buf + 3 + strlen(TEST_U32_NAME) + 2); + + buf = e->start + TEST_NAMED_U64_BUF_OFFSET; + *buf = AA_NAME; + *(buf + 1) = strlen(TEST_U64_NAME) + 1; + strscpy(buf + 3, TEST_U64_NAME, e->end - (void *)(buf + 3)); + *(buf + 3 + strlen(TEST_U64_NAME) + 1) = AA_U64; + *((__le64 *)(buf + 3 + strlen(TEST_U64_NAME) + 2)) = cpu_to_le64(TEST_U64_DATA); + + buf = e->start + TEST_NAMED_BLOB_BUF_OFFSET; + *buf = AA_NAME; + *(buf + 1) = strlen(TEST_BLOB_NAME) + 1; + strscpy(buf + 3, TEST_BLOB_NAME, e->end - (void *)(buf + 3)); + *(buf + 3 + strlen(TEST_BLOB_NAME) + 1) = AA_BLOB; + *(buf + 3 + strlen(TEST_BLOB_NAME) + 2) = TEST_BLOB_DATA_SIZE; + memcpy(buf + 3 + strlen(TEST_BLOB_NAME) + 6, + TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE); + + buf = e->start + TEST_NAMED_ARRAY_BUF_OFFSET; + *buf = AA_NAME; + *(buf + 1) = strlen(TEST_ARRAY_NAME) + 1; + strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3)); + *(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY; + put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2); + + return e; +} + +static int policy_unpack_test_init(struct kunit *test) +{ + size_t e_size = TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1; + struct policy_unpack_fixture *puf; + + puf = kunit_kmalloc(test, sizeof(*puf), GFP_USER); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, puf); + + puf->e_size = e_size; + puf->e = build_aa_ext_struct(puf, test, e_size); + + test->priv = puf; + return 0; +} + +static void policy_unpack_test_inbounds_when_inbounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + + KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, 0)); + KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size / 2)); + KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size)); +} + +static void policy_unpack_test_inbounds_when_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + + KUNIT_EXPECT_FALSE(test, aa_inbounds(puf->e, puf->e_size + 1)); +} + +static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + u16 array_size = 0; + + puf->e->pos += TEST_ARRAY_BUF_OFFSET; + + KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, NULL, &array_size)); + KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1); +} + +static void policy_unpack_test_unpack_array_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_ARRAY_NAME; + u16 array_size = 0; + + puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET; + + KUNIT_EXPECT_TRUE(test, aa_unpack_array(puf->e, name, &array_size)); + KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16) + 1); +} + +static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_ARRAY_NAME; + u16 array_size; + + puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET; + puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16); + + KUNIT_EXPECT_FALSE(test, aa_unpack_array(puf->e, name, &array_size)); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_NAMED_ARRAY_BUF_OFFSET); +} + +static void policy_unpack_test_unpack_blob_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *blob = NULL; + size_t size; + + puf->e->pos += TEST_BLOB_BUF_OFFSET; + size = aa_unpack_blob(puf->e, &blob, NULL); + + KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE); + KUNIT_EXPECT_TRUE(test, + memcmp(blob, TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE) == 0); +} + +static void policy_unpack_test_unpack_blob_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *blob = NULL; + size_t size; + + puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET; + size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME); + + KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE); + KUNIT_EXPECT_TRUE(test, + memcmp(blob, TEST_BLOB_DATA, TEST_BLOB_DATA_SIZE) == 0); +} + +static void policy_unpack_test_unpack_blob_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *blob = NULL; + void *start; + int size; + + puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET; + start = puf->e->pos; + puf->e->end = puf->e->start + TEST_BLOB_BUF_OFFSET + + TEST_BLOB_DATA_SIZE - 1; + + size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start); +} + +static void policy_unpack_test_unpack_str_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char *string = NULL; + size_t size; + + puf->e->pos += TEST_STRING_BUF_OFFSET; + size = aa_unpack_str(puf->e, &string, NULL); + + KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1); + KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA); +} + +static void policy_unpack_test_unpack_str_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char *string = NULL; + size_t size; + + size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME); + + KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1); + KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA); +} + +static void policy_unpack_test_unpack_str_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char *string = NULL; + void *start = puf->e->pos; + int size; + + puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET + + strlen(TEST_STRING_DATA) - 1; + + size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start); +} + +static void policy_unpack_test_unpack_strdup_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *string = NULL; + size_t size; + + puf->e->pos += TEST_STRING_BUF_OFFSET; + size = aa_unpack_strdup(puf->e, &string, NULL); + + KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1); + KUNIT_EXPECT_FALSE(test, + ((uintptr_t)puf->e->start <= (uintptr_t)string) + && ((uintptr_t)string <= (uintptr_t)puf->e->end)); + KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA); + + kfree(string); +} + +static void policy_unpack_test_unpack_strdup_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *string = NULL; + size_t size; + + size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME); + + KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1); + KUNIT_EXPECT_FALSE(test, + ((uintptr_t)puf->e->start <= (uintptr_t)string) + && ((uintptr_t)string <= (uintptr_t)puf->e->end)); + KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA); + + kfree(string); +} + +static void policy_unpack_test_unpack_strdup_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + void *start = puf->e->pos; + char *string = NULL; + int size; + + puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET + + strlen(TEST_STRING_DATA) - 1; + + size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_NULL(test, string); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start); + + kfree(string); +} + +static void policy_unpack_test_unpack_nameX_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success; + + puf->e->pos += TEST_U32_BUF_OFFSET; + + success = aa_unpack_nameX(puf->e, AA_U32, NULL); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U32_BUF_OFFSET + 1); +} + +static void policy_unpack_test_unpack_nameX_with_wrong_code(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success; + + puf->e->pos += TEST_U32_BUF_OFFSET; + + success = aa_unpack_nameX(puf->e, AA_BLOB, NULL); + + KUNIT_EXPECT_FALSE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U32_BUF_OFFSET); +} + +static void policy_unpack_test_unpack_nameX_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_U32_NAME; + bool success; + + puf->e->pos += TEST_NAMED_U32_BUF_OFFSET; + + success = aa_unpack_nameX(puf->e, AA_U32, name); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U32_BUF_OFFSET + 1); +} + +static void policy_unpack_test_unpack_nameX_with_wrong_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + static const char name[] = "12345678"; + bool success; + + puf->e->pos += TEST_NAMED_U32_BUF_OFFSET; + + success = aa_unpack_nameX(puf->e, AA_U32, name); + + KUNIT_EXPECT_FALSE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_NAMED_U32_BUF_OFFSET); +} + +static void policy_unpack_test_unpack_u16_chunk_basic(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *chunk = NULL; + size_t size; + + puf->e->pos += TEST_U16_OFFSET; + /* + * WARNING: For unit testing purposes, we're pushing puf->e->end past + * the end of the allocated memory. Doing anything other than comparing + * memory addresses is dangerous. + */ + puf->e->end += TEST_U16_DATA; + + size = aa_unpack_u16_chunk(puf->e, &chunk); + + KUNIT_EXPECT_PTR_EQ(test, chunk, + puf->e->start + TEST_U16_OFFSET + 2); + KUNIT_EXPECT_EQ(test, size, TEST_U16_DATA); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, (chunk + TEST_U16_DATA)); +} + +static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_1( + struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *chunk = NULL; + size_t size; + + puf->e->pos = puf->e->end - 1; + + size = aa_unpack_u16_chunk(puf->e, &chunk); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_NULL(test, chunk); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->end - 1); +} + +static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_2( + struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + char *chunk = NULL; + size_t size; + + puf->e->pos += TEST_U16_OFFSET; + /* + * WARNING: For unit testing purposes, we're pushing puf->e->end past + * the end of the allocated memory. Doing anything other than comparing + * memory addresses is dangerous. + */ + puf->e->end = puf->e->pos + TEST_U16_DATA - 1; + + size = aa_unpack_u16_chunk(puf->e, &chunk); + + KUNIT_EXPECT_EQ(test, size, 0); + KUNIT_EXPECT_NULL(test, chunk); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->start + TEST_U16_OFFSET); +} + +static void policy_unpack_test_unpack_u32_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success; + u32 data = 0; + + puf->e->pos += TEST_U32_BUF_OFFSET; + + success = aa_unpack_u32(puf->e, &data, NULL); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32) + 1); +} + +static void policy_unpack_test_unpack_u32_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_U32_NAME; + bool success; + u32 data = 0; + + puf->e->pos += TEST_NAMED_U32_BUF_OFFSET; + + success = aa_unpack_u32(puf->e, &data, name); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32) + 1); +} + +static void policy_unpack_test_unpack_u32_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_U32_NAME; + bool success; + u32 data = 0; + + puf->e->pos += TEST_NAMED_U32_BUF_OFFSET; + puf->e->end = puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32); + + success = aa_unpack_u32(puf->e, &data, name); + + KUNIT_EXPECT_FALSE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_NAMED_U32_BUF_OFFSET); +} + +static void policy_unpack_test_unpack_u64_with_null_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success; + u64 data = 0; + + puf->e->pos += TEST_U64_BUF_OFFSET; + + success = aa_unpack_u64(puf->e, &data, NULL); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64) + 1); +} + +static void policy_unpack_test_unpack_u64_with_name(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_U64_NAME; + bool success; + u64 data = 0; + + puf->e->pos += TEST_NAMED_U64_BUF_OFFSET; + + success = aa_unpack_u64(puf->e, &data, name); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64) + 1); +} + +static void policy_unpack_test_unpack_u64_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + const char name[] = TEST_U64_NAME; + bool success; + u64 data = 0; + + puf->e->pos += TEST_NAMED_U64_BUF_OFFSET; + puf->e->end = puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64); + + success = aa_unpack_u64(puf->e, &data, name); + + KUNIT_EXPECT_FALSE(test, success); + KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, + puf->e->start + TEST_NAMED_U64_BUF_OFFSET); +} + +static void policy_unpack_test_unpack_X_code_match(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success = aa_unpack_X(puf->e, AA_NAME); + + KUNIT_EXPECT_TRUE(test, success); + KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start + 1); +} + +static void policy_unpack_test_unpack_X_code_mismatch(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success = aa_unpack_X(puf->e, AA_STRING); + + KUNIT_EXPECT_FALSE(test, success); + KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start); +} + +static void policy_unpack_test_unpack_X_out_of_bounds(struct kunit *test) +{ + struct policy_unpack_fixture *puf = test->priv; + bool success; + + puf->e->pos = puf->e->end; + success = aa_unpack_X(puf->e, AA_NAME); + + KUNIT_EXPECT_FALSE(test, success); +} + +static struct kunit_case apparmor_policy_unpack_test_cases[] = { + KUNIT_CASE(policy_unpack_test_inbounds_when_inbounds), + KUNIT_CASE(policy_unpack_test_inbounds_when_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_array_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_array_with_name), + KUNIT_CASE(policy_unpack_test_unpack_array_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_blob_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_blob_with_name), + KUNIT_CASE(policy_unpack_test_unpack_blob_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_nameX_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_nameX_with_wrong_code), + KUNIT_CASE(policy_unpack_test_unpack_nameX_with_name), + KUNIT_CASE(policy_unpack_test_unpack_nameX_with_wrong_name), + KUNIT_CASE(policy_unpack_test_unpack_str_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_str_with_name), + KUNIT_CASE(policy_unpack_test_unpack_str_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_strdup_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_strdup_with_name), + KUNIT_CASE(policy_unpack_test_unpack_strdup_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_basic), + KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_out_of_bounds_1), + KUNIT_CASE(policy_unpack_test_unpack_u16_chunk_out_of_bounds_2), + KUNIT_CASE(policy_unpack_test_unpack_u32_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_u32_with_name), + KUNIT_CASE(policy_unpack_test_unpack_u32_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_u64_with_null_name), + KUNIT_CASE(policy_unpack_test_unpack_u64_with_name), + KUNIT_CASE(policy_unpack_test_unpack_u64_out_of_bounds), + KUNIT_CASE(policy_unpack_test_unpack_X_code_match), + KUNIT_CASE(policy_unpack_test_unpack_X_code_mismatch), + KUNIT_CASE(policy_unpack_test_unpack_X_out_of_bounds), + {}, +}; + +static struct kunit_suite apparmor_policy_unpack_test_module = { + .name = "apparmor_policy_unpack", + .init = policy_unpack_test_init, + .test_cases = apparmor_policy_unpack_test_cases, +}; + +kunit_test_suite(apparmor_policy_unpack_test_module); + +MODULE_DESCRIPTION("KUnit tests for AppArmor's policy unpack"); +MODULE_LICENSE("GPL"); diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index d81617379d63..ce40f15d4952 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,15 +6,10 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include "include/apparmor.h" -#include "include/context.h" +#include "include/cred.h" #include "include/policy.h" #include "include/policy_ns.h" #include "include/domain.h" @@ -21,20 +17,18 @@ /** - * aa_getprocattr - Return the profile information for @profile - * @profile: the profile to print profile info about (NOT NULL) - * @string: Returns - string containing the profile info (NOT NULL) - * - * Returns: length of @string on success else error on failure + * aa_getprocattr - Return the label information for @label + * @label: the label to print label info about (NOT NULL) + * @string: Returns - string containing the label info (NOT NULL) + * @newline: indicates that a newline should be added * - * Requires: profile != NULL + * Requires: label != NULL && string != NULL * - * Creates a string containing the namespace_name://profile_name for - * @profile. + * Creates a string containing the label information for @label. * * Returns: size of string placed in @string else error code on failure */ -int aa_getprocattr(struct aa_label *label, char **string) +int aa_getprocattr(struct aa_label *label, char **string, bool newline) { struct aa_ns *ns = labels_ns(label); struct aa_ns *current_ns = aa_get_current_ns(); @@ -64,11 +58,12 @@ int aa_getprocattr(struct aa_label *label, char **string) return len; } - (*string)[len] = '\n'; - (*string)[len + 1] = 0; + if (newline) + (*string)[len++] = '\n'; + (*string)[len] = 0; aa_put_ns(current_ns); - return len + 1; + return len; } /** @@ -96,7 +91,7 @@ static char *split_token_from_name(const char *op, char *args, u64 *token) } /** - * aa_setprocattr_chagnehat - handle procattr interface to change_hat + * aa_setprocattr_changehat - handle procattr interface to change_hat * @args: args received from writing to /proc/<pid>/attr/current (NOT NULL) * @size: size of the args * @flags: set of flags governing behavior @@ -130,12 +125,14 @@ int aa_setprocattr_changehat(char *args, size_t size, int flags) 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" + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" , __func__, current->pid, token, count, hat); hat = next; } } else - AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", __func__, current->pid, token, count, "<NULL>"); return aa_change_hat(hats, count, token, flags); diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index d8bc842594ed..8e80db3ae21c 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * @@ -5,18 +6,13 @@ * * Copyright (C) 1998-2008 Novell/SUSE * Copyright 2009-2010 Canonical Ltd. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. */ #include <linux/audit.h> #include <linux/security.h> #include "include/audit.h" -#include "include/context.h" +#include "include/cred.h" #include "include/resource.h" #include "include/policy.h" @@ -34,42 +30,49 @@ struct aa_sfs_entry aa_sfs_entry_rlimit[] = { static void audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); audit_log_format(ab, " rlimit=%s value=%lu", - rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max); - if (aad(sa)->peer) { + rlim_names[ad->rlim.rlim], ad->rlim.max); + if (ad->peer) { audit_log_format(ab, " peer="); - aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer, + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, FLAGS_NONE, GFP_ATOMIC); } } /** * audit_resource - audit setting resource limit + * @subj_cred: cred setting the resource * @profile: profile being enforced (NOT NULL) - * @resoure: rlimit being auditing + * @resource: rlimit being auditing * @value: value being set + * @peer: aa_albel of the task being set + * @info: info being auditing * @error: error value * - * Returns: 0 or sa->error else other error code on failure + * Returns: 0 or ad->error else other error code on failure */ -static int audit_resource(struct aa_profile *profile, unsigned int resource, +static int audit_resource(const struct cred *subj_cred, + struct aa_profile *profile, unsigned int resource, unsigned long value, struct aa_label *peer, const char *info, int error) { - DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS, + OP_SETRLIMIT); - aad(&sa)->rlim.rlim = resource; - aad(&sa)->rlim.max = value; - aad(&sa)->peer = peer; - aad(&sa)->info = info; - aad(&sa)->error = error; + ad.subj_cred = subj_cred; + ad.rlim.rlim = resource; + ad.rlim.max = value; + ad.peer = peer; + ad.info = info; + ad.error = error; - return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb); + return aa_audit(AUDIT_APPARMOR_AUTO, profile, &ad, audit_cb); } /** - * aa_map_resouce - map compiled policy resource to internal # + * aa_map_resource - map compiled policy resource to internal # * @resource: flattened policy resource number * * Returns: resource # for the current architecture. @@ -82,30 +85,34 @@ int aa_map_resource(int resource) return rlim_map[resource]; } -static int profile_setrlimit(struct aa_profile *profile, unsigned int resource, +static int profile_setrlimit(const struct cred *subj_cred, + struct aa_profile *profile, unsigned int resource, struct rlimit *new_rlim) { + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; - if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max > - profile->rlimits.limits[resource].rlim_max) + if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > + rules->rlimits.limits[resource].rlim_max) e = -EACCES; - return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL, - e); + return audit_resource(subj_cred, profile, resource, new_rlim->rlim_max, + NULL, NULL, e); } /** * aa_task_setrlimit - test permission to set an rlimit - * @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) + * @subj_cred: cred setting the limit + * @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) * * Control raising the processes hard limit. * * Returns: 0 or error code if setting resource failed */ -int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, +int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label, + struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { struct aa_profile *profile; @@ -124,14 +131,15 @@ int aa_task_setrlimit(struct aa_label *label, struct task_struct *task, */ if (label != peer && - !aa_capable(label, CAP_SYS_RESOURCE, SECURITY_CAP_NOAUDIT)) + aa_capable(subj_cred, label, CAP_SYS_RESOURCE, CAP_OPT_NOAUDIT) != 0) error = fn_for_each(label, profile, - audit_resource(profile, resource, + audit_resource(subj_cred, profile, resource, new_rlim->rlim_max, peer, - "cap_sys_resoure", -EACCES)); + "cap_sys_resource", -EACCES)); else error = fn_for_each_confined(label, profile, - profile_setrlimit(profile, resource, new_rlim)); + profile_setrlimit(subj_cred, profile, resource, + new_rlim)); aa_put_label(peer); return error; @@ -156,12 +164,13 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) * to the lesser of the tasks hard limit and the init tasks soft limit */ label_for_each_confined(i, old_l, old) { - if (old->rlimits.mask) { + struct aa_ruleset *rules = old->label.rules[0]; + if (rules->rlimits.mask) { int j; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (old->rlimits.mask & mask) { + if (rules->rlimits.mask & mask) { rlim = current->signal->rlim + j; initrlim = init_task.signal->rlim + j; rlim->rlim_cur = min(rlim->rlim_max, @@ -173,17 +182,18 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) /* set any new hard limits as dictated by the new profile */ label_for_each_confined(i, new_l, new) { + struct aa_ruleset *rules = new->label.rules[0]; int j; - if (!new->rlimits.mask) + if (!rules->rlimits.mask) continue; for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) + if (!(rules->rlimits.mask & mask)) continue; rlim = current->signal->rlim + j; rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[j].rlim_max); + rules->rlimits.limits[j].rlim_max); /* soft limit should not exceed hard limit */ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); } diff --git a/security/apparmor/secid.c b/security/apparmor/secid.c index 3a3edbad0b21..28caf66b9033 100644 --- a/security/apparmor/secid.c +++ b/security/apparmor/secid.c @@ -1,48 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * * This file contains AppArmor security identifier (secid) manipulation fns * - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-2017 Canonical Ltd. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * - * AppArmor allocates a unique secid for every profile loaded. If a profile - * is replaced it receives the secid of the profile it is replacing. - * - * The secid value of 0 is invalid. + * AppArmor allocates a unique secid for every label used. If a label + * is replaced it receives the secid of the label it is replacing. */ -#include <linux/spinlock.h> #include <linux/errno.h> #include <linux/err.h> +#include <linux/gfp.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/xarray.h> +#include "include/cred.h" +#include "include/lib.h" #include "include/secid.h" +#include "include/label.h" +#include "include/policy_ns.h" + +/* + * secids - do not pin labels with a refcount. They rely on the label + * properly updating/freeing them + */ +#define AA_FIRST_SECID 2 + +static DEFINE_XARRAY_FLAGS(aa_secids, XA_FLAGS_LOCK_IRQ | XA_FLAGS_TRACK_FREE); + +int apparmor_display_secid_mode; + +/* + * TODO: allow policy to reserve a secid range? + * TODO: add secid pinning + * TODO: use secid_update in label replace + */ + +/* + * see label for inverse aa_label_to_secid + */ +struct aa_label *aa_secid_to_label(u32 secid) +{ + return xa_load(&aa_secids, secid); +} + +static int apparmor_label_to_secctx(struct aa_label *label, + struct lsm_context *cp) +{ + /* TODO: cache secctx and ref count so we don't have to recreate */ + int flags = FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED | FLAG_ABS_ROOT; + int len; + + if (!label) + return -EINVAL; + + if (apparmor_display_secid_mode) + flags |= FLAG_SHOW_MODE; + + if (cp) + len = aa_label_asxprint(&cp->context, root_ns, label, + flags, GFP_ATOMIC); + else + len = aa_label_snxprint(NULL, 0, root_ns, label, flags); -/* global counter from which secids are allocated */ -static u32 global_secid; -static DEFINE_SPINLOCK(secid_lock); + if (len < 0) + return -ENOMEM; -/* TODO FIXME: add secid to profile mapping, and secid recycling */ + if (cp) { + cp->len = len; + cp->id = LSM_ID_APPARMOR; + } + + return len; +} + +int apparmor_secid_to_secctx(u32 secid, struct lsm_context *cp) +{ + struct aa_label *label = aa_secid_to_label(secid); + + return apparmor_label_to_secctx(label, cp); +} + +int apparmor_lsmprop_to_secctx(struct lsm_prop *prop, struct lsm_context *cp) +{ + struct aa_label *label; + + label = prop->apparmor.label; + + return apparmor_label_to_secctx(label, cp); +} + +int apparmor_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + struct aa_label *label; + + label = aa_label_strn_parse(&root_ns->unconfined->label, secdata, + seclen, GFP_KERNEL, false, false); + if (IS_ERR(label)) + return PTR_ERR(label); + *secid = label->secid; + + return 0; +} + +void apparmor_release_secctx(struct lsm_context *cp) +{ + if (cp->id == LSM_ID_APPARMOR) { + kfree(cp->context); + cp->context = NULL; + cp->id = LSM_ID_UNDEF; + } +} /** * aa_alloc_secid - allocate a new secid for a profile + * @label: the label to allocate a secid for + * @gfp: memory allocation flags + * + * Returns: 0 with @label->secid initialized + * <0 returns error with @label->secid set to AA_SECID_INVALID */ -u32 aa_alloc_secid(void) +int aa_alloc_secid(struct aa_label *label, gfp_t gfp) { - u32 secid; + unsigned long flags; + int ret; + + xa_lock_irqsave(&aa_secids, flags); + ret = __xa_alloc(&aa_secids, &label->secid, label, + XA_LIMIT(AA_FIRST_SECID, INT_MAX), gfp); + xa_unlock_irqrestore(&aa_secids, flags); - /* - * TODO FIXME: secid recycling - part of profile mapping table - */ - spin_lock(&secid_lock); - secid = (++global_secid); - spin_unlock(&secid_lock); - return secid; + if (ret < 0) { + label->secid = AA_SECID_INVALID; + return ret; + } + + return 0; } /** @@ -51,5 +147,9 @@ u32 aa_alloc_secid(void) */ void aa_free_secid(u32 secid) { - ; /* NOP ATM */ + unsigned long flags; + + xa_lock_irqsave(&aa_secids, flags); + __xa_erase(&aa_secids, secid); + xa_unlock_irqrestore(&aa_secids, flags); } diff --git a/security/apparmor/stacksplitdfa.in b/security/apparmor/stacksplitdfa.in new file mode 100644 index 000000000000..4bddd10b62a9 --- /dev/null +++ b/security/apparmor/stacksplitdfa.in @@ -0,0 +1,114 @@ +/* 0x1 [^\000]*[^/\000]//& */ 0x1B, 0x5E, 0x78, 0x3D, 0x00, 0x00, +0x00, 0x18, 0x00, 0x00, 0x04, 0xD8, 0x00, 0x00, 0x6E, 0x6F, 0x74, +0x66, 0x6C, 0x65, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, +0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, +0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, +0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, +0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x08, 0x00, +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, +0x04, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, +0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, +0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00 diff --git a/security/apparmor/task.c b/security/apparmor/task.c new file mode 100644 index 000000000000..c9bc9cc69475 --- /dev/null +++ b/security/apparmor/task.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor task related definitions and mediation + * + * Copyright 2017 Canonical Ltd. + * + * TODO + * If a task uses change_hat it currently does not return to the old + * cred or task context but instead creates a new one. Ideally the task + * should return to the previous cred if it has not been modified. + */ + +#include <linux/gfp.h> +#include <linux/ptrace.h> + +#include "include/audit.h" +#include "include/cred.h" +#include "include/policy.h" +#include "include/task.h" + +/** + * 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_cred_label(__task_cred(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_label(struct aa_label *label) +{ + struct aa_label *old = aa_current_raw_label(); + struct aa_task_ctx *ctx = task_ctx(current); + struct cred *new; + + AA_BUG(!label); + + if (old == label) + return 0; + + if (current_cred() != current_real_cred()) + return -EBUSY; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + if (ctx->nnp && label_is_stale(ctx->nnp)) { + struct aa_label *tmp = ctx->nnp; + + ctx->nnp = aa_get_newest_label(tmp); + aa_put_label(tmp); + } + if (unconfined(label) || (labels_ns(old) != labels_ns(label))) + /* + * if switching to unconfined or a different label namespace + * clear out context state + */ + aa_clear_task_ctx_trans(task_ctx(current)); + + /* + * be careful switching cred label, when racing replacement it + * is possible that the cred labels's->proxy->label is the reference + * keeping @label valid, so make sure to get its reference before + * dropping the reference on the cred's label + */ + aa_get_label(label); + aa_put_label(cred_label(new)); + set_cred_label(new, label); + + commit_creds(new); + return 0; +} + + +/** + * aa_set_current_onexec - set the tasks change_profile to happen onexec + * @label: system label to set at exec (MAYBE NULL to clear value) + * @stack: whether stacking should be done + */ +void aa_set_current_onexec(struct aa_label *label, bool stack) +{ + struct aa_task_ctx *ctx = task_ctx(current); + + aa_get_label(label); + aa_put_label(ctx->onexec); + ctx->onexec = label; + ctx->token = stack; +} + +/** + * aa_set_current_hat - set the current tasks hat + * @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 + * validate the token to match. + * + * Returns: 0 or error on failure + */ +int aa_set_current_hat(struct aa_label *label, u64 token) +{ + struct aa_task_ctx *ctx = task_ctx(current); + struct cred *new; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + AA_BUG(!label); + + if (!ctx->previous) { + /* transfer refcount */ + ctx->previous = cred_label(new); + ctx->token = token; + } else if (ctx->token == token) { + aa_put_label(cred_label(new)); + } else { + /* previous_profile && ctx->token != token */ + abort_creds(new); + return -EACCES; + } + + set_cred_label(new, aa_get_newest_label(label)); + /* clear exec on switching context */ + aa_put_label(ctx->onexec); + ctx->onexec = NULL; + + commit_creds(new); + return 0; +} + +/** + * 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 label. The token + * must match the stored token value. + * + * Returns: 0 or error of failure + */ +int aa_restore_previous_label(u64 token) +{ + struct aa_task_ctx *ctx = task_ctx(current); + struct cred *new; + + if (ctx->token != token) + return -EACCES; + /* ignore restores when there is no saved label */ + if (!ctx->previous) + return 0; + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + aa_put_label(cred_label(new)); + set_cred_label(new, aa_get_newest_label(ctx->previous)); + AA_BUG(!cred_label(new)); + /* clear exec && prev information when restoring to previous context */ + aa_clear_task_ctx_trans(ctx); + + commit_creds(new); + + return 0; +} + +/** + * audit_ptrace_mask - convert mask to permission string + * @mask: permission mask to convert + * + * Returns: pointer to static string + */ +static const char *audit_ptrace_mask(u32 mask) +{ + switch (mask) { + case MAY_READ: + return "read"; + case MAY_WRITE: + return "trace"; + case AA_MAY_BE_READ: + return "readby"; + case AA_MAY_BE_TRACED: + return "tracedby"; + } + return ""; +} + +/* call back to audit ptrace fields */ +static void audit_ptrace_cb(struct audit_buffer *ab, void *va) +{ + struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); + + if (ad->request & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " requested_mask=\"%s\"", + audit_ptrace_mask(ad->request)); + + if (ad->denied & AA_PTRACE_PERM_MASK) { + audit_log_format(ab, " denied_mask=\"%s\"", + audit_ptrace_mask(ad->denied)); + } + } + audit_log_format(ab, " peer="); + aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, + FLAGS_NONE, GFP_ATOMIC); +} + +/* assumes check for RULE_MEDIATES is already done */ +/* TODO: conditionals */ +static int profile_ptrace_perm(const struct cred *cred, + struct aa_profile *profile, + struct aa_label *peer, u32 request, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms perms = { }; + + ad->subj_cred = cred; + ad->peer = peer; + aa_profile_match_label(profile, rules, peer, AA_CLASS_PTRACE, request, + &perms); + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, ad, audit_ptrace_cb); +} + +static int profile_tracee_perm(const struct cred *cred, + struct aa_profile *tracee, + struct aa_label *tracer, u32 request, + struct apparmor_audit_data *ad) +{ + if (profile_unconfined(tracee) || unconfined(tracer) || + !label_mediates(&tracee->label, AA_CLASS_PTRACE)) + return 0; + + return profile_ptrace_perm(cred, tracee, tracer, request, ad); +} + +static int profile_tracer_perm(const struct cred *cred, + struct aa_profile *tracer, + struct aa_label *tracee, u32 request, + struct apparmor_audit_data *ad) +{ + if (profile_unconfined(tracer)) + return 0; + + if (label_mediates(&tracer->label, AA_CLASS_PTRACE)) + return profile_ptrace_perm(cred, tracer, tracee, request, ad); + + /* profile uses the old style capability check for ptrace */ + if (&tracer->label == tracee) + return 0; + + ad->subj_label = &tracer->label; + ad->peer = tracee; + ad->request = 0; + ad->error = aa_capable(cred, &tracer->label, CAP_SYS_PTRACE, + CAP_OPT_NONE); + + return aa_audit(AUDIT_APPARMOR_AUTO, tracer, ad, audit_ptrace_cb); +} + +/** + * aa_may_ptrace - test if tracer task can trace the tracee + * @tracer_cred: cred of task doing the tracing (NOT NULL) + * @tracer: label of the task doing the tracing (NOT NULL) + * @tracee_cred: cred of task to be traced + * @tracee: task label to be traced + * @request: permission request + * + * Returns: %0 else error code if permission denied or error + */ +int aa_may_ptrace(const struct cred *tracer_cred, struct aa_label *tracer, + const struct cred *tracee_cred, struct aa_label *tracee, + u32 request) +{ + struct aa_profile *profile; + u32 xrequest = request << PTRACE_PERM_SHIFT; + DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_PTRACE, OP_PTRACE); + + return xcheck_labels(tracer, tracee, profile, + profile_tracer_perm(tracer_cred, profile, tracee, + request, &sa), + profile_tracee_perm(tracee_cred, profile, tracer, + xrequest, &sa)); +} + +/* call back to audit ptrace fields */ +static void audit_ns_cb(struct audit_buffer *ab, void *va) +{ + struct apparmor_audit_data *ad = aad_of_va(va); + + if (ad->request & AA_USERNS_CREATE) + audit_log_format(ab, " requested=\"userns_create\""); + + if (ad->denied & AA_USERNS_CREATE) + audit_log_format(ab, " denied=\"userns_create\""); +} + +int aa_profile_ns_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request) +{ + struct aa_perms perms = { }; + int error = 0; + + ad->subj_label = &profile->label; + ad->request = request; + + if (!profile_unconfined(profile)) { + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + + state = RULE_MEDIATES(rules, ad->class); + if (!state) + /* TODO: add flag to complain about unmediated */ + return 0; + perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &perms); + error = aa_check_perms(profile, &perms, request, ad, + audit_ns_cb); + } + + return error; +} |
