diff options
Diffstat (limited to 'security/apparmor')
60 files changed, 19127 insertions, 4077 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 9b9013b2e321..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,18 +15,108 @@ 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" +config SECURITY_APPARMOR_DEBUG + bool "Build AppArmor with debug code" 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. + default n + help + Build apparmor with debugging logic in apparmor. Not all + debugging logic will necessarily be enabled. A submenu will + provide fine grained control of the debug options that are + available. + +config SECURITY_APPARMOR_DEBUG_ASSERTS + bool "Build AppArmor with debugging asserts" + depends on SECURITY_APPARMOR_DEBUG + default y + help + Enable code assertions made with AA_BUG. These are primarily + function entry preconditions but also exist at other key + points. If the assert is triggered it will trigger a WARN + message. + +config SECURITY_APPARMOR_DEBUG_MESSAGES + bool "Debug messages enabled by default" + depends on SECURITY_APPARMOR_DEBUG + default n + help + 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 5706b74c857f..12fb419714c0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -1,13 +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 sid.o file.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 @@ -18,7 +57,11 @@ quiet_cmd_make-caps = GEN $@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e '/CAP_FS_MASK/d' \ -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/[\2] = "\L\1",/p';\ - echo "};" >> $@ + echo "};" >> $@ ;\ + printf '%s' '\#define AA_SFS_CAPS_MASK "' >> $@ ;\ + sed $< -r -n -e '/CAP_FS_MASK/d' \ + -e 's/^\#define[ \t]+CAP_([A-Z0-9_]+)[ \t]+([0-9]+)/\L\1/p' | \ + tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ # Build a lower case string table of rlimit names. @@ -41,7 +84,7 @@ cmd_make-caps = echo "static const char *const capability_names[] = {" > $@ ;\ # #define RLIMIT_FSIZE 1 /* Maximum filesize */ # #define RLIMIT_STACK 3 /* max stack size */ # to -# #define AA_FS_RLIMIT_MASK "fsize stack" +# #define AA_SFS_RLIMIT_MASK "fsize stack" quiet_cmd_make-rlim = GEN $@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ > $@ ;\ @@ -51,11 +94,12 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ echo "static const int rlim_map[RLIM_NLIMITS] = {" >> $@ ;\ sed -r -n "s/^\# ?define[ \t]+(RLIMIT_[A-Z0-9_]+).*/\1,/p" $< >> $@ ;\ echo "};" >> $@ ; \ - echo -n '\#define AA_FS_RLIMIT_MASK "' >> $@ ;\ + printf '%s' '\#define AA_SFS_RLIMIT_MASK "' >> $@ ;\ sed -r -n 's/^\# ?define[ \t]+RLIMIT_([A-Z0-9_]+).*/\L\1/p' $< | \ tr '\n' ' ' | sed -e 's/ $$/"\n/' >> $@ $(obj)/capability.o : $(obj)/capability_names.h +$(obj)/net.o : $(obj)/net_names.h $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile @@ -63,3 +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 16c15ec6f670..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,31 +6,381 @@ * * 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> #include <linux/namei.h> #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/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 specify the location of the security filesystem. + * + * fns and data are prefixed with + * aa_sfs_ + * + * an apparmorfs component: + * used loaded policy content and introspection. It is not part of a + * regular mounted filesystem and is available only through the magic + * policy symlink in the root of the securityfs apparmor/ directory. + * Tasks queries will be magically redirected to the correct portion + * of the policy tree based on their confinement. + * + * fns and data are prefixed with + * aafs_ + * + * The aa_fs_ prefix is used to indicate the fn is used by both the + * securityfs and apparmorfs filesystems. + */ + + +/* + * 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 + +/** + * mangle_name - mangle a profile name to std profile layout form + * @name: profile name to mangle (NOT NULL) + * @target: buffer to store mangled name, same length as @name (MAYBE NULL) + * + * Returns: length of mangled name + */ +static int mangle_name(const char *name, char *target) +{ + char *t = target; + + while (*name == '/' || *name == '.') + name++; + + if (target) { + for (; *name; name++) { + if (*name == '/') + *(t)++ = '.'; + else if (isspace(*name)) + *(t)++ = '_'; + else if (isalnum(*name) || strchr("._-", *name)) + *(t)++ = *name; + } + + *t = 0; + } else { + int len = 0; + for (; *name; name++) { + if (isalnum(*name) || isspace(*name) || + strchr("/._-", *name)) + len++; + } + + return len; + } + + return t - target; +} + + +/* + * aafs - core fns and data for the policy tree + */ + +#define AAFS_NAME "apparmorfs" +static struct vfsmount *aafs_mnt; +static int aafs_count; + + +static int aafs_show_path(struct seq_file *seq, struct dentry *dentry) +{ + seq_printf(seq, "%s:[%lu]", AAFS_NAME, d_inode(dentry)->i_ino); + return 0; +} + +static void aafs_free_inode(struct 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, + .free_inode = aafs_free_inode, + .show_path = aafs_show_path, +}; + +static int apparmorfs_fill_super(struct super_block *sb, struct fs_context *fc) +{ + static struct tree_descr files[] = { {""} }; + int error; + + error = simple_fill_super(sb, AAFS_MAGIC, files); + if (error) + return error; + sb->s_op = &aafs_super_ops; + + return 0; +} + +static int apparmorfs_get_tree(struct fs_context *fc) +{ + 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, + .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 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 + * @fops: struct file_operations that should be used + * @iops: struct of inode_operations that should be used + */ +static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry, + umode_t mode, void *data, char *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct inode *inode = new_inode(dir->i_sb); + + AA_BUG(!dir); + AA_BUG(!dentry); + + if (!inode) + return -ENOMEM; + + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + simple_inode_init_ts(inode); + inode->i_private = data; + if (S_ISDIR(mode)) { + inode->i_op = iops ? iops : &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inc_nlink(inode); + inc_nlink(dir); + } else if (S_ISLNK(mode)) { + inode->i_op = iops ? iops : &simple_symlink_inode_operations; + inode->i_link = link; + } else { + inode->i_fop = fops; + } + d_instantiate(dentry, inode); + dget(dentry); + + return 0; +} + +/** + * aafs_create - create a dentry in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @link: if symlink, symlink target string + * @fops: struct file_operations that should be used for + * @iops: struct of inode_operations that should be used + * + * This is the basic "create a xxx" function for apparmorfs. + * + * Returns a pointer to a dentry if it succeeds, that must be free with + * aafs_remove(). Will return ERR_PTR on failure. + */ +static struct dentry *aafs_create(const char *name, umode_t mode, + struct dentry *parent, void *data, void *link, + const struct file_operations *fops, + const struct inode_operations *iops) +{ + struct dentry *dentry; + struct inode *dir; + int error; + + AA_BUG(!name); + AA_BUG(!parent); + + if (!(mode & S_IFMT)) + mode = (mode & S_IALLUGO) | S_IFREG; + + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + if (error) + return ERR_PTR(error); + + dir = d_inode(parent); + + inode_lock(dir); + 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; + goto fail_dentry; + } + + error = __aafs_setup_d_inode(dir, dentry, mode, data, link, fops, iops); + if (error) + goto fail_dentry; + inode_unlock(dir); + + return dentry; + +fail_dentry: + dput(dentry); + +fail_lock: + inode_unlock(dir); + simple_release_fs(&aafs_mnt, &aafs_count); + + return ERR_PTR(error); +} + +/** + * aafs_create_file - create a file in the apparmorfs filesystem + * + * @name: name of dentry to create + * @mode: permissions the file should have + * @parent: parent directory for this dentry + * @data: data to store on inode.i_private, available in open() + * @fops: struct file_operations that should be used for + * + * see aafs_create + */ +static struct dentry *aafs_create_file(const char *name, umode_t mode, + struct dentry *parent, void *data, + const struct file_operations *fops) +{ + return aafs_create(name, mode, parent, data, NULL, fops, NULL); +} + +/** + * aafs_create_dir - create a directory in the apparmorfs filesystem + * + * @name: name of dentry to create + * @parent: parent directory for this dentry + * + * see aafs_create + */ +static struct dentry *aafs_create_dir(const char *name, struct dentry *parent) +{ + return aafs_create(name, S_IFDIR | 0755, parent, NULL, NULL, NULL, + NULL); +} + +/** + * aafs_remove - removes a file or directory from the apparmorfs filesystem + * + * @dentry: dentry of the file/directory/symlink to removed. + */ +static void aafs_remove(struct dentry *dentry) +{ + struct inode *dir; + + if (!dentry || IS_ERR(dentry)) + return; + + /* ->d_parent is stable as rename is not supported */ + dir = d_inode(dentry->d_parent); + 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); + } + end_removing(dentry); + simple_release_fs(&aafs_mnt, &aafs_count); +} + + +/* + * aa_fs - policy load/replace/remove + */ /** * aa_simple_write_to_buffer - common routine for getting policy from user - * @op: operation doing the user buffer copy * @userbuf: user buffer to copy data from (NOT NULL) * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) * @copy_size: size of data to copy from user buffer @@ -38,53 +389,69 @@ * Returns: kernel buffer containing copy of user buffer data or an * ERR_PTR on failure. */ -static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, - size_t alloc_size, size_t copy_size, - loff_t *pos) +static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, + size_t copy_size, + loff_t *pos) { - char *data; + struct aa_loaddata *data; - BUG_ON(copy_size > alloc_size); + AA_BUG(copy_size > alloc_size); if (*pos != 0) /* only writes from pos 0, that is complete writes */ return ERR_PTR(-ESPIPE); - /* - * Don't allow profile load/replace/remove from profiles that don't - * have CAP_MAC_ADMIN - */ - if (!aa_may_manage_policy(op)) - return ERR_PTR(-EACCES); - /* freed by caller to simple_write_to_buffer */ - data = kvmalloc(alloc_size); - if (data == NULL) - return ERR_PTR(-ENOMEM); + data = aa_loaddata_alloc(alloc_size); + if (IS_ERR(data)) + return data; - if (copy_from_user(data, userbuf, copy_size)) { - kvfree(data); + data->size = copy_size; + if (copy_from_user(data->data, userbuf, copy_size)) { + aa_put_loaddata(data); return ERR_PTR(-EFAULT); } return data; } - -/* .load file hook fn to load policy */ -static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, - loff_t *pos) +static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, + loff_t *pos, struct aa_ns *ns) { - char *data; + struct aa_loaddata *data; + struct aa_label *label; ssize_t error; - data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); + label = begin_current_label_crit_section(); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(current_cred(), label, ns, mask); + if (error) + goto end_section; + + data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_ADD); - kvfree(data); + error = aa_replace_profiles(ns, label, mask, data); + aa_put_loaddata(data); } +end_section: + end_current_label_crit_section(label); + + return error; +} + +/* .load file hook fn to load policy */ +static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, + loff_t *pos) +{ + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns); + + aa_put_ns(ns); return error; } @@ -98,15 +465,10 @@ static const struct file_operations aa_fs_profile_load = { static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; - ssize_t error; - - data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); - error = PTR_ERR(data); - if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_REPLACE); - kvfree(data); - } + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, + buf, size, pos, ns); + aa_put_ns(ns); return error; } @@ -120,22 +482,35 @@ static const struct file_operations aa_fs_profile_replace = { static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; + struct aa_loaddata *data; + struct aa_label *label; ssize_t error; + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + + label = begin_current_label_crit_section(); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(current_cred(), label, ns, + AA_MAY_REMOVE_POLICY); + if (error) + goto out; /* * aa_remove_profile needs a null terminated string so 1 extra * byte is allocated and the copied data is null terminated. */ - data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); + data = aa_simple_write_to_buffer(buf, size + 1, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - data[size] = 0; - error = aa_remove_profiles(data, size); - kvfree(data); + data->data[size] = 0; + error = aa_remove_profiles(ns, label, data->data, size); + aa_put_loaddata(data); } - + out: + end_current_label_crit_section(label); + aa_put_ns(ns); return error; } @@ -144,88 +519,1952 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; -static int aa_fs_seq_show(struct seq_file *seq, void *v) +struct aa_revision { + struct aa_ns *ns; + long last_read; +}; + +/* revision file hook fn for policy loads */ +static int ns_revision_release(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = file->private_data; + + if (rev) { + aa_put_ns(rev->ns); + kfree(rev); + } + + return 0; +} + +static ssize_t ns_revision_read(struct file *file, char __user *buf, + size_t size, loff_t *ppos) { - struct aa_fs_entry *fs_file = seq->private; + struct aa_revision *rev = file->private_data; + char buffer[32]; + long last_read; + int avail; + + 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); + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + if (wait_event_interruptible(rev->ns->wait, + last_read != + READ_ONCE(rev->ns->revision))) + return -ERESTARTSYS; + mutex_lock_nested(&rev->ns->lock, rev->ns->level); + } + + avail = sprintf(buffer, "%ld\n", rev->ns->revision); + if (*ppos + size > avail) { + rev->last_read = rev->ns->revision; + *ppos = 0; + } + mutex_unlock(&rev->ns->lock); + + return simple_read_from_buffer(buf, size, ppos, buffer, avail); +} + +static int ns_revision_open(struct inode *inode, struct file *file) +{ + struct aa_revision *rev = kzalloc(sizeof(*rev), GFP_KERNEL); + + if (!rev) + return -ENOMEM; + + rev->ns = aa_get_ns(inode->i_private); + if (!rev->ns) + rev->ns = aa_get_current_ns(); + file->private_data = rev; + + return 0; +} + +static __poll_t ns_revision_poll(struct file *file, poll_table *pt) +{ + struct aa_revision *rev = file->private_data; + __poll_t mask = 0; + + if (rev) { + mutex_lock_nested(&rev->ns->lock, rev->ns->level); + poll_wait(file, &rev->ns->wait, pt); + if (rev->last_read < rev->ns->revision) + mask |= EPOLLIN | EPOLLRDNORM; + mutex_unlock(&rev->ns->lock); + } + + return mask; +} + +void __aa_bump_ns_revision(struct aa_ns *ns) +{ + WRITE_ONCE(ns->revision, READ_ONCE(ns->revision) + 1); + wake_up_interruptible(&ns->wait); +} + +static const struct file_operations aa_fs_ns_revision_fops = { + .owner = THIS_MODULE, + .open = ns_revision_open, + .poll = ns_revision_poll, + .read = ns_revision_read, + .llseek = generic_file_llseek, + .release = ns_revision_release, +}; + +static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, + const char *match_str, size_t match_len) +{ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms tmp = { }; + aa_state_t state = DFA_NOMATCH; + + if (profile_unconfined(profile)) + return; + 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); + if (state) { + struct path_cond cond = { }; + + tmp = *(aa_lookup_condperms(current_fsuid(), + rules->file, state, &cond)); + } + } else if (rules->policy->dfa) { + if (!RULE_MEDIATES(rules, *match_str)) + return; /* no change to current perms */ + /* 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) + tmp = *aa_lookup_perms(rules->policy, state); + } + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum_raw(perms, &tmp); +} + + +/** + * query_data - queries a policy and writes its data to buf + * @buf: the resulting data is stored here (NOT NULL) + * @buf_len: size of buf + * @query: query string used to retrieve data + * @query_len: size of query including second NUL byte + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "<LABEL>\0<KEY>\0", where <LABEL> is the name of + * the security confinement context and <KEY> is the name of the data to + * retrieve. <LABEL> and <KEY> must not be NUL-terminated. + * + * Don't expect the contents of buf to be preserved on failure. + * + * Returns: number of characters written to buf or -errno on failure + */ +static ssize_t query_data(char *buf, size_t buf_len, + char *query, size_t query_len) +{ + char *out; + const char *key; + struct label_it i; + struct aa_label *label, *curr; + struct aa_profile *profile; + struct aa_data *data; + u32 bytes, blocks; + __le32 outle32; + + if (!query_len) + return -EINVAL; /* need a query */ + + key = query + strnlen(query, query_len) + 1; + if (key + 1 >= query + query_len) + return -EINVAL; /* not enough space for a non-empty key */ + if (key + strnlen(key, query + query_len - key) >= query + query_len) + return -EINVAL; /* must end with NUL */ + + if (buf_len < sizeof(bytes) + sizeof(blocks)) + return -EINVAL; /* not enough space */ + + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, query, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); + + /* We are going to leave space for two numbers. The first is the total + * number of bytes we are writing after the first number. This is so + * users can read the full output without reallocation. + * + * The second number is the number of data blocks we're writing. An + * application might be confined by multiple policies having data in + * the same key. + */ + memset(buf, 0, sizeof(bytes) + sizeof(blocks)); + out = buf + sizeof(bytes) + sizeof(blocks); + + blocks = 0; + label_for_each_confined(i, label, profile) { + if (!profile->data) + continue; + + data = rhashtable_lookup_fast(profile->data, &key, + profile->data->p); + + if (data) { + if (out + sizeof(outle32) + data->size > buf + + buf_len) { + aa_put_label(label); + return -EINVAL; /* not enough space */ + } + outle32 = __cpu_to_le32(data->size); + memcpy(out, &outle32, sizeof(outle32)); + out += sizeof(outle32); + memcpy(out, data->data, data->size); + out += data->size; + blocks++; + } + } + aa_put_label(label); + + outle32 = __cpu_to_le32(out - buf - sizeof(bytes)); + memcpy(buf, &outle32, sizeof(outle32)); + outle32 = __cpu_to_le32(blocks); + memcpy(buf + sizeof(bytes), &outle32, sizeof(outle32)); + + return out - buf; +} + +/** + * query_label - queries a label and writes permissions to buf + * @buf: the resulting permissions string is stored here (NOT NULL) + * @buf_len: size of buf + * @query: binary query string to match against the dfa + * @query_len: size of query + * @view_only: only compute for querier's view + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "LABEL_NAME\0DFA_STRING" where LABEL_NAME is + * the name of the label, in the current namespace, that is to be queried and + * DFA_STRING is a binary string to match against the label(s)'s DFA. + * + * LABEL_NAME must be NUL terminated. DFA_STRING may contain NUL characters + * but must *not* be NUL terminated. + * + * Returns: number of characters written to buf or -errno on failure + */ +static ssize_t query_label(char *buf, size_t buf_len, + char *query, size_t query_len, bool view_only) +{ + struct aa_profile *profile; + struct aa_label *label, *curr; + char *label_name, *match_str; + size_t label_name_len, match_len; + struct aa_perms perms; + struct label_it i; + + if (!query_len) + return -EINVAL; + + label_name = query; + label_name_len = strnlen(query, query_len); + if (!label_name_len || label_name_len == query_len) + return -EINVAL; + + /** + * The extra byte is to account for the null byte between the + * profile name and dfa string. profile_name_len is greater + * than zero and less than query_len, so a byte can be safely + * added or subtracted. + */ + match_str = label_name + label_name_len + 1; + match_len = query_len - label_name_len - 1; + + curr = begin_current_label_crit_section(); + label = aa_label_parse(curr, label_name, GFP_KERNEL, false, false); + end_current_label_crit_section(curr); + if (IS_ERR(label)) + return PTR_ERR(label); + + perms = allperms; + if (view_only) { + label_for_each_in_ns(i, labels_ns(label), label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } else { + label_for_each(i, label, profile) { + profile_query_cb(profile, &perms, match_str, match_len); + } + } + aa_put_label(label); + + return scnprintf(buf, buf_len, + "allow 0x%08x\ndeny 0x%08x\naudit 0x%08x\nquiet 0x%08x\n", + perms.allow, perms.deny, perms.audit, perms.quiet); +} + +/* + * Transaction based IO. + * The file expects a write which triggers the transaction, and then + * possibly a read(s) which collects the result - which is stored in a + * file-local buffer. Once a new write is performed, a new set of results + * are stored in the file-local buffer. + */ +struct multi_transaction { + struct kref count; + ssize_t size; + char data[]; +}; + +#define MULTI_TRANSACTION_LIMIT (PAGE_SIZE - sizeof(struct multi_transaction)) + +static void multi_transaction_kref(struct kref *kref) +{ + struct multi_transaction *t; + + t = container_of(kref, struct multi_transaction, count); + free_page((unsigned long) t); +} + +static struct multi_transaction * +get_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_get(&(t->count)); + + return t; +} + +static void put_multi_transaction(struct multi_transaction *t) +{ + if (t) + kref_put(&(t->count), multi_transaction_kref); +} + +/* does not increment @new's count */ +static void multi_transaction_set(struct file *file, + struct multi_transaction *new, size_t n) +{ + struct multi_transaction *old; + + AA_BUG(n > MULTI_TRANSACTION_LIMIT); + + new->size = n; + spin_lock(&file->f_lock); + old = (struct multi_transaction *) file->private_data; + file->private_data = new; + spin_unlock(&file->f_lock); + put_multi_transaction(old); +} + +static struct multi_transaction *multi_transaction_new(struct file *file, + const char __user *buf, + size_t size) +{ + struct multi_transaction *t; + + if (size > MULTI_TRANSACTION_LIMIT - 1) + return ERR_PTR(-EFBIG); + + t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL); + if (!t) + return ERR_PTR(-ENOMEM); + kref_init(&t->count); + if (copy_from_user(t->data, buf, size)) { + put_multi_transaction(t); + return ERR_PTR(-EFAULT); + } + + return t; +} + +static ssize_t multi_transaction_read(struct file *file, char __user *buf, + size_t size, loff_t *pos) +{ + struct multi_transaction *t; + ssize_t ret; + + spin_lock(&file->f_lock); + t = get_multi_transaction(file->private_data); + spin_unlock(&file->f_lock); + + if (!t) + return 0; + + ret = simple_read_from_buffer(buf, size, pos, t->data, t->size); + put_multi_transaction(t); + + return ret; +} + +static int multi_transaction_release(struct inode *inode, struct file *file) +{ + put_multi_transaction(file->private_data); + + return 0; +} + +#define QUERY_CMD_LABEL "label\0" +#define QUERY_CMD_LABEL_LEN 6 +#define QUERY_CMD_PROFILE "profile\0" +#define QUERY_CMD_PROFILE_LEN 8 +#define QUERY_CMD_LABELALL "labelall\0" +#define QUERY_CMD_LABELALL_LEN 9 +#define QUERY_CMD_DATA "data\0" +#define QUERY_CMD_DATA_LEN 5 + +/** + * aa_write_access - generic permissions and data query + * @file: pointer to open apparmorfs/access file + * @ubuf: user buffer containing the complete query string (NOT NULL) + * @count: size of ubuf + * @ppos: position in the file (MUST BE ZERO) + * + * Allows for one permissions or data query per open(), write(), and read() + * sequence. The only queries currently supported are label-based queries for + * permissions or data. + * + * For permissions queries, ubuf must begin with "label\0", followed by the + * profile query specific format described in the query_label() function + * documentation. + * + * For data queries, ubuf must have the form "data\0<LABEL>\0<KEY>\0", where + * <LABEL> is the name of the security confinement context and <KEY> is the + * name of the data to retrieve. + * + * Returns: number of bytes written or -errno on failure + */ +static ssize_t aa_write_access(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct multi_transaction *t; + ssize_t len; + + if (*ppos) + return -ESPIPE; + + t = multi_transaction_new(file, ubuf, count); + if (IS_ERR(t)) + return PTR_ERR(t); + + if (count > QUERY_CMD_PROFILE_LEN && + !memcmp(t->data, QUERY_CMD_PROFILE, QUERY_CMD_PROFILE_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_PROFILE_LEN, + count - QUERY_CMD_PROFILE_LEN, true); + } else if (count > QUERY_CMD_LABEL_LEN && + !memcmp(t->data, QUERY_CMD_LABEL, QUERY_CMD_LABEL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABEL_LEN, + count - QUERY_CMD_LABEL_LEN, true); + } else if (count > QUERY_CMD_LABELALL_LEN && + !memcmp(t->data, QUERY_CMD_LABELALL, + QUERY_CMD_LABELALL_LEN)) { + len = query_label(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_LABELALL_LEN, + count - QUERY_CMD_LABELALL_LEN, false); + } else if (count > QUERY_CMD_DATA_LEN && + !memcmp(t->data, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { + len = query_data(t->data, MULTI_TRANSACTION_LIMIT, + t->data + QUERY_CMD_DATA_LEN, + count - QUERY_CMD_DATA_LEN); + } else + len = -EINVAL; + + if (len < 0) { + put_multi_transaction(t); + return len; + } + + multi_transaction_set(file, t, len); + + return count; +} + +static const struct file_operations aa_sfs_access = { + .write = aa_write_access, + .read = multi_transaction_read, + .release = multi_transaction_release, + .llseek = generic_file_llseek, +}; + +static int aa_sfs_seq_show(struct seq_file *seq, void *v) +{ + struct aa_sfs_entry *fs_file = seq->private; if (!fs_file) return 0; switch (fs_file->v_type) { - case AA_FS_TYPE_BOOLEAN: - seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + case AA_SFS_TYPE_BOOLEAN: + seq_printf(seq, "%s\n", str_yes_no(fs_file->v.boolean)); break; - case AA_FS_TYPE_STRING: + case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); break; - case AA_FS_TYPE_U64: + case AA_SFS_TYPE_U64: seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: - /* Ignore unpritable entry types. */ + /* Ignore unprintable entry types. */ break; } return 0; } -static int aa_fs_seq_open(struct inode *inode, struct file *file) +static int aa_sfs_seq_open(struct inode *inode, struct file *file) { - return single_open(file, aa_fs_seq_show, inode->i_private); + return single_open(file, aa_sfs_seq_show, inode->i_private); } -const struct file_operations aa_fs_seq_file_ops = { +const struct file_operations aa_sfs_seq_file_ops = { .owner = THIS_MODULE, - .open = aa_fs_seq_open, + .open = aa_sfs_seq_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; +/* + * profile based file operations + * policy/profiles/XXXX/profiles/ * + */ + +#define SEQ_PROFILE_FOPS(NAME) \ +static int seq_profile_ ##NAME ##_open(struct inode *inode, struct file *file)\ +{ \ + return seq_profile_open(inode, file, seq_profile_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_profile_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_profile_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_profile_release, \ +} \ + +static int seq_profile_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) +{ + struct aa_proxy *proxy = aa_get_proxy(inode->i_private); + int error = single_open(file, show, proxy); + + if (error) { + file->private_data = NULL; + aa_put_proxy(proxy); + } + + return error; +} + +static int seq_profile_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = (struct seq_file *) file->private_data; + if (seq) + aa_put_proxy(seq->private); + return single_release(inode, file); +} + +static int seq_profile_name_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); + seq_printf(seq, "%s\n", profile->base.name); + aa_put_label(label); + + return 0; +} + +static int seq_profile_mode_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); + seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); + aa_put_label(label); + + return 0; +} + +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.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); + aa_put_label(label); + + return 0; +} + +static int seq_profile_hash_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); + unsigned int i, size = aa_hash_size(); + + if (profile->hash) { + for (i = 0; i < size; i++) + seq_printf(seq, "%.2x", profile->hash[i]); + seq_putc(seq, '\n'); + } + aa_put_label(label); + + return 0; +} + +SEQ_PROFILE_FOPS(name); +SEQ_PROFILE_FOPS(mode); +SEQ_PROFILE_FOPS(attach); +SEQ_PROFILE_FOPS(hash); + +/* + * namespace based files + * several root files and + * policy/ * + */ + +#define SEQ_NS_FOPS(NAME) \ +static int seq_ns_ ##NAME ##_open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, seq_ns_ ##NAME ##_show, inode->i_private); \ +} \ + \ +static const struct file_operations seq_ns_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_ns_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} \ + +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", str_yes_no(label->size > 1)); + end_current_label_crit_section(label); + + return 0; +} + +static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) +{ + struct aa_label *label; + struct aa_profile *profile; + struct label_it it; + int count = 1; + + label = begin_current_label_crit_section(); + + if (label->size > 1) { + label_for_each(it, label, profile) + if (profile->ns != labels_ns(label)) { + count++; + break; + } + } + + seq_printf(seq, "%s\n", str_yes_no(count > 1)); + end_current_label_crit_section(label); + + return 0; +} + +static int seq_ns_level_show(struct seq_file *seq, void *v) +{ + struct aa_label *label; + + label = begin_current_label_crit_section(); + seq_printf(seq, "%d\n", labels_ns(label)->level); + end_current_label_crit_section(label); + + return 0; +} + +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", 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)\ +{ \ + return seq_rawdata_open(inode, file, seq_rawdata_ ##NAME ##_show); \ +} \ + \ +static const struct file_operations seq_rawdata_ ##NAME ##_fops = { \ + .owner = THIS_MODULE, \ + .open = seq_rawdata_ ##NAME ##_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = seq_rawdata_release, \ +} \ + +static int seq_rawdata_open(struct inode *inode, struct file *file, + int (*show)(struct seq_file *, void *)) +{ + struct aa_loaddata *data = __aa_get_loaddata(inode->i_private); + int error; + + if (!data) + /* lost race this ent is being reaped */ + return -ENOENT; + + error = single_open(file, show, data); + if (error) { + AA_BUG(file->private_data && + ((struct seq_file *)file->private_data)->private); + aa_put_loaddata(data); + } + + return error; +} + +static int seq_rawdata_release(struct inode *inode, struct file *file) +{ + struct seq_file *seq = (struct seq_file *) file->private_data; + + if (seq) + aa_put_loaddata(seq->private); + + return single_release(inode, file); +} + +static int seq_rawdata_abi_show(struct seq_file *seq, void *v) +{ + struct aa_loaddata *data = seq->private; + + seq_printf(seq, "v%d\n", data->abi); + + return 0; +} + +static int seq_rawdata_revision_show(struct seq_file *seq, void *v) +{ + struct aa_loaddata *data = seq->private; + + seq_printf(seq, "%ld\n", data->revision); + + return 0; +} + +static int seq_rawdata_hash_show(struct seq_file *seq, void *v) +{ + struct aa_loaddata *data = seq->private; + unsigned int i, size = aa_hash_size(); + + if (data->hash) { + for (i = 0; i < size; i++) + seq_printf(seq, "%.2x", data->hash[i]); + seq_putc(seq, '\n'); + } + + 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 rawdata_f_data *private = file->private_data; + + 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) +{ + rawdata_f_data_free(file->private_data); + + return 0; +} + +static int rawdata_open(struct inode *inode, struct file *file) +{ + int error; + struct aa_loaddata *loaddata; + struct rawdata_f_data *private; + + if (!aa_current_policy_view_capable(NULL)) + return -EACCES; + + 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 = { + .open = rawdata_open, + .read = rawdata_read, + .llseek = generic_file_llseek, + .release = rawdata_release, +}; + +static void remove_rawdata_dents(struct aa_loaddata *rawdata) +{ + int i; + + for (i = 0; i < AAFS_LOADDATA_NDENTS; i++) { + if (!IS_ERR_OR_NULL(rawdata->dents[i])) { + /* no refcounts on i_private */ + aafs_remove(rawdata->dents[i]); + rawdata->dents[i] = NULL; + } + } +} + +void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata) +{ + AA_BUG(rawdata->ns && !mutex_is_locked(&rawdata->ns->lock)); + + if (rawdata->ns) { + remove_rawdata_dents(rawdata); + list_del_init(&rawdata->list); + aa_put_ns(rawdata->ns); + rawdata->ns = NULL; + } +} + +int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata) +{ + struct dentry *dent, *dir; + + AA_BUG(!ns); + AA_BUG(!rawdata); + AA_BUG(!mutex_is_locked(&ns->lock)); + AA_BUG(!ns_subdata_dir(ns)); + + /* + * just use ns revision dir was originally created at. This is + * under ns->lock and if load is successful revision will be + * bumped and is guaranteed to be unique + */ + rawdata->name = kasprintf(GFP_KERNEL, "%ld", ns->revision); + if (!rawdata->name) + return -ENOMEM; + + dir = aafs_create_dir(rawdata->name, ns_subdata_dir(ns)); + if (IS_ERR(dir)) + /* ->name freed when rawdata freed */ + return PTR_ERR(dir); + rawdata->dents[AAFS_LOADDATA_DIR] = dir; + + dent = aafs_create_file("abi", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_abi_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_ABI] = dent; + + dent = aafs_create_file("revision", S_IFREG | 0444, dir, rawdata, + &seq_rawdata_revision_fops); + if (IS_ERR(dent)) + goto fail; + rawdata->dents[AAFS_LOADDATA_REVISION] = dent; + + if (aa_g_hash_policy) { + 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)) + goto fail; + rawdata->dents[AAFS_LOADDATA_DATA] = dent; + d_inode(dent)->i_size = rawdata->size; + + rawdata->ns = aa_get_ns(ns); + list_add(&rawdata->list, &ns->rawdata_list); + /* no refcount on inode rawdata */ + + return 0; + +fail: + remove_rawdata_dents(rawdata); + + return PTR_ERR(dent); +} +#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + + +/** fns to setup dynamic per profile/namespace files **/ + +/* + * + * Requires: @profile->ns->lock held + */ +void __aafs_profile_rmdir(struct aa_profile *profile) +{ + struct aa_profile *child; + int i; + + if (!profile) + return; + + list_for_each_entry(child, &profile->base.profiles, base.list) + __aafs_profile_rmdir(child); + + for (i = AAFS_PROF_SIZEOF - 1; i >= 0; --i) { + struct aa_proxy *proxy; + if (!profile->dents[i]) + continue; + + proxy = d_inode(profile->dents[i])->i_private; + aafs_remove(profile->dents[i]); + aa_put_proxy(proxy); + profile->dents[i] = NULL; + } +} + +/* + * + * Requires: @old->ns->lock held + */ +void __aafs_profile_migrate_dents(struct aa_profile *old, + struct aa_profile *new) +{ + int i; + + AA_BUG(!old); + AA_BUG(!new); + AA_BUG(!mutex_is_locked(&profiles_ns(old)->lock)); + + for (i = 0; i < AAFS_PROF_SIZEOF; i++) { + new->dents[i] = old->dents[i]; + if (new->dents[i]) { + struct inode *inode = d_inode(new->dents[i]); + + inode_set_mtime_to_ts(inode, + inode_set_ctime_current(inode)); + } + old->dents[i] = NULL; + } +} + +static struct dentry *create_profile_file(struct dentry *dir, const char *name, + struct aa_profile *profile, + const struct file_operations *fops) +{ + struct aa_proxy *proxy = aa_get_proxy(profile->label.proxy); + struct dentry *dent; + + dent = aafs_create_file(name, S_IFREG | 0444, dir, proxy, fops); + if (IS_ERR(dent)) + aa_put_proxy(proxy); + + return dent; +} + +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY +static int profile_depth(struct aa_profile *profile) +{ + int depth = 0; + + rcu_read_lock(); + for (depth = 0; profile; profile = rcu_access_pointer(profile->parent)) + depth++; + rcu_read_unlock(); + + return depth; +} + +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--) { + strcpy(s, "../../"); + s += 6; + size -= 6; + } + + error = snprintf(s, size, "raw_data/%s/%s", dirname, fname); + if (error >= size || error < 0) { + kfree(buffer); + return ERR_PTR(-ENAMETOOLONG); + } + + 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 + */ +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) +{ + struct aa_profile *child; + struct dentry *dent = NULL, *dir; + int error; + + AA_BUG(!profile); + AA_BUG(!mutex_is_locked(&profiles_ns(profile)->lock)); + + if (!parent) { + struct aa_profile *p; + p = aa_deref_parent(profile); + dent = prof_dir(p); + 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)) + goto fail; + prof_child_dir(p) = parent = dent; + } + + if (!profile->dirname) { + int len, id_len; + len = mangle_name(profile->base.name, NULL); + id_len = snprintf(NULL, 0, ".%ld", profile->ns->uniq_id); + + profile->dirname = kmalloc(len + id_len + 1, GFP_KERNEL); + if (!profile->dirname) { + error = -ENOMEM; + goto fail2; + } + + mangle_name(profile->base.name, profile->dirname); + sprintf(profile->dirname + len, ".%ld", profile->ns->uniq_id++); + } + + dent = aafs_create_dir(profile->dirname, parent); + if (IS_ERR(dent)) + goto fail; + prof_dir(profile) = dir = dent; + + dent = create_profile_file(dir, "name", profile, + &seq_profile_name_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_NAME] = dent; + + dent = create_profile_file(dir, "mode", profile, + &seq_profile_mode_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_MODE] = dent; + + dent = create_profile_file(dir, "attach", profile, + &seq_profile_attach_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_ATTACH] = dent; + + if (profile->hash) { + 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) { + 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; + + 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)); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aafs_profile_rmdir(profile); + + return error; +} + +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 */ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = aa_may_manage_policy(current_cred(), label, NULL, + AA_MAY_LOAD_POLICY); + end_current_label_crit_section(label); + if (error) + return ERR_PTR(error); + + parent = aa_get_ns(dir->i_private); + AA_BUG(d_inode(ns_subns_dir(parent)) != dir); + + /* we have to unlock and then relock to get locking order right + * for pin_fs + */ + inode_unlock(dir); + error = simple_pin_fs(&aafs_ops, &aafs_mnt, &aafs_count); + mutex_lock_nested(&parent->lock, parent->level); + inode_lock_nested(dir, I_MUTEX_PARENT); + if (error) + goto out; + + error = __aafs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL, + NULL, NULL, NULL); + if (error) + goto out_pin; + + ns = __aa_find_or_create_ns(parent, READ_ONCE(dentry->d_name.name), + dentry); + if (IS_ERR(ns)) { + error = PTR_ERR(ns); + ns = NULL; + } + + aa_put_ns(ns); /* list ref remains */ +out_pin: + if (error) + simple_release_fs(&aafs_mnt, &aafs_count); +out: + mutex_unlock(&parent->lock); + aa_put_ns(parent); + + return ERR_PTR(error); +} + +static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) +{ + struct aa_ns *ns, *parent; + /* TODO: improve permission check */ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + 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); + /* rmdir calls the generic securityfs functions to remove files + * from the apparmor dir. It is up to the apparmor ns locking + * to avoid races. + */ + inode_unlock(dir); + inode_unlock(dentry->d_inode); + + 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) { + error = -ENOENT; + goto out; + } + AA_BUG(ns_dir(ns) != dentry); + + __aa_remove_ns(ns); + aa_put_ns(ns); + +out: + mutex_unlock(&parent->lock); + inode_lock_nested(dir, I_MUTEX_PARENT); + inode_lock(dentry->d_inode); + aa_put_ns(parent); + + return error; +} + +static const struct inode_operations ns_dir_inode_operations = { + .lookup = simple_lookup, + .mkdir = ns_mkdir_op, + .rmdir = ns_rmdir_op, +}; + +static void __aa_fs_list_remove_rawdata(struct aa_ns *ns) +{ + struct aa_loaddata *ent, *tmp; + + AA_BUG(!mutex_is_locked(&ns->lock)); + + list_for_each_entry_safe(ent, tmp, &ns->rawdata_list, list) + __aa_fs_remove_rawdata(ent); +} + +/* + * + * Requires: @ns->lock held + */ +void __aafs_ns_rmdir(struct aa_ns *ns) +{ + struct aa_ns *sub; + struct aa_profile *child; + int i; + + if (!ns) + return; + AA_BUG(!mutex_is_locked(&ns->lock)); + + list_for_each_entry(child, &ns->base.profiles, base.list) + __aafs_profile_rmdir(child); + + list_for_each_entry(sub, &ns->sub_ns, base.list) { + mutex_lock_nested(&sub->lock, sub->level); + __aafs_ns_rmdir(sub); + mutex_unlock(&sub->lock); + } + + __aa_fs_list_remove_rawdata(ns); + + if (ns_subns_dir(ns)) { + sub = d_inode(ns_subns_dir(ns))->i_private; + aa_put_ns(sub); + } + if (ns_subload(ns)) { + sub = d_inode(ns_subload(ns))->i_private; + aa_put_ns(sub); + } + if (ns_subreplace(ns)) { + sub = d_inode(ns_subreplace(ns))->i_private; + aa_put_ns(sub); + } + if (ns_subremove(ns)) { + sub = d_inode(ns_subremove(ns))->i_private; + aa_put_ns(sub); + } + if (ns_subrevision(ns)) { + sub = d_inode(ns_subrevision(ns))->i_private; + aa_put_ns(sub); + } + + for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { + aafs_remove(ns->dents[i]); + ns->dents[i] = NULL; + } +} + +/* assumes cleanup in caller */ +static int __aafs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir) +{ + struct dentry *dent; + + AA_BUG(!ns); + AA_BUG(!dir); + + dent = aafs_create_dir("profiles", dir); + if (IS_ERR(dent)) + return PTR_ERR(dent); + ns_subprofs_dir(ns) = dent; + + dent = aafs_create_dir("raw_data", dir); + if (IS_ERR(dent)) + return PTR_ERR(dent); + ns_subdata_dir(ns) = dent; + + dent = aafs_create_file("revision", 0444, dir, ns, + &aa_fs_ns_revision_fops); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subrevision(ns) = dent; + + dent = aafs_create_file(".load", 0640, dir, ns, + &aa_fs_profile_load); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subload(ns) = dent; + + dent = aafs_create_file(".replace", 0640, dir, ns, + &aa_fs_profile_replace); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subreplace(ns) = dent; + + dent = aafs_create_file(".remove", 0640, dir, ns, + &aa_fs_profile_remove); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subremove(ns) = dent; + + /* use create_dentry so we can supply private data */ + dent = aafs_create("namespaces", S_IFDIR | 0755, dir, ns, NULL, NULL, + &ns_dir_inode_operations); + if (IS_ERR(dent)) + return PTR_ERR(dent); + aa_get_ns(ns); + ns_subns_dir(ns) = dent; + + return 0; +} + +/* + * Requires: @ns->lock held + */ +int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name, + struct dentry *dent) +{ + struct aa_ns *sub; + struct aa_profile *child; + struct dentry *dir; + int error; + + AA_BUG(!ns); + AA_BUG(!parent); + AA_BUG(!mutex_is_locked(&ns->lock)); + + if (!name) + name = ns->base.name; + + if (!dent) { + /* create ns dir if it doesn't already exist */ + dent = aafs_create_dir(name, parent); + if (IS_ERR(dent)) + goto fail; + } else + dget(dent); + ns_dir(ns) = dir = dent; + error = __aafs_ns_mkdir_entries(ns, dir); + if (error) + goto fail2; + + /* profiles */ + list_for_each_entry(child, &ns->base.profiles, base.list) { + error = __aafs_profile_mkdir(child, ns_subprofs_dir(ns)); + if (error) + goto fail2; + } + + /* subnamespaces */ + list_for_each_entry(sub, &ns->sub_ns, base.list) { + mutex_lock_nested(&sub->lock, sub->level); + error = __aafs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL); + mutex_unlock(&sub->lock); + if (error) + goto fail2; + } + + return 0; + +fail: + error = PTR_ERR(dent); + +fail2: + __aafs_ns_rmdir(ns); + + return error; +} + +/** + * __next_ns - find the next namespace to list + * @root: root namespace to stop search at (NOT NULL) + * @ns: current ns position (NOT NULL) + * + * Find the next namespace from @ns under @root and handle all locking needed + * while switching current namespace. + * + * Returns: next namespace or NULL if at last namespace under @root + * Requires: ns->parent->lock to be held + * NOTE: will not unlock root->lock + */ +static struct aa_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_nested(&next->lock, next->level); + return next; + } + + /* check if the next ns is a sibling, parent, gp, .. */ + parent = ns->parent; + while (ns != root) { + mutex_unlock(&ns->lock); + next = list_next_entry(ns, base.list); + if (!list_entry_is_head(next, &parent->sub_ns, base.list)) { + mutex_lock_nested(&next->lock, next->level); + return next; + } + ns = parent; + parent = parent->parent; + } + + return NULL; +} + +/** + * __first_profile - find the first profile in a namespace + * @root: namespace that is root of profiles being displayed (NOT NULL) + * @ns: namespace to start in (NOT NULL) + * + * Returns: unrefcounted profile or NULL if no profile + * Requires: profile->ns.lock to be held + */ +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, + struct aa_profile, base.list); + } + return NULL; +} + +/** + * __next_profile - step to the next profile in a profile tree + * @p: current profile in tree (NOT NULL) + * + * Perform a depth first traversal on the profile tree in a namespace + * + * Returns: next profile or NULL if done + * Requires: profile->ns.lock to be held + */ +static struct aa_profile *__next_profile(struct aa_profile *p) +{ + struct aa_profile *parent; + struct aa_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), + base.list); + + /* is next profile a sibling, parent sibling, gp, sibling, .. */ + parent = rcu_dereference_protected(p->parent, + mutex_is_locked(&p->ns->lock)); + while (parent) { + p = list_next_entry(p, base.list); + if (!list_entry_is_head(p, &parent->base.profiles, base.list)) + return p; + p = parent; + parent = rcu_dereference_protected(parent->parent, + mutex_is_locked(&parent->ns->lock)); + } + + /* is next another profile in the namespace */ + p = list_next_entry(p, base.list); + if (!list_entry_is_head(p, &ns->base.profiles, base.list)) + return p; + + return NULL; +} + +/** + * next_profile - step to the next profile in where ever it may be + * @root: root namespace (NOT NULL) + * @profile: current profile (NOT NULL) + * + * Returns: next profile or NULL if there isn't one + */ +static struct aa_profile *next_profile(struct aa_ns *root, + struct aa_profile *profile) +{ + struct aa_profile *next = __next_profile(profile); + if (next) + return next; + + /* finished all profiles in namespace move to next namespace */ + return __first_profile(root, __next_ns(root, profile->ns)); +} + +/** + * p_start - start a depth first traversal of profile tree + * @f: seq_file to fill + * @pos: current position + * + * Returns: first profile under current namespace or NULL if none found + * + * acquires first ns->lock + */ +static void *p_start(struct seq_file *f, loff_t *pos) +{ + struct aa_profile *profile = NULL; + struct aa_ns *root = aa_get_current_ns(); + loff_t l = *pos; + f->private = root; + + /* find the first profile */ + mutex_lock_nested(&root->lock, root->level); + profile = __first_profile(root, root); + + /* skip to position */ + for (; profile && l > 0; l--) + profile = next_profile(root, profile); + + return profile; +} + +/** + * p_next - read the next profile entry + * @f: seq_file to fill + * @p: profile previously returned + * @pos: current position + * + * Returns: next profile after @p or NULL if none + * + * may acquire/release locks in namespace tree as necessary + */ +static void *p_next(struct seq_file *f, void *p, loff_t *pos) +{ + struct aa_profile *profile = p; + struct aa_ns *ns = f->private; + (*pos)++; + + return next_profile(ns, profile); +} + +/** + * p_stop - stop depth first traversal + * @f: seq_file we are filling + * @p: the last profile written + * + * Release all locking done by p_start/p_next on namespace tree + */ +static void p_stop(struct seq_file *f, void *p) +{ + struct aa_profile *profile = p; + struct aa_ns *root = f->private, *ns; + + if (profile) { + for (ns = profile->ns; ns && ns != root; ns = ns->parent) + mutex_unlock(&ns->lock); + } + mutex_unlock(&root->lock); + aa_put_ns(root); +} + +/** + * seq_show_profile - show a profile entry + * @f: seq_file to file + * @p: current position (profile) (NOT NULL) + * + * Returns: error on failure + */ +static int seq_show_profile(struct seq_file *f, void *p) +{ + struct aa_profile *profile = (struct aa_profile *)p; + struct aa_ns *root = f->private; + + aa_label_seq_xprint(f, root, &profile->label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS, GFP_KERNEL); + seq_putc(f, '\n'); + + return 0; +} + +static const struct seq_operations aa_sfs_profiles_op = { + .start = p_start, + .next = p_next, + .stop = p_stop, + .show = seq_show_profile, +}; + +static int profiles_open(struct inode *inode, struct file *file) +{ + if (!aa_current_policy_view_capable(NULL)) + return -EACCES; + + return seq_open(file, &aa_sfs_profiles_op); +} + +static int profiles_release(struct inode *inode, struct file *file) +{ + return seq_release(inode, file); +} + +static const struct file_operations aa_sfs_profiles_fops = { + .open = profiles_open, + .read = seq_read, + .llseek = seq_lseek, + .release = profiles_release, +}; + + /** Base file system setup **/ +static struct aa_sfs_entry aa_sfs_entry_file[] = { + AA_SFS_FILE_STRING("mask", + "create read write exec append mmap_exec link lock"), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry_ptrace[] = { + AA_SFS_FILE_STRING("mask", "read trace"), + { } +}; + +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", 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"), + { } +}; -static struct aa_fs_entry aa_fs_entry_file[] = { - AA_FS_FILE_STRING("mask", "create read write exec append mmap_exec " \ - "link lock"), +static struct aa_sfs_entry aa_sfs_entry_query_label[] = { + AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), + AA_SFS_FILE_BOOLEAN("data", 1), + AA_SFS_FILE_BOOLEAN("multi_transaction", 1), { } }; -static struct aa_fs_entry aa_fs_entry_domain[] = { - AA_FS_FILE_BOOLEAN("change_hat", 1), - AA_FS_FILE_BOOLEAN("change_hatv", 1), - AA_FS_FILE_BOOLEAN("change_onexec", 1), - AA_FS_FILE_BOOLEAN("change_profile", 1), +static struct aa_sfs_entry aa_sfs_entry_query[] = { + AA_SFS_DIR("label", aa_sfs_entry_query_label), { } }; -static struct aa_fs_entry aa_fs_entry_features[] = { - AA_FS_DIR("domain", aa_fs_entry_domain), - AA_FS_DIR("file", aa_fs_entry_file), - AA_FS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), - AA_FS_DIR("rlimit", aa_fs_entry_rlimit), +static struct aa_sfs_entry aa_sfs_entry_io_uring[] = { + AA_SFS_FILE_STRING("mask", "sqpoll override_creds"), { } }; -static struct aa_fs_entry aa_fs_entry_apparmor[] = { - AA_FS_FILE_FOPS(".load", 0640, &aa_fs_profile_load), - AA_FS_FILE_FOPS(".replace", 0640, &aa_fs_profile_replace), - AA_FS_FILE_FOPS(".remove", 0640, &aa_fs_profile_remove), - AA_FS_DIR("features", aa_fs_entry_features), +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_fs_entry aa_fs_entry = - AA_FS_DIR("apparmor", aa_fs_entry_apparmor); +static struct aa_sfs_entry aa_sfs_entry_apparmor[] = { + 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", 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), + { } +}; + +static struct aa_sfs_entry aa_sfs_entry = + AA_SFS_DIR("apparmor", aa_sfs_entry_apparmor); /** - * aafs_create_file - create a file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to build an entry for (NOT NULL) + * entry_create_file - create a file entry in the apparmor securityfs + * @fs_file: aa_sfs_entry to build an entry for (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_file to remove entries created with this fn. + * Use entry_remove_file to remove entries created with this fn. */ -static int __init aafs_create_file(struct aa_fs_entry *fs_file, - struct dentry *parent) +static int __init entry_create_file(struct aa_sfs_entry *fs_file, + struct dentry *parent) { int error = 0; @@ -240,31 +2479,31 @@ static int __init aafs_create_file(struct aa_fs_entry *fs_file, return error; } +static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir); /** - * aafs_create_dir - recursively create a directory entry in the securityfs - * @fs_dir: aa_fs_entry (and all child entries) to build (NOT NULL) + * entry_create_dir - recursively create a directory entry in the securityfs + * @fs_dir: aa_sfs_entry (and all child entries) to build (NOT NULL) * @parent: the parent dentry in the securityfs * - * Use aafs_remove_dir to remove entries created with this fn. + * Use entry_remove_dir to remove entries created with this fn. */ -static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, - struct dentry *parent) +static int __init entry_create_dir(struct aa_sfs_entry *fs_dir, + struct dentry *parent) { + struct aa_sfs_entry *fs_file; + struct dentry *dir; int error; - struct aa_fs_entry *fs_file; - fs_dir->dentry = securityfs_create_dir(fs_dir->name, parent); - if (IS_ERR(fs_dir->dentry)) { - error = PTR_ERR(fs_dir->dentry); - fs_dir->dentry = NULL; - goto failed; - } + dir = securityfs_create_dir(fs_dir->name, parent); + if (IS_ERR(dir)) + return PTR_ERR(dir); + fs_dir->dentry = dir; - for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { - if (fs_file->v_type == AA_FS_TYPE_DIR) - error = aafs_create_dir(fs_file, fs_dir->dentry); + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_SFS_TYPE_DIR) + error = entry_create_dir(fs_file, fs_dir->dentry); else - error = aafs_create_file(fs_file, fs_dir->dentry); + error = entry_create_file(fs_file, fs_dir->dentry); if (error) goto failed; } @@ -272,14 +2511,16 @@ static int __init aafs_create_dir(struct aa_fs_entry *fs_dir, return 0; failed: + entry_remove_dir(fs_dir); + return error; } /** - * aafs_remove_file - drop a single file entry in the apparmor securityfs - * @fs_file: aa_fs_entry to detach from the securityfs (NOT NULL) + * entry_remove_file - drop a single file entry in the apparmor securityfs + * @fs_file: aa_sfs_entry to detach from the securityfs (NOT NULL) */ -static void __init aafs_remove_file(struct aa_fs_entry *fs_file) +static void __init entry_remove_file(struct aa_sfs_entry *fs_file) { if (!fs_file->dentry) return; @@ -289,21 +2530,21 @@ static void __init aafs_remove_file(struct aa_fs_entry *fs_file) } /** - * aafs_remove_dir - recursively drop a directory entry from the securityfs - * @fs_dir: aa_fs_entry (and all child entries) to detach (NOT NULL) + * entry_remove_dir - recursively drop a directory entry from the securityfs + * @fs_dir: aa_sfs_entry (and all child entries) to detach (NOT NULL) */ -static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) +static void __init entry_remove_dir(struct aa_sfs_entry *fs_dir) { - struct aa_fs_entry *fs_file; + struct aa_sfs_entry *fs_file; - for (fs_file = fs_dir->v.files; fs_file->name; ++fs_file) { - if (fs_file->v_type == AA_FS_TYPE_DIR) - aafs_remove_dir(fs_file); + for (fs_file = fs_dir->v.files; fs_file && fs_file->name; ++fs_file) { + if (fs_file->v_type == AA_SFS_TYPE_DIR) + entry_remove_dir(fs_file); else - aafs_remove_file(fs_file); + entry_remove_file(fs_file); } - aafs_remove_file(fs_dir); + entry_remove_file(fs_dir); } /** @@ -313,9 +2554,99 @@ static void __init aafs_remove_dir(struct aa_fs_entry *fs_dir) */ void __init aa_destroy_aafs(void) { - aafs_remove_dir(&aa_fs_entry); + entry_remove_dir(&aa_sfs_entry); } + +#define NULL_FILE_NAME ".null" +struct path aa_null; + +static int aa_mk_null_file(struct dentry *parent) +{ + struct vfsmount *mount = NULL; + struct dentry *dentry; + struct inode *inode; + int count = 0; + int error = simple_pin_fs(parent->d_sb->s_type, &mount, &count); + + if (error) + return error; + + inode_lock(d_inode(parent)); + dentry = lookup_noperm(&QSTR(NULL_FILE_NAME), parent); + if (IS_ERR(dentry)) { + error = PTR_ERR(dentry); + goto out; + } + inode = new_inode(parent->d_inode->i_sb); + if (!inode) { + error = -ENOMEM; + goto out1; + } + + inode->i_ino = get_next_ino(); + inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO; + simple_inode_init_ts(inode); + init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, + MKDEV(MEM_MAJOR, 3)); + d_instantiate(dentry, inode); + aa_null.dentry = dget(dentry); + aa_null.mnt = mntget(mount); + + error = 0; + +out1: + dput(dentry); +out: + inode_unlock(d_inode(parent)); + simple_release_fs(&mount, &count); + return error; +} + + + +static const char *policy_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + 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)); + error = nd_jump_link(&path); + aa_put_ns(ns); + + return ERR_PTR(error); +} + +static int policy_readlink(struct dentry *dentry, char __user *buffer, + int buflen) +{ + char name[32]; + int res; + + 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; +} + +static const struct inode_operations policy_link_iops = { + .readlink = policy_readlink, + .get_link = policy_get_link, +}; + + /** * aa_create_aafs - create the apparmor security filesystem * @@ -323,33 +2654,82 @@ void __init aa_destroy_aafs(void) * * Returns: error on failure */ -static int __init aa_create_aafs(void) +int __init aa_create_aafs(void) { + struct dentry *dent; int error; if (!apparmor_initialized) return 0; - if (aa_fs_entry.dentry) { + if (aa_sfs_entry.dentry) { AA_ERROR("%s: AppArmor securityfs already exists\n", __func__); return -EEXIST; } + /* setup apparmorfs used to virtualize policy/ */ + aafs_mnt = kern_mount(&aafs_ops); + if (IS_ERR(aafs_mnt)) + panic("can't set apparmorfs up\n"); + aafs_mnt->mnt_sb->s_flags &= ~SB_NOUSER; + /* Populate fs tree. */ - error = aafs_create_dir(&aa_fs_entry, NULL); + error = entry_create_dir(&aa_sfs_entry, NULL); if (error) goto error; - /* TODO: add support for apparmorfs_null and apparmorfs_mnt */ + dent = securityfs_create_file(".load", 0666, aa_sfs_entry.dentry, + NULL, &aa_fs_profile_load); + 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)) + 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)) + 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)) + goto dent_error; + ns_subrevision(root_ns) = dent; + + /* policy tree referenced by magic policy symlink */ + 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); + if (error) + goto error; + + /* 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)) + goto dent_error; + + error = aa_mk_null_file(aa_sfs_entry.dentry); + if (error) + goto error; + + /* TODO: add default profile to apparmorfs */ /* Report that AppArmor fs is enabled */ 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 031d2d9dd695..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> @@ -18,60 +14,8 @@ #include "include/apparmor.h" #include "include/audit.h" #include "include/policy.h" - -const char *const op_table[] = { - "null", - - "sysctl", - "capable", - - "unlink", - "mkdir", - "rmdir", - "mknod", - "truncate", - "link", - "symlink", - "rename_src", - "rename_dest", - "chmod", - "chown", - "getattr", - "open", - - "file_perm", - "file_lock", - "file_mmap", - "file_mprotect", - - "create", - "post_create", - "bind", - "connect", - "listen", - "accept", - "sendmsg", - "recvmsg", - "getsockname", - "getpeername", - "getsockopt", - "setsockopt", - "socket_shutdown", - - "ptrace", - - "exec", - "change_hat", - "change_profile", - "change_onexec", - - "setprocattr", - "setrlimit", - - "profile_replace", - "profile_load", - "profile_remove" -}; +#include "include/policy_ns.h" +#include "include/secid.h" const char *const audit_mode_names[] = { "normal", @@ -92,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. * @@ -102,93 +83,94 @@ 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 task_struct *tsk = sa->aad->tsk ? sa->aad->tsk : current; + 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[sa->aad->type]); + audit_log_format(ab, "apparmor=\"%s\"", + aa_audit_type[ad->type]); } - if (sa->aad->op) { - audit_log_format(ab, " operation="); - audit_log_string(ab, op_table[sa->aad->op]); - } + if (ad->op) + audit_log_format(ab, " operation=\"%s\"", ad->op); + + if (ad->class) + audit_log_format(ab, " class=\"%s\"", + ad->class <= AA_CLASS_LAST ? + aa_class_names[ad->class] : + "unknown"); - if (sa->aad->info) { - audit_log_format(ab, " info="); - audit_log_string(ab, sa->aad->info); - if (sa->aad->error) - audit_log_format(ab, " error=%d", sa->aad->error); + if (ad->info) { + audit_log_format(ab, " info=\"%s\"", ad->info); + if (ad->error) + audit_log_format(ab, " error=%d", ad->error); } - if (sa->aad->profile) { - struct aa_profile *profile = sa->aad->profile; - pid_t pid; - rcu_read_lock(); - pid = rcu_dereference(tsk->real_parent)->pid; - rcu_read_unlock(); - audit_log_format(ab, " parent=%d", pid); - if (profile->ns != root_ns) { - audit_log_format(ab, " namespace="); - audit_log_untrustedstring(ab, profile->ns->base.hname); + if (ad->subj_label) { + struct aa_label *label = ad->subj_label; + + if (label_isprofile(label)) { + struct aa_profile *profile = labels_profile(label); + + if (profile->ns != root_ns) { + audit_log_format(ab, " namespace="); + audit_log_untrustedstring(ab, + profile->ns->base.hname); + } + audit_log_format(ab, " profile="); + audit_log_untrustedstring(ab, profile->base.hname); + } else { + audit_log_format(ab, " label="); + aa_label_xaudit(ab, root_ns, label, FLAG_VIEW_SUBNS, + GFP_ATOMIC); } - audit_log_format(ab, " profile="); - audit_log_untrustedstring(ab, profile->base.hname); } - if (sa->aad->name) { + if (ad->name) { audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, sa->aad->name); - } - - if (sa->aad->tsk) { - audit_log_format(ab, " pid=%d comm=", tsk->pid); - audit_log_untrustedstring(ab, tsk->comm); + audit_log_untrustedstring(ab, 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 *)) { - sa->aad->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) - * @gfp: allocation flags to use - * @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, gfp_t gfp, - 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 *)) { - BUG_ON(!profile); + AA_BUG(!profile); if (type == AUDIT_APPARMOR_AUTO) { - if (likely(!sa->aad->error)) { + if (likely(!ad->error)) { if (AUDIT_MODE(profile) != AUDIT_ALL) return 0; type = AUDIT_APPARMOR_AUDIT; @@ -199,23 +181,111 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, } if (AUDIT_MODE(profile) == AUDIT_QUIET || (type == AUDIT_APPARMOR_DENIED && - AUDIT_MODE(profile) == AUDIT_QUIET)) - return sa->aad->error; + AUDIT_MODE(profile) == AUDIT_QUIET_DENIED)) + return ad->error; if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED) type = AUDIT_APPARMOR_KILL; - if (!unconfined(profile)) - sa->aad->profile = profile; + 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; + } + + rule = kzalloc(sizeof(struct aa_audit_rule), gfp); - aa_audit_msg(type, sa, cb); + if (!rule) + return -ENOMEM; - if (sa->aad->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(SIGKILL, NULL, - sa->aad->tsk ? sa->aad->tsk : current); + /* 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; + } - if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) - return complain_error(sa->aad->error); + *vrule = rule; + return 0; +} + +int aa_audit_rule_known(struct audit_krule *rule) +{ + int i; - return sa->aad->error; + 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 887a5e948945..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,20 +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" @@ -27,61 +25,66 @@ */ #include "capability_names.h" +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) { struct common_audit_data *sa = va; + audit_log_format(ab, " capname="); audit_log_untrustedstring(ab, capability_names[sa->u.cap]); } /** * audit_caps - audit a capability - * @profile: profile confining task (NOT NULL) - * @task: task capability test was performed against (NOT NULL) + * @ad: audit data + * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested * @error: error code returned by test * * 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 aa_profile *profile, struct task_struct *task, +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; - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_CAP; - sa.aad = &aad; - sa.u.cap = cap; - sa.aad->tsk = task; - sa.aad->op = OP_CAPABLE; - sa.aad->error = error; + + 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 */ @@ -90,54 +93,131 @@ static int audit_caps(struct aa_profile *profile, struct task_struct *task, /* 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, GFP_ATOMIC, &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 + * @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) +static int profile_capable(struct aa_profile *profile, int cap, + unsigned int opts, struct apparmor_audit_data *ad) { - return cap_raised(profile->caps.allow, cap) ? 0 : -EPERM; + struct aa_ruleset *rules = profile->label.rules[0]; + aa_state_t state; + int error; + + 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 (opts & CAP_OPT_NOAUDIT) { + if (!COMPLAIN_MODE(profile)) + return error; + /* audit the cap request in complain mode but note that it + * should be optional. + */ + ad->info = "optional: no audit"; + } + + return audit_caps(ad, profile, cap, error); } /** * aa_capable - test permission to use capability - * @task: task doing capability test against (NOT NULL) - * @profile: profile confining @task (NOT NULL) + * @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 task_struct *task, struct aa_profile *profile, int cap, - int audit) +int aa_capable(const struct cred *subj_cred, struct aa_label *label, + int cap, unsigned int opts) { - int error = profile_capable(profile, cap); + struct aa_profile *profile; + int error = 0; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE); - if (!audit) { - if (COMPLAIN_MODE(profile)) - return complain_error(error); - return error; + ad.subj_cred = subj_cred; + ad.common.u.cap = cap; + error = fn_for_each_confined(label, profile, + 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; } - return audit_caps(profile, task, cap, error); + /* 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 d5af1d15f26d..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_cxt and - * the aa_task_cxt.profile, both of which are required and are not allowed - * to be NULL. The aa_task_cxt is not reference counted and is unique - * to each cred (which is reference count). The profile pointed to by - * the task_cxt 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_cxt - * @flags: gfp flags for allocation - * - * Returns: allocated buffer or NULL on failure - */ -struct aa_task_cxt *aa_alloc_task_context(gfp_t flags) -{ - return kzalloc(sizeof(struct aa_task_cxt), flags); -} - -/** - * aa_free_task_context - free a task_cxt - * @cxt: task_cxt to free (MAYBE NULL) - */ -void aa_free_task_context(struct aa_task_cxt *cxt) -{ - if (cxt) { - aa_put_profile(cxt->profile); - aa_put_profile(cxt->previous); - aa_put_profile(cxt->onexec); - - kzfree(cxt); - } -} - -/** - * 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_cxt *new, const struct aa_task_cxt *old) -{ - *new = *old; - aa_get_profile(new->profile); - aa_get_profile(new->previous); - aa_get_profile(new->onexec); -} - -/** - * aa_get_task_profile - Get another task's profile - * @task: task to query (NOT NULL) - * - * Returns: counted reference to @task's profile - */ -struct aa_profile *aa_get_task_profile(struct task_struct *task) -{ - struct aa_profile *p; - - rcu_read_lock(); - p = aa_get_profile(__aa_task_profile(task)); - rcu_read_unlock(); - - return p; -} - -/** - * aa_replace_current_profile - replace the current tasks profiles - * @profile: new profile (NOT NULL) - * - * Returns: 0 or error on failure - */ -int aa_replace_current_profile(struct aa_profile *profile) -{ - struct aa_task_cxt *cxt = current_cxt(); - struct cred *new; - BUG_ON(!profile); - - if (cxt->profile == profile) - return 0; - - new = prepare_creds(); - if (!new) - return -ENOMEM; - - cxt = cred_cxt(new); - if (unconfined(profile) || (cxt->profile->ns != profile->ns)) - /* if switching to unconfined or a different profile namespace - * clear out context state - */ - aa_clear_task_cxt_trans(cxt); - - /* be careful switching cxt->profile, when racing replacement it - * is possible that cxt->profile->replacedby is the reference keeping - * @profile valid, so make sure to get its reference before dropping - * the reference on cxt->profile */ - aa_get_profile(profile); - aa_put_profile(cxt->profile); - cxt->profile = profile; - - commit_creds(new); - return 0; -} - -/** - * aa_set_current_onexec - set the tasks change_profile to happen onexec - * @profile: system profile to set at exec (MAYBE NULL to clear value) - * - * Returns: 0 or error on failure - */ -int aa_set_current_onexec(struct aa_profile *profile) -{ - struct aa_task_cxt *cxt; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - - cxt = cred_cxt(new); - aa_get_profile(profile); - aa_put_profile(cxt->onexec); - cxt->onexec = profile; - - commit_creds(new); - return 0; -} - -/** - * aa_set_current_hat - set the current tasks hat - * @profile: profile 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_profile *profile, u64 token) -{ - struct aa_task_cxt *cxt; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - BUG_ON(!profile); - - cxt = cred_cxt(new); - if (!cxt->previous) { - /* transfer refcount */ - cxt->previous = cxt->profile; - cxt->token = token; - } else if (cxt->token == token) { - aa_put_profile(cxt->profile); - } else { - /* previous_profile && cxt->token != token */ - abort_creds(new); - return -EACCES; - } - cxt->profile = aa_get_profile(aa_newest_version(profile)); - /* clear exec on switching context */ - aa_put_profile(cxt->onexec); - cxt->onexec = NULL; - - commit_creds(new); - return 0; -} - -/** - * aa_restore_previous_profile - exit from hat context restoring the profile - * @token: the token that must be matched to exit hat context - * - * Attempt to return out of a hat to the previous profile. The token - * must match the stored token value. - * - * Returns: 0 or error of failure - */ -int aa_restore_previous_profile(u64 token) -{ - struct aa_task_cxt *cxt; - struct cred *new = prepare_creds(); - if (!new) - return -ENOMEM; - - cxt = cred_cxt(new); - if (cxt->token != token) { - abort_creds(new); - return -EACCES; - } - /* ignore restores when there is no saved profile */ - if (!cxt->previous) { - abort_creds(new); - return 0; - } - - aa_put_profile(cxt->profile); - cxt->profile = aa_newest_version(cxt->previous); - BUG_ON(!cxt->profile); - if (unlikely(cxt->profile != cxt->previous)) { - aa_get_profile(cxt->profile); - aa_put_profile(cxt->previous); - } - /* ref has been transfered so avoid putting ref in clear_task_cxt */ - cxt->previous = NULL; - /* clear exec && prev information when restoring to previous context */ - aa_clear_task_cxt_trans(cxt); - - commit_creds(new); - return 0; -} diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c new file mode 100644 index 000000000000..d8a7bde94d79 --- /dev/null +++ b/security/apparmor/crypto.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright 2013 Canonical Ltd. + * + * 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/sha2.h> + +#include "include/apparmor.h" +#include "include/crypto.h" + +unsigned int aa_hash_size(void) +{ + return SHA256_DIGEST_SIZE; +} + +char *aa_calc_hash(void *data, size_t len) +{ + char *hash; + + hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); + if (!hash) + return ERR_PTR(-ENOMEM); + + sha256(data, len, hash); + return hash; +} + +int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, + size_t len) +{ + struct sha256_ctx sctx; + __le32 le32_version = cpu_to_le32(version); + + if (!aa_g_hash_policy) + return 0; + + profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); + if (!profile->hash) + 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; +} + +int __init init_profile_hash(void) +{ + if (apparmor_initialized) + aa_info_message("AppArmor sha256 policy hashing enabled"); + return 0; +} diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 01b7bd669a88..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,225 +6,498 @@ * * 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" #include "include/match.h" #include "include/path.h" #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 - * @task: task we want to change profile of (NOT NULL) - * @to_profile: profile to change to (NOT NULL) + * @to_cred: cred of task changing domain + * @to_label: profile to change to (NOT NULL) + * @info: message if there is an error * - * Check if the task is ptraced and if so if the tracing task is allowed + * Check if current is ptraced and if so if the tracing task is allowed * to trace the new domain * * Returns: %0 or error if change not allowed */ -static int may_change_ptraced_domain(struct task_struct *task, - struct aa_profile *to_profile) +static int may_change_ptraced_domain(const struct cred *to_cred, + struct aa_label *to_label, + const char **info) { struct task_struct *tracer; - struct aa_profile *tracerp = NULL; + struct aa_label *tracerl = NULL; + const struct cred *tracer_cred = NULL; + int error = 0; rcu_read_lock(); - tracer = ptrace_parent(task); - if (tracer) + tracer = ptrace_parent(current); + if (tracer) { /* released below */ - tracerp = aa_get_task_profile(tracer); - + tracerl = aa_get_task_label(tracer); + tracer_cred = get_task_cred(tracer); + } /* not ptraced */ - if (!tracer || unconfined(tracerp)) + if (!tracer || unconfined(tracerl)) goto out; - error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); + error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label, + PTRACE_MODE_ATTACH); out: rcu_read_unlock(); - aa_put_profile(tracerp); + aa_put_label(tracerl); + put_cred(tracer_cred); + if (error) + *info = "ptrace prevents transition"; return error; } +/**** 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 + ****/ +/* 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 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(rules->file->dfa, state, "&"); + if (profile->ns == tp->ns) + 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(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); +} + /** - * change_profile_perms - find permissions for change_profile - * @profile: the current profile (NOT NULL) - * @ns: the namespace being switched to (NOT NULL) - * @name: the name of the profile to change to (NOT NULL) - * @request: requested perms - * @start: state to start matching in + * label_compound_match - find perms for full compound label + * @profile: profile to find perms for + * @label: label to check access permissions for + * @stack: whether this is a stacking request + * @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 * - * Returns: permission set + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for A//&B//&C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. */ -static struct file_perms change_profile_perms(struct aa_profile *profile, - struct aa_namespace *ns, - const char *name, u32 request, - unsigned int start) +static int label_compound_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + aa_state_t state, bool subns, u32 request, + struct aa_perms *perms) { - struct file_perms perms; + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_profile *tp; + struct label_it i; struct path_cond cond = { }; - unsigned int state; - if (unconfined(profile)) { - perms.allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; - perms.audit = perms.quiet = perms.kill = 0; - return perms; - } else if (!profile->file.dfa) { - return nullperms; - } else if ((ns == profile->ns)) { - /* try matching against rules with out namespace prepended */ - aa_str_perms(profile->file.dfa, start, name, &cond, &perms); - if (COMBINED_PERM_MASK(perms) & request) - return perms; + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, state); + if (!state) + goto fail; + goto next; } - /* try matching with namespace name and then profile */ - state = aa_dfa_match(profile->file.dfa, start, ns->base.name); - state = aa_dfa_match_len(profile->file.dfa, state, ":", 1); - aa_str_perms(profile->file.dfa, state, name, &cond, &perms); + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(rules->file->dfa, state, "//&"); + state = match_component(profile, tp, false, state); + if (!state) + goto fail; + } + *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; - return perms; + return 0; + +fail: + *perms = nullperms; + return -EACCES; } /** - * __attach_match_ - find an attachment match - * @name - to match against (NOT NULL) - * @head - profile list to walk (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 - * expressions to match, and matching expressions with the greatest - * xmatch_len are preferred. + * label_components_match - find perms for all subcomponents of a label + * @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 + * @subns: whether to do permission checks on components in a subns + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to * - * Requires: @head not be shared or have appropriate locks held + * Returns: 0 on success else ERROR * - * Returns: profile or NULL if no match found + * For the label A//&B//&C this does the perm match for each of A and B and C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. */ -static struct aa_profile *__attach_match(const char *name, - struct list_head *head) +static int label_components_match(struct aa_profile *profile, + struct aa_label *label, bool stack, + aa_state_t start, bool subns, u32 request, + struct aa_perms *perms) { - int len = 0; - struct aa_profile *profile, *candidate = NULL; + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_profile *tp; + struct label_it i; + struct aa_perms tmp; + struct path_cond cond = { }; + aa_state_t state = 0; - list_for_each_entry(profile, head, base.list) { - if (profile->flags & PFLAG_NULL) + /* find first subcomponent to test */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) 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); - /* any accepting state means a valid match. */ - if (perm & MAY_EXEC) { - candidate = profile; - len = profile->xmatch_len; - } - } else if (!strcmp(profile->base.name, name)) - /* exact non-re match, no more searching required */ - return profile; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + goto next; } - return candidate; + /* no subcomponents visible - no change in perms */ + return 0; + +next: + 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) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, tp, stack, start); + if (!state) + goto fail; + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; } /** - * 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) + * label_match - do a multi-component label match + * @profile: profile to match against (NOT NULL) + * @label: label to match (NOT NULL) + * @stack: whether this is a stacking request + * @state: state to start in + * @subns: whether to match subns components + * @request: permission request + * @perms: Returns computed perms (NOT NULL) * - * Returns: profile or NULL if no match found + * Returns: the state the match finished in, may be the none matching state */ -static struct aa_profile *find_attach(struct aa_namespace *ns, - struct list_head *list, const char *name) +static int label_match(struct aa_profile *profile, struct aa_label *label, + bool stack, aa_state_t state, bool subns, u32 request, + struct aa_perms *perms) { - struct aa_profile *profile; + int error; + + *perms = nullperms; + error = label_compound_match(profile, label, stack, state, subns, + request, perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, label, stack, state, subns, + request, perms); +} + +/******* end TODO: dedup *****/ - read_lock(&ns->lock); - profile = aa_get_profile(__attach_match(name, list)); - read_unlock(&ns->lock); +/** + * change_profile_perms - find permissions for change_profile + * @profile: the current profile (NOT NULL) + * @target: label to transition to (NOT NULL) + * @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 + * + * currently only matches full label A//&B//&C or individual components A, B, C + * not arbitrary combinations. Eg. A//&B, C + */ +static int change_profile_perms(struct aa_profile *profile, + struct aa_label *target, bool stack, + u32 request, aa_state_t start, + struct aa_perms *perms) +{ + if (profile_unconfined(profile)) { + perms->allow = AA_MAY_CHANGE_PROFILE | AA_MAY_ONEXEC; + perms->audit = perms->quiet = perms->kill = 0; + return 0; + } - return profile; + /* TODO: add profile in ns screening */ + return label_match(profile, target, stack, start, true, request, perms); } /** - * separate_fqname - separate the namespace and profile names - * @fqname: the fqname name to split (NOT NULL) - * @ns_name: the namespace name if it exists (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 * - * This is the xtable equivalent routine of aa_split_fqname. It finds the - * split in an xtable fqname which contains an embedded \0 instead of a : - * if a namespace is specified. This is done so the xtable is constant and - * isn't re-split on every lookup. + * 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) * - * Either the profile or namespace name may be optional but if the namespace - * is specified the profile name termination must be present. This results - * in the following possible encodings: - * profile_name\0 - * :ns_name\0profile_name\0 - * :ns_name\0\0 + * 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 + * expressions to match, and matching expressions with the greatest + * xmatch_len are preferred. * - * NOTE: the xtable fqname is pre-validated at load time in unpack_trans_table + * Requires: @head not be shared or have appropriate locks held * - * Returns: profile name if it is specified else NULL + * Returns: label or NULL if no match found */ -static const char *separate_fqname(const char *fqname, const char **ns_name) +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) { - const char *name; + 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) { + struct aa_attachment *attach = &profile->attach; - if (fqname[0] == ':') { - /* In this case there is guaranteed to be two \0 terminators - * in the string. They are verified at load time by - * by unpack_trans_table + if (profile->label.flags & FLAG_NULL && + &profile->label == ns_unconfined(profile->ns)) + continue; + + /* 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. */ - *ns_name = fqname + 1; /* skip : */ - name = *ns_name + strlen(*ns_name) + 1; - if (!*name) - name = NULL; - } else { - *ns_name = NULL; - name = fqname; + 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 (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; + candidate_len = max(count, attach->xmatch_len); + candidate_xattrs = ret; + conflict = false; + } + } 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; + } + } + + if (!candidate || conflict) { + if (conflict) + *info = CONFLICTING_ATTACH_STR; + rcu_read_unlock(); + return NULL; } - return name; +out: + candidate = aa_get_newest_profile(candidate); + rcu_read_unlock(); + + return &candidate->label; } static const char *next_name(int xtype, const char *name) @@ -235,362 +509,696 @@ static const char *next_name(int xtype, const char *name) * x_table_lookup - lookup an x transition name via transition table * @profile: current profile (NOT NULL) * @xindex: index into x transition table + * @name: returns: name tested to find label (NOT NULL) * - * Returns: refcounted profile, or NULL on failure (MAYBE NULL) + * Returns: refcounted label, or NULL on failure (MAYBE NULL) + * @name will always be set with the last name tried */ -static struct aa_profile *x_table_lookup(struct aa_profile *profile, u32 xindex) +struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name) { - struct aa_profile *new_profile = NULL; - struct aa_namespace *ns = profile->ns; + 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 *name; + const char *next; - /* index is guaranteed to be in range, validated at load time */ - for (name = profile->file.trans.table[index]; !new_profile && name; - name = next_name(xtype, name)) { - struct aa_namespace *new_ns; - const char *xname = NULL; + AA_BUG(!name); - new_ns = NULL; + /* index is guaranteed to be in range, validated at load time */ + /* TODO: move lookup parsing to unpack time so this is a straight + * index into the resultant label + */ + 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) { - /* release by caller */ - new_profile = aa_find_child(profile, name); - continue; - } else if (*name == ':') { - /* switching namespace */ - const char *ns_name; - xname = name = separate_fqname(name, &ns_name); - if (!xname) - /* no name so use profile name */ - xname = profile->base.hname; - if (*ns_name == '@') { - /* TODO: variable support */ - ; - } - /* released below */ - new_ns = aa_find_namespace(ns, ns_name); - if (!new_ns) - continue; - } else if (*name == '@') { - /* TODO: variable support */ + /* 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; - } else { - /* basic namespace lookup */ - xname = name; } - - /* released by caller */ - new_profile = aa_lookup_profile(new_ns ? new_ns : ns, xname); - aa_put_namespace(new_ns); + label = aa_label_parse(&profile->label, lookup, GFP_KERNEL, + true, false); + if (!IS_ERR_OR_NULL(label)) + /* release by caller */ + return label; } - /* released by caller */ - return new_profile; + return NULL; } /** - * x_to_profile - get target profile for a given xindex + * 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 profile for a transition index + * find label for a transition index * - * Returns: refcounted profile or NULL if not found available + * Returns: refcounted label or NULL if not found available */ -static struct aa_profile *x_to_profile(struct aa_profile *profile, - const char *name, u32 xindex) +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_profile *new_profile = NULL; - struct aa_namespace *ns = profile->ns; + struct aa_label *new = NULL; + struct aa_label *stack = NULL; + struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; + /* Used for info checks during fallback handling */ + const char *old_info = NULL; switch (xtype) { case AA_X_NONE: /* fail exec unless ix || ux fallback - handled by caller */ - return NULL; + *lookupname = NULL; + break; + case AA_X_TABLE: + /* TODO: fix when perm mapping done at unload */ + /* 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; + stack = new; + new = NULL; + fallthrough; /* to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ - new_profile = find_attach(ns, &profile->base.profiles, - name); + new = find_attach(bprm, ns, &profile->base.profiles, + name, info); else /* released by caller */ - new_profile = find_attach(ns, &ns->base.profiles, - name); - break; - case AA_X_TABLE: - /* released by caller */ - new_profile = x_table_lookup(profile, xindex); + 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 + */ + 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)); + 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); + } + } + + if (new && stack) { + /* base the stack on post domain transition */ + struct aa_label *base = new; + + new = aa_label_merge(base, stack, GFP_KERNEL); + /* null on error */ + aa_put_label(base); + } + + aa_put_label(stack); /* released by caller */ - return new_profile; + return new; } -/** - * apparmor_bprm_set_creds - set the new creds on the bprm struct - * @bprm: binprm for the exec (NOT NULL) - * - * Returns: %0 or error on failure - */ -int apparmor_bprm_set_creds(struct linux_binprm *bprm) +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_task_cxt *cxt; - struct aa_profile *profile, *new_profile = NULL; - struct aa_namespace *ns; - char *buffer = NULL; - unsigned int state; - struct file_perms perms = {}; - struct path_cond cond = { - file_inode(bprm->file)->i_uid, - file_inode(bprm->file)->i_mode - }; - const char *name = NULL, *target = NULL, *info = NULL; - int error = cap_bprm_set_creds(bprm); - if (error) - return error; - - if (bprm->cred_prepared) - return 0; + 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; + aa_state_t state = rules->file->start[AA_CLASS_FILE]; + struct aa_perms perms = {}; + bool nonewprivs = false; + int error = 0; - cxt = cred_cxt(bprm->cred); - BUG_ON(!cxt); + AA_BUG(!profile); + AA_BUG(!bprm); + AA_BUG(!buffer); - profile = aa_get_profile(aa_newest_version(cxt->profile)); - /* - * get the namespace from the replacement profile as replacement - * can change the namespace - */ - ns = profile->ns; - state = profile->file.start; - - /* buffer freed below, name is pointer into buffer */ - error = aa_path_name(&bprm->file->f_path, profile->path_flags, &buffer, - &name, &info); + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, + &name, &info, profile->disconnected); if (error) { - if (profile->flags & - (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED)) + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; + new = aa_get_newest_label(&profile->label); + } name = bprm->filename; goto audit; } - /* Test for onexec first as onexec directives override other - * x transitions. - */ - if (unconfined(profile)) { - /* unconfined task */ - if (cxt->onexec) - /* change_profile on exec already been granted */ - new_profile = aa_get_profile(cxt->onexec); - else - new_profile = find_attach(ns, &ns->base.profiles, name); - if (!new_profile) - goto cleanup; - /* - * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results - * in a further reduction of permissions. + if (profile_unconfined(profile)) { + 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 */ - goto apply; + 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(DEBUG_DOMAIN, "unconfined attached to new label"); + return new; + } + 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); - if (cxt->onexec) { - struct file_perms cp; - info = "change_profile onexec"; - if (!(perms.allow & AA_MAY_ONEXEC)) - goto audit; - - /* test if this exec can be paired with change_profile onexec. - * onexec permission is linked to exec with a standard pairing - * exec\0change_profile - */ - state = aa_dfa_null_transition(profile->file.dfa, state); - cp = change_profile_perms(profile, cxt->onexec->ns, - cxt->onexec->base.name, - AA_MAY_ONEXEC, state); - - if (!(cp.allow & AA_MAY_ONEXEC)) - goto audit; - new_profile = aa_get_profile(aa_newest_version(cxt->onexec)); - goto apply; - } - + state = aa_str_perms(rules->file, state, name, cond, &perms); if (perms.allow & MAY_EXEC) { /* exec permission determine how to transition */ - new_profile = x_to_profile(profile, name, perms.xindex); - if (!new_profile) { - if (perms.xindex & AA_X_INHERIT) { - /* (p|c|n)ix - don't change profile but do - * use the newest version, which was picked - * up above when getting profile - */ - info = "ix fallback"; - new_profile = aa_get_profile(profile); - goto x_clear; - } else if (perms.xindex & AA_X_UNCONFINED) { - new_profile = aa_get_profile(ns->unconfined); - info = "ux fallback"; - } else { - error = -ENOENT; - info = "profile not found"; - /* remove MAY_EXEC to audit as failure */ - perms.allow &= ~MAY_EXEC; + 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) { + 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 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)) { - /* no exec permission - are we in learning mode */ - new_profile = aa_new_null_profile(profile, 0); +create_learning_profile: + /* no exec permission - learning mode */ + new_profile = aa_new_learning_profile(profile, false, name, + GFP_KERNEL); if (!new_profile) { error = -ENOMEM; info = "could not create null profile"; } else { error = -EACCES; - target = new_profile->base.hname; + new = &new_profile->label; } perms.xindex |= AA_X_UNSAFE; } else /* fail exec */ error = -EACCES; - /* - * Policy has specified a domain transition, if no_new_privs then - * fail the exec. - */ - if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) { - aa_put_profile(new_profile); - error = -EPERM; - goto cleanup; + if (!new) + goto audit; + + + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: setting AT_SECURE for %s profile=", + name); + aa_label_printk(new, GFP_KERNEL); + dbg_printk("\n"); + } + *secure_exec = true; } - if (!new_profile) - goto audit; +audit: + 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); + return ERR_PTR(error); + } - if (bprm->unsafe & LSM_UNSAFE_SHARE) { - /* FIXME: currently don't mediate shared state */ - ; + return new; +} + +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) +{ + 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; + + AA_BUG(!profile); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + if (profile_unconfined(profile)) { + /* change_profile on exec already granted */ + /* + * NOTE: Domain transitions from unconfined are allowed + * even when no_new_privs is set because this always results + * in a further reduction of permissions. + */ + return 0; } - if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { - error = may_change_ptraced_domain(current, new_profile); - if (error) { - aa_put_profile(new_profile); - goto audit; + error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer, + &xname, &info, profile->disconnected); + if (error) { + if (profile_unconfined(profile) || + (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); + error = 0; } + xname = bprm->filename; + goto audit; } - /* Determine if secure exec is needed. - * Can be at this point for the following reasons: - * 1. unconfined switching to confined - * 2. confined switching to different confinement - * 3. confined switching to unconfined - * - * Cases 2 and 3 are marked as requiring secure exec - * (unless policy specified "unsafe exec") - * - * bprm->unsafe is used to cache the AA_X_UNSAFE permission - * to avoid having to recompute in secureexec + /* find exec permissions for name */ + 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; + } + /* test if this exec can be paired with change_profile onexec. + * onexec permission is linked to exec with a standard pairing + * exec\0change_profile */ - if (!(perms.xindex & AA_X_UNSAFE)) { - AA_DEBUG("scrubbing environment variables for %s profile=%s\n", - name, new_profile->base.hname); - bprm->unsafe |= AA_SECURE_X_NEEDED; + 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; } -apply: - target = new_profile->base.hname; - /* when transitioning profiles clear unsafe personality bits */ - bprm->per_clear |= PER_CLEAR_ON_SETID; -x_clear: - aa_put_profile(cxt->profile); - /* transfer new profile reference will be released when cxt is freed */ - cxt->profile = new_profile; - - /* clear out all temporary/transitional state from the context */ - aa_clear_task_cxt_trans(cxt); + if (!(perms.xindex & AA_X_UNSAFE)) { + if (DEBUG_ON) { + dbg_printk("apparmor: setting AT_SECURE for %s label=", + xname); + aa_label_printk(onexec, GFP_KERNEL); + dbg_printk("\n"); + } + *secure_exec = true; + } audit: - error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_EXEC, MAY_EXEC, - name, target, cond.uid, info, error); + return aa_audit_file(subj_cred, profile, &perms, OP_EXEC, + AA_MAY_ONEXEC, xname, + NULL, onexec, cond->uid, info, error); +} -cleanup: - aa_put_profile(profile); - kfree(buffer); +/* ensure none ns domain transitions are correctly applied with onexec */ - return error; +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, + bool *unsafe) +{ + struct aa_profile *profile; + struct aa_label *new; + int error; + + AA_BUG(!label); + AA_BUG(!onexec); + AA_BUG(!bprm); + AA_BUG(!buffer); + + /* 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(subj_cred, profile, &nullperms, + OP_CHANGE_ONEXEC, + AA_MAY_ONEXEC, bprm->filename, NULL, + onexec, GLOBAL_ROOT_UID, + "failed to build target label", -ENOMEM)); + return ERR_PTR(error); } /** - * apparmor_bprm_secureexec - determine if secureexec is needed - * @bprm: binprm for exec (NOT NULL) + * 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 * - * Returns: %1 if secureexec is needed else %0 + * TODO: once the other paths are done see if we can't refactor into a fn */ -int apparmor_bprm_secureexec(struct linux_binprm *bprm) +int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) { - int ret = cap_bprm_secureexec(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 = { + vfsuid_into_kuid(vfsuid), + file_inode(bprm->file)->i_mode + }; + + subj_cred = current_cred(); + ctx = task_ctx(current); + AA_BUG(!cred_label(bprm->cred)); + AA_BUG(!ctx); - /* the decision to use secure exec is computed in set_creds - * and stored in bprm->unsafe. + 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 (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED)) - ret = 1; + if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && + !ctx->nnp) + ctx->nnp = aa_get_label(label); - return ret; -} + /* buffer freed below, name is pointer into buffer */ + buffer = aa_get_buffer(false); + if (!buffer) { + error = -ENOMEM; + goto done; + } -/** - * apparmor_bprm_committing_creds - do task cleanup on committing new creds - * @bprm: binprm for the exec (NOT NULL) - */ -void apparmor_bprm_committing_creds(struct linux_binprm *bprm) -{ - struct aa_profile *profile = __aa_current_profile(); - struct aa_task_cxt *new_cxt = cred_cxt(bprm->cred); + /* Test for onexec first as onexec override other x transitions. */ + if (ctx->onexec) + new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token, + bprm, buffer, &cond, &unsafe); + else + new = fn_label_build(label, profile, GFP_KERNEL, + profile_transition(subj_cred, profile, bprm, + buffer, + &cond, &unsafe)); + + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + goto done; + } else if (!new) { + error = -ENOMEM; + goto done; + } - /* bail out if unconfined or not changing profile */ - if ((new_cxt->profile == profile) || - (unconfined(new_cxt->profile))) - return; + /* 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; + } - current->pdeath_signal = 0; + if (bprm->unsafe & LSM_UNSAFE_SHARE) { + /* FIXME: currently don't mediate shared state */ + ; + } - /* reset soft limits and set hard limits for the new profile */ - __aa_transition_rlimits(profile, new_cxt->profile); -} + if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) { + /* TODO: test needs to be profile of label to new */ + error = may_change_ptraced_domain(bprm->cred, new, &info); + if (error) + goto audit; + } -/** - * apparmor_bprm_commited_cred - do cleanup after new creds committed - * @bprm: binprm for the exec (NOT NULL) - */ -void apparmor_bprm_committed_creds(struct linux_binprm *bprm) -{ - /* TODO: cleanup signals - ipc mediation */ - return; + if (unsafe) { + if (DEBUG_ON) { + dbg_printk("setting AT_SECURE for %s label=", + bprm->filename); + aa_label_printk(new, GFP_KERNEL); + dbg_printk("\n"); + } + 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_KERNEL); + dbg_printk("\n"); + } + bprm->per_clear |= PER_CLEAR_ON_SETID; + } + aa_put_label(cred_label(bprm->cred)); + /* transfer reference, released when cred is freed */ + set_cred_label(bprm->cred, new); + +done: + aa_put_label(label); + aa_put_buffer(buffer); + + return error; + +audit: + error = fn_for_each(label, profile, + aa_audit_file(current_cred(), profile, &nullperms, + OP_EXEC, MAY_EXEC, + bprm->filename, NULL, new, + vfsuid_into_kuid(vfsuid), info, error)); + aa_put_label(new); + goto done; } /* * Functions for self directed profile change */ -/** - * new_compound_name - create an hname with @n2 appended to @n1 - * @n1: base of hname (NOT NULL) - * @n2: name to append (NOT NULL) + +/* helper fn for change_hat * - * Returns: new name or NULL on error + * Returns: label for hat transition OR ERR_PTR. Does NOT return NULL */ -static char *new_compound_name(const char *n1, const char *n2) +static struct aa_label *build_change_hat(const struct cred *subj_cred, + struct aa_profile *profile, + const char *name, bool sibling) { - char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL); - if (name) - sprintf(name, "%s//%s", n1, n2); - return name; + struct aa_profile *root, *hat = NULL; + const char *info = NULL; + int error = 0; + + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { + info = "conflicting target types"; + error = -EPERM; + goto audit; + } + + hat = aa_find_child(root, name); + if (!hat) { + error = -ENOENT; + if (COMPLAIN_MODE(profile)) { + hat = aa_new_learning_profile(profile, true, name, + GFP_KERNEL); + if (!hat) { + info = "failed null profile create"; + error = -ENOMEM; + } + } + } + aa_put_profile(root); + +audit: + 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, info, + error); + if (!hat || (error && error != -ENOENT)) + return ERR_PTR(error); + /* if hat && error - complain mode, already audited and we adjust for + * complain mode allow by returning hat->label + */ + return &hat->label; +} + +/* helper fn for changing into a hat + * + * Returns: label for hat transition or ERR_PTR. Does not return NULL + */ +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; + struct aa_label *new; + struct label_it it; + bool sibling = false; + const char *name, *info = NULL; + int i, error; + + AA_BUG(!label); + AA_BUG(!hats); + AA_BUG(count < 1); + + if (PROFILE_IS_HAT(labels_profile(label))) + sibling = true; + + /*find first matching hat */ + for (i = 0; i < count && !hat; i++) { + name = hats[i]; + label_for_each_in_ns(it, labels_ns(label), label, profile) { + if (sibling && PROFILE_IS_HAT(profile)) { + root = aa_get_profile_rcu(&profile->parent); + } else if (!sibling && !PROFILE_IS_HAT(profile)) { + root = aa_get_profile(profile); + } else { /* conflicting change type */ + info = "conflicting targets types"; + error = -EPERM; + goto fail; + } + hat = aa_find_child(root, name); + aa_put_profile(root); + if (!hat) { + if (!COMPLAIN_MODE(profile)) + goto outer_continue; + /* complain mode succeed as if hat */ + } else if (!PROFILE_IS_HAT(hat)) { + info = "target not hat"; + error = -EPERM; + aa_put_profile(hat); + goto fail; + } + aa_put_profile(hat); + } + /* found a hat for all profiles in ns */ + goto build; +outer_continue: + ; + } + /* no hats that match, find appropriate error + * + * In complain mode audit of the failure is based off of the first + * hat supplied. This is done due how userspace interacts with + * change_hat. + */ + name = NULL; + label_for_each_in_ns(it, labels_ns(label), label, profile) { + if (!list_empty(&profile->base.profiles)) { + info = "hat not found"; + error = -ENOENT; + goto fail; + } + } + info = "no hats defined"; + error = -ECHILD; + +fail: + label_for_each_in_ns(it, labels_ns(label), label, profile) { + /* + * no target as it has failed to be found or built + * + * change_hat uses probing and should not log failures + * related to missing hats + */ + /* TODO: get rid of GLOBAL_ROOT_UID */ + if (count > 1 || COMPLAIN_MODE(profile)) { + aa_audit_file(subj_cred, profile, &nullperms, + OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, name, NULL, NULL, + GLOBAL_ROOT_UID, info, error); + } + } + return ERR_PTR(error); + +build: + new = fn_label_build_in_ns(label, profile, GFP_KERNEL, + build_change_hat(subj_cred, profile, name, + sibling), + aa_get_label(&profile->label)); + if (!new) { + info = "label build failed"; + error = -ENOMEM; + goto fail; + } /* else if (IS_ERR) build_change_hat has logged error so return new */ + + return new; } /** @@ -598,139 +1206,176 @@ static char *new_compound_name(const char *n1, const char *n2) * @hats: vector of hat names to try changing into (MAYBE NULL if @count == 0) * @count: number of hat names in @hats * @token: magic value to validate the hat change - * @permtest: true if this is just a permission test + * @flags: flags affecting behavior of the change + * + * Returns %0 on success, error otherwise. * * Change to the first profile specified in @hats that exists, and store * the @hat_magic in the current task context. If the count == 0 and the * @token matches that stored in the current task context, return to the * top level profile. * - * Returns %0 on success, error otherwise. + * change_hat only applies to profiles in the current ns, and each profile + * in the ns must make the same transition otherwise change_hat will fail. */ -int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) +int aa_change_hat(const char *hats[], int count, u64 token, int flags) { - const struct cred *cred; - struct aa_task_cxt *cxt; - struct aa_profile *profile, *previous_profile, *hat = NULL; - char *name = NULL; - int i; - struct file_perms perms = {}; - const char *target = NULL, *info = NULL; + 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; + /* released below */ + subj_cred = get_current_cred(); + label = aa_get_newest_cred_label(subj_cred); + previous = aa_get_newest_label(ctx->previous); + /* - * Fail explicitly requested domain transitions if no_new_privs. - * There is no exception for unconfined as change_hat is not - * available. + * 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 (current->no_new_privs) - return -EPERM; + if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) + ctx->nnp = aa_get_label(label); - /* released below */ - cred = get_current_cred(); - cxt = cred_cxt(cred); - profile = aa_cred_profile(cred); - previous_profile = cxt->previous; + /* return -EPERM when unconfined doesn't have children to avoid + * changing the traditional error code for unconfined. + */ + if (unconfined(label)) { + struct label_it i; + bool empty = true; - if (unconfined(profile)) { - info = "unconfined"; - error = -EPERM; - goto audit; + 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) { - /* attempting to change into a new hat or switch to a sibling */ - struct aa_profile *root; - root = PROFILE_IS_HAT(profile) ? profile->parent : profile; - - /* find first matching hat */ - for (i = 0; i < count && !hat; i++) - /* released below */ - hat = aa_find_child(root, hats[i]); - if (!hat) { - if (!COMPLAIN_MODE(root) || permtest) { - if (list_empty(&root->base.profiles)) - error = -ECHILD; - else - error = -ENOENT; - goto out; - } + new = change_hat(subj_cred, label, hats, count, flags); + AA_BUG(!new); + if (IS_ERR(new)) { + error = PTR_ERR(new); + new = NULL; + /* already audited */ + goto out; + } - /* - * In complain mode and failed to match any hats. - * Audit the failure is based off of the first hat - * supplied. This is done due how userspace - * interacts with change_hat. - * - * TODO: Add logging of all failed hats - */ + /* target cred is the same as current except new label */ + error = may_change_ptraced_domain(subj_cred, new, &info); + if (error) + goto fail; - /* freed below */ - name = new_compound_name(root->base.hname, hats[0]); - target = name; - /* released below */ - hat = aa_new_null_profile(profile, 1); - if (!hat) { - info = "failed null profile create"; - error = -ENOMEM; - goto audit; - } - } else { - target = hat->base.hname; - if (!PROFILE_IS_HAT(hat)) { - info = "target not hat"; - error = -EPERM; - goto audit; - } + /* + * 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; } - error = may_change_ptraced_domain(current, hat); - if (error) { - info = "ptraced"; + if (flags & AA_CHANGE_TEST) + goto out; + + target = new; + error = aa_set_current_hat(new, token); + if (error == -EACCES) + /* 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 audit; + goto out; } - if (!permtest) { - error = aa_set_current_hat(hat, token); - if (error == -EACCES) - /* kill task in case of brute force attacks */ - perms.kill = AA_MAY_CHANGEHAT; - else if (name && !error) - /* reset error for learning of new hats */ - error = -ENOENT; - } - } else if (previous_profile) { - /* Return to saved profile. Kill task if restore fails + /* Return to saved label. Kill task if restore fails * to avoid brute force attacks */ - target = previous_profile->base.hname; - error = aa_restore_previous_profile(token); - perms.kill = AA_MAY_CHANGEHAT; - } else - /* ignore restores when there is no saved profile */ - goto out; - -audit: - if (!permtest) - error = aa_audit_file(profile, &perms, GFP_KERNEL, - OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, - target, GLOBAL_ROOT_UID, info, error); + target = previous; + error = aa_restore_previous_label(token); + if (error) { + if (error == -EACCES) + goto kill; + goto fail; + } + } /* else ignore @flags && restores when there is no saved profile */ out: - aa_put_profile(hat); - kfree(name); - put_cred(cred); + aa_put_label(new); + aa_put_label(previous); + aa_put_label(label); + put_cred(subj_cred); + + return error; + +kill: + info = "failed token match"; + perms.kill = AA_MAY_CHANGEHAT; + +fail: + fn_for_each_in_ns(label, profile, + aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT, + AA_MAY_CHANGEHAT, NULL, NULL, target, + GLOBAL_ROOT_UID, info, error)); + + goto out; +} + + +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; + + if (!error) + error = change_profile_perms(profile, target, stack, request, + rules->file->start[AA_CLASS_FILE], + perms); + if (error) + 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 - * @ns_name: name of the profile namespace to change to (MAYBE NULL) - * @hname: name of profile to change to (MAYBE NULL) - * @onexec: whether this transition is to take place immediately or at exec - * @permtest: true if this is just a permission test + * @fqname: name of profile may include namespace (NOT NULL) + * @flags: flags affecting change behavior * * Change to new profile @name. Unlike with hats, there is no way * to change back. If @name isn't specified the current profile name is @@ -740,111 +1385,197 @@ out: * * Returns %0 on success, error otherwise. */ -int aa_change_profile(const char *ns_name, const char *hname, bool onexec, - bool permtest) +int aa_change_profile(const char *fqname, int flags) { - const struct cred *cred; - struct aa_profile *profile, *target = NULL; - struct aa_namespace *ns = NULL; - struct file_perms perms = {}; - const char *name = NULL, *info = NULL; - int op, error = 0; + struct aa_label *label, *new = NULL, *target = NULL; + struct aa_profile *profile; + struct aa_perms perms = {}; + 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; - if (!hname && !ns_name) + 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_put_label(label); + AA_DEBUG(DEBUG_DOMAIN, "no profile name"); return -EINVAL; + } - if (onexec) { + if (flags & AA_CHANGE_ONEXEC) { request = AA_MAY_ONEXEC; - op = OP_CHANGE_ONEXEC; + if (stack) + op = OP_STACK_ONEXEC; + else + op = OP_CHANGE_ONEXEC; } else { request = AA_MAY_CHANGE_PROFILE; - op = OP_CHANGE_PROFILE; + if (stack) + op = OP_STACK; + else + op = OP_CHANGE_PROFILE; } - cred = get_current_cred(); - profile = aa_cred_profile(cred); - - /* - * Fail explicitly requested domain transitions if no_new_privs - * and not unconfined. - * Domain transitions from unconfined are allowed even when - * no_new_privs is set because this aways results in a reduction - * of permissions. + /* This should move to a per profile test. Requires pushing build + * into callback */ - if (current->no_new_privs && !unconfined(profile)) { - put_cred(cred); - return -EPERM; - } - - if (ns_name) { - /* released below */ - ns = aa_find_namespace(profile->ns, ns_name); - if (!ns) { - /* we don't create new namespace in complain mode */ - name = ns_name; - info = "namespace not found"; - error = -ENOENT; - goto audit; - } - } else - /* released below */ - ns = aa_get_namespace(profile->ns); - - /* if the name was not specified, use the name of the current profile */ - if (!hname) { - if (unconfined(profile)) - hname = ns->unconfined->base.hname; - else - hname = profile->base.hname; + 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; } - perms = change_profile_perms(profile, ns, hname, request, - profile->file.start); - if (!(perms.allow & request)) { - error = -EACCES; - goto audit; + if (*fqname == '&') { + stack = true; + /* don't have label_parse() do stacking */ + fqname++; } + target = aa_label_parse(label, fqname, GFP_KERNEL, true, false); + if (IS_ERR(target)) { + struct aa_profile *tprofile; - /* released below */ - target = aa_lookup_profile(ns, hname); - if (!target) { - info = "profile not found"; - error = -ENOENT; - if (permtest || !COMPLAIN_MODE(profile)) + info = "label not found"; + error = PTR_ERR(target); + target = NULL; + /* + * TODO: fixme using labels_profile is not right - do profile + * per complain profile + */ + if ((flags & AA_CHANGE_TEST) || + !COMPLAIN_MODE(labels_profile(label))) goto audit; /* released below */ - target = aa_new_null_profile(profile, 0); - if (!target) { + tprofile = aa_new_learning_profile(labels_profile(label), false, + fqname, GFP_KERNEL); + if (!tprofile) { info = "failed null profile create"; error = -ENOMEM; goto audit; } + target = &tprofile->label; + goto check; } + /* + * self directed transitions only apply to current policy ns + * TODO: currently requiring perms for stacking and straight change + * stacking doesn't strictly need this. Determine how much + * we want to loosen this restriction for stacking + * + * if (!stack) { + */ + error = fn_for_each_in_ns(label, profile, + change_profile_perms_wrapper(op, auditname, + subj_cred, + profile, target, stack, + request, &perms)); + if (error) + /* auditing done in change_profile_perms_wrapper */ + goto out; + + /* } */ + +check: /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(current, target); - if (error) { - info = "ptrace prevents transition"; + error = may_change_ptraced_domain(subj_cred, target, &info); + if (error && !fn_for_each_in_ns(label, profile, + COMPLAIN_MODE(profile))) goto audit; + + /* TODO: add permission check to allow this + * if ((flags & AA_CHANGE_ONEXEC) && !current_is_single_threaded()) { + * info = "not a single threaded task"; + * error = -EACCES; + * goto audit; + * } + */ + 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 (permtest) - goto audit; + if (!(flags & AA_CHANGE_ONEXEC)) { + /* only transition profiles in the current ns */ + if (stack) + new = aa_label_merge(label, target, GFP_KERNEL); + if (IS_ERR_OR_NULL(new)) { + info = "failed to build target label"; + if (!new) + error = -ENOMEM; + else + error = PTR_ERR(new); + new = NULL; + perms.allow = 0; + goto audit; + } + error = aa_replace_current_label(new); + } else { + if (new) { + aa_put_label(new); + new = NULL; + } - if (onexec) - error = aa_set_current_onexec(target); - else - error = aa_replace_current_profile(target); + /* full transition will be built in exec path */ + aa_set_current_onexec(target, stack); + } audit: - if (!permtest) - error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, - name, hname, GLOBAL_ROOT_UID, info, error); + error = fn_for_each_in_ns(label, profile, + aa_audit_file(subj_cred, + profile, &perms, op, request, auditname, + NULL, new ? new : target, + GLOBAL_ROOT_UID, info, error)); - aa_put_namespace(ns); - aa_put_profile(target); - put_cred(cred); +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 fdaa50cb1876..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,56 +6,35 @@ * * 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/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" -struct file_perms nullperms; - - -/** - * 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) +static u32 map_mask_to_chr_mask(u32 mask) { - char str[10]; + u32 m = mask & PERMS_CHRS_MASK; + + if (mask & AA_MAY_GETATTR) + m |= MAY_READ; + if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN)) + m |= MAY_WRITE; - char *m = str; - - if (mask & AA_EXEC_MMAP) - *m++ = 'm'; - if (mask & (MAY_READ | AA_MAY_META_READ)) - *m++ = 'r'; - if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD | - AA_MAY_CHOWN)) - *m++ = 'w'; - else if (mask & MAY_APPEND) - *m++ = 'a'; - if (mask & AA_MAY_CREATE) - *m++ = 'c'; - if (mask & AA_MAY_DELETE) - *m++ = 'd'; - if (mask & AA_MAY_LINK) - *m++ = 'l'; - if (mask & AA_MAY_LOCK) - *m++ = 'k'; - if (mask & MAY_EXEC) - *m++ = 'x'; - *m = '\0'; - - audit_log_string(ab, str); + return m; } /** @@ -65,209 +45,226 @@ 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 (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { - audit_log_format(ab, " requested_mask="); - audit_file_mask(ab, sa->aad->fs.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 (sa->aad->fs.denied & AA_AUDIT_FILE_MASK) { - audit_log_format(ab, " denied_mask="); - audit_file_mask(ab, sa->aad->fs.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 (sa->aad->fs.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, sa->aad->fs.ouid)); + from_kuid(&init_user_ns, ad->fs.ouid)); } - if (sa->aad->fs.target) { + if (ad->peer) { + audit_log_format(ab, " 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, sa->aad->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) - * @gfp: allocation flags * @op: operation being mediated * @request: permissions requested * @name: name of object being mediated (MAYBE NULL) * @target: name of target (MAYBE NULL) + * @tlabel: target label (MAY BE NULL) * @ouid: object uid * @info: extra information message (MAYBE NULL) * @error: 0 if operation allowed else failure error code * * Returns: %0 or error on failure */ -int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, - gfp_t gfp, int op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error) +int 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; - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.op = op, - aad.fs.request = request; - aad.name = name; - aad.fs.target = target; - aad.fs.ouid = ouid; - aad.info = info; - aad.error = error; - - if (likely(!sa.aad->error)) { + DEFINE_AUDIT_DATA(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 */ - sa.aad->fs.request &= mask; + ad.request &= mask; - if (likely(!sa.aad->fs.request)) + if (likely(!ad.request)) return 0; type = AUDIT_APPARMOR_AUDIT; } else { /* only report permissions that were denied */ - sa.aad->fs.request = sa.aad->fs.request & ~perms->allow; + ad.request = ad.request & ~perms->allow; + AA_BUG(!ad.request); - if (sa.aad->fs.request & perms->kill) + if (ad.request & perms->kill) type = AUDIT_APPARMOR_KILL; /* quiet known rejects, assumes quiet and kill do not overlap */ - if ((sa.aad->fs.request & perms->quiet) && + if ((ad.request & perms->quiet) && AUDIT_MODE(profile) != AUDIT_NOQUIET && AUDIT_MODE(profile) != AUDIT_ALL) - sa.aad->fs.request &= ~perms->quiet; + ad.request &= ~perms->quiet; - if (!sa.aad->fs.request) - return COMPLAIN_MODE(profile) ? 0 : sa.aad->error; + if (!ad.request) + return ad.error; } - sa.aad->fs.denied = sa.aad->fs.request & ~perms->allow; - return aa_audit(type, profile, gfp, &sa, file_audit_cb); + ad.denied = ad.request & ~perms->allow; + return aa_audit(type, profile, &ad, file_audit_cb); } -/** - * 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) +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) { - u32 new = old & 0xf; - if (old & MAY_READ) - new |= AA_MAY_META_READ; - if (old & MAY_WRITE) - new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE | - AA_MAY_CHMOD | AA_MAY_CHOWN; - 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; + struct aa_profile *profile; + const char *info = NULL; + int error; - return new; + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); + if (error) { + fn_for_each_confined(label, profile, + aa_audit_file(subj_cred, + profile, &nullperms, op, request, *name, + NULL, NULL, cond->uid, info, error)); + return error; + } + + return 0; } +struct aa_perms default_perms = {}; /** - * compute_perms - 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 */ -static struct file_perms compute_perms(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 file_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.kill = 0; + if (!(rules->perms)) + return &default_perms; - 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); + 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]); } - perms.allow |= AA_MAY_META_READ; - - /* 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; - 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 file_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; - if (!dfa) { - *perms = nullperms; - return DFA_NOMATCH; - } - - state = aa_dfa_match(dfa, start, name); - *perms = compute_perms(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; } -/** - * is_deleted - test if a file has been completely unlinked - * @dentry: dentry of file to test for deletion (NOT NULL) - * - * Returns: %1 if deleted else %0 - */ -static inline bool is_deleted(struct dentry *dentry) +int __aa_path_perm(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) { - if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0) - return 1; - return 0; + struct aa_ruleset *rules = profile->label.rules[0]; + int e = 0; + + if (profile_unconfined(profile) || + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) + return 0; + aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], + name, cond, perms); + if (request & ~perms->allow) + e = -EACCES; + 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, 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) +{ + const char *name; + int error; + + if (profile_unconfined(profile)) + return 0; + + 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, subj_cred, profile, name, request, cond, + flags, perms); } /** * aa_path_perm - do permissions check & audit for @path * @op: operation being checked - * @profile: profile being enforced (NOT NULL) + * @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 * @request: requested permissions @@ -275,34 +272,26 @@ static inline bool is_deleted(struct dentry *dentry) * * Returns: %0 else error if access denied or other error */ -int aa_path_perm(int op, struct aa_profile *profile, struct path *path, - int flags, u32 request, struct path_cond *cond) +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) { + struct aa_perms perms = {}; + struct aa_profile *profile; char *buffer = NULL; - struct file_perms perms = {}; - const char *name, *info = NULL; int error; - flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0); - error = aa_path_name(path, flags, &buffer, &name, &info); - if (error) { - if (error == -ENOENT && is_deleted(path->dentry)) { - /* Access to open files that are deleted are - * give a pass (implicit delegation) - */ - error = 0; - info = NULL; - perms.allow = request; - } - } else { - aa_str_perms(profile->file.dfa, profile->file.start, name, cond, - &perms); - if (request & ~perms.allow) - error = -EACCES; - } - error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name, - NULL, cond->uid, info, error); - kfree(buffer); + flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : + 0); + buffer = aa_get_buffer(false); + if (!buffer) + return -ENOMEM; + error = fn_for_each_confined(label, profile, + profile_path_perm(op, subj_cred, profile, path, buffer, + request, cond, flags, &perms)); + + aa_put_buffer(buffer); return error; } @@ -316,76 +305,56 @@ int aa_path_perm(int op, struct aa_profile *profile, struct path *path, * 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; } -/** - * aa_path_link - Handle hard link permission check - * @profile: the profile being enforced (NOT NULL) - * @old_dentry: the target dentry (NOT NULL) - * @new_dir: directory the new link will be created in (NOT NULL) - * @new_dentry: the link being created (NOT NULL) - * - * Handle the permission test for a link & target pair. Permission - * is encoded as a pair where the link permission is determined - * first, and if allowed, the target is tested. The target test - * is done from the point of the link match (not start of DFA) - * making the target permission dependent on the link permission match. - * - * The subset test if required forces that permissions granted - * on link are a subset of the permission granted to target. - * - * Returns: %0 if allowed else error - */ -int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, - struct path *new_dir, struct dentry *new_dentry) +static int profile_path_link(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 path link = { new_dir->mnt, new_dentry }; - struct path target = { new_dir->mnt, old_dentry }; - struct path_cond cond = { - old_dentry->d_inode->i_uid, - old_dentry->d_inode->i_mode - }; - char *buffer = NULL, *buffer2 = NULL; - const char *lname, *tname = NULL, *info = NULL; - struct file_perms lperms, perms; + 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; - lperms = nullperms; - - /* buffer freed below, lname is pointer in buffer */ - error = aa_path_name(&link, profile->path_flags, &buffer, &lname, - &info); + 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 = aa_path_name(&target, profile->path_flags, &buffer2, &tname, - &info); + 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, - &cond, &lperms); + 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. @@ -396,6 +365,7 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_MAY_LINK)) { info = "target restricted"; + lperms = perms; goto audit; } @@ -403,11 +373,11 @@ int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry, if (!(perms.allow & AA_LINK_SUBSET)) goto done_tests; - /* Do link perm subset test requiring allowed permission on link are a - * subset of the allowed permissions on target. + /* Do link perm subset test requiring allowed permission on link are + * a subset of the allowed permissions on target. */ - aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond, - &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; @@ -428,31 +398,331 @@ done_tests: error = 0; audit: - error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request, - lname, tname, cond.uid, info, error); - kfree(buffer); - kfree(buffer2); + return aa_audit_file(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) + * @new_dentry: the link being created (NOT NULL) + * + * Handle the permission test for a link & target pair. Permission + * is encoded as a pair where the link permission is determined + * first, and if allowed, the target is tested. The target test + * is done from the point of the link match (not start of DFA) + * making the target permission dependent on the link permission match. + * + * The subset test if required forces that permissions granted + * on link are a subset of the permission granted to target. + * + * Returns: %0 if allowed else error + */ +int aa_path_link(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 = { + .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 */ + 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; +} + +static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, + u32 request) +{ + struct aa_label *l, *old; + + /* update caching of label on file_ctx */ + spin_lock(&fctx->lock); + old = rcu_dereference_protected(fctx->label, + lockdep_is_held(&fctx->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(fctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + fctx->allow |= request; + } + spin_unlock(&fctx->lock); +} + +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, 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 = vfsuid_into_kuid(vfsuid), + .mode = file_inode(file)->i_mode + }; + char *buffer; + int flags, error; + + /* revalidation due to label out of date. No revocation at this time */ + if (!denied && aa_label_is_subset(flabel, label)) + /* TODO: check for revocation on stale profiles */ + return 0; + + flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0); + 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, subj_cred, profile, + &file->f_path, buffer, + request, &cond, flags, &perms)); + if (denied && !error) { + /* + * check every profile in file label that was not tested + * in the initial check above. + * + * TODO: cache full perms so this only happens because of + * conditionals + * TODO: don't audit here + */ + if (label == flabel) + error = fn_for_each(label, profile, + 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, subj_cred, + profile, &file->f_path, + buffer, request, &cond, flags, + &perms)); + } + if (!error) + update_file_ctx(file_ctx(file), label, request); + + 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 - * @profile: profile being enforced (NOT NULL) + * @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(int op, struct aa_profile *profile, 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 path_cond cond = { - .uid = file_inode(file)->i_uid, - .mode = file_inode(file)->i_mode - }; + struct aa_file_ctx *fctx; + struct aa_label *flabel; + u32 denied; + int error = 0; + + AA_BUG(!label); + AA_BUG(!file); + + fctx = file_ctx(file); + + rcu_read_lock(); + flabel = rcu_dereference(fctx->label); + AA_BUG(!flabel); + + /* revalidate access, if task is unconfined, or the cached cred + * doesn't match or if the request is for more permissions than + * was granted. + * + * Note: the test for !unconfined(flabel) is to handle file + * delegation from unconfined tasks + */ + denied = request & ~fctx->allow; + 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; + } + + /* slow path - revalidate access */ + flabel = aa_get_newest_label(flabel); + rcu_read_unlock(); + + if (path_mediated_fs(file->f_path.dentry)) + error = __file_path_perm(op, subj_cred, label, flabel, file, + request, denied, in_atomic); - return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED, - request, &cond); + 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(const struct cred *subj_cred, struct aa_label *label) +{ + struct tty_struct *tty; + int drop_tty = 0; + + tty = get_current_tty(); + if (!tty) + return; + + spin_lock(&tty->files_lock); + if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; + struct file *file; + /* TODO: Revalidate access to controlling tty. */ + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; + + if (aa_file_perm(OP_INHERIT, subj_cred, label, file, + MAY_READ | MAY_WRITE, IN_ATOMIC)) + drop_tty = 1; + } + spin_unlock(&tty->files_lock); + tty_kref_put(tty); + + if (drop_tty) + 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 cred_label *cl = (struct cred_label *)p; + + if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file, + aa_map_file_to_perms(file), IN_ATOMIC)) + return fd + 1; + return 0; +} + + +/* based on selinux's flush_unauthorized_files */ +void aa_inherit_files(const struct cred *cred, struct files_struct *files) +{ + struct aa_label *label = aa_get_newest_cred_label(cred); + struct cred_label cl = { + .cred = cred, + .label = label, + }; + struct file *devnull = NULL; + unsigned int n; + + revalidate_tty(cred, label); + + /* Revalidate access to inherited open files. */ + n = iterate_fd(files, 0, match_file, &cl); + if (!n) /* none found? */ + goto out; + + devnull = dentry_open(&aa_null, O_RDWR, cred); + if (IS_ERR(devnull)) + devnull = NULL; + /* replace all the matching ones with this */ + do { + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, &cl)) != 0); + if (devnull) + fput(devnull); +out: + aa_put_label(label); } diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h new file mode 100644 index 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 1ba2ca56a6ef..cc6e3df1bc62 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -1,115 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * - * This file contains AppArmor basic global and lib definitions + * This file contains AppArmor basic global * * 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. + * Copyright 2009-2017 Canonical Ltd. */ #ifndef __APPARMOR_H #define __APPARMOR_H -#include <linux/slab.h> -#include <linux/fs.h> - -#include "match.h" +#include <linux/types.h> /* * 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_LAST AA_CLASS_DOMAIN +#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 + +/* 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; -/* - * DEBUG remains global (no per profile flag) since it is mostly used in sysctl - * which is not related to profile accesses. - */ - -#define AA_DEBUG(fmt, args...) \ - do { \ - if (aa_g_debug && printk_ratelimit()) \ - printk(KERN_DEBUG "AppArmor: " fmt, ##args); \ - } while (0) - -#define AA_ERROR(fmt, args...) \ - do { \ - if (printk_ratelimit()) \ - printk(KERN_ERR "AppArmor: " fmt, ##args); \ - } while (0) - -/* Flag indicating whether initialization completed */ -extern int apparmor_initialized __initdata; - -/* fn's in lib */ -char *aa_split_fqname(char *args, char **ns_name); -void aa_info_message(const char *str); -void *__aa_kvmalloc(size_t size, gfp_t flags); -void kvfree(void *buffer); - -static inline void *kvmalloc(size_t size) -{ - return __aa_kvmalloc(size, 0); -} - -static inline void *kvzalloc(size_t size) -{ - return __aa_kvmalloc(size, __GFP_ZERO); -} - -/** - * aa_strneq - compare null terminated @str to a non null terminated substring - * @str: a null terminated string - * @sub: a substring, not necessarily null terminated - * @len: length of @sub to compare - * - * The @str string must be full consumed for this to be considered a match - */ -static inline bool aa_strneq(const char *str, const char *sub, int len) -{ - return !strncmp(str, sub, len) && !str[len]; -} - -/** - * aa_dfa_null_transition - step to next state after null character - * @dfa: the dfa to match against - * @start: the state of the dfa to start matching in - * - * aa_dfa_null_transition transitions to the next state after a null - * 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) -{ - /* the null transition only needs the string's null terminator byte */ - return aa_dfa_next(dfa, start, 0); -} +#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 */ -static inline bool mediated_filesystem(struct inode *inode) -{ - return !(inode->i_sb->s_flags & MS_NOUSER); -} #endif /* __APPARMOR_H */ diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index 7ea4769fab3f..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,60 +6,131 @@ * * 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 #define __AA_APPARMORFS_H -enum aa_fs_type { - AA_FS_TYPE_BOOLEAN, - AA_FS_TYPE_STRING, - AA_FS_TYPE_U64, - AA_FS_TYPE_FOPS, - AA_FS_TYPE_DIR, +extern struct path aa_null; + +enum aa_sfs_type { + AA_SFS_TYPE_BOOLEAN, + AA_SFS_TYPE_STRING, + AA_SFS_TYPE_U64, + AA_SFS_TYPE_FOPS, + AA_SFS_TYPE_DIR, }; -struct aa_fs_entry; +struct aa_sfs_entry; -struct aa_fs_entry { +struct aa_sfs_entry { const char *name; struct dentry *dentry; umode_t mode; - enum aa_fs_type v_type; + enum aa_sfs_type v_type; union { bool boolean; char *string; unsigned long u64; - struct aa_fs_entry *files; + struct aa_sfs_entry *files; } v; const struct file_operations *file_ops; }; -extern const struct file_operations aa_fs_seq_file_ops; +extern const struct file_operations aa_sfs_seq_file_ops; -#define AA_FS_FILE_BOOLEAN(_name, _value) \ +#define AA_SFS_FILE_BOOLEAN(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_BOOLEAN, .v.boolean = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_STRING(_name, _value) \ + .v_type = AA_SFS_TYPE_BOOLEAN, .v.boolean = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_STRING(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_STRING, .v.string = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_U64(_name, _value) \ + .v_type = AA_SFS_TYPE_STRING, .v.string = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_U64(_name, _value) \ { .name = (_name), .mode = 0444, \ - .v_type = AA_FS_TYPE_U64, .v.u64 = (_value), \ - .file_ops = &aa_fs_seq_file_ops } -#define AA_FS_FILE_FOPS(_name, _mode, _fops) \ - { .name = (_name), .v_type = AA_FS_TYPE_FOPS, \ + .v_type = AA_SFS_TYPE_U64, .v.u64 = (_value), \ + .file_ops = &aa_sfs_seq_file_ops } +#define AA_SFS_FILE_FOPS(_name, _mode, _fops) \ + { .name = (_name), .v_type = AA_SFS_TYPE_FOPS, \ .mode = (_mode), .file_ops = (_fops) } -#define AA_FS_DIR(_name, _value) \ - { .name = (_name), .v_type = AA_FS_TYPE_DIR, .v.files = (_value) } +#define AA_SFS_DIR(_name, _value) \ + { .name = (_name), .v_type = AA_SFS_TYPE_DIR, .v.files = (_value) } extern void __init aa_destroy_aafs(void); +struct aa_profile; +struct aa_ns; + +enum aafs_ns_type { + AAFS_NS_DIR, + AAFS_NS_PROFS, + AAFS_NS_NS, + AAFS_NS_RAW_DATA, + AAFS_NS_LOAD, + AAFS_NS_REPLACE, + AAFS_NS_REMOVE, + AAFS_NS_REVISION, + AAFS_NS_COUNT, + AAFS_NS_MAX_COUNT, + AAFS_NS_SIZE, + AAFS_NS_MAX_SIZE, + AAFS_NS_OWNER, + AAFS_NS_SIZEOF, +}; + +enum aafs_prof_type { + AAFS_PROF_DIR, + AAFS_PROF_PROFS, + AAFS_PROF_NAME, + AAFS_PROF_MODE, + AAFS_PROF_ATTACH, + AAFS_PROF_HASH, + AAFS_PROF_RAW_DATA, + AAFS_PROF_RAW_HASH, + AAFS_PROF_RAW_ABI, + AAFS_PROF_SIZEOF, +}; + +#define ns_dir(X) ((X)->dents[AAFS_NS_DIR]) +#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) +#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) +#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA]) +#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD]) +#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE]) +#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE]) +#define ns_subrevision(X) ((X)->dents[AAFS_NS_REVISION]) + +#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, + struct aa_profile *new); +int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); +void __aafs_ns_rmdir(struct aa_ns *ns); +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 69d8cae634e7..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 @@ -22,12 +18,10 @@ #include <linux/slab.h> #include "file.h" - -struct aa_profile; +#include "label.h" extern const char *const audit_mode_names[]; #define AUDIT_MAX_INDEX 5 - enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ @@ -47,98 +41,160 @@ enum audit_type { AUDIT_APPARMOR_AUTO }; -extern const char *const op_table[]; -enum aa_ops { - OP_NULL, - - OP_SYSCTL, - OP_CAPABLE, - - OP_UNLINK, - OP_MKDIR, - OP_RMDIR, - OP_MKNOD, - OP_TRUNC, - OP_LINK, - OP_SYMLINK, - OP_RENAME_SRC, - OP_RENAME_DEST, - OP_CHMOD, - OP_CHOWN, - OP_GETATTR, - OP_OPEN, - - OP_FPERM, - OP_FLOCK, - OP_FMMAP, - OP_FMPROT, - - OP_CREATE, - OP_POST_CREATE, - OP_BIND, - OP_CONNECT, - OP_LISTEN, - OP_ACCEPT, - OP_SENDMSG, - OP_RECVMSG, - OP_GETSOCKNAME, - OP_GETPEERNAME, - OP_GETSOCKOPT, - OP_SETSOCKOPT, - OP_SOCK_SHUTDOWN, - - OP_PTRACE, - - OP_EXEC, - OP_CHANGE_HAT, - OP_CHANGE_PROFILE, - OP_CHANGE_ONEXEC, - - OP_SETPROCATTR, - OP_SETRLIMIT, - - OP_PROF_REPL, - OP_PROF_LOAD, - OP_PROF_RM, -}; - +#define OP_NULL NULL + +#define OP_SYSCTL "sysctl" +#define OP_CAPABLE "capable" + +#define OP_UNLINK "unlink" +#define OP_MKDIR "mkdir" +#define OP_RMDIR "rmdir" +#define OP_MKNOD "mknod" +#define OP_TRUNC "truncate" +#define OP_LINK "link" +#define OP_SYMLINK "symlink" +#define OP_RENAME_SRC "rename_src" +#define OP_RENAME_DEST "rename_dest" +#define OP_CHMOD "chmod" +#define OP_CHOWN "chown" +#define OP_GETATTR "getattr" +#define OP_OPEN "open" + +#define OP_FRECEIVE "file_receive" +#define OP_FPERM "file_perm" +#define OP_FLOCK "file_lock" +#define OP_FMMAP "file_mmap" +#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" +#define OP_CONNECT "connect" +#define OP_LISTEN "listen" +#define OP_ACCEPT "accept" +#define OP_SENDMSG "sendmsg" +#define OP_RECVMSG "recvmsg" +#define OP_GETSOCKNAME "getsockname" +#define OP_GETPEERNAME "getpeername" +#define OP_GETSOCKOPT "getsockopt" +#define OP_SETSOCKOPT "setsockopt" +#define OP_SHUTDOWN "socket_shutdown" + +#define OP_PTRACE "ptrace" +#define OP_SIGNAL "signal" + +#define OP_EXEC "exec" + +#define OP_CHANGE_HAT "change_hat" +#define OP_CHANGE_PROFILE "change_profile" +#define OP_CHANGE_ONEXEC "change_onexec" +#define OP_STACK "stack" +#define OP_STACK_ONEXEC "stack_onexec" + +#define OP_SETPROCATTR "setprocattr" +#define OP_SETRLIMIT "setrlimit" + +#define OP_PROF_REPL "profile_replace" +#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 op; int type; - void *profile; + u16 class; + const char *op; + const struct cred *subj_cred; + struct aa_label *subj_label; const char *name; const char *info; - struct task_struct *tsk; + u32 request; + u32 denied; union { - void *target; + /* these entries require a custom callback fn */ + struct { + struct aa_label *peer; + 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 { + struct aa_profile *profile; + const char *ns; long pos; - void *target; } 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 { - const char *target; - u32 request; - u32 denied; - kuid_t ouid; - } fs; + struct aa_label *target; + } uring; }; + + struct common_audit_data common; }; -/* define a short hand for apparmor_audit_data structure */ -#define aad apparmor_audit_data +/* macros for dealing with apparmor_audit_data structure */ +#define aad(SA) (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 = { \ + .class = (C), \ + .op = (X), \ + .common.type = (T), \ + .common.u.tsk = NULL, \ + .common.apparmor_audit_data = &NAME, \ + }; -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 *)); -int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, - struct common_audit_data *sa, +int aa_audit(int type, struct aa_profile *profile, + struct apparmor_audit_data *ad, void (*cb) (struct audit_buffer *, void *)); +#define aa_audit_error(ERROR, AD, CB) \ +({ \ + (AD)->error = (ERROR); \ + aa_audit_msg(AUDIT_APPARMOR_ERROR, (AD), (CB)); \ + (AD)->error; \ +}) + + static inline int complain_error(int error) { if (error == -EPERM || error == -EACCES) @@ -146,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 c24d2959ea02..1ddcec2d1160 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -1,15 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor capability mediation definitions. * * 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. + * Copyright 2009-2013 Canonical Ltd. */ #ifndef __AA_CAPABILITY_H @@ -17,11 +13,14 @@ #include <linux/sched.h> -struct aa_profile; +#include "apparmorfs.h" + +struct aa_label; /* aa_caps - confinement data for capabilities * @allowed: capabilities mask * @audit: caps that are to be audited + * @denied: caps that are explicitly denied * @quiet: caps that should not be audited * @kill: caps that when requested will result in the task being killed * @extended: caps that are subject finer grained mediation @@ -29,13 +28,17 @@ struct aa_profile; struct aa_caps { kernel_cap_t allow; kernel_cap_t audit; + kernel_cap_t denied; kernel_cap_t quiet; kernel_cap_t kill; kernel_cap_t extended; }; -int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, - int audit); +extern struct aa_sfs_entry aa_sfs_entry_caps[]; + +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/context.h deleted file mode 100644 index d44ba5802e3d..000000000000 --- a/security/apparmor/include/context.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * AppArmor security module - * - * This file contains AppArmor contexts used to associate "labels" to objects. - * - * 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 -#define __AA_CONTEXT_H - -#include <linux/cred.h> -#include <linux/slab.h> -#include <linux/sched.h> - -#include "policy.h" - -#define cred_cxt(X) (X)->security -#define current_cxt() cred_cxt(current_cred()) - -/* struct aa_file_cxt - the AppArmor context the file was opened in - * @perms: the permission the file was opened with - * - * The file_cxt could currently be directly stored in file->f_security - * as the profile reference is now stored in the f_cred. However the - * cxt struct will expand in the future so we keep the struct. - */ -struct aa_file_cxt { - u16 allow; -}; - -/** - * aa_alloc_file_context - allocate file_cxt - * @gfp: gfp flags for allocation - * - * Returns: file_cxt or NULL on failure - */ -static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp) -{ - return kzalloc(sizeof(struct aa_file_cxt), gfp); -} - -/** - * aa_free_file_context - free a file_cxt - * @cxt: file_cxt to free (MAYBE_NULL) - */ -static inline void aa_free_file_context(struct aa_file_cxt *cxt) -{ - if (cxt) - kzfree(cxt); -} - -/** - * struct aa_task_cxt - primary label for confined tasks - * @profile: the current profile (NOT NULL) - * @exec: profile to transition to on next exec (MAYBE NULL) - * @previous: profile the task may return to (MAYBE NULL) - * @token: magic value the task must know for returning to @previous_profile - * - * Contains the task's current profile (which could change due to - * change_hat). Plus the hat_magic needed during change_hat. - * - * TODO: make so a task can be confined by a stack of contexts - */ -struct aa_task_cxt { - struct aa_profile *profile; - struct aa_profile *onexec; - struct aa_profile *previous; - u64 token; -}; - -struct aa_task_cxt *aa_alloc_task_context(gfp_t flags); -void aa_free_task_context(struct aa_task_cxt *cxt); -void aa_dup_task_context(struct aa_task_cxt *new, - const struct aa_task_cxt *old); -int aa_replace_current_profile(struct aa_profile *profile); -int aa_set_current_onexec(struct aa_profile *profile); -int aa_set_current_hat(struct aa_profile *profile, u64 token); -int aa_restore_previous_profile(u64 cookie); -struct aa_profile *aa_get_task_profile(struct task_struct *task); - - -/** - * aa_cred_profile - obtain cred's profiles - * @cred: cred to obtain profiles from (NOT NULL) - * - * Returns: confining profile - * - * does NOT increment reference count - */ -static inline struct aa_profile *aa_cred_profile(const struct cred *cred) -{ - struct aa_task_cxt *cxt = cred_cxt(cred); - BUG_ON(!cxt || !cxt->profile); - return aa_newest_version(cxt->profile); -} - -/** - * __aa_task_profile - retrieve another task's profile - * @task: task to query (NOT NULL) - * - * Returns: @task's profile without incrementing its ref count - * - * If @task != current needs to be called in RCU safe critical section - */ -static inline struct aa_profile *__aa_task_profile(struct task_struct *task) -{ - return aa_cred_profile(__task_cred(task)); -} - -/** - * __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) -{ - return !unconfined(__aa_task_profile(task)); -} - -/** - * __aa_current_profile - find the current tasks confining profile - * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) - * - * This fn will not update the tasks cred to the most up to date version - * of the profile so it is safe to call when inside of locks. - */ -static inline struct aa_profile *__aa_current_profile(void) -{ - return aa_cred_profile(current_cred()); -} - -/** - * aa_current_profile - find the current tasks confining profile and do updates - * - * Returns: up to date confining profile or the ns unconfined profile (NOT NULL) - * - * This fn will update the tasks cred structure if the profile has been - * replaced. Not safe to call inside locks - */ -static inline struct aa_profile *aa_current_profile(void) -{ - const struct aa_task_cxt *cxt = current_cxt(); - struct aa_profile *profile; - BUG_ON(!cxt || !cxt->profile); - - profile = aa_newest_version(cxt->profile); - /* - * Whether or not replacement succeeds, use newest profile so - * there is no need to update it after replacement. - */ - if (unlikely((cxt->profile != profile))) - aa_replace_current_profile(profile); - - return profile; -} - -/** - * aa_clear_task_cxt_trans - clear transition tracking info from the cxt - * @cxt: task context to clear (NOT NULL) - */ -static inline void aa_clear_task_cxt_trans(struct aa_task_cxt *cxt) -{ - aa_put_profile(cxt->previous); - aa_put_profile(cxt->onexec); - cxt->previous = NULL; - cxt->onexec = NULL; - cxt->token = 0; -} - -#endif /* __AA_CONTEXT_H */ diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h new file mode 100644 index 000000000000..b028e4c13b6f --- /dev/null +++ b/security/apparmor/include/cred.h @@ -0,0 +1,212 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor contexts used to associate "labels" to objects. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2010 Canonical Ltd. + */ + +#ifndef __AA_CONTEXT_H +#define __AA_CONTEXT_H + +#include <linux/cred.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include "label.h" +#include "policy_ns.h" +#include "task.h" + +static inline struct aa_label *cred_label(const struct cred *cred) +{ + struct aa_label **blob = cred->security + apparmor_blob_sizes.lbs_cred; + + 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 + * @cred: cred to obtain label from (NOT NULL) + * + * Returns: confining label + * + * does NOT increment reference count + */ +static inline struct aa_label *aa_cred_raw_label(const struct cred *cred) +{ + struct aa_label *label = cred_label(cred); + + AA_BUG(!label); + return label; +} + +/** + * aa_get_newest_cred_label - obtain the newest label on a cred + * @cred: cred to obtain label from (NOT NULL) + * + * Returns: newest version of confining label + */ +static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred) +{ + return aa_get_newest_label(aa_cred_raw_label(cred)); +} + +static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred, + bool *needput) +{ + 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; +} + +static inline void aa_put_label_condref(struct aa_label *l, bool needput) +{ + if (unlikely(needput)) + aa_put_label(l); +} + +/** + * aa_current_raw_label - find the current tasks confining label + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * This fn will not update the tasks cred to the most up to date version + * of the label so it is safe to call when inside of locks. + */ +static inline struct aa_label *aa_current_raw_label(void) +{ + return aa_cred_raw_label(current_cred()); +} + +/** + * aa_get_current_label - get the newest version of the current tasks label + * + * Returns: newest version of confining label (NOT NULL) + * + * This fn will not update the tasks cred, so it is safe inside of locks + * + * The returned reference must be put with aa_put_label() + */ +static inline struct aa_label *aa_get_current_label(void) +{ + struct aa_label *l = aa_current_raw_label(); + + if (label_is_stale(l)) + return aa_get_newest_label(l); + return aa_get_label(l); +} + +/** + * __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_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 + * begin_current_label_crit_section and never used in situations where the + * task cred may be updated + */ +static inline void end_current_label_crit_section(struct aa_label *label) +{ + if (label != aa_current_raw_label()) + aa_put_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) + * + * safe to call inside locks + * + * The returned reference must be put with __end_current_label_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between __begin_current_label_crit_section() .. + * __end_current_label_crit_section() + */ +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)) { + *needput = true; + return aa_get_newest_label(label); + } + + *needput = false; + return label; +} + +/** + * begin_current_label_crit_section - current's confining label and update it + * + * Returns: up to date confining label or the ns unconfined label (NOT NULL) + * + * Not safe to call inside locks + * + * The returned reference must be put with end_current_label_crit_section() + * This must NOT be used if the task cred could be updated within the + * critical section between begin_current_label_crit_section() .. + * end_current_label_crit_section() + */ +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) + /* task cred will keep the reference */ + aa_put_label(label); + } + + return label; +} + +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(&needput); + ns = aa_get_ns(labels_ns(label)); + __end_current_label_crit_section(label, needput); + + return ns; +} + +#endif /* __AA_CONTEXT_H */ diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h new file mode 100644 index 000000000000..f3ffd388cc58 --- /dev/null +++ b/security/apparmor/include/crypto.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor policy loading interface function definitions. + * + * Copyright 2013 Canonical Ltd. + */ + +#ifndef __APPARMOR_CRYPTO_H +#define __APPARMOR_CRYPTO_H + +#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, + size_t len); +#else +static inline char *aa_calc_hash(void *data, size_t len) +{ + return NULL; +} +static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version, + void *start, size_t len) +{ + return 0; +} + +static inline unsigned int aa_hash_size(void) +{ + return 0; +} +#endif + +#endif /* __APPARMOR_CRYPTO_H */ diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h index de04464f0a3f..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,32 +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. */ #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 + +struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, + const char **name); -int apparmor_bprm_set_creds(struct linux_binprm *bprm); -int apparmor_bprm_secureexec(struct linux_binprm *bprm); -void apparmor_bprm_committing_creds(struct linux_binprm *bprm); -void apparmor_bprm_committed_creds(struct linux_binprm *bprm); +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, bool permtest); -int aa_change_profile(const char *ns_name, const char *name, bool onexec, - bool permtest); +int aa_change_hat(const char *hats[], int count, u64 token, int flags); +int aa_change_profile(const char *fqname, int flags); #endif /* __AA_DOMAIN_H */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 2c922b86bd44..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,69 +6,62 @@ * * 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 #define __AA_FILE_H +#include <linux/spinlock.h> + #include "domain.h" #include "match.h" +#include "perms.h" +struct aa_policydb; struct aa_profile; struct path; -/* - * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags - * for profile permissions - */ -#define AA_MAY_CREATE 0x0010 -#define AA_MAY_DELETE 0x0020 -#define AA_MAY_META_WRITE 0x0040 -#define AA_MAY_META_READ 0x0080 - -#define AA_MAY_CHMOD 0x0100 -#define AA_MAY_CHOWN 0x0200 -#define AA_MAY_LOCK 0x0400 -#define AA_EXEC_MMAP 0x0800 - -#define AA_MAY_LINK 0x1000 -#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ -#define AA_MAY_ONEXEC 0x40000000 /* exec allows onexec */ -#define AA_MAY_CHANGE_PROFILE 0x80000000 -#define AA_MAY_CHANGEHAT 0x80000000 /* ctrl auditing only */ +#define mask_mode_t(X) (X & (MAY_EXEC | MAY_WRITE | MAY_READ | MAY_APPEND)) #define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\ AA_MAY_CREATE | AA_MAY_DELETE | \ - AA_MAY_META_READ | AA_MAY_META_WRITE | \ + AA_MAY_GETATTR | AA_MAY_SETATTR | \ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) +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 + * @label: label currently cached on the ctx + * @perms: the permission the file was opened with + */ +struct aa_file_ctx { + spinlock_t lock; + struct aa_label __rcu *label; + u32 allow; +}; + /* * 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 { @@ -75,119 +69,42 @@ struct path_cond { umode_t mode; }; -/* struct file_perms - file permission - * @allow: mask of permissions that are allowed - * @audit: mask of permissions to force an audit message for - * @quiet: mask of permissions to quiet audit messages for - * @kill: mask of permissions that when matched will kill the task - * @xindex: exec transition index if @allow contains MAY_EXEC - * - * The @audit and @queit mask should be mutually exclusive. - */ -struct file_perms { - u32 allow; - u32 audit; - u32 quiet; - u32 kill; - u16 xindex; -}; - -extern struct file_perms nullperms; - #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 file_perms *perms, - gfp_t gfp, int op, u32 request, const char *name, - const char *target, kuid_t ouid, const char *info, int error); - -/** - * struct aa_file_rules - components used for file rule permissions - * @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 */ -}; - -unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, - const char *name, struct path_cond *cond, - struct file_perms *perms); - -int aa_path_perm(int op, struct aa_profile *profile, struct path *path, +int aa_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_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(struct aa_profile *profile, struct dentry *old_dentry, - struct path *new_dir, struct dentry *new_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); -int aa_file_perm(int op, struct aa_profile *profile, 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); + +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 aeda0fbc8b2f..323dd071afe9 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -1,15 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * AppArmor security module * * This file contains AppArmor ipc mediation function definitions. * * 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. + * Copyright 2009-2017 Canonical Ltd. */ #ifndef __AA_IPC_H @@ -17,12 +13,11 @@ #include <linux/sched.h> -struct aa_profile; - -int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, - struct aa_profile *tracee, unsigned int mode); +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode); +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 new file mode 100644 index 000000000000..c0812dbc1b5b --- /dev/null +++ b/security/apparmor/include/label.h @@ -0,0 +1,459 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + */ + +#ifndef __AA_LABEL_H +#define __AA_LABEL_H + +#include <linux/atomic.h> +#include <linux/audit.h> +#include <linux/rbtree.h> +#include <linux/rcupdate.h> + +#include "apparmor.h" +#include "lib.h" + +struct aa_ns; +struct aa_ruleset; + +#define LOCAL_VEC_ENTRIES 8 +#define DEFINE_VEC(T, V) \ + struct aa_ ## T *(_ ## V ## _localtmp)[LOCAL_VEC_ENTRIES]; \ + struct aa_ ## T **(V) + +#define vec_setup(T, V, N, GFP) \ +({ \ + if ((N) <= LOCAL_VEC_ENTRIES) { \ + typeof(N) i; \ + (V) = (_ ## V ## _localtmp); \ + for (i = 0; i < (N); i++) \ + (V)[i] = NULL; \ + } else \ + (V) = kzalloc(sizeof(struct aa_ ## T *) * (N), (GFP)); \ + (V) ? 0 : -ENOMEM; \ +}) + +#define vec_cleanup(T, V, N) \ +do { \ + int i; \ + for (i = 0; i < (N); i++) { \ + if (!IS_ERR_OR_NULL((V)[i])) \ + aa_put_ ## T((V)[i]); \ + } \ + if ((V) != _ ## V ## _localtmp) \ + kfree(V); \ +} while (0) + +#define vec_last(VEC, SIZE) ((VEC)[(SIZE) - 1]) +#define vec_ns(VEC, SIZE) (vec_last((VEC), (SIZE))->ns) +#define vec_labelset(VEC, SIZE) (&vec_ns((VEC), (SIZE))->labels) +#define cleanup_domain_vec(V, L) cleanup_label_vec((V), (L)->size) + +struct aa_profile; +#define VEC_FLAG_TERMINATE 1 +int aa_vec_unique(struct aa_profile **vec, int n, int flags); +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp); +#define aa_sort_and_merge_vec(N, V) \ + aa_sort_and_merge_profiles((N), (struct aa_profile **)(V)) + + +/* struct aa_labelset - set of labels for a namespace + * + * Labels are reference counted; aa_labelset does not contribute to label + * reference counts. Once a label's last refcount is put it is removed from + * the set. + */ +struct aa_labelset { + rwlock_t lock; + + struct rb_root root; +}; + +#define __labelset_for_each(LS, N) \ + for ((N) = rb_first(&(LS)->root); (N); (N) = rb_next(N)) + +enum label_flags { + FLAG_HAT = 1, /* profile is a hat */ + FLAG_UNCONFINED = 2, /* label unconfined only if all */ + FLAG_NULL = 4, /* profile is null learning profile */ + FLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ + FLAG_IMMUTIBLE = 0x10, /* don't allow changes/replacement */ + FLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ + FLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ + FLAG_NS_COUNT = 0x80, /* carries NS ref count */ + FLAG_IN_TREE = 0x100, /* label is in tree */ + FLAG_PROFILE = 0x200, /* label is a profile */ + FLAG_EXPLICIT = 0x400, /* explicit static label */ + 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 */ +}; + +struct aa_label; +struct aa_proxy { + struct kref count; + struct aa_label __rcu *label; +}; + +struct label_it { + int i, j; +}; + +/* struct aa_label_base - base info of label + * @count: ref count of active users + * @node: rbtree position + * @rcu: rcu callback struct + * @proxy: is set to the label that replaced this label + * @hname: text representation of the label (MAYBE_NULL) + * @flags: stale and other flags - values may change under label set lock + * @secid: secid that references this label + * @size: number of entries in @ent[] + * @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; + struct rb_node node; + struct rcu_head rcu; + struct aa_proxy *proxy; + __counted char *hname; + long flags; + u32 secid; + int size; + 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) \ +do { \ + int __subE = (FN); \ + if (__subE) \ + (E) = __subE; \ +} while (0) + +#define label_isprofile(X) ((X)->flags & FLAG_PROFILE) +#define label_unconfined(X) ((X)->flags & FLAG_UNCONFINED) +#define unconfined(X) label_unconfined(X) +#define label_is_stale(X) ((X)->flags & FLAG_STALE) +#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]) + + +int aa_label_next_confined(struct aa_label *l, int i); + +/* for each profile in a label */ +#define label_for_each(I, L, P) \ + for ((I).i = 0; ((P) = (L)->vec[(I).i]); ++((I).i)) + +/* assumes break/goto ended label_for_each */ +#define label_for_each_cont(I, L, P) \ + for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i)) + + + +/* for each profile that is enforcing confinement in a label */ +#define label_for_each_confined(I, L, P) \ + for ((I).i = aa_label_next_confined((L), 0); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = aa_label_next_confined((L), (I).i + 1)) + +#define label_for_each_in_merge(I, A, B, P) \ + for ((I).i = (I).j = 0; \ + ((P) = aa_label_next_in_merge(&(I), (A), (B))); \ + ) + +#define label_for_each_not_in_set(I, SET, SUB, P) \ + for ((I).i = (I).j = 0; \ + ((P) = __aa_label_next_not_in_set(&(I), (SET), (SUB))); \ + ) + +#define next_in_ns(i, NS, L) \ +({ \ + typeof(i) ___i = (i); \ + while ((L)->vec[___i] && (L)->vec[___i]->ns != (NS)) \ + (___i)++; \ + (___i); \ +}) + +#define label_for_each_in_ns(I, NS, L, P) \ + for ((I).i = next_in_ns(0, (NS), (L)); \ + ((P) = (L)->vec[(I).i]); \ + (I).i = next_in_ns((I).i + 1, (NS), (L))) + +#define fn_for_each_in_ns(L, P, FN) \ +({ \ + struct label_it __i; \ + struct aa_ns *__ns = labels_ns(L); \ + int __E = 0; \ + label_for_each_in_ns(__i, __ns, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + + +#define fn_for_each_XXX(L, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each(L, P, FN) fn_for_each_XXX(L, P, FN) +#define fn_for_each_confined(L, P, FN) fn_for_each_XXX(L, P, FN, _confined) + +#define fn_for_each2_XXX(L1, L2, P, FN, ...) \ +({ \ + struct label_it i; \ + int __E = 0; \ + label_for_each ## __VA_ARGS__(i, (L1), (L2), (P)) { \ + last_error(__E, (FN)); \ + } \ + __E; \ +}) + +#define fn_for_each_in_merge(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _in_merge) +#define fn_for_each_not_in_set(L1, L2, P, FN) \ + fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) + +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, 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); +bool aa_label_remove(struct aa_label *label); +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *l); +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_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b); +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b); +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp); + + +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp); + +#define FLAGS_NONE 0 +#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, + int flags, gfp_t gfp); +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp); +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_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; +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); + + +/** + * __aa_get_label - get a reference count to uncounted label reference + * @l: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: lock held, and the return code MUST be checked + */ +static inline struct aa_label *__aa_get_label(struct aa_label *l) +{ + if (l && kref_get_unless_zero(&l->count)) + return l; + + return NULL; +} + +static inline struct aa_label *aa_get_label(struct aa_label *l) +{ + if (l) + kref_get(&(l->count)); + + return l; +} + + +/** + * aa_get_label_rcu - increment refcount on a label that can be replaced + * @l: pointer to label that can be replaced (NOT NULL) + * + * Returns: pointer to a refcounted label. + * else NULL if no label + */ +static inline struct aa_label *aa_get_label_rcu(struct aa_label __rcu **l) +{ + struct aa_label *c; + + rcu_read_lock(); + do { + c = rcu_dereference(*l); + } while (c && !kref_get_unless_zero(&c->count)); + rcu_read_unlock(); + + return c; +} + +/** + * aa_get_newest_label - find the newest version of @l + * @l: the label to check for newer versions of + * + * Returns: refcounted newest version of @l taking into account + * replacement, renames and removals + * return @l. + */ +static inline struct aa_label *aa_get_newest_label(struct aa_label *l) +{ + if (!l) + return NULL; + + if (label_is_stale(l)) { + struct aa_label *tmp; + + AA_BUG(!l->proxy); + AA_BUG(!l->proxy->label); + /* BUG: only way this can happen is @l ref count and its + * replacement count have gone to 0 and are on their way + * to destruction. ie. we have a refcounting error + */ + tmp = aa_get_label_rcu(&l->proxy->label); + AA_BUG(!tmp); + + return tmp; + } + + return aa_get_label(l); +} + +static inline void aa_put_label(struct aa_label *l) +{ + if (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); + +static inline struct aa_proxy *aa_get_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_get(&(proxy->count)); + + return proxy; +} + +static inline void aa_put_proxy(struct aa_proxy *proxy) +{ + if (proxy) + kref_put(&proxy->count, aa_proxy_kref); +} + +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new); + +#endif /* __AA_LABEL_H */ diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h new file mode 100644 index 000000000000..444197075fd6 --- /dev/null +++ b/security/apparmor/include/lib.h @@ -0,0 +1,325 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor lib definitions + * + * 2017 Canonical Ltd. + */ + +#ifndef __AA_LIB_H +#define __AA_LIB_H + +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/lsm_hooks.h> + +#include "match.h" + +extern struct aa_dfa *stacksplitdfa; + +/* + * split individual debug cases out in preparation for finer grained + * debug controls in the future. + */ +#define dbg_printk(__fmt, __args...) pr_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 (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...) \ + 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...) \ + 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) + +/* Flag indicating whether initialization completed */ +extern int apparmor_initialized; + +/* fn's in lib */ +const char *skipn_spaces(const char *str, size_t n); +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 + * @sub: a substring, not necessarily null terminated + * @len: length of @sub to compare + * + * The @str string must be full consumed for this to be considered a match + */ +static inline bool aa_strneq(const char *str, const char *sub, int len) +{ + return !strncmp(str, sub, len) && !str[len]; +} + +/** + * aa_dfa_null_transition - step to next state after null character + * @dfa: the dfa to match against + * @start: the state of the dfa to start matching in + * + * aa_dfa_null_transition transitions to the next state after a null + * character which is not used in standard matching and is only + * used to separate pairs. + */ +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); +} + +static inline bool path_mediated_fs(struct dentry *dentry) +{ + 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; + char name[]; +}; + +#define str_to_counted(str) \ + ((struct counted_str *)(str - offsetof(struct counted_str, name))) + +#define __counted /* atm just a notation */ + +void aa_str_kref(struct kref *kref); +char *aa_str_alloc(int size, gfp_t gfp); + + +static inline __counted char *aa_get_str(__counted char *str) +{ + if (str) + kref_get(&(str_to_counted(str)->count)); + + return str; +} + +static inline void aa_put_str(__counted char *str) +{ + if (str) + kref_put(&str_to_counted(str)->count, aa_str_kref); +} + + +/* struct aa_policy - common part of both namespaces and profiles + * @name: name of the object + * @hname - The hierarchical name + * @list: list policy object is on + * @profiles: head of the profiles list contained in the object + */ +struct aa_policy { + const char *name; + __counted char *hname; + struct list_head list; + struct list_head profiles; +}; + +/** + * basename - find the last component of an hname + * @hname: hname to find the base profile name component of (NOT NULL) + * + * Returns: the tail (base profile name) name component of an hname + */ +static inline const char *basename(const char *hname) +{ + char *split; + + hname = strim((char *)hname); + for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) + hname = split + 2; + + return hname; +} + +/** + * __policy_find - find a policy by @name on a policy list + * @head: list to search (NOT NULL) + * @name: name to search for (NOT NULL) + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted policy that match @name or NULL if not found + */ +static inline struct aa_policy *__policy_find(struct list_head *head, + const char *name) +{ + struct aa_policy *policy; + + list_for_each_entry_rcu(policy, head, list) { + if (!strcmp(policy->name, name)) + return policy; + } + return NULL; +} + +/** + * __policy_strn_find - find a policy that's name matches @len chars of @str + * @head: list to search (NOT NULL) + * @str: string to search for (NOT NULL) + * @len: length of match required + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted policy that match @str or NULL if not found + * + * if @len == strlen(@strlen) then this is equiv to __policy_find + * other wise it allows searching for policy by a partial match of name + */ +static inline struct aa_policy *__policy_strn_find(struct list_head *head, + const char *str, int len) +{ + struct aa_policy *policy; + + list_for_each_entry_rcu(policy, head, list) { + if (aa_strneq(policy->name, str, len)) + return policy; + } + + return NULL; +} + +bool aa_policy_init(struct aa_policy *policy, const char *prefix, + const char *name, gfp_t gfp); +void aa_policy_destroy(struct aa_policy *policy); + + +/* + * fn_label_build - abstract out the build of a label transition + * @L: label the transition is being computed for + * @P: profile parameter derived from L by this macro, can be passed to FN + * @GFP: memory allocation type to use + * @FN: fn to call for each profile transition. @P is set to the profile + * + * Returns: new label on success + * ERR_PTR if build @FN fails + * NULL if label_build fails due to low memory conditions + * + * @FN must return a label or ERR_PTR on failure. NULL is not allowed + */ +#define fn_label_build(L, P, GFP, FN) \ +({ \ + __label__ __do_cleanup, __done; \ + struct aa_label *__new_; \ + \ + if ((L)->size > 1) { \ + /* TODO: add cache of transitions already done */ \ + struct label_it __i; \ + int __j, __k, __count; \ + DEFINE_VEC(label, __lvec); \ + DEFINE_VEC(profile, __pvec); \ + if (vec_setup(label, __lvec, (L)->size, (GFP))) { \ + __new_ = NULL; \ + goto __done; \ + } \ + __j = 0; \ + label_for_each(__i, (L), (P)) { \ + __new_ = (FN); \ + AA_BUG(!__new_); \ + if (IS_ERR(__new_)) \ + goto __do_cleanup; \ + __lvec[__j++] = __new_; \ + } \ + for (__j = __count = 0; __j < (L)->size; __j++) \ + __count += __lvec[__j]->size; \ + if (!vec_setup(profile, __pvec, __count, (GFP))) { \ + for (__j = __k = 0; __j < (L)->size; __j++) { \ + label_for_each(__i, __lvec[__j], (P)) \ + __pvec[__k++] = aa_get_profile(P); \ + } \ + __count -= aa_vec_unique(__pvec, __count, 0); \ + if (__count > 1) { \ + __new_ = aa_vec_find_or_create_label(__pvec,\ + __count, (GFP)); \ + /* only fails if out of Mem */ \ + if (!__new_) \ + __new_ = NULL; \ + } else \ + __new_ = aa_get_label(&__pvec[0]->label); \ + vec_cleanup(profile, __pvec, __count); \ + } else \ + __new_ = NULL; \ +__do_cleanup: \ + vec_cleanup(label, __lvec, (L)->size); \ + } else { \ + (P) = labels_profile(L); \ + __new_ = (FN); \ + } \ +__done: \ + if (!__new_) \ + AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ + (__new_); \ +}) + + +#define __fn_build_in_ns(NS, P, NS_FN, OTHER_FN) \ +({ \ + struct aa_label *__new; \ + if ((P)->ns != (NS)) \ + __new = (OTHER_FN); \ + else \ + __new = (NS_FN); \ + (__new); \ +}) + +#define fn_label_build_in_ns(L, P, GFP, NS_FN, OTHER_FN) \ +({ \ + fn_label_build((L), (P), (GFP), \ + __fn_build_in_ns(labels_ns(L), (P), (NS_FN), (OTHER_FN))); \ +}) + +#endif /* __AA_LIB_H */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 001c43aa0406..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 */ @@ -62,6 +63,7 @@ struct table_set_header { #define YYTD_ID_ACCEPT2 6 #define YYTD_ID_NXT 7 #define YYTD_ID_TSIZE 8 +#define YYTD_ID_MAX 8 #define YYTD_DATA8 1 #define YYTD_DATA16 2 @@ -85,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)) @@ -96,16 +100,17 @@ struct table_header { struct aa_dfa { struct kref count; u16 flags; + u32 max_oob; struct table_header *tables[YYTD_ID_TSIZE]; }; #define byte_to_byte(X) (X) -#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \ +#define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \ do { \ typeof(LEN) __i; \ - TYPE *__t = (TYPE *) TABLE; \ - TYPE *__b = (TYPE *) BLOB; \ + TTYPE *__t = (TTYPE *) TABLE; \ + BTYPE *__b = (BTYPE *) BLOB; \ for (__i = 0; __i < LEN; __i++) { \ __t[__i] = NTOHX(__b[__i]); \ } \ @@ -116,16 +121,53 @@ static inline size_t table_size(size_t len, size_t el_size) return ALIGN(sizeof(struct table_header) + len * el_size, 8); } +#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) + * + * Returns: pointer to @dfa if @dfa is NULL will return NULL + * Requires: @dfa must be held with valid refcount when called + */ +static inline struct aa_dfa *aa_get_dfa(struct aa_dfa *dfa) +{ + if (dfa) + kref_get(&(dfa->count)); + + return dfa; +} + /** * aa_put_dfa - put a dfa refcount * @dfa: dfa to put refcount (MAYBE NULL) @@ -138,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 286ac75dc88b..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,28 +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(struct path *path, int flags, char **buffer, - const char **name, const char **info); +int aa_path_name(const struct path *path, int flags, char *buffer, + const char **name, const char **info, + const char *disconnected); + +#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 new file mode 100644 index 000000000000..37a3781b99a0 --- /dev/null +++ b/security/apparmor/include/perms.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor basic permission sets definitions. + * + * Copyright 2017 Canonical Ltd. + */ + +#ifndef __AA_PERM_H +#define __AA_PERM_H + +#include <linux/fs.h> +#include "label.h" + +#define AA_MAY_EXEC MAY_EXEC +#define AA_MAY_WRITE MAY_WRITE +#define AA_MAY_READ MAY_READ +#define AA_MAY_APPEND MAY_APPEND + +#define AA_MAY_CREATE 0x0010 +#define AA_MAY_DELETE 0x0020 +#define AA_MAY_OPEN 0x0040 +#define AA_MAY_RENAME 0x0080 /* pair */ + +#define AA_MAY_SETATTR 0x0100 /* meta write */ +#define AA_MAY_GETATTR 0x0200 /* meta read */ +#define AA_MAY_SETCRED 0x0400 /* security cred/attr */ +#define AA_MAY_GETCRED 0x0800 + +#define AA_MAY_CHMOD 0x1000 /* pair */ +#define AA_MAY_CHOWN 0x2000 /* pair */ +#define AA_MAY_CHGRP 0x4000 /* pair */ +#define AA_MAY_LOCK 0x8000 /* LINK_SUBSET overlaid */ + +#define AA_EXEC_MMAP 0x00010000 +#define AA_MAY_MPROT 0x00020000 /* extend conditions */ +#define AA_MAY_LINK 0x00040000 /* pair */ +#define AA_MAY_SNAPSHOT 0x00080000 /* pair */ + +#define AA_MAY_DELEGATE +#define AA_CONT_MATCH 0x08000000 + +#define AA_MAY_STACK 0x10000000 +#define AA_MAY_ONEXEC 0x20000000 /* either stack or change_profile */ +#define AA_MAY_CHANGE_PROFILE 0x40000000 +#define AA_MAY_CHANGEHAT 0x80000000 + +#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */ + +#define 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 | \ + AA_MAY_EXEC | AA_EXEC_MMAP | AA_MAY_APPEND) + +#define PERMS_NAMES_MASK (PERMS_CHRS_MASK | AA_MAY_OPEN | AA_MAY_RENAME | \ + AA_MAY_SETATTR | AA_MAY_GETATTR | AA_MAY_SETCRED | \ + AA_MAY_GETCRED | AA_MAY_CHMOD | AA_MAY_CHOWN | \ + AA_MAY_CHGRP | AA_MAY_MPROT | AA_MAY_SNAPSHOT | \ + AA_MAY_STACK | AA_MAY_ONEXEC | \ + AA_MAY_CHANGE_PROFILE | AA_MAY_CHANGEHAT) + +extern const char aa_file_perm_chrs[]; +extern const char *aa_file_perm_names[]; + +struct aa_perms { + u32 allow; + u32 deny; /* explicit deny, or conflict if allow also set */ + + u32 subtree; /* allow perm on full subtree only when allow is set */ + u32 cond; /* set only when ~allow and ~deny */ + + u32 kill; /* set only when ~allow | deny */ + u32 complain; /* accumulates only used when ~allow & ~deny */ + u32 prompt; /* accumulates only used when ~allow & ~deny */ + + 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) \ +({ \ + int e, error = FN1; \ + e = FN2; \ + if (e) \ + error = e; \ + error; \ +}) + + +/* + * TODO: update for labels pointing to labels instead of profiles + * TODO: optimize the walk, currently does subwalk of L2 for each P in L1 + * gah this doesn't allow for label compound check!!!! + */ +#define xcheck_ns_profile_profile(P1, P2, FN, args...) \ +({ \ + int ____e = 0; \ + if (P1->ns == P2->ns) \ + ____e = FN((P1), (P2), args); \ + (____e); \ +}) + +#define xcheck_ns_profile_label(P, L, FN, args...) \ +({ \ + struct aa_profile *__p2; \ + fn_for_each((L), __p2, \ + xcheck_ns_profile_profile((P), __p2, (FN), args)); \ +}) + +#define xcheck_ns_labels(L1, L2, FN, args...) \ +({ \ + struct aa_profile *__p1; \ + fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ +}) + +/* Do the cross check but applying FN at the profiles level */ +#define xcheck_labels_profiles(L1, L2, FN, args...) \ + xcheck_ns_labels((L1), (L2), xcheck_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, 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 * const *names, u32 namesmask); +void aa_apply_modes_to_perms(struct aa_profile *profile, + 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_ruleset *rules, struct aa_label *label, + int type, u32 request, struct aa_perms *perms); +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + 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 b25491a3046a..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 @@ -18,6 +14,7 @@ #include <linux/capability.h> #include <linux/cred.h> #include <linux/kref.h> +#include <linux/rhashtable.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/socket.h> @@ -27,10 +24,20 @@ #include "capability.h" #include "domain.h" #include "file.h" +#include "lib.h" +#include "label.h" +#include "net.h" +#include "perms.h" #include "resource.h" -extern const char *const profile_mode_names[]; -#define APPARMOR_NAMES_MAX_INDEX 3 + +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 #define PROFILE_MODE(_profile, _mode) \ ((aa_g_profile_mode == (_mode)) || \ @@ -38,9 +45,24 @@ extern const char *const 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)->flags & PFLAG_HAT) +#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 @@ -52,122 +74,156 @@ enum profile_mode { APPARMOR_ENFORCE, /* enforce access rules */ APPARMOR_COMPLAIN, /* allow and log access violations */ APPARMOR_KILL, /* kill task on access violation */ + APPARMOR_UNCONFINED, /* profile set to unconfined */ + APPARMOR_USER, /* modified complain mode to userspace */ }; -enum profile_flags { - PFLAG_HAT = 1, /* profile is a hat */ - PFLAG_UNCONFINED = 2, /* profile is an unconfined profile */ - PFLAG_NULL = 4, /* profile is null learning profile */ - PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */ - PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */ - PFLAG_USER_DEFINED = 0x20, /* user based profile - lower privs */ - PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */ - PFLAG_OLD_NULL_TRANS = 0x100, /* use // as the null transition */ - - /* These flags must correspond with PATH_flags */ - PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */ -}; -struct aa_profile; - -/* struct aa_policy - common part of both namespaces and profiles - * @name: name of the object - * @hname - The hierarchical name - * @count: reference count of the obj - * @list: list policy object is on - * @profiles: head of the profiles list contained in the object +/* 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_policy { - char *name; - char *hname; +struct aa_policydb { struct kref count; - struct list_head list; - struct list_head profiles; + struct aa_dfa *dfa; + struct { + struct aa_perms *perms; + u32 size; + }; + struct aa_str_table trans; + aa_state_t start[AA_CLASS_LAST + 1]; }; -/* struct aa_ns_acct - accounting of profiles in namespace - * @max_size: maximum space allowed for all profiles in namespace - * @max_count: maximum number of profiles that can be in this namespace - * @size: current size of profiles - * @count: current count of profiles (includes null profiles) - */ -struct aa_ns_acct { - int max_size; - int max_count; - int size; - int count; -}; +extern struct aa_policydb *nullpdb; -/* struct aa_namespace - namespace for a set of profiles - * @base: common policy - * @parent: parent of namespace - * @lock: lock for modifying the object - * @acct: accounting for the namespace - * @unconfined: special unconfined profile for the namespace - * @sub_ns: list of namespaces under the current namespace. - * @uniq_null: uniq value used for null learning profiles - * - * An aa_namespace defines the set profiles that are searched to determine - * which profile to attach to a task. Profiles can not be shared between - * aa_namespaces and profile names within a namespace are guaranteed to be - * unique. When profiles in separate namespaces have the same name they - * are NOT considered to be equivalent. - * - * Namespaces are hierarchical and only namespaces and profiles below the - * current namespace are visible. +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) * - * Namespace names must be unique and can not contain the characters :/\0 + * 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) * - * FIXME TODO: add vserver support of namespaces (can it all be done in - * userspace?) + * Requires: if @pdb != NULL that a valid refcount be held */ -struct aa_namespace { - struct aa_policy base; - struct aa_namespace *parent; - rwlock_t lock; - struct aa_ns_acct acct; - struct aa_profile *unconfined; - struct list_head sub_ns; - atomic_t uniq_null; +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 + * data: binary data + * head: reserved for rhashtable + */ +struct aa_data { + char *key; + u32 size; + char *data; + struct rhash_head head; }; -/* struct aa_policydb - match engine for a policy - * dfa: dfa pattern match - * start: set of start states for the different classes of data +/* 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_policydb { - /* Generic policy DFA specific rule types will be subsections of it */ - struct aa_dfa *dfa; - unsigned int start[AA_CLASS_LAST + 1]; +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 ...) * @parent: parent of profile * @ns: namespace the profile is in - * @replacedby: is set to the profile that replaced this profile * @rename: optional profile name that this profile renamed - * @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 - * @flags: flags controlling profile behavior * @path_flags: flags controlling path generation behavior - * @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 + * @signal: the signal that should be used when kill is used + * @disconnected: what to prepend if attach_disconnected is specified + * @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 * used to determine profile attachment against unconfined tasks. All other * attachments are determined by profile X transition rules. * - * The @replacedby field is write protected by the profile lock. Reads - * are assumed to be atomic, and are done without locking. - * * Profiles have a hierarchy where hats and children profiles keep * a reference to their parent. * @@ -177,107 +233,116 @@ struct aa_policydb { */ struct aa_profile { struct aa_policy base; - struct aa_profile *parent; + struct aa_profile __rcu *parent; - struct aa_namespace *ns; - struct aa_profile *replacedby; + struct aa_ns *ns; const char *rename; - struct aa_dfa *xmatch; - int xmatch_len; enum audit_mode audit; - enum profile_mode mode; - u32 flags; + long mode; u32 path_flags; - int size; + int signal; + const char *disconnected; - struct aa_policydb policy; - struct aa_file_rules file; - struct aa_caps caps; - struct aa_rlimit rlimits; -}; + struct aa_attachment attach; -extern struct aa_namespace *root_ns; -extern enum profile_mode aa_g_profile_mode; + struct aa_loaddata *rawdata; + unsigned char *hash; + char *dirname; + struct dentry *dents[AAFS_PROF_SIZEOF]; + struct rhashtable *data; -void aa_add_profile(struct aa_policy *common, struct aa_profile *profile); + int n_rules; + /* special - variable length must be last entry in profile */ + struct aa_label label; +}; -bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view); -const char *aa_ns_name(struct aa_namespace *parent, struct aa_namespace *child); -int aa_alloc_root_ns(void); -void aa_free_root_ns(void); -void aa_free_namespace_kref(struct kref *kref); +extern enum profile_mode aa_g_profile_mode; -struct aa_namespace *aa_find_namespace(struct aa_namespace *root, - const char *name); +#define AA_MAY_LOAD_POLICY AA_MAY_APPEND +#define AA_MAY_REPLACE_POLICY AA_MAY_WRITE +#define AA_MAY_REMOVE_POLICY AA_MAY_DELETE + +#define profiles_ns(P) ((P)->ns) +#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname) + +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_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); +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_fqlookupn_profile(struct aa_label *base, + const char *fqname, size_t n); -static inline struct aa_policy *aa_get_common(struct aa_policy *c) -{ - if (c) - kref_get(&c->count); +ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label, + u32 mask, struct aa_loaddata *udata); +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); - return c; -} +#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED) /** - * aa_get_namespace - increment references count on @ns - * @ns: namespace to increment reference count of (MAYBE NULL) + * aa_get_newest_profile - simple wrapper fn to wrap the label version + * @p: profile (NOT NULL) + * + * Returns refcount to newest version of the profile (maybe @p) * - * Returns: pointer to @ns, if @ns is NULL returns NULL - * Requires: @ns must be held with valid refcount when called + * Requires: @p must be held with a valid refcount */ -static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns) +static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p) { - if (ns) - kref_get(&(ns->base.count)); + return labels_profile(aa_get_newest_label(&p->label)); +} - return ns; +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); } -/** - * aa_put_namespace - decrement refcount on @ns - * @ns: namespace to put reference of - * - * Decrement reference count of @ns and if no longer in use free it - */ -static inline void aa_put_namespace(struct aa_namespace *ns) +static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules) { - if (ns) - kref_put(&ns->base.count, aa_free_namespace_kref); + return RULE_MEDIATES(rules, AA_CLASS_NETV9); } -struct aa_profile *aa_alloc_profile(const char *name); -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat); -void aa_free_profile_kref(struct kref *kref); -struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name); -struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *name); -struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name); +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); -ssize_t aa_replace_profiles(void *udata, size_t size, bool noreplace); -ssize_t aa_remove_profiles(char *name, size_t size); + /* fallback and check v7/8 if v9 is NOT mediated */ + if (!state) + state = RULE_MEDIATES(rules, AA_CLASS_NET); -#define PROF_ADD 1 -#define PROF_REPLACE 0 + return state; +} -#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED) -/** - * aa_newest_version - find the newest version of @profile - * @profile: the profile to check for newer versions of (NOT NULL) - * - * Returns: newest version of @profile, if @profile is the newest version - * return @profile. - * - * NOTE: the profile returned is not refcounted, The refcount on @profile - * must be held until the caller decides what to do with the returned newest - * version. - */ -static inline struct aa_profile *aa_newest_version(struct aa_profile *profile) +void aa_compute_profile_mediates(struct aa_profile *profile); +static inline bool profile_mediates(struct aa_profile *profile, + unsigned char class) { - while (profile->replacedby) - profile = profile->replacedby; + return label_mediates(&profile->label, class); +} - return profile; +static inline bool profile_mediates_safe(struct aa_profile *profile, + unsigned char class) +{ + return label_mediates_safe(&profile->label, class); } /** @@ -290,19 +355,54 @@ static inline struct aa_profile *aa_newest_version(struct aa_profile *profile) static inline struct aa_profile *aa_get_profile(struct aa_profile *p) { if (p) - kref_get(&(p->base.count)); + kref_get(&(p->label.count)); return p; } /** + * aa_get_profile_not0 - increment refcount on profile @p found via lookup + * @p: profile (MAYBE NULL) + * + * Returns: pointer to @p if @p is NULL will return NULL + * Requires: @p must be held with valid refcount when called + */ +static inline struct aa_profile *aa_get_profile_not0(struct aa_profile *p) +{ + if (p && kref_get_unless_zero(&p->label.count)) + return p; + + return NULL; +} + +/** + * aa_get_profile_rcu - increment a refcount profile that can be replaced + * @p: pointer to profile that can be replaced (NOT NULL) + * + * Returns: pointer to a refcounted profile. + * else NULL if no profile + */ +static inline struct aa_profile *aa_get_profile_rcu(struct aa_profile __rcu **p) +{ + struct aa_profile *c; + + rcu_read_lock(); + do { + c = rcu_dereference(*p); + } while (c && !kref_get_unless_zero(&c->label.count)); + rcu_read_unlock(); + + return c; +} + +/** * aa_put_profile - decrement refcount on profile @p * @p: profile (MAYBE NULL) */ static inline void aa_put_profile(struct aa_profile *p) { if (p) - kref_put(&p->base.count, aa_free_profile_kref); + kref_put(&p->label.count, aa_label_kref); } static inline int AUDIT_MODE(struct aa_profile *profile) @@ -313,6 +413,14 @@ static inline int AUDIT_MODE(struct aa_profile *profile) return profile->audit; } -bool aa_may_manage_policy(int op); +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 new file mode 100644 index 000000000000..d646070fd966 --- /dev/null +++ b/security/apparmor/include/policy_ns.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor policy definitions. + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2017 Canonical Ltd. + */ + +#ifndef __AA_NAMESPACE_H +#define __AA_NAMESPACE_H + +#include <linux/kref.h> + +#include "apparmor.h" +#include "apparmorfs.h" +#include "label.h" +#include "policy.h" + + +/* struct aa_ns_acct - accounting of profiles in namespace + * @max_size: maximum space allowed for all profiles in namespace + * @max_count: maximum number of profiles that can be in this namespace + * @size: current size of profiles + * @count: current count of profiles (includes null profiles) + */ +struct aa_ns_acct { + int max_size; + int max_count; + int size; + int count; +}; + +/* struct aa_ns - namespace for a set of profiles + * @base: common policy + * @parent: parent of namespace + * @lock: lock for modifying the object + * @acct: accounting for the namespace + * @unconfined: special unconfined profile for the namespace + * @sub_ns: list of namespaces under the current namespace. + * @uniq_null: uniq value used for null learning profiles + * @uniq_id: a unique id count for the profiles in the namespace + * @level: level of ns within the tree hierarchy + * @dents: dentries for the namespaces file entries in apparmorfs + * + * An aa_ns defines the set profiles that are searched to determine which + * profile to attach to a task. Profiles can not be shared between aa_ns + * and profile names within a namespace are guaranteed to be unique. When + * profiles in separate namespaces have the same name they are NOT considered + * to be equivalent. + * + * Namespaces are hierarchical and only namespaces and profiles below the + * current namespace are visible. + * + * Namespace names must be unique and can not contain the characters :/\0 + */ +struct aa_ns { + struct aa_policy base; + struct aa_ns *parent; + struct mutex lock; + struct aa_ns_acct acct; + struct aa_profile *unconfined; + struct list_head sub_ns; + atomic_t uniq_null; + long uniq_id; + int level; + long revision; + wait_queue_head_t wait; + + struct aa_labelset labels; + struct list_head rawdata_list; + + 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; + +#define ns_unconfined(NS) (&(NS)->unconfined->label) + +bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns); +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); + +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, + struct dentry *dir); +struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); +void __aa_remove_ns(struct aa_ns *ns); + +static inline struct aa_profile *aa_deref_parent(struct aa_profile *p) +{ + return rcu_dereference_protected(p->parent, + mutex_is_locked(&p->ns->lock)); +} + +/** + * aa_get_ns - increment references count on @ns + * @ns: namespace to increment reference count of (MAYBE NULL) + * + * Returns: pointer to @ns, if @ns is NULL returns NULL + * Requires: @ns must be held with valid refcount when called + */ +static inline struct aa_ns *aa_get_ns(struct aa_ns *ns) +{ + if (ns) + aa_get_profile(ns->unconfined); + + return ns; +} + +/** + * aa_put_ns - decrement refcount on @ns + * @ns: namespace to put reference of + * + * Decrement reference count of @ns and if no longer in use free it + */ +static inline void aa_put_ns(struct aa_ns *ns) +{ + if (ns) + aa_put_profile(ns->unconfined); +} + +/** + * __aa_findn_ns - find a namespace on a list by @name + * @head: list to search for namespace on (NOT NULL) + * @name: name of namespace to look for (NOT NULL) + * @n: length of @name + * Returns: unrefcounted namespace + * + * Requires: rcu_read_lock be held + */ +static inline struct aa_ns *__aa_findn_ns(struct list_head *head, + const char *name, size_t n) +{ + return (struct aa_ns *)__policy_strn_find(head, name, n); +} + +static inline struct aa_ns *__aa_find_ns(struct list_head *head, + const char *name) +{ + return __aa_findn_ns(head, name, strlen(name)); +} + +#endif /* AA_NAMESPACE_H */ diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index a2dcccac45aa..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,16 +6,176 @@ * * 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 #define __POLICY_INTERFACE_H -struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns); +#include <linux/list.h> +#include <linux/kref.h> +#include <linux/dcache.h> +#include <linux/workqueue.h> + + +struct aa_load_ent { + struct list_head list; + struct aa_profile *new; + struct aa_profile *old; + struct aa_profile *rename; + const char *ns_name; +}; + +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; + +enum { + AAFS_LOADDATA_ABI = 0, + 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 + * d_inode(@dentry) when grab a ref from these, @ns->lock must be held + * && __aa_get_loaddata() needs to be used, and the return value + * checked, if NULL the loaddata is already being reaped and should be + * considered dead. + */ +struct aa_loaddata { + struct kref count; + struct list_head list; + struct work_struct work; + struct dentry *dents[AAFS_LOADDATA_NDENTS]; + struct aa_ns *ns; + char *name; + 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; + + /* 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); + +/** + * __aa_get_loaddata - get a reference count to uncounted data reference + * @data: reference to get a count on + * + * Returns: pointer to reference OR NULL if race is lost and reference is + * being repeated. + * Requires: @data->ns->lock held, and the return code MUST be checked + * + * Use only from inode->i_private and @data->list found references + */ +static inline struct aa_loaddata * +__aa_get_loaddata(struct aa_loaddata *data) +{ + if (data && kref_get_unless_zero(&(data->count))) + return data; + + return NULL; +} + +/** + * aa_get_loaddata - get a reference count from a counted data reference + * @data: reference to get a count on + * + * Returns: point to reference + * Requires: @data to have a valid reference count on it. It is a bug + * if the race to reap can be encountered when it is used. + */ +static inline struct aa_loaddata * +aa_get_loaddata(struct aa_loaddata *data) +{ + struct aa_loaddata *tmp = __aa_get_loaddata(data); + + AA_BUG(data && !tmp); + + return tmp; +} + +void __aa_loaddata_update(struct aa_loaddata *data, long revision); +bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); +void aa_loaddata_kref(struct kref *kref); +struct aa_loaddata *aa_loaddata_alloc(size_t size); +static inline void aa_put_loaddata(struct aa_loaddata *data) +{ + if (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 6bd5f33d9533..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,21 +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 -#define AA_DO_TEST 1 -#define AA_ONEXEC 1 - -int aa_getprocattr(struct aa_profile *profile, char **string); -int aa_setprocattr_changehat(char *args, size_t size, int test); -int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test); +int aa_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 d3f4cf027957..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 @@ -34,13 +30,14 @@ struct aa_rlimit { struct rlimit limits[RLIM_NLIMITS]; }; -extern struct aa_fs_entry aa_fs_entry_rlimit[]; +extern struct aa_sfs_entry aa_sfs_entry_rlimit[]; int aa_map_resource(int resource); -int aa_task_setrlimit(struct aa_profile *profile, struct task_struct *, +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_profile *old, struct aa_profile *new); +void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new); static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims) { diff --git a/security/apparmor/include/secid.h b/security/apparmor/include/secid.h new file mode 100644 index 000000000000..6025d3849cf8 --- /dev/null +++ b/security/apparmor/include/secid.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (secid) definitions + * + * 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 + +/* 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/sid.h b/security/apparmor/include/sid.h deleted file mode 100644 index 513ca0e48965..000000000000 --- a/security/apparmor/include/sid.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * AppArmor security module - * - * This file contains AppArmor security identifier (sid) 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. - */ - -#ifndef __AA_SID_H -#define __AA_SID_H - -#include <linux/types.h> - -/* sid value that will not be allocated */ -#define AA_SID_INVALID 0 -#define AA_SID_ALLOC AA_SID_INVALID - -u32 aa_alloc_sid(void); -void aa_free_sid(u32 sid); - -#endif /* __AA_SID_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 c51d2266587e..df5712cea685 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -1,112 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * AppArmor security module * * This file contains AppArmor ipc mediation * * 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. + * Copyright 2009-2017 Canonical Ltd. */ #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" + -/* call back to audit ptrace fields */ -static void audit_cb(struct audit_buffer *ab, void *va) +static inline int map_signal_num(int sig) { - struct common_audit_data *sa = va; - audit_log_format(ab, " target="); - audit_log_untrustedstring(ab, sa->aad->target); + 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; } /** - * aa_audit_ptrace - do auditing for ptrace - * @profile: profile being enforced (NOT NULL) - * @target: profile being traced (NOT NULL) - * @error: error condition + * audit_signal_mask - convert mask to permission string + * @mask: permission mask to convert * - * Returns: %0 or error code + * Returns: pointer to static string */ -static int aa_audit_ptrace(struct aa_profile *profile, - struct aa_profile *target, int error) +static const char *audit_signal_mask(u32 mask) { - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.op = OP_PTRACE; - aad.target = target; - aad.error = error; - - return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_ATOMIC, &sa, - audit_cb); + if (mask & MAY_READ) + return "receive"; + if (mask & MAY_WRITE) + return "send"; + return ""; } /** - * aa_may_ptrace - test if tracer task can trace the tracee - * @tracer_task: task who will do the tracing (NOT NULL) - * @tracer: profile of the task doing the tracing (NOT NULL) - * @tracee: task to be traced - * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH - * - * Returns: %0 else error code if permission denied or error + * audit_signal_cb() - call back for signal specific audit fields + * @ab: audit_buffer (NOT NULL) + * @va: audit struct to audit values of (NOT NULL) */ -int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, - struct aa_profile *tracee, unsigned int mode) +static void audit_signal_cb(struct audit_buffer *ab, void *va) { - /* TODO: currently only based on capability, not extended ptrace - * rules, - * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH - */ + struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); - if (unconfined(tracer) || tracer == tracee) - return 0; - /* log this capability request */ - return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1); + 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(ad->subj_label), ad->peer, + FLAGS_NONE, GFP_ATOMIC); } -/** - * aa_ptrace - do ptrace permission check and auditing - * @tracer: task doing the tracing (NOT NULL) - * @tracee: task being traced (NOT NULL) - * @mode: ptrace mode either PTRACE_MODE_READ || PTRACE_MODE_ATTACH - * - * Returns: %0 else error code if permission denied or error - */ -int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, - unsigned int mode) +static int profile_signal_perm(const struct cred *cred, + struct aa_profile *profile, + struct aa_label *peer, u32 request, + struct apparmor_audit_data *ad) { - /* - * tracer can ptrace tracee when - * - tracer is unconfined || - * - tracer is in complain mode - * - tracer has rules allowing it to trace tracee currently this is: - * - confined by the same profile || - * - tracer profile has CAP_SYS_PTRACE - */ + struct aa_ruleset *rules = profile->label.rules[0]; + struct aa_perms perms; + aa_state_t state; - struct aa_profile *tracer_p = aa_get_task_profile(tracer); - int error = 0; - - if (!unconfined(tracer_p)) { - struct aa_profile *tracee_p = aa_get_task_profile(tracee); + if (profile_unconfined(profile)) + return 0; - error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode); - error = aa_audit_ptrace(tracer_p, tracee_p, error); + 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; + 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_put_profile(tracee_p); - } - aa_put_profile(tracer_p); +int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender, + const struct cred *target_cred, struct aa_label *target, + int sig) +{ + struct aa_profile *profile; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL); - return error; + 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 new file mode 100644 index 000000000000..913678f199c3 --- /dev/null +++ b/security/apparmor/label.c @@ -0,0 +1,2136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor label definitions + * + * Copyright 2017 Canonical Ltd. + */ + +#include <linux/audit.h> +#include <linux/seq_file.h> +#include <linux/sort.h> + +#include "include/apparmor.h" +#include "include/cred.h" +#include "include/label.h" +#include "include/policy.h" +#include "include/secid.h" + + +/* + * the aa_label represents the set of profiles confining an object + * + * Labels maintain a reference count to the set of pointers they reference + * Labels are ref counted by + * tasks and object via the security field/security context off the field + * code - will take a ref count on a label if it needs the label + * beyond what is possible with an rcu_read_lock. + * profiles - each profile is a label + * secids - a pinned secid will keep a refcount of the label it is + * referencing + * objects - inode, files, sockets, ... + * + * Labels are not ref counted by the label set, so they maybe removed and + * freed when no longer in use. + * + */ + +#define PROXY_POISON 97 +#define LABEL_POISON 100 + +static void free_proxy(struct aa_proxy *proxy) +{ + if (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)); + RCU_INIT_POINTER(proxy->label, (struct aa_label *)PROXY_POISON); + kfree(proxy); + } +} + +void aa_proxy_kref(struct kref *kref) +{ + struct aa_proxy *proxy = container_of(kref, struct aa_proxy, count); + + free_proxy(proxy); +} + +struct aa_proxy *aa_alloc_proxy(struct aa_label *label, gfp_t gfp) +{ + struct aa_proxy *new; + + new = kzalloc(sizeof(struct aa_proxy), gfp); + if (new) { + kref_init(&new->count); + rcu_assign_pointer(new->label, aa_get_label(label)); + } + return new; +} + +/* requires profile list write lock held */ +void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) +{ + struct aa_label *tmp; + + AA_BUG(!orig); + AA_BUG(!new); + lockdep_assert_held_write(&labels_set(orig)->lock); + + tmp = rcu_dereference_protected(orig->proxy->label, + &labels_ns(orig)->lock); + rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); + orig->flags |= FLAG_STALE; + aa_put_label(tmp); +} + +static void __proxy_share(struct aa_label *old, struct aa_label *new) +{ + struct aa_proxy *proxy = new->proxy; + + new->proxy = aa_get_proxy(old->proxy); + __aa_proxy_redirect(old, new); + aa_put_proxy(proxy); +} + + +/** + * ns_cmp - compare ns for label set ordering + * @a: ns to compare (NOT NULL) + * @b: ns to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int ns_cmp(struct aa_ns *a, struct aa_ns *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b) + return 0; + + res = a->level - b->level; + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * profile_cmp - profile comparison for set ordering + * @a: profile to compare (NOT NULL) + * @b: profile to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int profile_cmp(struct aa_profile *a, struct aa_profile *b) +{ + int res; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!a->ns); + AA_BUG(!b->ns); + AA_BUG(!a->base.hname); + AA_BUG(!b->base.hname); + + if (a == b || a->base.hname == b->base.hname) + return 0; + res = ns_cmp(a->ns, b->ns); + if (res) + return res; + + return strcmp(a->base.hname, b->base.hname); +} + +/** + * 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) +{ + int i; + + AA_BUG(!a); + AA_BUG(!*a); + AA_BUG(!b); + AA_BUG(!*b); + AA_BUG(an <= 0); + AA_BUG(bn <= 0); + + for (i = 0; i < an && i < bn; i++) { + int res = profile_cmp(a[i], b[i]); + + if (res != 0) + return res; + } + + return an - bn; +} + +static bool vec_is_stale(struct aa_profile **vec, int n) +{ + int i; + + AA_BUG(!vec); + + for (i = 0; i < n; i++) { + if (profile_is_stale(vec[i])) + return true; + } + + return false; +} + +static void accum_label_info(struct aa_label *new) +{ + long u = FLAG_UNCONFINED; + int i; + + AA_BUG(!new); + + /* size == 1 is a profile and flags must be set as part of creation */ + if (new->size == 1) + return; + + 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) +{ + return profile_cmp(*(struct aa_profile **)a, *(struct aa_profile **)b); +} + +/* + * assumes vec is sorted + * Assumes @vec has null terminator at vec[n], and will null terminate + * vec[n - dups] + */ +static inline int unique(struct aa_profile **vec, int n) +{ + int i, pos, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + pos = 0; + for (i = 1; i < n; i++) { + int res = profile_cmp(vec[pos], vec[i]); + + AA_BUG(res > 0, "vec not sorted"); + if (res == 0) { + /* drop duplicate */ + aa_put_profile(vec[i]); + dups++; + continue; + } + pos++; + if (dups) + vec[pos] = vec[i]; + } + + AA_BUG(dups < 0); + + return dups; +} + +/** + * 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 + * + * If @flags & VEC_FLAG_TERMINATE @vec has null terminator at vec[n], and will + * null terminate vec[n - dups] + */ +int aa_vec_unique(struct aa_profile **vec, int n, int flags) +{ + int i, dups = 0; + + AA_BUG(n < 1); + AA_BUG(!vec); + + /* vecs are usually small and inorder, have a fallback for larger */ + if (n > 8) { + sort(vec, n, sizeof(struct aa_profile *), sort_cmp, NULL); + dups = unique(vec, n); + goto out; + } + + /* insertion sort + unique in one */ + for (i = 1; i < n; i++) { + struct aa_profile *tmp = vec[i]; + int pos, j; + + for (pos = i - 1 - dups; pos >= 0; pos--) { + int res = profile_cmp(vec[pos], tmp); + + if (res == 0) { + /* drop duplicate entry */ + aa_put_profile(tmp); + dups++; + goto continue_outer; + } else if (res < 0) + break; + } + /* pos is at entry < tmp, or index -1. Set to insert pos */ + pos++; + + for (j = i - dups; j > pos; j--) + vec[j] = vec[j - 1]; + vec[pos] = tmp; +continue_outer: + ; + } + + AA_BUG(dups < 0); + +out: + if (flags & VEC_FLAG_TERMINATE) + vec[n - dups] = NULL; + + return dups; +} + + +void aa_label_destroy(struct aa_label *label) +{ + AA_BUG(!label); + + if (!label_isprofile(label)) { + struct aa_profile *profile; + struct label_it i; + + aa_put_str(label->hname); + + label_for_each(i, label, profile) { + aa_put_profile(profile); + label->vec[i.i] = (struct aa_profile *) + (LABEL_POISON + (long) i.i); + } + } + + 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); + + label->proxy = (struct aa_proxy *) PROXY_POISON + 1; +} + +void aa_label_free(struct aa_label *label) +{ + if (!label) + return; + + aa_label_destroy(label); + kfree(label); +} + +static void label_free_switch(struct aa_label *label) +{ + if (label->flags & FLAG_NS_COUNT) + aa_free_ns(labels_ns(label)); + else if (label_isprofile(label)) + aa_free_profile(labels_profile(label)); + else + aa_label_free(label); +} + +static void label_free_rcu(struct rcu_head *head) +{ + struct aa_label *label = container_of(head, struct aa_label, rcu); + + if (label->flags & FLAG_IN_TREE) + (void) aa_label_remove(label); + label_free_switch(label); +} + +void aa_label_kref(struct kref *kref) +{ + struct aa_label *label = container_of(kref, struct aa_label, count); + struct aa_ns *ns = labels_ns(label); + + if (!ns) { + /* never live, no rcu callback needed, just using the fn */ + label_free_switch(label); + return; + } + /* TODO: update labels_profile macro so it works here */ + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.profiles)); + AA_BUG(label_isprofile(label) && + on_list_rcu(&label->vec[0]->base.list)); + + /* TODO: if compound label and not stale add to reclaim cache */ + call_rcu(&label->rcu, label_free_rcu); +} + +static void label_free_or_put_new(struct aa_label *label, struct aa_label *new) +{ + if (label != new) + /* need to free directly to break circular ref with proxy */ + aa_label_free(new); + else + aa_put_label(new); +} + +bool aa_label_init(struct aa_label *label, int size, gfp_t gfp) +{ + AA_BUG(!label); + AA_BUG(size < 1); + + if (aa_alloc_secid(label, gfp) < 0) + return false; + + label->size = size; /* doesn't include null */ + label->vec[size] = NULL; /* null terminate */ + kref_init(&label->count); + RB_CLEAR_NODE(&label->node); + + return true; +} + +/** + * aa_label_alloc - allocate a label with a profile vector of @size length + * @size: size of profile vector in the label + * @proxy: proxy to use OR null if to allocate a new one + * @gfp: memory allocation type + * + * Returns: new label + * else NULL if failed + */ +struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) +{ + struct aa_label *new; + + AA_BUG(size < 1); + + /* + 1 for null terminator entry on vec */ + 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, gfp)) + goto fail; + + if (!proxy) { + proxy = aa_alloc_proxy(new, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + /* just set new's proxy, don't redirect proxy here if it was passed in*/ + new->proxy = proxy; + + return new; + +fail: + kfree(new); + + return NULL; +} + + +/** + * label_cmp - label comparison for set ordering + * @a: label to compare (NOT NULL) + * @b: label to compare (NOT NULL) + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_cmp(struct aa_label *a, struct aa_label *b) +{ + AA_BUG(!b); + + if (a == b) + return 0; + + return vec_cmp(a->vec, a->size, b->vec, b->size); +} + +/* helper fn for label_for_each_confined */ +int aa_label_next_confined(struct aa_label *label, int i) +{ + AA_BUG(!label); + AA_BUG(i < 0); + + for (; i < label->size; i++) { + if (!profile_unconfined(label->vec[i])) + return i; + } + + return i; +} + +/** + * __aa_label_next_not_in_set - return the next profile of @sub not in @set + * @I: label iterator + * @set: label to test against + * @sub: label to if is subset of @set + * + * Returns: profile in @sub that is not in @set, with iterator set pos after + * else NULL if @sub is a subset of @set + */ +struct aa_profile *__aa_label_next_not_in_set(struct label_it *I, + struct aa_label *set, + struct aa_label *sub) +{ + AA_BUG(!set); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > set->size); + AA_BUG(!sub); + AA_BUG(I->j < 0); + AA_BUG(I->j > sub->size); + + while (I->j < sub->size && I->i < set->size) { + int res = profile_cmp(sub->vec[I->j], set->vec[I->i]); + + if (res == 0) { + (I->j)++; + (I->i)++; + } else if (res > 0) + (I->i)++; + else + return sub->vec[(I->j)++]; + } + + if (I->j < sub->size) + return sub->vec[(I->j)++]; + + return NULL; +} + +/** + * aa_label_is_subset - test if @sub is a subset of @set + * @set: label to test against + * @sub: label to test if is subset of @set + * + * Returns: true if @sub is subset of @set + * else false + */ +bool aa_label_is_subset(struct aa_label *set, struct aa_label *sub) +{ + struct label_it i = { }; + + AA_BUG(!set); + AA_BUG(!sub); + + if (sub == set) + return true; + + return __aa_label_next_not_in_set(&i, set, sub) == NULL; +} + +/** + * 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 + * @label: label to remove + * @new: label to redirect to + * + * Requires: labels_set(@label)->lock write_lock + * Returns: true if the label was in the tree and removed + */ +static bool __label_remove(struct aa_label *label, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(label); + + AA_BUG(!ls); + AA_BUG(!label); + lockdep_assert_held_write(&ls->lock); + + if (new) + __aa_proxy_redirect(label, new); + + if (!label_is_stale(label)) + __label_make_stale(label); + + if (label->flags & FLAG_IN_TREE) { + rb_erase(&label->node, &ls->root); + label->flags &= ~FLAG_IN_TREE; + return true; + } + + return false; +} + +/** + * __label_replace - replace @old with @new in label set + * @old: label to remove from label set + * @new: label to replace @old with + * + * Requires: labels_set(@old)->lock write_lock + * valid ref count be held on @new + * Returns: true if @old was in set and replaced by @new + * + * Note: current implementation requires label set be order in such a way + * that @new directly replaces @old position in the set (ie. + * using pointer comparison of the label address would not work) + */ +static bool __label_replace(struct aa_label *old, struct aa_label *new) +{ + struct aa_labelset *ls = labels_set(old); + + AA_BUG(!ls); + AA_BUG(!old); + AA_BUG(!new); + lockdep_assert_held_write(&ls->lock); + AA_BUG(new->flags & FLAG_IN_TREE); + + if (!label_is_stale(old)) + __label_make_stale(old); + + if (old->flags & FLAG_IN_TREE) { + rb_replace_node(&old->node, &new->node, &ls->root); + old->flags &= ~FLAG_IN_TREE; + new->flags |= FLAG_IN_TREE; + accum_label_info(new); + return true; + } + + return false; +} + +/** + * __label_insert - attempt to insert @l into a label set + * @ls: set of labels to insert @l into (NOT NULL) + * @label: new label to insert (NOT NULL) + * @replace: whether insertion should replace existing entry that is not stale + * + * Requires: @ls->lock + * caller to hold a valid ref on l + * if @replace is true l has a preallocated proxy associated + * Returns: @l if successful in inserting @l - with additional refcount + * else ref counted equivalent label that is already in the set, + * the else condition only happens if @replace is false + */ +static struct aa_label *__label_insert(struct aa_labelset *ls, + struct aa_label *label, bool replace) +{ + struct rb_node **new, *parent = NULL; + + AA_BUG(!ls); + AA_BUG(!label); + AA_BUG(labels_set(label) != ls); + lockdep_assert_held_write(&ls->lock); + AA_BUG(label->flags & FLAG_IN_TREE); + + /* Figure out where to put new node */ + new = &ls->root.rb_node; + while (*new) { + struct aa_label *this = rb_entry(*new, struct aa_label, node); + int result = label_cmp(label, this); + + parent = *new; + if (result == 0) { + /* !__aa_get_label means queued for destruction, + * so replace in place, however the label has + * died before the replacement so do not share + * the proxy + */ + if (!replace && !label_is_stale(this)) { + if (__aa_get_label(this)) + return this; + } else + __proxy_share(this, label); + AA_BUG(!__label_replace(this, label)); + return aa_get_label(label); + } else if (result < 0) + new = &((*new)->rb_left); + else /* (result > 0) */ + new = &((*new)->rb_right); + } + + /* Add new node and rebalance tree. */ + 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); +} + +/** + * __vec_find - find label that matches @vec in label set + * @vec: vec of profiles to find matching label for (NOT NULL) + * @n: length of @vec + * + * Requires: @vec_labelset(vec) lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if matching label is in tree + * ref counted label that is equiv to @l in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *__vec_find(struct aa_profile **vec, int n) +{ + struct rb_node *node; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + node = vec_labelset(vec, n)->root.rb_node; + while (node) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + int result = vec_cmp(this->vec, this->size, vec, n); + + if (result > 0) + node = node->rb_left; + else if (result < 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + +/** + * __label_find - find label @label in label set + * @label: label to find (NOT NULL) + * + * Requires: labels_set(@label)->lock held + * caller to hold a valid ref on l + * + * Returns: ref counted @label if @label is in tree OR + * ref counted label that is equiv to @label in tree + * else NULL if @label or equiv is not in tree + */ +static struct aa_label *__label_find(struct aa_label *label) +{ + AA_BUG(!label); + + return __vec_find(label->vec, label->size); +} + + +/** + * aa_label_remove - remove a label from the labelset + * @label: label to remove + * + * Returns: true if @label was removed from the tree + * else @label was not in tree so it could not be removed + */ +bool aa_label_remove(struct aa_label *label) +{ + struct aa_labelset *ls = labels_set(label); + unsigned long flags; + bool res; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(label, ns_unconfined(labels_ns(label))); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/** + * aa_label_replace - replace a label @old with a new version @new + * @old: label to replace + * @new: label replacing @old + * + * Returns: true if @old was in tree and replaced + * else @old was not in tree, and @new was not inserted + */ +bool aa_label_replace(struct aa_label *old, struct aa_label *new) +{ + unsigned long flags; + bool res; + + if (name_is_shared(old, new) && labels_ns(old) == labels_ns(new)) { + write_lock_irqsave(&labels_set(old)->lock, flags); + if (old->proxy != new->proxy) + __proxy_share(old, new); + else + __aa_proxy_redirect(old, new); + res = __label_replace(old, new); + write_unlock_irqrestore(&labels_set(old)->lock, flags); + } else { + struct aa_label *l; + struct aa_labelset *ls = labels_set(old); + + write_lock_irqsave(&ls->lock, flags); + res = __label_remove(old, new); + if (labels_ns(old) != labels_ns(new)) { + write_unlock_irqrestore(&ls->lock, flags); + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + } + l = __label_insert(ls, new, true); + res = (l == new); + write_unlock_irqrestore(&ls->lock, flags); + aa_put_label(l); + } + + return res; +} + +/** + * vec_find - find label @l in label set + * @vec: array of profiles to find equiv label for (NOT NULL) + * @n: length of @vec + * + * Returns: refcounted label if @vec equiv is in tree + * else NULL if @vec equiv is not in tree + */ +static struct aa_label *vec_find(struct aa_profile **vec, int n) +{ + struct aa_labelset *ls; + struct aa_label *label; + unsigned long flags; + + AA_BUG(!vec); + AA_BUG(!*vec); + AA_BUG(n <= 0); + + ls = vec_labelset(vec, n); + read_lock_irqsave(&ls->lock, flags); + label = __vec_find(vec, n); + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/* requires sort and merge done first */ +static struct aa_label *vec_create_and_insert_label(struct aa_profile **vec, + int len, gfp_t gfp) +{ + struct aa_label *label = NULL; + struct aa_labelset *ls; + unsigned long flags; + struct aa_label *new; + int i; + + AA_BUG(!vec); + + if (len == 1) + return aa_get_label(&vec[0]->label); + + ls = labels_set(&vec[len - 1]->label); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + */ + new = aa_label_alloc(len, NULL, gfp); + if (!new) + return NULL; + + for (i = 0; i < len; i++) + new->vec[i] = aa_get_profile(vec[i]); + + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(ls, new, false); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(label, new); + + return label; +} + +struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len, + gfp_t gfp) +{ + struct aa_label *label = vec_find(vec, len); + + if (label) + return label; + + return vec_create_and_insert_label(vec, len, gfp); +} + + +/** + * aa_label_insert - insert label @label into @ls or return existing label + * @ls: labelset to insert @label into + * @label: label to insert + * + * Requires: caller to hold a valid ref on @label + * + * Returns: ref counted @label if successful in inserting @label + * else ref counted equivalent label that is already in the set + */ +struct aa_label *aa_label_insert(struct aa_labelset *ls, struct aa_label *label) +{ + struct aa_label *l; + unsigned long flags; + + AA_BUG(!ls); + AA_BUG(!label); + + /* check if label exists before taking lock */ + if (!label_is_stale(label)) { + read_lock_irqsave(&ls->lock, flags); + l = __label_find(label); + read_unlock_irqrestore(&ls->lock, flags); + if (l) + return l; + } + + write_lock_irqsave(&ls->lock, flags); + l = __label_insert(ls, label, false); + write_unlock_irqrestore(&ls->lock, flags); + + return l; +} + + +/** + * aa_label_next_in_merge - find the next profile when merging @a and @b + * @I: label iterator + * @a: label to merge + * @b: label to merge + * + * Returns: next profile + * else null if no more profiles + */ +struct aa_profile *aa_label_next_in_merge(struct label_it *I, + struct aa_label *a, + struct aa_label *b) +{ + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!I); + AA_BUG(I->i < 0); + AA_BUG(I->i > a->size); + AA_BUG(I->j < 0); + AA_BUG(I->j > b->size); + + if (I->i < a->size) { + if (I->j < b->size) { + int res = profile_cmp(a->vec[I->i], b->vec[I->j]); + + if (res > 0) + return b->vec[(I->j)++]; + if (res == 0) + (I->j)++; + } + + return a->vec[(I->i)++]; + } + + if (I->j < b->size) + return b->vec[(I->j)++]; + + return NULL; +} + +/** + * label_merge_cmp - cmp of @a merging with @b against @z for set ordering + * @a: label to merge then compare (NOT NULL) + * @b: label to merge then compare (NOT NULL) + * @z: label to compare merge against (NOT NULL) + * + * Assumes: using the most recent versions of @a, @b, and @z + * + * Returns: <0 if a < b + * ==0 if a == b + * >0 if a > b + */ +static int label_merge_cmp(struct aa_label *a, struct aa_label *b, + struct aa_label *z) +{ + struct aa_profile *p = NULL; + struct label_it i = { }; + int k; + + AA_BUG(!a); + AA_BUG(!b); + AA_BUG(!z); + + for (k = 0; + k < z->size && (p = aa_label_next_in_merge(&i, a, b)); + k++) { + int res = profile_cmp(p, z->vec[k]); + + if (res != 0) + return res; + } + + if (p) + return 1; + else if (k < z->size) + return -1; + return 0; +} + +/** + * label_merge_insert - create a new label by merging @a and @b + * @new: preallocated label to merge into (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: preallocated proxy + * + * Returns: ref counted label either @new if merge is unique + * @a if @b is a subset of @a + * @b if @a is a subset of @b + * + * NOTE: will not use @new if the merge results in @new == @a or @b + * + * Must be used within labelset write lock to avoid racing with + * setting labels stale. + */ +static struct aa_label *label_merge_insert(struct aa_label *new, + struct aa_label *a, + struct aa_label *b) +{ + struct aa_label *label; + struct aa_labelset *ls; + struct aa_profile *next; + struct label_it i; + unsigned long flags; + int k = 0, invcount = 0; + bool stale = false; + + AA_BUG(!a); + AA_BUG(a->size < 0); + AA_BUG(!b); + AA_BUG(b->size < 0); + AA_BUG(!new); + AA_BUG(new->size < a->size + b->size); + + label_for_each_in_merge(i, a, b, next) { + AA_BUG(!next); + if (profile_is_stale(next)) { + new->vec[k] = aa_get_newest_profile(next); + AA_BUG(!new->vec[k]->label.proxy); + AA_BUG(!new->vec[k]->label.proxy->label); + if (next->label.proxy != new->vec[k]->label.proxy) + invcount++; + k++; + stale = true; + } else + new->vec[k++] = aa_get_profile(next); + } + /* set to actual size which is <= allocated len */ + new->size = k; + new->vec[k] = NULL; + + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + label = aa_get_label(&new->vec[0]->label); + return label; + } + } else if (!stale) { + /* + * merge could be same as a || b, note: it is not possible + * for new->size == a->size == b->size unless a == b + */ + if (k == a->size) + return aa_get_label(a); + else if (k == b->size) + return aa_get_label(b); + } + ls = labels_set(new); + write_lock_irqsave(&ls->lock, flags); + label = __label_insert(labels_set(new), new, false); + write_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * labelset_of_merge - find which labelset a merged label should be inserted + * @a: label to merge and insert + * @b: label to merge and insert + * + * Returns: labelset that the merged label should be inserted into + */ +static struct aa_labelset *labelset_of_merge(struct aa_label *a, + struct aa_label *b) +{ + struct aa_ns *nsa = labels_ns(a); + struct aa_ns *nsb = labels_ns(b); + + if (ns_cmp(nsa, nsb) <= 0) + return &nsa->labels; + return &nsb->labels; +} + +/** + * __label_find_merge - find label that is equiv to merge of @a and @b + * @ls: set of labels to search (NOT NULL) + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: ls->lock read_lock held + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +static struct aa_label *__label_find_merge(struct aa_labelset *ls, + struct aa_label *a, + struct aa_label *b) +{ + struct rb_node *node; + + AA_BUG(!ls); + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return __label_find(a); + + node = ls->root.rb_node; + while (node) { + struct aa_label *this = container_of(node, struct aa_label, + node); + int result = label_merge_cmp(a, b, this); + + if (result < 0) + node = node->rb_left; + else if (result > 0) + node = node->rb_right; + else + return __aa_get_label(this); + } + + return NULL; +} + + +/** + * aa_label_find_merge - find label that is equiv to merge of @a and @b + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * + * Requires: labels be fully constructed with a valid ns + * + * Returns: ref counted label that is equiv to merge of @a and @b + * else NULL if merge of @a and @b is not in set + */ +struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) +{ + struct aa_labelset *ls; + struct aa_label *label, *ar = NULL, *br = NULL; + unsigned long flags; + + AA_BUG(!a); + AA_BUG(!b); + + if (label_is_stale(a)) + a = ar = aa_get_newest_label(a); + if (label_is_stale(b)) + b = br = aa_get_newest_label(b); + ls = labelset_of_merge(a, b); + read_lock_irqsave(&ls->lock, flags); + label = __label_find_merge(ls, a, b); + read_unlock_irqrestore(&ls->lock, flags); + aa_put_label(ar); + aa_put_label(br); + + return label; +} + +/** + * aa_label_merge - attempt to insert new merged label of @a and @b + * @a: label to merge with @b (NOT NULL) + * @b: label to merge with @a (NOT NULL) + * @gfp: memory allocation type + * + * Requires: caller to hold valid refs on @a and @b + * labels be fully constructed with a valid ns + * + * Returns: ref counted new label if successful in inserting merge of a & b + * else ref counted equivalent label that is already in the set. + * else NULL if could not create label (-ENOMEM) + */ +struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, + gfp_t gfp) +{ + struct aa_label *label = NULL; + + AA_BUG(!a); + AA_BUG(!b); + + if (a == b) + return aa_get_newest_label(a); + + /* TODO: enable when read side is lockless + * check if label exists before taking locks + if (!label_is_stale(a) && !label_is_stale(b)) + label = aa_label_find_merge(a, b); + */ + + if (!label) { + struct aa_label *new; + + a = aa_get_newest_label(a); + b = aa_get_newest_label(b); + + /* could use label_merge_len(a, b), but requires double + * comparison for small savings + */ + new = aa_label_alloc(a->size + b->size, NULL, gfp); + if (!new) + goto out; + + label = label_merge_insert(new, a, b); + label_free_or_put_new(label, new); +out: + aa_put_label(a); + aa_put_label(b); + } + + return label; +} + +/* 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 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(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(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 + * @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 + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for A//&B//&C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_compound_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) +{ + struct aa_profile *tp; + struct label_it i; + + /* find first subcomponent that is visible */ + label_for_each(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = match_component(profile, rules, tp, state); + if (!state) + goto fail; + goto next; + } + + /* no component visible */ + *perms = allperms; + return 0; + +next: + label_for_each_cont(i, label, tp) { + if (!aa_ns_visible(profile->ns, tp->ns, subns)) + continue; + state = aa_dfa_match(rules->policy->dfa, state, "//&"); + state = match_component(profile, rules, tp, state); + if (!state) + goto fail; + } + *perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, perms); + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return state; +} + +/** + * 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 + * @request: permissions to request + * @perms: an initialized perms struct to add accumulation to + * + * Returns: 0 on success else ERROR + * + * For the label A//&B//&C this does the perm match for each of A and B and C + * @perms should be preinitialized with allperms OR a previous permission + * check to be stacked. + */ +static int label_components_match(struct aa_profile *profile, + 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; + 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, rules, tp, start); + if (!state) + goto fail; + goto next; + } + + /* no subcomponents visible - no change in perms */ + return 0; + +next: + 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, rules, tp, start); + if (!state) + goto fail; + tmp = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &tmp); + aa_perms_accum(perms, &tmp); + } + + if ((perms->allow & request) != request) + return -EACCES; + + return 0; + +fail: + *perms = nullperms; + return -EACCES; +} + +/** + * 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 + * @request: permission request + * @perms: Returns computed perms (NOT NULL) + * + * Returns: the state the match finished in, may be the none matching state + */ +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, rules, label, state, subns, + request, perms); + if (!error) + return error; + + *perms = allperms; + return label_components_match(profile, rules, label, state, subns, + request, perms); +} + + +/** + * aa_update_label_name - update a label to have a stored name + * @ns: ns being viewed from (NOT NULL) + * @label: label to update (NOT NULL) + * @gfp: type of memory allocation + * + * Requires: labels_set(label) not locked in caller + * + * note: only updates the label name if it does not have a name already + * and if it is in the labelset + */ +bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) +{ + struct aa_labelset *ls; + unsigned long flags; + char __counted *name; + bool res = false; + + AA_BUG(!ns); + AA_BUG(!label); + + if (label->hname || labels_ns(label) != ns) + return res; + + if (aa_label_acntsxprint(&name, ns, label, FLAGS_NONE, gfp) < 0) + return res; + + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + if (!label->hname && label->flags & FLAG_IN_TREE) { + label->hname = name; + res = true; + } else + aa_put_str(name); + write_unlock_irqrestore(&ls->lock, flags); + + return res; +} + +/* + * cached label name is present and visible + * @label->hname only exists if label is namespace hierarchical + */ +static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + if (label->hname && (!ns || labels_ns(label) == ns) && + !(flags & ~FLAG_SHOW_MODE)) + return true; + + return false; +} + +/* helper macro for snprint routines */ +#define update_for_len(total, len, size, str) \ +do { \ + size_t ulen = len; \ + \ + AA_BUG(len < 0); \ + total += ulen; \ + ulen = min(ulen, size); \ + size -= ulen; \ + str += ulen; \ +} while (0) + +/** + * aa_profile_snxprint - print a profile name to a buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @view: namespace profile is being viewed from + * @profile: profile to view (NOT NULL) + * @flags: whether to include the mode string + * @prev_ns: last ns printed when used in compound print + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: will not print anything if the profile is not visible + */ +static int aa_profile_snxprint(char *str, size_t size, struct aa_ns *view, + struct aa_profile *profile, int flags, + struct aa_ns **prev_ns) +{ + const char *ns_name = NULL; + + AA_BUG(!str && size != 0); + AA_BUG(!profile); + + if (!view) + view = profiles_ns(profile); + + if (view != profile->ns && + (!prev_ns || (*prev_ns != profile->ns))) { + if (prev_ns) + *prev_ns = profile->ns; + ns_name = aa_ns_name(view, profile->ns, + flags & FLAG_VIEW_SUBNS); + if (ns_name == aa_hidden_ns_name) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", ns_name); + } + } + + if ((flags & FLAG_SHOW_MODE) && profile != profile->ns->unconfined) { + const char *modestr = aa_profile_mode_names[profile->mode]; + + if (ns_name) + return snprintf(str, size, ":%s:%s (%s)", ns_name, + profile->base.hname, modestr); + return snprintf(str, size, "%s (%s)", profile->base.hname, + modestr); + } + + if (ns_name) + return snprintf(str, size, ":%s:%s", ns_name, + profile->base.hname); + return snprintf(str, size, "%s", profile->base.hname); +} + +static const char *label_modename(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + struct aa_profile *profile; + struct label_it i; + int mode = -1, count = 0; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, flags & FLAG_VIEW_SUBNS)) { + 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; + if (mode == -1) + mode = profile->mode; + else if (mode != profile->mode) + return "mixed"; + } + } + + if (count == 0) + return "-"; + if (mode == -1) + /* everything was unconfined */ + mode = APPARMOR_UNCONFINED; + + return aa_profile_mode_names[mode]; +} + +/* if any visible label is not unconfined the display_mode returns true */ +static inline bool display_mode(struct aa_ns *ns, struct aa_label *label, + int flags) +{ + if ((flags & FLAG_SHOW_MODE)) { + struct aa_profile *profile; + struct label_it i; + + label_for_each(i, label, profile) { + if (aa_ns_visible(ns, profile->ns, + flags & FLAG_VIEW_SUBNS) && + profile != profile->ns->unconfined) + return true; + } + /* only ns->unconfined in set of profiles in ns */ + return false; + } + + return false; +} + +/** + * aa_label_snxprint - print a label name to a string buffer + * @str: buffer to write to. (MAY BE NULL if @size == 0) + * @size: size of buffer + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: whether to include the mode string + * + * Returns: size of name written or would be written if larger than + * available buffer + * + * Note: labels do not have to be strictly hierarchical to the ns as + * objects may be shared across different namespaces and thus + * pickup labeling from each ns. If a particular part of the + * label is not visible it will just be excluded. And if none + * of the label is visible "---" will be used. + */ +int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, + struct aa_label *label, int flags) +{ + struct aa_profile *profile; + struct aa_ns *prev_ns = NULL; + struct label_it i; + int count = 0, total = 0; + ssize_t len; + + AA_BUG(!str && size != 0); + AA_BUG(!label); + + 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)) { + if (count > 0) { + len = snprintf(str, size, "//&"); + update_for_len(total, len, size, str); + } + len = aa_profile_snxprint(str, size, ns, profile, + flags & FLAG_VIEW_SUBNS, + &prev_ns); + update_for_len(total, len, size, str); + count++; + } + } + + if (count == 0) { + if (flags & FLAG_HIDDEN_UNCONFINED) + return snprintf(str, size, "%s", "unconfined"); + return snprintf(str, size, "%s", aa_hidden_ns_name); + } + + /* count == 1 && ... is for backwards compat where the mode + * is not displayed for 'unconfined' in the current ns + */ + if (display_mode(ns, label, flags)) { + len = snprintf(str, size, " (%s)", + label_modename(ns, label, flags)); + update_for_len(total, len, size, str); + } + + return total; +} +#undef update_for_len + +/** + * aa_label_asxprint - allocate a string buffer and print label into it + * @strp: Returns - the allocated buffer with the label name. (NOT NULL) + * @ns: namespace profile is being viewed from + * @label: label to view (NOT NULL) + * @flags: flags controlling what label info is printed + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_asxprint(char **strp, struct aa_ns *ns, struct aa_label *label, + int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = kmalloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + +/** + * aa_label_acntsxprint - allocate a __counted string buffer and print label + * @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 + * @gfp: kernel memory allocation type + * + * Returns: size of name written or would be written if larger than + * available buffer + */ +int aa_label_acntsxprint(char __counted **strp, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + int size; + + AA_BUG(!strp); + AA_BUG(!label); + + size = aa_label_snxprint(NULL, 0, ns, label, flags); + if (size < 0) + return size; + + *strp = aa_str_alloc(size + 1, gfp); + if (!*strp) + return -ENOMEM; + return aa_label_snxprint(*strp, size + 1, ns, label, flags); +} + + +void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + const char *str; + char *name = NULL; + int len; + + AA_BUG(!ab); + AA_BUG(!label); + + if (!use_label_hname(ns, label, flags) || + display_mode(ns, label, flags)) { + len = aa_label_asxprint(&name, ns, label, flags, gfp); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); + return; + } + str = name; + } else { + str = (char *) label->hname; + len = strlen(str); + } + if (audit_string_contains_control(str, len)) + audit_log_n_hex(ab, str, len); + else + audit_log_n_string(ab, str, len); + + kfree(name); +} + +void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, + struct aa_label *label, int flags, gfp_t gfp) +{ + AA_BUG(!f); + AA_BUG(!label); + + if (!use_label_hname(ns, label, flags)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); + return; + } + 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_puts(f, label->hname); +} + +void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, + gfp_t gfp) +{ + AA_BUG(!label); + + if (!use_label_hname(ns, label, flags)) { + char *str; + int len; + + len = aa_label_asxprint(&str, ns, label, flags, gfp); + if (len < 0) { + AA_DEBUG(DEBUG_LABEL, "label print error"); + return; + } + pr_info("%s", str); + kfree(str); + } else if (display_mode(ns, label, flags)) + pr_info("%s (%s)", label->hname, + label_modename(ns, label, flags)); + else + pr_info("%s", label->hname); +} + +void aa_label_printk(struct aa_label *label, gfp_t gfp) +{ + struct aa_ns *ns = aa_get_current_ns(); + + aa_label_xprintk(ns, label, FLAG_VIEW_SUBNS, gfp); + aa_put_ns(ns); +} + +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 = aa_label_strn_split(str, end - str); + split; + split = aa_label_strn_split(str, end - str)) { + count++; + str = split + 3; + } + + return count; +} + +/* + * ensure stacks with components like + * :ns:A//&B + * have :ns: applied to both 'A' and 'B' by making the lookup relative + * to the base if the lookup specifies an ns, else making the stacked lookup + * relative to the last embedded ns in the string. + */ +static struct aa_profile *fqlookupn_profile(struct aa_label *base, + struct aa_label *currentbase, + const char *str, size_t n) +{ + const char *first = skipn_spaces(str, n); + + if (first && *first == ':') + return aa_fqlookupn_profile(base, str, n); + + return aa_fqlookupn_profile(currentbase, str, n); +} + +/** + * 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 & + * + * Returns: the matching refcounted label if present + * else ERRPTR + */ +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; + const char *end = str + n; + const char *split; + + AA_BUG(!base); + AA_BUG(!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; + len += stack; + if (*str == '&') + str++; + } + + error = vec_setup(profile, vec, len, gfp); + if (error) + return ERR_PTR(error); + + for (i = 0; i < stack; i++) + vec[i] = aa_get_profile(base->vec[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; + /* + * if component specified a new ns it becomes the new base + * so that subsequent lookups are relative to it + */ + if (vec[i]->ns != labels_ns(currbase)) + currbase = &vec[i]->label; + str = split + 3; + 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, end - str); + if (!vec[i]) + goto fail; + } + if (len == 1) + /* no need to free vec as len < LOCAL_VEC_ENTRIES */ + return &vec[0]->label; + + len -= aa_vec_unique(vec, len, VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (len == 1) { + label = aa_get_label(&vec[0]->label); + goto out; + } + + if (create) + label = aa_vec_find_or_create_label(vec, len, gfp); + else + label = vec_find(vec, len); + if (!label) + goto fail; + +out: + /* use adjusted len from after vec_unique, not original */ + vec_cleanup(profile, vec, len); + return label; + +fail: + label = ERR_PTR(-ENOENT); + 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 + * @ls: label set to cleanup (NOT NULL) + * + * Labels that are removed from the set may still exist beyond the set + * being destroyed depending on their reference counting + */ +void aa_labelset_destroy(struct aa_labelset *ls) +{ + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + write_lock_irqsave(&ls->lock, flags); + for (node = rb_first(&ls->root); node; node = rb_first(&ls->root)) { + struct aa_label *this = rb_entry(node, struct aa_label, node); + + if (labels_ns(this) != root_ns) + __label_remove(this, + ns_unconfined(labels_ns(this)->parent)); + else + __label_remove(this, NULL); + } + write_unlock_irqrestore(&ls->lock, flags); +} + +/* + * @ls: labelset to init (NOT NULL) + */ +void aa_labelset_init(struct aa_labelset *ls) +{ + AA_BUG(!ls); + + rwlock_init(&ls->lock); + ls->root = RB_ROOT; +} + +static struct aa_label *labelset_next_stale(struct aa_labelset *ls) +{ + struct aa_label *label; + struct rb_node *node; + unsigned long flags; + + AA_BUG(!ls); + + read_lock_irqsave(&ls->lock, flags); + + __labelset_for_each(ls, node) { + label = rb_entry(node, struct aa_label, node); + if ((label_is_stale(label) || + vec_is_stale(label->vec, label->size)) && + __aa_get_label(label)) + goto out; + + } + label = NULL; + +out: + read_unlock_irqrestore(&ls->lock, flags); + + return label; +} + +/** + * __label_update - insert updated version of @label into labelset + * @label: the label to update/replace + * + * Returns: new label that is up to date + * else NULL on failure + * + * Requires: @ns lock be held + * + * Note: worst case is the stale @label does not get updated and has + * to be updated at a later time. + */ +static struct aa_label *__label_update(struct aa_label *label) +{ + struct aa_label *new, *tmp; + struct aa_labelset *ls; + unsigned long flags; + int i, invcount = 0; + + AA_BUG(!label); + AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); + + new = aa_label_alloc(label->size, label->proxy, GFP_KERNEL); + if (!new) + return NULL; + + /* + * while holding the ns_lock will stop profile replacement, removal, + * and label updates, label merging and removal can be occurring + */ + ls = labels_set(label); + write_lock_irqsave(&ls->lock, flags); + for (i = 0; i < label->size; i++) { + AA_BUG(!label->vec[i]); + new->vec[i] = aa_get_newest_profile(label->vec[i]); + AA_BUG(!new->vec[i]); + AA_BUG(!new->vec[i]->label.proxy); + AA_BUG(!new->vec[i]->label.proxy->label); + if (new->vec[i]->label.proxy != label->vec[i]->label.proxy) + invcount++; + } + + /* updated stale label by being removed/renamed from labelset */ + if (invcount) { + new->size -= aa_vec_unique(&new->vec[0], new->size, + VEC_FLAG_TERMINATE); + /* TODO: deal with reference labels */ + if (new->size == 1) { + tmp = aa_get_label(&new->vec[0]->label); + AA_BUG(tmp == label); + goto remove; + } + if (labels_set(label) != labels_set(new)) { + write_unlock_irqrestore(&ls->lock, flags); + tmp = aa_label_insert(labels_set(new), new); + write_lock_irqsave(&ls->lock, flags); + goto remove; + } + } else + AA_BUG(labels_ns(label) != labels_ns(new)); + + tmp = __label_insert(labels_set(label), new, true); +remove: + /* ensure label is removed, and redirected correctly */ + __label_remove(label, tmp); + write_unlock_irqrestore(&ls->lock, flags); + label_free_or_put_new(tmp, new); + + return tmp; +} + +/** + * __labelset_update - update labels in @ns + * @ns: namespace to update labels in (NOT NULL) + * + * Requires: @ns lock be held + * + * Walk the labelset ensuring that all labels are up to date and valid + * Any label that has a stale component is marked stale and replaced and + * by an updated version. + * + * If failures happen due to memory pressures then stale labels will + * be left in place until the next pass. + */ +static void __labelset_update(struct aa_ns *ns) +{ + struct aa_label *label; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + do { + label = labelset_next_stale(&ns->labels); + if (label) { + struct aa_label *l = __label_update(label); + + aa_put_label(l); + aa_put_label(label); + } + } while (label); +} + +/** + * __aa_labelset_update_subtree - update all labels with a stale component + * @ns: ns to start update at (NOT NULL) + * + * Requires: @ns lock be held + * + * Invalidates labels based on @p in @ns and any children namespaces. + */ +void __aa_labelset_update_subtree(struct aa_ns *ns) +{ + struct aa_ns *child; + + AA_BUG(!ns); + AA_BUG(!mutex_is_locked(&ns->lock)); + + __labelset_update(ns); + + list_for_each_entry(child, &ns->sub_ns, base.list) { + mutex_lock_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 fcfe0233574c..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,13 +6,9 @@ * * 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/mm.h> #include <linux/slab.h> #include <linux/string.h> @@ -19,39 +16,197 @@ #include "include/audit.h" #include "include/apparmor.h" +#include "include/lib.h" +#include "include/perms.h" +#include "include/policy.h" + +struct aa_perms nullperms; +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. + * 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 + */ +static int val_mask_to_str(char *str, size_t size, + const struct val_table_ent *table, u32 mask) +{ + 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; + } + } + + 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 * - * NOTE: may modify the @fqname string. The pointers returned point - * into the @fqname string. + * Returns a pointer to the first non-whitespace character in @str. + * if all whitespace will return NULL */ -char *aa_split_fqname(char *fqname, char **ns_name) + +const char *skipn_spaces(const char *str, size_t n) { - char *name = strim(fqname); + for (; n && isspace(*str); --n) + ++str; + if (n) + return (char *)str; + return NULL; +} + +const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name, + size_t *ns_len) +{ + const char *end = fqname + n; + const char *name = skipn_spaces(fqname, n); *ns_name = NULL; + *ns_len = 0; + + if (!name) + return NULL; + if (name[0] == ':') { - char *split = strchr(&name[1], ':'); - *ns_name = skip_spaces(&name[1]); + char *split = strnchr(&name[1], end - &name[1], ':'); + *ns_name = skipn_spaces(&name[1], end - &name[1]); + if (!*ns_name) + return NULL; if (split) { - /* overwrite ':' with \0 */ - *split++ = 0; - if (strncmp(split, "//", 2) == 0) + *ns_len = split - *ns_name; + if (*ns_len == 0) + *ns_name = NULL; + split++; + if (end - split > 1 && strncmp(split, "//", 2) == 0) split += 2; - name = skip_spaces(split); - } else + name = skipn_spaces(split, end - split); + } else { /* a ns name without a following profile is allowed */ name = NULL; + *ns_len = end - *ns_name; + } } if (name && *name == 0) name = NULL; @@ -66,60 +221,294 @@ char *aa_split_fqname(char *fqname, char **ns_name) void aa_info_message(const char *str) { if (audit_enabled) { - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.info = str; - aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); + + ad.info = str; + aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, NULL); } printk(KERN_INFO "AppArmor: %s\n", str); } +__counted char *aa_str_alloc(int size, gfp_t gfp) +{ + struct counted_str *str; + + str = kmalloc(struct_size(str, name, size), gfp); + if (!str) + return NULL; + + kref_init(&str->count); + return str->name; +} + +void aa_str_kref(struct kref *kref) +{ + kfree(container_of(kref, struct counted_str, count)); +} + + +const char aa_file_perm_chrs[] = "xwracd km l "; +const char *aa_file_perm_names[] = { + "exec", + "write", + "read", + "append", + + "create", + "delete", + "open", + "rename", + + "setattr", + "getattr", + "setcred", + "getcred", + + "chmod", + "chown", + "chgrp", + "lock", + + "mmap", + "mprot", + "link", + "snapshot", + + "unknown", + "unknown", + "unknown", + "unknown", + + "unknown", + "unknown", + "unknown", + "unknown", + + "stack", + "change_onexec", + "change_profile", + "change_hat", +}; + +/** + * aa_perm_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @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, 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; + + *str++ = chrs[i]; + str_size--; + } + } + *str = '\0'; +} + +void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names, + u32 mask) +{ + const char *fmt = "%s"; + unsigned int i, perm = 1; + bool prev = false; + + for (i = 0; i < 32; perm <<= 1, i++) { + if (mask & perm) { + audit_log_format(ab, fmt, names[i]); + if (!prev) { + prev = true; + fmt = " %s"; + } + } + } +} + +void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs, + u32 chrsmask, const char * const *names, u32 namesmask) +{ + char str[33]; + + audit_log_format(ab, "\""); + if ((mask & chrsmask) && chrs) { + aa_perm_mask_to_str(str, sizeof(str), chrs, mask & chrsmask); + mask &= ~chrsmask; + audit_log_format(ab, "%s", str); + if (mask & namesmask) + audit_log_format(ab, " "); + } + if ((mask & namesmask) && names) + aa_audit_perm_names(ab, names, mask & namesmask); + audit_log_format(ab, "\""); +} + +/** + * aa_apply_modes_to_perms - apply namespace and profile flags to perms + * @profile: that perms where computed from + * @perms: perms to apply mode modifiers to + * + * TODO: split into profile and ns based flags for when accumulating perms + */ +void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) +{ + switch (AUDIT_MODE(profile)) { + case AUDIT_ALL: + perms->audit = ALL_PERMS_MASK; + fallthrough; + case AUDIT_NOQUIET: + perms->quiet = 0; + break; + case AUDIT_QUIET: + perms->audit = 0; + fallthrough; + case AUDIT_QUIET_DENIED: + perms->quiet = ALL_PERMS_MASK; + break; + } + + if (KILL_MODE(profile)) + perms->kill = ALL_PERMS_MASK; + else if (COMPLAIN_MODE(profile)) + perms->complain = ALL_PERMS_MASK; + else if (USER_MODE(profile)) + perms->prompt = ALL_PERMS_MASK; +} + +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 */ + aa_state_t state; + + state = aa_dfa_next(rules->policy->dfa, + rules->policy->start[AA_CLASS_LABEL], + type); + aa_label_match(profile, rules, label, state, false, request, perms); +} + + /** - * __aa_kvmalloc - do allocation preferring kmalloc but falling back to vmalloc - * @size: how many bytes of memory are required - * @flags: the type of memory to allocate (see kmalloc). + * aa_check_perms - do audit mode selection based on perms set + * @profile: profile being checked + * @perms: perms computed for the request + * @request: requested perms + * @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 * - * Return: allocated buffer or NULL if failed + * Note: profile audit modes need to be set before calling by setting the + * perm masks appropriately. * - * It is possible that policy being loaded from the user is larger than - * what can be allocated by kmalloc, in those cases fall back to vmalloc. + * If not auditing then complain mode is not enabled and the + * error code will indicate whether there was an explicit deny + * with a positive value. */ -void *__aa_kvmalloc(size_t size, gfp_t flags) +int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms, + u32 request, struct apparmor_audit_data *ad, + void (*cb)(struct audit_buffer *, void *)) { - void *buffer = NULL; + int type, error; + u32 denied = request & (~perms->allow | perms->deny); - if (size == 0) - return NULL; + if (likely(!denied)) { + /* mask off perms that are not being force audited */ + request &= perms->audit; + if (!request || !ad) + return 0; + + type = AUDIT_APPARMOR_AUDIT; + error = 0; + } else { + error = -EACCES; - /* do not attempt kmalloc if we need more than 16 pages at once */ - if (size <= (16*PAGE_SIZE)) - buffer = kmalloc(size, flags | GFP_NOIO | __GFP_NOWARN); - if (!buffer) { - /* see kvfree for why size must be at least work_struct size - * when allocated via vmalloc - */ - if (size < sizeof(struct work_struct)) - size = sizeof(struct work_struct); - if (flags & __GFP_ZERO) - buffer = vzalloc(size); + if (denied & perms->kill) + type = AUDIT_APPARMOR_KILL; + else if (denied == (denied & perms->complain)) + type = AUDIT_APPARMOR_ALLOWED; else - buffer = vmalloc(size); + type = AUDIT_APPARMOR_DENIED; + + if (denied == (denied & perms->hide)) + error = -ENOENT; + + denied &= ~perms->quiet; + if (!ad || !denied) + return error; } - return buffer; + + 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) + error = 0; + + return error; } + /** - * kvfree - free an allocation do by kvmalloc - * @buffer: buffer to free (MAYBE_NULL) + * aa_policy_init - initialize a policy structure + * @policy: policy to initialize (NOT NULL) + * @prefix: prefix name if any is required. (MAYBE NULL) + * @name: name of the policy, init will make a copy of it (NOT NULL) + * @gfp: allocation mode * - * Free a buffer allocated by kvmalloc + * Note: this fn creates a copy of strings passed in + * + * Returns: true if policy init successful + */ +bool aa_policy_init(struct aa_policy *policy, const char *prefix, + const char *name, gfp_t gfp) +{ + char *hname; + + /* freed by policy_free */ + if (prefix) { + hname = aa_str_alloc(strlen(prefix) + strlen(name) + 3, gfp); + if (hname) + sprintf(hname, "%s//%s", prefix, name); + } else { + hname = aa_str_alloc(strlen(name) + 1, gfp); + if (hname) + strcpy(hname, name); + } + if (!hname) + return false; + policy->hname = hname; + /* base.name is a substring of fqname */ + policy->name = basename(policy->hname); + INIT_LIST_HEAD(&policy->list); + INIT_LIST_HEAD(&policy->profiles); + + return true; +} + +/** + * aa_policy_destroy - free the elements referenced by @policy + * @policy: policy that is to have its elements freed (NOT NULL) */ -void kvfree(void *buffer) +void aa_policy_destroy(struct aa_policy *policy) { - if (is_vmalloc_addr(buffer)) - vfree(buffer); - else - kfree(buffer); + AA_BUG(on_list_rcu(&policy->profiles)); + AA_BUG(on_list_rcu(&policy->list)); + + /* don't free name as its a subset of hname */ + aa_put_str(policy->hname); } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 2e2a0dd4a73f..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,14 +6,9 @@ * * 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/security.h> +#include <linux/lsm_hooks.h> #include <linux/moduleparam.h> #include <linux/mm.h> #include <linux/mman.h> @@ -23,33 +19,64 @@ #include <linux/sysctl.h> #include <linux/audit.h> #include <linux/user_namespace.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 __initdata; +int apparmor_initialized; + +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_cxt and put its profiles + * put the associated labels */ static void apparmor_cred_free(struct cred *cred) { - aa_free_task_context(cred_cxt(cred)); - cred_cxt(cred) = NULL; + aa_put_label(cred_label(cred)); + set_cred_label(cred, NULL); } /* @@ -57,28 +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_cxt *cxt = aa_alloc_task_context(gfp); - if (!cxt) - return -ENOMEM; - - cred_cxt(cred) = cxt; + set_cred_label(cred, NULL); return 0; } /* - * prepare new aa_task_cxt 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_cxt *cxt = aa_alloc_task_context(gfp); - if (!cxt) - return -ENOMEM; - - aa_dup_task_context(cxt, cred_cxt(old)); - cred_cxt(new) = cxt; + set_cred_label(new, aa_get_newest_label(cred_label(old))); return 0; } @@ -87,66 +103,107 @@ static int apparmor_cred_prepare(struct cred *new, const struct cred *old, */ static void apparmor_cred_transfer(struct cred *new, const struct cred *old) { - const struct aa_task_cxt *old_cxt = cred_cxt(old); - struct aa_task_cxt *new_cxt = cred_cxt(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)); +} - aa_dup_task_context(new_cxt, old_cxt); +static int apparmor_task_alloc(struct task_struct *task, + u64 clone_flags) +{ + struct aa_task_ctx *new = task_ctx(task); + + aa_dup_task_ctx(new, task_ctx(current)); + + return 0; } static int apparmor_ptrace_access_check(struct task_struct *child, unsigned int mode) { - int error = cap_ptrace_access_check(child, mode); - if (error) - return error; + struct aa_label *tracer, *tracee; + const struct cred *cred; + int error; + 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 aa_ptrace(current, child, mode); + return error; } static int apparmor_ptrace_traceme(struct task_struct *parent) { - int error = cap_ptrace_traceme(parent); - if (error) - return error; + struct aa_label *tracer, *tracee; + const struct cred *cred; + int error; + bool needput; + + 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 aa_ptrace(parent, current, PTRACE_MODE_ATTACH); + 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_profile *profile; + struct aa_label *label; const struct cred *cred; rcu_read_lock(); cred = __task_cred(target); - profile = aa_cred_profile(cred); + label = aa_get_newest_cred_label(cred); + + /* + * cap_capget is stacked ahead of this and will + * initialize effective and permitted. + */ + if (!unconfined(label)) { + struct aa_profile *profile; + struct label_it i; - *effective = cred->cap_effective; - *inheritable = cred->cap_inheritable; - *permitted = cred->cap_permitted; + label_for_each_confined(i, label, profile) { + kernel_cap_t allowed; - if (!unconfined(profile) && !COMPLAIN_MODE(profile)) { - *effective = cap_intersect(*effective, profile->caps.allow); - *permitted = cap_intersect(*permitted, profile->caps.allow); + allowed = aa_profile_capget(profile); + *effective = cap_intersect(*effective, allowed); + *permitted = cap_intersect(*permitted, allowed); + } } rcu_read_unlock(); + aa_put_label(label); return 0; } static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, - int cap, int audit) + int cap, unsigned int opts) { - struct aa_profile *profile; - /* cap_capable returns 0 on success, else -EPERM */ - int error = cap_capable(cred, ns, cap, audit); - if (!error) { - profile = aa_cred_profile(cred); - if (!unconfined(profile)) - error = aa_capable(current, profile, cap, audit); - } + struct aa_label *label; + int error = 0; + + label = aa_get_newest_cred_label(cred); + if (!unconfined(label)) + error = aa_capable(cred, label, cap, opts); + aa_put_label(label); + return error; } @@ -159,56 +216,62 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, * * Returns: %0 else error code if error or permission denied */ -static int common_perm(int op, struct path *path, u32 mask, +static int common_perm(const char *op, const struct path *path, u32 mask, struct path_cond *cond) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; + bool needput; - profile = __aa_current_profile(); - if (!unconfined(profile)) - error = aa_path_perm(op, profile, path, 0, mask, cond); + label = __begin_current_label_crit_section(&needput); + if (!unconfined(label)) + error = aa_path_perm(op, current_cred(), label, path, 0, mask, + cond); + __end_current_label_crit_section(label, needput); return error; } /** - * common_perm_dir_dentry - common permission wrapper when path is dir, dentry + * common_perm_cond - common permission wrapper around inode cond * @op: operation being checked - * @dir: directory of the dentry (NOT NULL) - * @dentry: dentry to check (NOT NULL) + * @path: location to check (NOT NULL) * @mask: requested permissions mask - * @cond: conditional info for the permission request (NOT NULL) * * Returns: %0 else error code if error or permission denied */ -static int common_perm_dir_dentry(int op, struct path *dir, - struct dentry *dentry, u32 mask, - struct path_cond *cond) +static int common_perm_cond(const char *op, const struct path *path, u32 mask) { - struct path path = { dir->mnt, dentry }; + 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 + }; - return common_perm(op, &path, mask, cond); + if (!path_mediated_fs(path->dentry)) + return 0; + + return common_perm(op, path, mask, &cond); } /** - * common_perm_mnt_dentry - common permission wrapper when mnt, dentry + * common_perm_dir_dentry - common permission wrapper when path is dir, dentry * @op: operation being checked - * @mnt: mount point of dentry (NOT NULL) + * @dir: directory of the dentry (NOT NULL) * @dentry: dentry to check (NOT NULL) * @mask: requested permissions mask + * @cond: conditional info for the permission request (NOT NULL) * * Returns: %0 else error code if error or permission denied */ -static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, - struct dentry *dentry, u32 mask) +static int common_perm_dir_dentry(const char *op, const struct path *dir, + struct dentry *dentry, u32 mask, + struct path_cond *cond) { - struct path path = { mnt, dentry }; - struct path_cond cond = { dentry->d_inode->i_uid, - dentry->d_inode->i_mode - }; + struct path path = { .mnt = dir->mnt, .dentry = dentry }; - return common_perm(op, &path, mask, &cond); + return common_perm(op, &path, mask, cond); } /** @@ -220,16 +283,18 @@ static int common_perm_mnt_dentry(int op, struct vfsmount *mnt, * * Returns: %0 else error code if error or permission denied */ -static int common_perm_rm(int op, struct path *dir, +static int common_perm_rm(const char *op, const struct path *dir, struct dentry *dentry, u32 mask) { - struct inode *inode = dentry->d_inode; + struct inode *inode = d_backing_inode(dentry); struct path_cond cond = { }; + vfsuid_t vfsuid; - if (!inode || !dir->mnt || !mediated_filesystem(inode)) + 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); @@ -245,215 +310,243 @@ static int common_perm_rm(int op, struct path *dir, * * Returns: %0 else error code if error or permission denied */ -static int common_perm_create(int op, struct path *dir, struct dentry *dentry, - u32 mask, umode_t mode) +static int common_perm_create(const char *op, const struct path *dir, + struct dentry *dentry, u32 mask, umode_t mode) { struct path_cond cond = { current_fsuid(), mode }; - if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode)) + if (!path_mediated_fs(dir->dentry)) return 0; return common_perm_dir_dentry(op, dir, dentry, mask, &cond); } -static int apparmor_path_unlink(struct path *dir, struct dentry *dentry) +static int apparmor_path_unlink(const struct path *dir, struct dentry *dentry) { return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE); } -static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, +static int apparmor_path_mkdir(const struct path *dir, struct dentry *dentry, umode_t mode) { return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE, S_IFDIR); } -static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry) +static int apparmor_path_rmdir(const struct path *dir, struct dentry *dentry) { return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE); } -static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, +static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev) { return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode); } -static int apparmor_path_truncate(struct path *path) +static int apparmor_path_truncate(const struct path *path) { - struct path_cond cond = { path->dentry->d_inode->i_uid, - path->dentry->d_inode->i_mode - }; - - if (!path->mnt || !mediated_filesystem(path->dentry->d_inode)) - return 0; + return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); +} - return common_perm(OP_TRUNC, path, MAY_WRITE | AA_MAY_META_WRITE, - &cond); +static int apparmor_file_truncate(struct file *file) +{ + return apparmor_path_truncate(&file->f_path); } -static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, +static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, const char *old_name) { return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE, S_IFLNK); } -static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, +static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - if (!mediated_filesystem(old_dentry->d_inode)) + if (!path_mediated_fs(old_dentry)) return 0; - profile = aa_current_profile(); - if (!unconfined(profile)) - error = aa_path_link(profile, old_dentry, new_dir, new_dentry); + label = begin_current_label_crit_section(); + if (!unconfined(label)) + 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(struct path *old_dir, struct dentry *old_dentry, - struct path *new_dir, struct dentry *new_dentry) +static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry, + const struct path *new_dir, struct dentry *new_dentry, + const unsigned int flags) { - struct aa_profile *profile; + struct aa_label *label; int error = 0; - if (!mediated_filesystem(old_dentry->d_inode)) + if (!path_mediated_fs(old_dentry)) + return 0; + if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry)) return 0; - profile = aa_current_profile(); - if (!unconfined(profile)) { - struct path old_path = { old_dir->mnt, old_dentry }; - struct path new_path = { new_dir->mnt, new_dentry }; - struct path_cond cond = { old_dentry->d_inode->i_uid, - old_dentry->d_inode->i_mode + 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 = { + .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, profile, &old_path, 0, - MAY_READ | AA_MAY_META_READ | MAY_WRITE | - AA_MAY_META_WRITE | AA_MAY_DELETE, - &cond); if (!error) - error = aa_path_perm(OP_RENAME_DEST, profile, &new_path, - 0, MAY_WRITE | AA_MAY_META_WRITE | + error = aa_path_perm(OP_RENAME_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); } + end_current_label_crit_section(label); + return error; } -static int apparmor_path_chmod(struct path *path, umode_t mode) +static int apparmor_path_chmod(const struct path *path, umode_t mode) { - if (!mediated_filesystem(path->dentry->d_inode)) - return 0; - - return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD); + return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD); } -static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid) +static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid) { - struct path_cond cond = { path->dentry->d_inode->i_uid, - path->dentry->d_inode->i_mode - }; - - if (!mediated_filesystem(path->dentry->d_inode)) - return 0; - - return common_perm(OP_CHOWN, path, AA_MAY_CHOWN, &cond); + return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN); } -static int apparmor_inode_getattr(struct vfsmount *mnt, struct dentry *dentry) +static int apparmor_inode_getattr(const struct path *path) { - if (!mediated_filesystem(dentry->d_inode)) - return 0; - - return common_perm_mnt_dentry(OP_GETATTR, mnt, dentry, - AA_MAY_META_READ); + 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_cxt *fcxt = file->f_security; - struct aa_profile *profile; + struct aa_file_ctx *fctx = file_ctx(file); + struct aa_label *label; int error = 0; + bool needput; - if (!mediated_filesystem(file_inode(file))) + if (!path_mediated_fs(file->f_path.dentry)) return 0; /* If in exec, permission is handled by bprm hooks. * 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) { - fcxt->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; + if (file->f_flags & __FMODE_EXEC) { + fctx->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP; return 0; } - profile = aa_cred_profile(cred); - if (!unconfined(profile)) { + 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, profile, &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 */ - fcxt->allow = aa_map_file_to_perms(file); + fctx->allow = aa_map_file_to_perms(file); } + aa_put_label_condref(label, needput); return error; } static int apparmor_file_alloc_security(struct file *file) { - /* freed by apparmor_file_free_security */ - file->f_security = aa_alloc_file_context(GFP_KERNEL); - if (!file->f_security) - return -ENOMEM; - return 0; + struct aa_file_ctx *ctx = file_ctx(file); + struct aa_label *label = begin_current_label_crit_section(); + 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) { - struct aa_file_cxt *cxt = file->f_security; + struct aa_file_ctx *ctx = file_ctx(file); - aa_free_file_context(cxt); + if (ctx) + aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(int op, struct file *file, u32 mask) +static int common_file_perm(const char *op, struct file *file, u32 mask, + bool in_atomic) { - struct aa_file_cxt *fcxt = file->f_security; - struct aa_profile *profile, *fprofile = aa_cred_profile(file->f_cred); + struct aa_label *label; int error = 0; + bool needput; - BUG_ON(!fprofile); - - if (!file->f_path.mnt || - !mediated_filesystem(file_inode(file))) - return 0; - - profile = __aa_current_profile(); + /* don't reaudit files closed during inheritance */ + if (unlikely(file->f_path.dentry == aa_null.dentry)) + return -EACCES; - /* revalidate access, if task is unconfined, or the cached cred - * doesn't match or if the request is for more permissions than - * was granted. - * - * Note: the test for !unconfined(fprofile) is to handle file - * delegation from unconfined tasks - */ - if (!unconfined(profile) && !unconfined(fprofile) && - ((fprofile != profile) || (mask & ~fcxt->allow))) - error = aa_file_perm(op, profile, file, mask); + label = __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), + 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) @@ -463,15 +556,15 @@ 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(int op, struct file *file, unsigned long prot, - unsigned long flags) +static int common_mmap(const char *op, struct file *file, unsigned long prot, + unsigned long flags, bool in_atomic) { int mask = 0; - if (!file || !file->f_security) + if (!file || !file_ctx(file)) return 0; if (prot & PROT_READ) @@ -485,177 +578,1202 @@ static int common_mmap(int 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 int apparmor_getprocattr(struct task_struct *task, char *name, +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; +} + +/** + * 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_cxt *cxt = cred_cxt(cred); + struct aa_task_ctx *ctx = task_ctx(current); + struct aa_label *label = NULL; if (strcmp(name, "current") == 0) - error = aa_getprocattr(aa_newest_version(cxt->profile), - value); - else if (strcmp(name, "prev") == 0 && cxt->previous) - error = aa_getprocattr(aa_newest_version(cxt->previous), - value); - else if (strcmp(name, "exec") == 0 && cxt->onexec) - error = aa_getprocattr(aa_newest_version(cxt->onexec), - value); + 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) + label = aa_get_newest_label(ctx->onexec); else error = -EINVAL; + if (label) + error = aa_getprocattr(label, value, true); + + aa_put_label(label); put_cred(cred); return error; } -static int apparmor_setprocattr(struct task_struct *task, char *name, - void *value, size_t size) +static int do_setattr(u64 attr, void *value, size_t size) { - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - char *command, *args = value; + char *command, *largs = NULL, *args = value; size_t arg_size; int error; + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, + OP_SETPROCATTR); if (size == 0) return -EINVAL; - /* args points to a PAGE_SIZE buffer, AppArmor requires that - * the buffer must be null terminated or have size <= PAGE_SIZE -1 - * so that AppArmor can null terminate them - */ + + /* AppArmor requires that the buffer must be null terminated atm */ if (args[size - 1] != '\0') { - if (size == PAGE_SIZE) - return -EINVAL; + /* null terminate */ + largs = args = kmalloc(size + 1, GFP_KERNEL); + if (!args) + return -ENOMEM; + memcpy(args, value, size); args[size] = '\0'; } - /* task can only write its own attributes */ - if (current != task) - return -EACCES; - - args = value; + error = -EINVAL; args = strim(args); command = strsep(&args, " "); if (!args) - return -EINVAL; + goto out; args = skip_spaces(args); if (!*args) - return -EINVAL; + goto out; - arg_size = size - (args - (char *) value); - if (strcmp(name, "current") == 0) { + arg_size = size - (args - (largs ? largs : (char *) value)); + if (attr == LSM_ATTR_CURRENT) { if (strcmp(command, "changehat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - !AA_DO_TEST); + AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permhat") == 0) { error = aa_setprocattr_changehat(args, arg_size, - AA_DO_TEST); + AA_CHANGE_TEST); } else if (strcmp(command, "changeprofile") == 0) { - error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, - !AA_DO_TEST); + error = aa_change_profile(args, AA_CHANGE_NOFLAGS); } else if (strcmp(command, "permprofile") == 0) { - error = aa_setprocattr_changeprofile(args, !AA_ONEXEC, - AA_DO_TEST); + error = aa_change_profile(args, AA_CHANGE_TEST); + } else if (strcmp(command, "stack") == 0) { + 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_setprocattr_changeprofile(args, AA_ONEXEC, - !AA_DO_TEST); + error = aa_change_profile(args, AA_CHANGE_ONEXEC); + else if (strcmp(command, "stack") == 0) + error = aa_change_profile(args, (AA_CHANGE_ONEXEC | + AA_CHANGE_STACK)); else goto fail; } else /* only support the "current" and "exec" process attributes */ - return -EINVAL; + goto fail; if (!error) error = size; +out: + kfree(largs); return error; fail: - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.profile = aa_current_profile(); - aad.op = OP_SETPROCATTR; - aad.info = name; - aad.error = -EINVAL; - aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); + 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(const struct linux_binprm *bprm) +{ + struct aa_label *label = aa_current_raw_label(); + struct aa_label *new_label = cred_label(bprm->cred); + + /* bail out if unconfined or not changing profile */ + if ((new_label->proxy == label->proxy) || + (unconfined(new_label))) + return; + + aa_inherit_files(bprm->cred, current->files); + + current->pdeath_signal = 0; + + /* reset soft limits and set hard limits for the new label */ + __aa_transition_rlimits(label, new_label); +} + +/** + * apparmor_bprm_committed_creds() - do cleanup after new creds committed + * @bprm: binprm for the exec (NOT NULL) + */ +static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm) +{ + /* 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_profile *profile = __aa_current_profile(); + struct aa_label *label; + int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); + + if (!unconfined(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; - if (!unconfined(profile)) - error = aa_task_setrlimit(profile, task, resource, new_rlim); + 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; } -static struct security_operations apparmor_ops = { - .name = "apparmor", +/** + * 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(); - .ptrace_access_check = apparmor_ptrace_access_check, - .ptrace_traceme = apparmor_ptrace_traceme, - .capget = apparmor_capget, - .capable = apparmor_capable, + if (sock->sk) { + struct aa_sk_ctx *ctx = aa_sock(sock->sk); - .path_link = apparmor_path_link, - .path_unlink = apparmor_path_unlink, - .path_symlink = apparmor_path_symlink, - .path_mkdir = apparmor_path_mkdir, - .path_rmdir = apparmor_path_rmdir, - .path_mknod = apparmor_path_mknod, - .path_rename = apparmor_path_rename, - .path_chmod = apparmor_path_chmod, - .path_chown = apparmor_path_chown, - .path_truncate = apparmor_path_truncate, - .inode_getattr = apparmor_inode_getattr, + /* 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); - .file_open = apparmor_file_open, - .file_permission = apparmor_file_permission, - .file_alloc_security = apparmor_file_alloc_security, - .file_free_security = apparmor_file_free_security, - .mmap_file = apparmor_mmap_file, - .mmap_addr = cap_mmap_addr, - .file_mprotect = apparmor_file_mprotect, - .file_lock = apparmor_file_lock, + return 0; +} - .getprocattr = apparmor_getprocattr, - .setprocattr = apparmor_setprocattr, +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)); + } - .cred_alloc_blank = apparmor_cred_alloc_blank, - .cred_free = apparmor_cred_free, - .cred_prepare = apparmor_cred_prepare, - .cred_transfer = apparmor_cred_transfer, + 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); - .bprm_set_creds = apparmor_bprm_set_creds, - .bprm_committing_creds = apparmor_bprm_committing_creds, - .bprm_committed_creds = apparmor_bprm_committed_creds, - .bprm_secureexec = apparmor_bprm_secureexec, + return 0; +} - .task_setrlimit = apparmor_task_setrlimit, +/** + * 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); + + 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), + LSM_HOOK_INIT(path_mkdir, apparmor_path_mkdir), + LSM_HOOK_INIT(path_rmdir, apparmor_path_rmdir), + LSM_HOOK_INIT(path_mknod, apparmor_path_mknod), + LSM_HOOK_INIT(path_rename, apparmor_path_rename), + LSM_HOOK_INIT(path_chmod, apparmor_path_chmod), + LSM_HOOK_INIT(path_chown, apparmor_path_chown), + LSM_HOOK_INIT(path_truncate, apparmor_path_truncate), + LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), + + LSM_HOOK_INIT(file_open, apparmor_file_open), + LSM_HOOK_INIT(file_receive, apparmor_file_receive), + LSM_HOOK_INIT(file_permission, apparmor_file_permission), + LSM_HOOK_INIT(file_alloc_security, apparmor_file_alloc_security), + LSM_HOOK_INIT(file_free_security, apparmor_file_free_security), + 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_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(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 }; /* @@ -665,7 +1783,8 @@ static struct security_operations apparmor_ops = { static int param_set_aabool(const char *val, const struct kernel_param *kp); static int param_get_aabool(char *buffer, const struct kernel_param *kp); #define param_check_aabool param_check_bool -static struct kernel_param_ops param_ops_aabool = { +static const struct kernel_param_ops param_ops_aabool = { + .flags = KERNEL_PARAM_OPS_FL_NOARG, .set = param_set_aabool, .get = param_get_aabool }; @@ -673,24 +1792,38 @@ static struct kernel_param_ops param_ops_aabool = { static int param_set_aauint(const char *val, const struct kernel_param *kp); static int param_get_aauint(char *buffer, const struct kernel_param *kp); #define param_check_aauint param_check_uint -static struct kernel_param_ops param_ops_aauint = { +static const struct kernel_param_ops param_ops_aauint = { .set = param_set_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 -static struct kernel_param_ops param_ops_aalockpolicy = { +static const struct kernel_param_ops param_ops_aalockpolicy = { + .flags = KERNEL_PARAM_OPS_FL_NOARG, .set = param_set_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. @@ -701,9 +1834,27 @@ enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; module_param_call(mode, param_set_mode, param_get_mode, &aa_g_profile_mode, S_IRUSR | S_IWUSR); +/* whether policy verification hashing is enabled */ +bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT); +#ifdef CONFIG_SECURITY_APPARMOR_HASH +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; -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; @@ -713,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); @@ -731,23 +1882,31 @@ module_param_named(logsyscall, aa_g_logsyscall, aabool, S_IRUSR | S_IWUSR); /* Maximum pathname length before accesses will start getting rejected */ unsigned int aa_g_path_max = 2 * PATH_MAX; -module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR); +module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR); /* Determines how paranoid loading of policy is and how much verification * on the loaded policy is done. + * 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; -module_param_named(paranoid_load, aa_g_paranoid_load, aabool, - S_IRUSR | S_IWUSR); - +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, aabool, S_IRUSR); +static int apparmor_enabled __ro_after_init = 1; +module_param_named(enabled, apparmor_enabled, aaintbool, 0444); static int __init apparmor_enabled_setup(char *str) { unsigned long enabled; - int error = strict_strtoul(str, 0, &enabled); + int error = kstrtoul(str, 0, &enabled); if (!error) apparmor_enabled = enabled ? 1 : 0; return 1; @@ -758,112 +1917,316 @@ __setup("apparmor=", apparmor_enabled_setup); /* set global flag turning off the ability to load policy */ static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; - if (aa_g_lock_policy) - return -EACCES; return param_set_bool(val, kp); } static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return param_get_bool(buffer, kp); } static int param_set_aabool(const char *val, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) return -EPERM; return param_set_bool(val, kp); } static int param_get_aabool(char *buffer, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) return -EPERM; return param_get_bool(buffer, kp); } static int param_set_aauint(const char *val, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + int error; + + if (!apparmor_enabled) + return -EINVAL; + /* file is ro but enforce 2nd line check */ + if (apparmor_initialized) return -EPERM; - return param_set_uint(val, kp); + + 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; } static int param_get_aauint(char *buffer, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + 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) { - if (!capable(CAP_MAC_ADMIN)) + 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) + return -EPERM; - return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]); + 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_set_audit(const char *val, struct kernel_param *kp) +static int param_get_aacompressionlevel(char *buffer, + const struct kernel_param *kp) { - int i; - if (!capable(CAP_MAC_ADMIN)) + 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; - for (i = 0; i < AUDIT_MAX_INDEX; i++) { - if (strcmp(val, audit_mode_names[i]) == 0) { - aa_g_audit = i; - return 0; - } - } + i = aa_parse_debug_params(val); + if (i == DEBUG_PARSE_ERROR) + return -EINVAL; - return -EINVAL; + aa_g_debug = i; + return 0; } -static int param_get_mode(char *buffer, struct kernel_param *kp) +static int param_get_audit(char *buffer, const struct kernel_param *kp) { - if (!capable(CAP_MAC_ADMIN)) + 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, 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 = match_string(audit_mode_names, AUDIT_MAX_INDEX, val); + if (i < 0) + return -EINVAL; - return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]); + aa_g_audit = i; + return 0; } -static int param_set_mode(const char *val, struct kernel_param *kp) +static int param_get_mode(char *buffer, const struct kernel_param *kp) { - int i; - if (!capable(CAP_MAC_ADMIN)) + if (!apparmor_enabled) + return -EINVAL; + 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, 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 = match_string(aa_profile_mode_names, APPARMOR_MODE_NAMES_MAX_INDEX, + val); + if (i < 0) + return -EINVAL; + + aa_g_profile_mode = i; + return 0; +} - for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) { - if (strcmp(val, profile_mode_names[i]) == 0) { - 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); } /* @@ -871,33 +2234,272 @@ static int param_set_mode(const char *val, struct kernel_param *kp) */ /** - * set_init_cxt - set a task context and profile on the first task. + * set_init_ctx - set a task context and profile on the first task. * * TODO: allow setting an alternate profile than unconfined */ -static int __init set_init_cxt(void) +static int __init set_init_ctx(void) +{ + struct cred *cred = (__force struct cred *)current->real_cred; + + set_cred_label(cred, aa_get_label(ns_unconfined(root_ns))); + + return 0; +} + +static void destroy_buffers(void) +{ + 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) +{ + 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) { + 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(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) { - struct cred *cred = (struct cred *)current->real_cred; - struct aa_task_cxt *cxt; + if (!aa_current_policy_admin_capable(NULL)) + return -EPERM; + if (!apparmor_enabled) + return -EINVAL; + + return proc_dointvec(table, write, buffer, lenp, ppos); +} + +static const struct ctl_table apparmor_sysctl_table[] = { +#ifdef CONFIG_USER_NS + { + .procname = "unprivileged_userns_apparmor_policy", + .data = &unprivileged_userns_apparmor_policy, + .maxlen = sizeof(int), + .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("kernel", apparmor_sysctl_table) ? 0 : -ENOMEM; +} +#else +static inline int apparmor_init_sysctl(void) +{ + return 0; +} +#endif /* CONFIG_SYSCTL */ + +#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 (!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)); +} - cxt = aa_alloc_task_context(GFP_KERNEL); - if (!cxt) +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; - cxt->profile = aa_get_profile(root_ns->unconfined); - cred_cxt(cred) = cxt; + 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; - if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) { - aa_info_message("AppArmor disabled by boot time parameter"); - apparmor_enabled = 0; - return 0; + error = aa_setup_dfa_engine(); + if (error) { + AA_ERROR("Unable to setup dfa engine\n"); + goto alloc_out; } error = aa_alloc_root_ns(); @@ -906,20 +2508,30 @@ static int __init apparmor_init(void) goto alloc_out; } - error = set_init_cxt(); + error = apparmor_init_sysctl(); if (error) { - AA_ERROR("Failed to set context on init task\n"); - goto register_security_out; + AA_ERROR("Unable to register sysctls\n"); + goto alloc_out; + + } + + error = alloc_buffers(); + if (error) { + AA_ERROR("Unable to allocate work buffers\n"); + goto alloc_out; } - error = register_security(&apparmor_ops); + error = set_init_ctx(); if (error) { - struct cred *cred = (struct cred *)current->real_cred; - aa_free_task_context(cred_cxt(cred)); - cred_cxt(cred) = NULL; - AA_ERROR("Unable to register AppArmor\n"); - goto register_security_out; + AA_ERROR("Failed to set context on init task\n"); + aa_free_root_ns(); + goto buffers_out; } + security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks), + &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; @@ -932,14 +2544,27 @@ static int __init apparmor_init(void) return error; -register_security_out: - aa_free_root_ns(); - +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 727eb4200d5c..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> @@ -20,7 +16,7 @@ #include <linux/err.h> #include <linux/kref.h> -#include "include/apparmor.h" +#include "include/lib.h" #include "include/match.h" #define base_idx(X) ((X) & 0xffffff) @@ -46,41 +42,48 @@ static struct table_header *unpack_table(char *blob, size_t bsize) /* loaded td_id's start at 1, subtract 1 now to avoid doing * it every time we use td_id as an index */ - th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1; - th.td_flags = be16_to_cpu(*(u16 *) (blob + 2)); - th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8)); + th.td_id = be16_to_cpu(*(__be16 *) (blob)) - 1; + if (th.td_id > YYTD_ID_MAX) + goto out; + th.td_flags = be16_to_cpu(*(__be16 *) (blob + 2)); + th.td_lolen = be32_to_cpu(*(__be32 *) (blob + 8)); blob += sizeof(struct table_header); if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 || 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; - table = kvzalloc(tsize); + table = kvzalloc(tsize, GFP_KERNEL); if (table) { - *table = th; + table->td_id = th.td_id; + table->td_flags = th.td_flags; + table->td_lolen = th.td_lolen; if (th.td_flags == YYTD_DATA8) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u8, byte_to_byte); + u8, u8, byte_to_byte); else if (th.td_flags == YYTD_DATA16) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u16, be16_to_cpu); + u16, __be16, be16_to_cpu); else if (th.td_flags == YYTD_DATA32) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, - u32, be32_to_cpu); + u32, __be32, be32_to_cpu); else goto fail; + /* if table was vmalloced make sure the page tables are synced + * before it is used, as it goes live to all cpus. + */ + if (is_vmalloc_addr(table)) + vm_unmap_aliases(); } out: - /* if table was vmalloced make sure the page tables are synced - * before it is used, as it goes live to all cpus. - */ - if (is_vmalloc_addr(table)) - vm_unmap_aliases(); return table; fail: kvfree(table); @@ -88,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 @@ -97,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; } @@ -180,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) { @@ -188,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) @@ -195,7 +290,7 @@ void aa_dfa_free_kref(struct kref *kref) * @flags: flags controlling what type of accept tables are acceptable * * Unpack a dfa that has been serialized. To find information on the dfa - * format look in Documentation/security/apparmor.txt + * format look in Documentation/admin-guide/LSM/apparmor.rst * Assumes the dfa @blob stream has been aligned on a 8 byte boundary * * Returns: an unpacked dfa ready for matching or ERR_PTR on failure @@ -218,14 +313,31 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) if (size < sizeof(struct table_set_header)) goto fail; - if (ntohl(*(u32 *) data) != YYTH_MAGIC) + if (ntohl(*(__be32 *) data) != YYTH_MAGIC) goto fail; - hsize = ntohl(*(u32 *) (data + 4)); + hsize = ntohl(*(__be32 *) (data + 4)); if (size < hsize) goto fail; - dfa->flags = ntohs(*(u16 *) (data + 12)); + 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; @@ -250,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) @@ -266,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: @@ -281,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) @@ -297,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) { - 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); + for (; len; len--) + match_char(state, def, base, next, check, + equiv[(u8) *str++]); + } else { /* default is direct to next state */ - for (; len; len--) { + 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) +{ + 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); + /* default is direct to next state */ + 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) { - u16 *def = DEFAULT_TABLE(dfa); + 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) +{ + 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 new file mode 100644 index 000000000000..095f42a24cbc --- /dev/null +++ b/security/apparmor/nulldfa.in @@ -0,0 +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 diff --git a/security/apparmor/path.c b/security/apparmor/path.c index 35b394a75d76..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> @@ -25,7 +21,6 @@ #include "include/path.h" #include "include/policy.h" - /* modified from dcache.c */ static int prepend(char **buffer, int buflen, const char *str, int namelen) { @@ -39,26 +34,65 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) #define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT) +/* If the path is not connected to the expected root, + * check if it is a sysctl and handle specially else remove any + * leading / that __d_path may have returned. + * Unless + * specifically directed to connect the path, + * OR + * if in a chroot and doing chroot relative paths and the path + * resolves to the namespace root (would be connected outside + * of chroot) and specifically directed to connect paths to + * namespace root. + */ +static int disconnect(const struct path *path, char *buf, char **name, + int flags, const char *disconnected) +{ + int error = 0; + + if (!(flags & PATH_CONNECT_PATH) && + !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && + our_mnt(path->mnt))) { + /* disconnected path, don't return pathname starting + * with '/' + */ + error = -EACCES; + if (**name == '/') + *name = *name + 1; + } else { + if (**name != '/') + /* CONNECT_PATH with missing root */ + error = prepend(name, *name - buf, "/", 1); + if (!error && disconnected) + error = prepend(name, *name - buf, disconnected, + strlen(disconnected)); + } + + return error; +} + /** * d_namespace_path - lookup a name associated with a given path * @path: path to lookup (NOT NULL) * @buf: buffer to store path to (NOT NULL) - * @buflen: length of @buf * @name: Returns - pointer for start of path name with in @buf (NOT NULL) * @flags: flags controlling path lookup + * @disconnected: string to prefix to disconnected paths * * Handle path name lookup. * * 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(struct path *path, char *buf, int buflen, - char **name, int flags) +static int d_namespace_path(const struct path *path, char *buf, char **name, + int flags, const char *disconnected) { char *res; int error = 0; int connected = 1; + int isdir = (flags & PATH_IS_DIR) ? 1 : 0; + int buflen = aa_g_path_max - isdir; if (path->mnt->mnt_flags & MNT_INTERNAL) { /* it's not mounted anywhere */ @@ -73,9 +107,12 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, /* TODO: convert over to using a per namespace * control instead of hard coded /proc */ - return prepend(name, *name - buf, "/proc", 5); - } - return 0; + error = prepend(name, *name - buf, "/proc", 5); + goto out; + } else + error = disconnect(path, buf, name, flags, + disconnected); + goto out; } /* resolve paths relative to chroot?*/ @@ -93,105 +130,58 @@ static int d_namespace_path(struct path *path, char *buf, int buflen, /* handle error conditions - and still allow a partial path to * be returned. */ - if (!res || IS_ERR(res)) { - if (PTR_ERR(res) == -ENAMETOOLONG) - return -ENAMETOOLONG; + if (IS_ERR_OR_NULL(res)) { + if (PTR_ERR(res) == -ENAMETOOLONG) { + error = -ENAMETOOLONG; + *name = buf; + goto out; + } connected = 0; res = dentry_path_raw(path->dentry, buf, buflen); if (IS_ERR(res)) { error = PTR_ERR(res); *name = buf; goto out; - }; + } } else if (!our_mnt(path->mnt)) connected = 0; *name = res; + if (!connected) + error = disconnect(path, buf, name, flags, disconnected); + /* Handle two cases: * 1. A deleted dentry && profile is not allowing mediation of deleted * 2. On some filesystems, newly allocated dentries appear to the * security_path hooks as a deleted dentry except without an inode * allocated. */ - if (d_unlinked(path->dentry) && path->dentry->d_inode && - !(flags & PATH_MEDIATE_DELETED)) { + if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && + !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) { error = -ENOENT; goto out; } - /* If the path is not connected to the expected root, - * check if it is a sysctl and handle specially else remove any - * leading / that __d_path may have returned. - * Unless - * specifically directed to connect the path, - * OR - * if in a chroot and doing chroot relative paths and the path - * resolves to the namespace root (would be connected outside - * of chroot) and specifically directed to connect paths to - * namespace root. - */ - if (!connected) { - if (!(flags & PATH_CONNECT_PATH) && - !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) && - our_mnt(path->mnt))) { - /* disconnected path, don't return pathname starting - * with '/' - */ - error = -EACCES; - if (*res == '/') - *name = res + 1; - } - } - out: - return error; -} - -/** - * get_name_to_buffer - get the pathname to a buffer ensure dir / is appended - * @path: path to get name for (NOT NULL) - * @flags: flags controlling path lookup - * @buffer: buffer to put name in (NOT NULL) - * @size: size of buffer - * @name: Returns - contains position of path name in @buffer (NOT NULL) - * - * Returns: %0 else error on failure - */ -static int get_name_to_buffer(struct path *path, int flags, char *buffer, - int size, char **name, const char **info) -{ - int adjust = (flags & PATH_IS_DIR) ? 1 : 0; - int error = d_namespace_path(path, buffer, size - adjust, name, flags); - - if (!error && (flags & PATH_IS_DIR) && (*name)[1] != '\0') - /* - * Append "/" to the pathname. The root directory is a special - * case; it already ends in slash. - */ - strcpy(&buffer[size - 2], "/"); - - if (info && error) { - if (error == -ENOENT) - *info = "Failed name lookup - deleted entry"; - else if (error == -EACCES) - *info = "Failed name lookup - disconnected path"; - else if (error == -ENAMETOOLONG) - *info = "Failed name lookup - name too long"; - else - *info = "Failed name lookup"; - } + /* + * Append "/" to the pathname. The root directory is a special + * case; it already ends in slash. + */ + if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/')) + strcpy(&buf[aa_g_path_max - 2], "/"); return error; } /** - * aa_path_name - compute the pathname of a file + * aa_path_name - get the pathname to a buffer ensure dir / is appended * @path: path the file (NOT NULL) * @flags: flags controlling path name generation - * @buffer: buffer that aa_get_name() allocated (NOT NULL) + * @buffer: buffer to put name in (NOT NULL) * @name: Returns - the generated path name if !error (NOT NULL) * @info: Returns - information on why the path lookup failed (MAYBE NULL) + * @disconnected: string to prepend to disconnected paths * * @name is a pointer to the beginning of the pathname (which usually differs * from the beginning of the buffer), or NULL. If there is an error @name @@ -204,32 +194,23 @@ static int get_name_to_buffer(struct path *path, int flags, char *buffer, * * Returns: %0 else error code if could retrieve name */ -int aa_path_name(struct path *path, int flags, char **buffer, const char **name, - const char **info) +int aa_path_name(const struct path *path, int flags, char *buffer, + const char **name, const char **info, const char *disconnected) { - char *buf, *str = NULL; - int size = 256; - int error; - - *name = NULL; - *buffer = NULL; - for (;;) { - /* freed by caller */ - buf = kmalloc(size, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - error = get_name_to_buffer(path, flags, buf, size, &str, info); - if (error != -ENAMETOOLONG) - break; - - kfree(buf); - size <<= 1; - if (size > aa_g_path_max) - return -ENAMETOOLONG; - *info = NULL; + char *str = NULL; + int error = d_namespace_path(path, buffer, &str, flags, disconnected); + + if (info && error) { + if (error == -ENOENT) + *info = "Failed name lookup - deleted entry"; + else if (error == -EACCES) + *info = "Failed name lookup - disconnected path"; + else if (error == -ENAMETOOLONG) + *info = "Failed name lookup - name too long"; + else + *info = "Failed name lookup"; } - *buffer = buf; + *name = str; return error; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 0f345c4dee5f..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 @@ -76,367 +71,71 @@ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/string.h> +#include <linux/cred.h> +#include <linux/rculist.h> +#include <linux/user_namespace.h> #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" #include "include/path.h" #include "include/policy.h" +#include "include/policy_ns.h" #include "include/policy_unpack.h" #include "include/resource.h" +int unprivileged_userns_apparmor_policy = 1; +int aa_unprivileged_unconfined_restricted; -/* root profile namespace */ -struct aa_namespace *root_ns; - -const char *const profile_mode_names[] = { +const char *const aa_profile_mode_names[] = { "enforce", "complain", "kill", + "unconfined", + "user", }; -/** - * hname_tail - find the last component of an hname - * @name: hname to find the base profile name component of (NOT NULL) - * - * Returns: the tail (base profile name) name component of an hname - */ -static const char *hname_tail(const char *hname) -{ - char *split; - hname = strim((char *)hname); - for (split = strstr(hname, "//"); split; split = strstr(hname, "//")) - hname = split + 2; - - return hname; -} -/** - * policy_init - initialize a policy structure - * @policy: policy to initialize (NOT NULL) - * @prefix: prefix name if any is required. (MAYBE NULL) - * @name: name of the policy, init will make a copy of it (NOT NULL) - * - * Note: this fn creates a copy of strings passed in - * - * Returns: true if policy init successful - */ -static bool policy_init(struct aa_policy *policy, const char *prefix, - const char *name) +static void aa_free_pdb(struct aa_policydb *pdb) { - /* freed by policy_free */ - if (prefix) { - policy->hname = kmalloc(strlen(prefix) + strlen(name) + 3, - GFP_KERNEL); - if (policy->hname) - sprintf(policy->hname, "%s//%s", prefix, name); - } else - policy->hname = kstrdup(name, GFP_KERNEL); - if (!policy->hname) - return 0; - /* base.name is a substring of fqname */ - policy->name = (char *)hname_tail(policy->hname); - INIT_LIST_HEAD(&policy->list); - INIT_LIST_HEAD(&policy->profiles); - kref_init(&policy->count); - - return 1; -} - -/** - * policy_destroy - free the elements referenced by @policy - * @policy: policy that is to have its elements freed (NOT NULL) - */ -static void policy_destroy(struct aa_policy *policy) -{ - /* still contains profiles -- invalid */ - if (!list_empty(&policy->profiles)) { - AA_ERROR("%s: internal error, " - "policy '%s' still contains profiles\n", - __func__, policy->name); - BUG(); - } - if (!list_empty(&policy->list)) { - AA_ERROR("%s: internal error, policy '%s' still on list\n", - __func__, policy->name); - BUG(); + if (pdb) { + aa_put_dfa(pdb->dfa); + kvfree(pdb->perms); + aa_free_str_table(&pdb->trans); + kfree(pdb); } - - /* don't free name as its a subset of hname */ - kzfree(policy->hname); } /** - * __policy_find - find a policy by @name on a policy list - * @head: list to search (NOT NULL) - * @name: name to search for (NOT NULL) - * - * Requires: correct locks for the @head list be held - * - * Returns: unrefcounted policy that match @name or NULL if not found + * aa_pdb_free_kref - free aa_policydb by kref (called by aa_put_pdb) + * @kref: kref callback for freeing of a dfa (NOT NULL) */ -static struct aa_policy *__policy_find(struct list_head *head, const char *name) +void aa_pdb_free_kref(struct kref *kref) { - struct aa_policy *policy; + struct aa_policydb *pdb = container_of(kref, struct aa_policydb, count); - list_for_each_entry(policy, head, list) { - if (!strcmp(policy->name, name)) - return policy; - } - return NULL; + aa_free_pdb(pdb); } -/** - * __policy_strn_find - find a policy that's name matches @len chars of @str - * @head: list to search (NOT NULL) - * @str: string to search for (NOT NULL) - * @len: length of match required - * - * Requires: correct locks for the @head list be held - * - * Returns: unrefcounted policy that match @str or NULL if not found - * - * if @len == strlen(@strlen) then this is equiv to __policy_find - * other wise it allows searching for policy by a partial match of name - */ -static struct aa_policy *__policy_strn_find(struct list_head *head, - const char *str, int len) -{ - struct aa_policy *policy; - - list_for_each_entry(policy, head, list) { - if (aa_strneq(policy->name, str, len)) - return policy; - } - - return NULL; -} - -/* - * Routines for AppArmor namespaces - */ - -static const char *hidden_ns_name = "---"; -/** - * aa_ns_visible - test if @view is visible from @curr - * @curr: namespace to treat as the parent (NOT NULL) - * @view: namespace to test if visible from @curr (NOT NULL) - * - * Returns: true if @view is visible from @curr else false - */ -bool aa_ns_visible(struct aa_namespace *curr, struct aa_namespace *view) -{ - if (curr == view) - return true; - - for ( ; view; view = view->parent) { - if (view->parent == curr) - return true; - } - return false; -} - -/** - * 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) - * - * Returns: name of @view visible from @curr - */ -const char *aa_ns_name(struct aa_namespace *curr, struct aa_namespace *view) -{ - /* if view == curr then the namespace name isn't displayed */ - if (curr == view) - return ""; - - if (aa_ns_visible(curr, view)) { - /* at this point if a ns is visible it is in a view ns - * thus the curr ns.hname is a prefix of its name. - * Only output the virtualized portion of the name - * Add + 2 to skip over // separating curr hname prefix - * from the visible tail of the views hname - */ - return view->base.hname + strlen(curr->base.hname) + 2; - } else - return hidden_ns_name; -} -/** - * alloc_namespace - allocate, initialize and return a new namespace - * @prefix: parent namespace name (MAYBE NULL) - * @name: a preallocated name (NOT NULL) - * - * Returns: refcounted namespace or NULL on failure. - */ -static struct aa_namespace *alloc_namespace(const char *prefix, - const char *name) +struct aa_policydb *aa_alloc_pdb(gfp_t gfp) { - struct aa_namespace *ns; + struct aa_policydb *pdb = kzalloc(sizeof(struct aa_policydb), gfp); - ns = kzalloc(sizeof(*ns), GFP_KERNEL); - AA_DEBUG("%s(%p)\n", __func__, ns); - if (!ns) + if (!pdb) return NULL; - if (!policy_init(&ns->base, prefix, name)) - goto fail_ns; - - INIT_LIST_HEAD(&ns->sub_ns); - rwlock_init(&ns->lock); - - /* released by free_namespace */ - ns->unconfined = aa_alloc_profile("unconfined"); - if (!ns->unconfined) - goto fail_unconfined; - ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR | - PFLAG_IMMUTABLE; + kref_init(&pdb->count); - /* - * released by free_namespace, however __remove_namespace breaks - * the cyclic references (ns->unconfined, and unconfined->ns) and - * replaces with refs to parent namespace unconfined - */ - ns->unconfined->ns = aa_get_namespace(ns); - - atomic_set(&ns->uniq_null, 0); - - return ns; - -fail_unconfined: - kzfree(ns->base.hname); -fail_ns: - kzfree(ns); - return NULL; + return pdb; } -/** - * free_namespace - free a profile namespace - * @ns: the namespace to free (MAYBE NULL) - * - * Requires: All references to the namespace must have been put, if the - * namespace was referenced by a profile confining a task, - */ -static void free_namespace(struct aa_namespace *ns) -{ - if (!ns) - return; - - policy_destroy(&ns->base); - aa_put_namespace(ns->parent); - - if (ns->unconfined && ns->unconfined->ns == ns) - ns->unconfined->ns = NULL; - - aa_put_profile(ns->unconfined); - kzfree(ns); -} - -/** - * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace) - * @kr: kref callback for freeing of a namespace (NOT NULL) - */ -void aa_free_namespace_kref(struct kref *kref) -{ - free_namespace(container_of(kref, struct aa_namespace, base.count)); -} - -/** - * __aa_find_namespace - find a namespace on a list by @name - * @head: list to search for namespace on (NOT NULL) - * @name: name of namespace to look for (NOT NULL) - * - * Returns: unrefcounted namespace - * - * Requires: ns lock be held - */ -static struct aa_namespace *__aa_find_namespace(struct list_head *head, - const char *name) -{ - return (struct aa_namespace *)__policy_find(head, name); -} - -/** - * aa_find_namespace - look up a profile namespace on the namespace list - * @root: namespace to search in (NOT NULL) - * @name: name of namespace to find (NOT NULL) - * - * Returns: a refcounted namespace on the list, or NULL if no namespace - * called @name exists. - * - * refcount released by caller - */ -struct aa_namespace *aa_find_namespace(struct aa_namespace *root, - const char *name) -{ - struct aa_namespace *ns = NULL; - - read_lock(&root->lock); - ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); - read_unlock(&root->lock); - - return ns; -} - -/** - * aa_prepare_namespace - find an existing or create a new namespace of @name - * @name: the namespace to find or add (MAYBE NULL) - * - * Returns: refcounted namespace or NULL if failed to create one - */ -static struct aa_namespace *aa_prepare_namespace(const char *name) -{ - struct aa_namespace *ns, *root; - - root = aa_current_profile()->ns; - - write_lock(&root->lock); - - /* if name isn't specified the profile is loaded to the current ns */ - if (!name) { - /* released by caller */ - ns = aa_get_namespace(root); - goto out; - } - - /* try and find the specified ns and if it doesn't exist create it */ - /* released by caller */ - ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name)); - if (!ns) { - /* namespace not found */ - struct aa_namespace *new_ns; - write_unlock(&root->lock); - new_ns = alloc_namespace(root->base.hname, name); - if (!new_ns) - return NULL; - write_lock(&root->lock); - /* test for race when new_ns was allocated */ - ns = __aa_find_namespace(&root->sub_ns, name); - if (!ns) { - /* add parent ref */ - new_ns->parent = aa_get_namespace(root); - - list_add(&new_ns->base.list, &root->sub_ns); - /* add list ref */ - ns = aa_get_namespace(new_ns); - } else { - /* raced so free the new one */ - free_namespace(new_ns); - /* get reference on namespace */ - aa_get_namespace(ns); - } - } -out: - write_unlock(&root->lock); - - /* return ref */ - return ns; -} /** - * __list_add_profile - add a profile to a list + * __add_profile - add a profiles to list and label tree * @list: list to add it to (NOT NULL) * @profile: the profile to add (NOT NULL) * @@ -444,12 +143,21 @@ out: * * Requires: namespace lock be held, or list not be shared */ -static void __list_add_profile(struct list_head *list, - struct aa_profile *profile) +static void __add_profile(struct list_head *list, struct aa_profile *profile) { - list_add(&profile->base.list, list); + struct aa_label *l; + + AA_BUG(!list); + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + + list_add_rcu(&profile->base.list, list); /* get list reference */ aa_get_profile(profile); + l = aa_label_insert(&profile->ns->labels, &profile->label); + AA_BUG(l != &profile->label); + aa_put_label(l); } /** @@ -466,53 +174,14 @@ static void __list_add_profile(struct list_head *list, */ static void __list_remove_profile(struct aa_profile *profile) { - list_del_init(&profile->base.list); - if (!(profile->flags & PFLAG_NO_LIST_REF)) - /* release list reference */ - aa_put_profile(profile); -} + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); -/** - * __replace_profile - replace @old with @new on a list - * @old: profile to be replaced (NOT NULL) - * @new: profile to replace @old with (NOT NULL) - * - * Will duplicate and refcount elements that @new inherits from @old - * and will inherit @old children. - * - * refcount @new for list, put @old list refcount - * - * Requires: namespace list lock be held, or list not be shared - */ -static void __replace_profile(struct aa_profile *old, struct aa_profile *new) -{ - struct aa_policy *policy; - struct aa_profile *child, *tmp; - - if (old->parent) - policy = &old->parent->base; - else - policy = &old->ns->base; - - /* released when @new is freed */ - new->parent = aa_get_profile(old->parent); - new->ns = aa_get_namespace(old->ns); - __list_add_profile(&policy->profiles, new); - /* inherit children */ - list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) { - aa_put_profile(child->parent); - child->parent = aa_get_profile(new); - /* list refcount transferred to @new*/ - list_move(&child->base.list, &new->base.profiles); - } - - /* released by free_profile */ - old->replacedby = aa_get_profile(new); - __list_remove_profile(old); + list_del_rcu(&profile->base.list); + aa_put_profile(profile); } -static void __profile_list_release(struct list_head *head); - /** * __remove_profile - remove old profile, and children * @profile: profile to be replaced (NOT NULL) @@ -521,121 +190,84 @@ static void __profile_list_release(struct list_head *head); */ static void __remove_profile(struct aa_profile *profile) { + AA_BUG(!profile); + AA_BUG(!profile->ns); + AA_BUG(!mutex_is_locked(&profile->ns->lock)); + /* release any children lists first */ - __profile_list_release(&profile->base.profiles); + __aa_profile_list_release(&profile->base.profiles); /* released by free_profile */ - profile->replacedby = aa_get_profile(profile->ns->unconfined); + aa_label_remove(&profile->label); + __aafs_profile_rmdir(profile); __list_remove_profile(profile); } /** - * __profile_list_release - remove all profiles on the list and put refs + * __aa_profile_list_release - remove all profiles on the list and put refs * @head: list of profiles (NOT NULL) * * Requires: namespace lock be held */ -static void __profile_list_release(struct list_head *head) +void __aa_profile_list_release(struct list_head *head) { struct aa_profile *profile, *tmp; list_for_each_entry_safe(profile, tmp, head, base.list) __remove_profile(profile); } -static void __ns_list_release(struct list_head *head); - /** - * destroy_namespace - remove everything contained by @ns - * @ns: namespace to have it contents removed (NOT NULL) + * aa_free_data - free a data blob + * @ptr: data to free + * @arg: unused */ -static void destroy_namespace(struct aa_namespace *ns) +static void aa_free_data(void *ptr, void *arg) { - if (!ns) - return; + struct aa_data *data = ptr; - write_lock(&ns->lock); - /* release all profiles in this namespace */ - __profile_list_release(&ns->base.profiles); - - /* release all sub namespaces */ - __ns_list_release(&ns->sub_ns); - - write_unlock(&ns->lock); + kvfree_sensitive(data->data, data->size); + kfree_sensitive(data->key); + kfree_sensitive(data); } -/** - * __remove_namespace - remove a namespace and all its children - * @ns: namespace to be removed (NOT NULL) - * - * Requires: ns->parent->lock be held and ns removed from parent. - */ -static void __remove_namespace(struct aa_namespace *ns) +static void free_attachment(struct aa_attachment *attach) { - struct aa_profile *unconfined = ns->unconfined; - - /* remove ns from namespace list */ - list_del_init(&ns->base.list); + int i; - /* - * break the ns, unconfined profile cyclic reference and forward - * all new unconfined profiles requests to the parent namespace - * This will result in all confined tasks that have a profile - * being removed, inheriting the parent->unconfined profile. - */ - if (ns->parent) - ns->unconfined = aa_get_profile(ns->parent->unconfined); - - destroy_namespace(ns); - - /* release original ns->unconfined ref */ - aa_put_profile(unconfined); - /* release ns->base.list ref, from removal above */ - aa_put_namespace(ns); + for (i = 0; i < attach->xattr_count; i++) + kfree_sensitive(attach->xattrs[i]); + kfree_sensitive(attach->xattrs); + aa_put_pdb(attach->xmatch); } -/** - * __ns_list_release - remove all profile namespaces on the list put refs - * @head: list of profile namespaces (NOT NULL) - * - * Requires: namespace lock be held - */ -static void __ns_list_release(struct list_head *head) +static void free_ruleset(struct aa_ruleset *rules) { - struct aa_namespace *ns, *tmp; - list_for_each_entry_safe(ns, tmp, head, base.list) - __remove_namespace(ns); + int i; -} + if (!rules) + return; -/** - * aa_alloc_root_ns - allocate the root profile namespace - * - * Returns: %0 on success else error - * - */ -int __init aa_alloc_root_ns(void) -{ - /* released by aa_free_root_ns - used as list ref*/ - root_ns = alloc_namespace(NULL, "root"); - if (!root_ns) - return -ENOMEM; + aa_put_pdb(rules->file); + aa_put_pdb(rules->policy); + aa_free_cap_rules(&rules->caps); + aa_free_rlimit_rules(&rules->rlimits); - return 0; + for (i = 0; i < rules->secmark_count; i++) + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); + kfree_sensitive(rules); } - /** - * aa_free_root_ns - free the root profile namespace - */ -void __init aa_free_root_ns(void) - { - struct aa_namespace *ns = root_ns; - root_ns = NULL; +struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) +{ + struct aa_ruleset *rules; + + rules = kzalloc(sizeof(*rules), gfp); - destroy_namespace(ns); - aa_put_namespace(ns); + return rules; } /** - * free_profile - free a profile + * aa_free_profile - free a profile * @profile: the profile to free (MAYBE NULL) * * Free a profile, its hats and null_profile. All references to the profile, @@ -644,178 +276,168 @@ void __init aa_free_root_ns(void) * If the profile was referenced from a task context, free_profile() will * be called from an rcu callback routine, so we must not sleep here. */ -static void free_profile(struct aa_profile *profile) +void aa_free_profile(struct aa_profile *profile) { - struct aa_profile *p; + struct rhashtable *rht; - AA_DEBUG("%s(%p)\n", __func__, profile); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile); if (!profile) return; - if (!list_empty(&profile->base.list)) { - AA_ERROR("%s: internal error, " - "profile '%s' still on ns list\n", - __func__, profile->base.name); - BUG(); - } - /* free children profiles */ - policy_destroy(&profile->base); - aa_put_profile(profile->parent); - - aa_put_namespace(profile->ns); - kzfree(profile->rename); + aa_policy_destroy(&profile->base); + aa_put_profile(rcu_access_pointer(profile->parent)); - aa_free_file_rules(&profile->file); - aa_free_cap_rules(&profile->caps); - aa_free_rlimit_rules(&profile->rlimits); + aa_put_ns(profile->ns); + kfree_sensitive(profile->rename); + kfree_sensitive(profile->disconnected); - aa_put_dfa(profile->xmatch); - aa_put_dfa(profile->policy.dfa); + free_attachment(&profile->attach); - /* put the profile reference for replacedby, but not via - * put_profile(kref_put). - * replacedby can form a long chain that can result in cascading - * frees that blows the stack because kref_put makes a nested fn - * call (it looks like recursion, with free_profile calling - * free_profile) for each profile in the chain lp#1056078. + /* + * at this point there are no tasks that can have a reference + * to rules */ - for (p = profile->replacedby; p; ) { - if (atomic_dec_and_test(&p->base.count.refcount)) { - /* no more refs on p, grab its replacedby */ - struct aa_profile *next = p->replacedby; - /* break the chain */ - p->replacedby = NULL; - /* now free p, chain is broken */ - free_profile(p); - - /* follow up with next profile in the chain */ - p = next; - } else - break; - } + for (int i = 0; i < profile->n_rules; i++) + free_ruleset(profile->label.rules[i]); - kzfree(profile); -} + kfree_sensitive(profile->dirname); -/** - * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile) - * @kr: kref callback for freeing of a profile (NOT NULL) - */ -void aa_free_profile_kref(struct kref *kref) -{ - struct aa_profile *p = container_of(kref, struct aa_profile, - base.count); + if (profile->data) { + rht = profile->data; + profile->data = NULL; + rhashtable_free_and_destroy(rht, aa_free_data, NULL); + kfree_sensitive(rht); + } - free_profile(p); + kfree_sensitive(profile->hash); + aa_put_loaddata(profile->rawdata); + aa_label_destroy(&profile->label); + + 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 */ -struct aa_profile *aa_alloc_profile(const char *hname) +struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, + gfp_t gfp) { struct aa_profile *profile; - /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(sizeof(*profile), GFP_KERNEL); + /* 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 (!policy_init(&profile->base, NULL, hname)) { - kzfree(profile); - return NULL; - } - - /* refcount released by caller */ - return profile; -} - -/** - * aa_new_null_profile - create a new 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 - * - * Create a null- complain mode profile used in learning mode. The name of - * the profile is unique and follows the format of parent//null-<uniq>. - * - * null profiles are added to the profile list but the list does not - * hold a count on them so that they are automatically released when - * not in use. - * - * Returns: new refcounted profile else NULL on failure - */ -struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat) -{ - struct aa_profile *profile = NULL; - char *name; - int uniq = atomic_inc_return(&parent->ns->uniq_null); - - /* freed below */ - name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL); - if (!name) + if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; - sprintf(name, "%s//null-%x", parent->base.hname, uniq); - - profile = aa_alloc_profile(name); - kfree(name); - if (!profile) + if (!aa_label_init(&profile->label, 1, gfp)) goto fail; - profile->mode = APPARMOR_COMPLAIN; - profile->flags = PFLAG_NULL; - if (hat) - profile->flags |= PFLAG_HAT; + /* 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; - /* released on free_profile */ - profile->parent = aa_get_profile(parent); - profile->ns = aa_get_namespace(parent->ns); + /* update being set needed by fs interface */ + if (!proxy) { + proxy = aa_alloc_proxy(&profile->label, gfp); + if (!proxy) + goto fail; + } else + aa_get_proxy(proxy); + profile->label.proxy = proxy; - write_lock(&profile->ns->lock); - __list_add_profile(&parent->base.profiles, profile); - write_unlock(&profile->ns->lock); + profile->label.hname = profile->base.hname; + profile->label.flags |= FLAG_PROFILE; + profile->label.vec[0] = profile; + profile->signal = SIGKILL; /* refcount released by caller */ return profile; fail: + aa_free_profile(profile); + return NULL; } +static inline bool ANY_RULE_MEDIATES(struct aa_profile *profile, + unsigned char class) +{ + int i; + + for (i = 0; i < profile->n_rules; i++) { + if (RULE_MEDIATES(profile->label.rules[i], class)) + return true; + } + return false; +} + +/* set of rules that are mediated by unconfined */ +static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 }; + +/* must be called after profile rulesets and start information is setup */ +void aa_compute_profile_mediates(struct aa_profile *profile) +{ + int c; + + if (profile_unconfined(profile)) { + int *pos; + + 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 */ /** - * __find_child - find a profile on @head list with a name matching @name + * __strn_find_child - find a profile on @head list using substring of @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) + * @len: length of @name substring to match * - * Requires: ns lock protecting list be held + * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__find_child(struct list_head *head, const char *name) +static struct aa_profile *__strn_find_child(struct list_head *head, + const char *name, int len) { - return (struct aa_profile *)__policy_find(head, name); + return (struct aa_profile *)__policy_strn_find(head, name, len); } /** - * __strn_find_child - find a profile on @head list using substring of @name + * __find_child - find a profile on @head list with a name matching @name * @head: list to search (NOT NULL) * @name: name of profile (NOT NULL) - * @len: length of @name substring to match * - * Requires: ns lock protecting list be held + * Requires: rcu_read_lock be held * * Returns: unrefcounted profile ptr, or NULL if not found */ -static struct aa_profile *__strn_find_child(struct list_head *head, - const char *name, int len) +static struct aa_profile *__find_child(struct list_head *head, const char *name) { - return (struct aa_profile *)__policy_strn_find(head, name, len); + return __strn_find_child(head, name, strlen(name)); } /** @@ -829,9 +451,11 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) { struct aa_profile *profile; - read_lock(&parent->ns->lock); - profile = aa_get_profile(__find_child(&parent->base.profiles, name)); - read_unlock(&parent->ns->lock); + rcu_read_lock(); + do { + profile = __find_child(&parent->base.profiles, name); + } while (profile && !aa_get_profile_not0(profile)); + rcu_read_unlock(); /* refcount released by caller */ return profile; @@ -846,11 +470,11 @@ struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name) * that matches hname does not need to exist, in general this * is used to load a new profile. * - * Requires: ns->lock be held + * Requires: rcu_read_lock be held * * Returns: unrefcounted policy or NULL if not found */ -static struct aa_policy *__lookup_parent(struct aa_namespace *ns, +static struct aa_policy *__lookup_parent(struct aa_ns *ns, const char *hname) { struct aa_policy *policy; @@ -874,59 +498,258 @@ static struct aa_policy *__lookup_parent(struct aa_namespace *ns, } /** - * __lookup_profile - lookup the profile matching @hname + * __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) + * @n: length of @hname * - * Requires: ns->lock be held + * Requires: rcu_read_lock be held * * Returns: unrefcounted profile pointer or NULL if not found * * Do a relative name lookup, recursing through profile tree. */ -static struct aa_profile *__lookup_profile(struct aa_policy *base, - const char *hname) +static struct aa_profile *__lookupn_profile(struct aa_policy *base, + const char *hname, size_t n) { struct aa_profile *profile = NULL; - char *split; + const char *split; - for (split = strstr(hname, "//"); split;) { + for (split = strnstr(hname, "//", n); split; + split = strnstr(hname, "//", n)) { profile = __strn_find_child(&base->profiles, hname, split - hname); if (!profile) return NULL; base = &profile->base; + n -= split + 2 - hname; hname = split + 2; - split = strstr(hname, "//"); } - profile = __find_child(&base->profiles, hname); + if (n) + return __strn_find_child(&base->profiles, hname, n); + return NULL; +} - return profile; +static struct aa_profile *__lookup_profile(struct aa_policy *base, + const char *hname) +{ + return __lookupn_profile(base, hname, strlen(hname)); } /** - * aa_lookup_profile - find a profile by its full or partial name + * 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 * * Returns: refcounted profile or NULL if not found */ -struct aa_profile *aa_lookup_profile(struct aa_namespace *ns, const char *hname) +struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname, + size_t n) { struct aa_profile *profile; - read_lock(&ns->lock); - profile = aa_get_profile(__lookup_profile(&ns->base, hname)); - read_unlock(&ns->lock); + rcu_read_lock(); + do { + profile = __lookupn_profile(&ns->base, hname, n); + } while (profile && !aa_get_profile_not0(profile)); + rcu_read_unlock(); /* the unconfined profile is not in the regular profile list */ - if (!profile && strcmp(hname, "unconfined") == 0) - profile = aa_get_profile(ns->unconfined); + if (!profile && strncmp(hname, "unconfined", n) == 0) + profile = aa_get_newest_profile(ns->unconfined); + + /* refcount released by caller */ + return profile; +} + +struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, + const char *fqname, size_t n) +{ + struct aa_profile *profile; + struct aa_ns *ns; + const char *name, *ns_name; + size_t ns_len; + + name = aa_splitn_fqname(fqname, n, &ns_name, &ns_len); + if (ns_name) { + ns = aa_lookupn_ns(labels_ns(base), ns_name, ns_len); + if (!ns) + return NULL; + } else + ns = aa_get_ns(labels_ns(base)); + + if (name) + profile = aa_lookupn_profile(ns, name, n - (name - fqname)); + else if (ns) + /* default profile for ns, currently unconfined */ + profile = aa_get_newest_profile(ns->unconfined); + else + profile = NULL; + aa_put_ns(ns); + + 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; } /** @@ -941,8 +764,8 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, const char **info) { if (profile) { - if (profile->flags & PFLAG_IMMUTABLE) { - *info = "cannot replace immutible profile"; + if (profile->label.flags & FLAG_IMMUTIBLE) { + *info = "cannot replace immutable profile"; return -EPERM; } else if (noreplace) { *info = "profile already exists"; @@ -952,197 +775,585 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace, return 0; } -/** - * __add_new_profile - simple wrapper around __list_add_profile - * @ns: namespace that profile is being added to (NOT NULL) - * @policy: the policy container to add the profile to (NOT NULL) - * @profile: profile to add (NOT NULL) - * - * add a profile to a list and do other required basic allocations - */ -static void __add_new_profile(struct aa_namespace *ns, struct aa_policy *policy, - struct aa_profile *profile) +/* audit callback for net specific fields */ +static void audit_cb(struct audit_buffer *ab, void *va) { - if (policy != &ns->base) - /* released on profile replacement or free_profile */ - profile->parent = aa_get_profile((struct aa_profile *) policy); - __list_add_profile(&policy->profiles, profile); - /* released on free_profile */ - profile->ns = aa_get_namespace(ns); + struct common_audit_data *sa = va; + struct apparmor_audit_data *ad = aad(sa); + + if (ad->iface.ns) { + audit_log_format(ab, " ns="); + audit_log_untrustedstring(ab, ad->iface.ns); + } } /** - * aa_audit_policy - Do auditing of policy changes + * audit_policy - Do auditing of policy changes + * @subj_label: label to check if it can manage policy * @op: policy operation being performed - * @gfp: memory allocation flags + * @ns_name: name of namespace being manipulated * @name: name of profile being manipulated (NOT NULL) * @info: any extra information to be audited (MAYBE NULL) * @error: error code * * Returns: the error to be returned after audit is done */ -static int audit_policy(int op, gfp_t gfp, const char *name, const char *info, - int error) +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(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, op); + + 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, &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; +} + +/** + * 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 aa_policy_view_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *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; + if (!ns) + ns = view_ns; + + if (root_in_user_ns && aa_ns_visible(view_ns, ns, true) && + (user_ns == &init_user_ns || + (unprivileged_userns_apparmor_policy != 0 && + user_ns->level == view_ns->level))) + response = true; + + return response; +} + +bool aa_policy_admin_capable(const struct cred *subj_cred, + struct aa_label *label, struct aa_ns *ns) { - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.op = op; - aad.name = name; - aad.info = info; - aad.error = error; - - return aa_audit(AUDIT_APPARMOR_STATUS, __aa_current_profile(), gfp, - &sa, NULL); + 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(DEBUG_POLICY, "cap_mac_admin? %d\n", capable); + AA_DEBUG(DEBUG_POLICY, "policy locked? %d\n", 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 - * @op: the policy manipulation operation being done + * @subj_cred: subjects cred + * @label: label to check if it can manage policy + * @ns: namespace being managed by @label (may be NULL if @label's ns) + * @mask: contains the policy manipulation operation being done * - * Returns: true if the task is allowed to manipulate policy + * Returns: 0 if the task is allowed to manipulate policy else error */ -bool aa_may_manage_policy(int op) +int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label, + struct aa_ns *ns, u32 mask) { + const char *op; + + if (mask & AA_MAY_REMOVE_POLICY) + op = OP_PROF_RM; + else if (mask & AA_MAY_REPLACE_POLICY) + op = OP_PROF_REPL; + else + op = OP_PROF_LOAD; + /* check if loading policy is locked out */ - if (aa_g_lock_policy) { - audit_policy(op, GFP_KERNEL, NULL, "policy_locked", -EACCES); - return 0; + if (aa_g_lock_policy) + return audit_policy(label, op, NULL, NULL, "policy_locked", + -EACCES); + + if (!aa_policy_admin_capable(subj_cred, label, ns)) + return audit_policy(label, op, NULL, NULL, "not policy admin", + -EACCES); + + /* TODO: add fine grained mediation of policy loads */ + return 0; +} + +static struct aa_profile *__list_lookup_parent(struct list_head *lh, + struct aa_profile *profile) +{ + const char *base = basename(profile->base.hname); + long len = base - profile->base.hname; + struct aa_load_ent *ent; + + /* parent won't have trailing // so remove from len */ + if (len <= 2) + return NULL; + len -= 2; + + list_for_each_entry(ent, lh, list) { + if (ent->new == profile) + continue; + if (strncmp(ent->new->base.hname, profile->base.hname, len) == + 0 && ent->new->base.hname[len] == 0) + return ent->new; + } + + return NULL; +} + +/** + * __replace_profile - replace @old with @new on a list + * @old: profile to be replaced (NOT NULL) + * @new: profile to replace @old with (NOT NULL) + * + * Will duplicate and refcount elements that @new inherits from @old + * and will inherit @old children. + * + * refcount @new for list, put @old list refcount + * + * Requires: namespace list lock be held, or list not be shared + */ +static void __replace_profile(struct aa_profile *old, struct aa_profile *new) +{ + struct aa_profile *child, *tmp; + + if (!list_empty(&old->base.profiles)) { + LIST_HEAD(lh); + list_splice_init_rcu(&old->base.profiles, &lh, synchronize_rcu); + + list_for_each_entry_safe(child, tmp, &lh, base.list) { + struct aa_profile *p; + + list_del_init(&child->base.list); + p = __find_child(&new->base.profiles, child->base.name); + if (p) { + /* @p replaces @child */ + __replace_profile(child, p); + continue; + } + + /* inherit @child and its children */ + /* TODO: update hname of inherited children */ + /* list refcount transferred to @new */ + p = aa_deref_parent(child); + rcu_assign_pointer(child->parent, aa_get_profile(new)); + list_add_rcu(&child->base.list, &new->base.profiles); + aa_put_profile(p); + } + } + + if (!rcu_access_pointer(new->parent)) { + struct aa_profile *parent = aa_deref_parent(old); + rcu_assign_pointer(new->parent, aa_get_profile(parent)); } + aa_label_replace(&old->label, &new->label); + /* migrate dents must come after label replacement b/c update */ + __aafs_profile_migrate_dents(old, new); + + if (list_empty(&new->base.list)) { + /* new is not on a list already */ + list_replace_rcu(&old->base.list, &new->base.list); + aa_get_profile(new); + aa_put_profile(old); + } else + __list_remove_profile(old); +} - if (!capable(CAP_MAC_ADMIN)) { - audit_policy(op, GFP_KERNEL, NULL, "not policy admin", -EACCES); - return 0; +/** + * __lookup_replace - lookup replacement information for a profile + * @ns: namespace the lookup occurs in + * @hname: name of profile to lookup + * @noreplace: true if not replacing an existing profile + * @p: Returns - profile to be replaced + * @info: Returns - info string on why lookup failed + * + * Returns: profile to replace (no ref) on success else ptr error + */ +static int __lookup_replace(struct aa_ns *ns, const char *hname, + bool noreplace, struct aa_profile **p, + const char **info) +{ + *p = aa_get_profile(__lookup_profile(&ns->base, hname)); + if (*p) { + int error = replacement_allowed(*p, noreplace, info); + if (error) { + *info = "profile can not be replaced"; + return error; + } } - return 1; + return 0; +} + +static void share_name(struct aa_profile *old, struct aa_profile *new) +{ + aa_put_str(new->base.hname); + aa_get_str(old->base.hname); + new->base.hname = old->base.hname; + new->base.name = old->base.name; + new->label.hname = old->label.hname; +} + +/* Update to newest version of parent after previous replacements + * Returns: unrefcount newest version of parent + */ +static struct aa_profile *update_to_newest_parent(struct aa_profile *new) +{ + struct aa_profile *parent, *newest; + + parent = rcu_dereference_protected(new->parent, + mutex_is_locked(&new->ns->lock)); + newest = aa_get_newest_profile(parent); + + /* parent replaced in this atomic set? */ + if (newest != parent) { + aa_put_profile(parent); + rcu_assign_pointer(new->parent, newest); + } else + aa_put_profile(newest); + + return newest; } /** * aa_replace_profiles - replace profile(s) on the profile list + * @policy_ns: namespace load is occurring on + * @label: label that is attempting to load/replace policy + * @mask: permission mask * @udata: serialized data stream (NOT NULL) - * @size: size of the serialized data stream - * @noreplace: true if only doing addition, no replacement allowed * * unpack and replace a profile on the profile list and uses of that profile - * by any aa_task_cxt. 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(void *udata, size_t size, bool noreplace) +ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, + u32 mask, struct aa_loaddata *udata) { - struct aa_policy *policy; - struct aa_profile *old_profile = NULL, *new_profile = NULL; - struct aa_profile *rename_profile = NULL; - struct aa_namespace *ns = NULL; - const char *ns_name, *name = NULL, *info = NULL; - int op = OP_PROF_REPL; - ssize_t error; - + const char *ns_name = NULL, *info = NULL; + struct aa_ns *ns = NULL; + struct aa_load_ent *ent, *tmp; + struct aa_loaddata *rawdata_ent; + const char *op; + ssize_t count, error; + LIST_HEAD(lh); + + op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; + aa_get_loaddata(udata); /* released below */ - new_profile = aa_unpack(udata, size, &ns_name); - if (IS_ERR(new_profile)) { - error = PTR_ERR(new_profile); - new_profile = NULL; - goto fail; - } + error = aa_unpack(udata, &lh, &ns_name); + if (error) + goto out; - /* released below */ - ns = aa_prepare_namespace(ns_name); - if (!ns) { - info = "failed to prepare namespace"; - error = -ENOMEM; - name = ns_name; - goto fail; + /* ensure that profiles are all for the same ns + * 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 + */ + count = 0; + list_for_each_entry(ent, &lh, list) { + if (ns_name) { + if (ent->ns_name && + strcmp(ent->ns_name, ns_name) != 0) { + info = "policy load has mixed namespaces"; + error = -EACCES; + goto fail; + } + } else if (ent->ns_name) { + if (count) { + info = "policy load has mixed namespaces"; + error = -EACCES; + goto fail; + } + ns_name = ent->ns_name; + } else + count++; } - - name = new_profile->base.hname; - - write_lock(&ns->lock); - /* no ref on policy only use inside lock */ - policy = __lookup_parent(ns, new_profile->base.hname); - - if (!policy) { - info = "parent does not exist"; - error = -ENOENT; - goto audit; + if (ns_name) { + ns = aa_prepare_ns(policy_ns ? policy_ns : labels_ns(label), + ns_name); + if (IS_ERR(ns)) { + op = OP_PROF_LOAD; + info = "failed to prepare namespace"; + error = PTR_ERR(ns); + ns = NULL; + ent = NULL; + goto fail; + } + } else + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(label)); + + mutex_lock_nested(&ns->lock, ns->level); + /* check for duplicate rawdata blobs: space and file dedup */ + 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; + + 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); + if (error) + goto fail_lock; + + if (ent->new->rename) { + error = __lookup_replace(ns, ent->new->rename, + !(mask & AA_MAY_REPLACE_POLICY), + &ent->rename, &info); + if (error) + goto fail_lock; + } - old_profile = __find_child(&policy->profiles, new_profile->base.name); - /* released below */ - aa_get_profile(old_profile); + /* released when @new is freed */ + ent->new->ns = aa_get_ns(ns); + + if (ent->old || ent->rename) + continue; + + /* no ref on policy only use inside lock */ + p = NULL; + policy = __lookup_parent(ns, ent->new->base.hname); + if (!policy) { + /* first check for parent in the load set */ + p = __list_lookup_parent(&lh, ent->new); + if (!p) { + /* + * 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; + } + } + } + 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)); + } - if (new_profile->rename) { - rename_profile = __lookup_profile(&ns->base, - new_profile->rename); - /* released below */ - aa_get_profile(rename_profile); + /* create new fs entries for introspection if needed */ + 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"; + ent = NULL; + goto fail_lock; + } + } + list_for_each_entry(ent, &lh, list) { + if (!ent->old) { + struct dentry *parent; + if (rcu_access_pointer(ent->new->parent)) { + struct aa_profile *p; + p = aa_deref_parent(ent->new); + parent = prof_child_dir(p); + } else + parent = ns_subprofs_dir(ent->new->ns); + error = __aafs_profile_mkdir(ent->new, parent); + } - if (!rename_profile) { - info = "profile to rename does not exist"; - name = new_profile->rename; - error = -ENOENT; - goto audit; + if (error) { + info = "failed to create"; + goto fail_lock; } } - error = replacement_allowed(old_profile, noreplace, &info); - if (error) - goto audit; + /* Done with checks that may fail - do actual replacement */ + __aa_bump_ns_revision(ns); + 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 && + 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; + } - error = replacement_allowed(rename_profile, noreplace, &info); - if (error) - goto audit; + /* + * TODO: finer dedup based on profile range in data. Load set + * can differ but profile may remain unchanged + */ + audit_policy(label, op, ns_name, ent->new->base.hname, NULL, + error); -audit: - if (!old_profile && !rename_profile) - op = OP_PROF_LOAD; + if (ent->old) { + share_name(ent->old, ent->new); + __replace_profile(ent->old, ent->new); + } else { + struct list_head *lh; - error = audit_policy(op, GFP_ATOMIC, name, info, error); + if (rcu_access_pointer(ent->new->parent)) { + struct aa_profile *parent; - if (!error) { - if (rename_profile) - __replace_profile(rename_profile, new_profile); - if (old_profile) - __replace_profile(old_profile, new_profile); - if (!(old_profile || rename_profile)) - __add_new_profile(ns, policy, new_profile); + parent = update_to_newest_parent(ent->new); + lh = &parent->base.profiles; + } else + lh = &ns->base.profiles; + __add_profile(lh, ent->new); + } + skip: + aa_load_ent_free(ent); } - write_unlock(&ns->lock); + __aa_labelset_update_subtree(ns); + mutex_unlock(&ns->lock); out: - aa_put_namespace(ns); - aa_put_profile(rename_profile); - aa_put_profile(old_profile); - aa_put_profile(new_profile); + aa_put_ns(ns); + aa_put_loaddata(udata); + kfree(ns_name); + if (error) return error; - return size; + return udata->size; +fail_lock: + mutex_unlock(&ns->lock); + + /* audit cause of failure */ + op = (ent && !ent->old) ? OP_PROF_LOAD : OP_PROF_REPL; fail: - error = audit_policy(op, GFP_KERNEL, name, info, error); + audit_policy(label, op, ns_name, ent ? ent->new->base.hname : NULL, + info, error); + /* audit status that rest of profiles in the atomic set failed too */ + info = "valid profile in failed atomic policy load"; + list_for_each_entry(tmp, &lh, list) { + if (tmp == ent) { + info = "unchecked profile in failed atomic policy load"; + /* skip entry that caused failure */ + continue; + } + op = (!tmp->old) ? OP_PROF_LOAD : OP_PROF_REPL; + audit_policy(label, op, ns_name, tmp->new->base.hname, info, + error); + } + list_for_each_entry_safe(ent, tmp, &lh, list) { + list_del_init(&ent->list); + aa_load_ent_free(ent); + } + goto out; } /** * aa_remove_profiles - remove profile(s) from the system + * @policy_ns: namespace the remove is being done from + * @subj: label attempting to remove policy * @fqname: name of the profile or namespace to remove (NOT NULL) * @size: size of the name * * 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 */ -ssize_t aa_remove_profiles(char *fqname, size_t size) +ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj, + char *fqname, size_t size) { - struct aa_namespace *root, *ns = NULL; + struct aa_ns *ns = NULL; struct aa_profile *profile = NULL; const char *name = fqname, *info = NULL; + const char *ns_name = NULL; ssize_t error = 0; if (*fqname == 0) { @@ -1151,13 +1362,13 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) goto fail; } - root = aa_current_profile()->ns; - if (fqname[0] == ':') { - char *ns_name; - name = aa_split_fqname(fqname, &ns_name); + size_t ns_len; + + name = aa_splitn_fqname(fqname, size, &ns_name, &ns_len); /* released below */ - ns = aa_find_namespace(root, ns_name); + ns = aa_lookupn_ns(policy_ns ? policy_ns : labels_ns(subj), + ns_name, ns_len); if (!ns) { info = "namespace does not exist"; error = -ENOENT; @@ -1165,16 +1376,17 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) } } else /* released below */ - ns = aa_get_namespace(root); + ns = aa_get_ns(policy_ns ? policy_ns : labels_ns(subj)); if (!name) { /* remove namespace - can only happen if fqname[0] == ':' */ - write_lock(&ns->parent->lock); - __remove_namespace(ns); - write_unlock(&ns->parent->lock); + 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 */ - write_lock(&ns->lock); + mutex_lock_nested(&ns->lock, ns->level); profile = aa_get_profile(__lookup_profile(&ns->base, name)); if (!profile) { error = -ENOENT; @@ -1182,21 +1394,25 @@ ssize_t aa_remove_profiles(char *fqname, size_t size) goto fail_ns_lock; } name = profile->base.hname; + __aa_bump_ns_revision(ns); __remove_profile(profile); - write_unlock(&ns->lock); + __aa_labelset_update_subtree(ns); + mutex_unlock(&ns->lock); } /* don't fail removal if audit fails */ - (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); - aa_put_namespace(ns); + (void) audit_policy(subj, OP_PROF_RM, ns_name, name, info, + error); + aa_put_ns(ns); aa_put_profile(profile); return size; fail_ns_lock: - write_unlock(&ns->lock); - aa_put_namespace(ns); + mutex_unlock(&ns->lock); + aa_put_ns(ns); fail: - (void) audit_policy(OP_PROF_RM, GFP_KERNEL, name, info, error); + (void) audit_policy(subj, OP_PROF_RM, ns_name, name, info, + error); return error; } 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 new file mode 100644 index 000000000000..64783ca3b0f2 --- /dev/null +++ b/security/apparmor/policy_ns.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor policy manipulation functions + * + * Copyright (C) 1998-2008 Novell/SUSE + * Copyright 2009-2017 Canonical Ltd. + * + * AppArmor policy namespaces, allow for different sets of policies + * to be loaded for tasks within the namespace. + */ + +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include "include/apparmor.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 = "---"; + +/** + * aa_ns_visible - test if @view is visible from @curr + * @curr: namespace to treat as the parent (NOT NULL) + * @view: namespace to test if visible from @curr (NOT NULL) + * @subns: whether view of a subns is allowed + * + * Returns: true if @view is visible from @curr else false + */ +bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns) +{ + if (curr == view) + return true; + + if (!subns) + return false; + + for ( ; view; view = view->parent) { + if (view->parent == curr) + return true; + } + + return false; +} + +/** + * 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 + */ +const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns) +{ + /* if view == curr then the namespace name isn't displayed */ + if (curr == view) + return ""; + + if (aa_ns_visible(curr, view, subns)) { + /* at this point if a ns is visible it is in a view ns + * thus the curr ns.hname is a prefix of its name. + * Only output the virtualized portion of the name + * Add + 2 to skip over // separating curr hname prefix + * from the visible tail of the views hname + */ + return view->base.hname + strlen(curr->base.hname) + 2; + } + + 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) + * @name: a preallocated name (NOT NULL) + * + * Returns: refcounted namespace or NULL on failure. + */ +static struct aa_ns *alloc_ns(const char *prefix, const char *name) +{ + struct aa_ns *ns; + + ns = kzalloc(sizeof(*ns), GFP_KERNEL); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns); + if (!ns) + return NULL; + if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) + goto fail_ns; + + INIT_LIST_HEAD(&ns->sub_ns); + INIT_LIST_HEAD(&ns->rawdata_list); + mutex_init(&ns->lock); + init_waitqueue_head(&ns->wait); + + /* released by aa_free_ns() */ + ns->unconfined = alloc_unconfined("unconfined"); + if (!ns->unconfined) + goto fail_unconfined; + /* ns and ns->unconfined share ns->unconfined refcount */ + ns->unconfined->ns = ns; + + atomic_set(&ns->uniq_null, 0); + + aa_labelset_init(&ns->labels); + + return ns; + +fail_unconfined: + aa_policy_destroy(&ns->base); +fail_ns: + kfree_sensitive(ns); + return NULL; +} + +/** + * aa_free_ns - free a profile namespace + * @ns: the namespace to free (MAYBE NULL) + * + * Requires: All references to the namespace must have been put, if the + * namespace was referenced by a profile confining a task, + */ +void aa_free_ns(struct aa_ns *ns) +{ + if (!ns) + return; + + aa_policy_destroy(&ns->base); + aa_labelset_destroy(&ns->labels); + aa_put_ns(ns->parent); + + ns->unconfined->ns = NULL; + aa_free_profile(ns->unconfined); + kfree_sensitive(ns); +} + +/** + * __aa_lookupn_ns - lookup the namespace matching @hname + * @view: namespace to search in (NOT NULL) + * @hname: hierarchical ns name (NOT NULL) + * @n: length of @hname + * + * Requires: rcu_read_lock be held + * + * Returns: unrefcounted ns pointer or NULL if not found + * + * Do a relative name lookup, recursing through profile tree. + */ +struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n) +{ + struct aa_ns *ns = view; + const char *split; + + for (split = strnstr(hname, "//", n); split; + split = strnstr(hname, "//", n)) { + ns = __aa_findn_ns(&ns->sub_ns, hname, split - hname); + if (!ns) + return NULL; + + n -= split + 2 - hname; + hname = split + 2; + } + + if (n) + return __aa_findn_ns(&ns->sub_ns, hname, n); + return NULL; +} + +/** + * aa_lookupn_ns - look up a policy namespace relative to @view + * @view: 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_lookupn_ns(struct aa_ns *view, const char *name, size_t n) +{ + struct aa_ns *ns = NULL; + + rcu_read_lock(); + ns = aa_get_ns(__aa_lookupn_ns(view, name, n)); + rcu_read_unlock(); + + return ns; +} + +static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name, + struct dentry *dir) +{ + struct aa_ns *ns; + int error; + + AA_BUG(!parent); + AA_BUG(!name); + AA_BUG(!mutex_is_locked(&parent->lock)); + + ns = alloc_ns(parent->base.hname, name); + if (!ns) + 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", + ns->base.name); + mutex_unlock(&ns->lock); + aa_free_ns(ns); + return ERR_PTR(error); + } + ns->parent = aa_get_ns(parent); + list_add_rcu(&ns->base.list, &parent->sub_ns); + /* add list ref */ + aa_get_ns(ns); + mutex_unlock(&ns->lock); + + return ns; +} + +/** + * __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 + * + * Returns: the a refcounted ns that has been add or an ERR_PTR + */ +struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name, + struct dentry *dir) +{ + struct aa_ns *ns; + + AA_BUG(!mutex_is_locked(&parent->lock)); + + /* try and find the specified ns */ + /* released by caller */ + ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name)); + if (!ns) + ns = __aa_create_ns(parent, name, dir); + else + ns = ERR_PTR(-EEXIST); + + /* return ref */ + return ns; +} + +/** + * aa_prepare_ns - find an existing or create a new namespace of @name + * @parent: ns to treat as parent + * @name: the namespace to find or add (NOT NULL) + * + * Returns: refcounted namespace or PTR_ERR if failed to create one + */ +struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name) +{ + struct aa_ns *ns; + + 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)); + if (!ns) + ns = __aa_create_ns(parent, name, NULL); + mutex_unlock(&parent->lock); + + /* return ref */ + return ns; +} + +static void __ns_list_release(struct list_head *head); + +/** + * destroy_ns - remove everything contained by @ns + * @ns: namespace to have it contents removed (NOT NULL) + */ +static void destroy_ns(struct aa_ns *ns) +{ + if (!ns) + return; + + mutex_lock_nested(&ns->lock, ns->level); + /* release all profiles in this namespace */ + __aa_profile_list_release(&ns->base.profiles); + + /* release all sub namespaces */ + __ns_list_release(&ns->sub_ns); + + if (ns->parent) { + unsigned long flags; + + write_lock_irqsave(&ns->labels.lock, flags); + __aa_proxy_redirect(ns_unconfined(ns), + ns_unconfined(ns->parent)); + write_unlock_irqrestore(&ns->labels.lock, flags); + } + __aafs_ns_rmdir(ns); + mutex_unlock(&ns->lock); +} + +/** + * __aa_remove_ns - remove a namespace and all its children + * @ns: namespace to be removed (NOT NULL) + * + * Requires: ns->parent->lock be held and ns removed from parent. + */ +void __aa_remove_ns(struct aa_ns *ns) +{ + /* remove ns from namespace list */ + list_del_rcu(&ns->base.list); + destroy_ns(ns); + aa_put_ns(ns); +} + +/** + * __ns_list_release - remove all profile namespaces on the list put refs + * @head: list of profile namespaces (NOT NULL) + * + * Requires: namespace lock be held + */ +static void __ns_list_release(struct list_head *head) +{ + struct aa_ns *ns, *tmp; + + list_for_each_entry_safe(ns, tmp, head, base.list) + __aa_remove_ns(ns); + +} + +/** + * aa_alloc_root_ns - allocate the root profile namespace + * + * Returns: %0 on success else error + * + */ +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; +} + + /** + * aa_free_root_ns - free the root profile namespace + */ +void __init aa_free_root_ns(void) +{ + struct aa_ns *ns = root_ns; + + 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 6dac7d77cb4d..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,80 +8,51 @@ * 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 look in Documentation/security/apparmor.txt + * 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" - -/* - * 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; - if (sa->aad->iface.target) { - struct aa_profile *name = sa->aad->iface.target; + struct apparmor_audit_data *ad = aad(sa); + + if (ad->iface.ns) { + audit_log_format(ab, " ns="); + audit_log_untrustedstring(ab, ad->iface.ns); + } + if (ad->name) { audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, name->base.hname); + audit_log_untrustedstring(ab, ad->name); } - if (sa->aad->iface.pos) - audit_log_format(ab, " offset=%ld", sa->aad->iface.pos); + if (ad->iface.pos) + audit_log_format(ab, " offset=%ld", ad->iface.pos); } /** * audit_iface - do audit message for policy unpacking/load/replace/remove * @new: profile if it has been allocated (MAYBE NULL) + * @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL) * @name: name of the profile being manipulated (MAYBE NULL) * @info: any extra info about the failure (MAYBE NULL) * @e: buffer position info @@ -88,66 +60,154 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: %0 or error */ -static int audit_iface(struct aa_profile *new, const char *name, - const char *info, struct aa_ext *e, int error) -{ - struct aa_profile *profile = __aa_current_profile(); - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; +static int audit_iface(struct aa_profile *new, const char *ns_name, + const char *name, const char *info, struct aa_ext *e, + int error) +{ + struct aa_profile *profile = labels_profile(aa_current_raw_label()); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); if (e) - aad.iface.pos = e->pos - e->start; - aad.iface.target = new; - aad.name = name; - aad.info = info; - aad.error = error; + ad.iface.pos = e->pos - e->start; + ad.iface.ns = ns_name; + if (new) + ad.name = new->base.hname; + else + ad.name = name; + ad.info = info; + ad.error = error; - return aa_audit(AUDIT_APPARMOR_STATUS, profile, GFP_KERNEL, &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(!mutex_is_locked(&data->ns->lock)); + AA_BUG(data->revision > revision); + + data->revision = 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->compressed_size ?: r->size) == 0; +} + +/* + * need to take the ns mutex lock which is NOT safe most places that + * put_loaddata is called, so we have to delay freeing it + */ +static void do_loaddata_free(struct work_struct *work) +{ + struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); + struct aa_ns *ns = aa_get_ns(d->ns); + + if (ns) { + mutex_lock_nested(&ns->lock, ns->level); + __aa_fs_remove_rawdata(d); + mutex_unlock(&ns->lock); + aa_put_ns(ns); + } + + kfree_sensitive(d->hash); + kfree_sensitive(d->name); + kvfree(d->data); + kfree_sensitive(d); +} + +void aa_loaddata_kref(struct kref *kref) +{ + struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); + + if (d) { + INIT_WORK(&d->work, do_loaddata_free); + schedule_work(&d->work); + } +} + +struct aa_loaddata *aa_loaddata_alloc(size_t size) +{ + 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); + + return d; } /* 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; - size = le16_to_cpu(get_unaligned((u16 *) e->pos)); - e->pos += sizeof(u16); - if (!inbounds(e, size)) - return 0; + if (!aa_inbounds(e, sizeof(u16))) + goto fail; + size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); + e->pos += sizeof(__le16); + 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) @@ -157,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 @@ -172,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 */ @@ -184,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((u32 *) e->pos)); + *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((u64 *) e->pos)); + *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 bool aa_unpack_cap_high(struct aa_ext *e, kernel_cap_t *data, const char *name) +{ + u32 val; + + if (!aa_unpack_u32(e, &val, name)) + return false; + data->val = (u32)data->val | ((u64)val << 32); + return true; } -static size_t unpack_array(struct aa_ext *e, const char *name) +VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size) { - if (unpack_nameX(e, AA_ARRAY, name)) { - int size; - if (!inbounds(e, sizeof(u16))) - return 0; - size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos)); + 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; - size = le32_to_cpu(get_unaligned((u32 *) e->pos)); + 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) @@ -288,388 +412,868 @@ 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 * from the beginning of the stream. + * alignment adjust needed by dfa unpack */ - size_t sz = blob - (char *)e->start; + size_t sz = blob - (char *) e->start - + ((e->pos - e->start) & 7); size_t pad = ALIGN(sz, 8) - sz; - int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | - TO_ACCEPT2_FLAG(YYTD_DATA32); - - 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, 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; /* count internal # of internal \0 */ - for (c = j = 0; j < size2 - 2; j++) { - if (!str[j]) + for (c = j = 0; j < size2 - 1; j++) { + if (!str[j]) { + pos = j; c++; + } } if (*str == ':') { + /* first character after : must be valid */ + if (!str[1]) + 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 */ - if (c != 1) - goto fail; - /* first character after : must be valid */ - if (!str[1]) + if (c == 1) + str[pos] = ':'; + else if (c > 1) goto fail; } else if (c) /* 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_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_profile *profile) +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 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 int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, + bool required_dfa, bool required_trans, + const char **info) +{ + 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; + +fail: + aa_put_pdb(pdb); + e->pos = pos; + return error; +} + +static u32 strhash(const void *data, u32 len, u32 seed) +{ + const char * const *key = data; + + return jhash(*key, strlen(*key), seed); +} + +static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) +{ + const struct aa_data *data = obj; + const char * const *key = arg->key; + + return strcmp(data->key, *key); } /** * 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) +static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) { + struct aa_ruleset *rules; struct aa_profile *profile = NULL; - const char *name = NULL; - int i, error = -EPROTO; + 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, *disconnected = NULL; + struct aa_data *data; + 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 (!aa_unpack_str(e, &name, NULL)) goto fail; - if (!unpack_str(e, &name, NULL)) + if (*name == '\0') goto fail; - profile = aa_alloc_profile(name); - if (!profile) - return ERR_PTR(-ENOMEM); + 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) { + info = "out of memory"; + error = -ENOMEM; + goto fail; + } + name = tmpname; + } + + profile = aa_alloc_profile(name, NULL, GFP_KERNEL); + 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) 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) 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) - profile->flags |= PFLAG_HAT; - if (!unpack_u32(e, &tmp, NULL)) + if (tmp & PACKED_FLAG_HAT) + profile->label.flags |= FLAG_HAT; + 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) + if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) { profile->mode = APPARMOR_COMPLAIN; - if (!unpack_u32(e, &tmp, NULL)) + } 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) { + profile->mode = APPARMOR_UNCONFINED; + 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")) - profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED; + 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 = PFLAG_MEDIATE_DELETED; + 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_secmark(e, rules)) { + info = "failed to unpack profile secmark rules"; goto fail; + } - if (unpack_nameX(e, AA_STRUCT, "policydb")) { + 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; - } - 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)) + /* 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; + } + } + } 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; + error = unpack_pdb(e, &rules->file, false, true, &info); + if (error) { goto fail; + } 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) { + error = -ENOMEM; + goto fail; + } + params.nelem_hint = 3; + params.key_len = sizeof(void *); + params.key_offset = offsetof(struct aa_data, key); + params.head_offset = offsetof(struct aa_data, head); + params.hashfn = strhash; + params.obj_cmpfn = datacmp; + + if (rhashtable_init(profile->data, ¶ms)) { + info = "failed to init key, value hash table"; + goto fail; + } + + while (aa_unpack_strdup(e, &key, NULL)) { + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + kfree_sensitive(key); + error = -ENOMEM; + goto fail; + } - if (!unpack_u32(e, &profile->file.start, "dfa_start")) - /* default start state */ - profile->file.start = DFA_START; + data->key = key; + data->size = aa_unpack_blob(e, &data->data, NULL); + data->data = kvmemdup(data->data, data->size, GFP_KERNEL); + if (data->size && !data->data) { + kfree_sensitive(data->key); + kfree_sensitive(data); + error = -ENOMEM; + goto fail; + } - if (!unpack_trans_table(e, profile)) - goto fail; + 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 (!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, name, "failed to unpack profile", e, error); - aa_put_profile(profile); + 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) * * Returns: error or 0 if header is good */ -static int verify_header(struct aa_ext *e, const char **ns) +static int verify_header(struct aa_ext *e, int required, const char **ns) { int error = -EPROTONOSUPPORT; + const char *name = NULL; + *ns = NULL; + /* get the interface version */ - if (!unpack_u32(e, &e->version, "version")) { - audit_iface(NULL, NULL, "invalid profile format", e, error); - return error; + if (!aa_unpack_u32(e, &e->version, "version")) { + if (required) { + audit_iface(NULL, NULL, NULL, "invalid profile format", + e, error); + return error; + } } - /* check that the interface version is currently supported */ - if (e->version != 5) { - audit_iface(NULL, NULL, "unsupported interface version", e, - error); + /* Check that the interface version is currently supported. + * if not specified use previous version + * Mask off everything that is not kernel abi version + */ + if (VERSION_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, ns, "namespace")) - *ns = NULL; + 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)) { + audit_iface(NULL, NULL, NULL, "invalid ns change", e, + error); + } 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 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; + 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) +{ + /* 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; + } + /* 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 1; + return true; } /** @@ -677,56 +1281,252 @@ 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 (aa_g_paranoid_load) { - if (profile->file.dfa && - !verify_dfa_xindex(profile->file.dfa, - profile->file.trans.size)) { - audit_iface(profile, NULL, "Invalid named transition", - NULL, -EPROTO); - return -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; + } + + return 0; +} + +void aa_load_ent_free(struct aa_load_ent *ent) +{ + if (ent) { + aa_put_profile(ent->rename); + aa_put_profile(ent->old); + aa_put_profile(ent->new); + kfree(ent->ns_name); + kfree_sensitive(ent); + } +} + +struct aa_load_ent *aa_load_ent_alloc(void) +{ + struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL); + if (ent) + INIT_LIST_HEAD(&ent->list); + return ent; +} + +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 data loaded from user space + * aa_unpack - unpack packed binary profile(s) data loaded from user space * @udata: user data copied to kmem (NOT NULL) - * @size: the size of the user data + * @lh: list to place unpacked profiles in a aa_repl_ws * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) * - * Unpack user data and return refcounted allocated profile or ERR_PTR + * Unpack user data and return refcounted allocated profile(s) stored in + * @lh in order of discovery, with the list chain stored in base.list + * or error * - * Returns: profile else error pointer if fails to unpack + * Returns: profile(s) on @lh else error pointer if fails to unpack */ -struct aa_profile *aa_unpack(void *udata, size_t size, const char **ns) +int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, + const char **ns) { + struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; + char *ns_name = NULL; int error; struct aa_ext e = { - .start = udata, - .end = udata + size, - .pos = udata, + .start = udata->data, + .end = udata->data + udata->size, + .pos = udata->data, }; - error = verify_header(&e, ns); - if (error) - return ERR_PTR(error); + *ns = NULL; + while (e.pos < e.end) { + void *start; + error = verify_header(&e, e.pos == e.start, ns); + if (error) + goto fail; + + start = e.pos; + profile = unpack_profile(&e, &ns_name); + if (IS_ERR(profile)) { + error = PTR_ERR(profile); + goto fail; + } + + error = verify_profile(profile); + if (error) + goto fail_profile; - profile = unpack_profile(&e); - if (IS_ERR(profile)) - return profile; + if (aa_g_hash_policy) + error = aa_calc_profile_hash(profile, e.version, start, + e.pos - start); + if (error) + goto fail_profile; - error = verify_profile(profile); - if (error) { - aa_put_profile(profile); - profile = ERR_PTR(error); + ent = aa_load_ent_alloc(); + if (!ent) { + error = -ENOMEM; + goto fail_profile; + } + + ent->new = profile; + ent->ns_name = ns_name; + ns_name = NULL; + list_add_tail(&ent->list, lh); + } + udata->abi = e.version & K_ABI_MASK; + if (aa_g_hash_policy) { + udata->hash = aa_calc_hash(udata->data, udata->size); + if (IS_ERR(udata->hash)) { + error = PTR_ERR(udata->hash); + udata->hash = NULL; + goto fail; + } } - /* return refcount */ - return profile; + 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: + list_for_each_entry_safe(ent, tmp, lh, list) { + list_del_init(&ent->list); + aa_load_ent_free(ent); + } + + return error; } diff --git a/security/apparmor/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 6c9390179b89..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,77 +6,63 @@ * * 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" #include "include/procattr.h" /** - * 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_profile *profile, char **string) +int aa_getprocattr(struct aa_label *label, char **string, bool newline) { - char *str; - int len = 0, mode_len = 0, ns_len = 0, name_len; - const char *mode_str = profile_mode_names[profile->mode]; - const char *ns_name = NULL; - struct aa_namespace *ns = profile->ns; - struct aa_namespace *current_ns = __aa_current_profile()->ns; - char *s; - - if (!aa_ns_visible(current_ns, ns)) - return -EACCES; + struct aa_ns *ns = labels_ns(label); + struct aa_ns *current_ns = aa_get_current_ns(); + int len; - ns_name = aa_ns_name(current_ns, ns); - ns_len = strlen(ns_name); - - /* if the visible ns_name is > 0 increase size for : :// seperator */ - if (ns_len) - ns_len += 4; + if (!aa_ns_visible(current_ns, ns, true)) { + aa_put_ns(current_ns); + return -EACCES; + } - /* unconfined profiles don't have a mode string appended */ - if (!unconfined(profile)) - mode_len = strlen(mode_str) + 3; /* + 3 for _() */ + len = aa_label_snxprint(NULL, 0, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + AA_BUG(len < 0); - name_len = strlen(profile->base.hname); - len = mode_len + ns_len + name_len + 1; /* + 1 for \n */ - s = str = kmalloc(len + 1, GFP_KERNEL); /* + 1 \0 */ - if (!str) + *string = kmalloc(len + 2, GFP_KERNEL); + if (!*string) { + aa_put_ns(current_ns); return -ENOMEM; + } - if (ns_len) { - /* skip over prefix current_ns->base.hname and separating // */ - sprintf(s, ":%s://", ns_name); - s += ns_len; + len = aa_label_snxprint(*string, len + 2, current_ns, label, + FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | + FLAG_HIDDEN_UNCONFINED); + if (len < 0) { + aa_put_ns(current_ns); + return len; } - if (unconfined(profile)) - /* mode string not being appended */ - sprintf(s, "%s\n", profile->base.hname); - else - sprintf(s, "%s (%s)\n", profile->base.hname, mode_str); - *string = str; - - /* NOTE: len does not include \0 of string, not saved as part of file */ + + if (newline) + (*string)[len++] = '\n'; + (*string)[len] = 0; + + aa_put_ns(current_ns); return len; } @@ -87,13 +74,13 @@ int aa_getprocattr(struct aa_profile *profile, char **string) * * Returns: start position of name after token else NULL on failure */ -static char *split_token_from_name(int op, char *args, u64 * token) +static char *split_token_from_name(const char *op, char *args, u64 *token) { char *name; *token = simple_strtoull(args, &name, 16); if ((name == args) || *name != '^') { - AA_ERROR("%s: Invalid input '%s'", op_table[op], args); + AA_ERROR("%s: Invalid input '%s'", op, args); return ERR_PTR(-EINVAL); } @@ -104,14 +91,14 @@ static char *split_token_from_name(int 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 - * @test: true if this is a test of change_hat permissions + * @flags: set of flags governing behavior * * Returns: %0 or error code if change_hat fails */ -int aa_setprocattr_changehat(char *args, size_t size, int test) +int aa_setprocattr_changehat(char *args, size_t size, int flags) { char *hat; u64 token; @@ -138,28 +125,15 @@ int aa_setprocattr_changehat(char *args, size_t size, int test) for (count = 0; (hat < end) && count < 16; ++count) { char *next = hat + strlen(hat) + 1; hats[count] = hat; + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" + , __func__, current->pid, token, count, hat); hat = next; } - } - - AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n", - __func__, token, hat ? hat : NULL); - - return aa_change_hat(hats, count, token, test); -} - -/** - * aa_setprocattr_changeprofile - handle procattr interface to changeprofile - * @fqname: args received from writting to /proc/<pid>/attr/current (NOT NULL) - * @onexec: true if change_profile should be delayed until exec - * @test: true if this is a test of change_profile permissions - * - * Returns: %0 or error code if change_profile fails - */ -int aa_setprocattr_changeprofile(char *fqname, bool onexec, int test) -{ - char *name, *ns_name; + } else + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", + __func__, current->pid, token, count, "<NULL>"); - name = aa_split_fqname(fqname, &ns_name); - return aa_change_profile(ns_name, name, onexec, test); + return aa_change_hat(hats, count, token, flags); } diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index 748bf0ca6c9f..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,17 +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" @@ -24,8 +21,8 @@ */ #include "rlim_names.h" -struct aa_fs_entry aa_fs_entry_rlimit[] = { - AA_FS_FILE_STRING("mask", AA_FS_RLIMIT_MASK), +struct aa_sfs_entry aa_sfs_entry_rlimit[] = { + AA_SFS_FILE_STRING("mask", AA_SFS_RLIMIT_MASK), { } }; @@ -33,38 +30,49 @@ struct aa_fs_entry aa_fs_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[sa->aad->rlim.rlim], sa->aad->rlim.max); + rlim_names[ad->rlim.rlim], ad->rlim.max); + if (ad->peer) { + audit_log_format(ab, " 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, - unsigned long value, int error) +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) { - struct common_audit_data sa; - struct apparmor_audit_data aad = {0,}; - - sa.type = LSM_AUDIT_DATA_NONE; - sa.aad = &aad; - aad.op = OP_SETRLIMIT, - aad.rlim.rlim = resource; - aad.rlim.max = value; - aad.error = error; - return aa_audit(AUDIT_APPARMOR_AUTO, profile, GFP_KERNEL, &sa, - audit_cb); + DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS, + OP_SETRLIMIT); + + 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, &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. @@ -77,78 +85,117 @@ int aa_map_resource(int resource) return rlim_map[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 (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > + rules->rlimits.limits[resource].rlim_max) + e = -EACCES; + return audit_resource(subj_cred, profile, resource, new_rlim->rlim_max, + NULL, NULL, e); +} + /** * aa_task_setrlimit - test permission to set an rlimit - * @profile - profile 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_profile *profile, 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 *task_profile; + struct aa_profile *profile; + struct aa_label *peer; int error = 0; rcu_read_lock(); - task_profile = aa_get_profile(aa_cred_profile(__task_cred(task))); + peer = aa_get_newest_cred_label(__task_cred(task)); rcu_read_unlock(); /* TODO: extend resource control to handle other (non current) * profiles. AppArmor rules currently have the implicit assumption * that the task is setting the resource of a task confined with - * the same profile. + * the same profile or that the task setting the resource of another + * task has CAP_SYS_RESOURCE. */ - if (profile != task_profile || - (profile->rlimits.mask & (1 << resource) && - new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max)) - error = -EACCES; - - aa_put_profile(task_profile); - return audit_resource(profile, resource, new_rlim->rlim_max, error); + if (label != peer && + aa_capable(subj_cred, label, CAP_SYS_RESOURCE, CAP_OPT_NOAUDIT) != 0) + error = fn_for_each(label, profile, + audit_resource(subj_cred, profile, resource, + new_rlim->rlim_max, peer, + "cap_sys_resource", -EACCES)); + else + error = fn_for_each_confined(label, profile, + profile_setrlimit(subj_cred, profile, resource, + new_rlim)); + aa_put_label(peer); + + return error; } /** * __aa_transition_rlimits - apply new profile rlimits - * @old: old profile on task (NOT NULL) - * @new: new profile with rlimits to apply (NOT NULL) + * @old_l: old label on task (NOT NULL) + * @new_l: new label with rlimits to apply (NOT NULL) */ -void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new) +void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) { unsigned int mask = 0; struct rlimit *rlim, *initrlim; - int i; + struct aa_profile *old, *new; + struct label_it i; + + old = labels_profile(old_l); + new = labels_profile(new_l); - /* for any rlimits the profile controlled reset the soft limit - * to the less of the tasks hard limit and the init tasks soft limit + /* for any rlimits the profile controlled, reset the soft limit + * to the lesser of the tasks hard limit and the init tasks soft limit */ - if (old->rlimits.mask) { - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (old->rlimits.mask & mask) { - rlim = current->signal->rlim + i; - initrlim = init_task.signal->rlim + i; - rlim->rlim_cur = min(rlim->rlim_max, - initrlim->rlim_cur); + label_for_each_confined(i, old_l, old) { + 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 (rules->rlimits.mask & mask) { + rlim = current->signal->rlim + j; + initrlim = init_task.signal->rlim + j; + rlim->rlim_cur = min(rlim->rlim_max, + initrlim->rlim_cur); + } } } } /* set any new hard limits as dictated by the new profile */ - if (!new->rlimits.mask) - return; - for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) { - if (!(new->rlimits.mask & mask)) - continue; + label_for_each_confined(i, new_l, new) { + struct aa_ruleset *rules = new->label.rules[0]; + int j; - rlim = current->signal->rlim + i; - rlim->rlim_max = min(rlim->rlim_max, - new->rlimits.limits[i].rlim_max); - /* soft limit should not exceed hard limit */ - rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max); + if (!rules->rlimits.mask) + continue; + for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) { + if (!(rules->rlimits.mask & mask)) + continue; + + rlim = current->signal->rlim + j; + rlim->rlim_max = min(rlim->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 new file mode 100644 index 000000000000..28caf66b9033 --- /dev/null +++ b/security/apparmor/secid.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor security identifier (secid) manipulation fns + * + * Copyright 2009-2017 Canonical Ltd. + * + * 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/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); + + if (len < 0) + return -ENOMEM; + + 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 + */ +int aa_alloc_secid(struct aa_label *label, gfp_t gfp) +{ + 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); + + if (ret < 0) { + label->secid = AA_SECID_INVALID; + return ret; + } + + return 0; +} + +/** + * aa_free_secid - free a secid + * @secid: secid to free + */ +void aa_free_secid(u32 secid) +{ + 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/sid.c b/security/apparmor/sid.c deleted file mode 100644 index f0b34f76ebef..000000000000 --- a/security/apparmor/sid.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * AppArmor security module - * - * This file contains AppArmor security identifier (sid) manipulation fns - * - * 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 allocates a unique sid for every profile loaded. If a profile - * is replaced it receives the sid of the profile it is replacing. - * - * The sid value of 0 is invalid. - */ - -#include <linux/spinlock.h> -#include <linux/errno.h> -#include <linux/err.h> - -#include "include/sid.h" - -/* global counter from which sids are allocated */ -static u32 global_sid; -static DEFINE_SPINLOCK(sid_lock); - -/* TODO FIXME: add sid to profile mapping, and sid recycling */ - -/** - * aa_alloc_sid - allocate a new sid for a profile - */ -u32 aa_alloc_sid(void) -{ - u32 sid; - - /* - * TODO FIXME: sid recycling - part of profile mapping table - */ - spin_lock(&sid_lock); - sid = (++global_sid); - spin_unlock(&sid_lock); - return sid; -} - -/** - * aa_free_sid - free a sid - * @sid: sid to free - */ -void aa_free_sid(u32 sid) -{ - ; /* NOP ATM */ -} 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; +} |
