summaryrefslogtreecommitdiff
path: root/security/apparmor
diff options
context:
space:
mode:
Diffstat (limited to 'security/apparmor')
-rw-r--r--security/apparmor/Kconfig89
-rw-r--r--security/apparmor/Makefile10
-rw-r--r--security/apparmor/af_unix.c799
-rw-r--r--security/apparmor/apparmorfs.c298
-rw-r--r--security/apparmor/audit.c124
-rw-r--r--security/apparmor/capability.c117
-rw-r--r--security/apparmor/crypto.c94
-rw-r--r--security/apparmor/domain.c499
-rw-r--r--security/apparmor/file.c361
-rw-r--r--security/apparmor/include/af_unix.h55
-rw-r--r--security/apparmor/include/apparmor.h29
-rw-r--r--security/apparmor/include/apparmorfs.h16
-rw-r--r--security/apparmor/include/audit.h57
-rw-r--r--security/apparmor/include/capability.h4
-rw-r--r--security/apparmor/include/cred.h58
-rw-r--r--security/apparmor/include/crypto.h1
-rw-r--r--security/apparmor/include/domain.h6
-rw-r--r--security/apparmor/include/file.h172
-rw-r--r--security/apparmor/include/ipc.h23
-rw-r--r--security/apparmor/include/label.h92
-rw-r--r--security/apparmor/include/lib.h64
-rw-r--r--security/apparmor/include/match.h50
-rw-r--r--security/apparmor/include/mount.h26
-rw-r--r--security/apparmor/include/net.h65
-rw-r--r--security/apparmor/include/path.h5
-rw-r--r--security/apparmor/include/perms.h99
-rw-r--r--security/apparmor/include/policy.h221
-rw-r--r--security/apparmor/include/policy_compat.h33
-rw-r--r--security/apparmor/include/policy_ns.h15
-rw-r--r--security/apparmor/include/policy_unpack.h54
-rw-r--r--security/apparmor/include/procattr.h2
-rw-r--r--security/apparmor/include/resource.h3
-rw-r--r--security/apparmor/include/secid.h11
-rw-r--r--security/apparmor/include/sig_names.h6
-rw-r--r--security/apparmor/include/signal.h19
-rw-r--r--security/apparmor/include/task.h27
-rw-r--r--security/apparmor/ipc.c174
-rw-r--r--security/apparmor/label.c176
-rw-r--r--security/apparmor/lib.c313
-rw-r--r--security/apparmor/lsm.c1197
-rw-r--r--security/apparmor/match.c224
-rw-r--r--security/apparmor/mount.c272
-rw-r--r--security/apparmor/net.c253
-rw-r--r--security/apparmor/path.c2
-rw-r--r--security/apparmor/policy.c417
-rw-r--r--security/apparmor/policy_compat.c327
-rw-r--r--security/apparmor/policy_ns.c92
-rw-r--r--security/apparmor/policy_unpack.c1010
-rw-r--r--security/apparmor/policy_unpack_test.c137
-rw-r--r--security/apparmor/procattr.c29
-rw-r--r--security/apparmor/resource.c76
-rw-r--r--security/apparmor/secid.c108
-rw-r--r--security/apparmor/task.c171
53 files changed, 5870 insertions, 2712 deletions
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
index 348ed6cfa08a..1e3bd44643da 100644
--- a/security/apparmor/Kconfig
+++ b/security/apparmor/Kconfig
@@ -6,8 +6,6 @@ config SECURITY_APPARMOR
select SECURITY_PATH
select SECURITYFS
select SECURITY_NETWORK
- select ZLIB_INFLATE
- select ZLIB_DEFLATE
default n
help
This enables the AppArmor security module.
@@ -17,29 +15,6 @@ config SECURITY_APPARMOR
If you are unsure how to answer this question, answer N.
-config SECURITY_APPARMOR_HASH
- bool "Enable introspection of sha1 hashes for loaded profiles"
- depends on SECURITY_APPARMOR
- select CRYPTO
- select CRYPTO_SHA1
- default y
- help
- This option selects whether introspection of loaded policy
- is available to userspace via the apparmor filesystem.
-
-config SECURITY_APPARMOR_HASH_DEFAULT
- bool "Enable policy hash introspection by default"
- depends on SECURITY_APPARMOR_HASH
- default y
- help
- This option selects whether sha1 hashing of loaded policy
- is enabled by default. The generation of sha1 hashes for
- loaded policy provide system administrators a quick way
- to verify that policy in the kernel matches what is expected,
- however it can slow down policy load on some devices. In
- these cases policy hashing can be disabled by default and
- enabled only if needed.
-
config SECURITY_APPARMOR_DEBUG
bool "Build AppArmor with debug code"
depends on SECURITY_APPARMOR
@@ -69,9 +44,69 @@ config SECURITY_APPARMOR_DEBUG_MESSAGES
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
- bool "Build KUnit tests for policy_unpack.c" if !KUNIT_ALL_TESTS
- depends on KUNIT=y && SECURITY_APPARMOR
+ 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.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
index ff23fcfefe19..12fb419714c0 100644
--- a/security/apparmor/Makefile
+++ b/security/apparmor/Makefile
@@ -5,9 +5,13 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
- resource.o secid.o file.o policy_ns.o label.o mount.o net.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
+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
@@ -24,7 +28,7 @@ clean-files := capability_names.h rlim_names.h net_names.h
# to
# #define AA_SFS_AF_MASK "local inet"
quiet_cmd_make-af = GEN $@
-cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
+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 "};" >> $@ ;\
@@ -39,7 +43,7 @@ cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\
# to
# [1] = "stream",
quiet_cmd_make-sock = GEN $@
-cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\
+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 "};" >> $@
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 0797edb2fb3d..907bd2667e28 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -21,7 +21,7 @@
#include <linux/fs.h>
#include <linux/fs_context.h>
#include <linux/poll.h>
-#include <linux/zlib.h>
+#include <linux/zstd.h>
#include <uapi/linux/major.h>
#include <uapi/linux/magic.h>
@@ -36,13 +36,14 @@
#include "include/policy_ns.h"
#include "include/resource.h"
#include "include/policy_unpack.h"
+#include "include/task.h"
/*
* The apparmor filesystem interface used for policy load and introspection
* The interface is split into two main components based on their function
* a securityfs component:
* used for static files that are always available, and which allows
- * userspace to specificy the location of the security filesystem.
+ * userspace to specify the location of the security filesystem.
*
* fns and data are prefixed with
* aa_sfs_
@@ -70,6 +71,7 @@ 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)
@@ -94,9 +96,10 @@ static struct rawdata_f_data *rawdata_f_data_alloc(size_t size)
return ret;
}
+#endif
/**
- * aa_mangle_name - mangle a profile name to std profile layout form
+ * mangle_name - mangle a profile name to std profile layout form
* @name: profile name to mangle (NOT NULL)
* @target: buffer to store mangled name, same length as @name (MAYBE NULL)
*
@@ -201,7 +204,7 @@ static struct file_system_type aafs_ops = {
/**
* __aafs_setup_d_inode - basic inode setup for apparmorfs
* @dir: parent directory for the dentry
- * @dentry: dentry we are seting the inode up for
+ * @dentry: dentry we are setting the inode up for
* @mode: permissions the file should have
* @data: data to store on inode.i_private, available in open()
* @link: if symlink, symlink target string
@@ -223,7 +226,7 @@ static int __aafs_setup_d_inode(struct inode *dir, struct dentry *dentry,
inode->i_ino = get_next_ino();
inode->i_mode = mode;
- inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
+ simple_inode_init_ts(inode);
inode->i_private = data;
if (S_ISDIR(mode)) {
inode->i_op = iops ? iops : &simple_dir_inode_operations;
@@ -280,7 +283,7 @@ static struct dentry *aafs_create(const char *name, umode_t mode,
dir = d_inode(parent);
inode_lock(dir);
- dentry = lookup_one_len(name, parent, strlen(name));
+ dentry = lookup_noperm(&QSTR(name), parent);
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
goto fail_lock;
@@ -352,17 +355,22 @@ static void aafs_remove(struct dentry *dentry)
if (!dentry || IS_ERR(dentry))
return;
+ /* ->d_parent is stable as rename is not supported */
dir = d_inode(dentry->d_parent);
- inode_lock(dir);
- if (simple_positive(dentry)) {
- if (d_is_dir(dentry))
- simple_rmdir(dir, dentry);
- else
- simple_unlink(dir, dentry);
+ 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);
- dput(dentry);
}
- inode_unlock(dir);
+ end_removing(dentry);
simple_release_fs(&aafs_mnt, &aafs_count);
}
@@ -401,7 +409,7 @@ static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
data->size = copy_size;
if (copy_from_user(data->data, userbuf, copy_size)) {
- kvfree(data);
+ aa_put_loaddata(data);
return ERR_PTR(-EFAULT);
}
@@ -420,7 +428,7 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
/* high level check about policy management - fine grained in
* below after unpack
*/
- error = aa_may_manage_policy(label, ns, mask);
+ error = aa_may_manage_policy(current_cred(), label, ns, mask);
if (error)
goto end_section;
@@ -483,7 +491,8 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
/* high level check about policy management - fine grained in
* below after unpack
*/
- error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY);
+ error = aa_may_manage_policy(current_cred(), label, ns,
+ AA_MAY_REMOVE_POLICY);
if (error)
goto out;
@@ -608,29 +617,38 @@ static const struct file_operations aa_fs_ns_revision_fops = {
static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
const char *match_str, size_t match_len)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms tmp = { };
- struct aa_dfa *dfa;
- unsigned int state = 0;
+ aa_state_t state = DFA_NOMATCH;
if (profile_unconfined(profile))
return;
- if (profile->file.dfa && *match_str == AA_CLASS_FILE) {
- dfa = profile->file.dfa;
- state = aa_dfa_match_len(dfa, profile->file.start,
+ if (rules->file->dfa && *match_str == AA_CLASS_FILE) {
+ state = aa_dfa_match_len(rules->file->dfa,
+ rules->file->start[AA_CLASS_FILE],
match_str + 1, match_len - 1);
if (state) {
struct path_cond cond = { };
- tmp = aa_compute_fperms(dfa, state, &cond);
+ tmp = *(aa_lookup_condperms(current_fsuid(),
+ rules->file, state, &cond));
}
- } else if (profile->policy.dfa) {
- if (!PROFILE_MEDIATES(profile, *match_str))
+ } else if (rules->policy->dfa) {
+ if (!RULE_MEDIATES(rules, *match_str))
return; /* no change to current perms */
- dfa = profile->policy.dfa;
- state = aa_dfa_match_len(dfa, profile->policy.start[0],
+ /* old user space does not correctly detect dbus mediation
+ * support so we may get dbus policy and requests when
+ * the abi doesn't support it. This can cause mediation
+ * regressions, so explicitly test for this situation.
+ */
+ if (*match_str == AA_CLASS_DBUS &&
+ !RULE_MEDIATES_v9NET(rules))
+ return; /* no change to current perms */
+ state = aa_dfa_match_len(rules->policy->dfa,
+ rules->policy->start[0],
match_str, match_len);
if (state)
- aa_compute_perms(dfa, state, &tmp);
+ tmp = *aa_lookup_perms(rules->policy, state);
}
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum_raw(perms, &tmp);
@@ -865,8 +883,10 @@ static struct multi_transaction *multi_transaction_new(struct file *file,
if (!t)
return ERR_PTR(-ENOMEM);
kref_init(&t->count);
- if (copy_from_user(t->data, buf, size))
+ if (copy_from_user(t->data, buf, size)) {
+ put_multi_transaction(t);
return ERR_PTR(-EFAULT);
+ }
return t;
}
@@ -990,7 +1010,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v)
switch (fs_file->v_type) {
case AA_SFS_TYPE_BOOLEAN:
- seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no");
+ seq_printf(seq, "%s\n", str_yes_no(fs_file->v.boolean));
break;
case AA_SFS_TYPE_STRING:
seq_printf(seq, "%s\n", fs_file->v.string);
@@ -999,7 +1019,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v)
seq_printf(seq, "%#08lx\n", fs_file->v.u64);
break;
default:
- /* Ignore unpritable entry types. */
+ /* Ignore unprintable entry types. */
break;
}
@@ -1087,9 +1107,9 @@ static int seq_profile_attach_show(struct seq_file *seq, void *v)
struct aa_proxy *proxy = seq->private;
struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
- if (profile->attach)
- seq_printf(seq, "%s\n", profile->attach);
- else if (profile->xmatch)
+ if (profile->attach.xmatch_str)
+ seq_printf(seq, "%s\n", profile->attach.xmatch_str);
+ else if (profile->attach.xmatch->dfa)
seq_puts(seq, "<unknown>\n");
else
seq_printf(seq, "%s\n", profile->base.name);
@@ -1145,7 +1165,7 @@ static int seq_ns_stacked_show(struct seq_file *seq, void *v)
struct aa_label *label;
label = begin_current_label_crit_section();
- seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no");
+ seq_printf(seq, "%s\n", str_yes_no(label->size > 1));
end_current_label_crit_section(label);
return 0;
@@ -1168,7 +1188,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v)
}
}
- seq_printf(seq, "%s\n", count > 1 ? "yes" : "no");
+ seq_printf(seq, "%s\n", str_yes_no(count > 1));
end_current_label_crit_section(label);
return 0;
@@ -1194,14 +1214,28 @@ static int seq_ns_name_show(struct seq_file *seq, void *v)
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)\
{ \
@@ -1292,46 +1326,39 @@ SEQ_RAWDATA_FOPS(revision);
SEQ_RAWDATA_FOPS(hash);
SEQ_RAWDATA_FOPS(compressed_size);
-static int deflate_decompress(char *src, size_t slen, char *dst, size_t dlen)
+static int decompress_zstd(char *src, size_t slen, char *dst, size_t dlen)
{
- int error;
- struct z_stream_s strm;
+ if (slen < dlen) {
+ const size_t wksp_len = zstd_dctx_workspace_bound();
+ zstd_dctx *ctx;
+ void *wksp;
+ size_t out_len;
+ int ret = 0;
- if (aa_g_rawdata_compression_level == 0) {
- if (dlen < slen)
- return -EINVAL;
- memcpy(dst, src, slen);
- return 0;
- }
-
- memset(&strm, 0, sizeof(strm));
-
- strm.workspace = kvzalloc(zlib_inflate_workspacesize(), GFP_KERNEL);
- if (!strm.workspace)
- return -ENOMEM;
-
- strm.next_in = src;
- strm.avail_in = slen;
-
- error = zlib_inflateInit(&strm);
- if (error != Z_OK) {
- error = -ENOMEM;
- goto fail_inflate_init;
+ 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;
}
- strm.next_out = dst;
- strm.avail_out = dlen;
-
- error = zlib_inflate(&strm, Z_FINISH);
- if (error != Z_STREAM_END)
- error = -EINVAL;
- else
- error = 0;
-
- zlib_inflateEnd(&strm);
-fail_inflate_init:
- kvfree(strm.workspace);
- return error;
+ 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,
@@ -1373,9 +1400,9 @@ static int rawdata_open(struct inode *inode, struct file *file)
private->loaddata = loaddata;
- error = deflate_decompress(loaddata->data, loaddata->compressed_size,
- RAWDATA_F_DATA_BUF(private),
- loaddata->size);
+ error = decompress_zstd(loaddata->data, loaddata->compressed_size,
+ RAWDATA_F_DATA_BUF(private),
+ loaddata->size);
if (error)
goto fail_decompress;
@@ -1460,7 +1487,7 @@ int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata)
rawdata->dents[AAFS_LOADDATA_REVISION] = dent;
if (aa_g_hash_policy) {
- dent = aafs_create_file("sha1", S_IFREG | 0444, dir,
+ dent = aafs_create_file("sha256", S_IFREG | 0444, dir,
rawdata, &seq_rawdata_hash_fops);
if (IS_ERR(dent))
goto fail;
@@ -1492,10 +1519,12 @@ fail:
return PTR_ERR(dent);
}
+#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
+
/** fns to setup dynamic per profile/namespace files **/
-/**
+/*
*
* Requires: @profile->ns->lock held
*/
@@ -1522,7 +1551,7 @@ void __aafs_profile_rmdir(struct aa_profile *profile)
}
}
-/**
+/*
*
* Requires: @old->ns->lock held
*/
@@ -1537,8 +1566,12 @@ void __aafs_profile_migrate_dents(struct aa_profile *old,
for (i = 0; i < AAFS_PROF_SIZEOF; i++) {
new->dents[i] = old->dents[i];
- if (new->dents[i])
- new->dents[i]->d_inode->i_mtime = current_time(new->dents[i]->d_inode);
+ if (new->dents[i]) {
+ struct inode *inode = d_inode(new->dents[i]);
+
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
+ }
old->dents[i] = NULL;
}
}
@@ -1557,6 +1590,7 @@ static struct dentry *create_profile_file(struct dentry *dir, const char *name,
return dent;
}
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
static int profile_depth(struct aa_profile *profile)
{
int depth = 0;
@@ -1594,11 +1628,6 @@ static char *gen_symlink_name(int depth, const char *dirname, const char *fname)
return buffer;
}
-static void rawdata_link_cb(void *arg)
-{
- kfree(arg);
-}
-
static const char *rawdata_get_link_base(struct dentry *dentry,
struct inode *inode,
struct delayed_call *done,
@@ -1622,16 +1651,16 @@ static const char *rawdata_get_link_base(struct dentry *dentry,
if (IS_ERR(target))
return target;
- set_delayed_call(done, rawdata_link_cb, target);
+ set_delayed_call(done, kfree_link, target);
return target;
}
-static const char *rawdata_get_link_sha1(struct dentry *dentry,
+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, "sha1");
+ return rawdata_get_link_base(dentry, inode, done, "sha256");
}
static const char *rawdata_get_link_abi(struct dentry *dentry,
@@ -1648,8 +1677,8 @@ static const char *rawdata_get_link_data(struct dentry *dentry,
return rawdata_get_link_base(dentry, inode, done, "raw_data");
}
-static const struct inode_operations rawdata_link_sha1_iops = {
- .get_link = rawdata_get_link_sha1,
+static const struct inode_operations rawdata_link_sha256_iops = {
+ .get_link = rawdata_get_link_sha256,
};
static const struct inode_operations rawdata_link_abi_iops = {
@@ -1658,7 +1687,7 @@ static const struct inode_operations rawdata_link_abi_iops = {
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
@@ -1676,6 +1705,10 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *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))
@@ -1722,22 +1755,24 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
profile->dents[AAFS_PROF_ATTACH] = dent;
if (profile->hash) {
- dent = create_profile_file(dir, "sha1", profile,
+ dent = create_profile_file(dir, "sha256", profile,
&seq_profile_hash_fops);
if (IS_ERR(dent))
goto fail;
profile->dents[AAFS_PROF_HASH] = dent;
}
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
if (profile->rawdata) {
- dent = aafs_create("raw_sha1", S_IFLNK | 0444, dir,
- profile->label.proxy, NULL, NULL,
- &rawdata_link_sha1_iops);
- if (IS_ERR(dent))
- goto fail;
- aa_get_proxy(profile->label.proxy);
- profile->dents[AAFS_PROF_RAW_HASH] = dent;
-
+ 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);
@@ -1754,6 +1789,7 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
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));
@@ -1772,8 +1808,8 @@ fail2:
return error;
}
-static int ns_mkdir_op(struct user_namespace *mnt_userns, struct inode *dir,
- struct dentry *dentry, umode_t mode)
+static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
{
struct aa_ns *ns, *parent;
/* TODO: improve permission check */
@@ -1781,10 +1817,11 @@ static int ns_mkdir_op(struct user_namespace *mnt_userns, struct inode *dir,
int error;
label = begin_current_label_crit_section();
- error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
+ error = aa_may_manage_policy(current_cred(), label, NULL,
+ AA_MAY_LOAD_POLICY);
end_current_label_crit_section(label);
if (error)
- return error;
+ return ERR_PTR(error);
parent = aa_get_ns(dir->i_private);
AA_BUG(d_inode(ns_subns_dir(parent)) != dir);
@@ -1819,7 +1856,7 @@ out:
mutex_unlock(&parent->lock);
aa_put_ns(parent);
- return error;
+ return ERR_PTR(error);
}
static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
@@ -1830,7 +1867,8 @@ static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
int error;
label = begin_current_label_crit_section();
- error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
+ error = aa_may_manage_policy(current_cred(), label, NULL,
+ AA_MAY_LOAD_POLICY);
end_current_label_crit_section(label);
if (error)
return error;
@@ -1880,7 +1918,7 @@ static void __aa_fs_list_remove_rawdata(struct aa_ns *ns)
__aa_fs_remove_rawdata(ent);
}
-/**
+/*
*
* Requires: @ns->lock held
*/
@@ -2219,7 +2257,7 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos)
/**
* p_stop - stop depth first traversal
* @f: seq_file we are filling
- * @p: the last profile writen
+ * @p: the last profile written
*
* Release all locking done by p_start/p_next on namespace tree
*/
@@ -2307,6 +2345,7 @@ static struct aa_sfs_entry aa_sfs_entry_attach[] = {
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),
@@ -2314,34 +2353,54 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = {
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"),
{ }
};
@@ -2356,11 +2415,18 @@ static struct aa_sfs_entry aa_sfs_entry_query[] = {
AA_SFS_DIR("label", aa_sfs_entry_query_label),
{ }
};
+
+static struct aa_sfs_entry aa_sfs_entry_io_uring[] = {
+ AA_SFS_FILE_STRING("mask", "sqpoll override_creds"),
+ { }
+};
+
static struct aa_sfs_entry aa_sfs_entry_features[] = {
AA_SFS_DIR("policy", aa_sfs_entry_policy),
AA_SFS_DIR("domain", aa_sfs_entry_domain),
AA_SFS_DIR("file", aa_sfs_entry_file),
AA_SFS_DIR("network_v8", aa_sfs_entry_network),
+ AA_SFS_DIR("network_v9", aa_sfs_entry_networkv9),
AA_SFS_DIR("mount", aa_sfs_entry_mount),
AA_SFS_DIR("namespaces", aa_sfs_entry_ns),
AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK),
@@ -2368,7 +2434,9 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = {
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),
{ }
};
@@ -2379,6 +2447,8 @@ static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
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),
{ }
};
@@ -2503,7 +2573,7 @@ static int aa_mk_null_file(struct dentry *parent)
return error;
inode_lock(d_inode(parent));
- dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME));
+ dentry = lookup_noperm(&QSTR(NULL_FILE_NAME), parent);
if (IS_ERR(dentry)) {
error = PTR_ERR(dentry);
goto out;
@@ -2516,7 +2586,7 @@ static int aa_mk_null_file(struct dentry *parent)
inode->i_ino = get_next_ino();
inode->i_mode = S_IFCHR | S_IRUGO | S_IWUGO;
- inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
+ simple_inode_init_ts(inode);
init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO,
MKDEV(MEM_MAJOR, 3));
d_instantiate(dentry, inode);
@@ -2564,7 +2634,7 @@ static int policy_readlink(struct dentry *dentry, char __user *buffer,
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);
+ res = readlink_copy(buffer, buflen, name, strlen(name));
else
res = -ENOENT;
@@ -2584,7 +2654,7 @@ static const struct inode_operations policy_link_iops = {
*
* Returns: error on failure
*/
-static int __init aa_create_aafs(void)
+int __init aa_create_aafs(void)
{
struct dentry *dent;
int error;
@@ -2663,5 +2733,3 @@ error:
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 f7e97c7e80f3..ac89602aa2d9 100644
--- a/security/apparmor/audit.c
+++ b/security/apparmor/audit.c
@@ -36,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.
*
@@ -46,33 +83,38 @@ static const char *const aa_audit_type[] = {
*/
/**
- * audit_base - core AppArmor function.
+ * audit_pre() - core AppArmor function.
* @ab: audit buffer to fill (NOT NULL)
- * @ca: audit structure containing data to audit (NOT NULL)
+ * @va: audit structure containing data to audit (NOT NULL)
*
- * Record common AppArmor audit data from @sa
+ * Record common AppArmor audit data from @va
*/
-static void audit_pre(struct audit_buffer *ab, void *ca)
+static void audit_pre(struct audit_buffer *ab, void *va)
{
- struct common_audit_data *sa = ca;
+ struct apparmor_audit_data *ad = aad_of_va(va);
if (aa_g_audit_header) {
audit_log_format(ab, "apparmor=\"%s\"",
- aa_audit_type[aad(sa)->type]);
+ aa_audit_type[ad->type]);
}
- if (aad(sa)->op) {
- audit_log_format(ab, " operation=\"%s\"", aad(sa)->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 (aad(sa)->info) {
- audit_log_format(ab, " info=\"%s\"", aad(sa)->info);
- if (aad(sa)->error)
- audit_log_format(ab, " error=%d", aad(sa)->error);
+ if (ad->info) {
+ audit_log_format(ab, " info=\"%s\"", ad->info);
+ if (ad->error)
+ audit_log_format(ab, " error=%d", ad->error);
}
- if (aad(sa)->label) {
- struct aa_label *label = aad(sa)->label;
+ if (ad->subj_label) {
+ struct aa_label *label = ad->subj_label;
if (label_isprofile(label)) {
struct aa_profile *profile = labels_profile(label);
@@ -91,42 +133,44 @@ static void audit_pre(struct audit_buffer *ab, void *ca)
}
}
- if (aad(sa)->name) {
+ if (ad->name) {
audit_log_format(ab, " name=");
- audit_log_untrustedstring(ab, aad(sa)->name);
+ audit_log_untrustedstring(ab, ad->name);
}
}
/**
* aa_audit_msg - Log a message to the audit subsystem
- * @sa: audit event structure (NOT NULL)
+ * @type: audit type for the message
+ * @ad: audit event structure (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*/
-void aa_audit_msg(int type, struct common_audit_data *sa,
+void aa_audit_msg(int type, struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *))
{
- aad(sa)->type = type;
- common_lsm_audit(sa, audit_pre, cb);
+ ad->type = type;
+ common_lsm_audit(&ad->common, audit_pre, cb);
}
/**
* aa_audit - Log a profile based audit event to the audit subsystem
* @type: audit type for the message
* @profile: profile to check against (NOT NULL)
- * @sa: audit event (NOT NULL)
+ * @ad: audit event (NOT NULL)
* @cb: optional callback fn for type specific fields (MAYBE NULL)
*
* Handle default message switching based off of audit mode flags
*
* Returns: error on failure
*/
-int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
+int aa_audit(int type, struct aa_profile *profile,
+ struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *))
{
AA_BUG(!profile);
if (type == AUDIT_APPARMOR_AUTO) {
- if (likely(!aad(sa)->error)) {
+ if (likely(!ad->error)) {
if (AUDIT_MODE(profile) != AUDIT_ALL)
return 0;
type = AUDIT_APPARMOR_AUDIT;
@@ -137,25 +181,25 @@ int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
}
if (AUDIT_MODE(profile) == AUDIT_QUIET ||
(type == AUDIT_APPARMOR_DENIED &&
- AUDIT_MODE(profile) == AUDIT_QUIET))
- return aad(sa)->error;
+ AUDIT_MODE(profile) == AUDIT_QUIET_DENIED))
+ return ad->error;
if (KILL_MODE(profile) && type == AUDIT_APPARMOR_DENIED)
type = AUDIT_APPARMOR_KILL;
- aad(sa)->label = &profile->label;
+ ad->subj_label = &profile->label;
- aa_audit_msg(type, sa, cb);
+ aa_audit_msg(type, ad, cb);
- if (aad(sa)->type == AUDIT_APPARMOR_KILL)
- (void)send_sig_info(SIGKILL, NULL,
- sa->type == LSM_AUDIT_DATA_TASK && sa->u.tsk ?
- sa->u.tsk : current);
+ if (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 (aad(sa)->type == AUDIT_APPARMOR_ALLOWED)
- return complain_error(aad(sa)->error);
+ if (ad->type == AUDIT_APPARMOR_ALLOWED)
+ return complain_error(ad->error);
- return aad(sa)->error;
+ return ad->error;
}
struct aa_audit_rule {
@@ -173,7 +217,7 @@ void aa_audit_rule_free(void *vrule)
}
}
-int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
+int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule, gfp_t gfp)
{
struct aa_audit_rule *rule;
@@ -186,14 +230,14 @@ int aa_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
return -EINVAL;
}
- rule = kzalloc(sizeof(struct aa_audit_rule), GFP_KERNEL);
+ rule = kzalloc(sizeof(struct aa_audit_rule), gfp);
if (!rule)
return -ENOMEM;
/* Currently rules are treated as coming from the root ns */
rule->label = aa_label_parse(&root_ns->unconfined->label, rulestr,
- GFP_KERNEL, true, false);
+ gfp, true, false);
if (IS_ERR(rule->label)) {
int err = PTR_ERR(rule->label);
aa_audit_rule_free(rule);
@@ -220,13 +264,13 @@ int aa_audit_rule_known(struct audit_krule *rule)
return 0;
}
-int aa_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule)
+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 = aa_secid_to_label(sid);
+ label = prop->apparmor.label;
if (!label)
return -ENOENT;
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index deccea8654ad..b9ea6bc45c1a 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -12,6 +12,7 @@
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/security.h>
+#include <linux/timekeeping.h>
#include "include/apparmor.h"
#include "include/capability.h"
@@ -26,20 +27,22 @@
struct aa_sfs_entry aa_sfs_entry_caps[] = {
AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK),
+ AA_SFS_FILE_BOOLEAN("extended", 1),
{ }
};
struct audit_cache {
- struct aa_profile *profile;
- kernel_cap_t caps;
+ const struct cred *ad_subj_cred;
+ /* Capabilities go from 0 to CAP_LAST_CAP */
+ u64 ktime_ns_expiration[CAP_LAST_CAP+1];
};
static DEFINE_PER_CPU(struct audit_cache, audit_cache);
/**
* audit_cb - call back for capability components of audit struct
- * @ab - audit buffer (NOT NULL)
- * @va - audit struct to audit data from (NOT NULL)
+ * @ab: audit buffer (NOT NULL)
+ * @va: audit struct to audit data from (NOT NULL)
*/
static void audit_cb(struct audit_buffer *ab, void *va)
{
@@ -51,7 +54,7 @@ static void audit_cb(struct audit_buffer *ab, void *va)
/**
* audit_caps - audit a capability
- * @sa: audit data
+ * @ad: audit data
* @profile: profile being tested for confinement (NOT NULL)
* @cap: capability tested
* @error: error code returned by test
@@ -59,26 +62,29 @@ static void audit_cb(struct audit_buffer *ab, void *va)
* Do auditing of capability and handle, audit/complain/kill modes switching
* and duplicate message elimination.
*
- * Returns: 0 or sa->error on success, error code on failure
+ * Returns: 0 or ad->error on success, error code on failure
*/
-static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
+static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile,
int cap, int error)
{
+ const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */
+
+ struct aa_ruleset *rules = profile->label.rules[0];
struct audit_cache *ent;
int type = AUDIT_APPARMOR_AUTO;
- aad(sa)->error = error;
+ ad->error = error;
if (likely(!error)) {
/* test if auditing is being forced */
if (likely((AUDIT_MODE(profile) != AUDIT_ALL) &&
- !cap_raised(profile->caps.audit, cap)))
+ !cap_raised(rules->caps.audit, cap)))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else if (KILL_MODE(profile) ||
- cap_raised(profile->caps.kill, cap)) {
+ cap_raised(rules->caps.kill, cap)) {
type = AUDIT_APPARMOR_KILL;
- } else if (cap_raised(profile->caps.quiet, cap) &&
+ } else if (cap_raised(rules->caps.quiet, cap) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL) {
/* quiet auditing */
@@ -87,19 +93,20 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
/* Do simple duplicate message elimination */
ent = &get_cpu_var(audit_cache);
- if (profile == ent->profile && cap_raised(ent->caps, cap)) {
+ /* If the capability was never raised the timestamp check would also catch that */
+ if (ad->subj_cred == ent->ad_subj_cred && ktime_get_ns() <= ent->ktime_ns_expiration[cap]) {
put_cpu_var(audit_cache);
if (COMPLAIN_MODE(profile))
return complain_error(error);
return error;
} else {
- aa_put_profile(ent->profile);
- ent->profile = aa_get_profile(profile);
- cap_raise(ent->caps, cap);
+ put_cred(ent->ad_subj_cred);
+ ent->ad_subj_cred = get_cred(ad->subj_cred);
+ ent->ktime_ns_expiration[cap] = ktime_get_ns() + AUDIT_CACHE_TIMEOUT_NS;
}
put_cpu_var(audit_cache);
- return aa_audit(type, profile, sa, audit_cb);
+ return aa_audit(type, profile, ad, audit_cb);
}
/**
@@ -107,17 +114,41 @@ static int audit_caps(struct common_audit_data *sa, struct aa_profile *profile,
* @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
- * @sa: audit data (MAY BE NULL indicating no auditing)
+ * @ad: audit data (NOT NULL)
*
* Returns: 0 if allowed else -EPERM
*/
static int profile_capable(struct aa_profile *profile, int cap,
- unsigned int opts, struct common_audit_data *sa)
+ unsigned int opts, struct apparmor_audit_data *ad)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
+ aa_state_t state;
int error;
- if (cap_raised(profile->caps.allow, cap) &&
- !cap_raised(profile->caps.denied, cap))
+ state = RULE_MEDIATES(rules, ad->class);
+ if (state) {
+ struct aa_perms perms = { };
+ u32 request;
+
+ /* caps broken into 256 x 32 bit permission chunks */
+ state = aa_dfa_next(rules->policy->dfa, state, cap >> 5);
+ request = 1 << (cap & 0x1f);
+ perms = *aa_lookup_perms(rules->policy, state);
+ aa_apply_modes_to_perms(profile, &perms);
+
+ if (opts & CAP_OPT_NOAUDIT) {
+ if (perms.complain & request)
+ ad->info = "optional: no audit";
+ else
+ ad = NULL;
+ }
+ return aa_check_perms(profile, &perms, request, ad,
+ audit_cb);
+ }
+
+ /* fallback to old caps mediation that doesn't support conditionals */
+ if (cap_raised(rules->caps.allow, cap) &&
+ !cap_raised(rules->caps.denied, cap))
error = 0;
else
error = -EPERM;
@@ -128,14 +159,15 @@ static int profile_capable(struct aa_profile *profile, int cap,
/* audit the cap request in complain mode but note that it
* should be optional.
*/
- aad(sa)->info = "optional: no audit";
+ ad->info = "optional: no audit";
}
- return audit_caps(sa, profile, cap, error);
+ return audit_caps(ad, profile, cap, error);
}
/**
* aa_capable - test permission to use capability
+ * @subj_cred: cred we are testing capability against
* @label: label being tested for capability (NOT NULL)
* @cap: capability to be tested
* @opts: CAP_OPT_NOAUDIT bit determines whether audit record is generated
@@ -144,15 +176,48 @@ static int profile_capable(struct aa_profile *profile, int cap,
*
* Returns: 0 on success, or else an error code.
*/
-int aa_capable(struct aa_label *label, int cap, unsigned int opts)
+int aa_capable(const struct cred *subj_cred, struct aa_label *label,
+ int cap, unsigned int opts)
{
struct aa_profile *profile;
int error = 0;
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_CAP, OP_CAPABLE);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_CAP, AA_CLASS_CAP, OP_CAPABLE);
- sa.u.cap = cap;
+ ad.subj_cred = subj_cred;
+ ad.common.u.cap = cap;
error = fn_for_each_confined(label, profile,
- profile_capable(profile, cap, opts, &sa));
+ profile_capable(profile, cap, opts, &ad));
return error;
}
+
+kernel_cap_t aa_profile_capget(struct aa_profile *profile)
+{
+ struct aa_ruleset *rules = profile->label.rules[0];
+ aa_state_t state;
+
+ state = RULE_MEDIATES(rules, AA_CLASS_CAP);
+ if (state) {
+ kernel_cap_t caps = CAP_EMPTY_SET;
+ int i;
+
+ /* caps broken into up to 256, 32 bit permission chunks */
+ for (i = 0; i < (CAP_LAST_CAP >> 5); i++) {
+ struct aa_perms perms = { };
+ aa_state_t tmp;
+
+ tmp = aa_dfa_next(rules->policy->dfa, state, i);
+ perms = *aa_lookup_perms(rules->policy, tmp);
+ aa_apply_modes_to_perms(profile, &perms);
+ caps.val |= ((u64)(perms.allow)) << (i * 5);
+ caps.val |= ((u64)(perms.complain)) << (i * 5);
+ }
+ return caps;
+ }
+
+ /* fallback to old caps */
+ if (COMPLAIN_MODE(profile))
+ return CAP_FULL_SET;
+
+ return rules->caps.allow;
+}
diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c
index b498ed302461..d8a7bde94d79 100644
--- a/security/apparmor/crypto.c
+++ b/security/apparmor/crypto.c
@@ -11,113 +11,51 @@
* it should be.
*/
-#include <crypto/hash.h>
+#include <crypto/sha2.h>
#include "include/apparmor.h"
#include "include/crypto.h"
-static unsigned int apparmor_hash_size;
-
-static struct crypto_shash *apparmor_tfm;
-
unsigned int aa_hash_size(void)
{
- return apparmor_hash_size;
+ return SHA256_DIGEST_SIZE;
}
char *aa_calc_hash(void *data, size_t len)
{
- SHASH_DESC_ON_STACK(desc, apparmor_tfm);
- char *hash = NULL;
- int error = -ENOMEM;
-
- if (!apparmor_tfm)
- return NULL;
+ char *hash;
- hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
+ hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
if (!hash)
- goto fail;
-
- desc->tfm = apparmor_tfm;
-
- error = crypto_shash_init(desc);
- if (error)
- goto fail;
- error = crypto_shash_update(desc, (u8 *) data, len);
- if (error)
- goto fail;
- error = crypto_shash_final(desc, hash);
- if (error)
- goto fail;
+ return ERR_PTR(-ENOMEM);
+ sha256(data, len, hash);
return hash;
-
-fail:
- kfree(hash);
-
- return ERR_PTR(error);
}
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len)
{
- SHASH_DESC_ON_STACK(desc, apparmor_tfm);
- int error = -ENOMEM;
+ struct sha256_ctx sctx;
__le32 le32_version = cpu_to_le32(version);
if (!aa_g_hash_policy)
return 0;
- if (!apparmor_tfm)
- return 0;
-
- profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
+ profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);
if (!profile->hash)
- goto fail;
-
- desc->tfm = apparmor_tfm;
-
- error = crypto_shash_init(desc);
- if (error)
- goto fail;
- error = crypto_shash_update(desc, (u8 *) &le32_version, 4);
- if (error)
- goto fail;
- error = crypto_shash_update(desc, (u8 *) start, len);
- if (error)
- goto fail;
- error = crypto_shash_final(desc, profile->hash);
- if (error)
- goto fail;
+ return -ENOMEM;
+ sha256_init(&sctx);
+ sha256_update(&sctx, (u8 *)&le32_version, 4);
+ sha256_update(&sctx, (u8 *)start, len);
+ sha256_final(&sctx, profile->hash);
return 0;
-
-fail:
- kfree(profile->hash);
- profile->hash = NULL;
-
- return error;
}
-static int __init init_profile_hash(void)
+int __init init_profile_hash(void)
{
- struct crypto_shash *tfm;
-
- if (!apparmor_initialized)
- return 0;
-
- tfm = crypto_alloc_shash("sha1", 0, 0);
- if (IS_ERR(tfm)) {
- int error = PTR_ERR(tfm);
- AA_ERROR("failed to setup profile sha1 hashing: %d\n", error);
- return error;
- }
- apparmor_tfm = tfm;
- apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm);
-
- aa_info_message("AppArmor sha1 policy hashing enabled");
-
+ if (apparmor_initialized)
+ aa_info_message("AppArmor sha256 policy hashing enabled");
return 0;
}
-
-late_initcall(init_profile_hash);
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 583680f6cd81..267da82afb14 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -9,12 +9,10 @@
*/
#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>
@@ -30,26 +28,15 @@
#include "include/policy.h"
#include "include/policy_ns.h"
-/**
- * aa_free_domain_entries - free entries in a domain table
- * @domain: the domain table to free (MAYBE NULL)
- */
-void aa_free_domain_entries(struct aa_domain *domain)
-{
- int i;
- if (domain) {
- if (!domain->table)
- return;
-
- for (i = 0; i < domain->size; i++)
- kfree_sensitive(domain->table[i]);
- kfree_sensitive(domain->table);
- domain->table = NULL;
- }
-}
+static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments";
+static const char * const CONFLICTING_ATTACH_STR_IX =
+ "conflicting profile attachments - ix fallback";
+static const char * const CONFLICTING_ATTACH_STR_UX =
+ "conflicting profile attachments - ux fallback";
/**
* may_change_ptraced_domain - check if can change profile on ptraced task
+ * @to_cred: cred of task changing domain
* @to_label: profile to change to (NOT NULL)
* @info: message if there is an error
*
@@ -58,28 +45,34 @@ void aa_free_domain_entries(struct aa_domain *domain)
*
* Returns: %0 or error if change not allowed
*/
-static int may_change_ptraced_domain(struct aa_label *to_label,
+static int may_change_ptraced_domain(const struct cred *to_cred,
+ struct aa_label *to_label,
const char **info)
{
struct task_struct *tracer;
struct aa_label *tracerl = NULL;
+ const struct cred *tracer_cred = NULL;
+
int error = 0;
rcu_read_lock();
tracer = ptrace_parent(current);
- if (tracer)
+ if (tracer) {
/* released below */
tracerl = aa_get_task_label(tracer);
-
+ tracer_cred = get_task_cred(tracer);
+ }
/* not ptraced */
if (!tracer || unconfined(tracerl))
goto out;
- error = aa_may_ptrace(tracerl, to_label, PTRACE_MODE_ATTACH);
+ error = aa_may_ptrace(tracer_cred, tracerl, to_cred, to_label,
+ PTRACE_MODE_ATTACH);
out:
rcu_read_unlock();
aa_put_label(tracerl);
+ put_cred(tracer_cred);
if (error)
*info = "ptrace prevents transition";
@@ -89,30 +82,31 @@ out:
/**** TODO: dedup to aa_label_match - needs perm and dfa, merging
* specifically this is an exact copy of aa_label_match except
* aa_compute_perms is replaced with aa_compute_fperms
- * and policy.dfa with file.dfa
+ * and policy->dfa with file->dfa
****/
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline unsigned int match_component(struct aa_profile *profile,
- struct aa_profile *tp,
- bool stack, unsigned int state)
+static inline aa_state_t match_component(struct aa_profile *profile,
+ struct aa_profile *tp,
+ bool stack, aa_state_t state)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
const char *ns_name;
if (stack)
- state = aa_dfa_match(profile->file.dfa, state, "&");
+ state = aa_dfa_match(rules->file->dfa, state, "&");
if (profile->ns == tp->ns)
- return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
+ return aa_dfa_match(rules->file->dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
- state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
- state = aa_dfa_match(profile->file.dfa, state, ns_name);
- state = aa_dfa_match_len(profile->file.dfa, state, ":", 1);
- return aa_dfa_match(profile->file.dfa, state, tp->base.hname);
+ state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);
+ state = aa_dfa_match(rules->file->dfa, state, ns_name);
+ state = aa_dfa_match_len(rules->file->dfa, state, ":", 1);
+ return aa_dfa_match(rules->file->dfa, state, tp->base.hname);
}
/**
@@ -120,7 +114,7 @@ static inline unsigned int match_component(struct aa_profile *profile,
* @profile: profile to find perms for
* @label: label to check access permissions for
* @stack: whether this is a stacking request
- * @start: state to start match in
+ * @state: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: perms struct to set
@@ -133,9 +127,10 @@ static inline unsigned int match_component(struct aa_profile *profile,
*/
static int label_compound_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
- unsigned int state, bool subns, u32 request,
+ aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_profile *tp;
struct label_it i;
struct path_cond cond = { };
@@ -158,12 +153,13 @@ next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
- state = aa_dfa_match(profile->file.dfa, state, "//&");
+ state = aa_dfa_match(rules->file->dfa, state, "//&");
state = match_component(profile, tp, false, state);
if (!state)
goto fail;
}
- *perms = aa_compute_fperms(profile->file.dfa, state, &cond);
+ *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
@@ -193,14 +189,15 @@ fail:
*/
static int label_components_match(struct aa_profile *profile,
struct aa_label *label, bool stack,
- unsigned int start, bool subns, u32 request,
+ aa_state_t start, bool subns, u32 request,
struct aa_perms *perms)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
struct path_cond cond = { };
- unsigned int state = 0;
+ aa_state_t state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
@@ -216,7 +213,8 @@ static int label_components_match(struct aa_profile *profile,
return 0;
next:
- tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
+ tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
@@ -225,7 +223,8 @@ next:
state = match_component(profile, tp, stack, start);
if (!state)
goto fail;
- tmp = aa_compute_fperms(profile->file.dfa, state, &cond);
+ tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state,
+ &cond));
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
@@ -253,7 +252,7 @@ fail:
* Returns: the state the match finished in, may be the none matching state
*/
static int label_match(struct aa_profile *profile, struct aa_label *label,
- bool stack, unsigned int state, bool subns, u32 request,
+ bool stack, aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
int error;
@@ -278,6 +277,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label,
* @stack: whether this is a stacking request
* @request: requested perms
* @start: state to start matching in
+ * @perms: Returns computed perms (NOT NULL)
*
*
* Returns: permission set
@@ -287,7 +287,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label,
*/
static int change_profile_perms(struct aa_profile *profile,
struct aa_label *target, bool stack,
- u32 request, unsigned int start,
+ u32 request, aa_state_t start,
struct aa_perms *perms)
{
if (profile_unconfined(profile)) {
@@ -309,45 +309,46 @@ static int change_profile_perms(struct aa_profile *profile,
* 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, unsigned int state)
+ struct aa_profile *profile, aa_state_t state)
{
int i;
- ssize_t size;
struct dentry *d;
char *value = NULL;
- int value_size = 0, ret = profile->xattr_count;
+ struct aa_attachment *attach = &profile->attach;
+ int size, value_size = 0, ret = attach->xattr_count;
- if (!bprm || !profile->xattr_count)
+ if (!bprm || !attach->xattr_count)
return 0;
might_sleep();
/* transition from exec match to xattr set */
- state = aa_dfa_outofband_transition(profile->xmatch, state);
+ state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
d = bprm->file->f_path.dentry;
- for (i = 0; i < profile->xattr_count; i++) {
- size = vfs_getxattr_alloc(&init_user_ns, d, profile->xattrs[i],
+ 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) {
- u32 perm;
+ 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(profile->xmatch, state);
+ state = aa_dfa_null_transition(attach->xmatch->dfa,
+ state);
/* Check xattr value */
- state = aa_dfa_match_len(profile->xmatch, state, value,
- size);
- perm = dfa_user_allow(profile->xmatch, state);
- if (!(perm & MAY_EXEC)) {
+ 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(profile->xmatch, state);
+ state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
if (size < 0) {
/*
* No xattr match, so verify if transition to
@@ -370,11 +371,11 @@ out:
/**
* find_attach - do attachment search for unconfined processes
- * @bprm - binprm structure of transitioning task
+ * @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)
+ * @head: profile list to walk (NOT NULL)
+ * @name: to match against (NOT NULL)
+ * @info: info message if there was an error (NOT NULL)
*
* Do a linear search on the profiles in the list. There is a matching
* preference where an exact match is preferred over a name which uses
@@ -399,6 +400,8 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm,
rcu_read_lock();
restart:
list_for_each_entry_rcu(profile, head, base.list) {
+ struct aa_attachment *attach = &profile->attach;
+
if (profile->label.flags & FLAG_NULL &&
&profile->label == ns_unconfined(profile->ns))
continue;
@@ -414,21 +417,23 @@ restart:
* as another profile, signal a conflict and refuse to
* match.
*/
- if (profile->xmatch) {
- unsigned int state, count;
- u32 perm;
-
- state = aa_dfa_leftmatch(profile->xmatch, DFA_START,
- name, &count);
- perm = dfa_user_allow(profile->xmatch, state);
+ if (attach->xmatch->dfa) {
+ unsigned int count;
+ aa_state_t state;
+ struct aa_perms *perms;
+
+ state = aa_dfa_leftmatch(attach->xmatch->dfa,
+ attach->xmatch->start[AA_CLASS_XMATCH],
+ name, &count);
+ perms = aa_lookup_perms(attach->xmatch, state);
/* any accepting state means a valid match. */
- if (perm & MAY_EXEC) {
+ if (perms->allow & MAY_EXEC) {
int ret = 0;
if (count < candidate_len)
continue;
- if (bprm && profile->xattr_count) {
+ if (bprm && attach->xattr_count) {
long rev = READ_ONCE(ns->revision);
if (!aa_get_profile_not0(profile))
@@ -467,7 +472,7 @@ restart:
* xattrs, or a longer match
*/
candidate = profile;
- candidate_len = profile->xmatch_len;
+ candidate_len = max(count, attach->xmatch_len);
candidate_xattrs = ret;
conflict = false;
}
@@ -483,7 +488,7 @@ restart:
if (!candidate || conflict) {
if (conflict)
- *info = "conflicting profile attachments";
+ *info = CONFLICTING_ATTACH_STR;
rcu_read_unlock();
return NULL;
}
@@ -507,13 +512,16 @@ static const char *next_name(int xtype, const char *name)
* @name: returns: name tested to find label (NOT NULL)
*
* Returns: refcounted label, or NULL on failure (MAYBE NULL)
+ * @name will always be set with the last name tried
*/
struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
const char **name)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_label *label = NULL;
u32 xtype = xindex & AA_X_TYPE_MASK;
int index = xindex & AA_X_INDEX_MASK;
+ const char *next;
AA_BUG(!name);
@@ -521,25 +529,27 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/* TODO: move lookup parsing to unpack time so this is a straight
* index into the resultant label
*/
- for (*name = profile->file.trans.table[index]; !label && *name;
- *name = next_name(xtype, *name)) {
+ for (next = rules->file->trans.table[index]; next;
+ next = next_name(xtype, next)) {
+ const char *lookup = (*next == '&') ? next + 1 : next;
+ *name = next;
if (xindex & AA_X_CHILD) {
- struct aa_profile *new_profile;
- /* release by caller */
- new_profile = aa_find_child(profile, *name);
- if (new_profile)
- label = &new_profile->label;
+ /* TODO: switich to parse to get stack of child */
+ struct aa_profile *new = aa_find_child(profile, lookup);
+
+ if (new)
+ /* release by caller */
+ return &new->label;
continue;
}
- label = aa_label_parse(&profile->label, *name, GFP_KERNEL,
+ label = aa_label_parse(&profile->label, lookup, GFP_KERNEL,
true, false);
- if (IS_ERR(label))
- label = NULL;
+ if (!IS_ERR_OR_NULL(label))
+ /* release by caller */
+ return label;
}
- /* released by caller */
-
- return label;
+ return NULL;
}
/**
@@ -549,6 +559,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
+ * @info: info message if there was an error (NOT NULL)
*
* find label for a transition index
*
@@ -561,9 +572,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
const char **info)
{
struct aa_label *new = NULL;
+ struct aa_label *stack = NULL;
struct aa_ns *ns = profile->ns;
u32 xtype = xindex & AA_X_TYPE_MASK;
- const char *stack = NULL;
+ /* Used for info checks during fallback handling */
+ const char *old_info = NULL;
switch (xtype) {
case AA_X_NONE:
@@ -572,13 +585,14 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
break;
case AA_X_TABLE:
/* TODO: fix when perm mapping done at unload */
- stack = profile->file.trans.table[xindex & AA_X_INDEX_MASK];
- if (*stack != '&') {
- /* released by caller */
- new = x_table_lookup(profile, xindex, lookupname);
- stack = NULL;
+ /* released by caller
+ * if null for both stack and direct want to try fallback
+ */
+ new = x_table_lookup(profile, xindex, lookupname);
+ if (!new || **lookupname != '&')
break;
- }
+ stack = new;
+ new = NULL;
fallthrough; /* to X_NAME */
case AA_X_NAME:
if (xindex & AA_X_CHILD)
@@ -593,17 +607,38 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
break;
}
+ /* fallback transition check */
if (!new) {
if (xindex & AA_X_INHERIT) {
/* (p|c|n)ix - don't change profile but do
* use the newest version
*/
- *info = "ix fallback";
+ if (*info == CONFLICTING_ATTACH_STR) {
+ *info = CONFLICTING_ATTACH_STR_IX;
+ } else {
+ old_info = *info;
+ *info = "ix fallback";
+ }
/* no profile && no error */
new = aa_get_newest_label(&profile->label);
} else if (xindex & AA_X_UNCONFINED) {
new = aa_get_newest_label(ns_unconfined(profile->ns));
- *info = "ux fallback";
+ if (*info == CONFLICTING_ATTACH_STR) {
+ *info = CONFLICTING_ATTACH_STR_UX;
+ } else {
+ old_info = *info;
+ *info = "ux fallback";
+ }
+ }
+ /* We set old_info on the code paths above where overwriting
+ * could have happened, so now check if info was set by
+ * find_attach as well (i.e. whether we actually overwrote)
+ * and warn accordingly.
+ */
+ if (old_info && old_info != CONFLICTING_ATTACH_STR) {
+ pr_warn_ratelimited(
+ "AppArmor: find_attach (from profile %s) audit info \"%s\" dropped",
+ profile->base.hname, old_info);
}
}
@@ -611,24 +646,27 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
/* base the stack on post domain transition */
struct aa_label *base = new;
- new = aa_label_parse(base, stack, GFP_KERNEL, true, false);
- if (IS_ERR(new))
- new = NULL;
+ new = aa_label_merge(base, stack, GFP_KERNEL);
+ /* null on error */
aa_put_label(base);
}
+ aa_put_label(stack);
/* released by caller */
return new;
}
-static struct aa_label *profile_transition(struct aa_profile *profile,
+static struct aa_label *profile_transition(const struct cred *subj_cred,
+ struct aa_profile *profile,
const struct linux_binprm *bprm,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_label *new = NULL;
+ struct aa_profile *new_profile = NULL;
const char *info = NULL, *name = NULL, *target = NULL;
- unsigned int state = profile->file.start;
+ aa_state_t state = rules->file->start[AA_CLASS_FILE];
struct aa_perms perms = {};
bool nonewprivs = false;
int error = 0;
@@ -642,7 +680,7 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
- AA_DEBUG("name lookup ix on error");
+ AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error");
error = 0;
new = aa_get_newest_label(&profile->label);
}
@@ -653,35 +691,66 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
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
+ */
+ if (info) {
+ /* Because perms is never used again after this audit
+ * we don't need to care about clobbering it
+ */
+ perms.audit |= MAY_EXEC;
+ perms.allow |= MAY_EXEC;
+ /* Don't cause error if auditing fails */
+ (void) aa_audit_file(subj_cred, profile, &perms,
+ OP_EXEC, MAY_EXEC, name, target, new, cond->uid,
+ info, error);
+ }
if (new) {
- AA_DEBUG("unconfined attached to new label");
+ AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label");
return new;
}
- AA_DEBUG("unconfined exec no attachment");
+ AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment");
return aa_get_newest_label(&profile->label);
}
/* find exec permissions for name */
- state = aa_str_perms(profile->file.dfa, state, name, cond, &perms);
+ state = aa_str_perms(rules->file, state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
new = x_to_label(profile, bprm, name, perms.xindex, &target,
&info);
if (new && new->proxy == profile->label.proxy && info) {
+ /* Force audit on conflicting attachment fallback
+ * Because perms is never used again after this audit
+ * we don't need to care about clobbering it
+ */
+ if (info == CONFLICTING_ATTACH_STR_IX
+ || info == CONFLICTING_ATTACH_STR_UX)
+ perms.audit |= MAY_EXEC;
/* hack ix fallback - improve how this is detected */
goto audit;
} else if (!new) {
- error = -EACCES;
+ if (info) {
+ pr_warn_ratelimited(
+ "AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition",
+ __func__, profile->base.hname, info);
+ }
info = "profile transition not found";
- /* remove MAY_EXEC to audit as failure */
+ /* remove MAY_EXEC to audit as failure or complaint */
perms.allow &= ~MAY_EXEC;
+ if (COMPLAIN_MODE(profile)) {
+ /* create null profile instead of failing */
+ goto create_learning_profile;
+ }
+ error = -EACCES;
}
} else if (COMPLAIN_MODE(profile)) {
+create_learning_profile:
/* no exec permission - learning mode */
- struct aa_profile *new_profile = NULL;
-
- new_profile = aa_new_null_profile(profile, false, name,
- GFP_KERNEL);
+ new_profile = aa_new_learning_profile(profile, false, name,
+ GFP_KERNEL);
if (!new_profile) {
error = -ENOMEM;
info = "could not create null profile";
@@ -700,8 +769,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
if (!(perms.xindex & AA_X_UNSAFE)) {
if (DEBUG_ON) {
- dbg_printk("apparmor: scrubbing environment variables"
- " for %s profile=", name);
+ dbg_printk("apparmor: setting AT_SECURE for %s profile=",
+ name);
aa_label_printk(new, GFP_KERNEL);
dbg_printk("\n");
}
@@ -709,7 +778,8 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
}
audit:
- aa_audit_file(profile, &perms, OP_EXEC, MAY_EXEC, name, target, new,
+ aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name,
+ target, new,
cond->uid, info, error);
if (!new || nonewprivs) {
aa_put_label(new);
@@ -719,12 +789,14 @@ audit:
return new;
}
-static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
+static int profile_onexec(const struct cred *subj_cred,
+ struct aa_profile *profile, struct aa_label *onexec,
bool stack, const struct linux_binprm *bprm,
char *buffer, struct path_cond *cond,
bool *secure_exec)
{
- unsigned int state = profile->file.start;
+ struct aa_ruleset *rules = profile->label.rules[0];
+ aa_state_t state = rules->file->start[AA_CLASS_FILE];
struct aa_perms perms = {};
const char *xname = NULL, *info = "change_profile onexec";
int error = -EACCES;
@@ -738,7 +810,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
/* change_profile on exec already granted */
/*
* NOTE: Domain transitions from unconfined are allowed
- * even when no_new_privs is set because this aways results
+ * even when no_new_privs is set because this always results
* in a further reduction of permissions.
*/
return 0;
@@ -749,7 +821,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
- AA_DEBUG("name lookup ix on error");
+ AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error");
error = 0;
}
xname = bprm->filename;
@@ -757,7 +829,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
}
/* find exec permissions for name */
- state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
+ state = aa_str_perms(rules->file, state, xname, cond, &perms);
if (!(perms.allow & AA_MAY_ONEXEC)) {
info = "no change_onexec valid for executable";
goto audit;
@@ -766,7 +838,7 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
* onexec permission is linked to exec with a standard pairing
* exec\0change_profile
*/
- state = aa_dfa_null_transition(profile->file.dfa, state);
+ state = aa_dfa_null_transition(rules->file->dfa, state);
error = change_profile_perms(profile, onexec, stack, AA_MAY_ONEXEC,
state, &perms);
if (error) {
@@ -776,8 +848,8 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
if (!(perms.xindex & AA_X_UNSAFE)) {
if (DEBUG_ON) {
- dbg_printk("apparmor: scrubbing environment "
- "variables for %s label=", xname);
+ dbg_printk("apparmor: setting AT_SECURE for %s label=",
+ xname);
aa_label_printk(onexec, GFP_KERNEL);
dbg_printk("\n");
}
@@ -785,13 +857,15 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
}
audit:
- return aa_audit_file(profile, &perms, OP_EXEC, AA_MAY_ONEXEC, xname,
+ return aa_audit_file(subj_cred, profile, &perms, OP_EXEC,
+ AA_MAY_ONEXEC, xname,
NULL, onexec, cond->uid, info, error);
}
/* ensure none ns domain transitions are correctly applied with onexec */
-static struct aa_label *handle_onexec(struct aa_label *label,
+static struct aa_label *handle_onexec(const struct cred *subj_cred,
+ struct aa_label *label,
struct aa_label *onexec, bool stack,
const struct linux_binprm *bprm,
char *buffer, struct path_cond *cond,
@@ -806,37 +880,26 @@ static struct aa_label *handle_onexec(struct aa_label *label,
AA_BUG(!bprm);
AA_BUG(!buffer);
- if (!stack) {
- error = fn_for_each_in_ns(label, profile,
- profile_onexec(profile, onexec, stack,
- bprm, buffer, cond, unsafe));
- if (error)
- return ERR_PTR(error);
- new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
- aa_get_newest_label(onexec),
- profile_transition(profile, bprm, buffer,
- cond, unsafe));
-
- } else {
- /* TODO: determine how much we want to loosen this */
- error = fn_for_each_in_ns(label, profile,
- profile_onexec(profile, onexec, stack, bprm,
- buffer, cond, unsafe));
- if (error)
- return ERR_PTR(error);
- new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
- aa_label_merge(&profile->label, onexec,
- GFP_KERNEL),
- profile_transition(profile, bprm, buffer,
- cond, unsafe));
- }
+ /* TODO: determine how much we want to loosen this */
+ error = fn_for_each_in_ns(label, profile,
+ profile_onexec(subj_cred, profile, onexec, stack,
+ bprm, buffer, cond, unsafe));
+ if (error)
+ return ERR_PTR(error);
+ new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
+ stack ? aa_label_merge(&profile->label, onexec,
+ GFP_KERNEL)
+ : aa_get_newest_label(onexec),
+ profile_transition(subj_cred, profile, bprm,
+ buffer, cond, unsafe));
if (new)
return new;
/* TODO: get rid of GLOBAL_ROOT_UID */
error = fn_for_each_in_ns(label, profile,
- aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC,
+ aa_audit_file(subj_cred, profile, &nullperms,
+ OP_CHANGE_ONEXEC,
AA_MAY_ONEXEC, bprm->filename, NULL,
onexec, GLOBAL_ROOT_UID,
"failed to build target label", -ENOMEM));
@@ -855,18 +918,20 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
{
struct aa_task_ctx *ctx;
struct aa_label *label, *new = NULL;
+ const struct cred *subj_cred;
struct aa_profile *profile;
char *buffer = NULL;
const char *info = NULL;
int error = 0;
bool unsafe = false;
- kuid_t i_uid = i_uid_into_mnt(file_mnt_user_ns(bprm->file),
- file_inode(bprm->file));
+ vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(bprm->file),
+ file_inode(bprm->file));
struct path_cond cond = {
- i_uid,
+ 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);
@@ -893,11 +958,12 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
/* Test for onexec first as onexec override other x transitions. */
if (ctx->onexec)
- new = handle_onexec(label, ctx->onexec, ctx->token,
+ new = handle_onexec(subj_cred, label, ctx->onexec, ctx->token,
bprm, buffer, &cond, &unsafe);
else
new = fn_label_build(label, profile, GFP_KERNEL,
- profile_transition(profile, bprm, buffer,
+ profile_transition(subj_cred, profile, bprm,
+ buffer,
&cond, &unsafe));
AA_BUG(!new);
@@ -915,7 +981,7 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
*
* NOTE: Domain transitions from unconfined and to stacked
* subsets are allowed even when no_new_privs is set because this
- * aways results in a further reduction of permissions.
+ * always results in a further reduction of permissions.
*/
if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
!unconfined(label) &&
@@ -932,15 +998,15 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
if (bprm->unsafe & (LSM_UNSAFE_PTRACE)) {
/* TODO: test needs to be profile of label to new */
- error = may_change_ptraced_domain(new, &info);
+ error = may_change_ptraced_domain(bprm->cred, new, &info);
if (error)
goto audit;
}
if (unsafe) {
if (DEBUG_ON) {
- dbg_printk("scrubbing environment variables for %s "
- "label=", bprm->filename);
+ dbg_printk("setting AT_SECURE for %s label=",
+ bprm->filename);
aa_label_printk(new, GFP_KERNEL);
dbg_printk("\n");
}
@@ -950,8 +1016,8 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm)
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);
+ dbg_printk("apparmor: clearing unsafe personality bits. %s label=",
+ bprm->filename);
aa_label_printk(new, GFP_KERNEL);
dbg_printk("\n");
}
@@ -969,9 +1035,10 @@ done:
audit:
error = fn_for_each(label, profile,
- aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC,
+ aa_audit_file(current_cred(), profile, &nullperms,
+ OP_EXEC, MAY_EXEC,
bprm->filename, NULL, new,
- i_uid, info, error));
+ vfsuid_into_kuid(vfsuid), info, error));
aa_put_label(new);
goto done;
}
@@ -985,7 +1052,8 @@ audit:
*
* Returns: label for hat transition OR ERR_PTR. Does NOT return NULL
*/
-static struct aa_label *build_change_hat(struct aa_profile *profile,
+static struct aa_label *build_change_hat(const struct cred *subj_cred,
+ struct aa_profile *profile,
const char *name, bool sibling)
{
struct aa_profile *root, *hat = NULL;
@@ -1006,8 +1074,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile,
if (!hat) {
error = -ENOENT;
if (COMPLAIN_MODE(profile)) {
- hat = aa_new_null_profile(profile, true, name,
- GFP_KERNEL);
+ hat = aa_new_learning_profile(profile, true, name,
+ GFP_KERNEL);
if (!hat) {
info = "failed null profile create";
error = -ENOMEM;
@@ -1017,7 +1085,8 @@ static struct aa_label *build_change_hat(struct aa_profile *profile,
aa_put_profile(root);
audit:
- aa_audit_file(profile, &nullperms, OP_CHANGE_HAT, AA_MAY_CHANGEHAT,
+ aa_audit_file(subj_cred, profile, &nullperms, OP_CHANGE_HAT,
+ AA_MAY_CHANGEHAT,
name, hat ? hat->base.hname : NULL,
hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
error);
@@ -1033,7 +1102,8 @@ audit:
*
* Returns: label for hat transition or ERR_PTR. Does not return NULL
*/
-static struct aa_label *change_hat(struct aa_label *label, const char *hats[],
+static struct aa_label *change_hat(const struct cred *subj_cred,
+ struct aa_label *label, const char *hats[],
int count, int flags)
{
struct aa_profile *profile, *root, *hat = NULL;
@@ -1109,7 +1179,8 @@ fail:
*/
/* TODO: get rid of GLOBAL_ROOT_UID */
if (count > 1 || COMPLAIN_MODE(profile)) {
- aa_audit_file(profile, &nullperms, OP_CHANGE_HAT,
+ aa_audit_file(subj_cred, profile, &nullperms,
+ OP_CHANGE_HAT,
AA_MAY_CHANGEHAT, name, NULL, NULL,
GLOBAL_ROOT_UID, info, error);
}
@@ -1118,7 +1189,8 @@ fail:
build:
new = fn_label_build_in_ns(label, profile, GFP_KERNEL,
- build_change_hat(profile, name, sibling),
+ build_change_hat(subj_cred, profile, name,
+ sibling),
aa_get_label(&profile->label));
if (!new) {
info = "label build failed";
@@ -1148,7 +1220,7 @@ build:
*/
int aa_change_hat(const char *hats[], int count, u64 token, int flags)
{
- const struct cred *cred;
+ 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;
@@ -1157,8 +1229,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
int error = 0;
/* released below */
- cred = get_current_cred();
- label = aa_get_newest_cred_label(cred);
+ subj_cred = get_current_cred();
+ label = aa_get_newest_cred_label(subj_cred);
previous = aa_get_newest_label(ctx->previous);
/*
@@ -1171,14 +1243,28 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp)
ctx->nnp = aa_get_label(label);
+ /* return -EPERM when unconfined doesn't have children to avoid
+ * changing the traditional error code for unconfined.
+ */
if (unconfined(label)) {
- info = "unconfined can not change_hat";
- error = -EPERM;
- goto fail;
+ struct label_it i;
+ bool empty = true;
+
+ rcu_read_lock();
+ label_for_each_in_ns(i, labels_ns(label), label, profile) {
+ empty &= list_empty(&profile->base.profiles);
+ }
+ rcu_read_unlock();
+
+ if (empty) {
+ info = "unconfined can not change_hat";
+ error = -EPERM;
+ goto fail;
+ }
}
if (count) {
- new = change_hat(label, hats, count, flags);
+ new = change_hat(subj_cred, label, hats, count, flags);
AA_BUG(!new);
if (IS_ERR(new)) {
error = PTR_ERR(new);
@@ -1187,7 +1273,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
goto out;
}
- error = may_change_ptraced_domain(new, &info);
+ /* target cred is the same as current except new label */
+ error = may_change_ptraced_domain(subj_cred, new, &info);
if (error)
goto fail;
@@ -1198,7 +1285,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
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("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}
@@ -1219,7 +1307,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags)
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("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}
@@ -1240,7 +1329,7 @@ out:
aa_put_label(new);
aa_put_label(previous);
aa_put_label(label);
- put_cred(cred);
+ put_cred(subj_cred);
return error;
@@ -1250,7 +1339,7 @@ kill:
fail:
fn_for_each_in_ns(label, profile,
- aa_audit_file(profile, &perms, OP_CHANGE_HAT,
+ aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
AA_MAY_CHANGEHAT, NULL, NULL, target,
GLOBAL_ROOT_UID, info, error));
@@ -1259,28 +1348,33 @@ fail:
static int change_profile_perms_wrapper(const char *op, const char *name,
+ const struct cred *subj_cred,
struct aa_profile *profile,
struct aa_label *target, bool stack,
u32 request, struct aa_perms *perms)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
const char *info = NULL;
int error = 0;
if (!error)
error = change_profile_perms(profile, target, stack, request,
- profile->file.start, perms);
+ rules->file->start[AA_CLASS_FILE],
+ perms);
if (error)
- error = aa_audit_file(profile, perms, op, request, name,
+ error = aa_audit_file(subj_cred, profile, perms, op, request,
+ name,
NULL, target, GLOBAL_ROOT_UID, info,
error);
return error;
}
+static const char *stack_msg = "change_profile unprivileged unconfined converted to stacking";
+
/**
* aa_change_profile - perform a one-way profile transition
* @fqname: name of profile may include namespace (NOT NULL)
- * @onexec: whether this transition is to take place immediately or at exec
* @flags: flags affecting change behavior
*
* Change to new profile @name. Unlike with hats, there is no way
@@ -1300,6 +1394,7 @@ int aa_change_profile(const char *fqname, int flags)
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;
@@ -1318,7 +1413,7 @@ int aa_change_profile(const char *fqname, int flags)
if (!fqname || !*fqname) {
aa_put_label(label);
- AA_DEBUG("no profile name");
+ AA_DEBUG(DEBUG_DOMAIN, "no profile name");
return -EINVAL;
}
@@ -1336,6 +1431,28 @@ int aa_change_profile(const char *fqname, int flags)
op = OP_CHANGE_PROFILE;
}
+ /* This should move to a per profile test. Requires pushing build
+ * into callback
+ */
+ if (!stack && unconfined(label) &&
+ label == &labels_ns(label)->unconfined->label &&
+ aa_unprivileged_unconfined_restricted &&
+ /* TODO: refactor so this check is a fn */
+ cap_capable(current_cred(), &init_user_ns, CAP_MAC_OVERRIDE,
+ CAP_OPT_NOAUDIT)) {
+ /* regardless of the request in this case apparmor
+ * stacks against unconfined so admin set policy can't be
+ * by-passed
+ */
+ stack = true;
+ perms.audit = request;
+ (void) fn_for_each_in_ns(label, profile,
+ aa_audit_file(subj_cred, profile, &perms, op,
+ request, auditname, NULL, target,
+ GLOBAL_ROOT_UID, stack_msg, 0));
+ perms.audit = 0;
+ }
+
if (*fqname == '&') {
stack = true;
/* don't have label_parse() do stacking */
@@ -1356,8 +1473,8 @@ int aa_change_profile(const char *fqname, int flags)
!COMPLAIN_MODE(labels_profile(label)))
goto audit;
/* released below */
- tprofile = aa_new_null_profile(labels_profile(label), false,
- fqname, GFP_KERNEL);
+ tprofile = aa_new_learning_profile(labels_profile(label), false,
+ fqname, GFP_KERNEL);
if (!tprofile) {
info = "failed null profile create";
error = -ENOMEM;
@@ -1377,6 +1494,7 @@ int aa_change_profile(const char *fqname, int flags)
*/
error = fn_for_each_in_ns(label, profile,
change_profile_perms_wrapper(op, auditname,
+ subj_cred,
profile, target, stack,
request, &perms));
if (error)
@@ -1387,7 +1505,7 @@ int aa_change_profile(const char *fqname, int flags)
check:
/* check if tracing task is allowed to trace target domain */
- error = may_change_ptraced_domain(target, &info);
+ error = may_change_ptraced_domain(subj_cred, target, &info);
if (error && !fn_for_each_in_ns(label, profile,
COMPLAIN_MODE(profile)))
goto audit;
@@ -1414,7 +1532,8 @@ check:
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("no_new_privs - change_hat denied");
+ AA_DEBUG(DEBUG_DOMAIN,
+ "no_new_privs - change_hat denied");
error = -EPERM;
goto out;
}
@@ -1442,12 +1561,13 @@ check:
}
/* full transition will be built in exec path */
- error = aa_set_current_onexec(target, stack);
+ aa_set_current_onexec(target, stack);
}
audit:
error = fn_for_each_in_ns(label, profile,
- aa_audit_file(profile, &perms, op, request, auditname,
+ aa_audit_file(subj_cred,
+ profile, &perms, op, request, auditname,
NULL, new ? new : target,
GLOBAL_ROOT_UID, info, error));
@@ -1455,6 +1575,7 @@ out:
aa_put_label(new);
aa_put_label(target);
aa_put_label(label);
+ put_cred(subj_cred);
return error;
}
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
index e1b7e93602e4..c75820402878 100644
--- a/security/apparmor/file.c
+++ b/security/apparmor/file.c
@@ -14,6 +14,7 @@
#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"
@@ -44,38 +45,40 @@ static u32 map_mask_to_chr_mask(u32 mask)
static void file_audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
- kuid_t fsuid = current_fsuid();
+ struct apparmor_audit_data *ad = aad(sa);
+ kuid_t fsuid = ad->subj_cred ? ad->subj_cred->fsuid : current_fsuid();
char str[10];
- if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
+ if (ad->request & AA_AUDIT_FILE_MASK) {
aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
- map_mask_to_chr_mask(aad(sa)->request));
+ map_mask_to_chr_mask(ad->request));
audit_log_format(ab, " requested_mask=\"%s\"", str);
}
- if (aad(sa)->denied & AA_AUDIT_FILE_MASK) {
+ if (ad->denied & AA_AUDIT_FILE_MASK) {
aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
- map_mask_to_chr_mask(aad(sa)->denied));
+ map_mask_to_chr_mask(ad->denied));
audit_log_format(ab, " denied_mask=\"%s\"", str);
}
- if (aad(sa)->request & AA_AUDIT_FILE_MASK) {
+ if (ad->request & AA_AUDIT_FILE_MASK) {
audit_log_format(ab, " fsuid=%d",
from_kuid(&init_user_ns, fsuid));
audit_log_format(ab, " ouid=%d",
- from_kuid(&init_user_ns, aad(sa)->fs.ouid));
+ from_kuid(&init_user_ns, ad->fs.ouid));
}
- if (aad(sa)->peer) {
+ if (ad->peer) {
audit_log_format(ab, " target=");
- aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAG_VIEW_SUBNS, GFP_KERNEL);
- } else if (aad(sa)->fs.target) {
+ } else if (ad->fs.target) {
audit_log_format(ab, " target=");
- audit_log_untrustedstring(ab, aad(sa)->fs.target);
+ audit_log_untrustedstring(ab, ad->fs.target);
}
}
/**
* aa_audit_file - handle the auditing of file operations
+ * @subj_cred: cred of the subject
* @profile: the profile being enforced (NOT NULL)
* @perms: the permissions computed for the request (NOT NULL)
* @op: operation being mediated
@@ -89,72 +92,61 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: %0 or error on failure
*/
-int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
+int aa_audit_file(const struct cred *subj_cred,
+ struct aa_profile *profile, struct aa_perms *perms,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel,
kuid_t ouid, const char *info, int error)
{
int type = AUDIT_APPARMOR_AUTO;
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op);
-
- sa.u.tsk = NULL;
- aad(&sa)->request = request;
- aad(&sa)->name = name;
- aad(&sa)->fs.target = target;
- aad(&sa)->peer = tlabel;
- aad(&sa)->fs.ouid = ouid;
- aad(&sa)->info = info;
- aad(&sa)->error = error;
- sa.u.tsk = NULL;
-
- if (likely(!aad(&sa)->error)) {
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
+
+ ad.subj_cred = subj_cred;
+ ad.request = request;
+ ad.name = name;
+ ad.fs.target = target;
+ ad.peer = tlabel;
+ ad.fs.ouid = ouid;
+ ad.info = info;
+ ad.error = error;
+ ad.common.u.tsk = NULL;
+
+ if (likely(!ad.error)) {
u32 mask = perms->audit;
if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
mask = 0xffff;
/* mask off perms that are not being force audited */
- aad(&sa)->request &= mask;
+ ad.request &= mask;
- if (likely(!aad(&sa)->request))
+ if (likely(!ad.request))
return 0;
type = AUDIT_APPARMOR_AUDIT;
} else {
/* only report permissions that were denied */
- aad(&sa)->request = aad(&sa)->request & ~perms->allow;
- AA_BUG(!aad(&sa)->request);
+ ad.request = ad.request & ~perms->allow;
+ AA_BUG(!ad.request);
- if (aad(&sa)->request & perms->kill)
+ if (ad.request & perms->kill)
type = AUDIT_APPARMOR_KILL;
/* quiet known rejects, assumes quiet and kill do not overlap */
- if ((aad(&sa)->request & perms->quiet) &&
+ if ((ad.request & perms->quiet) &&
AUDIT_MODE(profile) != AUDIT_NOQUIET &&
AUDIT_MODE(profile) != AUDIT_ALL)
- aad(&sa)->request &= ~perms->quiet;
+ ad.request &= ~perms->quiet;
- if (!aad(&sa)->request)
- return aad(&sa)->error;
+ if (!ad.request)
+ return ad.error;
}
- aad(&sa)->denied = aad(&sa)->request & ~perms->allow;
- return aa_audit(type, profile, &sa, file_audit_cb);
+ ad.denied = ad.request & ~perms->allow;
+ return aa_audit(type, profile, &ad, file_audit_cb);
}
-/**
- * is_deleted - test if a file has been completely unlinked
- * @dentry: dentry of file to test for deletion (NOT NULL)
- *
- * Returns: true if deleted else false
- */
-static inline bool is_deleted(struct dentry *dentry)
-{
- if (d_unlinked(dentry) && d_backing_inode(dentry)->i_nlink == 0)
- return true;
- return false;
-}
-
-static int path_name(const char *op, struct aa_label *label,
+static int path_name(const char *op, const struct cred *subj_cred,
+ struct aa_label *label,
const struct path *path, int flags, char *buffer,
const char **name, struct path_cond *cond, u32 request)
{
@@ -166,7 +158,8 @@ static int path_name(const char *op, struct aa_label *label,
labels_profile(label)->disconnected);
if (error) {
fn_for_each_confined(label, profile,
- aa_audit_file(profile, &nullperms, op, request, *name,
+ aa_audit_file(subj_cred,
+ profile, &nullperms, op, request, *name,
NULL, NULL, cond->uid, info, error));
return error;
}
@@ -174,114 +167,80 @@ static int path_name(const char *op, struct aa_label *label,
return 0;
}
+struct aa_perms default_perms = {};
/**
- * map_old_perms - map old file perms layout to the new layout
- * @old: permission set in old mapping
- *
- * Returns: new permission mapping
- */
-static u32 map_old_perms(u32 old)
-{
- u32 new = old & 0xf;
- if (old & MAY_READ)
- new |= AA_MAY_GETATTR | AA_MAY_OPEN;
- if (old & MAY_WRITE)
- new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
- AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
- if (old & 0x10)
- new |= AA_MAY_LINK;
- /* the old mapping lock and link_subset flags where overlaid
- * and use was determined by part of a pair that they were in
- */
- if (old & 0x20)
- new |= AA_MAY_LOCK | AA_LINK_SUBSET;
- if (old & 0x40) /* AA_EXEC_MMAP */
- new |= AA_EXEC_MMAP;
-
- return new;
-}
-
-/**
- * aa_compute_fperms - convert dfa compressed perms to internal perms
- * @dfa: dfa to compute perms for (NOT NULL)
+ * aa_lookup_condperms - convert dfa compressed perms to internal perms
+ * @subj_uid: uid to use for subject owner test
+ * @rules: the aa_policydb to lookup perms for (NOT NULL)
* @state: state in dfa
* @cond: conditions to consider (NOT NULL)
*
- * TODO: convert from dfa + state to permission entry, do computation conversion
- * at load time.
+ * TODO: convert from dfa + state to permission entry
*
- * Returns: computed permission set
+ * Returns: a pointer to a file permission set
*/
-struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
- struct path_cond *cond)
+struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules,
+ aa_state_t state, struct path_cond *cond)
{
- /* 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
- */
- struct aa_perms perms = { };
+ unsigned int index = ACCEPT_TABLE(rules->dfa)[state];
- if (uid_eq(current_fsuid(), cond->uid)) {
- perms.allow = map_old_perms(dfa_user_allow(dfa, state));
- perms.audit = map_old_perms(dfa_user_audit(dfa, state));
- perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
- perms.xindex = dfa_user_xindex(dfa, state);
- } else {
- perms.allow = map_old_perms(dfa_other_allow(dfa, state));
- perms.audit = map_old_perms(dfa_other_audit(dfa, state));
- perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
- perms.xindex = dfa_other_xindex(dfa, state);
- }
- perms.allow |= AA_MAY_GETATTR;
+ if (!(rules->perms))
+ return &default_perms;
- /* change_profile wasn't determined by ownership in old mapping */
- if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
- perms.allow |= AA_MAY_CHANGE_PROFILE;
- if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
- perms.allow |= AA_MAY_ONEXEC;
+ if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) {
+ if (uid_eq(subj_uid, cond->uid))
+ return &(rules->perms[index]);
+ return &(rules->perms[index + 1]);
+ }
- return perms;
+ return &(rules->perms[index]);
}
/**
* aa_str_perms - find permission that match @name
- * @dfa: to match against (MAYBE NULL)
- * @state: state to start matching in
+ * @file_rules: the aa_policydb to match against (NOT NULL)
+ * @start: state to start matching in
* @name: string to match against dfa (NOT NULL)
* @cond: conditions to consider for permission set computation (NOT NULL)
* @perms: Returns - the permissions found when matching @name
*
* Returns: the final state in @dfa when beginning @start and walking @name
*/
-unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
- const char *name, struct path_cond *cond,
- struct aa_perms *perms)
+aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
+ const char *name, struct path_cond *cond,
+ struct aa_perms *perms)
{
- unsigned int state;
- state = aa_dfa_match(dfa, start, name);
- *perms = aa_compute_fperms(dfa, state, cond);
+ aa_state_t state;
+ state = aa_dfa_match(file_rules->dfa, start, name);
+ *perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state,
+ cond));
return state;
}
-int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
+int __aa_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_profile *profile, const char *name,
u32 request, struct path_cond *cond, int flags,
struct aa_perms *perms)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
int e = 0;
- if (profile_unconfined(profile))
+ if (profile_unconfined(profile) ||
+ ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules)))
return 0;
- aa_str_perms(profile->file.dfa, profile->file.start, name, cond, perms);
+ aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
+ name, cond, perms);
if (request & ~perms->allow)
e = -EACCES;
- return aa_audit_file(profile, perms, op, request, name, NULL, NULL,
+ return aa_audit_file(subj_cred,
+ profile, perms, op, request, name, NULL, NULL,
cond->uid, NULL, e);
}
-static int profile_path_perm(const char *op, struct aa_profile *profile,
+static int profile_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_profile *profile,
const struct path *path, char *buffer, u32 request,
struct path_cond *cond, int flags,
struct aa_perms *perms)
@@ -292,18 +251,19 @@ static int profile_path_perm(const char *op, struct aa_profile *profile,
if (profile_unconfined(profile))
return 0;
- error = path_name(op, &profile->label, path,
+ error = path_name(op, subj_cred, &profile->label, path,
flags | profile->path_flags, buffer, &name, cond,
request);
if (error)
return error;
- return __aa_path_perm(op, profile, name, request, cond, flags,
- perms);
+ return __aa_path_perm(op, subj_cred, profile, name, request, cond,
+ flags, perms);
}
/**
* aa_path_perm - do permissions check & audit for @path
* @op: operation being checked
+ * @subj_cred: subject cred
* @label: profile being enforced (NOT NULL)
* @path: path to check permissions of (NOT NULL)
* @flags: any additional path flags beyond what the profile specifies
@@ -312,7 +272,8 @@ static int profile_path_perm(const char *op, struct aa_profile *profile,
*
* Returns: %0 else error if access denied or other error
*/
-int aa_path_perm(const char *op, struct aa_label *label,
+int aa_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_label *label,
const struct path *path, int flags, u32 request,
struct path_cond *cond)
{
@@ -327,8 +288,8 @@ int aa_path_perm(const char *op, struct aa_label *label,
if (!buffer)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
- profile_path_perm(op, profile, path, buffer, request,
- cond, flags, &perms));
+ profile_path_perm(op, subj_cred, profile, path, buffer,
+ request, cond, flags, &perms));
aa_put_buffer(buffer);
@@ -355,40 +316,45 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return true;
}
-static int profile_path_link(struct aa_profile *profile,
+static int profile_path_link(const struct cred *subj_cred,
+ struct aa_profile *profile,
const struct path *link, char *buffer,
const struct path *target, char *buffer2,
struct path_cond *cond)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
const char *lname, *tname = NULL;
struct aa_perms lperms = {}, perms;
const char *info = NULL;
u32 request = AA_MAY_LINK;
- unsigned int state;
+ aa_state_t state;
int error;
- error = path_name(OP_LINK, &profile->label, link, profile->path_flags,
+ error = path_name(OP_LINK, subj_cred, &profile->label, link,
+ profile->path_flags,
buffer, &lname, cond, AA_MAY_LINK);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
- error = path_name(OP_LINK, &profile->label, target, profile->path_flags,
+ error = path_name(OP_LINK, subj_cred, &profile->label, target,
+ profile->path_flags,
buffer2, &tname, cond, AA_MAY_LINK);
if (error)
goto audit;
error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */
- state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
+ state = aa_str_perms(rules->file,
+ rules->file->start[AA_CLASS_FILE], lname,
cond, &lperms);
if (!(lperms.allow & AA_MAY_LINK))
goto audit;
/* test to see if target can be paired with link */
- state = aa_dfa_null_transition(profile->file.dfa, state);
- aa_str_perms(profile->file.dfa, state, tname, cond, &perms);
+ state = aa_dfa_null_transition(rules->file->dfa, state);
+ aa_str_perms(rules->file, state, tname, cond, &perms);
/* force audit/quiet masks for link are stored in the second entry
* in the link pair.
@@ -410,8 +376,8 @@ static int profile_path_link(struct aa_profile *profile,
/* Do link perm subset test requiring allowed permission on link are
* a subset of the allowed permissions on target.
*/
- aa_str_perms(profile->file.dfa, profile->file.start, tname, cond,
- &perms);
+ aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
+ tname, cond, &perms);
/* AA_MAY_LINK is not considered in the subset test */
request = lperms.allow & ~AA_MAY_LINK;
@@ -432,12 +398,14 @@ done_tests:
error = 0;
audit:
- return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
+ return aa_audit_file(subj_cred,
+ profile, &lperms, OP_LINK, request, lname, tname,
NULL, cond->uid, info, error);
}
/**
* aa_path_link - Handle hard link permission check
+ * @subj_cred: subject cred
* @label: the label being enforced (NOT NULL)
* @old_dentry: the target dentry (NOT NULL)
* @new_dir: directory the new link will be created in (NOT NULL)
@@ -454,14 +422,17 @@ audit:
*
* Returns: %0 if allowed else error
*/
-int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
+int aa_path_link(const struct cred *subj_cred,
+ struct aa_label *label, struct dentry *old_dentry,
const struct path *new_dir, struct dentry *new_dentry)
{
struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
+ struct inode *inode = d_backing_inode(old_dentry);
+ vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode);
struct path_cond cond = {
- d_backing_inode(old_dentry)->i_uid,
- d_backing_inode(old_dentry)->i_mode
+ .uid = vfsuid_into_kuid(vfsuid),
+ .mode = inode->i_mode,
};
char *buffer = NULL, *buffer2 = NULL;
struct aa_profile *profile;
@@ -475,8 +446,8 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
goto out;
error = fn_for_each_confined(label, profile,
- profile_path_link(profile, &link, buffer, &target,
- buffer2, &cond));
+ profile_path_link(subj_cred, profile, &link, buffer,
+ &target, buffer2, &cond));
out:
aa_put_buffer(buffer);
aa_put_buffer(buffer2);
@@ -504,14 +475,17 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
spin_unlock(&fctx->lock);
}
-static int __file_path_perm(const char *op, struct aa_label *label,
+static int __file_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_label *label,
struct aa_label *flabel, struct file *file,
u32 request, u32 denied, 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 = i_uid_into_mnt(file_mnt_user_ns(file), file_inode(file)),
+ .uid = vfsuid_into_kuid(vfsuid),
.mode = file_inode(file)->i_mode
};
char *buffer;
@@ -529,7 +503,8 @@ static int __file_path_perm(const char *op, struct aa_label *label,
/* check every profile in task label not in current cache */
error = fn_for_each_not_in_set(flabel, label, profile,
- profile_path_perm(op, profile, &file->f_path, buffer,
+ profile_path_perm(op, subj_cred, profile,
+ &file->f_path, buffer,
request, &cond, flags, &perms));
if (denied && !error) {
/*
@@ -542,12 +517,14 @@ static int __file_path_perm(const char *op, struct aa_label *label,
*/
if (label == flabel)
error = fn_for_each(label, profile,
- profile_path_perm(op, profile, &file->f_path,
+ profile_path_perm(op, subj_cred,
+ profile, &file->f_path,
buffer, request, &cond, flags,
&perms));
else
error = fn_for_each_not_in_set(label, flabel, profile,
- profile_path_perm(op, profile, &file->f_path,
+ profile_path_perm(op, subj_cred,
+ profile, &file->f_path,
buffer, request, &cond, flags,
&perms));
}
@@ -559,25 +536,24 @@ static int __file_path_perm(const char *op, struct aa_label *label,
return error;
}
-static int __file_sock_perm(const char *op, struct aa_label *label,
+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)
{
- struct socket *sock = (struct socket *) file->private_data;
int error;
- AA_BUG(!sock);
-
/* revalidation due to label out of date. No revocation at this time */
if (!denied && aa_label_is_subset(flabel, label))
return 0;
/* TODO: improve to skip profiles cached in flabel */
- error = aa_sock_file_perm(label, op, request, sock);
+ 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(flabel, op, request, sock));
+ last_error(error, aa_sock_file_perm(subj_cred, flabel, op,
+ request, file));
}
if (!error)
update_file_ctx(file_ctx(file), label, request);
@@ -585,9 +561,39 @@ static int __file_sock_perm(const char *op, struct aa_label *label,
return error;
}
+/* for now separate fn to indicate semantics of the check */
+static bool __file_is_delegated(struct aa_label *obj_label)
+{
+ return unconfined(obj_label);
+}
+
+static bool __unix_needs_revalidation(struct file *file, struct aa_label *label,
+ u32 request)
+{
+ struct socket *sock = (struct socket *) file->private_data;
+
+ lockdep_assert_in_rcu_read_lock();
+
+ if (!S_ISSOCK(file_inode(file)->i_mode))
+ return false;
+ if (request & NET_PEER_MASK)
+ return false;
+ if (sock->sk->sk_family == PF_UNIX) {
+ struct aa_sk_ctx *ctx = aa_sock(sock->sk);
+
+ if (rcu_access_pointer(ctx->peer) !=
+ rcu_access_pointer(ctx->peer_lastupdate))
+ return true;
+ return !__aa_subj_label_is_cached(rcu_dereference(ctx->label),
+ label);
+ }
+ return false;
+}
+
/**
* aa_file_perm - do permission revalidation check & audit for @file
* @op: operation being checked
+ * @subj_cred: subject cred
* @label: label being enforced (NOT NULL)
* @file: file to revalidate access permissions on (NOT NULL)
* @request: requested permissions
@@ -595,7 +601,8 @@ static int __file_sock_perm(const char *op, struct aa_label *label,
*
* Returns: %0 if access allowed else error
*/
-int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
+int aa_file_perm(const char *op, const struct cred *subj_cred,
+ struct aa_label *label, struct file *file,
u32 request, bool in_atomic)
{
struct aa_file_ctx *fctx;
@@ -620,30 +627,31 @@ int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
* delegation from unconfined tasks
*/
denied = request & ~fctx->allow;
- if (unconfined(label) || unconfined(flabel) ||
- (!denied && aa_label_is_subset(flabel, label))) {
+ if (unconfined(label) || __file_is_delegated(flabel) ||
+ __unix_needs_revalidation(file, label, request) ||
+ (!denied && __aa_subj_label_is_cached(label, flabel))) {
rcu_read_unlock();
goto done;
}
+ /* slow path - revalidate access */
flabel = aa_get_newest_label(flabel);
rcu_read_unlock();
- /* TODO: label cross check */
- if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry))
- error = __file_path_perm(op, label, flabel, file, request,
- denied, in_atomic);
+ if (path_mediated_fs(file->f_path.dentry))
+ error = __file_path_perm(op, subj_cred, label, flabel, file,
+ request, denied, in_atomic);
else if (S_ISSOCK(file_inode(file)->i_mode))
- error = __file_sock_perm(op, label, flabel, file, request,
- denied);
+ error = __file_sock_perm(op, subj_cred, label, flabel, file,
+ request, denied);
aa_put_label(flabel);
done:
return error;
}
-static void revalidate_tty(struct aa_label *label)
+static void revalidate_tty(const struct cred *subj_cred, struct aa_label *label)
{
struct tty_struct *tty;
int drop_tty = 0;
@@ -661,8 +669,8 @@ static void revalidate_tty(struct aa_label *label)
struct tty_file_private, list);
file = file_priv->file;
- if (aa_file_perm(OP_INHERIT, label, file, MAY_READ | MAY_WRITE,
- IN_ATOMIC))
+ if (aa_file_perm(OP_INHERIT, subj_cred, label, file,
+ MAY_READ | MAY_WRITE, IN_ATOMIC))
drop_tty = 1;
}
spin_unlock(&tty->files_lock);
@@ -672,12 +680,17 @@ static void revalidate_tty(struct aa_label *label)
no_tty();
}
+struct cred_label {
+ const struct cred *cred;
+ struct aa_label *label;
+};
+
static int match_file(const void *p, struct file *file, unsigned int fd)
{
- struct aa_label *label = (struct aa_label *)p;
+ struct cred_label *cl = (struct cred_label *)p;
- if (aa_file_perm(OP_INHERIT, label, file, aa_map_file_to_perms(file),
- IN_ATOMIC))
+ if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file,
+ aa_map_file_to_perms(file), IN_ATOMIC))
return fd + 1;
return 0;
}
@@ -687,13 +700,17 @@ static int match_file(const void *p, struct file *file, unsigned int fd)
void aa_inherit_files(const struct cred *cred, struct files_struct *files)
{
struct aa_label *label = aa_get_newest_cred_label(cred);
+ struct cred_label cl = {
+ .cred = cred,
+ .label = label,
+ };
struct file *devnull = NULL;
unsigned int n;
- revalidate_tty(label);
+ revalidate_tty(cred, label);
/* Revalidate access to inherited open files. */
- n = iterate_fd(files, 0, match_file, label);
+ n = iterate_fd(files, 0, match_file, &cl);
if (!n) /* none found? */
goto out;
@@ -703,7 +720,7 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files)
/* replace all the matching ones with this */
do {
replace_fd(n - 1, devnull, 0);
- } while ((n = iterate_fd(files, n, match_file, label)) != 0);
+ } while ((n = iterate_fd(files, n, match_file, &cl)) != 0);
if (devnull)
fput(devnull);
out:
diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h
new file mode 100644
index 000000000000..4a62e600d82b
--- /dev/null
+++ b/security/apparmor/include/af_unix.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor af_unix fine grained mediation
+ *
+ * Copyright 2023 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+#ifndef __AA_AF_UNIX_H
+
+#include <net/af_unix.h>
+
+#include "label.h"
+
+#define unix_addr(A) ((struct sockaddr_un *)(A))
+#define unix_addr_len(L) ((L) - sizeof(sa_family_t))
+#define unix_peer(sk) (unix_sk(sk)->peer)
+#define is_unix_addr_abstract_name(B) ((B)[0] == 0)
+#define is_unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0)
+#define is_unix_addr_fs(A, L) (!is_unix_addr_anon(A, L) && \
+ !is_unix_addr_abstract_name(unix_addr(A)->sun_path))
+
+#define is_unix_anonymous(U) (!unix_sk(U)->addr)
+#define is_unix_fs(U) (!is_unix_anonymous(U) && \
+ unix_sk(U)->addr->name->sun_path[0])
+#define is_unix_connected(S) ((S)->state == SS_CONNECTED)
+
+
+struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen);
+int aa_unix_peer_perm(const struct cred *subj_cred,
+ struct aa_label *label, const char *op, u32 request,
+ struct sock *sk, struct sock *peer_sk,
+ struct aa_label *peer_label);
+int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock);
+int aa_unix_create_perm(struct aa_label *label, int family, int type,
+ int protocol);
+int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address,
+ int addrlen);
+int aa_unix_listen_perm(struct socket *sock, int backlog);
+int aa_unix_accept_perm(struct socket *sock, struct socket *newsock);
+int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock,
+ struct msghdr *msg, int size);
+int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level,
+ int optname);
+int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label,
+ const char *op, u32 request, struct file *file);
+
+#endif /* __AA_AF_UNIX_H */
diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
index 1fbabdb565a8..cc6e3df1bc62 100644
--- a/security/apparmor/include/apparmor.h
+++ b/security/apparmor/include/apparmor.h
@@ -16,7 +16,7 @@
/*
* 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
@@ -26,20 +26,43 @@
#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_LAST AA_CLASS_LABEL
+#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;
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
+#define AA_MIN_CLEVEL zstd_min_clevel()
+#define AA_MAX_CLEVEL zstd_max_clevel()
+#define AA_DEFAULT_CLEVEL ZSTD_CLEVEL_DEFAULT
+#else
+#define AA_MIN_CLEVEL 0
+#define AA_MAX_CLEVEL 0
+#define AA_DEFAULT_CLEVEL 0
+#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
+
+
#endif /* __APPARMOR_H */
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
index 6e14f6cecdb9..dd580594dfb7 100644
--- a/security/apparmor/include/apparmorfs.h
+++ b/security/apparmor/include/apparmorfs.h
@@ -104,6 +104,8 @@ enum aafs_prof_type {
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
+int aa_create_aafs(void);
+
void __aa_bump_ns_revision(struct aa_ns *ns);
void __aafs_profile_rmdir(struct aa_profile *profile);
void __aafs_profile_migrate_dents(struct aa_profile *old,
@@ -114,7 +116,21 @@ int __aafs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
struct dentry *dent);
struct aa_loaddata;
+
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata);
int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata);
+#else
+static inline void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata)
+{
+ /* empty stub */
+}
+
+static inline int __aa_fs_create_rawdata(struct aa_ns *ns,
+ struct aa_loaddata *rawdata)
+{
+ return 0;
+}
+#endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */
#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
index 18519a4eb67e..1a71a94ea19c 100644
--- a/security/apparmor/include/audit.h
+++ b/security/apparmor/include/audit.h
@@ -103,12 +103,18 @@ enum audit_type {
#define OP_PROF_LOAD "profile_load"
#define OP_PROF_RM "profile_remove"
+#define OP_USERNS_CREATE "userns_create"
+
+#define OP_URING_OVERRIDE "uring_override"
+#define OP_URING_SQPOLL "uring_sqpoll"
struct apparmor_audit_data {
int error;
int type;
+ u16 class;
const char *op;
- struct aa_label *label;
+ const struct cred *subj_cred;
+ struct aa_label *subj_label;
const char *name;
const char *info;
u32 request;
@@ -132,9 +138,12 @@ struct apparmor_audit_data {
};
struct {
int type, protocol;
- struct sock *peer_sk;
void *addr;
int addrlen;
+ struct {
+ void *addr;
+ int addrlen;
+ } peer;
} net;
};
};
@@ -150,31 +159,39 @@ struct apparmor_audit_data {
const char *data;
unsigned long flags;
} mnt;
+ struct {
+ struct aa_label *target;
+ } uring;
};
+
+ struct common_audit_data common;
};
/* macros for dealing with apparmor_audit_data structure */
-#define aad(SA) ((SA)->apparmor_audit_data)
-#define DEFINE_AUDIT_DATA(NAME, T, X) \
+#define aad(SA) (container_of(SA, struct apparmor_audit_data, common))
+#define aad_of_va(VA) aad((struct common_audit_data *)(VA))
+
+#define DEFINE_AUDIT_DATA(NAME, T, C, X) \
/* TODO: cleanup audit init so we don't need _aad = {0,} */ \
- struct apparmor_audit_data NAME ## _aad = { .op = (X), }; \
- struct common_audit_data NAME = \
- { \
- .type = (T), \
- .u.tsk = NULL, \
- }; \
- NAME.apparmor_audit_data = &(NAME ## _aad)
-
-void aa_audit_msg(int type, struct common_audit_data *sa,
+ struct apparmor_audit_data NAME = { \
+ .class = (C), \
+ .op = (X), \
+ .common.type = (T), \
+ .common.u.tsk = NULL, \
+ .common.apparmor_audit_data = &NAME, \
+ };
+
+void aa_audit_msg(int type, struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *));
-int aa_audit(int type, struct aa_profile *profile, struct common_audit_data *sa,
+int aa_audit(int type, struct aa_profile *profile,
+ struct apparmor_audit_data *ad,
void (*cb) (struct audit_buffer *, void *));
-#define aa_audit_error(ERROR, SA, CB) \
+#define aa_audit_error(ERROR, AD, CB) \
({ \
- aad((SA))->error = (ERROR); \
- aa_audit_msg(AUDIT_APPARMOR_ERROR, (SA), (CB)); \
- aad((SA))->error; \
+ (AD)->error = (ERROR); \
+ aa_audit_msg(AUDIT_APPARMOR_ERROR, (AD), (CB)); \
+ (AD)->error; \
})
@@ -186,8 +203,8 @@ static inline int complain_error(int error)
}
void aa_audit_rule_free(void *vrule);
-int aa_audit_rule_init(u32 field, u32 op, char *rulestr, 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(u32 sid, u32 field, u32 op, void *vrule);
+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 d420e2d10b31..1ddcec2d1160 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -36,7 +36,9 @@ struct aa_caps {
extern struct aa_sfs_entry aa_sfs_entry_caps[];
-int aa_capable(struct aa_label *label, int cap, unsigned int opts);
+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/cred.h b/security/apparmor/include/cred.h
index 0b9ae4804ef7..b028e4c13b6f 100644
--- a/security/apparmor/include/cred.h
+++ b/security/apparmor/include/cred.h
@@ -63,17 +63,24 @@ static inline struct aa_label *aa_get_newest_cred_label(const struct cred *cred)
return aa_get_newest_label(aa_cred_raw_label(cred));
}
-/**
- * __aa_task_raw_label - retrieve another task's label
- * @task: task to query (NOT NULL)
- *
- * Returns: @task's label without incrementing its ref count
- *
- * If @task != current needs to be called in RCU safe critical section
- */
-static inline struct aa_label *__aa_task_raw_label(struct task_struct *task)
+static inline struct aa_label *aa_get_newest_cred_label_condref(const struct cred *cred,
+ bool *needput)
+{
+ 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)
{
- return aa_cred_raw_label(__task_cred(task));
+ if (unlikely(needput))
+ aa_put_label(l);
}
/**
@@ -107,10 +114,22 @@ static inline struct aa_label *aa_get_current_label(void)
return aa_get_label(l);
}
-#define __end_current_label_crit_section(X) end_current_label_crit_section(X)
+/**
+ * __end_current_label_crit_section - end crit section begun with __begin_...
+ * @label: label obtained from __begin_current_label_crit_section
+ * @needput: output: bool set by __begin_current_label_crit_section
+ *
+ * Returns: label to use for this crit section
+ */
+static inline void __end_current_label_crit_section(struct aa_label *label,
+ bool needput)
+{
+ if (unlikely(needput))
+ aa_put_label(label);
+}
/**
- * end_label_crit_section - put a reference found with begin_current_label..
+ * end_current_label_crit_section - put a reference found with begin_current_label..
* @label: label reference to put
*
* Should only be used with a reference obtained with
@@ -125,6 +144,7 @@ static inline void end_current_label_crit_section(struct aa_label *label)
/**
* __begin_current_label_crit_section - current's confining label
+ * @needput: store whether the label needs to be put when ending crit section
*
* Returns: up to date confining label or the ns unconfined label (NOT NULL)
*
@@ -135,13 +155,16 @@ static inline void end_current_label_crit_section(struct aa_label *label)
* critical section between __begin_current_label_crit_section() ..
* __end_current_label_crit_section()
*/
-static inline struct aa_label *__begin_current_label_crit_section(void)
+static inline struct aa_label *__begin_current_label_crit_section(bool *needput)
{
struct aa_label *label = aa_current_raw_label();
- if (label_is_stale(label))
- label = aa_get_newest_label(label);
+ if (label_is_stale(label)) {
+ *needput = true;
+ return aa_get_newest_label(label);
+ }
+ *needput = false;
return label;
}
@@ -177,10 +200,11 @@ static inline struct aa_ns *aa_get_current_ns(void)
{
struct aa_label *label;
struct aa_ns *ns;
+ bool needput;
- label = __begin_current_label_crit_section();
+ label = __begin_current_label_crit_section(&needput);
ns = aa_get_ns(labels_ns(label));
- __end_current_label_crit_section(label);
+ __end_current_label_crit_section(label, needput);
return ns;
}
diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h
index 636a04e20d91..f3ffd388cc58 100644
--- a/security/apparmor/include/crypto.h
+++ b/security/apparmor/include/crypto.h
@@ -13,6 +13,7 @@
#include "policy.h"
#ifdef CONFIG_SECURITY_APPARMOR_HASH
+int init_profile_hash(void);
unsigned int aa_hash_size(void);
char *aa_calc_hash(void *data, size_t len);
int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h
index d14928fe1c6f..77f9a0ed0f04 100644
--- a/security/apparmor/include/domain.h
+++ b/security/apparmor/include/domain.h
@@ -16,11 +16,6 @@
#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
@@ -32,7 +27,6 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm);
-void aa_free_domain_entries(struct aa_domain *domain);
int aa_change_hat(const char *hats[], int count, u64 token, int flags);
int aa_change_profile(const char *fqname, int flags);
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h
index 7517605a183d..ef60f99bc5ae 100644
--- a/security/apparmor/include/file.h
+++ b/security/apparmor/include/file.h
@@ -17,6 +17,7 @@
#include "match.h"
#include "perms.h"
+struct aa_policydb;
struct aa_profile;
struct path;
@@ -44,61 +45,23 @@ struct aa_file_ctx {
u32 allow;
};
-/**
- * aa_alloc_file_ctx - allocate file_ctx
- * @label: initial label of task creating the file
- * @gfp: gfp flags for allocation
- *
- * Returns: file_ctx or NULL on failure
- */
-static inline struct aa_file_ctx *aa_alloc_file_ctx(struct aa_label *label,
- gfp_t gfp)
-{
- struct aa_file_ctx *ctx;
-
- ctx = kzalloc(sizeof(struct aa_file_ctx), gfp);
- if (ctx) {
- spin_lock_init(&ctx->lock);
- rcu_assign_pointer(ctx->label, aa_get_label(label));
- }
- return ctx;
-}
-
-/**
- * aa_free_file_ctx - free a file_ctx
- * @ctx: file_ctx to free (MAYBE_NULL)
- */
-static inline void aa_free_file_ctx(struct aa_file_ctx *ctx)
-{
- if (ctx) {
- aa_put_label(rcu_access_pointer(ctx->label));
- kfree_sensitive(ctx);
- }
-}
-
-static inline struct aa_label *aa_get_file_label(struct aa_file_ctx *ctx)
-{
- return aa_get_label_rcu(&ctx->label);
-}
-
/*
* The xindex is broken into 3 parts
* - index - an index into either the exec name table or the variable table
* - exec type - which determines how the executable name and index are used
* - flags - which modify how the destination name is applied
*/
-#define AA_X_INDEX_MASK 0x03ff
+#define AA_X_INDEX_MASK AA_INDEX_MASK
-#define AA_X_TYPE_MASK 0x0c00
-#define AA_X_TYPE_SHIFT 10
-#define AA_X_NONE 0x0000
-#define AA_X_NAME 0x0400 /* use executable name px */
-#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
+#define AA_X_TYPE_MASK 0x0c000000
+#define AA_X_NONE AA_INDEX_NONE
+#define AA_X_NAME 0x04000000 /* use executable name px */
+#define AA_X_TABLE 0x08000000 /* use a specified name ->n# */
-#define AA_X_UNSAFE 0x1000
-#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
-#define AA_X_INHERIT 0x4000
-#define AA_X_UNCONFINED 0x8000
+#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 {
@@ -108,107 +71,40 @@ struct path_cond {
#define COMBINED_PERM_MASK(X) ((X).allow | (X).audit | (X).quiet | (X).kill)
-/* FIXME: split perms from dfa and match this to description
- * also add delegation info.
- */
-static inline u16 dfa_map_xindex(u16 mask)
-{
- u16 old_index = (mask >> 10) & 0xf;
- u16 index = 0;
-
- if (mask & 0x100)
- index |= AA_X_UNSAFE;
- if (mask & 0x200)
- index |= AA_X_INHERIT;
- if (mask & 0x80)
- index |= AA_X_UNCONFINED;
-
- if (old_index == 1) {
- index |= AA_X_UNCONFINED;
- } else if (old_index == 2) {
- index |= AA_X_NAME;
- } else if (old_index == 3) {
- index |= AA_X_NAME | AA_X_CHILD;
- } else if (old_index) {
- index |= AA_X_TABLE;
- index |= old_index - 4;
- }
-
- return index;
-}
-
-/*
- * map old dfa inline permissions to new format
- */
-#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
- ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
-#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
-#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
-#define dfa_user_xindex(dfa, state) \
- (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
-
-#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
- 0x7f) | \
- ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
-#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
-#define dfa_other_quiet(dfa, state) \
- ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
-#define dfa_other_xindex(dfa, state) \
- dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
-
-int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
+int aa_audit_file(const struct cred *cred,
+ struct aa_profile *profile, struct aa_perms *perms,
const char *op, u32 request, const char *name,
const char *target, struct aa_label *tlabel, kuid_t ouid,
const char *info, int error);
-/**
- * struct aa_file_rules - components used for file rule permissions
- * @dfa: dfa to match path names and conditionals against
- * @perms: permission table indexed by the matched state accept entry of @dfa
- * @trans: transition table for indexed by named x transitions
- *
- * File permission are determined by matching a path against @dfa and
- * then using the value of the accept entry for the matching state as
- * an index into @perms. If a named exec transition is required it is
- * looked up in the transition table.
- */
-struct aa_file_rules {
- unsigned int start;
- struct aa_dfa *dfa;
- /* struct perms perms; */
- struct aa_domain trans;
- /* TODO: add delegate table */
-};
-
-struct aa_perms aa_compute_fperms(struct aa_dfa *dfa, unsigned int state,
- struct path_cond *cond);
-unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
- const char *name, struct path_cond *cond,
- struct aa_perms *perms);
-
-int __aa_path_perm(const char *op, struct aa_profile *profile,
- const char *name, u32 request, struct path_cond *cond,
- int flags, struct aa_perms *perms);
-int aa_path_perm(const char *op, struct aa_label *label,
- const struct path *path, int flags, u32 request,
- struct path_cond *cond);
-
-int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
- const struct path *new_dir, struct dentry *new_dentry);
-
-int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
+struct aa_perms *aa_lookup_condperms(kuid_t subj_uid,
+ struct aa_policydb *file_rules,
+ aa_state_t state, struct path_cond *cond);
+aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
+ const char *name, struct path_cond *cond,
+ struct aa_perms *perms);
+
+int __aa_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_profile *profile, const char *name,
+ u32 request, struct path_cond *cond, int flags,
+ struct aa_perms *perms);
+int aa_path_perm(const char *op, const struct cred *subj_cred,
+ struct aa_label *label, const struct path *path,
+ int flags, u32 request, struct path_cond *cond);
+
+int aa_path_link(const struct cred *subj_cred, struct aa_label *label,
+ struct dentry *old_dentry, const struct path *new_dir,
+ struct dentry *new_dentry);
+
+int aa_file_perm(const char *op, const struct cred *subj_cred,
+ struct aa_label *label, struct file *file,
u32 request, bool in_atomic);
void aa_inherit_files(const struct cred *cred, struct files_struct *files);
-static inline void aa_free_file_rules(struct aa_file_rules *rules)
-{
- aa_put_dfa(rules->dfa);
- aa_free_domain_entries(&rules->trans);
-}
/**
- * aa_map_file_perms - map file flags to AppArmor permissions
+ * aa_map_file_to_perms - map file flags to AppArmor permissions
* @file: open file to map flags to AppArmor permissions
*
* Returns: apparmor permission set for the file
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
index 9cafd80f7731..323dd071afe9 100644
--- a/security/apparmor/include/ipc.h
+++ b/security/apparmor/include/ipc.h
@@ -13,24 +13,11 @@
#include <linux/sched.h>
-struct aa_profile;
+#define SIGUNKNOWN 0
+#define MAXMAPPED_SIG 35
-#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(struct aa_label *tracer, struct aa_label *tracee,
- u32 request);
-int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig);
+int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender,
+ const struct cred *target_cred, struct aa_label *target,
+ int sig);
#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h
index 9101c2c76d9e..c0812dbc1b5b 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -19,6 +19,7 @@
#include "lib.h"
struct aa_ns;
+struct aa_ruleset;
#define LOCAL_VEC_ENTRIES 8
#define DEFINE_VEC(T, V) \
@@ -92,6 +93,8 @@ enum label_flags {
FLAG_STALE = 0x800, /* replaced/removed */
FLAG_RENAMED = 0x1000, /* label has renaming in it */
FLAG_REVOKED = 0x2000, /* label has revocation in it */
+ FLAG_DEBUG1 = 0x4000,
+ FLAG_DEBUG2 = 0x8000,
/* These flags must correspond with PATH_flags */
/* TODO: add new path flags */
@@ -107,7 +110,7 @@ struct label_it {
int i, j;
};
-/* struct aa_label - lazy labeling struct
+/* struct aa_label_base - base info of label
* @count: ref count of active users
* @node: rbtree position
* @rcu: rcu callback struct
@@ -116,7 +119,10 @@ struct label_it {
* @flags: stale and other flags - values may change under label set lock
* @secid: secid that references this label
* @size: number of entries in @ent[]
- * @ent: set of profiles for label, actual size determined by @size
+ * @mediates: bitmask for label_mediates
+ * profile: label vec when embedded in a profile FLAG_PROFILE is set
+ * rules: variable length rules in a profile FLAG_PROFILE is set
+ * vec: vector of profiles comprising the compound label
*/
struct aa_label {
struct kref count;
@@ -127,7 +133,18 @@ struct aa_label {
long flags;
u32 secid;
int size;
- struct aa_profile *vec[];
+ u64 mediates;
+ union {
+ struct {
+ /* only used is the label is a profile, size of
+ * rules[] is determined by the profile
+ * profile[1] is poison or null as guard
+ */
+ struct aa_profile *profile[2];
+ DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules);
+ };
+ DECLARE_FLEX_ARRAY(struct aa_profile *, vec);
+ };
};
#define last_error(E, FN) \
@@ -158,32 +175,8 @@ int aa_label_next_confined(struct aa_label *l, int i);
#define label_for_each_cont(I, L, P) \
for (++((I).i); ((P) = (L)->vec[(I).i]); ++((I).i))
-#define next_comb(I, L1, L2) \
-do { \
- (I).j++; \
- if ((I).j >= (L2)->size) { \
- (I).i++; \
- (I).j = 0; \
- } \
-} while (0)
-/* for each combination of P1 in L1, and P2 in L2 */
-#define label_for_each_comb(I, L1, L2, P1, P2) \
-for ((I).i = (I).j = 0; \
- ((P1) = (L1)->vec[(I).i]) && ((P2) = (L2)->vec[(I).j]); \
- (I) = next_comb(I, L1, L2))
-
-#define fn_for_each_comb(L1, L2, P1, P2, FN) \
-({ \
- struct label_it i; \
- int __E = 0; \
- label_for_each_comb(i, (L1), (L2), (P1), (P2)) { \
- last_error(__E, (FN)); \
- } \
- __E; \
-})
-
/* for each profile that is enforcing confinement in a label */
#define label_for_each_confined(I, L, P) \
for ((I).i = aa_label_next_confined((L), 0); \
@@ -253,20 +246,17 @@ for ((I).i = (I).j = 0; \
#define fn_for_each_not_in_set(L1, L2, P, FN) \
fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set)
-#define LABEL_MEDIATES(L, C) \
-({ \
- struct aa_profile *profile; \
- struct label_it i; \
- int ret = 0; \
- label_for_each(i, (L), profile) { \
- if (PROFILE_MEDIATES(profile, (C))) { \
- ret = 1; \
- break; \
- } \
- } \
- ret; \
-})
+static inline bool label_mediates(struct aa_label *L, unsigned char C)
+{
+ return (L)->mediates & (((u64) 1) << (C));
+}
+static inline bool label_mediates_safe(struct aa_label *L, unsigned char C)
+{
+ if (C > AA_CLASS_LAST)
+ return false;
+ return label_mediates(L, C);
+}
void aa_labelset_destroy(struct aa_labelset *ls);
void aa_labelset_init(struct aa_labelset *ls);
@@ -289,8 +279,6 @@ bool aa_label_replace(struct aa_label *old, struct aa_label *new);
bool aa_label_make_newest(struct aa_labelset *ls, struct aa_label *old,
struct aa_label *new);
-struct aa_label *aa_label_find(struct aa_label *l);
-
struct aa_profile *aa_label_next_in_merge(struct label_it *I,
struct aa_label *a,
struct aa_label *b);
@@ -318,8 +306,6 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
struct aa_label *label, int flags, gfp_t gfp);
void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
gfp_t gfp);
-void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp);
-void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp);
void aa_label_printk(struct aa_label *label, gfp_t gfp);
struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str,
@@ -331,7 +317,7 @@ struct aa_label *aa_label_parse(struct aa_label *base, const char *str,
static inline const char *aa_label_strn_split(const char *str, int n)
{
const char *pos;
- unsigned int state;
+ aa_state_t state;
state = aa_dfa_matchn_until(stacksplitdfa, DFA_START, str, n, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
@@ -343,7 +329,7 @@ static inline const char *aa_label_strn_split(const char *str, int n)
static inline const char *aa_label_str_split(const char *str)
{
const char *pos;
- unsigned int state;
+ aa_state_t state;
state = aa_dfa_match_until(stacksplitdfa, DFA_START, str, &pos);
if (!ACCEPT_TABLE(stacksplitdfa)[state])
@@ -355,9 +341,10 @@ static inline const char *aa_label_str_split(const char *str)
struct aa_perms;
-int aa_label_match(struct aa_profile *profile, struct aa_label *label,
- unsigned int state, bool subns, u32 request,
- struct aa_perms *perms);
+struct aa_ruleset;
+int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
+ struct aa_label *label, aa_state_t state, bool subns,
+ u32 request, struct aa_perms *perms);
/**
@@ -442,6 +429,13 @@ static inline void aa_put_label(struct aa_label *l)
kref_put(&l->count, aa_label_kref);
}
+/* wrapper fn to indicate semantics of the check */
+static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label,
+ struct aa_label *obj_label)
+{
+ return aa_label_is_subset(obj_label, subj_label);
+}
+
struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp);
void aa_proxy_kref(struct kref *kref);
diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h
index e2e8df0c6f1c..444197075fd6 100644
--- a/security/apparmor/include/lib.h
+++ b/security/apparmor/include/lib.h
@@ -16,18 +16,37 @@
#include "match.h"
+extern struct aa_dfa *stacksplitdfa;
+
/*
- * DEBUG remains global (no per profile flag) since it is mostly used in sysctl
- * which is not related to profile accesses.
+ * split individual debug cases out in preparation for finer grained
+ * debug controls in the future.
*/
-
-#define DEBUG_ON (aa_g_debug)
#define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args)
-#define AA_DEBUG(fmt, args...) \
+
+#define DEBUG_NONE 0
+#define DEBUG_LABEL_ABS_ROOT 1
+#define DEBUG_LABEL 2
+#define DEBUG_DOMAIN 4
+#define DEBUG_POLICY 8
+#define DEBUG_INTERFACE 0x10
+
+#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */
+#define DEBUG_PARSE_ERROR (-1)
+
+#define DEBUG_ON (aa_g_debug != DEBUG_NONE)
+#define DEBUG_ABS_ROOT (aa_g_debug & DEBUG_LABEL_ABS_ROOT)
+
+#define AA_DEBUG(opt, fmt, args...) \
do { \
- if (DEBUG_ON) \
- pr_debug_ratelimited("AppArmor: " fmt, ##args); \
+ if (aa_g_debug & opt) \
+ pr_warn_ratelimited("%s: " fmt, __func__, ##args); \
} while (0)
+#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \
+do { \
+ if ((LAB)->flags & FLAG_DEBUG1) \
+ AA_DEBUG(X, fmt, args); \
+} while (0)
#define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X)
@@ -41,9 +60,16 @@
#define AA_BUG_FMT(X, fmt, args...) \
WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __func__, ##args)
#else
-#define AA_BUG_FMT(X, fmt, args...) no_printk(fmt, ##args)
+#define AA_BUG_FMT(X, fmt, args...) \
+ do { \
+ BUILD_BUG_ON_INVALID(X); \
+ no_printk(fmt, ##args); \
+ } while (0)
#endif
+int aa_parse_debug_params(const char *str);
+int aa_print_debug_params(char *buffer);
+
#define AA_ERROR(fmt, args...) \
pr_err_ratelimited("AppArmor: " fmt, ##args)
@@ -52,7 +78,6 @@ extern int apparmor_initialized;
/* fn's in lib */
const char *skipn_spaces(const char *str, size_t n);
-char *aa_split_fqname(char *args, char **ns_name);
const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
size_t *ns_len);
void aa_info_message(const char *str);
@@ -82,8 +107,8 @@ static inline bool aa_strneq(const char *str, const char *sub, int len)
* character which is not used in standard matching and is only
* used to separate pairs.
*/
-static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
- unsigned int start)
+static inline aa_state_t aa_dfa_null_transition(struct aa_dfa *dfa,
+ aa_state_t start)
{
/* the null transition only needs the string's null terminator byte */
return aa_dfa_next(dfa, start, 0);
@@ -94,6 +119,13 @@ 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;
@@ -139,7 +171,7 @@ struct aa_policy {
/**
* basename - find the last component of an hname
- * @name: hname to find the base profile name component of (NOT NULL)
+ * @hname: hname to find the base profile name component of (NOT NULL)
*
* Returns: the tail (base profile name) name component of an hname
*/
@@ -221,7 +253,7 @@ void aa_policy_destroy(struct aa_policy *policy);
*/
#define fn_label_build(L, P, GFP, FN) \
({ \
- __label__ __cleanup, __done; \
+ __label__ __do_cleanup, __done; \
struct aa_label *__new_; \
\
if ((L)->size > 1) { \
@@ -239,7 +271,7 @@ void aa_policy_destroy(struct aa_policy *policy);
__new_ = (FN); \
AA_BUG(!__new_); \
if (IS_ERR(__new_)) \
- goto __cleanup; \
+ goto __do_cleanup; \
__lvec[__j++] = __new_; \
} \
for (__j = __count = 0; __j < (L)->size; __j++) \
@@ -261,7 +293,7 @@ void aa_policy_destroy(struct aa_policy *policy);
vec_cleanup(profile, __pvec, __count); \
} else \
__new_ = NULL; \
-__cleanup: \
+__do_cleanup: \
vec_cleanup(label, __lvec, (L)->size); \
} else { \
(P) = labels_profile(L); \
@@ -269,7 +301,7 @@ __cleanup: \
} \
__done: \
if (!__new_) \
- AA_DEBUG("label build failed\n"); \
+ AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \
(__new_); \
})
diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
index 884489590588..1fbe82f5021b 100644
--- a/security/apparmor/include/match.h
+++ b/security/apparmor/include/match.h
@@ -17,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
@@ -87,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))
@@ -102,9 +104,6 @@ struct aa_dfa {
struct table_header *tables[YYTD_ID_TSIZE];
};
-extern struct aa_dfa *nulldfa;
-extern struct aa_dfa *stacksplitdfa;
-
#define byte_to_byte(X) (X)
#define UNPACK_ARRAY(TABLE, BLOB, LEN, TTYPE, BTYPE, NTOHX) \
@@ -122,42 +121,37 @@ static inline size_t table_size(size_t len, size_t el_size)
return ALIGN(sizeof(struct table_header) + len * el_size, 8);
}
-int aa_setup_dfa_engine(void);
-void aa_teardown_dfa_engine(void);
+#define aa_state_t unsigned int
struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags);
-unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
- const char *str, int len);
-unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
- const char *str);
-unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
- const char c);
-unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa,
- unsigned int state);
-unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
- const char *str, const char **retpos);
-unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
- const char *str, int n, const char **retpos);
+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);
-#define WB_HISTORY_SIZE 24
+/* This needs to be a power of 2 */
+#define WB_HISTORY_SIZE 32
struct match_workbuf {
- unsigned int count;
unsigned int pos;
unsigned int len;
- unsigned int size; /* power of 2, same as history size */
- unsigned int history[WB_HISTORY_SIZE];
+ aa_state_t history[WB_HISTORY_SIZE];
};
#define DEFINE_MATCH_WB(N) \
struct match_workbuf N = { \
- .count = 0, \
.pos = 0, \
.len = 0, \
}
-unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
- const char *str, unsigned int *count);
+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
diff --git a/security/apparmor/include/mount.h b/security/apparmor/include/mount.h
index a710683b2496..46834f828179 100644
--- a/security/apparmor/include/mount.h
+++ b/security/apparmor/include/mount.h
@@ -25,26 +25,36 @@
#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
-int aa_remount(struct aa_label *label, const struct path *path,
+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(struct aa_label *label, const struct path *path,
+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(struct aa_label *label, const struct path *path,
+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(struct aa_label *label, const struct path *path,
- const char *old_name);
+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(struct aa_label *label, const char *dev_name,
+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(struct aa_label *label, struct vfsmount *mnt, int flags);
+int aa_umount(const struct cred *subj_cred,
+ struct aa_label *label, struct vfsmount *mnt, int flags);
-int aa_pivotroot(struct aa_label *label, const struct path *old_path,
+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
index aadb4b29fb66..0d0b0ce42723 100644
--- a/security/apparmor/include/net.h
+++ b/security/apparmor/include/net.h
@@ -47,38 +47,34 @@
#define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \
AA_MAY_ACCEPT)
struct aa_sk_ctx {
- struct aa_label *label;
- struct aa_label *peer;
+ struct aa_label __rcu *label;
+ struct aa_label __rcu *peer;
+ struct aa_label __rcu *peer_lastupdate; /* ptr cmp only, no deref */
};
-#define SK_CTX(X) ((X)->sk_security)
-#define SOCK_ctx(X) SOCK_INODE(X)->i_security
-#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \
+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.u.net = &(NAME ## _net); \
- aad(&NAME)->net.type = (T); \
- aad(&NAME)->net.protocol = (P)
+ NAME.common.u.net = &(NAME ## _net); \
+ NAME.subj_cred = (CRED); \
+ NAME.net.type = (T); \
+ NAME.net.protocol = (P)
-#define DEFINE_AUDIT_SK(NAME, OP, SK) \
- DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \
+#define DEFINE_AUDIT_SK(NAME, OP, CRED, SK) \
+ DEFINE_AUDIT_NET(NAME, OP, CRED, SK, (SK)->sk_family, (SK)->sk_type, \
(SK)->sk_protocol)
-#define af_select(FAMILY, FN, DEF_FN) \
-({ \
- int __e; \
- switch ((FAMILY)) { \
- default: \
- __e = DEF_FN; \
- } \
- __e; \
-})
-
struct aa_secmark {
u8 audit;
u8 deny;
@@ -87,24 +83,35 @@ struct aa_secmark {
};
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 common_audit_data *sa,
- u32 request, u16 family, int type);
-int aa_af_perm(struct aa_label *label, const char *op, u32 request, u16 family,
+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 common_audit_data *sa,
+ struct apparmor_audit_data *ad,
u32 request,
struct sock *sk)
{
- return aa_profile_af_perm(profile, sa, request, sk->sk_family,
- sk->sk_type);
+ 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(struct aa_label *label, const char *op, u32 request,
- struct socket *sock);
+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);
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
index 44a7945fbe3c..8bb915d48dc7 100644
--- a/security/apparmor/include/path.h
+++ b/security/apparmor/include/path.h
@@ -13,12 +13,13 @@
enum path_flags {
PATH_IS_DIR = 0x1, /* path is a directory */
+ PATH_SOCK_COND = 0x2,
PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
- PATH_DELEGATE_DELETED = 0x08000, /* delegate deleted files */
- PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
+ PATH_DELEGATE_DELETED = 0x10000, /* delegate deleted files */
+ PATH_MEDIATE_DELETED = 0x20000, /* mediate deleted paths */
};
int aa_path_name(const struct path *path, int flags, char *buffer,
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
index 13f20c598448..37a3781b99a0 100644
--- a/security/apparmor/include/perms.h
+++ b/security/apparmor/include/perms.h
@@ -48,6 +48,9 @@
#define AA_LINK_SUBSET AA_MAY_LOCK /* overlaid */
+#define AA_MAY_CREATE_SQPOLL AA_MAY_CREATE
+#define AA_MAY_OVERRIDE_CRED AA_MAY_APPEND
+#define AA_URING_PERM_MASK (AA_MAY_OVERRIDE_CRED | AA_MAY_CREATE_SQPOLL)
#define PERMS_CHRS_MASK (MAY_READ | MAY_WRITE | AA_MAY_CREATE | \
AA_MAY_DELETE | AA_MAY_LINK | AA_MAY_LOCK | \
@@ -65,29 +68,90 @@ extern const char *aa_file_perm_names[];
struct aa_perms {
u32 allow;
- u32 audit; /* set only when allow is set */
-
u32 deny; /* explicit deny, or conflict if allow also set */
- u32 quiet; /* set only when ~allow | deny */
- u32 kill; /* set only when ~allow | deny */
- u32 stop; /* set only when ~allow | deny */
- u32 complain; /* accumulates only used when ~allow & ~deny */
+ u32 subtree; /* allow perm on full subtree only when allow is set */
u32 cond; /* set only when ~allow and ~deny */
- u32 hide; /* set only when ~allow | deny */
+ u32 kill; /* set only when ~allow | deny */
+ u32 complain; /* accumulates only used when ~allow & ~deny */
u32 prompt; /* accumulates only used when ~allow & ~deny */
- /* Reserved:
- * u32 subtree; / * set only when allow is set * /
- */
- u16 xindex;
+ u32 audit; /* set only when allow is set */
+ u32 quiet; /* set only when ~allow | deny */
+ u32 hide; /* set only when ~allow | deny */
+
+
+ u32 xindex;
+ u32 tag; /* tag string index, if present */
+ u32 label; /* label string index, if present */
};
+/*
+ * Indexes are broken into a 24 bit index and 8 bit flag.
+ * For the index to be valid there must be a value in the flag
+ */
+#define AA_INDEX_MASK 0x00ffffff
+#define AA_INDEX_FLAG_MASK 0xff000000
+#define AA_INDEX_NONE 0
+
#define ALL_PERMS_MASK 0xffffffff
extern struct aa_perms nullperms;
extern struct aa_perms allperms;
+/**
+ * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
+ * @accum: perms struct to accumulate into
+ * @addend: perms struct to add to @accum
+ */
+static inline void aa_perms_accum_raw(struct aa_perms *accum,
+ struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~addend->deny;
+ accum->audit |= addend->audit & addend->allow;
+ accum->quiet &= addend->quiet & ~addend->allow;
+ accum->kill |= addend->kill & ~addend->allow;
+ accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
+ accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
+ accum->hide &= addend->hide & ~addend->allow;
+ accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
+ accum->subtree |= addend->subtree & ~addend->deny;
+
+ if (!accum->xindex)
+ accum->xindex = addend->xindex;
+ if (!accum->tag)
+ accum->tag = addend->tag;
+ if (!accum->label)
+ accum->label = addend->label;
+}
+
+/**
+ * aa_perms_accum - accumulate perms, masking off overlapping perms
+ * @accum: perms struct to accumulate into
+ * @addend: perms struct to add to @accum
+ */
+static inline void aa_perms_accum(struct aa_perms *accum,
+ struct aa_perms *addend)
+{
+ accum->deny |= addend->deny;
+ accum->allow &= addend->allow & ~accum->deny;
+ accum->audit |= addend->audit & accum->allow;
+ accum->quiet &= addend->quiet & ~accum->allow;
+ accum->kill |= addend->kill & ~accum->allow;
+ accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
+ accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
+ accum->hide &= addend->hide & ~accum->allow;
+ accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
+ accum->subtree &= addend->subtree & ~accum->deny;
+
+ if (!accum->xindex)
+ accum->xindex = addend->xindex;
+ if (!accum->tag)
+ accum->tag = addend->tag;
+ if (!accum->label)
+ accum->label = addend->label;
+}
#define xcheck(FN1, FN2) \
({ \
@@ -133,6 +197,9 @@ extern struct aa_perms allperms;
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,
@@ -141,16 +208,12 @@ 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_compute_perms(struct aa_dfa *dfa, unsigned int state,
- struct aa_perms *perms);
void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
-void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
+void aa_profile_match_label(struct aa_profile *profile,
+ struct aa_ruleset *rules, struct aa_label *label,
int type, u32 request, struct aa_perms *perms);
-int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
- u32 request, int type, u32 *deny,
- struct common_audit_data *sa);
int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
- u32 request, struct common_audit_data *sa,
+ u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *));
#endif /* __AA_PERM_H */
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index cb5ef21991b7..4c50875c9d13 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -34,6 +34,7 @@
struct aa_ns;
extern int unprivileged_userns_apparmor_policy;
+extern int aa_unprivileged_unconfined_restricted;
extern const char *const aa_profile_mode_names[];
#define APPARMOR_MODE_NAMES_MAX_INDEX 4
@@ -44,14 +45,25 @@ extern const char *const aa_profile_mode_names[];
#define COMPLAIN_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_COMPLAIN)
+#define USER_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_USER)
+
#define KILL_MODE(_profile) PROFILE_MODE((_profile), APPARMOR_KILL)
#define PROFILE_IS_HAT(_profile) ((_profile)->label.flags & FLAG_HAT)
+#define CHECK_DEBUG1(_profile) ((_profile)->label.flags & FLAG_DEBUG1)
+
+#define CHECK_DEBUG2(_profile) ((_profile)->label.flags & FLAG_DEBUG2)
+
#define profile_is_stale(_profile) (label_is_stale(&(_profile)->label))
#define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2)
+/* flags in the dfa accept2 table */
+enum dfa_accept_flags {
+ ACCEPT_FLAG_OWNER = 1,
+};
+
/*
* FIXME: currently need a clean way to replace and remove profiles as a
* set. It should be done at the namespace level.
@@ -63,20 +75,72 @@ enum profile_mode {
APPARMOR_COMPLAIN, /* allow and log access violations */
APPARMOR_KILL, /* kill task on access violation */
APPARMOR_UNCONFINED, /* profile set to unconfined */
+ APPARMOR_USER, /* modified complain mode to userspace */
};
/* struct aa_policydb - match engine for a policy
+ * count: refcount for the pdb
* dfa: dfa pattern match
+ * perms: table of permissions
+ * strs: table of strings, index by x
* start: set of start states for the different classes of data
*/
struct aa_policydb {
- /* Generic policy DFA specific rule types will be subsections of it */
+ struct kref count;
struct aa_dfa *dfa;
- unsigned int start[AA_CLASS_LAST + 1];
-
+ struct {
+ struct aa_perms *perms;
+ u32 size;
+ };
+ struct aa_str_table trans;
+ aa_state_t start[AA_CLASS_LAST + 1];
};
+extern struct aa_policydb *nullpdb;
+
+struct aa_policydb *aa_alloc_pdb(gfp_t gfp);
+void aa_pdb_free_kref(struct kref *kref);
+
+/**
+ * aa_get_pdb - increment refcount on @pdb
+ * @pdb: policydb (MAYBE NULL)
+ *
+ * Returns: pointer to @pdb if @pdb is NULL will return NULL
+ * Requires: @pdb must be held with valid refcount when called
+ */
+static inline struct aa_policydb *aa_get_pdb(struct aa_policydb *pdb)
+{
+ if (pdb)
+ kref_get(&(pdb->count));
+
+ return pdb;
+}
+
+/**
+ * aa_put_pdb - put a pdb refcount
+ * @pdb: pdb to put refcount (MAYBE NULL)
+ *
+ * Requires: if @pdb != NULL that a valid refcount be held
+ */
+static inline void aa_put_pdb(struct aa_policydb *pdb)
+{
+ if (pdb)
+ kref_put(&pdb->count, aa_pdb_free_kref);
+}
+
+/* lookup perm that doesn't have and object conditional */
+static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy,
+ aa_state_t state)
+{
+ unsigned int index = ACCEPT_TABLE(policy->dfa)[state];
+
+ if (!(policy->perms))
+ return &default_perms;
+
+ return &(policy->perms[index]);
+}
+
/* struct aa_data - generic data structure
* key: name for retrieving this data
* size: size of data in bytes
@@ -90,29 +154,70 @@ struct aa_data {
struct rhash_head head;
};
+/* struct aa_ruleset - data covering mediation rules
+ * @list: list the rule is on
+ * @size: the memory consumed by this ruleset
+ * @policy: general match rules governing policy
+ * @file: The set of rules governing basic file access and domain transitions
+ * @caps: capabilities for the profile
+ * @rlimits: rlimits for the profile
+ * @secmark_count: number of secmark entries
+ * @secmark: secmark label match info
+ */
+struct aa_ruleset {
+ int size;
+
+ /* TODO: merge policy and file */
+ struct aa_policydb *policy;
+ struct aa_policydb *file;
+ struct aa_caps caps;
+
+ struct aa_rlimit rlimits;
+
+ int secmark_count;
+ struct aa_secmark *secmark;
+};
+
+
+/* struct aa_attachment - data and rules for a profiles attachment
+ * @list:
+ * @xmatch_str: human readable attachment string
+ * @xmatch: optional extended matching for unconfined executables names
+ * @xmatch_len: xmatch prefix len, used to determine xmatch priority
+ * @xattr_count: number of xattrs in table
+ * @xattrs: table of xattrs
+ */
+struct aa_attachment {
+ const char *xmatch_str;
+ struct aa_policydb *xmatch;
+ unsigned int xmatch_len;
+ int xattr_count;
+ char **xattrs;
+};
/* struct aa_profile - basic confinement data
* @base - base components of the profile (name, refcount, lists, lock ...)
- * @label - label this profile is an extension of
* @parent: parent of profile
* @ns: namespace the profile is in
* @rename: optional profile name that this profile renamed
- * @attach: human readable attachment string
- * @xmatch: optional extended matching for unconfined executables names
- * @xmatch_len: xmatch prefix len, used to determine xmatch priority
+ *
* @audit: the auditing mode of the profile
* @mode: the enforcement mode of the profile
* @path_flags: flags controlling path generation behavior
+ * @signal: the signal that should be used when kill is used
* @disconnected: what to prepend if attach_disconnected is specified
- * @size: the memory consumed by this profiles rules
- * @policy: general match rules governing policy
- * @file: The set of rules governing basic file access and domain transitions
- * @caps: capabilities for the profile
- * @rlimits: rlimits for the profile
+ * @attach: attachment rules for the profile
+ * @rules: rules to be enforced
*
+ * learning_cache: the accesses learned in complain mode
+ * raw_data: rawdata of the loaded profile policy
+ * hash: cryptographic hash of the profile
* @dents: dentries for the profiles file entries in apparmorfs
* @dirname: name of the profile dir in apparmorfs
+ * @dents: set of dentries associated with the profile
* @data: hashtable for free-form policy aa_data
+ * @label - label this profile is an extension of
+ * @rules - label with the rule vec on its end
*
* The AppArmor profile contains the basic confinement data. Each profile
* has a name, and exists in a namespace. The @name and @exec_match are
@@ -133,32 +238,22 @@ struct aa_profile {
struct aa_ns *ns;
const char *rename;
- const char *attach;
- struct aa_dfa *xmatch;
- int xmatch_len;
enum audit_mode audit;
long mode;
u32 path_flags;
+ int signal;
const char *disconnected;
- int size;
- struct aa_policydb policy;
- struct aa_file_rules file;
- struct aa_caps caps;
-
- int xattr_count;
- char **xattrs;
-
- struct aa_rlimit rlimits;
-
- int secmark_count;
- struct aa_secmark *secmark;
+ struct aa_attachment attach;
struct aa_loaddata *rawdata;
unsigned char *hash;
char *dirname;
struct dentry *dents[AAFS_PROF_SIZEOF];
struct rhashtable *data;
+
+ int n_rules;
+ /* special - variable length must be last entry in profile */
struct aa_label label;
};
@@ -171,23 +266,19 @@ extern enum profile_mode aa_g_profile_mode;
#define profiles_ns(P) ((P)->ns)
#define name_is_shared(A, B) ((A)->hname && (A)->hname == (B)->hname)
-void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
-
-
-void aa_free_proxy_kref(struct kref *kref);
+struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp);
struct aa_profile *aa_alloc_profile(const char *name, struct aa_proxy *proxy,
gfp_t gfp);
-struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
- const char *base, gfp_t gfp);
+struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
+ gfp_t gfp);
+struct aa_profile *aa_new_learning_profile(struct aa_profile *parent, bool hat,
+ const char *base, gfp_t gfp);
void aa_free_profile(struct aa_profile *profile);
-void aa_free_profile_kref(struct kref *kref);
struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
size_t n);
-struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *name);
struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n);
-struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
u32 mask, struct aa_loaddata *udata);
@@ -195,9 +286,6 @@ ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
char *name, size_t size);
void __aa_profile_list_release(struct list_head *head);
-#define PROF_ADD 1
-#define PROF_REPLACE 0
-
#define profile_unconfined(X) ((X)->mode == APPARMOR_UNCONFINED)
/**
@@ -213,24 +301,48 @@ static inline struct aa_profile *aa_get_newest_profile(struct aa_profile *p)
return labels_profile(aa_get_newest_label(&p->label));
}
-static inline unsigned int PROFILE_MEDIATES(struct aa_profile *profile,
- unsigned char class)
+static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules,
+ unsigned char class)
{
if (class <= AA_CLASS_LAST)
- return profile->policy.start[class];
+ return rules->policy->start[class];
else
- return aa_dfa_match_len(profile->policy.dfa,
- profile->policy.start[0], &class, 1);
+ return aa_dfa_match_len(rules->policy->dfa,
+ rules->policy->start[0], &class, 1);
+}
+
+static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules)
+{
+ return RULE_MEDIATES(rules, AA_CLASS_NETV9);
}
-static inline unsigned int PROFILE_MEDIATES_AF(struct aa_profile *profile,
- u16 AF) {
- unsigned int state = PROFILE_MEDIATES(profile, AA_CLASS_NET);
- __be16 be_af = cpu_to_be16(AF);
+static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules)
+{
+ /* can not use RULE_MEDIATE_v9AF here, because AF match fail
+ * can not be distiguished from class match fail, and we only
+ * fallback to checking older class on class match failure
+ */
+ aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9);
+ /* fallback and check v7/8 if v9 is NOT mediated */
if (!state)
- return 0;
- return aa_dfa_match_len(profile->policy.dfa, state, (char *) &be_af, 2);
+ state = RULE_MEDIATES(rules, AA_CLASS_NET);
+
+ return state;
+}
+
+
+void aa_compute_profile_mediates(struct aa_profile *profile);
+static inline bool profile_mediates(struct aa_profile *profile,
+ unsigned char class)
+{
+ return label_mediates(&profile->label, class);
+}
+
+static inline bool profile_mediates_safe(struct aa_profile *profile,
+ unsigned char class)
+{
+ return label_mediates_safe(&profile->label, class);
}
/**
@@ -301,9 +413,12 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
return profile->audit;
}
-bool aa_policy_view_capable(struct aa_label *label, struct aa_ns *ns);
-bool aa_policy_admin_capable(struct aa_label *label, struct aa_ns *ns);
-int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns,
+bool aa_policy_view_capable(const struct cred *subj_cred,
+ struct aa_label *label, struct aa_ns *ns);
+bool aa_policy_admin_capable(const struct cred *subj_cred,
+ struct aa_label *label, struct aa_ns *ns);
+int aa_may_manage_policy(const struct cred *subj_cred,
+ struct aa_label *label, struct aa_ns *ns,
u32 mask);
bool aa_current_policy_view_capable(struct aa_ns *ns);
bool aa_current_policy_admin_capable(struct aa_ns *ns);
diff --git a/security/apparmor/include/policy_compat.h b/security/apparmor/include/policy_compat.h
new file mode 100644
index 000000000000..af0e174332df
--- /dev/null
+++ b/security/apparmor/include/policy_compat.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * AppArmor security module
+ *
+ * Code to provide backwards compatibility with older policy versions,
+ * by converting/mapping older policy formats into the newer internal
+ * formats.
+ *
+ * Copyright 2022 Canonical Ltd.
+ */
+
+#ifndef __POLICY_COMPAT_H
+#define __POLICY_COMPAT_H
+
+#include "policy.h"
+
+#define K_ABI_MASK 0x3ff
+#define FORCE_COMPLAIN_FLAG 0x800
+#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK))
+#define VERSION_LE(X, Y) (((X) & K_ABI_MASK) <= ((Y) & K_ABI_MASK))
+#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK))
+
+#define v5 5 /* base version */
+#define v6 6 /* per entry policydb mediation check */
+#define v7 7
+#define v8 8 /* full network masking */
+#define v9 9 /* xbits are used as permission bits in policydb */
+
+int aa_compat_map_xmatch(struct aa_policydb *policy);
+int aa_compat_map_policy(struct aa_policydb *policy, u32 version);
+int aa_compat_map_file(struct aa_policydb *policy);
+
+#endif /* __POLICY_COMPAT_H */
diff --git a/security/apparmor/include/policy_ns.h b/security/apparmor/include/policy_ns.h
index 3df6f804922d..d646070fd966 100644
--- a/security/apparmor/include/policy_ns.h
+++ b/security/apparmor/include/policy_ns.h
@@ -74,6 +74,7 @@ struct aa_ns {
struct dentry *dents[AAFS_NS_SIZEOF];
};
+extern struct aa_label *kernel_t;
extern struct aa_ns *root_ns;
extern const char *aa_hidden_ns_name;
@@ -85,10 +86,7 @@ const char *aa_ns_name(struct aa_ns *parent, struct aa_ns *child, bool subns);
void aa_free_ns(struct aa_ns *ns);
int aa_alloc_root_ns(void);
void aa_free_root_ns(void);
-void aa_free_ns_kref(struct kref *kref);
-struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name);
-struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n);
struct aa_ns *__aa_lookupn_ns(struct aa_ns *view, const char *hname, size_t n);
struct aa_ns *aa_lookupn_ns(struct aa_ns *view, const char *name, size_t n);
struct aa_ns *__aa_find_or_create_ns(struct aa_ns *parent, const char *name,
@@ -150,15 +148,4 @@ static inline struct aa_ns *__aa_find_ns(struct list_head *head,
return __aa_findn_ns(head, name, strlen(name));
}
-static inline struct aa_ns *__aa_lookup_ns(struct aa_ns *base,
- const char *hname)
-{
- return __aa_lookupn_ns(base, hname, strlen(hname));
-}
-
-static inline struct aa_ns *aa_lookup_ns(struct aa_ns *view, const char *name)
-{
- return aa_lookupn_ns(view, name, strlen(name));
-}
-
#endif /* AA_NAMESPACE_H */
diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
index e0e1ca7ebc38..a6f4611ee50c 100644
--- a/security/apparmor/include/policy_unpack.h
+++ b/security/apparmor/include/policy_unpack.h
@@ -16,6 +16,7 @@
#include <linux/dcache.h>
#include <linux/workqueue.h>
+
struct aa_load_ent {
struct list_head list;
struct aa_profile *new;
@@ -28,11 +29,14 @@ void aa_load_ent_free(struct aa_load_ent *ent);
struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_FLAG_HAT 1
+#define PACKED_FLAG_DEBUG1 2
+#define PACKED_FLAG_DEBUG2 4
#define PACKED_MODE_ENFORCE 0
#define PACKED_MODE_COMPLAIN 1
#define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3
+#define PACKED_MODE_USER 4
struct aa_ns;
@@ -47,6 +51,43 @@ enum {
};
/*
+ * 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
@@ -124,4 +165,17 @@ static inline void aa_put_loaddata(struct aa_loaddata *data)
kref_put(&data->count, aa_loaddata_kref);
}
+#if IS_ENABLED(CONFIG_KUNIT)
+bool aa_inbounds(struct aa_ext *e, size_t size);
+size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk);
+bool aa_unpack_X(struct aa_ext *e, enum aa_code code);
+bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name);
+bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name);
+bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name);
+bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size);
+size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name);
+int aa_unpack_str(struct aa_ext *e, const char **string, const char *name);
+int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name);
+#endif
+
#endif /* __POLICY_INTERFACE_H */
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
index 31689437e0e1..03dbfdb2f2c0 100644
--- a/security/apparmor/include/procattr.h
+++ b/security/apparmor/include/procattr.h
@@ -11,7 +11,7 @@
#ifndef __AA_PROCATTR_H
#define __AA_PROCATTR_H
-int aa_getprocattr(struct aa_label *label, char **string);
+int aa_getprocattr(struct aa_label *label, char **string, bool newline);
int aa_setprocattr_changehat(char *args, size_t size, int flags);
#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h
index 961d85d328ea..ad2c0da8e64f 100644
--- a/security/apparmor/include/resource.h
+++ b/security/apparmor/include/resource.h
@@ -33,7 +33,8 @@ struct aa_rlimit {
extern struct aa_sfs_entry aa_sfs_entry_rlimit[];
int aa_map_resource(int resource);
-int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
+int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label,
+ struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim);
void __aa_transition_rlimits(struct aa_label *old, struct aa_label *new);
diff --git a/security/apparmor/include/secid.h b/security/apparmor/include/secid.h
index 48ff1ddecad5..6025d3849cf8 100644
--- a/security/apparmor/include/secid.h
+++ b/security/apparmor/include/secid.h
@@ -21,16 +21,17 @@ struct aa_label;
/* 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, char **secdata, u32 *seclen);
+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(char *secdata, u32 seclen);
+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);
-void aa_secid_update(u32 secid, struct aa_label *label);
-
-void aa_secids_init(void);
#endif /* __AA_SECID_H */
diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h
index cbf7a997ed84..c772668cdc62 100644
--- a/security/apparmor/include/sig_names.h
+++ b/security/apparmor/include/sig_names.h
@@ -1,9 +1,5 @@
#include <linux/signal.h>
-
-#define SIGUNKNOWN 0
-#define MAXMAPPED_SIG 35
-#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1)
-#define SIGRT_BASE 128
+#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
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
index f13d12373b25..b1aaaf60fa8b 100644
--- a/security/apparmor/include/task.h
+++ b/security/apparmor/include/task.h
@@ -30,7 +30,7 @@ struct aa_task_ctx {
};
int aa_replace_current_label(struct aa_label *label);
-int aa_set_current_onexec(struct aa_label *label, bool stack);
+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);
@@ -77,4 +77,29 @@ static inline void aa_clear_task_ctx_trans(struct aa_task_ctx *ctx)
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 fe36d112aad9..df5712cea685 100644
--- a/security/apparmor/ipc.c
+++ b/security/apparmor/ipc.c
@@ -9,7 +9,6 @@
*/
#include <linux/gfp.h>
-#include <linux/ptrace.h>
#include "include/audit.h"
#include "include/capability.h"
@@ -18,115 +17,6 @@
#include "include/ipc.h"
#include "include/sig_names.h"
-/**
- * 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;
-
- if (aad(sa)->request & AA_PTRACE_PERM_MASK) {
- audit_log_format(ab, " requested_mask=\"%s\"",
- audit_ptrace_mask(aad(sa)->request));
-
- if (aad(sa)->denied & AA_PTRACE_PERM_MASK) {
- audit_log_format(ab, " denied_mask=\"%s\"",
- audit_ptrace_mask(aad(sa)->denied));
- }
- }
- audit_log_format(ab, " peer=");
- aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
- FLAGS_NONE, GFP_ATOMIC);
-}
-
-/* assumes check for PROFILE_MEDIATES is already done */
-/* TODO: conditionals */
-static int profile_ptrace_perm(struct aa_profile *profile,
- struct aa_label *peer, u32 request,
- struct common_audit_data *sa)
-{
- struct aa_perms perms = { };
-
- aad(sa)->peer = peer;
- aa_profile_match_label(profile, peer, AA_CLASS_PTRACE, request,
- &perms);
- aa_apply_modes_to_perms(profile, &perms);
- return aa_check_perms(profile, &perms, request, sa, audit_ptrace_cb);
-}
-
-static int profile_tracee_perm(struct aa_profile *tracee,
- struct aa_label *tracer, u32 request,
- struct common_audit_data *sa)
-{
- if (profile_unconfined(tracee) || unconfined(tracer) ||
- !PROFILE_MEDIATES(tracee, AA_CLASS_PTRACE))
- return 0;
-
- return profile_ptrace_perm(tracee, tracer, request, sa);
-}
-
-static int profile_tracer_perm(struct aa_profile *tracer,
- struct aa_label *tracee, u32 request,
- struct common_audit_data *sa)
-{
- if (profile_unconfined(tracer))
- return 0;
-
- if (PROFILE_MEDIATES(tracer, AA_CLASS_PTRACE))
- return profile_ptrace_perm(tracer, tracee, request, sa);
-
- /* profile uses the old style capability check for ptrace */
- if (&tracer->label == tracee)
- return 0;
-
- aad(sa)->label = &tracer->label;
- aad(sa)->peer = tracee;
- aad(sa)->request = 0;
- aad(sa)->error = aa_capable(&tracer->label, CAP_SYS_PTRACE,
- CAP_OPT_NONE);
-
- return aa_audit(AUDIT_APPARMOR_AUTO, tracer, sa, audit_ptrace_cb);
-}
-
-/**
- * aa_may_ptrace - test if tracer task can trace the tracee
- * @tracer: label of the task doing the tracing (NOT NULL)
- * @tracee: task label to be traced
- * @request: permission request
- *
- * Returns: %0 else error code if permission denied or error
- */
-int aa_may_ptrace(struct aa_label *tracer, struct aa_label *tracee,
- u32 request)
-{
- struct aa_profile *profile;
- u32 xrequest = request << PTRACE_PERM_SHIFT;
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_PTRACE);
-
- return xcheck_labels(tracer, tracee, profile,
- profile_tracer_perm(profile, tracee, request, &sa),
- profile_tracee_perm(profile, tracer, xrequest, &sa));
-}
-
static inline int map_signal_num(int sig)
{
@@ -155,64 +45,72 @@ static const char *audit_signal_mask(u32 mask)
}
/**
- * audit_cb - call back for signal specific audit fields
+ * audit_signal_cb() - call back for signal specific audit fields
* @ab: audit_buffer (NOT NULL)
* @va: audit struct to audit values of (NOT NULL)
*/
static void audit_signal_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+ struct apparmor_audit_data *ad = aad(sa);
- if (aad(sa)->request & AA_SIGNAL_PERM_MASK) {
+ if (ad->request & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " requested_mask=\"%s\"",
- audit_signal_mask(aad(sa)->request));
- if (aad(sa)->denied & AA_SIGNAL_PERM_MASK) {
+ audit_signal_mask(ad->request));
+ if (ad->denied & AA_SIGNAL_PERM_MASK) {
audit_log_format(ab, " denied_mask=\"%s\"",
- audit_signal_mask(aad(sa)->denied));
+ audit_signal_mask(ad->denied));
}
}
- if (aad(sa)->signal == SIGUNKNOWN)
+ if (ad->signal == SIGUNKNOWN)
audit_log_format(ab, "signal=unknown(%d)",
- aad(sa)->unmappedsig);
- else if (aad(sa)->signal < MAXMAPPED_SIGNAME)
- audit_log_format(ab, " signal=%s", sig_names[aad(sa)->signal]);
+ 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",
- aad(sa)->signal - SIGRT_BASE);
+ ad->signal - SIGRT_BASE);
audit_log_format(ab, " peer=");
- aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
-static int profile_signal_perm(struct aa_profile *profile,
+static int profile_signal_perm(const struct cred *cred,
+ struct aa_profile *profile,
struct aa_label *peer, u32 request,
- struct common_audit_data *sa)
+ struct apparmor_audit_data *ad)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
struct aa_perms perms;
- unsigned int state;
+ aa_state_t state;
- if (profile_unconfined(profile) ||
- !PROFILE_MEDIATES(profile, AA_CLASS_SIGNAL))
+ if (profile_unconfined(profile))
return 0;
- aad(sa)->peer = peer;
+ ad->subj_cred = cred;
+ ad->peer = peer;
/* TODO: secondary cache check <profile, profile, perm> */
- state = aa_dfa_next(profile->policy.dfa,
- profile->policy.start[AA_CLASS_SIGNAL],
- aad(sa)->signal);
- aa_label_match(profile, peer, state, false, request, &perms);
+ 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, sa, audit_signal_cb);
+ return aa_check_perms(profile, &perms, request, ad, audit_signal_cb);
}
-int aa_may_signal(struct aa_label *sender, struct aa_label *target, int sig)
+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(sa, LSM_AUDIT_DATA_NONE, OP_SIGNAL);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_SIGNAL, OP_SIGNAL);
- aad(&sa)->signal = map_signal_num(sig);
- aad(&sa)->unmappedsig = sig;
+ ad.signal = map_signal_num(sig);
+ ad.unmappedsig = sig;
return xcheck_labels(sender, target, profile,
- profile_signal_perm(profile, target, MAY_WRITE, &sa),
- profile_signal_perm(profile, sender, MAY_READ, &sa));
+ profile_signal_perm(subj_cred, profile, target,
+ MAY_WRITE, &ad),
+ profile_signal_perm(target_cred, profile, sender,
+ MAY_READ, &ad));
}
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 0b0265da1926..913678f199c3 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -154,13 +154,14 @@ static int profile_cmp(struct aa_profile *a, struct aa_profile *b)
/**
* vec_cmp - label comparison for set ordering
- * @a: label to compare (NOT NULL)
- * @vec: vector of profiles to compare (NOT NULL)
- * @n: length of @vec
- *
- * Returns: <0 if a < vec
- * ==0 if a == vec
- * >0 if a > vec
+ * @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)
{
@@ -197,18 +198,25 @@ static bool vec_is_stale(struct aa_profile **vec, int n)
return false;
}
-static bool vec_unconfined(struct aa_profile **vec, int n)
+static void accum_label_info(struct aa_label *new)
{
+ long u = FLAG_UNCONFINED;
int i;
- AA_BUG(!vec);
+ AA_BUG(!new);
- for (i = 0; i < n; i++) {
- if (!profile_unconfined(vec[i]))
- return false;
- }
+ /* size == 1 is a profile and flags must be set as part of creation */
+ if (new->size == 1)
+ return;
- return true;
+ for (i = 0; i < new->size; i++) {
+ u |= new->vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 |
+ FLAG_STALE);
+ if (!(u & new->vec[i]->label.flags & FLAG_UNCONFINED))
+ u &= ~FLAG_UNCONFINED;
+ new->mediates |= new->vec[i]->label.mediates;
+ }
+ new->flags |= u;
}
static int sort_cmp(const void *a, const void *b)
@@ -253,6 +261,7 @@ static inline int unique(struct aa_profile **vec, int n)
* aa_vec_unique - canonical sort and unique a list of profiles
* @n: number of refcounted profiles in the list (@n > 0)
* @vec: list of profiles to sort and merge
+ * @flags: null terminator flags of @vec
*
* Returns: the number of duplicates eliminated == references put
*
@@ -426,7 +435,7 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp)
/* + 1 for null terminator entry on vec */
new = kzalloc(struct_size(new, vec, size + 1), gfp);
- AA_DEBUG("%s (%p)\n", __func__, new);
+ AA_DEBUG(DEBUG_LABEL, "%s (%p)\n", __func__, new);
if (!new)
goto fail;
@@ -485,7 +494,7 @@ int aa_label_next_confined(struct aa_label *label, int i)
}
/**
- * aa_label_next_not_in_set - return the next profile of @sub not in @set
+ * __aa_label_next_not_in_set - return the next profile of @sub not in @set
* @I: label iterator
* @set: label to test against
* @sub: label to if is subset of @set
@@ -581,7 +590,7 @@ bool aa_label_is_unconfined_subset(struct aa_label *set, struct aa_label *sub)
/**
* __label_remove - remove @label from the label set
- * @l: label to remove
+ * @label: label to remove
* @new: label to redirect to
*
* Requires: labels_set(@label)->lock write_lock
@@ -640,6 +649,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new)
rb_replace_node(&old->node, &new->node, &ls->root);
old->flags &= ~FLAG_IN_TREE;
new->flags |= FLAG_IN_TREE;
+ accum_label_info(new);
return true;
}
@@ -700,6 +710,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls,
rb_link_node(&label->node, parent, new);
rb_insert_color(&label->node, &ls->root);
label->flags |= FLAG_IN_TREE;
+ accum_label_info(label);
return aa_get_label(label);
}
@@ -894,28 +905,11 @@ struct aa_label *aa_vec_find_or_create_label(struct aa_profile **vec, int len,
return vec_create_and_insert_label(vec, len, gfp);
}
-/**
- * aa_label_find - find label @label in label set
- * @label: label to find (NOT NULL)
- *
- * Requires: caller to hold a valid ref on l
- *
- * Returns: refcounted @label if @label is in tree
- * refcounted label that is equiv to @label in tree
- * else NULL if @label or equiv is not in tree
- */
-struct aa_label *aa_label_find(struct aa_label *label)
-{
- AA_BUG(!label);
-
- return vec_find(label->vec, label->size);
-}
-
/**
* aa_label_insert - insert label @label into @ls or return existing label
- * @ls - labelset to insert @label into
- * @label - label to insert
+ * @ls: labelset to insert @label into
+ * @label: label to insert
*
* Requires: caller to hold a valid ref on @label
*
@@ -1097,8 +1091,6 @@ static struct aa_label *label_merge_insert(struct aa_label *new,
else if (k == b->size)
return aa_get_label(b);
}
- if (vec_unconfined(new->vec, new->size))
- new->flags |= FLAG_UNCONFINED;
ls = labels_set(new);
write_lock_irqsave(&ls->lock, flags);
label = __label_insert(labels_set(new), new, false);
@@ -1202,7 +1194,6 @@ struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b)
/**
* aa_label_merge - attempt to insert new merged label of @a and @b
- * @ls: set of labels to insert label into (NOT NULL)
* @a: label to merge with @b (NOT NULL)
* @b: label to merge with @a (NOT NULL)
* @gfp: memory allocation type
@@ -1254,39 +1245,35 @@ out:
return label;
}
-static inline bool label_is_visible(struct aa_profile *profile,
- struct aa_label *label)
-{
- return aa_ns_visible(profile->ns, labels_ns(label), true);
-}
-
/* match a profile and its associated ns component if needed
* Assumes visibility test has already been done.
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline unsigned int match_component(struct aa_profile *profile,
- struct aa_profile *tp,
- unsigned int state)
+static inline aa_state_t match_component(struct aa_profile *profile,
+ struct aa_ruleset *rules,
+ struct aa_profile *tp,
+ aa_state_t state)
{
const char *ns_name;
if (profile->ns == tp->ns)
- return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
+ return aa_dfa_match(rules->policy->dfa, state, tp->base.hname);
/* try matching with namespace name and then profile */
ns_name = aa_ns_name(profile->ns, tp->ns, true);
- state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
- state = aa_dfa_match(profile->policy.dfa, state, ns_name);
- state = aa_dfa_match_len(profile->policy.dfa, state, ":", 1);
- return aa_dfa_match(profile->policy.dfa, state, tp->base.hname);
+ state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1);
+ state = aa_dfa_match(rules->policy->dfa, state, ns_name);
+ state = aa_dfa_match_len(rules->policy->dfa, state, ":", 1);
+ return aa_dfa_match(rules->policy->dfa, state, tp->base.hname);
}
/**
* label_compound_match - find perms for full compound label
* @profile: profile to find perms for
+ * @rules: ruleset to search
* @label: label to check access permissions for
- * @start: state to start match in
+ * @state: state to start match in
* @subns: whether to do permission checks on components in a subns
* @request: permissions to request
* @perms: perms struct to set
@@ -1298,8 +1285,9 @@ static inline unsigned int match_component(struct aa_profile *profile,
* check to be stacked.
*/
static int label_compound_match(struct aa_profile *profile,
+ struct aa_ruleset *rules,
struct aa_label *label,
- unsigned int state, bool subns, u32 request,
+ aa_state_t state, bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
@@ -1309,7 +1297,7 @@ static int label_compound_match(struct aa_profile *profile,
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
- state = match_component(profile, tp, state);
+ state = match_component(profile, rules, tp, state);
if (!state)
goto fail;
goto next;
@@ -1323,12 +1311,12 @@ next:
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
- state = aa_dfa_match(profile->policy.dfa, state, "//&");
- state = match_component(profile, tp, state);
+ state = aa_dfa_match(rules->policy->dfa, state, "//&");
+ state = match_component(profile, rules, tp, state);
if (!state)
goto fail;
}
- aa_compute_perms(profile->policy.dfa, state, perms);
+ *perms = *aa_lookup_perms(rules->policy, state);
aa_apply_modes_to_perms(profile, perms);
if ((perms->allow & request) != request)
return -EACCES;
@@ -1343,6 +1331,7 @@ fail:
/**
* label_components_match - find perms for all subcomponents of a label
* @profile: profile to find perms for
+ * @rules: ruleset to search
* @label: label to check access permissions for
* @start: state to start match in
* @subns: whether to do permission checks on components in a subns
@@ -1356,20 +1345,21 @@ fail:
* check to be stacked.
*/
static int label_components_match(struct aa_profile *profile,
- struct aa_label *label, unsigned int start,
+ struct aa_ruleset *rules,
+ struct aa_label *label, aa_state_t start,
bool subns, u32 request,
struct aa_perms *perms)
{
struct aa_profile *tp;
struct label_it i;
struct aa_perms tmp;
- unsigned int state = 0;
+ aa_state_t state = 0;
/* find first subcomponent to test */
label_for_each(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
- state = match_component(profile, tp, start);
+ state = match_component(profile, rules, tp, start);
if (!state)
goto fail;
goto next;
@@ -1379,16 +1369,16 @@ static int label_components_match(struct aa_profile *profile,
return 0;
next:
- aa_compute_perms(profile->policy.dfa, state, &tmp);
+ tmp = *aa_lookup_perms(rules->policy, state);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
label_for_each_cont(i, label, tp) {
if (!aa_ns_visible(profile->ns, tp->ns, subns))
continue;
- state = match_component(profile, tp, start);
+ state = match_component(profile, rules, tp, start);
if (!state)
goto fail;
- aa_compute_perms(profile->policy.dfa, state, &tmp);
+ tmp = *aa_lookup_perms(rules->policy, state);
aa_apply_modes_to_perms(profile, &tmp);
aa_perms_accum(perms, &tmp);
}
@@ -1406,6 +1396,7 @@ fail:
/**
* aa_label_match - do a multi-component label match
* @profile: profile to match against (NOT NULL)
+ * @rules: ruleset to search
* @label: label to match (NOT NULL)
* @state: state to start in
* @subns: whether to match subns components
@@ -1414,18 +1405,18 @@ fail:
*
* Returns: the state the match finished in, may be the none matching state
*/
-int aa_label_match(struct aa_profile *profile, struct aa_label *label,
- unsigned int state, bool subns, u32 request,
- struct aa_perms *perms)
+int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
+ struct aa_label *label, aa_state_t state, bool subns,
+ u32 request, struct aa_perms *perms)
{
- int error = label_compound_match(profile, label, state, subns, request,
- perms);
+ int error = label_compound_match(profile, rules, label, state, subns,
+ request, perms);
if (!error)
return error;
*perms = allperms;
- return label_components_match(profile, label, state, subns, request,
- perms);
+ return label_components_match(profile, rules, label, state, subns,
+ request, perms);
}
@@ -1470,7 +1461,7 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp)
/*
* cached label name is present and visible
- * @label->hname only exists if label is namespace hierachical
+ * @label->hname only exists if label is namespace hierarchical
*/
static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label,
int flags)
@@ -1631,9 +1622,9 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,
AA_BUG(!str && size != 0);
AA_BUG(!label);
- if (flags & FLAG_ABS_ROOT) {
+ if (DEBUG_ABS_ROOT && (flags & FLAG_ABS_ROOT)) {
ns = root_ns;
- len = snprintf(str, size, "=");
+ len = snprintf(str, size, "_");
update_for_len(total, len, size, str);
} else if (!ns) {
ns = labels_ns(label);
@@ -1744,8 +1735,8 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,
if (!use_label_hname(ns, label, flags) ||
display_mode(ns, label, flags)) {
len = aa_label_asxprint(&name, ns, label, flags, gfp);
- if (len == -1) {
- AA_DEBUG("label print error");
+ if (len < 0) {
+ AA_DEBUG(DEBUG_LABEL, "label print error");
return;
}
str = name;
@@ -1772,8 +1763,8 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,
int len;
len = aa_label_asxprint(&str, ns, label, flags, gfp);
- if (len == -1) {
- AA_DEBUG("label print error");
+ if (len < 0) {
+ AA_DEBUG(DEBUG_LABEL, "label print error");
return;
}
seq_puts(f, str);
@@ -1795,8 +1786,8 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
int len;
len = aa_label_asxprint(&str, ns, label, flags, gfp);
- if (len == -1) {
- AA_DEBUG("label print error");
+ if (len < 0) {
+ AA_DEBUG(DEBUG_LABEL, "label print error");
return;
}
pr_info("%s", str);
@@ -1808,22 +1799,6 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,
pr_info("%s", label->hname);
}
-void aa_label_audit(struct audit_buffer *ab, struct aa_label *label, gfp_t gfp)
-{
- struct aa_ns *ns = aa_get_current_ns();
-
- aa_label_xaudit(ab, ns, label, FLAG_VIEW_SUBNS, gfp);
- aa_put_ns(ns);
-}
-
-void aa_label_seq_print(struct seq_file *f, struct aa_label *label, gfp_t gfp)
-{
- struct aa_ns *ns = aa_get_current_ns();
-
- aa_label_seq_xprint(f, ns, label, FLAG_VIEW_SUBNS, gfp);
- aa_put_ns(ns);
-}
-
void aa_label_printk(struct aa_label *label, gfp_t gfp)
{
struct aa_ns *ns = aa_get_current_ns();
@@ -1895,7 +1870,8 @@ struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str,
AA_BUG(!str);
str = skipn_spaces(str, n);
- if (str == NULL || (*str == '=' && base != &root_ns->unconfined->label))
+ if (str == NULL || (DEBUG_ABS_ROOT && *str == '_' &&
+ base != &root_ns->unconfined->label))
return ERR_PTR(-EINVAL);
len = label_count_strn_entries(str, end - str);
@@ -2035,7 +2011,7 @@ out:
/**
* __label_update - insert updated version of @label into labelset
- * @label - the label to update/replace
+ * @label: the label to update/replace
*
* Returns: new label that is up to date
* else NULL on failure
@@ -2136,7 +2112,7 @@ static void __labelset_update(struct aa_ns *ns)
}
/**
- * __aa_labelset_udate_subtree - update all labels with a stale component
+ * __aa_labelset_update_subtree - update all labels with a stale component
* @ns: ns to start update at (NOT NULL)
*
* Requires: @ns lock be held
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index fa49b81eb54c..82dbb97ad406 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -25,47 +25,144 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK,
.quiet = ALL_PERMS_MASK,
.hide = ALL_PERMS_MASK };
+struct val_table_ent {
+ const char *str;
+ int value;
+};
+
+static struct val_table_ent debug_values_table[] = {
+ { "N", DEBUG_NONE },
+ { "none", DEBUG_NONE },
+ { "n", DEBUG_NONE },
+ { "0", DEBUG_NONE },
+ { "all", DEBUG_ALL },
+ { "Y", DEBUG_ALL },
+ { "y", DEBUG_ALL },
+ { "1", DEBUG_ALL },
+ { "abs_root", DEBUG_LABEL_ABS_ROOT },
+ { "label", DEBUG_LABEL },
+ { "domain", DEBUG_DOMAIN },
+ { "policy", DEBUG_POLICY },
+ { "interface", DEBUG_INTERFACE },
+ { NULL, 0 }
+};
+
+static struct val_table_ent *val_table_find_ent(struct val_table_ent *table,
+ const char *name, size_t len)
+{
+ struct val_table_ent *entry;
+
+ for (entry = table; entry->str != NULL; entry++) {
+ if (strncmp(entry->str, name, len) == 0 &&
+ strlen(entry->str) == len)
+ return entry;
+ }
+ return NULL;
+}
+
+int aa_parse_debug_params(const char *str)
+{
+ struct val_table_ent *ent;
+ const char *next;
+ int val = 0;
+
+ do {
+ size_t n = strcspn(str, "\r\n,");
+
+ next = str + n;
+ ent = val_table_find_ent(debug_values_table, str, next - str);
+ if (ent)
+ val |= ent->value;
+ else
+ AA_DEBUG(DEBUG_INTERFACE, "unknown debug type '%.*s'",
+ (int)(next - str), str);
+ str = next + 1;
+ } while (*next != 0);
+ return val;
+}
+
/**
- * aa_split_fqname - split a fqname into a profile and namespace name
- * @fqname: a full qualified name in namespace profile format (NOT NULL)
- * @ns_name: pointer to portion of the string containing the ns name (NOT NULL)
- *
- * Returns: profile name or NULL if one is not specified
- *
- * Split a namespace name from a profile name (see policy.c for naming
- * description). If a portion of the name is missing it returns NULL for
- * that portion.
- *
- * NOTE: may modify the @fqname string. The pointers returned point
- * into the @fqname string.
+ * val_mask_to_str - convert a perm mask to its short string
+ * @str: character buffer to store string in (at least 10 characters)
+ * @size: size of the @str buffer
+ * @table: NUL-terminated character buffer of permission characters (NOT NULL)
+ * @mask: permission mask to convert
*/
-char *aa_split_fqname(char *fqname, char **ns_name)
+static int val_mask_to_str(char *str, size_t size,
+ const struct val_table_ent *table, u32 mask)
{
- char *name = strim(fqname);
-
- *ns_name = NULL;
- if (name[0] == ':') {
- char *split = strchr(&name[1], ':');
- *ns_name = skip_spaces(&name[1]);
- if (split) {
- /* overwrite ':' with \0 */
- *split++ = 0;
- if (strncmp(split, "//", 2) == 0)
- split += 2;
- name = skip_spaces(split);
- } else
- /* a ns name without a following profile is allowed */
- name = NULL;
+ const struct val_table_ent *ent;
+ int total = 0;
+
+ for (ent = table; ent->str; ent++) {
+ if (ent->value && (ent->value & mask) == ent->value) {
+ int len = scnprintf(str, size, "%s%s", total ? "," : "",
+ ent->str);
+ size -= len;
+ str += len;
+ total += len;
+ mask &= ~ent->value;
+ }
}
- if (name && *name == 0)
- name = NULL;
- return name;
+ return total;
+}
+
+int aa_print_debug_params(char *buffer)
+{
+ if (!aa_g_debug)
+ return sprintf(buffer, "N");
+ return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table,
+ aa_g_debug);
+}
+
+bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
+{
+ char **n;
+ int i;
+
+ if (t->size == newsize)
+ return true;
+ n = kcalloc(newsize, sizeof(*n), gfp);
+ if (!n)
+ return false;
+ for (i = 0; i < min(t->size, newsize); i++)
+ n[i] = t->table[i];
+ for (; i < t->size; i++)
+ kfree_sensitive(t->table[i]);
+ if (newsize > t->size)
+ memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n));
+ kfree_sensitive(t->table);
+ t->table = n;
+ t->size = newsize;
+
+ return true;
+}
+
+/**
+ * aa_free_str_table - free entries str table
+ * @t: the string table to free (MAYBE NULL)
+ */
+void aa_free_str_table(struct aa_str_table *t)
+{
+ int i;
+
+ if (t) {
+ if (!t->table)
+ return;
+
+ for (i = 0; i < t->size; i++)
+ kfree_sensitive(t->table[i]);
+ kfree_sensitive(t->table);
+ t->table = NULL;
+ t->size = 0;
+ }
}
/**
* skipn_spaces - Removes leading whitespace from @str.
* @str: The string to be stripped.
+ * @n: length of str to parse, will stop at \0 if encountered before n
*
* Returns a pointer to the first non-whitespace character in @str.
* if all whitespace will return NULL
@@ -124,10 +221,10 @@ const char *aa_splitn_fqname(const char *fqname, size_t n, const char **ns_name,
void aa_info_message(const char *str)
{
if (audit_enabled) {
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
- aad(&sa)->info = str;
- aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, NULL);
+ ad.info = str;
+ aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, NULL);
}
printk(KERN_INFO "AppArmor: %s\n", str);
}
@@ -136,7 +233,7 @@ __counted char *aa_str_alloc(int size, gfp_t gfp)
{
struct counted_str *str;
- str = kmalloc(sizeof(struct counted_str) + size, gfp);
+ str = kmalloc(struct_size(str, name, size), gfp);
if (!str)
return NULL;
@@ -255,32 +352,6 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
}
/**
- * aa_audit_perms_cb - generic callback fn for auditing perms
- * @ab: audit buffer (NOT NULL)
- * @va: audit struct to audit values of (NOT NULL)
- */
-static void aa_audit_perms_cb(struct audit_buffer *ab, void *va)
-{
- struct common_audit_data *sa = va;
-
- if (aad(sa)->request) {
- audit_log_format(ab, " requested_mask=");
- aa_audit_perm_mask(ab, aad(sa)->request, aa_file_perm_chrs,
- PERMS_CHRS_MASK, aa_file_perm_names,
- PERMS_NAMES_MASK);
- }
- if (aad(sa)->denied) {
- audit_log_format(ab, "denied_mask=");
- aa_audit_perm_mask(ab, aad(sa)->denied, aa_file_perm_chrs,
- PERMS_CHRS_MASK, aa_file_perm_names,
- PERMS_NAMES_MASK);
- }
- audit_log_format(ab, " peer=");
- aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
- FLAGS_NONE, GFP_ATOMIC);
-}
-
-/**
* aa_apply_modes_to_perms - apply namespace and profile flags to perms
* @profile: that perms where computed from
* @perms: perms to apply mode modifiers to
@@ -308,113 +379,31 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
perms->kill = ALL_PERMS_MASK;
else if (COMPLAIN_MODE(profile))
perms->complain = ALL_PERMS_MASK;
-/*
- * TODO:
- * else if (PROMPT_MODE(profile))
- * perms->prompt = ALL_PERMS_MASK;
- */
-}
-
-static u32 map_other(u32 x)
-{
- return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
- ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
- ((x & 0x60) << 19); /* SETOPT/GETOPT */
-}
-
-void aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
- struct aa_perms *perms)
-{
- *perms = (struct aa_perms) {
- .allow = dfa_user_allow(dfa, state),
- .audit = dfa_user_audit(dfa, state),
- .quiet = dfa_user_quiet(dfa, state),
- };
-
- /* for v5 perm mapping in the policydb, the other set is used
- * to extend the general perm set
- */
- perms->allow |= map_other(dfa_other_allow(dfa, state));
- perms->audit |= map_other(dfa_other_audit(dfa, state));
- perms->quiet |= map_other(dfa_other_quiet(dfa, state));
-// perms->xindex = dfa_user_xindex(dfa, state);
-}
-
-/**
- * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms
- * @accum - perms struct to accumulate into
- * @addend - perms struct to add to @accum
- */
-void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend)
-{
- accum->deny |= addend->deny;
- accum->allow &= addend->allow & ~addend->deny;
- accum->audit |= addend->audit & addend->allow;
- accum->quiet &= addend->quiet & ~addend->allow;
- accum->kill |= addend->kill & ~addend->allow;
- accum->stop |= addend->stop & ~addend->allow;
- accum->complain |= addend->complain & ~addend->allow & ~addend->deny;
- accum->cond |= addend->cond & ~addend->allow & ~addend->deny;
- accum->hide &= addend->hide & ~addend->allow;
- accum->prompt |= addend->prompt & ~addend->allow & ~addend->deny;
+ else if (USER_MODE(profile))
+ perms->prompt = ALL_PERMS_MASK;
}
-/**
- * aa_perms_accum - accumulate perms, masking off overlapping perms
- * @accum - perms struct to accumulate into
- * @addend - perms struct to add to @accum
- */
-void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend)
-{
- accum->deny |= addend->deny;
- accum->allow &= addend->allow & ~accum->deny;
- accum->audit |= addend->audit & accum->allow;
- accum->quiet &= addend->quiet & ~accum->allow;
- accum->kill |= addend->kill & ~accum->allow;
- accum->stop |= addend->stop & ~accum->allow;
- accum->complain |= addend->complain & ~accum->allow & ~accum->deny;
- accum->cond |= addend->cond & ~accum->allow & ~accum->deny;
- accum->hide &= addend->hide & ~accum->allow;
- accum->prompt |= addend->prompt & ~accum->allow & ~accum->deny;
-}
-
-void aa_profile_match_label(struct aa_profile *profile, struct aa_label *label,
+void aa_profile_match_label(struct aa_profile *profile,
+ struct aa_ruleset *rules,
+ struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
{
/* TODO: doesn't yet handle extended types */
- unsigned int state;
+ aa_state_t state;
- state = aa_dfa_next(profile->policy.dfa,
- profile->policy.start[AA_CLASS_LABEL],
+ state = aa_dfa_next(rules->policy->dfa,
+ rules->policy->start[AA_CLASS_LABEL],
type);
- aa_label_match(profile, label, state, false, request, perms);
+ aa_label_match(profile, rules, label, state, false, request, perms);
}
-/* currently unused */
-int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
- u32 request, int type, u32 *deny,
- struct common_audit_data *sa)
-{
- struct aa_perms perms;
-
- aad(sa)->label = &profile->label;
- aad(sa)->peer = &target->label;
- aad(sa)->request = request;
-
- aa_profile_match_label(profile, &target->label, type, request, &perms);
- aa_apply_modes_to_perms(profile, &perms);
- *deny |= request & perms.deny;
- return aa_check_perms(profile, &perms, request, sa, aa_audit_perms_cb);
-}
-
/**
* aa_check_perms - do audit mode selection based on perms set
* @profile: profile being checked
* @perms: perms computed for the request
* @request: requested perms
- * @deny: Returns: explicit deny set
- * @sa: initialized audit structure (MAY BE NULL if not auditing)
+ * @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
@@ -427,7 +416,7 @@ int aa_profile_label_perm(struct aa_profile *profile, struct aa_profile *target,
* with a positive value.
*/
int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
- u32 request, struct common_audit_data *sa,
+ u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *))
{
int type, error;
@@ -436,7 +425,7 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
if (likely(!denied)) {
/* mask off perms that are not being force audited */
request &= perms->audit;
- if (!request || !sa)
+ if (!request || !ad)
return 0;
type = AUDIT_APPARMOR_AUDIT;
@@ -455,16 +444,16 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
error = -ENOENT;
denied &= ~perms->quiet;
- if (!sa || !denied)
+ if (!ad || !denied)
return error;
}
- if (sa) {
- aad(sa)->label = &profile->label;
- aad(sa)->request = request;
- aad(sa)->denied = denied;
- aad(sa)->error = error;
- aa_audit_msg(type, sa, cb);
+ if (ad) {
+ ad->subj_label = &profile->label;
+ ad->request = request;
+ ad->denied = denied;
+ ad->error = error;
+ aa_audit_msg(type, ad, cb);
}
if (type == AUDIT_APPARMOR_ALLOWED)
diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
index 4f0eecb67dde..a87cd60ed206 100644
--- a/security/apparmor/lsm.c
+++ b/security/apparmor/lsm.c
@@ -21,15 +21,18 @@
#include <linux/user_namespace.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
-#include <linux/zlib.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/cred.h"
+#include "include/crypto.h"
#include "include/file.h"
#include "include/ipc.h"
#include "include/net.h"
@@ -46,7 +49,13 @@ int apparmor_initialized;
union aa_buffer {
struct list_head list;
- char buffer[1];
+ DECLARE_FLEX_ARRAY(char, buffer);
+};
+
+struct aa_local_cache {
+ unsigned int hold;
+ unsigned int count;
+ struct list_head head;
};
#define RESERVE_COUNT 2
@@ -55,6 +64,7 @@ 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
@@ -103,7 +113,7 @@ static void apparmor_task_free(struct task_struct *task)
}
static int apparmor_task_alloc(struct task_struct *task,
- unsigned long clone_flags)
+ u64 clone_flags)
{
struct aa_task_ctx *new = task_ctx(task);
@@ -116,15 +126,18 @@ static int apparmor_ptrace_access_check(struct task_struct *child,
unsigned int mode)
{
struct aa_label *tracer, *tracee;
+ const struct cred *cred;
int error;
+ bool needput;
- tracer = __begin_current_label_crit_section();
- tracee = aa_get_task_label(child);
- error = aa_may_ptrace(tracer, tracee,
+ 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);
- aa_put_label(tracee);
- __end_current_label_crit_section(tracer);
+ __end_current_label_crit_section(tracer, needput);
+ put_cred(cred);
return error;
}
@@ -132,19 +145,23 @@ static int apparmor_ptrace_access_check(struct task_struct *child,
static int apparmor_ptrace_traceme(struct task_struct *parent)
{
struct aa_label *tracer, *tracee;
+ const struct cred *cred;
int error;
+ bool needput;
- tracee = __begin_current_label_crit_section();
- tracer = aa_get_task_label(parent);
- error = aa_may_ptrace(tracer, tracee, AA_PTRACE_TRACE);
- aa_put_label(tracer);
- __end_current_label_crit_section(tracee);
+ tracee = __begin_current_label_crit_section(&needput);
+ cred = get_task_cred(parent);
+ tracer = cred_label(cred); /* ref count on cred */
+ error = aa_may_ptrace(cred, tracer, current_cred(), tracee,
+ AA_PTRACE_TRACE);
+ put_cred(cred);
+ __end_current_label_crit_section(tracee, needput);
return error;
}
/* Derived from security/commoncap.c:cap_capget */
-static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
+static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effective,
kernel_cap_t *inheritable, kernel_cap_t *permitted)
{
struct aa_label *label;
@@ -163,12 +180,11 @@ static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
struct label_it i;
label_for_each_confined(i, label, profile) {
- if (COMPLAIN_MODE(profile))
- continue;
- *effective = cap_intersect(*effective,
- profile->caps.allow);
- *permitted = cap_intersect(*permitted,
- profile->caps.allow);
+ kernel_cap_t allowed;
+
+ allowed = aa_profile_capget(profile);
+ *effective = cap_intersect(*effective, allowed);
+ *permitted = cap_intersect(*permitted, allowed);
}
}
rcu_read_unlock();
@@ -185,7 +201,7 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
label = aa_get_newest_cred_label(cred);
if (!unconfined(label))
- error = aa_capable(label, cap, opts);
+ error = aa_capable(cred, label, cap, opts);
aa_put_label(label);
return error;
@@ -205,11 +221,13 @@ static int common_perm(const char *op, const struct path *path, u32 mask,
{
struct aa_label *label;
int error = 0;
+ bool needput;
- label = __begin_current_label_crit_section();
+ label = __begin_current_label_crit_section(&needput);
if (!unconfined(label))
- error = aa_path_perm(op, label, path, 0, mask, cond);
- __end_current_label_crit_section(label);
+ error = aa_path_perm(op, current_cred(), label, path, 0, mask,
+ cond);
+ __end_current_label_crit_section(label, needput);
return error;
}
@@ -224,9 +242,10 @@ static int common_perm(const char *op, const struct path *path, u32 mask,
*/
static int common_perm_cond(const char *op, const struct path *path, u32 mask)
{
- struct user_namespace *mnt_userns = mnt_user_ns(path->mnt);
+ vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt),
+ d_backing_inode(path->dentry));
struct path_cond cond = {
- i_uid_into_mnt(mnt_userns, d_backing_inode(path->dentry)),
+ vfsuid_into_kuid(vfsuid),
d_backing_inode(path->dentry)->i_mode
};
@@ -268,13 +287,14 @@ static int common_perm_rm(const char *op, const struct path *dir,
struct dentry *dentry, u32 mask)
{
struct inode *inode = d_backing_inode(dentry);
- struct user_namespace *mnt_userns = mnt_user_ns(dir->mnt);
struct path_cond cond = { };
+ vfsuid_t vfsuid;
if (!inode || !path_mediated_fs(dentry))
return 0;
- cond.uid = i_uid_into_mnt(mnt_userns, inode);
+ 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);
@@ -329,6 +349,11 @@ static int apparmor_path_truncate(const struct path *path)
return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR);
}
+static int apparmor_file_truncate(struct file *file)
+{
+ return apparmor_path_truncate(&file->f_path);
+}
+
static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry,
const char *old_name)
{
@@ -347,39 +372,67 @@ static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_
label = begin_current_label_crit_section();
if (!unconfined(label))
- error = aa_path_link(label, old_dentry, new_dir, new_dentry);
+ error = aa_path_link(current_cred(), label, old_dentry, new_dir,
+ new_dentry);
end_current_label_crit_section(label);
return error;
}
static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry,
- const struct path *new_dir, struct dentry *new_dentry)
+ const struct path *new_dir, struct dentry *new_dentry,
+ const unsigned int flags)
{
struct aa_label *label;
int error = 0;
if (!path_mediated_fs(old_dentry))
return 0;
+ if ((flags & RENAME_EXCHANGE) && !path_mediated_fs(new_dentry))
+ return 0;
label = begin_current_label_crit_section();
if (!unconfined(label)) {
- struct user_namespace *mnt_userns = mnt_user_ns(old_dir->mnt);
+ 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 = {
- i_uid_into_mnt(mnt_userns, d_backing_inode(old_dentry)),
- d_backing_inode(old_dentry)->i_mode
+ .mode = d_backing_inode(old_dentry)->i_mode
};
+ vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(old_dentry));
+ cond.uid = vfsuid_into_kuid(vfsuid);
+
+ if (flags & RENAME_EXCHANGE) {
+ struct path_cond cond_exchange = {
+ .mode = d_backing_inode(new_dentry)->i_mode,
+ };
+ vfsuid = i_uid_into_vfsuid(idmap, d_backing_inode(old_dentry));
+ cond_exchange.uid = vfsuid_into_kuid(vfsuid);
+
+ error = aa_path_perm(OP_RENAME_SRC, current_cred(),
+ label, &new_path, 0,
+ MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
+ AA_MAY_SETATTR | AA_MAY_DELETE,
+ &cond_exchange);
+ if (!error)
+ error = aa_path_perm(OP_RENAME_DEST, current_cred(),
+ label, &old_path,
+ 0, MAY_WRITE | AA_MAY_SETATTR |
+ AA_MAY_CREATE, &cond_exchange);
+ }
- error = aa_path_perm(OP_RENAME_SRC, label, &old_path, 0,
- MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
- AA_MAY_SETATTR | AA_MAY_DELETE,
- &cond);
if (!error)
- error = aa_path_perm(OP_RENAME_DEST, label, &new_path,
+ error = aa_path_perm(OP_RENAME_SRC, current_cred(),
+ label, &old_path, 0,
+ MAY_READ | AA_MAY_GETATTR | MAY_WRITE |
+ AA_MAY_SETATTR | AA_MAY_DELETE,
+ &cond);
+ if (!error)
+ error = aa_path_perm(OP_RENAME_DEST, current_cred(),
+ label, &new_path,
0, MAY_WRITE | AA_MAY_SETATTR |
AA_MAY_CREATE, &cond);
@@ -409,6 +462,7 @@ static int apparmor_file_open(struct file *file)
struct aa_file_ctx *fctx = file_ctx(file);
struct aa_label *label;
int error = 0;
+ bool needput;
if (!path_mediated_fs(file->f_path.dentry))
return 0;
@@ -417,27 +471,32 @@ static int apparmor_file_open(struct file *file)
* Cache permissions granted by the previous exec check, with
* implicit read and executable mmap which are required to
* actually execute the image.
+ *
+ * Illogically, FMODE_EXEC is in f_flags, not f_mode.
*/
- if (current->in_execve) {
+ if (file->f_flags & __FMODE_EXEC) {
fctx->allow = MAY_EXEC | MAY_READ | AA_EXEC_MMAP;
return 0;
}
- label = aa_get_newest_cred_label(file->f_cred);
+ label = aa_get_newest_cred_label_condref(file->f_cred, &needput);
if (!unconfined(label)) {
- struct user_namespace *mnt_userns = file_mnt_user_ns(file);
+ struct mnt_idmap *idmap = file_mnt_idmap(file);
struct inode *inode = file_inode(file);
+ vfsuid_t vfsuid;
struct path_cond cond = {
- i_uid_into_mnt(mnt_userns, inode),
- inode->i_mode
+ .mode = inode->i_mode,
};
+ vfsuid = i_uid_into_vfsuid(idmap, inode);
+ cond.uid = vfsuid_into_kuid(vfsuid);
- error = aa_path_perm(OP_OPEN, label, &file->f_path, 0,
+ error = aa_path_perm(OP_OPEN, file->f_cred,
+ label, &file->f_path, 0,
aa_map_file_to_perms(file), &cond);
/* todo cache full allowed permissions set and state */
fctx->allow = aa_map_file_to_perms(file);
}
- aa_put_label(label);
+ aa_put_label_condref(label, needput);
return error;
}
@@ -466,14 +525,15 @@ static int common_file_perm(const char *op, struct file *file, u32 mask,
{
struct aa_label *label;
int error = 0;
+ bool needput;
/* don't reaudit files closed during inheritance */
- if (file->f_path.dentry == aa_null.dentry)
+ if (unlikely(file->f_path.dentry == aa_null.dentry))
return -EACCES;
- label = __begin_current_label_crit_section();
- error = aa_file_perm(op, label, file, mask, in_atomic);
- __end_current_label_crit_section(label);
+ label = __begin_current_label_crit_section(&needput);
+ error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic);
+ __end_current_label_crit_section(label, needput);
return error;
}
@@ -535,11 +595,122 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
false);
}
+#ifdef CONFIG_IO_URING
+static const char *audit_uring_mask(u32 mask)
+{
+ if (mask & AA_MAY_CREATE_SQPOLL)
+ return "sqpoll";
+ if (mask & AA_MAY_OVERRIDE_CRED)
+ return "override_creds";
+ return "";
+}
+
+static void audit_uring_cb(struct audit_buffer *ab, void *va)
+{
+ struct apparmor_audit_data *ad = aad_of_va(va);
+
+ if (ad->request & AA_URING_PERM_MASK) {
+ audit_log_format(ab, " requested=\"%s\"",
+ audit_uring_mask(ad->request));
+ if (ad->denied & AA_URING_PERM_MASK) {
+ audit_log_format(ab, " denied=\"%s\"",
+ audit_uring_mask(ad->denied));
+ }
+ }
+ if (ad->uring.target) {
+ audit_log_format(ab, " tcontext=");
+ aa_label_xaudit(ab, labels_ns(ad->subj_label),
+ ad->uring.target,
+ FLAGS_NONE, GFP_ATOMIC);
+ }
+}
+
+static int profile_uring(struct aa_profile *profile, u32 request,
+ struct aa_label *new, int cap,
+ struct apparmor_audit_data *ad)
+{
+ unsigned int state;
+ struct aa_ruleset *rules;
+ int error = 0;
+
+ AA_BUG(!profile);
+
+ rules = profile->label.rules[0];
+ state = RULE_MEDIATES(rules, AA_CLASS_IO_URING);
+ if (state) {
+ struct aa_perms perms = { };
+
+ if (new) {
+ aa_label_match(profile, rules, new, state,
+ false, request, &perms);
+ } else {
+ perms = *aa_lookup_perms(rules->policy, state);
+ }
+ aa_apply_modes_to_perms(profile, &perms);
+ error = aa_check_perms(profile, &perms, request, ad,
+ audit_uring_cb);
+ }
+
+ return error;
+}
+
+/**
+ * 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)
@@ -547,22 +718,42 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,
flags &= ~AA_MS_IGNORE_MASK;
- label = __begin_current_label_crit_section();
+ label = __begin_current_label_crit_section(&needput);
if (!unconfined(label)) {
if (flags & MS_REMOUNT)
- error = aa_remount(label, path, flags, data);
+ error = aa_remount(current_cred(), label, path, flags,
+ data);
else if (flags & MS_BIND)
- error = aa_bind_mount(label, path, dev_name, flags);
+ 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(label, path, flags);
+ error = aa_mount_change_type(current_cred(), label,
+ path, flags);
else if (flags & MS_MOVE)
- error = aa_move_mount(label, path, dev_name);
+ error = aa_move_mount_old(current_cred(), label, path,
+ dev_name);
else
- error = aa_new_mount(label, dev_name, path, type,
- flags, data);
+ error = aa_new_mount(current_cred(), label, dev_name,
+ path, type, flags, data);
}
- __end_current_label_crit_section(label);
+ __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;
}
@@ -571,11 +762,12 @@ 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();
+ label = __begin_current_label_crit_section(&needput);
if (!unconfined(label))
- error = aa_umount(label, mnt, flags);
- __end_current_label_crit_section(label);
+ error = aa_umount(current_cred(), label, mnt, flags);
+ __end_current_label_crit_section(label, needput);
return error;
}
@@ -588,13 +780,53 @@ static int apparmor_sb_pivotroot(const struct path *old_path,
label = aa_get_current_label();
if (!unconfined(label))
- error = aa_pivotroot(label, old_path, new_path);
+ error = aa_pivotroot(current_cred(), label, old_path, new_path);
aa_put_label(label);
return error;
}
-static int apparmor_getprocattr(struct task_struct *task, char *name,
+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;
@@ -613,7 +845,7 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
error = -EINVAL;
if (label)
- error = aa_getprocattr(label, value);
+ error = aa_getprocattr(label, value, true);
aa_put_label(label);
put_cred(cred);
@@ -621,13 +853,13 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
return error;
}
-static int apparmor_setprocattr(const char *name, void *value,
- size_t size)
+static int do_setattr(u64 attr, void *value, size_t size)
{
char *command, *largs = NULL, *args = value;
size_t arg_size;
int error;
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE,
+ OP_SETPROCATTR);
if (size == 0)
return -EINVAL;
@@ -652,7 +884,7 @@ static int apparmor_setprocattr(const char *name, void *value,
goto out;
arg_size = size - (args - (largs ? largs : (char *) value));
- if (strcmp(name, "current") == 0) {
+ if (attr == LSM_ATTR_CURRENT) {
if (strcmp(command, "changehat") == 0) {
error = aa_setprocattr_changehat(args, arg_size,
AA_CHANGE_NOFLAGS);
@@ -667,7 +899,7 @@ static int apparmor_setprocattr(const char *name, void *value,
error = aa_change_profile(args, AA_CHANGE_STACK);
} else
goto fail;
- } else if (strcmp(name, "exec") == 0) {
+ } else if (attr == LSM_ATTR_EXEC) {
if (strcmp(command, "exec") == 0)
error = aa_change_profile(args, AA_CHANGE_ONEXEC);
else if (strcmp(command, "stack") == 0)
@@ -686,19 +918,48 @@ out:
return error;
fail:
- aad(&sa)->label = begin_current_label_crit_section();
- aad(&sa)->info = name;
- aad(&sa)->error = error = -EINVAL;
- aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
- end_current_label_crit_section(aad(&sa)->label);
+ ad.subj_label = begin_current_label_crit_section();
+ if (attr == LSM_ATTR_CURRENT)
+ ad.info = "current";
+ else if (attr == LSM_ATTR_EXEC)
+ ad.info = "exec";
+ else
+ ad.info = "invalid";
+ ad.error = error = -EINVAL;
+ aa_audit_msg(AUDIT_APPARMOR_DENIED, &ad, NULL);
+ end_current_label_crit_section(ad.subj_label);
goto out;
}
+static int apparmor_setselfattr(unsigned int attr, struct lsm_ctx *ctx,
+ u32 size, u32 flags)
+{
+ int rc;
+
+ if (attr != LSM_ATTR_CURRENT && attr != LSM_ATTR_EXEC)
+ return -EOPNOTSUPP;
+
+ rc = do_setattr(attr, ctx->ctx, ctx->ctx_len);
+ if (rc > 0)
+ return 0;
+ return rc;
+}
+
+static int apparmor_setprocattr(const char *name, void *value,
+ size_t size)
+{
+ int attr = lsm_name_to_attr(name);
+
+ if (attr)
+ return do_setattr(attr, value, size);
+ return -EINVAL;
+}
+
/**
* apparmor_bprm_committing_creds - do task cleanup on committing new creds
* @bprm: binprm for the exec (NOT NULL)
*/
-static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
+static void apparmor_bprm_committing_creds(const struct linux_binprm *bprm)
{
struct aa_label *label = aa_current_raw_label();
struct aa_label *new_label = cred_label(bprm->cred);
@@ -717,10 +978,10 @@ static void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
}
/**
- * apparmor_bprm_committed_cred - do cleanup after new creds committed
+ * apparmor_bprm_committed_creds() - do cleanup after new creds committed
* @bprm: binprm for the exec (NOT NULL)
*/
-static void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm)
{
/* clear out temporary/transitional state from the context */
aa_clear_task_ctx_trans(task_ctx(current));
@@ -728,29 +989,38 @@ static void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
return;
}
-static void apparmor_current_getsecid_subj(u32 *secid)
+static void apparmor_current_getlsmprop_subj(struct lsm_prop *prop)
{
- struct aa_label *label = aa_get_current_label();
- *secid = label->secid;
- aa_put_label(label);
+ 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_getsecid_obj(struct task_struct *p, u32 *secid)
+static void apparmor_task_getlsmprop_obj(struct task_struct *p,
+ struct lsm_prop *prop)
{
struct aa_label *label = aa_get_task_label(p);
- *secid = label->secid;
+
+ prop->apparmor.label = label;
aa_put_label(label);
}
static int apparmor_task_setrlimit(struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
- struct aa_label *label = __begin_current_label_crit_section();
+ struct aa_label *label;
int error = 0;
+ bool needput;
+
+ label = __begin_current_label_crit_section(&needput);
if (!unconfined(label))
- error = aa_task_setrlimit(label, task, resource, new_rlim);
- __end_current_label_crit_section(label);
+ error = aa_task_setrlimit(current_cred(), label, task,
+ resource, new_rlim);
+ __end_current_label_crit_section(label, needput);
return error;
}
@@ -758,80 +1028,237 @@ static int apparmor_task_setrlimit(struct task_struct *task,
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);
- tl = aa_get_task_label(target);
- error = aa_may_signal(cl, tl, sig);
+ error = aa_may_signal(cred, cl, tc, tl, sig);
aa_put_label(cl);
- aa_put_label(tl);
- return error;
+ } 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);
}
-
- cl = __begin_current_label_crit_section();
- tl = aa_get_task_label(target);
- error = aa_may_signal(cl, tl, sig);
aa_put_label(tl);
- __end_current_label_crit_section(cl);
+ put_cred(tc);
return error;
}
-/**
- * apparmor_sk_alloc_security - allocate and attach the sk_security field
- */
-static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t flags)
+static int apparmor_userns_create(const struct cred *cred)
{
- struct aa_sk_ctx *ctx;
+ 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);
- ctx = kzalloc(sizeof(*ctx), flags);
- if (!ctx)
- return -ENOMEM;
+ ad.subj_cred = current_cred();
- SK_CTX(sk) = ctx;
+ 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;
}
-/**
- * apparmor_sk_free_security - free the sk_security field
- */
static void apparmor_sk_free_security(struct sock *sk)
{
- struct aa_sk_ctx *ctx = SK_CTX(sk);
+ struct aa_sk_ctx *ctx = aa_sock(sk);
- SK_CTX(sk) = NULL;
- aa_put_label(ctx->label);
- aa_put_label(ctx->peer);
- kfree(ctx);
+ /* 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_clone_security - clone the sk_security field
+ * 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 = SK_CTX(sk);
- struct aa_sk_ctx *new = SK_CTX(newsk);
+ struct aa_sk_ctx *ctx = aa_sock(sk);
+ struct aa_sk_ctx *new = aa_sock(newsk);
- if (new->label)
- aa_put_label(new->label);
- new->label = aa_get_label(ctx->label);
+ /* 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 (new->peer)
- aa_put_label(new->peer);
- new->peer = aa_get_label(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_socket_create - check perms before creating a new socket
+ * 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;
@@ -839,12 +1266,19 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern)
AA_BUG(in_interrupt());
+ if (kern)
+ return 0;
+
label = begin_current_label_crit_section();
- if (!(kern || unconfined(label)))
- error = af_select(family,
- create_perm(label, family, type, protocol),
- aa_af_perm(label, OP_CREATE, AA_MAY_CREATE,
- family, type, protocol));
+ 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;
@@ -852,10 +1286,14 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern)
/**
* 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 currently labeled unconfined but we may want to
- * move to a special kernel label
+ * - 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.
@@ -866,26 +1304,64 @@ static int apparmor_socket_post_create(struct socket *sock, int family,
struct aa_label *label;
if (kern) {
- struct aa_ns *ns = aa_get_current_ns();
-
- label = aa_get_label(ns_unconfined(ns));
- aa_put_ns(ns);
+ label = aa_get_label(kernel_t);
} else
label = aa_get_current_label();
if (sock->sk) {
- struct aa_sk_ctx *ctx = SK_CTX(sock->sk);
+ struct aa_sk_ctx *ctx = aa_sock(sock->sk);
- aa_put_label(ctx->label);
- ctx->label = aa_get_label(label);
+ /* still not live */
+ aa_put_label(rcu_dereference_protected(ctx->label, true));
+ rcu_assign_pointer(ctx->label, aa_get_label(label));
}
aa_put_label(label);
return 0;
}
+static int apparmor_socket_socketpair(struct socket *socka,
+ struct socket *sockb)
+{
+ struct aa_sk_ctx *a_ctx = aa_sock(socka->sk);
+ struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk);
+ struct aa_label *label;
+
+ /* socks not live yet - initial values set in sk_alloc */
+ label = begin_current_label_crit_section();
+ if (rcu_access_pointer(a_ctx->label) != label) {
+ AA_BUG("a_ctx != label");
+ aa_put_label(rcu_dereference_protected(a_ctx->label, true));
+ rcu_assign_pointer(a_ctx->label, aa_get_label(label));
+ }
+ if (rcu_access_pointer(b_ctx->label) != label) {
+ AA_BUG("b_ctx != label");
+ aa_put_label(rcu_dereference_protected(b_ctx->label, true));
+ rcu_assign_pointer(b_ctx->label, aa_get_label(label));
+ }
+
+ if (socka->sk->sk_family == PF_UNIX) {
+ /* unix socket pairs by-pass unix_stream_connect */
+ unix_connect_peers(a_ctx, b_ctx);
+ }
+ end_current_label_crit_section(label);
+
+ return 0;
+}
+
/**
* apparmor_socket_bind - check perms before bind addr to socket
+ * @sock: socket to bind the address to (must be non-NULL)
+ * @address: address that is being bound (must be non-NULL)
+ * @addrlen: length of @address
+ *
+ * Performs security checks before allowing a socket to bind to an address.
+ * Handles Unix domain sockets specially through aa_unix_bind_perm().
+ * For other socket families, uses generic permission check via aa_sk_perm().
+ *
+ * Return:
+ * 0 if binding is permitted
+ * error code on denial or invalid parameters
*/
static int apparmor_socket_bind(struct socket *sock,
struct sockaddr *address, int addrlen)
@@ -895,14 +1371,11 @@ static int apparmor_socket_bind(struct socket *sock,
AA_BUG(!address);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- bind_perm(sock, address, addrlen),
- aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk));
+ 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);
}
-/**
- * apparmor_socket_connect - check perms before connecting @sock to @address
- */
static int apparmor_socket_connect(struct socket *sock,
struct sockaddr *address, int addrlen)
{
@@ -911,28 +1384,24 @@ static int apparmor_socket_connect(struct socket *sock,
AA_BUG(!address);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- connect_perm(sock, address, addrlen),
- aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk));
+ /* 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);
}
-/**
- * apparmor_socket_list - check perms before allowing listen
- */
static int apparmor_socket_listen(struct socket *sock, int backlog)
{
AA_BUG(!sock);
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- listen_perm(sock, backlog),
- aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk));
+ 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);
}
-/**
- * apparmor_socket_accept - check perms before accepting a new connection.
- *
+/*
* Note: while @newsock is created and has some information, the accept
* has not been done.
*/
@@ -943,9 +1412,9 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
AA_BUG(!newsock);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- accept_perm(sock, newsock),
- aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk));
+ 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,
@@ -956,23 +1425,18 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(!msg);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- msg_perm(op, request, sock, msg, size),
- aa_sk_perm(op, request, sock->sk));
+ /* PF_UNIX goes through unix_may_send */
+ if (sock->sk->sk_family == PF_UNIX)
+ return 0;
+ return aa_sk_perm(op, request, sock->sk);
}
-/**
- * apparmor_socket_sendmsg - check perms before sending msg to another socket
- */
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);
}
-/**
- * apparmor_socket_recvmsg - check perms before receiving a message
- */
static int apparmor_socket_recvmsg(struct socket *sock,
struct msghdr *msg, int size, int flags)
{
@@ -986,22 +1450,16 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock)
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- sock_perm(op, request, sock),
- aa_sk_perm(op, request, sock->sk));
+ if (sock->sk->sk_family == PF_UNIX)
+ return aa_unix_sock_perm(op, request, sock);
+ return aa_sk_perm(op, request, sock->sk);
}
-/**
- * apparmor_socket_getsockname - check perms before getting the local address
- */
static int apparmor_socket_getsockname(struct socket *sock)
{
return aa_sock_perm(OP_GETSOCKNAME, AA_MAY_GETATTR, sock);
}
-/**
- * apparmor_socket_getpeername - check perms before getting remote address
- */
static int apparmor_socket_getpeername(struct socket *sock)
{
return aa_sock_perm(OP_GETPEERNAME, AA_MAY_GETATTR, sock);
@@ -1015,14 +1473,11 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock,
AA_BUG(!sock->sk);
AA_BUG(in_interrupt());
- return af_select(sock->sk->sk_family,
- opt_perm(op, request, sock, level, optname),
- aa_sk_perm(op, request, sock->sk));
+ 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);
}
-/**
- * apparmor_getsockopt - check perms before getting socket options
- */
static int apparmor_socket_getsockopt(struct socket *sock, int level,
int optname)
{
@@ -1030,9 +1485,6 @@ static int apparmor_socket_getsockopt(struct socket *sock, int level,
level, optname);
}
-/**
- * apparmor_setsockopt - check perms before setting socket options
- */
static int apparmor_socket_setsockopt(struct socket *sock, int level,
int optname)
{
@@ -1040,9 +1492,6 @@ static int apparmor_socket_setsockopt(struct socket *sock, int level,
level, optname);
}
-/**
- * apparmor_socket_shutdown - check perms before shutting down @sock conn
- */
static int apparmor_socket_shutdown(struct socket *sock, int how)
{
return aa_sock_perm(OP_SHUTDOWN, AA_MAY_SHUTDOWN, sock);
@@ -1050,7 +1499,9 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
#ifdef CONFIG_NETWORK_SECMARK
/**
- * apparmor_socket_sock_recv_skb - check perms before associating skb to sk
+ * 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
*
@@ -1059,71 +1510,92 @@ static int apparmor_socket_shutdown(struct socket *sock, int how)
*/
static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
- struct aa_sk_ctx *ctx = SK_CTX(sk);
+ struct aa_sk_ctx *ctx = aa_sock(sk);
+ int error;
if (!skb->secmark)
return 0;
- return apparmor_secmark_check(ctx->label, OP_RECVMSG, AA_MAY_RECEIVE,
- skb->secmark, sk);
+ /*
+ * 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_label(struct sock *sk)
+static struct aa_label *sk_peer_get_label(struct sock *sk)
{
- struct aa_sk_ctx *ctx = SK_CTX(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 (ctx->peer)
- return ctx->peer;
+ if (sk->sk_family != PF_UNIX)
+ return ERR_PTR(-ENOPROTOOPT);
- 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,
- char __user *optval,
- int __user *optlen,
+ sockptr_t optval, sockptr_t optlen,
unsigned int len)
{
- char *name;
+ char *name = NULL;
int slen, error = 0;
struct aa_label *label;
struct aa_label *peer;
- label = begin_current_label_crit_section();
- peer = sk_peer_label(sock->sk);
+ 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;
- } else {
- if (slen > len) {
- error = -ERANGE;
- } else if (copy_to_user(optval, name, slen)) {
- error = -EFAULT;
- goto out;
- }
- if (put_user(slen, optlen))
- error = -EFAULT;
-out:
- kfree(name);
-
+ goto done_put;
+ }
+ if (slen > len) {
+ error = -ERANGE;
+ goto done_len;
}
-done:
- end_current_label_crit_section(label);
+ 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;
}
@@ -1156,41 +1628,54 @@ static int apparmor_socket_getpeersec_dgram(struct socket *sock,
*/
static void apparmor_sock_graft(struct sock *sk, struct socket *parent)
{
- struct aa_sk_ctx *ctx = SK_CTX(sk);
+ struct aa_sk_ctx *ctx = aa_sock(sk);
- if (!ctx->label)
- ctx->label = aa_get_current_label();
+ /* 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 = SK_CTX(sk);
+ struct aa_sk_ctx *ctx = aa_sock(sk);
+ int error;
if (!skb->secmark)
return 0;
- return apparmor_secmark_check(ctx->label, OP_CONNECT, AA_MAY_CONNECT,
- skb->secmark, sk);
+ 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_task_ctx.
+ * The cred blob is a pointer to, not an instance of, an aa_label.
*/
-struct lsm_blob_sizes apparmor_blob_sizes __lsm_ro_after_init = {
- .lbs_cred = sizeof(struct aa_task_ctx *),
+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[] __lsm_ro_after_init = {
+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),
@@ -1215,7 +1700,10 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(mmap_file, apparmor_mmap_file),
LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect),
LSM_HOOK_INIT(file_lock, apparmor_file_lock),
+ LSM_HOOK_INIT(file_truncate, apparmor_file_truncate),
+ LSM_HOOK_INIT(getselfattr, apparmor_getselfattr),
+ LSM_HOOK_INIT(setselfattr, apparmor_setselfattr),
LSM_HOOK_INIT(getprocattr, apparmor_getprocattr),
LSM_HOOK_INIT(setprocattr, apparmor_setprocattr),
@@ -1223,8 +1711,12 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
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),
@@ -1259,10 +1751,12 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(task_free, apparmor_task_free),
LSM_HOOK_INIT(task_alloc, apparmor_task_alloc),
- LSM_HOOK_INIT(current_getsecid_subj, apparmor_current_getsecid_subj),
- LSM_HOOK_INIT(task_getsecid_obj, apparmor_task_getsecid_obj),
+ 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),
@@ -1272,8 +1766,14 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = {
#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
};
/*
@@ -1316,6 +1816,9 @@ static const struct kernel_param_ops param_ops_aalockpolicy = {
.get = param_get_aalockpolicy
};
+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_audit(const char *val, const struct kernel_param *kp);
static int param_get_audit(char *buffer, const struct kernel_param *kp);
@@ -1337,14 +1840,21 @@ bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT);
module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUSR | S_IWUSR);
#endif
+/* whether policy exactly as loaded is retained for debug and checkpointing */
+bool aa_g_export_binary = IS_ENABLED(CONFIG_SECURITY_APPARMOR_EXPORT_BINARY);
+#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY
+module_param_named(export_binary, aa_g_export_binary, aabool, 0600);
+#endif
+
/* policy loaddata compression level */
-int aa_g_rawdata_compression_level = Z_DEFAULT_COMPRESSION;
+int aa_g_rawdata_compression_level = AA_DEFAULT_CLEVEL;
module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level,
aacompressionlevel, 0400);
/* Debug mode */
-bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES);
-module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
+int aa_g_debug;
+module_param_call(debug, param_set_debug, param_get_debug,
+ &aa_g_debug, 0600);
/* Audit mode */
enum audit_mode aa_g_audit;
@@ -1379,7 +1889,7 @@ module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR);
* DEPRECATED: read only as strict checking of load is always done now
* that none root users (user namespaces) can load policy.
*/
-bool aa_g_paranoid_load = true;
+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);
@@ -1390,7 +1900,7 @@ static const struct kernel_param_ops param_ops_aaintbool = {
.get = param_get_aaintbool
};
/* Boot time disable flag */
-static int apparmor_enabled __lsm_ro_after_init = 1;
+static int apparmor_enabled __ro_after_init = 1;
module_param_named(enabled, apparmor_enabled, aaintbool, 0444);
static int __init apparmor_enabled_setup(char *str)
@@ -1520,9 +2030,8 @@ static int param_set_aacompressionlevel(const char *val,
error = param_set_int(val, kp);
aa_g_rawdata_compression_level = clamp(aa_g_rawdata_compression_level,
- Z_NO_COMPRESSION,
- Z_BEST_COMPRESSION);
- pr_info("AppArmor: policy rawdata compression level set to %u\n",
+ AA_MIN_CLEVEL, AA_MAX_CLEVEL);
+ pr_info("AppArmor: policy rawdata compression level set to %d\n",
aa_g_rawdata_compression_level);
return error;
@@ -1538,6 +2047,34 @@ static int param_get_aacompressionlevel(char *buffer,
return param_get_int(buffer, kp);
}
+static int param_get_debug(char *buffer, const struct kernel_param *kp)
+{
+ if (!apparmor_enabled)
+ return -EINVAL;
+ if (apparmor_initialized && !aa_current_policy_view_capable(NULL))
+ return -EPERM;
+ return aa_print_debug_params(buffer);
+}
+
+static int param_set_debug(const char *val, const struct kernel_param *kp)
+{
+ int i;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+ if (!val)
+ return -EINVAL;
+ if (apparmor_initialized && !aa_current_policy_admin_capable(NULL))
+ return -EPERM;
+
+ i = aa_parse_debug_params(val);
+ if (i == DEBUG_PARSE_ERROR)
+ return -EINVAL;
+
+ aa_g_debug = i;
+ return 0;
+}
+
static int param_get_audit(char *buffer, const struct kernel_param *kp)
{
if (!apparmor_enabled)
@@ -1599,11 +2136,32 @@ static int param_set_mode(const char *val, const struct kernel_param *kp)
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:
- spin_lock(&aa_buffers_lock);
if (buffer_count > reserve_count ||
(in_atomic && !list_empty(&aa_global_buffers))) {
aa_buf = list_first_entry(&aa_global_buffers, union aa_buffer,
@@ -1611,7 +2169,7 @@ retry:
list_del(&aa_buf->list);
buffer_count--;
spin_unlock(&aa_buffers_lock);
- return &aa_buf->buffer[0];
+ return aa_buf->buffer;
}
if (in_atomic) {
/*
@@ -1629,26 +2187,46 @@ retry:
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[0];
+ return aa_buf->buffer;
}
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]);
- spin_lock(&aa_buffers_lock);
- list_add(&aa_buf->list, &aa_global_buffers);
- buffer_count++;
- spin_unlock(&aa_buffers_lock);
+ 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);
}
/*
@@ -1691,12 +2269,21 @@ static int __init alloc_buffers(void)
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 extremly high.
+ * disabled early at boot if aa_g_path_max is extremely high.
*/
if (num_online_cpus() > 1)
num = 4 + RESERVE_COUNT;
@@ -1711,13 +2298,13 @@ static int __init alloc_buffers(void)
destroy_buffers();
return -ENOMEM;
}
- aa_put_buffer(&aa_buf->buffer[0]);
+ aa_put_buffer(aa_buf->buffer);
}
return 0;
}
#ifdef CONFIG_SYSCTL
-static int apparmor_dointvec(struct ctl_table *table, int write,
+static int apparmor_dointvec(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
if (!aa_current_policy_admin_capable(NULL))
@@ -1728,12 +2315,8 @@ static int apparmor_dointvec(struct ctl_table *table, int write,
return proc_dointvec(table, write, buffer, lenp, ppos);
}
-static struct ctl_path apparmor_sysctl_path[] = {
- { .procname = "kernel", },
- { }
-};
-
-static struct ctl_table apparmor_sysctl_table[] = {
+static const struct ctl_table apparmor_sysctl_table[] = {
+#ifdef CONFIG_USER_NS
{
.procname = "unprivileged_userns_apparmor_policy",
.data = &unprivileged_userns_apparmor_policy,
@@ -1741,13 +2324,26 @@ static struct ctl_table apparmor_sysctl_table[] = {
.mode = 0600,
.proc_handler = apparmor_dointvec,
},
- { }
+#endif /* CONFIG_USER_NS */
+ {
+ .procname = "apparmor_display_secid_mode",
+ .data = &apparmor_display_secid_mode,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = apparmor_dointvec,
+ },
+ {
+ .procname = "apparmor_restrict_unprivileged_unconfined",
+ .data = &aa_unprivileged_unconfined_restricted,
+ .maxlen = sizeof(int),
+ .mode = 0600,
+ .proc_handler = apparmor_dointvec,
+ },
};
static int __init apparmor_init_sysctl(void)
{
- return register_sysctl_paths(apparmor_sysctl_path,
- apparmor_sysctl_table) ? 0 : -ENOMEM;
+ return register_sysctl("kernel", apparmor_sysctl_table) ? 0 : -ENOMEM;
}
#else
static inline int apparmor_init_sysctl(void)
@@ -1763,6 +2359,7 @@ static unsigned int apparmor_ip_postroute(void *priv,
{
struct aa_sk_ctx *ctx;
struct sock *sk;
+ int error;
if (!skb->secmark)
return NF_ACCEPT;
@@ -1771,9 +2368,12 @@ static unsigned int apparmor_ip_postroute(void *priv,
if (sk == NULL)
return NF_ACCEPT;
- ctx = SK_CTX(sk);
- if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND,
- skb->secmark, sk))
+ 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);
@@ -1799,11 +2399,8 @@ static const struct nf_hook_ops apparmor_nf_ops[] = {
static int __net_init apparmor_nf_register(struct net *net)
{
- int ret;
-
- ret = nf_register_net_hooks(net, apparmor_nf_ops,
+ return nf_register_net_hooks(net, apparmor_nf_ops,
ARRAY_SIZE(apparmor_nf_ops));
- return ret;
}
static void __net_exit apparmor_nf_unregister(struct net *net)
@@ -1830,15 +2427,75 @@ static int __init apparmor_nf_ip_init(void)
return 0;
}
-__initcall(apparmor_nf_ip_init);
#endif
+static char nulldfa_src[] __aligned(8) = {
+ #include "nulldfa.in"
+};
+static struct aa_dfa *nulldfa;
+
+static char stacksplitdfa_src[] __aligned(8) = {
+ #include "stacksplitdfa.in"
+};
+struct aa_dfa *stacksplitdfa;
+struct aa_policydb *nullpdb;
+
+static int __init aa_setup_dfa_engine(void)
+{
+ int error = -ENOMEM;
+
+ nullpdb = aa_alloc_pdb(GFP_KERNEL);
+ if (!nullpdb)
+ return -ENOMEM;
+
+ nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src),
+ TO_ACCEPT1_FLAG(YYTD_DATA32) |
+ TO_ACCEPT2_FLAG(YYTD_DATA32));
+ if (IS_ERR(nulldfa)) {
+ error = PTR_ERR(nulldfa);
+ goto fail;
+ }
+ nullpdb->dfa = aa_get_dfa(nulldfa);
+ nullpdb->perms = kcalloc(2, sizeof(struct aa_perms), GFP_KERNEL);
+ if (!nullpdb->perms)
+ goto fail;
+ nullpdb->size = 2;
+
+ stacksplitdfa = aa_dfa_unpack(stacksplitdfa_src,
+ sizeof(stacksplitdfa_src),
+ TO_ACCEPT1_FLAG(YYTD_DATA32) |
+ TO_ACCEPT2_FLAG(YYTD_DATA32));
+ if (IS_ERR(stacksplitdfa)) {
+ error = PTR_ERR(stacksplitdfa);
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ aa_put_pdb(nullpdb);
+ aa_put_dfa(nulldfa);
+ nullpdb = NULL;
+ nulldfa = NULL;
+ stacksplitdfa = NULL;
+
+ return error;
+}
+
+static void __init aa_teardown_dfa_engine(void)
+{
+ aa_put_dfa(stacksplitdfa);
+ aa_put_dfa(nulldfa);
+ aa_put_pdb(nullpdb);
+ nullpdb = NULL;
+ stacksplitdfa = NULL;
+ nulldfa = NULL;
+}
+
static int __init apparmor_init(void)
{
int error;
- aa_secids_init();
-
error = aa_setup_dfa_engine();
if (error) {
AA_ERROR("Unable to setup dfa engine\n");
@@ -1871,7 +2528,10 @@ static int __init apparmor_init(void)
goto buffers_out;
}
security_add_hooks(apparmor_hooks, ARRAY_SIZE(apparmor_hooks),
- "apparmor");
+ &apparmor_lsmid);
+
+ /* Inform the audit system that secctx is used */
+ audit_cfg_lsm(&apparmor_lsmid, AUDIT_CFG_LSM_SECCTX_SUBJECT);
/* Report that AppArmor successfully initialized */
apparmor_initialized = 1;
@@ -1895,9 +2555,16 @@ alloc_out:
}
DEFINE_LSM(apparmor) = {
- .name = "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 3e9e1eaf990e..c5a91600842a 100644
--- a/security/apparmor/match.c
+++ b/security/apparmor/match.c
@@ -21,50 +21,6 @@
#define base_idx(X) ((X) & 0xffffff)
-static char nulldfa_src[] = {
- #include "nulldfa.in"
-};
-struct aa_dfa *nulldfa;
-
-static char stacksplitdfa_src[] = {
- #include "stacksplitdfa.in"
-};
-struct aa_dfa *stacksplitdfa;
-
-int aa_setup_dfa_engine(void)
-{
- int error;
-
- nulldfa = aa_dfa_unpack(nulldfa_src, sizeof(nulldfa_src),
- TO_ACCEPT1_FLAG(YYTD_DATA32) |
- TO_ACCEPT2_FLAG(YYTD_DATA32));
- if (IS_ERR(nulldfa)) {
- error = PTR_ERR(nulldfa);
- nulldfa = NULL;
- return error;
- }
-
- stacksplitdfa = aa_dfa_unpack(stacksplitdfa_src,
- sizeof(stacksplitdfa_src),
- TO_ACCEPT1_FLAG(YYTD_DATA32) |
- TO_ACCEPT2_FLAG(YYTD_DATA32));
- if (IS_ERR(stacksplitdfa)) {
- aa_put_dfa(nulldfa);
- nulldfa = NULL;
- error = PTR_ERR(stacksplitdfa);
- stacksplitdfa = NULL;
- return error;
- }
-
- return 0;
-}
-
-void aa_teardown_dfa_engine(void)
-{
- aa_put_dfa(stacksplitdfa);
- aa_put_dfa(nulldfa);
-}
-
/**
* unpack_table - unpack a dfa table (one of accept, default, base, next check)
* @blob: data to unpack (NOT NULL)
@@ -136,7 +92,7 @@ fail:
/**
* verify_table_headers - verify that the tables headers are as expected
- * @tables - array of dfa tables to check (NOT NULL)
+ * @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
@@ -283,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)
{
@@ -291,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)
@@ -370,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)
@@ -386,6 +380,23 @@ 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_table_headers(dfa->tables, flags);
@@ -436,17 +447,17 @@ do { \
*
* 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;
+ 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]) {
@@ -476,17 +487,16 @@ unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
*
* 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_match(struct aa_dfa *dfa, aa_state_t start, const char *str)
{
- u16 *def = DEFAULT_TABLE(dfa);
+ u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
- u16 *next = NEXT_TABLE(dfa);
- u16 *check = CHECK_TABLE(dfa);
- unsigned int state = start;
+ 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]) {
@@ -515,13 +525,12 @@ unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
*
* Returns: state reach after input @c
*/
-unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
- const char c)
+aa_state_t aa_dfa_next(struct aa_dfa *dfa, aa_state_t state, const char c)
{
- 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);
+ u32 *next = NEXT_TABLE(dfa);
+ u32 *check = CHECK_TABLE(dfa);
/* current state is <state>, matching character *str */
if (dfa->tables[YYTD_ID_EC]) {
@@ -534,12 +543,12 @@ unsigned int aa_dfa_next(struct aa_dfa *dfa, unsigned int state,
return state;
}
-unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state)
+aa_state_t aa_dfa_outofband_transition(struct aa_dfa *dfa, aa_state_t state)
{
- 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);
+ u32 *next = NEXT_TABLE(dfa);
+ u32 *check = CHECK_TABLE(dfa);
u32 b = (base)[(state)];
if (!(b & MATCH_FLAG_OOB_TRANSITION))
@@ -564,18 +573,18 @@ unsigned int aa_dfa_outofband_transition(struct aa_dfa *dfa, unsigned int state)
*
* Returns: final state reached after input is consumed
*/
-unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
+aa_state_t aa_dfa_match_until(struct aa_dfa *dfa, aa_state_t start,
const char *str, 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);
+ u32 *next = NEXT_TABLE(dfa);
+ u32 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
- unsigned int state = start, pos;
+ aa_state_t state = start, pos;
- 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]) {
@@ -625,19 +634,19 @@ unsigned int aa_dfa_match_until(struct aa_dfa *dfa, unsigned int start,
*
* Returns: final state reached after input is consumed
*/
-unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
+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);
+ u32 *next = NEXT_TABLE(dfa);
+ u32 *check = CHECK_TABLE(dfa);
u32 *accept = ACCEPT_TABLE(dfa);
- unsigned int state = start, pos;
+ aa_state_t state = start, pos;
*retpos = NULL;
- 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]) {
@@ -670,45 +679,46 @@ unsigned int aa_dfa_matchn_until(struct aa_dfa *dfa, unsigned int start,
return state;
}
-#define inc_wb_pos(wb) \
-do { \
+#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 - 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 */
-static bool is_loop(struct match_workbuf *wb, unsigned int state,
+/* adjust is only set if is_loop returns true */
+static bool is_loop(struct match_workbuf *wb, aa_state_t state,
unsigned int *adjust)
{
- unsigned int pos = wb->pos;
- unsigned int i;
+ int pos = wb->pos;
+ int i;
if (wb->history[pos] < state)
return false;
- for (i = 0; i <= wb->len; i++) {
+ for (i = 0; i < wb->len; i++) {
if (wb->history[pos] == state) {
*adjust = i;
return true;
}
- if (pos == 0)
- pos = WB_HISTORY_SIZE;
- pos--;
+ /* -1 wraps to WB_HISTORY_SIZE - 1 */
+ pos = (pos - 1) & (WB_HISTORY_SIZE - 1);
}
- *adjust = i;
- return true;
+ return false;
}
-static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
+static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start,
const char *str, struct match_workbuf *wb,
unsigned int *count)
{
- u16 *def = DEFAULT_TABLE(dfa);
+ u32 *def = DEFAULT_TABLE(dfa);
u32 *base = BASE_TABLE(dfa);
- u16 *next = NEXT_TABLE(dfa);
- u16 *check = CHECK_TABLE(dfa);
- unsigned int state = start, pos;
+ u32 *next = NEXT_TABLE(dfa);
+ u32 *check = CHECK_TABLE(dfa);
+ aa_state_t state = start, pos;
AA_BUG(!dfa);
AA_BUG(!str);
@@ -716,8 +726,8 @@ static unsigned int leftmatch_fb(struct aa_dfa *dfa, unsigned int start,
AA_BUG(!count);
*count = 0;
- 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]) {
@@ -781,8 +791,8 @@ out:
*
* Returns: final state reached after input is consumed
*/
-unsigned int aa_dfa_leftmatch(struct aa_dfa *dfa, unsigned int start,
- const char *str, unsigned int *count)
+aa_state_t aa_dfa_leftmatch(struct aa_dfa *dfa, aa_state_t start,
+ const char *str, unsigned int *count)
{
DEFINE_MATCH_WB(wb);
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index aa6fcfde3051..523570aa1a5a 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -44,6 +44,8 @@ static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
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)
@@ -86,32 +88,34 @@ static void audit_mnt_flags(struct audit_buffer *ab, unsigned long flags)
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+ struct apparmor_audit_data *ad = aad(sa);
- if (aad(sa)->mnt.type) {
+ if (ad->mnt.type) {
audit_log_format(ab, " fstype=");
- audit_log_untrustedstring(ab, aad(sa)->mnt.type);
+ audit_log_untrustedstring(ab, ad->mnt.type);
}
- if (aad(sa)->mnt.src_name) {
+ if (ad->mnt.src_name) {
audit_log_format(ab, " srcname=");
- audit_log_untrustedstring(ab, aad(sa)->mnt.src_name);
+ audit_log_untrustedstring(ab, ad->mnt.src_name);
}
- if (aad(sa)->mnt.trans) {
+ if (ad->mnt.trans) {
audit_log_format(ab, " trans=");
- audit_log_untrustedstring(ab, aad(sa)->mnt.trans);
+ audit_log_untrustedstring(ab, ad->mnt.trans);
}
- if (aad(sa)->mnt.flags) {
+ if (ad->mnt.flags) {
audit_log_format(ab, " flags=\"");
- audit_mnt_flags(ab, aad(sa)->mnt.flags);
+ audit_mnt_flags(ab, ad->mnt.flags);
audit_log_format(ab, "\"");
}
- if (aad(sa)->mnt.data) {
+ if (ad->mnt.data) {
audit_log_format(ab, " options=");
- audit_log_untrustedstring(ab, aad(sa)->mnt.data);
+ 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)
@@ -127,14 +131,15 @@ static void audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: %0 or error on failure
*/
-static int audit_mount(struct aa_profile *profile, const char *op,
+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(sa, LSM_AUDIT_DATA_NONE, op);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_MOUNT, op);
if (likely(!error)) {
u32 mask = perms->audit;
@@ -165,17 +170,18 @@ static int audit_mount(struct aa_profile *profile, const char *op,
return error;
}
- aad(&sa)->name = name;
- aad(&sa)->mnt.src_name = src_name;
- aad(&sa)->mnt.type = type;
- aad(&sa)->mnt.trans = trans;
- aad(&sa)->mnt.flags = flags;
+ 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))
- aad(&sa)->mnt.data = data;
- aad(&sa)->info = info;
- aad(&sa)->error = error;
+ ad.mnt.data = data;
+ ad.info = info;
+ ad.error = error;
- return aa_audit(audit_type, profile, &sa, audit_cb);
+ return aa_audit(audit_type, profile, &ad, audit_cb);
}
/**
@@ -190,7 +196,7 @@ static int audit_mount(struct aa_profile *profile, const char *op,
*
* Returns: next state after flags match
*/
-static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
+static aa_state_t match_mnt_flags(struct aa_dfa *dfa, aa_state_t state,
unsigned long flags)
{
unsigned int i;
@@ -203,89 +209,72 @@ static unsigned int match_mnt_flags(struct aa_dfa *dfa, unsigned int state,
return state;
}
-/**
- * compute_mnt_perms - compute mount permission associated with @state
- * @dfa: dfa to match against (NOT NULL)
- * @state: state match finished in
- *
- * Returns: mount permissions
- */
-static struct aa_perms compute_mnt_perms(struct aa_dfa *dfa,
- unsigned int state)
-{
- struct aa_perms perms = {
- .allow = dfa_user_allow(dfa, state),
- .audit = dfa_user_audit(dfa, state),
- .quiet = dfa_user_quiet(dfa, state),
- .xindex = dfa_user_xindex(dfa, state),
- };
-
- return perms;
-}
-
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 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_dfa *dfa, unsigned int start,
+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)
{
- unsigned int state;
+ aa_state_t state;
- AA_BUG(!dfa);
+ AA_BUG(!policy);
+ AA_BUG(!policy->dfa);
+ AA_BUG(!policy->perms);
AA_BUG(!perms);
- state = aa_dfa_match(dfa, start, mntpnt);
- state = aa_dfa_null_transition(dfa, state);
+ 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(dfa, state, devname);
- state = aa_dfa_null_transition(dfa, state);
+ 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(dfa, state, type);
- state = aa_dfa_null_transition(dfa, state);
+ state = aa_dfa_match(policy->dfa, state, type);
+ state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 3;
- state = match_mnt_flags(dfa, state, flags);
+ state = match_mnt_flags(policy->dfa, state, flags);
if (!state)
return 4;
- *perms = compute_mnt_perms(dfa, state);
+ *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(dfa, state);
+ state = aa_dfa_null_transition(policy->dfa, state);
if (!state)
return 4;
- state = aa_dfa_match(dfa, state, data);
+ state = aa_dfa_match(policy->dfa, state, data);
if (!state)
return 5;
- *perms = compute_mnt_perms(dfa, state);
+ *perms = *aa_lookup_perms(policy, state);
if (perms->allow & AA_MAY_MOUNT)
return 0;
}
- /* failed at end of flags match */
- return 4;
+ /* failed at perms check, don't confuse with flags match */
+ return 6;
}
@@ -300,10 +289,11 @@ static int path_flags(struct aa_profile *profile, const struct path *path)
/**
* 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
- * @devnme: string for the devname/src_name (MAY BE NULL OR ERRPTR)
+ * @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)
@@ -312,7 +302,8 @@ static int path_flags(struct aa_profile *profile, const struct path *path)
*
* Returns: 0 on success else error
*/
-static int match_mnt_path_str(struct aa_profile *profile,
+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,
@@ -320,13 +311,14 @@ static int match_mnt_path_str(struct aa_profile *profile,
{
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 (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
+ if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
@@ -341,8 +333,8 @@ static int match_mnt_path_str(struct aa_profile *profile,
}
error = -EACCES;
- pos = do_match_mnt(profile->policy.dfa,
- profile->policy.start[AA_CLASS_MOUNT],
+ 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];
@@ -351,14 +343,16 @@ static int match_mnt_path_str(struct aa_profile *profile,
error = 0;
audit:
- return audit_mount(profile, OP_MOUNT, mntpnt, devname, type, NULL,
+ 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
- * @mntpath: for the mntpnt (NOT NULL)
+ * @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
@@ -369,18 +363,20 @@ audit:
*
* Returns: 0 on success else error
*/
-static int match_mnt(struct aa_profile *profile, const struct path *path,
+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 (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
+ if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
if (devpath) {
@@ -391,11 +387,12 @@ static int match_mnt(struct aa_profile *profile, const struct path *path,
devname = ERR_PTR(error);
}
- return match_mnt_path_str(profile, path, buffer, devname, type, flags,
- data, binary, info);
+ return match_mnt_path_str(subj_cred, profile, path, buffer, devname,
+ type, flags, data, binary, info);
}
-int aa_remount(struct aa_label *label, const struct path *path,
+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;
@@ -412,14 +409,16 @@ int aa_remount(struct aa_label *label, const struct path *path,
if (!buffer)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
- match_mnt(profile, path, buffer, NULL, NULL, NULL,
+ match_mnt(subj_cred, profile, path, buffer, NULL,
+ NULL, NULL,
flags, data, binary));
aa_put_buffer(buffer);
return error;
}
-int aa_bind_mount(struct aa_label *label, const struct path *path,
+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;
@@ -446,8 +445,8 @@ int aa_bind_mount(struct aa_label *label, const struct path *path,
goto out;
error = fn_for_each_confined(label, profile,
- match_mnt(profile, path, buffer, &old_path, old_buffer,
- NULL, flags, NULL, false));
+ 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);
@@ -456,7 +455,8 @@ out:
return error;
}
-int aa_mount_change_type(struct aa_label *label, const struct path *path,
+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;
@@ -474,50 +474,67 @@ int aa_mount_change_type(struct aa_label *label, const struct path *path,
if (!buffer)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
- match_mnt(profile, path, buffer, NULL, NULL, NULL,
+ match_mnt(subj_cred, profile, path, buffer, NULL,
+ NULL, NULL,
flags, NULL, false));
aa_put_buffer(buffer);
return error;
}
-int aa_move_mount(struct aa_label *label, const struct path *path,
- const char *orig_name)
+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 *buffer = NULL, *old_buffer = NULL;
- struct path old_path;
+ char *to_buffer = NULL, *from_buffer = NULL;
int error;
AA_BUG(!label);
- AA_BUG(!path);
+ 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;
- 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(profile, path, buffer, &old_path, old_buffer,
- NULL, MS_MOVE, NULL, false));
-out:
- aa_put_buffer(buffer);
- aa_put_buffer(old_buffer);
+ error = aa_move_mount(subj_cred, label, &old_path, path);
path_put(&old_path);
return error;
}
-int aa_new_mount(struct aa_label *label, const char *dev_name,
- const struct path *path, const char *type, unsigned long flags,
- void *data)
+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;
@@ -562,12 +579,14 @@ int aa_new_mount(struct aa_label *label, const char *dev_name,
goto out;
}
error = fn_for_each_confined(label, profile,
- match_mnt(profile, path, buffer, dev_path, dev_buffer,
+ 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(profile, path, buffer, dev_name,
- type, flags, data, binary, NULL));
+ match_mnt_path_str(subj_cred, profile, path,
+ buffer, dev_name,
+ type, flags, data, binary, NULL));
}
out:
@@ -579,18 +598,20 @@ out:
return error;
}
-static int profile_umount(struct aa_profile *profile, const struct path *path,
+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;
- unsigned int state;
+ aa_state_t state;
int error;
AA_BUG(!profile);
AA_BUG(!path);
- if (!PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
+ if (!RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return 0;
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
@@ -598,19 +619,21 @@ static int profile_umount(struct aa_profile *profile, const struct path *path,
if (error)
goto audit;
- state = aa_dfa_match(profile->policy.dfa,
- profile->policy.start[AA_CLASS_MOUNT],
+ state = aa_dfa_match(rules->policy->dfa,
+ rules->policy->start[AA_CLASS_MOUNT],
name);
- perms = compute_mnt_perms(profile->policy.dfa, state);
+ perms = *aa_lookup_perms(rules->policy, state);
if (AA_MAY_UMOUNT & ~perms.allow)
error = -EACCES;
audit:
- return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
+ return audit_mount(subj_cred, profile, OP_UMOUNT, name, NULL, NULL,
+ NULL, 0, NULL,
AA_MAY_UMOUNT, &perms, info, error);
}
-int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
+int aa_umount(const struct cred *subj_cred, struct aa_label *label,
+ struct vfsmount *mnt, int flags)
{
struct aa_profile *profile;
char *buffer = NULL;
@@ -625,7 +648,7 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
return -ENOMEM;
error = fn_for_each_confined(label, profile,
- profile_umount(profile, &path, buffer));
+ profile_umount(subj_cred, profile, &path, buffer));
aa_put_buffer(buffer);
return error;
@@ -635,16 +658,18 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
*
* Returns: label for transition or ERR_PTR. Does not return NULL
*/
-static struct aa_label *build_pivotroot(struct aa_profile *profile,
+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 = { };
- unsigned int state;
+ aa_state_t state;
int error;
AA_BUG(!profile);
@@ -652,7 +677,7 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile,
AA_BUG(!old_path);
if (profile_unconfined(profile) ||
- !PROFILE_MEDIATES(profile, AA_CLASS_MOUNT))
+ !RULE_MEDIATES(rules, AA_CLASS_MOUNT))
return aa_get_newest_label(&profile->label);
error = aa_path_name(old_path, path_flags(profile, old_path),
@@ -667,18 +692,19 @@ static struct aa_label *build_pivotroot(struct aa_profile *profile,
goto audit;
error = -EACCES;
- state = aa_dfa_match(profile->policy.dfa,
- profile->policy.start[AA_CLASS_MOUNT],
+ state = aa_dfa_match(rules->policy->dfa,
+ rules->policy->start[AA_CLASS_MOUNT],
new_name);
- state = aa_dfa_null_transition(profile->policy.dfa, state);
- state = aa_dfa_match(profile->policy.dfa, state, old_name);
- perms = compute_mnt_perms(profile->policy.dfa, state);
+ 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(profile, OP_PIVOTROOT, new_name, old_name,
+ 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)
@@ -687,7 +713,8 @@ audit:
return aa_get_newest_label(&profile->label);
}
-int aa_pivotroot(struct aa_label *label, const struct path *old_path,
+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;
@@ -705,7 +732,8 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path,
if (!old_buffer || !new_buffer)
goto out;
target = fn_label_build(label, profile, GFP_KERNEL,
- build_pivotroot(profile, new_path, new_buffer,
+ build_pivotroot(subj_cred, profile, new_path,
+ new_buffer,
old_path, old_buffer));
if (!target) {
info = "label build failed";
@@ -718,6 +746,7 @@ int aa_pivotroot(struct aa_label *label, const struct path *old_path,
aa_put_label(target);
goto out;
}
+ aa_put_label(target);
} else
/* already audited error */
error = PTR_ERR(target);
@@ -730,7 +759,8 @@ out:
fail:
/* TODO: add back in auditing of new_name and old_name */
error = fn_for_each(label, profile,
- audit_mount(profile, OP_PIVOTROOT, NULL /*new_name */,
+ audit_mount(subj_cred, profile, OP_PIVOTROOT,
+ NULL /*new_name */,
NULL /* old_name */,
NULL, NULL,
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index e0c1b50d6edd..45cf25605c34 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -8,6 +8,7 @@
* Copyright 2009-2017 Canonical Ltd.
*/
+#include "include/af_unix.h"
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
@@ -24,6 +25,12 @@ struct aa_sfs_entry aa_sfs_entry_network[] = {
{ }
};
+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",
@@ -66,96 +73,231 @@ static const char * const net_mask_names[] = {
"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[sa->u.net->family])
+ if (address_family_names[ad->common.u.net->family])
audit_log_format(ab, " family=\"%s\"",
- address_family_names[sa->u.net->family]);
+ address_family_names[ad->common.u.net->family]);
else
audit_log_format(ab, " family=\"unknown(%d)\"",
- sa->u.net->family);
- if (sock_type_names[aad(sa)->net.type])
+ ad->common.u.net->family);
+ if (sock_type_names[ad->net.type])
audit_log_format(ab, " sock_type=\"%s\"",
- sock_type_names[aad(sa)->net.type]);
+ sock_type_names[ad->net.type]);
else
audit_log_format(ab, " sock_type=\"unknown(%d)\"",
- aad(sa)->net.type);
- audit_log_format(ab, " protocol=%d", aad(sa)->net.protocol);
+ ad->net.type);
+ audit_log_format(ab, " protocol=%d", ad->net.protocol);
- if (aad(sa)->request & NET_PERMS_MASK) {
+ if (ad->request & NET_PERMS_MASK) {
audit_log_format(ab, " requested_mask=");
- aa_audit_perm_mask(ab, aad(sa)->request, NULL, 0,
+ aa_audit_perm_mask(ab, ad->request, NULL, 0,
net_mask_names, NET_PERMS_MASK);
- if (aad(sa)->denied & NET_PERMS_MASK) {
+ if (ad->denied & NET_PERMS_MASK) {
audit_log_format(ab, " denied_mask=");
- aa_audit_perm_mask(ab, aad(sa)->denied, NULL, 0,
+ aa_audit_perm_mask(ab, ad->denied, NULL, 0,
net_mask_names, NET_PERMS_MASK);
}
}
- if (aad(sa)->peer) {
+ 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(aad(sa)->label), aad(sa)->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 common_audit_data *sa,
- u32 request, u16 family, int type)
+int aa_profile_af_perm(struct aa_profile *profile,
+ struct apparmor_audit_data *ad, u32 request, u16 family,
+ int type, int protocol)
{
- struct aa_perms perms = { };
- unsigned int state;
- __be16 buffer[2];
+ 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 = PROFILE_MEDIATES(profile, AA_CLASS_NET);
+ state = RULE_MEDIATES_NET(rules);
if (!state)
return 0;
-
- buffer[0] = cpu_to_be16(family);
- buffer[1] = cpu_to_be16((u16) type);
- state = aa_dfa_match_len(profile->policy.dfa, state, (char *) &buffer,
- 4);
- aa_compute_perms(profile->policy.dfa, state, &perms);
- aa_apply_modes_to_perms(profile, &perms);
-
- return aa_check_perms(profile, &perms, request, sa, audit_net_cb);
+ 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(struct aa_label *label, const char *op, 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)
{
struct aa_profile *profile;
- DEFINE_AUDIT_NET(sa, op, NULL, family, type, protocol);
+ DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol);
return fn_for_each_confined(label, profile,
- aa_profile_af_perm(profile, &sa, request, family,
- type));
+ aa_profile_af_perm(profile, &ad, request, family,
+ type, protocol));
}
-static int aa_label_sk_perm(struct aa_label *label, const char *op, u32 request,
+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 (!unconfined(label)) {
+ if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) {
struct aa_profile *profile;
- DEFINE_AUDIT_SK(sa, op, sk);
+ 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, &sa, request, sk));
+ aa_profile_af_sk_perm(profile, &ad, request, sk));
}
return error;
@@ -171,21 +313,25 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk)
/* TODO: switch to begin_current_label ???? */
label = begin_current_label_crit_section();
- error = aa_label_sk_perm(label, op, request, sk);
+ error = aa_label_sk_perm(current_cred(), label, op, request, sk);
end_current_label_crit_section(label);
return error;
}
-int aa_sock_file_perm(struct aa_label *label, const char *op, u32 request,
- struct socket *sock)
+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);
- return aa_label_sk_perm(label, op, request, 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
@@ -211,46 +357,47 @@ static int apparmor_secmark_init(struct aa_secmark *secmark)
}
static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid,
- struct common_audit_data *sa)
+ struct apparmor_audit_data *ad)
{
int i, ret;
struct aa_perms perms = { };
+ struct aa_ruleset *rules = profile->label.rules[0];
- if (profile->secmark_count == 0)
+ if (rules->secmark_count == 0)
return 0;
- for (i = 0; i < profile->secmark_count; i++) {
- if (!profile->secmark[i].secid) {
- ret = apparmor_secmark_init(&profile->secmark[i]);
+ 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 (profile->secmark[i].secid == secid ||
- profile->secmark[i].secid == AA_SECID_WILDCARD) {
- if (profile->secmark[i].deny)
+ 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 (profile->secmark[i].audit)
+ if (rules->secmark[i].audit)
perms.audit = ALL_PERMS_MASK;
}
}
aa_apply_modes_to_perms(profile, &perms);
- return aa_check_perms(profile, &perms, request, sa, audit_net_cb);
+ 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(sa, op, sk);
+ DEFINE_AUDIT_SK(ad, op, NULL, sk);
return fn_for_each_confined(label, profile,
aa_secmark_perm(profile, request, secid,
- &sa));
+ &ad));
}
#endif
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
index 45ec994b558d..d6c74c357ffd 100644
--- a/security/apparmor/path.c
+++ b/security/apparmor/path.c
@@ -130,7 +130,7 @@ static int d_namespace_path(const struct path *path, char *buf, char **name,
/* handle error conditions - and still allow a partial path to
* be returned.
*/
- if (!res || IS_ERR(res)) {
+ if (IS_ERR_OR_NULL(res)) {
if (PTR_ERR(res) == -ENAMETOOLONG) {
error = -ENAMETOOLONG;
*name = buf;
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index b0cbc4906cb3..50d5345ff5cb 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -88,15 +88,52 @@
#include "include/resource.h"
int unprivileged_userns_apparmor_policy = 1;
+int aa_unprivileged_unconfined_restricted;
const char *const aa_profile_mode_names[] = {
"enforce",
"complain",
"kill",
"unconfined",
+ "user",
};
+static void aa_free_pdb(struct aa_policydb *pdb)
+{
+ if (pdb) {
+ aa_put_dfa(pdb->dfa);
+ kvfree(pdb->perms);
+ aa_free_str_table(&pdb->trans);
+ kfree(pdb);
+ }
+}
+
+/**
+ * aa_pdb_free_kref - free aa_policydb by kref (called by aa_put_pdb)
+ * @kref: kref callback for freeing of a dfa (NOT NULL)
+ */
+void aa_pdb_free_kref(struct kref *kref)
+{
+ struct aa_policydb *pdb = container_of(kref, struct aa_policydb, count);
+
+ aa_free_pdb(pdb);
+}
+
+
+struct aa_policydb *aa_alloc_pdb(gfp_t gfp)
+{
+ struct aa_policydb *pdb = kzalloc(sizeof(struct aa_policydb), gfp);
+
+ if (!pdb)
+ return NULL;
+
+ kref_init(&pdb->count);
+
+ return pdb;
+}
+
+
/**
* __add_profile - add a profiles to list and label tree
* @list: list to add it to (NOT NULL)
@@ -187,11 +224,48 @@ static void aa_free_data(void *ptr, void *arg)
{
struct aa_data *data = ptr;
- kfree_sensitive(data->data);
+ kvfree_sensitive(data->data, data->size);
kfree_sensitive(data->key);
kfree_sensitive(data);
}
+static void free_attachment(struct aa_attachment *attach)
+{
+ int i;
+
+ for (i = 0; i < attach->xattr_count; i++)
+ kfree_sensitive(attach->xattrs[i]);
+ kfree_sensitive(attach->xattrs);
+ aa_put_pdb(attach->xmatch);
+}
+
+static void free_ruleset(struct aa_ruleset *rules)
+{
+ int i;
+
+ if (!rules)
+ return;
+
+ aa_put_pdb(rules->file);
+ aa_put_pdb(rules->policy);
+ aa_free_cap_rules(&rules->caps);
+ aa_free_rlimit_rules(&rules->rlimits);
+
+ for (i = 0; i < rules->secmark_count; i++)
+ kfree_sensitive(rules->secmark[i].label);
+ kfree_sensitive(rules->secmark);
+ kfree_sensitive(rules);
+}
+
+struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp)
+{
+ struct aa_ruleset *rules;
+
+ rules = kzalloc(sizeof(*rules), gfp);
+
+ return rules;
+}
+
/**
* aa_free_profile - free a profile
* @profile: the profile to free (MAYBE NULL)
@@ -205,9 +279,8 @@ static void aa_free_data(void *ptr, void *arg)
void aa_free_profile(struct aa_profile *profile)
{
struct rhashtable *rht;
- int i;
- AA_DEBUG("%s(%p)\n", __func__, profile);
+ AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile);
if (!profile)
return;
@@ -218,20 +291,18 @@ void aa_free_profile(struct aa_profile *profile)
aa_put_ns(profile->ns);
kfree_sensitive(profile->rename);
+ kfree_sensitive(profile->disconnected);
- aa_free_file_rules(&profile->file);
- aa_free_cap_rules(&profile->caps);
- aa_free_rlimit_rules(&profile->rlimits);
+ free_attachment(&profile->attach);
+
+ /*
+ * at this point there are no tasks that can have a reference
+ * to rules
+ */
+ for (int i = 0; i < profile->n_rules; i++)
+ free_ruleset(profile->label.rules[i]);
- for (i = 0; i < profile->xattr_count; i++)
- kfree_sensitive(profile->xattrs[i]);
- kfree_sensitive(profile->xattrs);
- for (i = 0; i < profile->secmark_count; i++)
- kfree_sensitive(profile->secmark[i].label);
- kfree_sensitive(profile->secmark);
kfree_sensitive(profile->dirname);
- aa_put_dfa(profile->xmatch);
- aa_put_dfa(profile->policy.dfa);
if (profile->data) {
rht = profile->data;
@@ -250,6 +321,7 @@ void aa_free_profile(struct aa_profile *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
@@ -259,8 +331,11 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
{
struct aa_profile *profile;
- /* freed by free_profile - usually through aa_put_profile */
- profile = kzalloc(struct_size(profile, label.vec, 2), gfp);
+ /* freed by free_profile - usually through aa_put_profile
+ * this adds space for a single ruleset in the rules section of the
+ * label
+ */
+ profile = kzalloc(struct_size(profile, label.rules, 1), gfp);
if (!profile)
return NULL;
@@ -269,6 +344,12 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
if (!aa_label_init(&profile->label, 1, gfp))
goto fail;
+ /* allocate the first ruleset, but leave it empty */
+ profile->label.rules[0] = aa_alloc_ruleset(gfp);
+ if (!profile->label.rules[0])
+ goto fail;
+ profile->n_rules = 1;
+
/* update being set needed by fs interface */
if (!proxy) {
proxy = aa_alloc_proxy(&profile->label, gfp);
@@ -282,6 +363,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,
profile->label.flags |= FLAG_PROFILE;
profile->label.vec[0] = profile;
+ profile->signal = SIGKILL;
/* refcount released by caller */
return profile;
@@ -291,6 +373,41 @@ fail:
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 */
/**
@@ -381,6 +498,55 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns,
}
/**
+ * __create_missing_ancestors - create place holders for missing ancestors
+ * @ns: namespace to lookup profile in (NOT NULL)
+ * @hname: hierarchical profile name to find parent of (NOT NULL)
+ * @gfp: type of allocation.
+ *
+ * Requires: ns mutex lock held
+ *
+ * Return: unrefcounted parent policy on success or %NULL if error creating
+ * place holder profiles.
+ */
+static struct aa_policy *__create_missing_ancestors(struct aa_ns *ns,
+ const char *hname,
+ gfp_t gfp)
+{
+ struct aa_policy *policy;
+ struct aa_profile *parent, *profile = NULL;
+ char *split;
+
+ AA_BUG(!ns);
+ AA_BUG(!hname);
+
+ policy = &ns->base;
+
+ for (split = strstr(hname, "//"); split;) {
+ parent = profile;
+ profile = __strn_find_child(&policy->profiles, hname,
+ split - hname);
+ if (!profile) {
+ const char *name = kstrndup(hname, split - hname,
+ gfp);
+ if (!name)
+ return NULL;
+ profile = aa_alloc_null(parent, name, gfp);
+ kfree(name);
+ if (!profile)
+ return NULL;
+ if (!parent)
+ profile->ns = aa_get_ns(ns);
+ }
+ policy = &profile->base;
+ hname = split + 2;
+ split = strstr(hname, "//");
+ }
+ if (!profile)
+ return &ns->base;
+ return &profile->base;
+}
+
+/**
* __lookupn_profile - lookup the profile matching @hname
* @base: base list to start looking up profile name from (NOT NULL)
* @hname: hierarchical profile name (NOT NULL)
@@ -422,7 +588,7 @@ static struct aa_profile *__lookup_profile(struct aa_policy *base,
}
/**
- * aa_lookup_profile - find a profile by its full or partial name
+ * aa_lookupn_profile - find a profile by its full or partial name
* @ns: the namespace to start from (NOT NULL)
* @hname: name to do lookup on. Does not contain namespace prefix (NOT NULL)
* @n: size of @hname
@@ -448,11 +614,6 @@ struct aa_profile *aa_lookupn_profile(struct aa_ns *ns, const char *hname,
return profile;
}
-struct aa_profile *aa_lookup_profile(struct aa_ns *ns, const char *hname)
-{
- return aa_lookupn_profile(ns, hname, strlen(hname));
-}
-
struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n)
{
@@ -481,8 +642,39 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
return profile;
}
+
+struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,
+ gfp_t gfp)
+{
+ struct aa_profile *profile;
+ struct aa_ruleset *rules;
+
+ profile = aa_alloc_profile(name, NULL, gfp);
+ if (!profile)
+ return NULL;
+
+ /* TODO: ideally we should inherit abi from parent */
+ profile->label.flags |= FLAG_NULL;
+ profile->attach.xmatch = aa_get_pdb(nullpdb);
+ rules = profile->label.rules[0];
+ rules->file = aa_get_pdb(nullpdb);
+ rules->policy = aa_get_pdb(nullpdb);
+ aa_compute_profile_mediates(profile);
+
+ if (parent) {
+ profile->path_flags = parent->path_flags;
+ /* override/inherit what is mediated from parent */
+ profile->label.mediates = parent->label.mediates;
+ /* released on free_profile */
+ rcu_assign_pointer(profile->parent, aa_get_profile(parent));
+ profile->ns = aa_get_ns(parent->ns);
+ }
+
+ return profile;
+}
+
/**
- * aa_new_null_profile - create or find a null-X learning 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
@@ -499,8 +691,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
*
* Returns: new refcounted profile else NULL on failure
*/
-struct aa_profile *aa_new_null_profile(struct aa_profile *parent, bool hat,
- const char *base, gfp_t gfp)
+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;
@@ -531,21 +723,12 @@ name:
if (profile)
goto out;
- profile = aa_alloc_profile(name, NULL, gfp);
+ profile = aa_alloc_null(parent, name, gfp);
if (!profile)
goto fail;
-
profile->mode = APPARMOR_COMPLAIN;
- profile->label.flags |= FLAG_NULL;
if (hat)
profile->label.flags |= FLAG_HAT;
- profile->path_flags = parent->path_flags;
-
- /* released on free_profile */
- rcu_assign_pointer(profile->parent, aa_get_profile(parent));
- profile->ns = aa_get_ns(parent->ns);
- profile->file.dfa = aa_get_dfa(nulldfa);
- profile->policy.dfa = aa_get_dfa(nulldfa);
mutex_lock_nested(&profile->ns->lock, profile->ns->level);
p = __find_child(&parent->base.profiles, bname);
@@ -596,16 +779,17 @@ static int replacement_allowed(struct aa_profile *profile, int noreplace,
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+ struct apparmor_audit_data *ad = aad(sa);
- if (aad(sa)->iface.ns) {
+ if (ad->iface.ns) {
audit_log_format(ab, " ns=");
- audit_log_untrustedstring(ab, aad(sa)->iface.ns);
+ audit_log_untrustedstring(ab, ad->iface.ns);
}
}
/**
* audit_policy - Do auditing of policy changes
- * @label: label to check if it can manage policy
+ * @subj_label: label to check if it can manage policy
* @op: policy operation being performed
* @ns_name: name of namespace being manipulated
* @name: name of profile being manipulated (NOT NULL)
@@ -614,19 +798,19 @@ static void audit_cb(struct audit_buffer *ab, void *va)
*
* Returns: the error to be returned after audit is done
*/
-static int audit_policy(struct aa_label *label, const char *op,
+static int audit_policy(struct aa_label *subj_label, const char *op,
const char *ns_name, const char *name,
const char *info, int error)
{
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, op);
- aad(&sa)->iface.ns = ns_name;
- aad(&sa)->name = name;
- aad(&sa)->info = info;
- aad(&sa)->error = error;
- aad(&sa)->label = label;
+ ad.iface.ns = ns_name;
+ ad.name = name;
+ ad.info = info;
+ ad.error = error;
+ ad.subj_label = subj_label;
- aa_audit_msg(AUDIT_APPARMOR_STATUS, &sa, audit_cb);
+ aa_audit_msg(AUDIT_APPARMOR_STATUS, &ad, audit_cb);
return error;
}
@@ -634,31 +818,35 @@ static int audit_policy(struct aa_label *label, const char *op,
/* don't call out to other LSMs in the stack for apparmor policy admin
* permissions
*/
-static int policy_ns_capable(struct aa_label *label,
+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(current_cred(), userns, cap, CAP_OPT_NONE);
+ err = cap_capable(subj_cred, userns, cap, CAP_OPT_NONE);
if (!err)
- err = aa_capable(label, cap, CAP_OPT_NONE);
+ 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
- * label: label that is trying to view policy in ns
- * ns: namespace being viewed by @label (may be NULL if @label's ns)
+ * @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(struct aa_label *label, struct aa_ns *ns)
+bool aa_policy_view_capable(const struct cred *subj_cred,
+ struct aa_label *label, struct aa_ns *ns)
{
- struct user_namespace *user_ns = current_user_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));
@@ -675,26 +863,28 @@ bool aa_policy_view_capable(struct aa_label *label, struct aa_ns *ns)
return response;
}
-bool aa_policy_admin_capable(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)
{
- struct user_namespace *user_ns = current_user_ns();
- bool capable = policy_ns_capable(label, user_ns, CAP_MAC_ADMIN) == 0;
+ struct user_namespace *user_ns = subj_cred->user_ns;
+ bool capable = policy_ns_capable(subj_cred, label, user_ns,
+ CAP_MAC_ADMIN) == 0;
- AA_DEBUG("cap_mac_admin? %d\n", capable);
- AA_DEBUG("policy locked? %d\n", aa_g_lock_policy);
+ AA_DEBUG(DEBUG_POLICY, "cap_mac_admin? %d\n", capable);
+ AA_DEBUG(DEBUG_POLICY, "policy locked? %d\n", aa_g_lock_policy);
- return aa_policy_view_capable(label, ns) && capable &&
+ 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 res;
+ bool needput, res;
- label = __begin_current_label_crit_section();
- res = aa_policy_view_capable(label, ns);
- __end_current_label_crit_section(label);
+ 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;
}
@@ -702,23 +892,26 @@ bool aa_current_policy_view_capable(struct aa_ns *ns)
bool aa_current_policy_admin_capable(struct aa_ns *ns)
{
struct aa_label *label;
- bool res;
+ bool needput, res;
- label = __begin_current_label_crit_section();
- res = aa_policy_admin_capable(label, ns);
- __end_current_label_crit_section(label);
+ label = __begin_current_label_crit_section(&needput);
+ res = aa_policy_admin_capable(current_cred(), label, ns);
+ __end_current_label_crit_section(label, needput);
return res;
}
/**
* aa_may_manage_policy - can the current task manage policy
+ * @subj_cred: subjects cred
* @label: label to check if it can manage policy
- * @op: the policy manipulation operation being done
+ * @ns: namespace being managed by @label (may be NULL if @label's ns)
+ * @mask: contains the policy manipulation operation being done
*
* Returns: 0 if the task is allowed to manipulate policy else error
*/
-int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask)
+int aa_may_manage_policy(const struct cred *subj_cred, struct aa_label *label,
+ struct aa_ns *ns, u32 mask)
{
const char *op;
@@ -734,7 +927,7 @@ int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask)
return audit_policy(label, op, NULL, NULL, "policy_locked",
-EACCES);
- if (!aa_policy_admin_capable(label, ns))
+ if (!aa_policy_admin_capable(subj_cred, label, ns))
return audit_policy(label, op, NULL, NULL, "not policy admin",
-EACCES);
@@ -769,7 +962,6 @@ static struct aa_profile *__list_lookup_parent(struct list_head *lh,
* __replace_profile - replace @old with @new on a list
* @old: profile to be replaced (NOT NULL)
* @new: profile to replace @old with (NOT NULL)
- * @share_proxy: transfer @old->proxy to @new
*
* Will duplicate and refcount elements that @new inherits from @old
* and will inherit @old children.
@@ -826,11 +1018,11 @@ static void __replace_profile(struct aa_profile *old, struct aa_profile *new)
/**
* __lookup_replace - lookup replacement information for a profile
- * @ns - namespace the lookup occurs in
- * @hname - name of profile to lookup
- * @noreplace - true if not replacing an existing profile
- * @p - Returns: profile to be replaced
- * @info - Returns: info string on why lookup failed
+ * @ns: namespace the lookup occurs in
+ * @hname: name of profile to lookup
+ * @noreplace: true if not replacing an existing profile
+ * @p: Returns - profile to be replaced
+ * @info: Returns - info string on why lookup failed
*
* Returns: profile to replace (no ref) on success else ptr error
*/
@@ -913,7 +1105,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
goto out;
/* ensure that profiles are all for the same ns
- * TODO: update locking to remove this constaint. All profiles in
+ * TODO: update locking to remove this constraint. All profiles in
* the load set must succeed as a set or the load will
* fail. Sort ent list and take ns locks in hierarchy order
*/
@@ -952,24 +1144,28 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
mutex_lock_nested(&ns->lock, ns->level);
/* check for duplicate rawdata blobs: space and file dedup */
- list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) {
- if (aa_rawdata_eq(rawdata_ent, udata)) {
- struct aa_loaddata *tmp;
-
- tmp = __aa_get_loaddata(rawdata_ent);
- /* check we didn't fail the race */
- if (tmp) {
- aa_put_loaddata(udata);
- udata = tmp;
- break;
+ if (!list_empty(&ns->rawdata_list)) {
+ list_for_each_entry(rawdata_ent, &ns->rawdata_list, list) {
+ if (aa_rawdata_eq(rawdata_ent, udata)) {
+ struct aa_loaddata *tmp;
+
+ tmp = __aa_get_loaddata(rawdata_ent);
+ /* check we didn't fail the race */
+ if (tmp) {
+ aa_put_loaddata(udata);
+ udata = tmp;
+ break;
+ }
}
}
}
/* setup parent and ns info */
list_for_each_entry(ent, &lh, list) {
struct aa_policy *policy;
+ struct aa_profile *p;
- ent->new->rawdata = aa_get_loaddata(udata);
+ if (aa_g_export_binary)
+ ent->new->rawdata = aa_get_loaddata(udata);
error = __lookup_replace(ns, ent->new->base.hname,
!(mask & AA_MAY_REPLACE_POLICY),
&ent->old, &info);
@@ -991,25 +1187,42 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
continue;
/* no ref on policy only use inside lock */
+ p = NULL;
policy = __lookup_parent(ns, ent->new->base.hname);
if (!policy) {
- struct aa_profile *p;
+ /* first check for parent in the load set */
p = __list_lookup_parent(&lh, ent->new);
if (!p) {
- error = -ENOENT;
- info = "parent does not exist";
- goto fail_lock;
+ /*
+ * fill in missing parent with null
+ * profile that doesn't have
+ * permissions. This allows for
+ * individual profile loading where
+ * the child is loaded before the
+ * parent, and outside of the current
+ * atomic set. This unfortunately can
+ * happen with some userspaces. The
+ * null profile will be replaced once
+ * the parent is loaded.
+ */
+ policy = __create_missing_ancestors(ns,
+ ent->new->base.hname,
+ GFP_KERNEL);
+ if (!policy) {
+ error = -ENOENT;
+ info = "parent does not exist";
+ goto fail_lock;
+ }
}
- rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
- } else if (policy != &ns->base) {
- /* released on profile replacement or free_profile */
- struct aa_profile *p = (struct aa_profile *) policy;
- rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
}
+ if (!p && policy != &ns->base)
+ /* released on profile replacement or free_profile */
+ p = (struct aa_profile *) policy;
+ rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
}
/* create new fs entries for introspection if needed */
- if (!udata->dents[AAFS_LOADDATA_DIR]) {
+ if (!udata->dents[AAFS_LOADDATA_DIR] && aa_g_export_binary) {
error = __aa_fs_create_rawdata(ns, udata);
if (error) {
info = "failed to create raw_data dir and files";
@@ -1037,12 +1250,14 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
/* Done with checks that may fail - do actual replacement */
__aa_bump_ns_revision(ns);
- __aa_loaddata_update(udata, ns->revision);
+ if (aa_g_export_binary)
+ __aa_loaddata_update(udata, ns->revision);
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
- if (ent->old && ent->old->rawdata == ent->new->rawdata) {
+ if (ent->old && ent->old->rawdata == ent->new->rawdata &&
+ ent->new->rawdata) {
/* dedup actual profile replacement */
audit_policy(label, op, ns_name, ent->new->base.hname,
"same as current profile, skipping",
@@ -1165,7 +1380,7 @@ ssize_t aa_remove_profiles(struct aa_ns *policy_ns, struct aa_label *subj,
if (!name) {
/* remove namespace - can only happen if fqname[0] == ':' */
- mutex_lock_nested(&ns->parent->lock, ns->level);
+ mutex_lock_nested(&ns->parent->lock, ns->parent->level);
__aa_bump_ns_revision(ns);
__aa_remove_ns(ns);
mutex_unlock(&ns->parent->lock);
diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c
new file mode 100644
index 000000000000..cfc2207e5a12
--- /dev/null
+++ b/security/apparmor/policy_compat.c
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions for unpacking policy loaded
+ * from userspace.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2022 Canonical Ltd.
+ *
+ * Code to provide backwards compatibility with older policy versions,
+ * by converting/mapping older policy formats into the newer internal
+ * formats.
+ */
+
+#include <linux/ctype.h>
+#include <linux/errno.h>
+
+#include "include/lib.h"
+#include "include/policy_unpack.h"
+#include "include/policy_compat.h"
+
+/* remap old accept table embedded permissions to separate permission table */
+static u32 dfa_map_xindex(u16 mask)
+{
+ u16 old_index = (mask >> 10) & 0xf;
+ u32 index = 0;
+
+ if (mask & 0x100)
+ index |= AA_X_UNSAFE;
+ if (mask & 0x200)
+ index |= AA_X_INHERIT;
+ if (mask & 0x80)
+ index |= AA_X_UNCONFINED;
+
+ if (old_index == 1) {
+ index |= AA_X_UNCONFINED;
+ } else if (old_index == 2) {
+ index |= AA_X_NAME;
+ } else if (old_index == 3) {
+ index |= AA_X_NAME | AA_X_CHILD;
+ } else if (old_index) {
+ index |= AA_X_TABLE;
+ index |= old_index - 4;
+ }
+
+ return index;
+}
+
+/*
+ * map old dfa inline permissions to new format
+ */
+#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
+ ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
+#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
+#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
+#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
+#define dfa_user_xindex(dfa, state) \
+ (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
+
+#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
+ 0x7f) | \
+ ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
+#define dfa_other_xbits(dfa, state) \
+ ((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f)
+#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
+#define dfa_other_quiet(dfa, state) \
+ ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
+#define dfa_other_xindex(dfa, state) \
+ dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
+
+/**
+ * map_old_perms - map old file perms layout to the new layout
+ * @old: permission set in old mapping
+ *
+ * Returns: new permission mapping
+ */
+static u32 map_old_perms(u32 old)
+{
+ u32 new = old & 0xf;
+
+ if (old & MAY_READ)
+ new |= AA_MAY_GETATTR | AA_MAY_OPEN;
+ if (old & MAY_WRITE)
+ new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
+ AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
+ if (old & 0x10)
+ new |= AA_MAY_LINK;
+ /* the old mapping lock and link_subset flags where overlaid
+ * and use was determined by part of a pair that they were in
+ */
+ if (old & 0x20)
+ new |= AA_MAY_LOCK | AA_LINK_SUBSET;
+ if (old & 0x40) /* AA_EXEC_MMAP */
+ new |= AA_EXEC_MMAP;
+
+ return new;
+}
+
+static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
+ aa_state_t state)
+{
+ perms->allow |= AA_MAY_GETATTR;
+
+ /* change_profile wasn't determined by ownership in old mapping */
+ if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
+ perms->allow |= AA_MAY_CHANGE_PROFILE;
+ if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
+ perms->allow |= AA_MAY_ONEXEC;
+}
+
+static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
+ aa_state_t state)
+{
+ struct aa_perms perms = { };
+
+ perms.allow = map_old_perms(dfa_user_allow(dfa, state));
+ perms.audit = map_old_perms(dfa_user_audit(dfa, state));
+ perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
+ perms.xindex = dfa_user_xindex(dfa, state);
+
+ compute_fperms_allow(&perms, dfa, state);
+
+ return perms;
+}
+
+static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
+ aa_state_t state)
+{
+ struct aa_perms perms = { };
+
+ perms.allow = map_old_perms(dfa_other_allow(dfa, state));
+ perms.audit = map_old_perms(dfa_other_audit(dfa, state));
+ perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
+ perms.xindex = dfa_other_xindex(dfa, state);
+
+ compute_fperms_allow(&perms, dfa, state);
+
+ return perms;
+}
+
+/**
+ * compute_fperms - convert dfa compressed perms to internal perms and store
+ * them so they can be retrieved later.
+ * @dfa: a dfa using fperms to remap to internal permissions
+ * @size: Returns the permission table size
+ *
+ * Returns: remapped perm table
+ */
+static struct aa_perms *compute_fperms(struct aa_dfa *dfa,
+ u32 *size)
+{
+ aa_state_t state;
+ unsigned int state_count;
+ struct aa_perms *table;
+
+ AA_BUG(!dfa);
+
+ state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
+ /* DFAs are restricted from having a state_count of less than 2 */
+ table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL);
+ if (!table)
+ return NULL;
+ *size = state_count * 2;
+
+ for (state = 0; state < state_count; state++) {
+ table[state * 2] = compute_fperms_user(dfa, state);
+ table[state * 2 + 1] = compute_fperms_other(dfa, state);
+ }
+
+ return table;
+}
+
+static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch,
+ u32 *size)
+{
+ struct aa_perms *perms;
+ int state;
+ int state_count;
+
+ AA_BUG(!xmatch);
+
+ state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen;
+ /* DFAs are restricted from having a state_count of less than 2 */
+ perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
+ if (!perms)
+ return NULL;
+ *size = state_count;
+
+ /* zero init so skip the trap state (state == 0) */
+ for (state = 1; state < state_count; state++)
+ perms[state].allow = dfa_user_allow(xmatch, state);
+
+ return perms;
+}
+
+static u32 map_other(u32 x)
+{
+ return ((x & 0x3) << 8) | /* SETATTR/GETATTR */
+ ((x & 0x1c) << 18) | /* ACCEPT/BIND/LISTEN */
+ ((x & 0x60) << 19); /* SETOPT/GETOPT */
+}
+
+static u32 map_xbits(u32 x)
+{
+ return ((x & 0x1) << 7) |
+ ((x & 0x7e) << 9);
+}
+
+static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
+ aa_state_t state,
+ u32 version)
+{
+ struct aa_perms perms = { };
+
+ perms.allow = dfa_user_allow(dfa, state);
+ perms.audit = dfa_user_audit(dfa, state);
+ perms.quiet = dfa_user_quiet(dfa, state);
+
+ /*
+ * This mapping is convulated due to history.
+ * v1-v4: only file perms, which are handled by compute_fperms
+ * v5: added policydb which dropped user conditional to gain new
+ * perm bits, but had to map around the xbits because the
+ * userspace compiler was still munging them.
+ * v9: adds using the xbits in policydb because the compiler now
+ * supports treating policydb permission bits different.
+ * Unfortunately there is no way to force auditing on the
+ * perms represented by the xbits
+ */
+ perms.allow |= map_other(dfa_other_allow(dfa, state));
+ if (VERSION_LE(version, v8))
+ perms.allow |= AA_MAY_LOCK;
+ else
+ perms.allow |= map_xbits(dfa_user_xbits(dfa, state));
+
+ /*
+ * for v5-v9 perm mapping in the policydb, the other set is used
+ * to extend the general perm set
+ */
+ perms.audit |= map_other(dfa_other_audit(dfa, state));
+ perms.quiet |= map_other(dfa_other_quiet(dfa, state));
+ if (VERSION_GT(version, v8))
+ perms.quiet |= map_xbits(dfa_other_xbits(dfa, state));
+
+ return perms;
+}
+
+static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version,
+ u32 *size)
+{
+ unsigned int state;
+ unsigned int state_count;
+ struct aa_perms *table;
+
+ AA_BUG(!dfa);
+
+ state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
+ /* DFAs are restricted from having a state_count of less than 2 */
+ table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
+ if (!table)
+ return NULL;
+ *size = state_count;
+
+ /* zero init so skip the trap state (state == 0) */
+ for (state = 1; state < state_count; state++)
+ table[state] = compute_perms_entry(dfa, state, version);
+
+ return table;
+}
+
+/**
+ * remap_dfa_accept - remap old dfa accept table to be an index
+ * @dfa: dfa to do the remapping on
+ * @factor: scaling factor for the index conversion.
+ *
+ * Used in conjunction with compute_Xperms, it converts old style perms
+ * that are encoded in the dfa accept tables to the new style where
+ * there is a permission table and the accept table is an index into
+ * the permission table.
+ */
+static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor)
+{
+ unsigned int state;
+ unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
+
+ AA_BUG(!dfa);
+
+ for (state = 0; state < state_count; state++) {
+ ACCEPT_TABLE(dfa)[state] = state * factor;
+ ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0;
+ }
+}
+
+/* TODO: merge different dfa mappings into single map_policy fn */
+int aa_compat_map_xmatch(struct aa_policydb *policy)
+{
+ policy->perms = compute_xmatch_perms(policy->dfa, &policy->size);
+ if (!policy->perms)
+ return -ENOMEM;
+
+ remap_dfa_accept(policy->dfa, 1);
+
+ return 0;
+}
+
+int aa_compat_map_policy(struct aa_policydb *policy, u32 version)
+{
+ policy->perms = compute_perms(policy->dfa, version, &policy->size);
+ if (!policy->perms)
+ return -ENOMEM;
+
+ remap_dfa_accept(policy->dfa, 1);
+
+ return 0;
+}
+
+int aa_compat_map_file(struct aa_policydb *policy)
+{
+ policy->perms = compute_fperms(policy->dfa, &policy->size);
+ if (!policy->perms)
+ return -ENOMEM;
+
+ remap_dfa_accept(policy->dfa, 2);
+
+ return 0;
+}
diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c
index 70921d95fb40..64783ca3b0f2 100644
--- a/security/apparmor/policy_ns.c
+++ b/security/apparmor/policy_ns.c
@@ -22,6 +22,9 @@
#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 = "---";
@@ -51,10 +54,10 @@ bool aa_ns_visible(struct aa_ns *curr, struct aa_ns *view, bool subns)
}
/**
- * aa_na_name - Find the ns name to display for @view from @curr
- * @curr - current namespace (NOT NULL)
- * @view - namespace attempting to view (NOT NULL)
- * @subns - are subns visible
+ * aa_ns_name - Find the ns name to display for @view from @curr
+ * @curr: current namespace (NOT NULL)
+ * @view: namespace attempting to view (NOT NULL)
+ * @subns: are subns visible
*
* Returns: name of @view visible from @curr
*/
@@ -77,6 +80,21 @@ const char *aa_ns_name(struct aa_ns *curr, struct aa_ns *view, bool subns)
return aa_hidden_ns_name;
}
+static struct aa_profile *alloc_unconfined(const char *name)
+{
+ struct aa_profile *profile;
+
+ profile = aa_alloc_null(NULL, name, GFP_KERNEL);
+ if (!profile)
+ return NULL;
+
+ profile->label.flags |= FLAG_IX_ON_NAME_ERROR |
+ FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
+ profile->mode = APPARMOR_UNCONFINED;
+
+ return profile;
+}
+
/**
* alloc_ns - allocate, initialize and return a new namespace
* @prefix: parent namespace name (MAYBE NULL)
@@ -89,7 +107,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
struct aa_ns *ns;
ns = kzalloc(sizeof(*ns), GFP_KERNEL);
- AA_DEBUG("%s(%p)\n", __func__, ns);
+ AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns);
if (!ns)
return NULL;
if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL))
@@ -101,16 +119,9 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
init_waitqueue_head(&ns->wait);
/* released by aa_free_ns() */
- ns->unconfined = aa_alloc_profile("unconfined", NULL, GFP_KERNEL);
+ ns->unconfined = alloc_unconfined("unconfined");
if (!ns->unconfined)
goto fail_unconfined;
-
- ns->unconfined->label.flags |= FLAG_IX_ON_NAME_ERROR |
- FLAG_IMMUTIBLE | FLAG_NS_COUNT | FLAG_UNCONFINED;
- ns->unconfined->mode = APPARMOR_UNCONFINED;
- ns->unconfined->file.dfa = aa_get_dfa(nulldfa);
- ns->unconfined->policy.dfa = aa_get_dfa(nulldfa);
-
/* ns and ns->unconfined share ns->unconfined refcount */
ns->unconfined->ns = ns;
@@ -121,7 +132,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
return ns;
fail_unconfined:
- kfree_sensitive(ns->base.hname);
+ aa_policy_destroy(&ns->base);
fail_ns:
kfree_sensitive(ns);
return NULL;
@@ -149,45 +160,8 @@ void aa_free_ns(struct aa_ns *ns)
}
/**
- * aa_findn_ns - look up a profile namespace on the namespace list
- * @root: namespace to search in (NOT NULL)
- * @name: name of namespace to find (NOT NULL)
- * @n: length of @name
- *
- * Returns: a refcounted namespace on the list, or NULL if no namespace
- * called @name exists.
- *
- * refcount released by caller
- */
-struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n)
-{
- struct aa_ns *ns = NULL;
-
- rcu_read_lock();
- ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, n));
- rcu_read_unlock();
-
- return ns;
-}
-
-/**
- * aa_find_ns - look up a profile namespace on the namespace list
- * @root: namespace to search in (NOT NULL)
- * @name: name of namespace to find (NOT NULL)
- *
- * Returns: a refcounted namespace on the list, or NULL if no namespace
- * called @name exists.
- *
- * refcount released by caller
- */
-struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name)
-{
- return aa_findn_ns(root, name, strlen(name));
-}
-
-/**
* __aa_lookupn_ns - lookup the namespace matching @hname
- * @base: base list to start looking up profile name from (NOT NULL)
+ * @view: namespace to search in (NOT NULL)
* @hname: hierarchical ns name (NOT NULL)
* @n: length of @hname
*
@@ -272,7 +246,7 @@ static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
}
/**
- * aa_create_ns - create an ns, fail if it already exists
+ * __aa_find_or_create_ns - create an ns, fail if it already exists
* @parent: the parent of the namespace being created
* @name: the name of the namespace
* @dir: if not null the dir to put the ns entries in
@@ -388,11 +362,22 @@ static void __ns_list_release(struct list_head *head)
*/
int __init aa_alloc_root_ns(void)
{
+ struct aa_profile *kernel_p;
+
/* released by aa_free_root_ns - used as list ref*/
root_ns = alloc_ns(NULL, "root");
if (!root_ns)
return -ENOMEM;
+ kernel_p = alloc_unconfined("kernel_t");
+ if (!kernel_p) {
+ destroy_ns(root_ns);
+ aa_free_ns(root_ns);
+ return -ENOMEM;
+ }
+ kernel_t = &kernel_p->label;
+ root_ns->unconfined->ns = aa_get_ns(root_ns);
+
return 0;
}
@@ -405,6 +390,7 @@ void __init aa_free_root_ns(void)
root_ns = NULL;
+ aa_label_free(kernel_t);
destroy_ns(ns);
aa_put_ns(ns);
}
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 0acca6f2a93f..7523971e37d9 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -13,82 +13,40 @@
* 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/zlib.h>
+#include <linux/zstd.h>
#include "include/apparmor.h"
#include "include/audit.h"
#include "include/cred.h"
#include "include/crypto.h"
+#include "include/file.h"
#include "include/match.h"
#include "include/path.h"
#include "include/policy.h"
#include "include/policy_unpack.h"
-
-#define K_ABI_MASK 0x3ff
-#define FORCE_COMPLAIN_FLAG 0x800
-#define VERSION_LT(X, Y) (((X) & K_ABI_MASK) < ((Y) & K_ABI_MASK))
-#define VERSION_GT(X, Y) (((X) & K_ABI_MASK) > ((Y) & K_ABI_MASK))
-
-#define v5 5 /* base version */
-#define v6 6 /* per entry policydb mediation check */
-#define v7 7
-#define v8 8 /* full network masking */
-
-/*
- * 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;
-};
+#include "include/policy_compat.h"
+#include "include/signal.h"
/* audit callback for unpack fields */
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+ struct apparmor_audit_data *ad = aad(sa);
- if (aad(sa)->iface.ns) {
+ if (ad->iface.ns) {
audit_log_format(ab, " ns=");
- audit_log_untrustedstring(ab, aad(sa)->iface.ns);
+ audit_log_untrustedstring(ab, ad->iface.ns);
}
- if (aad(sa)->name) {
+ if (ad->name) {
audit_log_format(ab, " name=");
- audit_log_untrustedstring(ab, aad(sa)->name);
+ audit_log_untrustedstring(ab, ad->name);
}
- if (aad(sa)->iface.pos)
- audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos);
+ if (ad->iface.pos)
+ audit_log_format(ab, " offset=%ld", ad->iface.pos);
}
/**
@@ -107,33 +65,37 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
int error)
{
struct aa_profile *profile = labels_profile(aa_current_raw_label());
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, NULL);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL);
if (e)
- aad(&sa)->iface.pos = e->pos - e->start;
- aad(&sa)->iface.ns = ns_name;
+ ad.iface.pos = e->pos - e->start;
+ ad.iface.ns = ns_name;
if (new)
- aad(&sa)->name = new->base.hname;
+ ad.name = new->base.hname;
else
- aad(&sa)->name = name;
- aad(&sa)->info = info;
- aad(&sa)->error = error;
+ ad.name = name;
+ ad.info = info;
+ ad.error = error;
- return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb);
+ return aa_audit(AUDIT_APPARMOR_STATUS, profile, &ad, audit_cb);
}
void __aa_loaddata_update(struct aa_loaddata *data, long revision)
{
AA_BUG(!data);
AA_BUG(!data->ns);
- AA_BUG(!data->dents[AAFS_LOADDATA_REVISION]);
AA_BUG(!mutex_is_locked(&data->ns->lock));
AA_BUG(data->revision > revision);
data->revision = revision;
- d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime =
- current_time(d_inode(data->dents[AAFS_LOADDATA_DIR]));
- d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime =
- current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION]));
+ if ((data->dents[AAFS_LOADDATA_REVISION])) {
+ struct inode *inode;
+
+ inode = d_inode(data->dents[AAFS_LOADDATA_DIR]);
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+
+ inode = d_inode(data->dents[AAFS_LOADDATA_REVISION]);
+ inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
+ }
}
bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r)
@@ -198,37 +160,29 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size)
}
/* test if read will be in packed data bounds */
-static bool inbounds(struct aa_ext *e, size_t size)
+VISIBLE_IF_KUNIT bool aa_inbounds(struct aa_ext *e, size_t size)
{
return (size <= e->end - e->pos);
}
-
-static void *kvmemdup(const void *src, size_t len)
-{
- void *p = kvmalloc(len, GFP_KERNEL);
-
- if (p)
- memcpy(p, src, len);
- return p;
-}
+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)))
+ if (!aa_inbounds(e, sizeof(u16)))
goto fail;
size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
e->pos += sizeof(__le16);
- if (!inbounds(e, size))
+ if (!aa_inbounds(e, size))
goto fail;
*chunk = e->pos;
e->pos += size;
@@ -238,20 +192,22 @@ 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))
+ if (!aa_inbounds(e, 1))
return false;
if (*(u8 *) e->pos != code)
return false;
e->pos++;
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)
@@ -266,7 +222,7 @@ static bool unpack_X(struct aa_ext *e, enum aa_code code)
*
* 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
@@ -276,9 +232,9 @@ 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 || tag[size-1] != '\0' || strcmp(name, tag)))
goto fail;
@@ -288,20 +244,21 @@ 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))
+ if (aa_unpack_X(e, code))
return true;
fail:
e->pos = pos;
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 (unpack_nameX(e, AA_U8, name)) {
- if (!inbounds(e, sizeof(u8)))
+ if (aa_unpack_nameX(e, AA_U8, name)) {
+ if (!aa_inbounds(e, sizeof(u8)))
goto fail;
if (data)
*data = *((u8 *)e->pos);
@@ -314,12 +271,12 @@ fail:
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)
{
void *pos = e->pos;
- if (unpack_nameX(e, AA_U32, name)) {
- if (!inbounds(e, sizeof(u32)))
+ if (aa_unpack_nameX(e, AA_U32, name)) {
+ if (!aa_inbounds(e, sizeof(u32)))
goto fail;
if (data)
*data = le32_to_cpu(get_unaligned((__le32 *) e->pos));
@@ -331,13 +288,14 @@ 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)
{
void *pos = e->pos;
- if (unpack_nameX(e, AA_U64, name)) {
- if (!inbounds(e, sizeof(u64)))
+ if (aa_unpack_nameX(e, AA_U64, name)) {
+ if (!aa_inbounds(e, sizeof(u64)))
goto fail;
if (data)
*data = le64_to_cpu(get_unaligned((__le64 *) e->pos));
@@ -349,36 +307,57 @@ 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)
{
void *pos = e->pos;
- if (unpack_nameX(e, AA_ARRAY, name)) {
- int size;
- if (!inbounds(e, sizeof(u16)))
+ if (aa_unpack_nameX(e, AA_ARRAY, name)) {
+ if (!aa_inbounds(e, sizeof(u16)))
goto fail;
- size = (int)le16_to_cpu(get_unaligned((__le16 *) e->pos));
+ *size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
e->pos += sizeof(u16);
- return size;
+ return true;
}
fail:
e->pos = pos;
- return 0;
+ 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)
{
void *pos = e->pos;
- if (unpack_nameX(e, AA_BLOB, name)) {
+ if (aa_unpack_nameX(e, AA_BLOB, name)) {
u32 size;
- if (!inbounds(e, sizeof(u32)))
+ 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;
@@ -389,15 +368,16 @@ 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)
@@ -412,12 +392,13 @@ 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)
@@ -431,21 +412,23 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
return res;
}
+EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup);
/**
* 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
@@ -455,8 +438,8 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
size_t sz = blob - (char *) e->start -
((e->pos - e->start) & 7);
size_t pad = ALIGN(sz, 8) - sz;
- int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
- TO_ACCEPT2_FLAG(YYTD_DATA32) | DFA_FLAG_VERIFY_STATES;
+ if (aa_g_paranoid_load)
+ flags |= DFA_FLAG_VERIFY_STATES;
dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
if (IS_ERR(dfa))
@@ -470,37 +453,43 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
/**
* 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: true 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 *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 = kcalloc(size, sizeof(char *),
- GFP_KERNEL);
- if (!profile->file.trans.table)
+ table = kcalloc(size, sizeof(char *), GFP_KERNEL);
+ if (!table)
goto fail;
- profile->file.trans.size = size;
+ strs->table = table;
+ strs->size = size;
for (i = 0; i < size; i++) {
char *str;
- int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
- /* unpack_strdup verifies that the last character is
+ int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL);
+ /* aa_unpack_strdup verifies that the last character is
* null termination byte.
*/
if (!size2)
goto fail;
- profile->file.trans.table[i] = str;
+ table[i] = str;
/* verify that name doesn't start with space */
if (isspace(*str))
goto fail;
@@ -518,7 +507,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
goto fail;
/* beginning with : requires an embedded \0,
* verify that exactly 1 internal \0 exists
- * trailing \0 already verified by unpack_strdup
+ * trailing \0 already verified by aa_unpack_strdup
*
* convert \0 back to : for label_parse
*/
@@ -530,15 +519,15 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
/* fail - all other cases with embedded \0 */
goto fail;
}
- if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
goto fail;
- if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
}
return true;
fail:
- aa_free_domain_entries(&profile->file.trans);
+ aa_free_str_table(strs);
e->pos = saved_pos;
return false;
}
@@ -547,21 +536,23 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile)
{
void *pos = e->pos;
- if (unpack_nameX(e, AA_STRUCT, "xattrs")) {
- int i, size;
+ if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) {
+ u16 size;
+ int i;
- size = unpack_array(e, NULL);
- profile->xattr_count = size;
- profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL);
- if (!profile->xattrs)
+ 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 (!unpack_strdup(e, &profile->xattrs[i], NULL))
+ if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL))
goto fail;
}
- 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;
}
@@ -572,75 +563,78 @@ fail:
return false;
}
-static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile)
+static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules)
{
void *pos = e->pos;
- int i, size;
+ u16 size;
+ int i;
- if (unpack_nameX(e, AA_STRUCT, "secmark")) {
- size = unpack_array(e, NULL);
+ if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) {
+ if (!aa_unpack_array(e, NULL, &size))
+ goto fail;
- profile->secmark = kcalloc(size, sizeof(struct aa_secmark),
+ rules->secmark = kcalloc(size, sizeof(struct aa_secmark),
GFP_KERNEL);
- if (!profile->secmark)
+ if (!rules->secmark)
goto fail;
- profile->secmark_count = size;
+ rules->secmark_count = size;
for (i = 0; i < size; i++) {
- if (!unpack_u8(e, &profile->secmark[i].audit, NULL))
+ if (!unpack_u8(e, &rules->secmark[i].audit, NULL))
goto fail;
- if (!unpack_u8(e, &profile->secmark[i].deny, NULL))
+ if (!unpack_u8(e, &rules->secmark[i].deny, NULL))
goto fail;
- if (!unpack_strdup(e, &profile->secmark[i].label, NULL))
+ if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL))
goto fail;
}
- 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 true;
fail:
- if (profile->secmark) {
+ if (rules->secmark) {
for (i = 0; i < size; i++)
- kfree(profile->secmark[i].label);
- kfree(profile->secmark);
- profile->secmark_count = 0;
- profile->secmark = NULL;
+ 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 true;
@@ -650,6 +644,181 @@ fail:
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;
@@ -668,104 +837,137 @@ static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
/**
* unpack_profile - unpack a serialized profile
* @e: serialized data extent information (NOT NULL)
+ * @ns_name: pointer of newly allocated copy of %NULL in case of error
*
* NOTE: unpack profile sets audit struct if there is a failure
*/
static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
{
+ struct aa_ruleset *rules;
struct aa_profile *profile = NULL;
const char *tmpname, *tmpns = NULL, *name = NULL;
const char *info = "failed to unpack profile";
size_t ns_len;
struct rhashtable_params params = { 0 };
- char *key = NULL;
+ char *key = NULL, *disconnected = NULL;
struct aa_data *data;
- int i, error = -EPROTO;
+ int error = -EPROTO;
kernel_cap_t tmpcap;
u32 tmp;
*ns_name = NULL;
/* check that we have the right struct being passed */
- if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ if (!aa_unpack_nameX(e, AA_STRUCT, "profile"))
goto fail;
- if (!unpack_str(e, &name, NULL))
+ if (!aa_unpack_str(e, &name, NULL))
goto fail;
if (*name == '\0')
goto fail;
tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len);
if (tmpns) {
+ if (!tmpname) {
+ info = "empty profile name";
+ goto fail;
+ }
*ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL);
if (!*ns_name) {
info = "out of memory";
+ error = -ENOMEM;
goto fail;
}
name = tmpname;
}
profile = aa_alloc_profile(name, NULL, GFP_KERNEL);
- if (!profile)
- return ERR_PTR(-ENOMEM);
+ if (!profile) {
+ info = "out of memory";
+ error = -ENOMEM;
+ goto fail;
+ }
+ rules = profile->label.rules[0];
/* profile renaming is optional */
- (void) unpack_str(e, &profile->rename, "rename");
+ (void) aa_unpack_str(e, &profile->rename, "rename");
/* attachment string is optional */
- (void) unpack_str(e, &profile->attach, "attach");
+ (void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach");
/* xmatch is optional and may be NULL */
- profile->xmatch = unpack_dfa(e);
- if (IS_ERR(profile->xmatch)) {
- error = PTR_ERR(profile->xmatch);
- profile->xmatch = NULL;
+ error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info);
+ if (error) {
info = "bad xmatch";
goto fail;
}
- /* xmatch_len is not optional if xmatch is set */
- if (profile->xmatch) {
- if (!unpack_u32(e, &tmp, NULL)) {
+
+ /* neither xmatch_len not xmatch_perms are optional if xmatch is set */
+ if (profile->attach.xmatch->dfa) {
+ if (!aa_unpack_u32(e, &tmp, NULL)) {
info = "missing xmatch len";
goto fail;
}
- profile->xmatch_len = tmp;
+ profile->attach.xmatch_len = tmp;
+ profile->attach.xmatch->start[AA_CLASS_XMATCH] = DFA_START;
+ if (!profile->attach.xmatch->perms) {
+ error = aa_compat_map_xmatch(profile->attach.xmatch);
+ if (error) {
+ info = "failed to convert xmatch permission table";
+ goto fail;
+ }
+ }
}
/* disconnected attachment string is optional */
- (void) unpack_str(e, &profile->disconnected, "disconnected");
+ (void) aa_unpack_strdup(e, &disconnected, "disconnected");
+ profile->disconnected = disconnected;
+ /* optional */
+ (void) aa_unpack_u32(e, &profile->signal, "kill");
+ if (profile->signal < 1 || profile->signal > MAXMAPPED_SIG) {
+ info = "profile kill.signal invalid value";
+ goto fail;
+ }
/* per profile debug flags (complain, audit) */
- if (!unpack_nameX(e, AA_STRUCT, "flags")) {
+ if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) {
info = "profile missing flags";
goto fail;
}
info = "failed to unpack profile flags";
- if (!unpack_u32(e, &tmp, NULL))
+ if (!aa_unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp & PACKED_FLAG_HAT)
profile->label.flags |= FLAG_HAT;
- if (!unpack_u32(e, &tmp, NULL))
+ if (tmp & PACKED_FLAG_DEBUG1)
+ profile->label.flags |= FLAG_DEBUG1;
+ if (tmp & PACKED_FLAG_DEBUG2)
+ profile->label.flags |= FLAG_DEBUG2;
+ if (!aa_unpack_u32(e, &tmp, NULL))
goto fail;
- if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG))
+ if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) {
profile->mode = APPARMOR_COMPLAIN;
- else if (tmp == PACKED_MODE_ENFORCE)
+ } else if (tmp == PACKED_MODE_ENFORCE) {
profile->mode = APPARMOR_ENFORCE;
- else if (tmp == PACKED_MODE_KILL)
+ } else if (tmp == PACKED_MODE_KILL) {
profile->mode = APPARMOR_KILL;
- else if (tmp == PACKED_MODE_UNCONFINED)
+ } else if (tmp == PACKED_MODE_UNCONFINED) {
profile->mode = APPARMOR_UNCONFINED;
- else
+ profile->label.flags |= FLAG_UNCONFINED;
+ } else if (tmp == PACKED_MODE_USER) {
+ profile->mode = APPARMOR_USER;
+ } else {
goto fail;
- if (!unpack_u32(e, &tmp, NULL))
+ }
+ if (!aa_unpack_u32(e, &tmp, NULL))
goto fail;
if (tmp)
profile->audit = AUDIT_ALL;
- if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
/* path_flags is optional */
- if (unpack_u32(e, &profile->path_flags, "path_flags"))
+ if (aa_unpack_u32(e, &profile->path_flags, "path_flags"))
profile->path_flags |= profile->label.flags &
PATH_MEDIATE_DELETED;
else
@@ -773,38 +975,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
profile->path_flags = PATH_MEDIATE_DELETED;
info = "failed to unpack profile capabilities";
- if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
+ 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;
info = "failed to unpack upper profile capabilities";
- if (unpack_nameX(e, AA_STRUCT, "caps64")) {
+ 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;
}
info = "failed to unpack extended profile capabilities";
- if (unpack_nameX(e, AA_STRUCT, "capsx")) {
+ 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;
}
@@ -813,72 +1015,71 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
goto fail;
}
- if (!unpack_rlimits(e, profile)) {
+ if (!unpack_rlimits(e, rules)) {
info = "failed to unpack profile rlimits";
goto fail;
}
- if (!unpack_secmark(e, profile)) {
+ 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 */
info = "failed to unpack policydb";
- profile->policy.dfa = unpack_dfa(e);
- if (IS_ERR(profile->policy.dfa)) {
- error = PTR_ERR(profile->policy.dfa);
- profile->policy.dfa = NULL;
+ error = unpack_pdb(e, &rules->policy, true, false,
+ &info);
+ if (error)
goto fail;
- } else if (!profile->policy.dfa) {
- error = -EPROTO;
+ /* Fixup: drop when we get rid of start array */
+ if (aa_dfa_next(rules->policy->dfa, rules->policy->start[0],
+ AA_CLASS_FILE))
+ rules->policy->start[AA_CLASS_FILE] =
+ aa_dfa_next(rules->policy->dfa,
+ rules->policy->start[0],
+ AA_CLASS_FILE);
+ if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
goto fail;
+ if (!rules->policy->perms) {
+ error = aa_compat_map_policy(rules->policy,
+ e->version);
+ if (error) {
+ info = "failed to remap policydb permission table";
+ goto fail;
+ }
}
- if (!unpack_u32(e, &profile->policy.start[0], "start"))
- /* default start state */
- profile->policy.start[0] = DFA_START;
- /* setup class index */
- for (i = AA_CLASS_FILE; i <= AA_CLASS_LAST; i++) {
- profile->policy.start[i] =
- aa_dfa_next(profile->policy.dfa,
- profile->policy.start[0],
- i);
- }
- if (!unpack_nameX(e, AA_STRUCTEND, NULL))
- goto fail;
- } else
- profile->policy.dfa = aa_get_dfa(nulldfa);
-
+ } else {
+ rules->policy = aa_get_pdb(nullpdb);
+ }
/* get file rules */
- profile->file.dfa = unpack_dfa(e);
- if (IS_ERR(profile->file.dfa)) {
- error = PTR_ERR(profile->file.dfa);
- profile->file.dfa = NULL;
- info = "failed to unpack profile file rules";
- goto fail;
- } else if (profile->file.dfa) {
- if (!unpack_u32(e, &profile->file.start, "dfa_start"))
- /* default start state */
- profile->file.start = DFA_START;
- } else if (profile->policy.dfa &&
- profile->policy.start[AA_CLASS_FILE]) {
- profile->file.dfa = aa_get_dfa(profile->policy.dfa);
- profile->file.start = profile->policy.start[AA_CLASS_FILE];
- } else
- profile->file.dfa = aa_get_dfa(nulldfa);
-
- if (!unpack_trans_table(e, profile)) {
- info = "failed to unpack profile transition table";
+ 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);
}
-
- if (unpack_nameX(e, AA_STRUCT, "data")) {
+ error = -EPROTO;
+ if (aa_unpack_nameX(e, AA_STRUCT, "data")) {
info = "out of memory";
profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
- if (!profile->data)
+ if (!profile->data) {
+ error = -ENOMEM;
goto fail;
-
+ }
params.nelem_hint = 3;
params.key_len = sizeof(void *);
params.key_offset = offsetof(struct aa_data, key);
@@ -891,40 +1092,57 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
goto fail;
}
- while (unpack_strdup(e, &key, NULL)) {
+ while (aa_unpack_strdup(e, &key, NULL)) {
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
kfree_sensitive(key);
+ error = -ENOMEM;
goto fail;
}
data->key = key;
- data->size = unpack_blob(e, &data->data, NULL);
- data->data = kvmemdup(data->data, data->size);
+ data->size = aa_unpack_blob(e, &data->data, NULL);
+ data->data = kvmemdup(data->data, data->size, GFP_KERNEL);
if (data->size && !data->data) {
kfree_sensitive(data->key);
kfree_sensitive(data);
+ error = -ENOMEM;
goto fail;
}
- rhashtable_insert_fast(profile->data, &data->head,
- profile->data->p);
+ if (rhashtable_insert_fast(profile->data, &data->head,
+ profile->data->p)) {
+ kvfree_sensitive(data->data, data->size);
+ kfree_sensitive(data->key);
+ kfree_sensitive(data);
+ info = "failed to insert data to table";
+ goto fail;
+ }
}
- if (!unpack_nameX(e, AA_STRUCTEND, NULL)) {
+ if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
info = "failed to unpack end of key, value data table";
goto fail;
}
}
- if (!unpack_nameX(e, AA_STRUCTEND, NULL)) {
+ if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
info = "failed to unpack end of profile";
goto fail;
}
+ aa_compute_profile_mediates(profile);
+
return profile;
fail:
+ if (error == 0)
+ /* default error covers most cases */
+ error = -EPROTO;
+ if (*ns_name) {
+ kfree(*ns_name);
+ *ns_name = NULL;
+ }
if (profile)
name = NULL;
else if (!name)
@@ -936,7 +1154,7 @@ fail:
}
/**
- * 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)
@@ -950,7 +1168,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
*ns = NULL;
/* get the interface version */
- if (!unpack_u32(e, &e->version, "version")) {
+ if (!aa_unpack_u32(e, &e->version, "version")) {
if (required) {
audit_iface(NULL, NULL, NULL, "invalid profile format",
e, error);
@@ -962,14 +1180,14 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
* if not specified use previous version
* Mask off everything that is not kernel abi version
*/
- if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v7)) {
+ if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) {
audit_iface(NULL, NULL, NULL, "unsupported interface version",
e, error);
return error;
}
/* read the namespace if present */
- if (unpack_str(e, &name, "namespace")) {
+ if (aa_unpack_str(e, &name, "namespace")) {
if (*name == '\0') {
audit_iface(NULL, NULL, NULL, "invalid namespace name",
e, error);
@@ -988,24 +1206,71 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
return 0;
}
-static bool verify_xindex(int xindex, int table_size)
+/**
+ * verify_dfa_accept_index - verify accept indexes are in range of perms table
+ * @dfa: the dfa to check accept indexes are in range
+ * @table_size: the permission table size the indexes should be within
+ */
+static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size)
+{
+ int i;
+ for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
+ if (ACCEPT_TABLE(dfa)[i] >= table_size)
+ return false;
+ }
+ return true;
+}
+
+static bool verify_perm(struct aa_perms *perm)
{
- int index, xtype;
- xtype = xindex & AA_X_TYPE_MASK;
- index = xindex & AA_X_INDEX_MASK;
- if (xtype == AA_X_TABLE && index >= table_size)
+ /* 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))
+ int xidx, xmax = -1;
+
+ for (i = 0; i < pdb->size; i++) {
+ if (!verify_perm(&pdb->perms[i]))
return false;
- if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
+ /* 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 true;
@@ -1016,14 +1281,44 @@ static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
* @profile: profile to verify (NOT NULL)
*
* Returns: 0 if passes verification else error
+ *
+ * This verification is post any unpack mapping or changes
*/
static int verify_profile(struct aa_profile *profile)
{
- if (profile->file.dfa &&
- !verify_dfa_xindex(profile->file.dfa,
- profile->file.trans.size)) {
- audit_iface(profile, NULL, NULL, "Invalid named transition",
- NULL, -EPROTO);
+ struct aa_ruleset *rules = profile->label.rules[0];
+
+ if (!rules)
+ return 0;
+
+ if (rules->file->dfa && !verify_dfa_accept_index(rules->file->dfa,
+ rules->file->size)) {
+ audit_iface(profile, NULL, NULL,
+ "Unpack: file Invalid named transition", NULL,
+ -EPROTO);
+ return -EPROTO;
+ }
+ if (rules->policy->dfa &&
+ !verify_dfa_accept_index(rules->policy->dfa, rules->policy->size)) {
+ audit_iface(profile, NULL, NULL,
+ "Unpack: policy Invalid named transition", NULL,
+ -EPROTO);
+ return -EPROTO;
+ }
+
+ if (!verify_perms(rules->file)) {
+ audit_iface(profile, NULL, NULL,
+ "Unpack: Invalid perm index", NULL, -EPROTO);
+ return -EPROTO;
+ }
+ if (!verify_perms(rules->policy)) {
+ audit_iface(profile, NULL, NULL,
+ "Unpack: Invalid perm index", NULL, -EPROTO);
+ return -EPROTO;
+ }
+ if (!verify_perms(profile->attach.xmatch)) {
+ audit_iface(profile, NULL, NULL,
+ "Unpack: Invalid perm index", NULL, -EPROTO);
return -EPROTO;
}
@@ -1049,85 +1344,81 @@ struct aa_load_ent *aa_load_ent_alloc(void)
return ent;
}
-static int deflate_compress(const char *src, size_t slen, char **dst,
- size_t *dlen)
+static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen)
{
- int error;
- struct z_stream_s strm;
- void *stgbuf, *dstbuf;
- size_t stglen = deflateBound(slen);
-
- memset(&strm, 0, sizeof(strm));
-
- if (stglen < slen)
- return -EFBIG;
-
- strm.workspace = kvzalloc(zlib_deflate_workspacesize(MAX_WBITS,
- MAX_MEM_LEVEL),
- GFP_KERNEL);
- if (!strm.workspace)
- return -ENOMEM;
-
- error = zlib_deflateInit(&strm, aa_g_rawdata_compression_level);
- if (error != Z_OK) {
- error = -ENOMEM;
- goto fail_deflate_init;
+#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(&params.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;
}
- stgbuf = kvzalloc(stglen, GFP_KERNEL);
- if (!stgbuf) {
- error = -ENOMEM;
- goto fail_stg_alloc;
+ wksp = kvzalloc(wksp_len, GFP_KERNEL);
+ if (!wksp) {
+ ret = -ENOMEM;
+ goto cleanup;
}
- strm.next_in = src;
- strm.avail_in = slen;
- strm.next_out = stgbuf;
- strm.avail_out = stglen;
+ ctx = zstd_init_cctx(wksp, wksp_len);
+ if (!ctx) {
+ ret = -EINVAL;
+ goto cleanup;
+ }
- error = zlib_deflate(&strm, Z_FINISH);
- if (error != Z_STREAM_END) {
- error = -EINVAL;
- goto fail_deflate;
+ out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, &params);
+ if (zstd_is_error(out_len) || out_len >= slen) {
+ ret = -EINVAL;
+ goto cleanup;
}
- error = 0;
- if (is_vmalloc_addr(stgbuf)) {
- dstbuf = kvzalloc(strm.total_out, GFP_KERNEL);
- if (dstbuf) {
- memcpy(dstbuf, stgbuf, strm.total_out);
- kvfree(stgbuf);
+ if (is_vmalloc_addr(out)) {
+ *dst = kvzalloc(out_len, GFP_KERNEL);
+ if (*dst) {
+ memcpy(*dst, out, out_len);
+ kvfree(out);
+ out = NULL;
}
- } else
+ } 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
*/
- dstbuf = krealloc(stgbuf, strm.total_out, GFP_KERNEL);
+ *dst = krealloc(out, out_len, GFP_KERNEL);
+ }
- if (!dstbuf) {
- error = -ENOMEM;
- goto fail_deflate;
+ if (!*dst) {
+ ret = -ENOMEM;
+ goto cleanup;
}
- *dst = dstbuf;
- *dlen = strm.total_out;
+ *dlen = out_len;
-fail_stg_alloc:
- zlib_deflateEnd(&strm);
-fail_deflate_init:
- kvfree(strm.workspace);
- return error;
+cleanup:
+ if (ret) {
+ kvfree(out);
+ *dst = NULL;
+ }
-fail_deflate:
- kvfree(stgbuf);
- goto fail_stg_alloc;
+ kvfree(wksp);
+ return ret;
+#else
+ *dlen = slen;
+ return 0;
+#endif
}
static int compress_loaddata(struct aa_loaddata *data)
{
-
AA_BUG(data->compressed_size > 0);
/*
@@ -1136,12 +1427,14 @@ static int compress_loaddata(struct aa_loaddata *data)
*/
if (aa_g_rawdata_compression_level != 0) {
void *udata = data->data;
- int error = deflate_compress(udata, data->size, &data->data,
- &data->compressed_size);
- if (error)
+ int error = compress_zstd(udata, data->size, &data->data,
+ &data->compressed_size);
+ if (error) {
+ data->compressed_size = data->size;
return error;
-
- kvfree(udata);
+ }
+ if (udata != data->data)
+ kvfree(udata);
} else
data->compressed_size = data->size;
@@ -1165,6 +1458,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
{
struct aa_load_ent *tmp, *ent;
struct aa_profile *profile = NULL;
+ char *ns_name = NULL;
int error;
struct aa_ext e = {
.start = udata->data,
@@ -1174,7 +1468,6 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
*ns = NULL;
while (e.pos < e.end) {
- char *ns_name = NULL;
void *start;
error = verify_header(&e, e.pos == e.start, ns);
if (error)
@@ -1205,6 +1498,7 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
ent->new = profile;
ent->ns_name = ns_name;
+ ns_name = NULL;
list_add_tail(&ent->list, lh);
}
udata->abi = e.version & K_ABI_MASK;
@@ -1216,12 +1510,16 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
goto fail;
}
}
- error = compress_loaddata(udata);
- if (error)
- goto fail;
+
+ if (aa_g_export_binary) {
+ error = compress_loaddata(udata);
+ if (error)
+ goto fail;
+ }
return 0;
fail_profile:
+ kfree(ns_name);
aa_put_profile(profile);
fail:
@@ -1232,7 +1530,3 @@ fail:
return error;
}
-
-#ifdef CONFIG_SECURITY_APPARMOR_KUNIT_TEST
-#include "policy_unpack_test.c"
-#endif /* CONFIG_SECURITY_APPARMOR_KUNIT_TEST */
diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c
index 533137f45361..cf18744dafe2 100644
--- a/security/apparmor/policy_unpack_test.c
+++ b/security/apparmor/policy_unpack_test.c
@@ -4,10 +4,13 @@
*/
#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 \
@@ -43,13 +46,15 @@
#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;
};
-struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,
- struct kunit *test, size_t buf_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;
@@ -66,31 +71,30 @@ struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_STRING_NAME) + 1;
- strcpy(buf + 3, TEST_STRING_NAME);
+ 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;
- strcpy(buf + 3, TEST_STRING_DATA);
-
+ 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;
- strcpy(buf + 3, TEST_U32_NAME);
+ strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32;
- *((u32 *)(buf + 3 + strlen(TEST_U32_NAME) + 2)) = TEST_U32_DATA;
+ 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;
- strcpy(buf + 3, TEST_U64_NAME);
+ strscpy(buf + 3, TEST_U64_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_U64_NAME) + 1) = AA_U64;
- *((u64 *)(buf + 3 + strlen(TEST_U64_NAME) + 2)) = TEST_U64_DATA;
+ *((__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;
- strcpy(buf + 3, TEST_BLOB_NAME);
+ 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,
@@ -99,9 +103,9 @@ struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,
buf = e->start + TEST_NAMED_ARRAY_BUF_OFFSET;
*buf = AA_NAME;
*(buf + 1) = strlen(TEST_ARRAY_NAME) + 1;
- strcpy(buf + 3, TEST_ARRAY_NAME);
+ strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3));
*(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY;
- *((u16 *)(buf + 3 + strlen(TEST_ARRAY_NAME) + 2)) = TEST_ARRAY_SIZE;
+ put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2);
return e;
}
@@ -125,27 +129,26 @@ static void policy_unpack_test_inbounds_when_inbounds(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
- KUNIT_EXPECT_TRUE(test, inbounds(puf->e, 0));
- KUNIT_EXPECT_TRUE(test, inbounds(puf->e, puf->e_size / 2));
- KUNIT_EXPECT_TRUE(test, inbounds(puf->e, puf->e_size));
+ 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, inbounds(puf->e, puf->e_size + 1));
+ 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;
+ u16 array_size = 0;
puf->e->pos += TEST_ARRAY_BUF_OFFSET;
- array_size = unpack_array(puf->e, NULL);
-
+ 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);
@@ -155,12 +158,11 @@ 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;
+ u16 array_size = 0;
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
- array_size = unpack_array(puf->e, name);
-
+ 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);
@@ -175,9 +177,7 @@ static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test)
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16);
- array_size = unpack_array(puf->e, name);
-
- KUNIT_EXPECT_EQ(test, array_size, (u16)0);
+ 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);
}
@@ -189,7 +189,7 @@ static void policy_unpack_test_unpack_blob_with_null_name(struct kunit *test)
size_t size;
puf->e->pos += TEST_BLOB_BUF_OFFSET;
- size = unpack_blob(puf->e, &blob, NULL);
+ size = aa_unpack_blob(puf->e, &blob, NULL);
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
KUNIT_EXPECT_TRUE(test,
@@ -203,7 +203,7 @@ static void policy_unpack_test_unpack_blob_with_name(struct kunit *test)
size_t size;
puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET;
- size = unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
+ size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
KUNIT_EXPECT_TRUE(test,
@@ -222,7 +222,7 @@ static void policy_unpack_test_unpack_blob_out_of_bounds(struct kunit *test)
puf->e->end = puf->e->start + TEST_BLOB_BUF_OFFSET
+ TEST_BLOB_DATA_SIZE - 1;
- size = unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
+ 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);
@@ -235,7 +235,7 @@ static void policy_unpack_test_unpack_str_with_null_name(struct kunit *test)
size_t size;
puf->e->pos += TEST_STRING_BUF_OFFSET;
- size = unpack_str(puf->e, &string, NULL);
+ 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);
@@ -247,7 +247,7 @@ static void policy_unpack_test_unpack_str_with_name(struct kunit *test)
const char *string = NULL;
size_t size;
- size = unpack_str(puf->e, &string, TEST_STRING_NAME);
+ 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);
@@ -263,7 +263,7 @@ static void policy_unpack_test_unpack_str_out_of_bounds(struct kunit *test)
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
+ strlen(TEST_STRING_DATA) - 1;
- size = unpack_str(puf->e, &string, TEST_STRING_NAME);
+ 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);
@@ -276,13 +276,15 @@ static void policy_unpack_test_unpack_strdup_with_null_name(struct kunit *test)
size_t size;
puf->e->pos += TEST_STRING_BUF_OFFSET;
- size = unpack_strdup(puf->e, &string, NULL);
+ 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)
@@ -291,13 +293,15 @@ static void policy_unpack_test_unpack_strdup_with_name(struct kunit *test)
char *string = NULL;
size_t size;
- size = unpack_strdup(puf->e, &string, TEST_STRING_NAME);
+ 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)
@@ -310,11 +314,13 @@ static void policy_unpack_test_unpack_strdup_out_of_bounds(struct kunit *test)
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
+ strlen(TEST_STRING_DATA) - 1;
- size = unpack_strdup(puf->e, &string, TEST_STRING_NAME);
+ size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME);
KUNIT_EXPECT_EQ(test, size, 0);
- KUNIT_EXPECT_PTR_EQ(test, string, (char *)NULL);
+ 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)
@@ -324,7 +330,7 @@ static void policy_unpack_test_unpack_nameX_with_null_name(struct kunit *test)
puf->e->pos += TEST_U32_BUF_OFFSET;
- success = unpack_nameX(puf->e, AA_U32, NULL);
+ success = aa_unpack_nameX(puf->e, AA_U32, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -338,7 +344,7 @@ static void policy_unpack_test_unpack_nameX_with_wrong_code(struct kunit *test)
puf->e->pos += TEST_U32_BUF_OFFSET;
- success = unpack_nameX(puf->e, AA_BLOB, NULL);
+ success = aa_unpack_nameX(puf->e, AA_BLOB, NULL);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -353,7 +359,7 @@ static void policy_unpack_test_unpack_nameX_with_name(struct kunit *test)
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
- success = unpack_nameX(puf->e, AA_U32, name);
+ success = aa_unpack_nameX(puf->e, AA_U32, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -368,7 +374,7 @@ static void policy_unpack_test_unpack_nameX_with_wrong_name(struct kunit *test)
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
- success = unpack_nameX(puf->e, AA_U32, name);
+ success = aa_unpack_nameX(puf->e, AA_U32, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -389,12 +395,12 @@ static void policy_unpack_test_unpack_u16_chunk_basic(struct kunit *test)
*/
puf->e->end += TEST_U16_DATA;
- size = unpack_u16_chunk(puf->e, &chunk);
+ size = aa_unpack_u16_chunk(puf->e, &chunk);
- KUNIT_EXPECT_PTR_EQ(test, (void *)chunk,
+ KUNIT_EXPECT_PTR_EQ(test, chunk,
puf->e->start + TEST_U16_OFFSET + 2);
- KUNIT_EXPECT_EQ(test, size, (size_t)TEST_U16_DATA);
- KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, (void *)(chunk + TEST_U16_DATA));
+ 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(
@@ -406,10 +412,10 @@ static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_1(
puf->e->pos = puf->e->end - 1;
- size = unpack_u16_chunk(puf->e, &chunk);
+ size = aa_unpack_u16_chunk(puf->e, &chunk);
- KUNIT_EXPECT_EQ(test, size, (size_t)0);
- KUNIT_EXPECT_PTR_EQ(test, chunk, (char *)NULL);
+ KUNIT_EXPECT_EQ(test, size, 0);
+ KUNIT_EXPECT_NULL(test, chunk);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, puf->e->end - 1);
}
@@ -428,10 +434,10 @@ static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_2(
*/
puf->e->end = puf->e->pos + TEST_U16_DATA - 1;
- size = unpack_u16_chunk(puf->e, &chunk);
+ size = aa_unpack_u16_chunk(puf->e, &chunk);
- KUNIT_EXPECT_EQ(test, size, (size_t)0);
- KUNIT_EXPECT_PTR_EQ(test, chunk, (char *)NULL);
+ 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);
}
@@ -439,11 +445,11 @@ static void policy_unpack_test_unpack_u32_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
- u32 data;
+ u32 data = 0;
puf->e->pos += TEST_U32_BUF_OFFSET;
- success = unpack_u32(puf->e, &data, NULL);
+ success = aa_unpack_u32(puf->e, &data, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
@@ -456,11 +462,11 @@ 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;
+ u32 data = 0;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
- success = unpack_u32(puf->e, &data, name);
+ success = aa_unpack_u32(puf->e, &data, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
@@ -473,12 +479,12 @@ 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;
+ u32 data = 0;
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32);
- success = unpack_u32(puf->e, &data, name);
+ success = aa_unpack_u32(puf->e, &data, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -489,11 +495,11 @@ static void policy_unpack_test_unpack_u64_with_null_name(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
bool success;
- u64 data;
+ u64 data = 0;
puf->e->pos += TEST_U64_BUF_OFFSET;
- success = unpack_u64(puf->e, &data, NULL);
+ success = aa_unpack_u64(puf->e, &data, NULL);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
@@ -506,11 +512,11 @@ 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;
+ u64 data = 0;
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
- success = unpack_u64(puf->e, &data, name);
+ success = aa_unpack_u64(puf->e, &data, name);
KUNIT_EXPECT_TRUE(test, success);
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
@@ -523,12 +529,12 @@ 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;
+ u64 data = 0;
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
puf->e->end = puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64);
- success = unpack_u64(puf->e, &data, name);
+ success = aa_unpack_u64(puf->e, &data, name);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
@@ -538,7 +544,7 @@ static void policy_unpack_test_unpack_u64_out_of_bounds(struct kunit *test)
static void policy_unpack_test_unpack_X_code_match(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
- bool success = unpack_X(puf->e, AA_NAME);
+ 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);
@@ -547,7 +553,7 @@ static void policy_unpack_test_unpack_X_code_match(struct kunit *test)
static void policy_unpack_test_unpack_X_code_mismatch(struct kunit *test)
{
struct policy_unpack_fixture *puf = test->priv;
- bool success = unpack_X(puf->e, AA_STRING);
+ bool success = aa_unpack_X(puf->e, AA_STRING);
KUNIT_EXPECT_FALSE(test, success);
KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start);
@@ -559,7 +565,7 @@ static void policy_unpack_test_unpack_X_out_of_bounds(struct kunit *test)
bool success;
puf->e->pos = puf->e->end;
- success = unpack_X(puf->e, AA_NAME);
+ success = aa_unpack_X(puf->e, AA_NAME);
KUNIT_EXPECT_FALSE(test, success);
}
@@ -605,3 +611,6 @@ static struct kunit_suite apparmor_policy_unpack_test_module = {
};
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 fde332e0ea7d..ce40f15d4952 100644
--- a/security/apparmor/procattr.c
+++ b/security/apparmor/procattr.c
@@ -17,18 +17,18 @@
/**
- * aa_getprocattr - Return the profile information for @profile
- * @profile: the profile to print profile info about (NOT NULL)
- * @string: Returns - string containing the profile info (NOT NULL)
+ * aa_getprocattr - Return the label information for @label
+ * @label: the label to print label info about (NOT NULL)
+ * @string: Returns - string containing the label info (NOT NULL)
+ * @newline: indicates that a newline should be added
*
- * Requires: profile != NULL
+ * Requires: label != NULL && string != NULL
*
- * Creates a string containing the namespace_name://profile_name for
- * @profile.
+ * Creates a string containing the label information for @label.
*
* Returns: size of string placed in @string else error code on failure
*/
-int aa_getprocattr(struct aa_label *label, char **string)
+int aa_getprocattr(struct aa_label *label, char **string, bool newline)
{
struct aa_ns *ns = labels_ns(label);
struct aa_ns *current_ns = aa_get_current_ns();
@@ -58,11 +58,12 @@ int aa_getprocattr(struct aa_label *label, char **string)
return len;
}
- (*string)[len] = '\n';
- (*string)[len + 1] = 0;
+ if (newline)
+ (*string)[len++] = '\n';
+ (*string)[len] = 0;
aa_put_ns(current_ns);
- return len + 1;
+ return len;
}
/**
@@ -90,7 +91,7 @@ static char *split_token_from_name(const char *op, char *args, u64 *token)
}
/**
- * aa_setprocattr_chagnehat - handle procattr interface to change_hat
+ * aa_setprocattr_changehat - handle procattr interface to change_hat
* @args: args received from writing to /proc/<pid>/attr/current (NOT NULL)
* @size: size of the args
* @flags: set of flags governing behavior
@@ -124,12 +125,14 @@ int aa_setprocattr_changehat(char *args, size_t size, int flags)
for (count = 0; (hat < end) && count < 16; ++count) {
char *next = hat + strlen(hat) + 1;
hats[count] = hat;
- AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
+ AA_DEBUG(DEBUG_DOMAIN,
+ "%s: (pid %d) Magic 0x%llx count %d hat '%s'\n"
, __func__, current->pid, token, count, hat);
hat = next;
}
} else
- AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
+ AA_DEBUG(DEBUG_DOMAIN,
+ "%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n",
__func__, current->pid, token, count, "<NULL>");
return aa_change_hat(hats, count, token, flags);
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
index 1ae4874251a9..8e80db3ae21c 100644
--- a/security/apparmor/resource.c
+++ b/security/apparmor/resource.c
@@ -30,42 +30,49 @@ struct aa_sfs_entry aa_sfs_entry_rlimit[] = {
static void audit_cb(struct audit_buffer *ab, void *va)
{
struct common_audit_data *sa = va;
+ struct apparmor_audit_data *ad = aad(sa);
audit_log_format(ab, " rlimit=%s value=%lu",
- rlim_names[aad(sa)->rlim.rlim], aad(sa)->rlim.max);
- if (aad(sa)->peer) {
+ rlim_names[ad->rlim.rlim], ad->rlim.max);
+ if (ad->peer) {
audit_log_format(ab, " peer=");
- aa_label_xaudit(ab, labels_ns(aad(sa)->label), aad(sa)->peer,
+ aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
FLAGS_NONE, GFP_ATOMIC);
}
}
/**
* audit_resource - audit setting resource limit
+ * @subj_cred: cred setting the resource
* @profile: profile being enforced (NOT NULL)
* @resource: rlimit being auditing
* @value: value being set
+ * @peer: aa_albel of the task being set
+ * @info: info being auditing
* @error: error value
*
- * Returns: 0 or sa->error else other error code on failure
+ * Returns: 0 or ad->error else other error code on failure
*/
-static int audit_resource(struct aa_profile *profile, unsigned int resource,
+static int audit_resource(const struct cred *subj_cred,
+ struct aa_profile *profile, unsigned int resource,
unsigned long value, struct aa_label *peer,
const char *info, int error)
{
- DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETRLIMIT);
+ DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_RLIMITS,
+ OP_SETRLIMIT);
- aad(&sa)->rlim.rlim = resource;
- aad(&sa)->rlim.max = value;
- aad(&sa)->peer = peer;
- aad(&sa)->info = info;
- aad(&sa)->error = error;
+ ad.subj_cred = subj_cred;
+ ad.rlim.rlim = resource;
+ ad.rlim.max = value;
+ ad.peer = peer;
+ ad.info = info;
+ ad.error = error;
- return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa, audit_cb);
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, &ad, audit_cb);
}
/**
- * aa_map_resouce - map compiled policy resource to internal #
+ * aa_map_resource - map compiled policy resource to internal #
* @resource: flattened policy resource number
*
* Returns: resource # for the current architecture.
@@ -78,30 +85,34 @@ int aa_map_resource(int resource)
return rlim_map[resource];
}
-static int profile_setrlimit(struct aa_profile *profile, unsigned int resource,
+static int profile_setrlimit(const struct cred *subj_cred,
+ struct aa_profile *profile, unsigned int resource,
struct rlimit *new_rlim)
{
+ struct aa_ruleset *rules = profile->label.rules[0];
int e = 0;
- if (profile->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
- profile->rlimits.limits[resource].rlim_max)
+ if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max >
+ rules->rlimits.limits[resource].rlim_max)
e = -EACCES;
- return audit_resource(profile, resource, new_rlim->rlim_max, NULL, NULL,
- e);
+ return audit_resource(subj_cred, profile, resource, new_rlim->rlim_max,
+ NULL, NULL, e);
}
/**
* aa_task_setrlimit - test permission to set an rlimit
- * @label - label confining the task (NOT NULL)
- * @task - task the resource is being set on
- * @resource - the resource being set
- * @new_rlim - the new resource limit (NOT NULL)
+ * @subj_cred: cred setting the limit
+ * @label: label confining the task (NOT NULL)
+ * @task: task the resource is being set on
+ * @resource: the resource being set
+ * @new_rlim: the new resource limit (NOT NULL)
*
* Control raising the processes hard limit.
*
* Returns: 0 or error code if setting resource failed
*/
-int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
+int aa_task_setrlimit(const struct cred *subj_cred, struct aa_label *label,
+ struct task_struct *task,
unsigned int resource, struct rlimit *new_rlim)
{
struct aa_profile *profile;
@@ -120,14 +131,15 @@ int aa_task_setrlimit(struct aa_label *label, struct task_struct *task,
*/
if (label != peer &&
- aa_capable(label, CAP_SYS_RESOURCE, CAP_OPT_NOAUDIT) != 0)
+ aa_capable(subj_cred, label, CAP_SYS_RESOURCE, CAP_OPT_NOAUDIT) != 0)
error = fn_for_each(label, profile,
- audit_resource(profile, resource,
+ audit_resource(subj_cred, profile, resource,
new_rlim->rlim_max, peer,
"cap_sys_resource", -EACCES));
else
error = fn_for_each_confined(label, profile,
- profile_setrlimit(profile, resource, new_rlim));
+ profile_setrlimit(subj_cred, profile, resource,
+ new_rlim));
aa_put_label(peer);
return error;
@@ -152,12 +164,13 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
* to the lesser of the tasks hard limit and the init tasks soft limit
*/
label_for_each_confined(i, old_l, old) {
- if (old->rlimits.mask) {
+ struct aa_ruleset *rules = old->label.rules[0];
+ if (rules->rlimits.mask) {
int j;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++,
mask <<= 1) {
- if (old->rlimits.mask & mask) {
+ if (rules->rlimits.mask & mask) {
rlim = current->signal->rlim + j;
initrlim = init_task.signal->rlim + j;
rlim->rlim_cur = min(rlim->rlim_max,
@@ -169,17 +182,18 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l)
/* set any new hard limits as dictated by the new profile */
label_for_each_confined(i, new_l, new) {
+ struct aa_ruleset *rules = new->label.rules[0];
int j;
- if (!new->rlimits.mask)
+ if (!rules->rlimits.mask)
continue;
for (j = 0, mask = 1; j < RLIM_NLIMITS; j++, mask <<= 1) {
- if (!(new->rlimits.mask & mask))
+ if (!(rules->rlimits.mask & mask))
continue;
rlim = current->signal->rlim + j;
rlim->rlim_max = min(rlim->rlim_max,
- new->rlimits.limits[j].rlim_max);
+ rules->rlimits.limits[j].rlim_max);
/* soft limit should not exceed hard limit */
rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
}
diff --git a/security/apparmor/secid.c b/security/apparmor/secid.c
index ce545f99259e..28caf66b9033 100644
--- a/security/apparmor/secid.c
+++ b/security/apparmor/secid.c
@@ -13,9 +13,9 @@
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/gfp.h>
-#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include <linux/xarray.h>
#include "include/cred.h"
#include "include/lib.h"
@@ -29,8 +29,9 @@
*/
#define AA_FIRST_SECID 2
-static DEFINE_IDR(aa_secids);
-static DEFINE_SPINLOCK(secid_lock);
+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?
@@ -38,61 +39,58 @@ static DEFINE_SPINLOCK(secid_lock);
* TODO: use secid_update in label replace
*/
-/**
- * aa_secid_update - update a secid mapping to a new label
- * @secid: secid to update
- * @label: label the secid will now map to
- */
-void aa_secid_update(u32 secid, struct aa_label *label)
-{
- unsigned long flags;
-
- spin_lock_irqsave(&secid_lock, flags);
- idr_replace(&aa_secids, label, secid);
- spin_unlock_irqrestore(&secid_lock, flags);
-}
-
-/**
- *
+/*
* see label for inverse aa_label_to_secid
*/
struct aa_label *aa_secid_to_label(u32 secid)
{
- struct aa_label *label;
-
- rcu_read_lock();
- label = idr_find(&aa_secids, secid);
- rcu_read_unlock();
-
- return label;
+ return xa_load(&aa_secids, secid);
}
-int apparmor_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
+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 */
- struct aa_label *label = aa_secid_to_label(secid);
+ int flags = FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED | FLAG_ABS_ROOT;
int len;
- AA_BUG(!seclen);
-
if (!label)
return -EINVAL;
- if (secdata)
- len = aa_label_asxprint(secdata, root_ns, label,
- FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
- FLAG_HIDDEN_UNCONFINED | FLAG_ABS_ROOT,
- GFP_ATOMIC);
+ 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,
- FLAG_SHOW_MODE | FLAG_VIEW_SUBNS |
- FLAG_HIDDEN_UNCONFINED | FLAG_ABS_ROOT);
+ len = aa_label_snxprint(NULL, 0, root_ns, label, flags);
+
if (len < 0)
return -ENOMEM;
- *seclen = len;
+ if (cp) {
+ cp->len = len;
+ cp->id = LSM_ID_APPARMOR;
+ }
- return 0;
+ 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)
@@ -108,9 +106,13 @@ int apparmor_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)
return 0;
}
-void apparmor_release_secctx(char *secdata, u32 seclen)
+void apparmor_release_secctx(struct lsm_context *cp)
{
- kfree(secdata);
+ if (cp->id == LSM_ID_APPARMOR) {
+ kfree(cp->context);
+ cp->context = NULL;
+ cp->id = LSM_ID_UNDEF;
+ }
}
/**
@@ -126,19 +128,16 @@ int aa_alloc_secid(struct aa_label *label, gfp_t gfp)
unsigned long flags;
int ret;
- idr_preload(gfp);
- spin_lock_irqsave(&secid_lock, flags);
- ret = idr_alloc(&aa_secids, label, AA_FIRST_SECID, 0, GFP_ATOMIC);
- spin_unlock_irqrestore(&secid_lock, flags);
- idr_preload_end();
+ 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;
}
- AA_BUG(ret == AA_SECID_INVALID);
- label->secid = ret;
return 0;
}
@@ -150,12 +149,7 @@ void aa_free_secid(u32 secid)
{
unsigned long flags;
- spin_lock_irqsave(&secid_lock, flags);
- idr_remove(&aa_secids, secid);
- spin_unlock_irqrestore(&secid_lock, flags);
-}
-
-void aa_secids_init(void)
-{
- idr_init_base(&aa_secids, AA_FIRST_SECID);
+ xa_lock_irqsave(&aa_secids, flags);
+ __xa_erase(&aa_secids, secid);
+ xa_unlock_irqrestore(&aa_secids, flags);
}
diff --git a/security/apparmor/task.c b/security/apparmor/task.c
index d17130ee6795..c9bc9cc69475 100644
--- a/security/apparmor/task.c
+++ b/security/apparmor/task.c
@@ -12,7 +12,12 @@
* 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"
/**
@@ -26,7 +31,7 @@ struct aa_label *aa_get_task_label(struct task_struct *task)
struct aa_label *p;
rcu_read_lock();
- p = aa_get_newest_label(__aa_task_raw_label(task));
+ p = aa_get_newest_cred_label(__task_cred(task));
rcu_read_unlock();
return p;
@@ -88,9 +93,8 @@ int aa_replace_current_label(struct aa_label *label)
* aa_set_current_onexec - set the tasks change_profile to happen onexec
* @label: system label to set at exec (MAYBE NULL to clear value)
* @stack: whether stacking should be done
- * Returns: 0 or error on failure
*/
-int aa_set_current_onexec(struct aa_label *label, bool stack)
+void aa_set_current_onexec(struct aa_label *label, bool stack)
{
struct aa_task_ctx *ctx = task_ctx(current);
@@ -98,8 +102,6 @@ int aa_set_current_onexec(struct aa_label *label, bool stack)
aa_put_label(ctx->onexec);
ctx->onexec = label;
ctx->token = stack;
-
- return 0;
}
/**
@@ -177,3 +179,162 @@ int aa_restore_previous_label(u64 token)
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;
+}