diff options
Diffstat (limited to 'security')
84 files changed, 3018 insertions, 1105 deletions
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 3fe9d7b945c4..b9a5bc3430aa 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -1,22 +1,6 @@  # SPDX-License-Identifier: GPL-2.0-only  menu "Kernel hardening options" -config GCC_PLUGIN_STRUCTLEAK -	bool -	help -	  While the kernel is built with warnings enabled for any missed -	  stack variable initializations, this warning is silenced for -	  anything passed by reference to another function, under the -	  occasionally misguided assumption that the function will do -	  the initialization. As this regularly leads to exploitable -	  flaws, this plugin is available to identify and zero-initialize -	  such variables, depending on the chosen level of coverage. - -	  This plugin was originally ported from grsecurity/PaX. More -	  information at: -	   * https://grsecurity.net/ -	   * https://pax.grsecurity.net/ -  menu "Memory initialization"  config CC_HAS_AUTO_VAR_INIT_PATTERN @@ -36,7 +20,6 @@ config CC_HAS_AUTO_VAR_INIT_ZERO  choice  	prompt "Initialize kernel stack variables at function entry" -	default GCC_PLUGIN_STRUCTLEAK_BYREF_ALL if COMPILE_TEST && GCC_PLUGINS  	default INIT_STACK_ALL_PATTERN if COMPILE_TEST && CC_HAS_AUTO_VAR_INIT_PATTERN  	default INIT_STACK_ALL_ZERO if CC_HAS_AUTO_VAR_INIT_ZERO  	default INIT_STACK_NONE @@ -60,55 +43,6 @@ choice  		  classes of uninitialized stack variable exploits  		  and information exposures. -	config GCC_PLUGIN_STRUCTLEAK_USER -		bool "zero-init structs marked for userspace (weak)" -		# Plugin can be removed once the kernel only supports GCC 12+ -		depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO -		select GCC_PLUGIN_STRUCTLEAK -		help -		  Zero-initialize any structures on the stack containing -		  a __user attribute. This can prevent some classes of -		  uninitialized stack variable exploits and information -		  exposures, like CVE-2013-2141: -		  https://git.kernel.org/linus/b9e146d8eb3b9eca - -	config GCC_PLUGIN_STRUCTLEAK_BYREF -		bool "zero-init structs passed by reference (strong)" -		# Plugin can be removed once the kernel only supports GCC 12+ -		depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO -		depends on !(KASAN && KASAN_STACK) -		select GCC_PLUGIN_STRUCTLEAK -		help -		  Zero-initialize any structures on the stack that may -		  be passed by reference and had not already been -		  explicitly initialized. This can prevent most classes -		  of uninitialized stack variable exploits and information -		  exposures, like CVE-2017-1000410: -		  https://git.kernel.org/linus/06e7e776ca4d3654 - -		  As a side-effect, this keeps a lot of variables on the -		  stack that can otherwise be optimized out, so combining -		  this with CONFIG_KASAN_STACK can lead to a stack overflow -		  and is disallowed. - -	config GCC_PLUGIN_STRUCTLEAK_BYREF_ALL -		bool "zero-init everything passed by reference (very strong)" -		# Plugin can be removed once the kernel only supports GCC 12+ -		depends on GCC_PLUGINS && !CC_HAS_AUTO_VAR_INIT_ZERO -		depends on !(KASAN && KASAN_STACK) -		select GCC_PLUGIN_STRUCTLEAK -		help -		  Zero-initialize any stack variables that may be passed -		  by reference and had not already been explicitly -		  initialized. This is intended to eliminate all classes -		  of uninitialized stack variable exploits and information -		  exposures. - -		  As a side-effect, this keeps a lot of variables on the -		  stack that can otherwise be optimized out, so combining -		  this with CONFIG_KASAN_STACK can lead to a stack overflow -		  and is disallowed. -  	config INIT_STACK_ALL_PATTERN  		bool "pattern-init everything (strongest)"  		depends on CC_HAS_AUTO_VAR_INIT_PATTERN @@ -148,20 +82,13 @@ choice  endchoice -config GCC_PLUGIN_STRUCTLEAK_VERBOSE -	bool "Report forcefully initialized variables" -	depends on GCC_PLUGIN_STRUCTLEAK -	depends on !COMPILE_TEST	# too noisy -	help -	  This option will cause a warning to be printed each time the -	  structleak plugin finds a variable it thinks needs to be -	  initialized. Since not all existing initializers are detected -	  by the plugin, this can produce false positive warnings. +config CC_HAS_SANCOV_STACK_DEPTH_CALLBACK +	def_bool $(cc-option,-fsanitize-coverage-stack-depth-callback-min=1) -config GCC_PLUGIN_STACKLEAK +config KSTACK_ERASE  	bool "Poison kernel stack before returning from syscalls" -	depends on GCC_PLUGINS -	depends on HAVE_ARCH_STACKLEAK +	depends on HAVE_ARCH_KSTACK_ERASE +	depends on GCC_PLUGINS || CC_HAS_SANCOV_STACK_DEPTH_CALLBACK  	help  	  This option makes the kernel erase the kernel stack before  	  returning from system calls. This has the effect of leaving @@ -179,6 +106,10 @@ config GCC_PLUGIN_STACKLEAK  	  are advised to test this feature on your expected workload before  	  deploying it. +config GCC_PLUGIN_STACKLEAK +	def_bool KSTACK_ERASE +	depends on GCC_PLUGINS +	help  	  This plugin was ported from grsecurity/PaX. More information at:  	   * https://grsecurity.net/  	   * https://pax.grsecurity.net/ @@ -193,37 +124,37 @@ config GCC_PLUGIN_STACKLEAK_VERBOSE  	  instrumented. This is useful for comparing coverage between  	  builds. -config STACKLEAK_TRACK_MIN_SIZE -	int "Minimum stack frame size of functions tracked by STACKLEAK" +config KSTACK_ERASE_TRACK_MIN_SIZE +	int "Minimum stack frame size of functions tracked by KSTACK_ERASE"  	default 100  	range 0 4096 -	depends on GCC_PLUGIN_STACKLEAK +	depends on KSTACK_ERASE  	help -	  The STACKLEAK gcc plugin instruments the kernel code for tracking +	  The KSTACK_ERASE option instruments the kernel code for tracking  	  the lowest border of the kernel stack (and for some other purposes). -	  It inserts the stackleak_track_stack() call for the functions with -	  a stack frame size greater than or equal to this parameter. +	  It inserts the __sanitizer_cov_stack_depth() call for the functions +	  with a stack frame size greater than or equal to this parameter.  	  If unsure, leave the default value 100. -config STACKLEAK_METRICS -	bool "Show STACKLEAK metrics in the /proc file system" -	depends on GCC_PLUGIN_STACKLEAK +config KSTACK_ERASE_METRICS +	bool "Show KSTACK_ERASE metrics in the /proc file system" +	depends on KSTACK_ERASE  	depends on PROC_FS  	help -	  If this is set, STACKLEAK metrics for every task are available in -	  the /proc file system. In particular, /proc/<pid>/stack_depth +	  If this is set, KSTACK_ERASE metrics for every task are available +	  in the /proc file system. In particular, /proc/<pid>/stack_depth  	  shows the maximum kernel stack consumption for the current and  	  previous syscalls. Although this information is not precise, it -	  can be useful for estimating the STACKLEAK performance impact for -	  your workloads. +	  can be useful for estimating the KSTACK_ERASE performance impact +	  for your workloads. -config STACKLEAK_RUNTIME_DISABLE +config KSTACK_ERASE_RUNTIME_DISABLE  	bool "Allow runtime disabling of kernel stack erasing" -	depends on GCC_PLUGIN_STACKLEAK +	depends on KSTACK_ERASE  	help  	  This option provides 'stack_erasing' sysctl, which can be used in  	  runtime to control kernel stack erasing for kernels built with -	  CONFIG_GCC_PLUGIN_STACKLEAK. +	  CONFIG_KSTACK_ERASE.  config INIT_ON_ALLOC_DEFAULT_ON  	bool "Enable heap memory zeroing on allocation by default" @@ -344,7 +275,7 @@ config CC_HAS_RANDSTRUCT  choice  	prompt "Randomize layout of sensitive kernel structures" -	default RANDSTRUCT_FULL if COMPILE_TEST && CC_HAS_RANDSTRUCT +	default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT)  	default RANDSTRUCT_NONE  	help  	  If you enable this, the layouts of structures that are entirely diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 64cc3044a42c..1e3bd44643da 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -59,8 +59,7 @@ config SECURITY_APPARMOR_INTROSPECT_POLICY  config SECURITY_APPARMOR_HASH  	bool "Enable introspection of sha256 hashes for loaded profiles"  	depends on SECURITY_APPARMOR_INTROSPECT_POLICY -	select CRYPTO -	select CRYPTO_SHA256 +	select CRYPTO_LIB_SHA256  	default y  	help  	  This option selects whether introspection of loaded policy diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index b9c5879dd599..12fb419714c0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,7 +6,7 @@ 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 \ -              policy_compat.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 @@ -28,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 "};" >> $@ ;\ @@ -43,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..9129766d1e9c --- /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, 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, 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, struct path *path, +			     struct sockaddr_un *peer_addr, +			     int peer_addrlen, 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, +				 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, struct path *path, +			  struct sockaddr_un *peer_addr, int peer_addrlen, +			  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 6039afae4bfc..391a586d0557 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -43,7 +43,7 @@   * 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_ @@ -204,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 @@ -283,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; @@ -612,8 +612,7 @@ 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 = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_perms tmp = { };  	aa_state_t state = DFA_NOMATCH; @@ -626,11 +625,20 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,  		if (state) {  			struct path_cond cond = { }; -			tmp = *(aa_lookup_fperms(rules->file, state, &cond)); +			tmp = *(aa_lookup_condperms(current_fsuid(), +						    rules->file, state, &cond));  		}  	} else if (rules->policy->dfa) {  		if (!RULE_MEDIATES(rules, *match_str))  			return;	/* no change to current perms */ +		/* old user space does not correctly detect dbus mediation +		 * support so we may get dbus policy and requests when +		 * the abi doesn't support it. This can cause mediation +		 * regressions, so explicitly test for this situation. +		 */ +		if (*match_str == AA_CLASS_DBUS && +		    !RULE_MEDIATES_v9NET(rules)) +			return; /* no change to current perms */  		state = aa_dfa_match_len(rules->policy->dfa,  					 rules->policy->start[0],  					 match_str, match_len); @@ -997,7 +1005,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); @@ -1006,7 +1014,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;  	} @@ -1152,7 +1160,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; @@ -1175,7 +1183,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; @@ -2244,7 +2252,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   */ @@ -2332,6 +2340,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), @@ -2340,6 +2349,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = {  	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"),  	{ }  }; @@ -2364,7 +2374,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = {  	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",	1), +	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), @@ -2384,6 +2394,11 @@ static struct aa_sfs_entry aa_sfs_entry_ns[] = {  	{ }  }; +static struct aa_sfs_entry aa_sfs_entry_dbus[] = { +	AA_SFS_FILE_STRING("mask", "acquire send receive"), +	{ } +}; +  static struct aa_sfs_entry aa_sfs_entry_query_label[] = {  	AA_SFS_FILE_STRING("perms", "allow deny audit quiet"),  	AA_SFS_FILE_BOOLEAN("data",		1), @@ -2406,6 +2421,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = {  	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), @@ -2413,6 +2429,7 @@ 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),  	{ } @@ -2551,7 +2568,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; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 73087d76f649..ac89602aa2d9 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -192,7 +192,7 @@ int aa_audit(int type, struct aa_profile *profile,  	aa_audit_msg(type, ad, cb);  	if (ad->type == AUDIT_APPARMOR_KILL) -		(void)send_sig_info(SIGKILL, NULL, +		(void)send_sig_info(profile->signal, NULL,  			ad->common.type == LSM_AUDIT_DATA_TASK &&  			ad->common.u.tsk ? ad->common.u.tsk : current); diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 7ca489ee1054..b9ea6bc45c1a 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -27,6 +27,7 @@  struct aa_sfs_entry aa_sfs_entry_caps[] = {  	AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), +	AA_SFS_FILE_BOOLEAN("extended", 1),  	{ }  }; @@ -68,8 +69,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile  {  	const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */ -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct audit_cache *ent;  	int type = AUDIT_APPARMOR_AUTO; @@ -121,10 +121,32 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile  static int profile_capable(struct aa_profile *profile, int cap,  			   unsigned int opts, struct apparmor_audit_data *ad)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0]; +	aa_state_t state;  	int error; +	state = RULE_MEDIATES(rules, ad->class); +	if (state) { +		struct aa_perms perms = { }; +		u32 request; + +		/* caps broken into 256 x 32 bit permission chunks */ +		state = aa_dfa_next(rules->policy->dfa, state, cap >> 5); +		request = 1 << (cap & 0x1f); +		perms = *aa_lookup_perms(rules->policy, state); +		aa_apply_modes_to_perms(profile, &perms); + +		if (opts & CAP_OPT_NOAUDIT) { +			if (perms.complain & request) +				ad->info = "optional: no audit"; +			else +				ad = NULL; +		} +		return aa_check_perms(profile, &perms, request, ad, +				      audit_cb); +	} + +	/* fallback to old caps mediation that doesn't support conditionals */  	if (cap_raised(rules->caps.allow, cap) &&  	    !cap_raised(rules->caps.denied, cap))  		error = 0; @@ -168,3 +190,34 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label,  	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 aad486b2fca6..227d47c14907 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -11,113 +11,52 @@   * 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; -	int error; - -	if (!apparmor_tfm) -		return NULL; -	hash = kzalloc(apparmor_hash_size, GFP_KERNEL); +	hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL);  	if (!hash)  		return ERR_PTR(-ENOMEM); -	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; - +	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; +	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)  		return -ENOMEM; -	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; - +	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)  { -	struct crypto_shash *tfm; - -	if (!apparmor_initialized) -		return 0; - -	tfm = crypto_alloc_shash("sha256", 0, 0); -	if (IS_ERR(tfm)) { -		int error = PTR_ERR(tfm); -		AA_ERROR("failed to setup profile sha256 hashing: %d\n", error); -		return error; -	} -	apparmor_tfm = tfm; -	apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm); - -	aa_info_message("AppArmor sha256 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 5939bd9a9b9b..267da82afb14 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -28,6 +28,12 @@  #include "include/policy.h"  #include "include/policy_ns.h" +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 @@ -87,8 +93,7 @@ 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 = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	const char *ns_name;  	if (stack) @@ -125,8 +130,7 @@ static int label_compound_match(struct aa_profile *profile,  				aa_state_t state, bool subns, u32 request,  				struct aa_perms *perms)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_profile *tp;  	struct label_it i;  	struct path_cond cond = { }; @@ -154,7 +158,8 @@ next:  		if (!state)  			goto fail;  	} -	*perms = *(aa_lookup_fperms(rules->file, 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; @@ -187,8 +192,7 @@ static int label_components_match(struct aa_profile *profile,  				  aa_state_t start, bool subns, u32 request,  				  struct aa_perms *perms)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_profile *tp;  	struct label_it i;  	struct aa_perms tmp; @@ -209,7 +213,8 @@ static int label_components_match(struct aa_profile *profile,  	return 0;  next: -	tmp = *(aa_lookup_fperms(rules->file, 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) { @@ -218,7 +223,8 @@ next:  		state = match_component(profile, tp, stack, start);  		if (!state)  			goto fail; -		tmp = *(aa_lookup_fperms(rules->file, state, &cond)); +		tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, +					    &cond));  		aa_apply_modes_to_perms(profile, &tmp);  		aa_perms_accum(perms, &tmp);  	} @@ -323,7 +329,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,  		size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],  					  &value, value_size, GFP_KERNEL);  		if (size >= 0) { -			u32 index, perm; +			struct aa_perms *perms;  			/*  			 * Check the xattr presence before value. This ensure @@ -335,9 +341,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,  			/* Check xattr value */  			state = aa_dfa_match_len(attach->xmatch->dfa, state,  						 value, size); -			index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; -			perm = attach->xmatch->perms[index].allow; -			if (!(perm & MAY_EXEC)) { +			perms = aa_lookup_perms(attach->xmatch, state); +			if (!(perms->allow & MAY_EXEC)) {  				ret = -EINVAL;  				goto out;  			} @@ -415,15 +420,14 @@ restart:  		if (attach->xmatch->dfa) {  			unsigned int count;  			aa_state_t state; -			u32 index, perm; +			struct aa_perms *perms;  			state = aa_dfa_leftmatch(attach->xmatch->dfa,  					attach->xmatch->start[AA_CLASS_XMATCH],  					name, &count); -			index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; -			perm = attach->xmatch->perms[index].allow; +			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) @@ -484,7 +488,7 @@ restart:  	if (!candidate || conflict) {  		if (conflict) -			*info = "conflicting profile attachments"; +			*info = CONFLICTING_ATTACH_STR;  		rcu_read_unlock();  		return NULL;  	} @@ -508,15 +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 = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	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); @@ -524,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 = rules->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;  }  /** @@ -564,12 +571,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile,  				   const char **lookupname,  				   const char **info)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list);  	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: @@ -578,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 = rules->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) @@ -599,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);  		}  	} @@ -617,12 +646,12 @@ 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;  } @@ -633,8 +662,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,  					   char *buffer, struct path_cond *cond,  					   bool *secure_exec)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	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; @@ -652,7 +680,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,  	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);  		} @@ -663,11 +691,27 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,  	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);  	} @@ -678,9 +722,21 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,  		new = x_to_label(profile, bprm, name, perms.xindex, &target,  				 &info);  		if (new && new->proxy == profile->label.proxy && info) { +			/* Force audit on conflicting attachment fallback +			 * Because perms is never used again after this audit +			 * we don't need to care about clobbering it +			 */ +			if (info == CONFLICTING_ATTACH_STR_IX +			    || info == CONFLICTING_ATTACH_STR_UX) +				perms.audit |= MAY_EXEC;  			/* hack ix fallback - improve how this is detected */  			goto audit;  		} else if (!new) { +			if (info) { +				pr_warn_ratelimited( +					"AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition", +					__func__, profile->base.hname, info); +			}  			info = "profile transition not found";  			/* remove MAY_EXEC to audit as failure or complaint */  			perms.allow &= ~MAY_EXEC; @@ -739,8 +795,7 @@ static int profile_onexec(const struct cred *subj_cred,  			  char *buffer, struct path_cond *cond,  			  bool *secure_exec)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	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"; @@ -755,7 +810,7 @@ static int profile_onexec(const struct cred *subj_cred,  		/* 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; @@ -766,7 +821,7 @@ static int profile_onexec(const struct cred *subj_cred,  	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; @@ -926,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) && @@ -1188,10 +1243,24 @@ 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) { @@ -1216,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;  		} @@ -1237,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;  		} @@ -1282,8 +1353,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name,  					struct aa_label *target, bool stack,  					u32 request, struct aa_perms *perms)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	const char *info = NULL;  	int error = 0; @@ -1343,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;  	} @@ -1462,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;  		} diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d52a5b14dad4..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" @@ -168,8 +169,9 @@ static int path_name(const char *op, const struct cred *subj_cred,  struct aa_perms default_perms = {};  /** - * aa_lookup_fperms - convert dfa compressed perms to internal perms - * @file_rules: the aa_policydb to lookup 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)   * @@ -177,18 +179,21 @@ struct aa_perms default_perms = {};   *   * Returns: a pointer to a file permission set   */ -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, -				 aa_state_t 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)  { -	unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; +	unsigned int index = ACCEPT_TABLE(rules->dfa)[state]; -	if (!(file_rules->perms)) +	if (!(rules->perms))  		return &default_perms; -	if (uid_eq(current_fsuid(), cond->uid)) -		return &(file_rules->perms[index]); +	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 &(file_rules->perms[index + 1]); +	return &(rules->perms[index]);  }  /** @@ -207,21 +212,22 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,  {  	aa_state_t state;  	state = aa_dfa_match(file_rules->dfa, start, name); -	*perms = *(aa_lookup_fperms(file_rules, state, cond)); +	*perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state, +				       cond));  	return state;  } -static 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_profile *profile, const char *name, +		   u32 request, struct path_cond *cond, int flags, +		   struct aa_perms *perms)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	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(rules->file, rules->file->start[AA_CLASS_FILE],  		     name, cond, perms); @@ -316,8 +322,7 @@ static int profile_path_link(const struct cred *subj_cred,  			     const struct path *target, char *buffer2,  			     struct path_cond *cond)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	const char *lname, *tname = NULL;  	struct aa_perms lperms = {}, perms;  	const char *info = NULL; @@ -423,9 +428,11 @@ int aa_path_link(const struct cred *subj_cred,  {  	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; @@ -534,22 +541,19 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred,  			    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(subj_cred, 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(subj_cred, flabel, op, -						    request, sock)); +						    request, file));  	}  	if (!error)  		update_file_ctx(file_ctx(file), label, request); @@ -557,6 +561,35 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred,  	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 @@ -594,17 +627,18 @@ int aa_file_perm(const char *op, const struct cred *subj_cred,  	 *       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)) +	if (path_mediated_fs(file->f_path.dentry))  		error = __file_path_perm(op, subj_cred, label, flabel, file,  					 request, denied, in_atomic); 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 f83934913b0f..cc6e3df1bc62 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -28,6 +28,7 @@  #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 @@ -38,12 +39,13 @@  #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; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index e27229349abb..1a71a94ea19c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -138,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;  			};  		}; diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index d6dcc604ec0c..1ddcec2d1160 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -36,6 +36,7 @@ struct aa_caps {  extern struct aa_sfs_entry aa_sfs_entry_caps[]; +kernel_cap_t aa_profile_capget(struct aa_profile *profile);  int aa_capable(const struct cred *subj_cred, struct aa_label *label,  	       int cap, unsigned int opts); diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 7265d2f81dd5..b028e4c13b6f 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -114,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 @@ -132,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)   * @@ -142,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;  } @@ -184,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/file.h b/security/apparmor/include/file.h index 6e8f2aa66cd6..ef60f99bc5ae 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -77,12 +77,17 @@ int aa_audit_file(const struct cred *cred,  		  const char *target, struct aa_label *tlabel, kuid_t ouid,  		  const char *info, int error); -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, -				  aa_state_t state, struct path_cond *cond); +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); @@ -99,7 +104,7 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files);  /** - * 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 74d17052f76b..323dd071afe9 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -13,6 +13,9 @@  #include <linux/sched.h> +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 +  int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender,  		  const struct cred *target_cred, struct aa_label *target,  		  int sig); diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 93290ae300bb..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)						\ @@ -109,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 @@ -118,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; @@ -129,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)				\ @@ -231,20 +246,17 @@ int aa_label_next_confined(struct aa_label *l, int i);  #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 (RULE_MEDIATES(&profile->rules, (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); @@ -417,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 f11a0db7f51d..444197075fd6 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -19,22 +19,34 @@  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. - */ - -#define DEBUG_ON (aa_g_debug) -/*   * split individual debug cases out in preparation for finer grained   * debug controls in the future.   */ -#define AA_DEBUG_LABEL DEBUG_ON  #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) @@ -48,9 +60,16 @@ extern struct aa_dfa *stacksplitdfa;  #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) @@ -106,6 +125,7 @@ struct aa_str_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; @@ -151,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   */ @@ -281,7 +301,7 @@ __do_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 536ce3abd598..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 @@ -137,17 +137,15 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start,  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,			\  } diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index c42ed8a73f1c..0d0b0ce42723 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -47,8 +47,9 @@  #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 */  };  static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) @@ -56,7 +57,7 @@ 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, SK, F, T, P)				  \ +#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,						  \ @@ -65,24 +66,15 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk)  						     AA_CLASS_NET,        \  			  OP);						  \  	NAME.common.u.net = &(NAME ## _net);				  \ +	NAME.subj_cred = (CRED);					  \  	NAME.net.type = (T);						  \  	NAME.net.protocol = (P) -#define DEFINE_AUDIT_SK(NAME, OP, 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; @@ -91,11 +83,19 @@ 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 apparmor_audit_data *ad, -		       u32 request, u16 family, int type); +		       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); @@ -105,13 +105,13 @@ static inline int aa_profile_af_sk_perm(struct aa_profile *profile,  					struct sock *sk)  {  	return aa_profile_af_perm(profile, ad, request, sk->sk_family, -				  sk->sk_type); +				  sk->sk_type, sk->sk_protocol);  }  int aa_sk_perm(const char *op, u32 request, struct sock *sk);  int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label,  		      const char *op, u32 request, -		      struct socket *sock); +		      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 343189903dba..8bb915d48dc7 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -13,6 +13,7 @@  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 */ diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index bbaa7d39a39a..37a3781b99a0 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -101,8 +101,8 @@ 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 + * @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) @@ -128,8 +128,8 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum,  /**   * aa_perms_accum - accumulate perms, masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum + * @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) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 757e3c232c57..4c50875c9d13 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -59,6 +59,11 @@ extern const char *const aa_profile_mode_names[];  #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. @@ -124,6 +129,7 @@ static inline void aa_put_pdb(struct aa_policydb *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)  { @@ -135,7 +141,6 @@ static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy,  	return &(policy->perms[index]);  } -  /* struct aa_data - generic data structure   * key: name for retrieving this data   * size: size of data in bytes @@ -160,8 +165,6 @@ struct aa_data {   * @secmark: secmark label match info   */  struct aa_ruleset { -	struct list_head list; -  	int size;  	/* TODO: merge policy and file */ @@ -175,6 +178,7 @@ struct aa_ruleset {  	struct aa_secmark *secmark;  }; +  /* struct aa_attachment - data and rules for a profiles attachment   * @list:   * @xmatch_str: human readable attachment string @@ -193,7 +197,6 @@ struct aa_attachment {  /* 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 @@ -201,13 +204,20 @@ struct aa_attachment {   * @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   * @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 @@ -231,16 +241,19 @@ struct aa_profile {  	enum audit_mode audit;  	long mode;  	u32 path_flags; +	int signal;  	const char *disconnected;  	struct aa_attachment attach; -	struct list_head rules;  	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;  }; @@ -298,24 +311,38 @@ static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules,  					rules->policy->start[0], &class, 1);  } -static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) +static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules)  { -	aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NET); -	__be16 be_af = cpu_to_be16(AF); +	return RULE_MEDIATES(rules, AA_CLASS_NETV9); +} + +static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) +{ +	/* can not use RULE_MEDIATE_v9AF here, because AF match fail +	 * can not be distiguished from class match fail, and we only +	 * fallback to checking older class on class match failure +	 */ +	aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9); +	/* fallback and check v7/8 if v9 is NOT mediated */  	if (!state) -		return DFA_NOMATCH; -	return aa_dfa_match_len(rules->policy->dfa, state, (char *) &be_af, 2); +		state = RULE_MEDIATES(rules, AA_CLASS_NET); + +	return state;  } -static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, -					   unsigned char class) + +void aa_compute_profile_mediates(struct aa_profile *profile); +static inline bool profile_mediates(struct aa_profile *profile, +				    unsigned char class)  { -	struct aa_ruleset *rule; +	return label_mediates(&profile->label, class); +} -	/* TODO: change to list walk */ -	rule = list_first_entry(head, typeof(*rule), list); -	return RULE_MEDIATES(rule, class); +static inline bool profile_mediates_safe(struct aa_profile *profile, +					 unsigned char class) +{ +	return label_mediates_safe(&profile->label, class);  }  /** 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/ipc.c b/security/apparmor/ipc.c index 0cdf4340b02d..df5712cea685 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -80,21 +80,20 @@ static int profile_signal_perm(const struct cred *cred,  			       struct aa_label *peer, u32 request,  			       struct apparmor_audit_data *ad)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_perms perms;  	aa_state_t state; -	if (profile_unconfined(profile) || -	    !ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL)) +	if (profile_unconfined(profile))  		return 0;  	ad->subj_cred = cred;  	ad->peer = peer;  	/* TODO: secondary cache check <profile, profile, perm> */ -	state = aa_dfa_next(rules->policy->dfa, -			    rules->policy->start[AA_CLASS_SIGNAL], -			    ad->signal); +	state = RULE_MEDIATES(rules, AA_CLASS_SIGNAL); +	if (!state) +		return 0; +	state = aa_dfa_next(rules->policy->dfa, state, ad->signal);  	aa_label_match(profile, rules, peer, state, false, request, &perms);  	aa_apply_modes_to_perms(profile, &perms);  	return aa_check_perms(profile, &perms, request, ad, audit_signal_cb); diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 91483ecacc16..913678f199c3 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -198,21 +198,25 @@ static bool vec_is_stale(struct aa_profile **vec, int n)  	return false;  } -static long accum_vec_flags(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++) { -		u |= vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | -					    FLAG_STALE); -		if (!(u & vec[i]->label.flags & FLAG_UNCONFINED)) +	/* size == 1 is a profile and flags must be set as part of creation */ +	if (new->size == 1) +		return; + +	for (i = 0; i < new->size; i++) { +		u |= new->vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | +						 FLAG_STALE); +		if (!(u & new->vec[i]->label.flags & FLAG_UNCONFINED))  			u &= ~FLAG_UNCONFINED; +		new->mediates |= new->vec[i]->label.mediates;  	} - -	return u; +	new->flags |= u;  }  static int sort_cmp(const void *a, const void *b) @@ -431,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; @@ -645,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;  	} @@ -705,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);  } @@ -1085,7 +1091,6 @@ static struct aa_label *label_merge_insert(struct aa_label *new,  		else if (k == b->size)  			return aa_get_label(b);  	} -	new->flags |= accum_vec_flags(new->vec, new->size);  	ls = labels_set(new);  	write_lock_irqsave(&ls->lock, flags);  	label = __label_insert(labels_set(new), new, false); @@ -1456,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) @@ -1617,7 +1622,7 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns,  	AA_BUG(!str && size != 0);  	AA_BUG(!label); -	if (AA_DEBUG_LABEL && (flags & FLAG_ABS_ROOT)) { +	if (DEBUG_ABS_ROOT && (flags & FLAG_ABS_ROOT)) {  		ns = root_ns;  		len = snprintf(str, size, "_");  		update_for_len(total, len, size, str); @@ -1731,7 +1736,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns,  	    display_mode(ns, label, flags)) {  		len  = aa_label_asxprint(&name, ns, label, flags, gfp);  		if (len < 0) { -			AA_DEBUG("label print error"); +			AA_DEBUG(DEBUG_LABEL, "label print error");  			return;  		}  		str = name; @@ -1759,7 +1764,7 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns,  		len = aa_label_asxprint(&str, ns, label, flags, gfp);  		if (len < 0) { -			AA_DEBUG("label print error"); +			AA_DEBUG(DEBUG_LABEL, "label print error");  			return;  		}  		seq_puts(f, str); @@ -1782,7 +1787,7 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags,  		len = aa_label_asxprint(&str, ns, label, flags, gfp);  		if (len < 0) { -			AA_DEBUG("label print error"); +			AA_DEBUG(DEBUG_LABEL, "label print error");  			return;  		}  		pr_info("%s", str); @@ -1865,7 +1870,7 @@ 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 || (AA_DEBUG_LABEL && *str == '_' && +	if (str == NULL || (DEBUG_ABS_ROOT && *str == '_' &&  			    base != &root_ns->unconfined->label))  		return ERR_PTR(-EINVAL); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7db62213e352..82dbb97ad406 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -25,6 +25,120 @@ 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; +} + +/** + * val_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @size: size of the @str buffer + * @table: NUL-terminated character buffer of permission characters (NOT NULL) + * @mask: permission mask to convert + */ +static int val_mask_to_str(char *str, size_t size, +			   const struct val_table_ent *table, u32 mask) +{ +	const struct val_table_ent *ent; +	int total = 0; + +	for (ent = table; ent->str; ent++) { +		if (ent->value && (ent->value & mask) == ent->value) { +			int len = scnprintf(str, size, "%s%s", total ? "," : "", +					    ent->str); +			size -= len; +			str += len; +			total += len; +			mask &= ~ent->value; +		} +	} + +	return total; +} + +int aa_print_debug_params(char *buffer) +{ +	if (!aa_g_debug) +		return sprintf(buffer, "N"); +	return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table, +			       aa_g_debug); +} + +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp) +{ +	char **n; +	int i; + +	if (t->size == newsize) +		return true; +	n = kcalloc(newsize, sizeof(*n), gfp); +	if (!n) +		return false; +	for (i = 0; i < min(t->size, newsize); i++) +		n[i] = t->table[i]; +	for (; i < t->size; i++) +		kfree_sensitive(t->table[i]); +	if (newsize > t->size) +		memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n)); +	kfree_sensitive(t->table); +	t->table = n; +	t->size = newsize; + +	return true; +} +  /**   * aa_free_str_table - free entries str table   * @t: the string table to free  (MAYBE NULL) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 9b6c2f157f83..8e1cc229b41b 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -26,6 +26,7 @@  #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" @@ -126,14 +127,15 @@ static int apparmor_ptrace_access_check(struct task_struct *child,  	struct aa_label *tracer, *tracee;  	const struct cred *cred;  	int error; +	bool needput;  	cred = get_task_cred(child);  	tracee = cred_label(cred);	/* ref count on cred */ -	tracer = __begin_current_label_crit_section(); +	tracer = __begin_current_label_crit_section(&needput);  	error = aa_may_ptrace(current_cred(), tracer, cred, tracee,  			(mode & PTRACE_MODE_READ) ? AA_PTRACE_READ  						  : AA_PTRACE_TRACE); -	__end_current_label_crit_section(tracer); +	__end_current_label_crit_section(tracer, needput);  	put_cred(cred);  	return error; @@ -144,14 +146,15 @@ 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(); +	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); +	__end_current_label_crit_section(tracee, needput);  	return error;  } @@ -176,15 +179,11 @@ static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effec  		struct label_it i;  		label_for_each_confined(i, label, profile) { -			struct aa_ruleset *rules; -			if (COMPLAIN_MODE(profile)) -				continue; -			rules = list_first_entry(&profile->rules, -						 typeof(*rules), list); -			*effective = cap_intersect(*effective, -						   rules->caps.allow); -			*permitted = cap_intersect(*permitted, -						   rules->caps.allow); +			kernel_cap_t allowed; + +			allowed = aa_profile_capget(profile); +			*effective = cap_intersect(*effective, allowed); +			*permitted = cap_intersect(*permitted, allowed);  		}  	}  	rcu_read_unlock(); @@ -221,12 +220,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, current_cred(), label, path, 0, mask,  				     cond); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -524,14 +524,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(); +	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); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -633,7 +634,7 @@ static int profile_uring(struct aa_profile *profile, u32 request,  	AA_BUG(!profile); -	rules = list_first_entry(&profile->rules, typeof(*rules), list); +	rules = profile->label.rules[0];  	state = RULE_MEDIATES(rules, AA_CLASS_IO_URING);  	if (state) {  		struct aa_perms perms = { }; @@ -664,15 +665,16 @@ 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(); +	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); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -688,14 +690,15 @@ 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(); +	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); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -706,6 +709,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,  {  	struct aa_label *label;  	int error = 0; +	bool needput;  	/* Discard magic */  	if ((flags & MS_MGC_MSK) == MS_MGC_VAL) @@ -713,7 +717,7 @@ 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(current_cred(), label, path, flags, @@ -732,7 +736,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path,  			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;  } @@ -742,12 +746,13 @@ static int apparmor_move_mount(const struct path *from_path,  {  	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_move_mount(current_cred(), label, from_path,  				      to_path); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -756,11 +761,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(current_cred(), label, mnt, flags); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -984,10 +990,12 @@ static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm)  static void apparmor_current_getlsmprop_subj(struct lsm_prop *prop)  { -	struct aa_label *label = __begin_current_label_crit_section(); +	struct aa_label *label; +	bool needput; +	label = __begin_current_label_crit_section(&needput);  	prop->apparmor.label = label; -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  }  static void apparmor_task_getlsmprop_obj(struct task_struct *p, @@ -1002,13 +1010,16 @@ static void apparmor_task_getlsmprop_obj(struct task_struct *p,  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(current_cred(), label, task,  					  resource, new_rlim); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return error;  } @@ -1019,6 +1030,7 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo  	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); @@ -1030,9 +1042,9 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo  		error = aa_may_signal(cred, cl, tc, tl, sig);  		aa_put_label(cl);  	} else { -		cl = __begin_current_label_crit_section(); +		cl = __begin_current_label_crit_section(&needput);  		error = aa_may_signal(current_cred(), cl, tc, tl, sig); -		__end_current_label_crit_section(cl); +		__end_current_label_crit_section(cl, needput);  	}  	aa_put_label(tl);  	put_cred(tc); @@ -1061,12 +1073,29 @@ static int apparmor_userns_create(const struct cred *cred)  	return error;  } +static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t gfp) +{ +	struct aa_sk_ctx *ctx = aa_sock(sk); +	struct aa_label *label; +	bool needput; + +	label = __begin_current_label_crit_section(&needput); +	//spin_lock_init(&ctx->lock); +	rcu_assign_pointer(ctx->label, aa_get_label(label)); +	rcu_assign_pointer(ctx->peer, NULL); +	rcu_assign_pointer(ctx->peer_lastupdate, NULL); +	__end_current_label_crit_section(label, needput); +	return 0; +} +  static void apparmor_sk_free_security(struct sock *sk)  {  	struct aa_sk_ctx *ctx = aa_sock(sk); -	aa_put_label(ctx->label); -	aa_put_label(ctx->peer); +	/* 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));  }  /** @@ -1080,13 +1109,153 @@ static void apparmor_sk_clone_security(const struct sock *sk,  	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 (rcu_access_pointer(ctx->peer_lastupdate) != rcu_access_pointer(new->peer_lastupdate)) { +		aa_put_label(rcu_dereference_protected(new->peer_lastupdate, true)); +		rcu_assign_pointer(new->peer_lastupdate, +				   aa_get_label_rcu(&ctx->peer_lastupdate)); +	} +} + +static int unix_connect_perm(const struct cred *cred, struct aa_label *label, +			     struct sock *sk, struct sock *peer_sk) +{ +	struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); +	int error; + +	error = aa_unix_peer_perm(cred, label, OP_CONNECT, +				(AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), +				  sk, peer_sk, +				  rcu_dereference_protected(peer_ctx->label, +				     lockdep_is_held(&unix_sk(peer_sk)->lock))); +	if (!is_unix_fs(peer_sk)) { +		last_error(error, +			   aa_unix_peer_perm(cred, +				rcu_dereference_protected(peer_ctx->label, +				     lockdep_is_held(&unix_sk(peer_sk)->lock)), +				OP_CONNECT, +				(AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), +							  peer_sk, sk, label)); +	} + +	return error; +} + +/* lockdep check in unix_connect_perm - push sks here to check */ +static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, +			       struct aa_sk_ctx *peer_ctx) +{ +	/* Cross reference the peer labels for SO_PEERSEC */ +	struct aa_label *label = rcu_dereference_protected(sk_ctx->label, true); + +	aa_get_label(label); +	aa_put_label(rcu_dereference_protected(peer_ctx->peer, +					     true)); +	rcu_assign_pointer(peer_ctx->peer, label);	/* transfer cnt */ + +	label = aa_get_label(rcu_dereference_protected(peer_ctx->label, +					     true)); +	//spin_unlock(&peer_ctx->lock); + +	//spin_lock(&sk_ctx->lock); +	aa_put_label(rcu_dereference_protected(sk_ctx->peer, +					       true)); +	aa_put_label(rcu_dereference_protected(sk_ctx->peer_lastupdate, +					       true)); + +	rcu_assign_pointer(sk_ctx->peer, aa_get_label(label)); +	rcu_assign_pointer(sk_ctx->peer_lastupdate, label);     /* transfer cnt */ +	//spin_unlock(&sk_ctx->lock); +} + +/** + * apparmor_unix_stream_connect - check perms before making unix domain conn + * @sk: sk attempting to connect + * @peer_sk: sk that is accepting the connection + * @newsk: new sk created for this connection + * peer is locked when this hook is called + * + * Return: + *   0 if connection is permitted + *   error code on denial or failure + */ +static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, +					struct sock *newsk) +{ +	struct aa_sk_ctx *sk_ctx = aa_sock(sk); +	struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); +	struct aa_sk_ctx *new_ctx = aa_sock(newsk); +	struct aa_label *label; +	int error; +	bool needput; + +	label = __begin_current_label_crit_section(&needput); +	error = unix_connect_perm(current_cred(), label, sk, peer_sk); +	__end_current_label_crit_section(label, needput); + +	if (error) +		return error; + +	/* newsk doesn't go through post_create, but does go through +	 * security_sk_alloc() +	 */ +	rcu_assign_pointer(new_ctx->label, +			   aa_get_label(rcu_dereference_protected(peer_ctx->label, +								  true))); + +	/* Cross reference the peer labels for SO_PEERSEC */ +	unix_connect_peers(sk_ctx, new_ctx); + +	return 0; +} + +/** + * apparmor_unix_may_send - check perms before conn or sending unix dgrams + * @sock: socket sending the message + * @peer: socket message is being send to + * + * Performs bidirectional permission checks for Unix domain socket communication: + * 1. Verifies sender has AA_MAY_SEND to target socket + * 2. Verifies receiver has AA_MAY_RECEIVE from source socket + * + * sock and peer are locked when this hook is called + * called by: dgram_connect peer setup but path not copied to newsk + * + * Return: + *   0 if transmission is permitted + *   error code on denial or failure + */ +static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) +{ +	struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk); +	struct aa_label *label; +	int error; +	bool needput; + +	label = __begin_current_label_crit_section(&needput); +	error = xcheck(aa_unix_peer_perm(current_cred(), +				label, OP_SENDMSG, AA_MAY_SEND, +				sock->sk, peer->sk, +				rcu_dereference_protected(peer_ctx->label, +							  true)), +		       aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, +				rcu_dereference_protected(peer_ctx->label, +							  true), +				OP_SENDMSG, AA_MAY_RECEIVE, peer->sk, +				sock->sk, label)); +	__end_current_label_crit_section(label, needput); -	if (new->peer) -		aa_put_label(new->peer); -	new->peer = aa_get_label(ctx->peer); +	return error;  }  static int apparmor_socket_create(int family, int type, int protocol, int kern) @@ -1096,13 +1265,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(current_cred(), 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; @@ -1135,14 +1310,58 @@ static int apparmor_socket_post_create(struct socket *sock, int family,  	if (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)  { @@ -1151,9 +1370,9 @@ 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);  }  static int apparmor_socket_connect(struct socket *sock, @@ -1164,9 +1383,10 @@ 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);  }  static int apparmor_socket_listen(struct socket *sock, int backlog) @@ -1175,9 +1395,9 @@ static int apparmor_socket_listen(struct socket *sock, int backlog)  	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);  }  /* @@ -1191,9 +1411,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, @@ -1204,9 +1424,10 @@ 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);  }  static int apparmor_socket_sendmsg(struct socket *sock, @@ -1228,9 +1449,9 @@ 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);  }  static int apparmor_socket_getsockname(struct socket *sock) @@ -1251,9 +1472,9 @@ 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);  }  static int apparmor_socket_getsockopt(struct socket *sock, int level, @@ -1289,6 +1510,7 @@ 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 = aa_sock(sk); +	int error;  	if (!skb->secmark)  		return 0; @@ -1297,23 +1519,31 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)  	 * If reach here before socket_post_create hook is called, in which  	 * case label is null, drop the packet.  	 */ -	if (!ctx->label) +	if (!rcu_access_pointer(ctx->label))  		return -EACCES; -	return apparmor_secmark_check(ctx->label, OP_RECVMSG, AA_MAY_RECEIVE, -				      skb->secmark, sk); +	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 = aa_sock(sk); +	struct aa_label *label = ERR_PTR(-ENOPROTOOPT); -	if (ctx->peer) -		return ctx->peer; +	if (rcu_access_pointer(ctx->peer)) +		return aa_get_label_rcu(&ctx->peer); -	return ERR_PTR(-ENOPROTOOPT); +	if (sk->sk_family != PF_UNIX) +		return ERR_PTR(-ENOPROTOOPT); + +	return label;  }  /** @@ -1335,19 +1565,19 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,  	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; -		goto done; +		goto done_put;  	}  	if (slen > len) {  		error = -ERANGE; @@ -1359,8 +1589,11 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock,  done_len:  	if (copy_to_sockptr(optlen, &slen, sizeof(slen)))  		error = -EFAULT; -done: + +done_put:  	end_current_label_crit_section(label); +	aa_put_label(peer); +done:  	kfree(name);  	return error;  } @@ -1396,8 +1629,9 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent)  {  	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 @@ -1405,12 +1639,17 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb  				      struct request_sock *req)  {  	struct aa_sk_ctx *ctx = aa_sock(sk); +	int error;  	if (!skb->secmark)  		return 0; -	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 @@ -1467,11 +1706,16 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = {  	LSM_HOOK_INIT(getprocattr, apparmor_getprocattr),  	LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), +	LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security),  	LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security),  	LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), +	LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect), +	LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send), +  	LSM_HOOK_INIT(socket_create, apparmor_socket_create),  	LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create), +	LSM_HOOK_INIT(socket_socketpair, apparmor_socket_socketpair),  	LSM_HOOK_INIT(socket_bind, apparmor_socket_bind),  	LSM_HOOK_INIT(socket_connect, apparmor_socket_connect),  	LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), @@ -1571,6 +1815,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); @@ -1604,8 +1851,9 @@ 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; @@ -1798,6 +2046,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) @@ -2006,7 +2282,7 @@ static int __init alloc_buffers(void)  	 * 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; @@ -2082,6 +2358,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; @@ -2091,8 +2368,11 @@ static unsigned int apparmor_ip_postroute(void *priv,  		return NF_ACCEPT;  	ctx = aa_sock(sk); -	if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND, -				    skb->secmark, 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); @@ -2149,12 +2429,12 @@ static int __init apparmor_nf_ip_init(void)  __initcall(apparmor_nf_ip_init);  #endif -static char nulldfa_src[] = { +static char nulldfa_src[] __aligned(8) = {  	#include "nulldfa.in"  };  static struct aa_dfa *nulldfa; -static char stacksplitdfa_src[] = { +static char stacksplitdfa_src[] __aligned(8) = {  	#include "stacksplitdfa.in"  };  struct aa_dfa *stacksplitdfa; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index f2d9c57f8794..c5a91600842a 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -679,34 +679,35 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t 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 */ +/* adjust is only set if is_loop returns true */  static bool is_loop(struct match_workbuf *wb, aa_state_t state,  		    unsigned int *adjust)  { -	aa_state_t pos = wb->pos; -	aa_state_t 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 aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index bf8863253e07..523570aa1a5a 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -311,8 +311,7 @@ static int match_mnt_path_str(const struct cred *subj_cred,  {  	struct aa_perms perms = { };  	const char *mntpnt = NULL, *info = NULL; -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	int pos, error;  	AA_BUG(!profile); @@ -371,8 +370,7 @@ static int match_mnt(const struct cred *subj_cred,  		     bool binary)  {  	const char *devname = NULL, *info = NULL; -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	int error = -EACCES;  	AA_BUG(!profile); @@ -604,8 +602,7 @@ static int profile_umount(const struct cred *subj_cred,  			  struct aa_profile *profile, const struct path *path,  			  char *buffer)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_perms perms = { };  	const char *name = NULL, *info = NULL;  	aa_state_t state; @@ -668,8 +665,7 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred,  					const struct path *old_path,  					char *old_buffer)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	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 = { }; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 77413a519117..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,6 +73,42 @@ 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) @@ -73,12 +116,12 @@ 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); +				 ad->common.u.net->family);  	if (sock_type_names[ad->net.type])  		audit_log_format(ab, " sock_type=\"%s\"",  				 sock_type_names[ad->net.type]); @@ -98,6 +141,19 @@ void audit_net_cb(struct audit_buffer *ab, void *va)  					   net_mask_names, NET_PERMS_MASK);  		}  	} +	if (ad->common.u.net->family == PF_UNIX) { +		if (ad->net.addr || !ad->common.u.net->sk) +			audit_unix_addr(ab, "addr", +					unix_addr(ad->net.addr), +					ad->net.addrlen); +		else +			audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk); +		if (ad->request & NET_PEER_MASK) { +			audit_unix_addr(ab, "peer_addr", +					unix_addr(ad->net.peer.addr), +					ad->net.peer.addrlen); +		} +	}  	if (ad->peer) {  		audit_log_format(ab, " peer=");  		aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, @@ -105,45 +161,123 @@ void audit_net_cb(struct audit_buffer *ab, void *va)  	}  } +/* standard permission lookup pattern - supports early bailout */ +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, +		aa_state_t state, u32 request, +		struct aa_perms *p, struct apparmor_audit_data *ad) +{ +	struct aa_perms perms; + +	AA_BUG(!profile); +	AA_BUG(!policy); + + +	if (state || !p) +		p = aa_lookup_perms(policy, state); +	perms = *p; +	aa_apply_modes_to_perms(profile, &perms); +	return aa_check_perms(profile, &perms, request, ad, +			      audit_net_cb); +} + +/* only continue match if + *   insufficient current perms at current state + *   indicates there are more perms in later state + * Returns: perms struct if early match + */ +static struct aa_perms *early_match(struct aa_policydb *policy, +				    aa_state_t state, u32 request) +{ +	struct aa_perms *p; + +	p = aa_lookup_perms(policy, state); +	if (((p->allow & request) != request) && (p->allow & AA_CONT_MATCH)) +		return NULL; +	return p; +} + +static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, +					  u16 data) +{ +	__be16 buffer = cpu_to_be16(data); + +	return aa_dfa_match_len(dfa, state, (char *) &buffer, 2); +} + +/** + * aa_match_to_prot - match the af, type, protocol triplet + * @policy: policy being matched + * @state: state to start in + * @request: permissions being requested, ignored if @p == NULL + * @af: socket address family + * @type: socket type + * @protocol: socket protocol + * @p: output - pointer to permission associated with match + * @info: output - pointer to string describing failure + * + * RETURNS: state match stopped in. + * + * If @(p) is assigned a value the returned state will be the + * corresponding state. Will not set @p on failure or if match completes + * only if an early match occurs + */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, +			    u32 request, u16 af, int type, int protocol, +			    struct aa_perms **p, const char **info) +{ +	state = aa_dfa_match_be16(policy->dfa, state, (u16)af); +	if (!state) { +		*info = "failed af match"; +		return state; +	} +	state = aa_dfa_match_be16(policy->dfa, state, (u16)type); +	if (state) { +		if (p) +			*p = early_match(policy, state, request); +		if (!p || !*p) { +			state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol); +			if (!state) +				*info = "failed protocol match"; +		} +	} else { +		*info = "failed type match"; +	} + +	return state; +} +  /* Generic af perm */  int aa_profile_af_perm(struct aa_profile *profile,  		       struct apparmor_audit_data *ad, u32 request, u16 family, -		       int type) +		       int type, int protocol)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); -	struct aa_perms perms = { }; +	struct aa_ruleset *rules = profile->label.rules[0]; +	struct aa_perms *p = NULL;  	aa_state_t state; -	__be16 buffer[2];  	AA_BUG(family >= AF_MAX);  	AA_BUG(type < 0 || type >= SOCK_MAX); +	AA_BUG(profile_unconfined(profile));  	if (profile_unconfined(profile))  		return 0; -	state = RULE_MEDIATES(rules, 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(rules->policy->dfa, state, (char *) &buffer, -				 4); -	perms = *aa_lookup_perms(rules->policy, state); -	aa_apply_modes_to_perms(profile, &perms); - -	return aa_check_perms(profile, &perms, request, ad, 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(const struct cred *subj_cred, struct aa_label *label,  	       const char *op, u32 request, u16 family, int type, int protocol)  {  	struct aa_profile *profile; -	DEFINE_AUDIT_NET(ad, op, 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, &ad, request, family, -					   type)); +					   type, protocol));  }  static int aa_label_sk_perm(const struct cred *subj_cred, @@ -157,9 +291,9 @@ static int aa_label_sk_perm(const struct cred *subj_cred,  	AA_BUG(!label);  	AA_BUG(!sk); -	if (ctx->label != kernel_t && !unconfined(label)) { +	if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) {  		struct aa_profile *profile; -		DEFINE_AUDIT_SK(ad, op, sk); +		DEFINE_AUDIT_SK(ad, op, subj_cred, sk);  		ad.subj_cred = subj_cred;  		error = fn_for_each_confined(label, profile, @@ -187,12 +321,16 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk)  int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, -		      const char *op, u32 request, struct socket *sock) +		      const char *op, u32 request, struct file *file)  { +	struct socket *sock = (struct socket *) file->private_data; +  	AA_BUG(!label);  	AA_BUG(!sock);  	AA_BUG(!sock->sk); +	if (sock->sk->sk_family == PF_UNIX) +		return aa_unix_file_perm(subj_cred, label, op, request, file);  	return aa_label_sk_perm(subj_cred, label, op, request, sock->sk);  } @@ -223,8 +361,7 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid,  {  	int i, ret;  	struct aa_perms perms = { }; -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	if (rules->secmark_count == 0)  		return 0; @@ -257,7 +394,7 @@ int apparmor_secmark_check(struct aa_label *label, char *op, u32 request,  			   u32 secid, const struct sock *sk)  {  	struct aa_profile *profile; -	DEFINE_AUDIT_SK(ad, op, sk); +	DEFINE_AUDIT_SK(ad, op, NULL, sk);  	return fn_for_each_confined(label, profile,  				    aa_secmark_perm(profile, request, secid, diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index d0244fab0653..50d5345ff5cb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -243,6 +243,9 @@ 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); @@ -259,8 +262,6 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp)  	struct aa_ruleset *rules;  	rules = kzalloc(sizeof(*rules), gfp); -	if (rules) -		INIT_LIST_HEAD(&rules->list);  	return rules;  } @@ -277,10 +278,9 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp)   */  void aa_free_profile(struct aa_profile *profile)  { -	struct aa_ruleset *rule, *tmp;  	struct rhashtable *rht; -	AA_DEBUG("%s(%p)\n", __func__, profile); +	AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile);  	if (!profile)  		return; @@ -299,10 +299,9 @@ void aa_free_profile(struct aa_profile *profile)  	 * at this point there are no tasks that can have a reference  	 * to rules  	 */ -	list_for_each_entry_safe(rule, tmp, &profile->rules, list) { -		list_del_init(&rule->list); -		free_ruleset(rule); -	} +	for (int i = 0; i < profile->n_rules; i++) +		free_ruleset(profile->label.rules[i]); +  	kfree_sensitive(profile->dirname);  	if (profile->data) { @@ -331,10 +330,12 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,  				    gfp_t gfp)  {  	struct aa_profile *profile; -	struct aa_ruleset *rules; -	/* 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; @@ -343,13 +344,11 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy,  	if (!aa_label_init(&profile->label, 1, gfp))  		goto fail; -	INIT_LIST_HEAD(&profile->rules); -  	/* allocate the first ruleset, but leave it empty */ -	rules = aa_alloc_ruleset(gfp); -	if (!rules) +	profile->label.rules[0] = aa_alloc_ruleset(gfp); +	if (!profile->label.rules[0])  		goto fail; -	list_add(&rules->list, &profile->rules); +	profile->n_rules = 1;  	/* update being set needed by fs interface */  	if (!proxy) { @@ -364,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; @@ -373,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 */  /** @@ -463,7 +498,7 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns,  }  /** - * __create_missing_ancestors - create place holders for missing ancestores + * __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. @@ -621,13 +656,15 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name,  	/* TODO: ideally we should inherit abi from parent */  	profile->label.flags |= FLAG_NULL;  	profile->attach.xmatch = aa_get_pdb(nullpdb); -	rules = list_first_entry(&profile->rules, typeof(*rules), list); +	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); @@ -833,8 +870,8 @@ bool aa_policy_admin_capable(const struct cred *subj_cred,  	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(subj_cred, label, ns) && capable &&  		!aa_g_lock_policy; @@ -843,11 +880,11 @@ bool aa_policy_admin_capable(const struct cred *subj_cred,  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(); +	label = __begin_current_label_crit_section(&needput);  	res = aa_policy_view_capable(current_cred(), label, ns); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return res;  } @@ -855,11 +892,11 @@ 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(); +	label = __begin_current_label_crit_section(&needput);  	res = aa_policy_admin_capable(current_cred(), label, ns); -	__end_current_label_crit_section(label); +	__end_current_label_crit_section(label, needput);  	return res;  } @@ -1068,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  	 */ diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 423227670e68..cfc2207e5a12 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -286,10 +286,10 @@ static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor)  	AA_BUG(!dfa); -	for (state = 0; state < state_count; state++) +	for (state = 0; state < state_count; state++) {  		ACCEPT_TABLE(dfa)[state] = state * factor; -	kvfree(dfa->tables[YYTD_ID_ACCEPT2]); -	dfa->tables[YYTD_ID_ACCEPT2] = NULL; +		ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0; +	}  }  /* TODO: merge different dfa mappings into single map_policy fn */ diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 1f02cfe1d974..64783ca3b0f2 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -107,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)) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 992b74c50d64..7523971e37d9 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -29,6 +29,7 @@  #include "include/policy.h"  #include "include/policy_unpack.h"  #include "include/policy_compat.h" +#include "include/signal.h"  /* audit callback for unpack fields */  static void audit_cb(struct audit_buffer *ab, void *va) @@ -598,8 +599,8 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules)  fail:  	if (rules->secmark) {  		for (i = 0; i < size; i++) -			kfree(rules->secmark[i].label); -		kfree(rules->secmark); +			kfree_sensitive(rules->secmark[i].label); +		kfree_sensitive(rules->secmark);  		rules->secmark_count = 0;  		rules->secmark = NULL;  	} @@ -716,6 +717,7 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,  	void *pos = e->pos;  	int i, flags, error = -EPROTO;  	ssize_t size; +	u32 version = 0;  	pdb = aa_alloc_pdb(GFP_KERNEL);  	if (!pdb) @@ -733,6 +735,9 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,  	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) | @@ -770,6 +775,21 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,  		}  	} +	/* 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 @@ -783,9 +803,13 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy,  	if (!pdb->dfa && pdb->trans.table)  		aa_free_str_table(&pdb->trans); -	/* TODO: move compat mapping here, requires dfa merging first */ -	/* TODO: move verify here, it has to be done after compat mappings */ - +	/* 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; @@ -862,7 +886,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)  		error = -ENOMEM;  		goto fail;  	} -	rules = list_first_entry(&profile->rules, typeof(*rules), list); +	rules = profile->label.rules[0];  	/* profile renaming is optional */  	(void) aa_unpack_str(e, &profile->rename, "rename"); @@ -898,6 +922,12 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)  	(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 (!aa_unpack_nameX(e, AA_STRUCT, "flags")) {  		info = "profile missing flags"; @@ -1101,6 +1131,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)  		goto fail;  	} +	aa_compute_profile_mediates(profile); +  	return profile;  fail: @@ -1215,21 +1247,32 @@ static bool verify_perm(struct aa_perms *perm)  static bool verify_perms(struct aa_policydb *pdb)  {  	int i; +	int xidx, xmax = -1;  	for (i = 0; i < pdb->size; i++) {  		if (!verify_perm(&pdb->perms[i]))  			return false;  		/* verify indexes into str table */ -		if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE && -		    (pdb->perms[i].xindex & AA_X_INDEX_MASK) >= pdb->trans.size) -			return false; +		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;  } @@ -1243,8 +1286,8 @@ static bool verify_perms(struct aa_policydb *pdb)   */  static int verify_profile(struct aa_profile *profile)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0]; +  	if (!rules)  		return 0; diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index 5b2ba88ae9e2..cf18744dafe2 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -9,6 +9,8 @@  #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 \ @@ -80,7 +82,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,  	*(buf + 1) = strlen(TEST_U32_NAME) + 1;  	strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3));  	*(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32; -	*((__le32 *)(buf + 3 + strlen(TEST_U32_NAME) + 2)) = cpu_to_le32(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; @@ -103,7 +105,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf,  	*(buf + 1) = strlen(TEST_ARRAY_NAME) + 1;  	strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3));  	*(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY; -	*((__le16 *)(buf + 3 + strlen(TEST_ARRAY_NAME) + 2)) = cpu_to_le16(TEST_ARRAY_SIZE); +	put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2);  	return e;  } diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index e3857e3d7c6c..ce40f15d4952 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -125,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 dcc94c3153d5..8e80db3ae21c 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -89,8 +89,7 @@ static int profile_setrlimit(const struct cred *subj_cred,  			     struct aa_profile *profile, unsigned int resource,  			     struct rlimit *new_rlim)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	int e = 0;  	if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > @@ -165,9 +164,7 @@ 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) { -		struct aa_ruleset *rules = list_first_entry(&old->rules, -							    typeof(*rules), -							    list); +		struct aa_ruleset *rules = old->label.rules[0];  		if (rules->rlimits.mask) {  			int j; @@ -185,9 +182,7 @@ 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 = list_first_entry(&new->rules, -							    typeof(*rules), -							    list); +		struct aa_ruleset *rules = new->label.rules[0];  		int j;  		if (!rules->rlimits.mask) diff --git a/security/apparmor/task.c b/security/apparmor/task.c index c87fb9f4ac18..c9bc9cc69475 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -228,8 +228,7 @@ static int profile_ptrace_perm(const struct cred *cred,  			       struct aa_label *peer, u32 request,  			       struct apparmor_audit_data *ad)  { -	struct aa_ruleset *rules = list_first_entry(&profile->rules, -						    typeof(*rules), list); +	struct aa_ruleset *rules = profile->label.rules[0];  	struct aa_perms perms = { };  	ad->subj_cred = cred; @@ -246,7 +245,7 @@ static int profile_tracee_perm(const struct cred *cred,  			       struct apparmor_audit_data *ad)  {  	if (profile_unconfined(tracee) || unconfined(tracer) || -	    !ANY_RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE)) +	    !label_mediates(&tracee->label, AA_CLASS_PTRACE))  		return 0;  	return profile_ptrace_perm(cred, tracee, tracer, request, ad); @@ -260,7 +259,7 @@ static int profile_tracer_perm(const struct cred *cred,  	if (profile_unconfined(tracer))  		return 0; -	if (ANY_RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE)) +	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 */ @@ -324,9 +323,7 @@ int aa_profile_ns_perm(struct aa_profile *profile,  	ad->request = request;  	if (!profile_unconfined(profile)) { -		struct aa_ruleset *rules = list_first_entry(&profile->rules, -							    typeof(*rules), -							    list); +		struct aa_ruleset *rules = profile->label.rules[0];  		aa_state_t state;  		state = RULE_MEDIATES(rules, ad->class); diff --git a/security/commoncap.c b/security/commoncap.c index 28d4248bf001..6bd4adeb4795 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -856,12 +856,6 @@ static void handle_privileged_root(struct linux_binprm *bprm, bool has_fcap,  #define __cap_full(field, cred) \  	cap_issubset(CAP_FULL_SET, cred->cap_##field) -static inline bool __is_setuid(struct cred *new, const struct cred *old) -{ return !uid_eq(new->euid, old->uid); } - -static inline bool __is_setgid(struct cred *new, const struct cred *old) -{ return !gid_eq(new->egid, old->gid); } -  /*   * 1) Audit candidate if current->cap_effective is set   * @@ -891,7 +885,7 @@ static inline bool nonroot_raised_pE(struct cred *new, const struct cred *old,  	    (root_privileged() &&  	     __is_suid(root, new) &&  	     !__cap_full(effective, new)) || -	    (!__is_setuid(new, old) && +	    (uid_eq(new->euid, old->euid) &&  	     ((has_fcap &&  	       __cap_gained(permitted, new, old)) ||  	      __cap_gained(ambient, new, old)))) @@ -917,7 +911,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)  	/* Process setpcap binaries and capabilities for uid 0 */  	const struct cred *old = current_cred();  	struct cred *new = bprm->cred; -	bool effective = false, has_fcap = false, is_setid; +	bool effective = false, has_fcap = false, id_changed;  	int ret;  	kuid_t root_uid; @@ -941,9 +935,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)  	 *  	 * In addition, if NO_NEW_PRIVS, then ensure we get no new privs.  	 */ -	is_setid = __is_setuid(new, old) || __is_setgid(new, old); +	id_changed = !uid_eq(new->euid, old->euid) || !in_group_p(new->egid); -	if ((is_setid || __cap_gained(permitted, new, old)) && +	if ((id_changed || __cap_gained(permitted, new, old)) &&  	    ((bprm->unsafe & ~LSM_UNSAFE_PTRACE) ||  	     !ptracer_capable(current, new->user_ns))) {  		/* downgrade; they get no more than they had, and maybe less */ @@ -960,7 +954,7 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)  	new->sgid = new->fsgid = new->egid;  	/* File caps or setid cancels ambient. */ -	if (has_fcap || is_setid) +	if (has_fcap || id_changed)  		cap_clear(new->cap_ambient);  	/* @@ -993,7 +987,9 @@ int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)  		return -EPERM;  	/* Check for privilege-elevated exec. */ -	if (is_setid || +	if (id_changed || +	    !uid_eq(new->euid, old->uid) || +	    !gid_eq(new->egid, old->gid) ||  	    (!__is_real(root_uid, new) &&  	     (effective ||  	      __cap_grew(permitted, ambient, new)))) diff --git a/security/inode.c b/security/inode.c index da3ab44c8e57..43382ef8896e 100644 --- a/security/inode.c +++ b/security/inode.c @@ -112,23 +112,25 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode,  	struct dentry *dentry;  	struct inode *dir, *inode;  	int error; +	bool pinned = false;  	if (!(mode & S_IFMT))  		mode = (mode & S_IALLUGO) | S_IFREG;  	pr_debug("securityfs: creating file '%s'\n",name); -	error = simple_pin_fs(&fs_type, &mount, &mount_count); -	if (error) -		return ERR_PTR(error); - -	if (!parent) +	if (!parent) { +		error = simple_pin_fs(&fs_type, &mount, &mount_count); +		if (error) +			return ERR_PTR(error); +		pinned = true;  		parent = mount->mnt_root; +	}  	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))  		goto out; @@ -159,7 +161,6 @@ static struct dentry *securityfs_create_dentry(const char *name, umode_t mode,  		inode->i_fop = fops;  	}  	d_instantiate(dentry, inode); -	dget(dentry);  	inode_unlock(dir);  	return dentry; @@ -168,7 +169,8 @@ out1:  	dentry = ERR_PTR(error);  out:  	inode_unlock(dir); -	simple_release_fs(&mount, &mount_count); +	if (pinned) +		simple_release_fs(&mount, &mount_count);  	return dentry;  } @@ -279,6 +281,12 @@ struct dentry *securityfs_create_symlink(const char *name,  }  EXPORT_SYMBOL_GPL(securityfs_create_symlink); +static void remove_one(struct dentry *victim) +{ +	if (victim->d_parent == victim->d_sb->s_root) +		simple_release_fs(&mount, &mount_count); +} +  /**   * securityfs_remove - removes a file or directory from the securityfs filesystem   * @@ -291,43 +299,11 @@ EXPORT_SYMBOL_GPL(securityfs_create_symlink);   * This function is required to be called in order for the file to be   * removed. No automatic cleanup of files will happen when a module is   * removed; you are responsible here. - */ -void securityfs_remove(struct dentry *dentry) -{ -	struct inode *dir; - -	if (IS_ERR_OR_NULL(dentry)) -		return; - -	dir = d_inode(dentry->d_parent); -	inode_lock(dir); -	if (simple_positive(dentry)) { -		if (d_is_dir(dentry)) -			simple_rmdir(dir, dentry); -		else -			simple_unlink(dir, dentry); -		dput(dentry); -	} -	inode_unlock(dir); -	simple_release_fs(&mount, &mount_count); -} -EXPORT_SYMBOL_GPL(securityfs_remove); - -static void remove_one(struct dentry *victim) -{ -	simple_release_fs(&mount, &mount_count); -} - -/** - * securityfs_recursive_remove - recursively removes a file or directory - * - * @dentry: a pointer to a the dentry of the file or directory to be removed.   * - * This function recursively removes a file or directory in securityfs that was - * previously created with a call to another securityfs function (like - * securityfs_create_file() or variants thereof.) + * AV: when applied to directory it will take all children out; no need to call + * it for descendents if ancestor is getting killed.   */ -void securityfs_recursive_remove(struct dentry *dentry) +void securityfs_remove(struct dentry *dentry)  {  	if (IS_ERR_OR_NULL(dentry))  		return; @@ -336,7 +312,7 @@ void securityfs_recursive_remove(struct dentry *dentry)  	simple_recursive_removal(dentry, remove_one);  	simple_release_fs(&mount, &mount_count);  } -EXPORT_SYMBOL_GPL(securityfs_recursive_remove); +EXPORT_SYMBOL_GPL(securityfs_remove);  #ifdef CONFIG_SECURITY  static struct dentry *lsm_dentry; diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index 9b907c2fee60..b0d2aad27850 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -17,7 +17,6 @@  #include "evm.h"  static struct dentry *evm_dir; -static struct dentry *evm_init_tpm;  static struct dentry *evm_symlink;  #ifdef CONFIG_EVM_ADD_XATTRS @@ -286,7 +285,7 @@ static int evm_init_xattrs(void)  {  	evm_xattrs = securityfs_create_file("evm_xattrs", 0660, evm_dir, NULL,  					    &evm_xattr_ops); -	if (!evm_xattrs || IS_ERR(evm_xattrs)) +	if (IS_ERR(evm_xattrs))  		return -EFAULT;  	return 0; @@ -301,21 +300,22 @@ static int evm_init_xattrs(void)  int __init evm_init_secfs(void)  {  	int error = 0; +	struct dentry *dentry;  	evm_dir = securityfs_create_dir("evm", integrity_dir); -	if (!evm_dir || IS_ERR(evm_dir)) +	if (IS_ERR(evm_dir))  		return -EFAULT; -	evm_init_tpm = securityfs_create_file("evm", 0660, -					      evm_dir, NULL, &evm_key_ops); -	if (!evm_init_tpm || IS_ERR(evm_init_tpm)) { +	dentry = securityfs_create_file("evm", 0660, +				      evm_dir, NULL, &evm_key_ops); +	if (IS_ERR(dentry)) {  		error = -EFAULT;  		goto out;  	}  	evm_symlink = securityfs_create_symlink("evm", NULL,  						"integrity/evm/evm", NULL); -	if (!evm_symlink || IS_ERR(evm_symlink)) { +	if (IS_ERR(evm_symlink)) {  		error = -EFAULT;  		goto out;  	} @@ -328,7 +328,6 @@ int __init evm_init_secfs(void)  	return 0;  out:  	securityfs_remove(evm_symlink); -	securityfs_remove(evm_init_tpm);  	securityfs_remove(evm_dir);  	return error;  } diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 475c32615006..976e75f9b9ba 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -321,4 +321,15 @@ config IMA_DISABLE_HTABLE  	help  	   This option disables htable to allow measurement of duplicate records. +config IMA_KEXEC_EXTRA_MEMORY_KB +	int "Extra memory for IMA measurements added during kexec soft reboot" +	range 0 40 +	depends on IMA_KEXEC +	default 0 +	help +	  IMA_KEXEC_EXTRA_MEMORY_KB determines the extra memory to be +	  allocated (in kb) for IMA measurements added during kexec soft reboot. +	  If set to the default value of 0, an extra half page of memory for those +	  additional measurements will be allocated. +  endif diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e0489c6f7f59..e3d71d8d56e3 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -244,6 +244,12 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,  				   unsigned long flags, bool create);  #endif +#ifdef CONFIG_IMA_KEXEC +void ima_measure_kexec_event(const char *event_name); +#else +static inline void ima_measure_kexec_event(const char *event_name) {} +#endif +  /*   * The default binary_runtime_measurements list format is defined as the   * platform native format.  The canonical format is defined as little-endian. diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e4a79a9b2d58..87045b09f120 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -116,28 +116,6 @@ void ima_putc(struct seq_file *m, void *data, int datalen)  		seq_putc(m, *(char *)data++);  } -static struct dentry **ascii_securityfs_measurement_lists __ro_after_init; -static struct dentry **binary_securityfs_measurement_lists __ro_after_init; -static int securityfs_measurement_list_count __ro_after_init; - -static void lookup_template_data_hash_algo(int *algo_idx, enum hash_algo *algo, -					   struct seq_file *m, -					   struct dentry **lists) -{ -	struct dentry *dentry; -	int i; - -	dentry = file_dentry(m->file); - -	for (i = 0; i < securityfs_measurement_list_count; i++) { -		if (dentry == lists[i]) { -			*algo_idx = i; -			*algo = ima_algo_array[i].algo; -			break; -		} -	} -} -  /* print format:   *       32bit-le=pcr#   *       char[n]=template digest @@ -160,9 +138,10 @@ int ima_measurements_show(struct seq_file *m, void *v)  	algo_idx = ima_sha1_idx;  	algo = HASH_ALGO_SHA1; -	if (m->file != NULL) -		lookup_template_data_hash_algo(&algo_idx, &algo, m, -					       binary_securityfs_measurement_lists); +	if (m->file != NULL) { +		algo_idx = (unsigned long)file_inode(m->file)->i_private; +		algo = ima_algo_array[algo_idx].algo; +	}  	/* get entry */  	e = qe->entry; @@ -256,9 +235,10 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v)  	algo_idx = ima_sha1_idx;  	algo = HASH_ALGO_SHA1; -	if (m->file != NULL) -		lookup_template_data_hash_algo(&algo_idx, &algo, m, -					       ascii_securityfs_measurement_lists); +	if (m->file != NULL) { +		algo_idx = (unsigned long)file_inode(m->file)->i_private; +		algo = ima_algo_array[algo_idx].algo; +	}  	/* get entry */  	e = qe->entry; @@ -396,11 +376,6 @@ out:  static struct dentry *ima_dir;  static struct dentry *ima_symlink; -static struct dentry *binary_runtime_measurements; -static struct dentry *ascii_runtime_measurements; -static struct dentry *runtime_measurements_count; -static struct dentry *violations; -static struct dentry *ima_policy;  enum ima_fs_flags {  	IMA_FS_BUSY, @@ -417,64 +392,33 @@ static const struct seq_operations ima_policy_seqops = {  };  #endif -static void __init remove_securityfs_measurement_lists(struct dentry **lists) -{ -	int i; - -	if (lists) { -		for (i = 0; i < securityfs_measurement_list_count; i++) -			securityfs_remove(lists[i]); - -		kfree(lists); -	} -} -  static int __init create_securityfs_measurement_lists(void)  { -	char file_name[NAME_MAX + 1]; -	struct dentry *dentry; -	u16 algo; -	int i; - -	securityfs_measurement_list_count = NR_BANKS(ima_tpm_chip); +	int count = NR_BANKS(ima_tpm_chip);  	if (ima_sha1_idx >= NR_BANKS(ima_tpm_chip)) -		securityfs_measurement_list_count++; +		count++; -	ascii_securityfs_measurement_lists = -	    kcalloc(securityfs_measurement_list_count, sizeof(struct dentry *), -		    GFP_KERNEL); -	if (!ascii_securityfs_measurement_lists) -		return -ENOMEM; - -	binary_securityfs_measurement_lists = -	    kcalloc(securityfs_measurement_list_count, sizeof(struct dentry *), -		    GFP_KERNEL); -	if (!binary_securityfs_measurement_lists) -		return -ENOMEM; - -	for (i = 0; i < securityfs_measurement_list_count; i++) { -		algo = ima_algo_array[i].algo; +	for (int i = 0; i < count; i++) { +		u16 algo = ima_algo_array[i].algo; +		char file_name[NAME_MAX + 1]; +		struct dentry *dentry;  		sprintf(file_name, "ascii_runtime_measurements_%s",  			hash_algo_name[algo]);  		dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, -						ima_dir, NULL, +						ima_dir, (void *)(uintptr_t)i,  						&ima_ascii_measurements_ops);  		if (IS_ERR(dentry))  			return PTR_ERR(dentry); -		ascii_securityfs_measurement_lists[i] = dentry; -  		sprintf(file_name, "binary_runtime_measurements_%s",  			hash_algo_name[algo]);  		dentry = securityfs_create_file(file_name, S_IRUSR | S_IRGRP, -						ima_dir, NULL, +						ima_dir, (void *)(uintptr_t)i,  						&ima_measurements_ops);  		if (IS_ERR(dentry))  			return PTR_ERR(dentry); - -		binary_securityfs_measurement_lists[i] = dentry;  	}  	return 0; @@ -533,8 +477,7 @@ static int ima_release_policy(struct inode *inode, struct file *file)  	ima_update_policy();  #if !defined(CONFIG_IMA_WRITE_POLICY) && !defined(CONFIG_IMA_READ_POLICY) -	securityfs_remove(ima_policy); -	ima_policy = NULL; +	securityfs_remove(file->f_path.dentry);  #elif defined(CONFIG_IMA_WRITE_POLICY)  	clear_bit(IMA_FS_BUSY, &ima_fs_flags);  #elif defined(CONFIG_IMA_READ_POLICY) @@ -553,11 +496,9 @@ static const struct file_operations ima_measure_policy_ops = {  int __init ima_fs_init(void)  { +	struct dentry *dentry;  	int ret; -	ascii_securityfs_measurement_lists = NULL; -	binary_securityfs_measurement_lists = NULL; -  	ima_dir = securityfs_create_dir("ima", integrity_dir);  	if (IS_ERR(ima_dir))  		return PTR_ERR(ima_dir); @@ -573,57 +514,45 @@ int __init ima_fs_init(void)  	if (ret != 0)  		goto out; -	binary_runtime_measurements = -	    securityfs_create_symlink("binary_runtime_measurements", ima_dir, +	dentry = securityfs_create_symlink("binary_runtime_measurements", ima_dir,  				      "binary_runtime_measurements_sha1", NULL); -	if (IS_ERR(binary_runtime_measurements)) { -		ret = PTR_ERR(binary_runtime_measurements); +	if (IS_ERR(dentry)) { +		ret = PTR_ERR(dentry);  		goto out;  	} -	ascii_runtime_measurements = -	    securityfs_create_symlink("ascii_runtime_measurements", ima_dir, +	dentry = securityfs_create_symlink("ascii_runtime_measurements", ima_dir,  				      "ascii_runtime_measurements_sha1", NULL); -	if (IS_ERR(ascii_runtime_measurements)) { -		ret = PTR_ERR(ascii_runtime_measurements); +	if (IS_ERR(dentry)) { +		ret = PTR_ERR(dentry);  		goto out;  	} -	runtime_measurements_count = -	    securityfs_create_file("runtime_measurements_count", +	dentry = securityfs_create_file("runtime_measurements_count",  				   S_IRUSR | S_IRGRP, ima_dir, NULL,  				   &ima_measurements_count_ops); -	if (IS_ERR(runtime_measurements_count)) { -		ret = PTR_ERR(runtime_measurements_count); +	if (IS_ERR(dentry)) { +		ret = PTR_ERR(dentry);  		goto out;  	} -	violations = -	    securityfs_create_file("violations", S_IRUSR | S_IRGRP, +	dentry = securityfs_create_file("violations", S_IRUSR | S_IRGRP,  				   ima_dir, NULL, &ima_htable_violations_ops); -	if (IS_ERR(violations)) { -		ret = PTR_ERR(violations); +	if (IS_ERR(dentry)) { +		ret = PTR_ERR(dentry);  		goto out;  	} -	ima_policy = securityfs_create_file("policy", POLICY_FILE_FLAGS, +	dentry = securityfs_create_file("policy", POLICY_FILE_FLAGS,  					    ima_dir, NULL,  					    &ima_measure_policy_ops); -	if (IS_ERR(ima_policy)) { -		ret = PTR_ERR(ima_policy); +	if (IS_ERR(dentry)) { +		ret = PTR_ERR(dentry);  		goto out;  	}  	return 0;  out: -	securityfs_remove(ima_policy); -	securityfs_remove(violations); -	securityfs_remove(runtime_measurements_count); -	securityfs_remove(ascii_runtime_measurements); -	securityfs_remove(binary_runtime_measurements); -	remove_securityfs_measurement_lists(ascii_securityfs_measurement_lists); -	remove_securityfs_measurement_lists(binary_securityfs_measurement_lists); -	securityfs_measurement_list_count = 0;  	securityfs_remove(ima_symlink);  	securityfs_remove(ima_dir); diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c index 9d45f4d26f73..7362f68f2d8b 100644 --- a/security/integrity/ima/ima_kexec.c +++ b/security/integrity/ima/ima_kexec.c @@ -12,66 +12,118 @@  #include <linux/kexec.h>  #include <linux/of.h>  #include <linux/ima.h> +#include <linux/reboot.h> +#include <asm/page.h>  #include "ima.h"  #ifdef CONFIG_IMA_KEXEC +#define IMA_KEXEC_EVENT_LEN 256 + +static bool ima_kexec_update_registered; +static struct seq_file ima_kexec_file; +static size_t kexec_segment_size; +static void *ima_kexec_buffer; + +static void ima_free_kexec_file_buf(struct seq_file *sf) +{ +	vfree(sf->buf); +	sf->buf = NULL; +	sf->size = 0; +	sf->read_pos = 0; +	sf->count = 0; +} + +void ima_measure_kexec_event(const char *event_name) +{ +	char ima_kexec_event[IMA_KEXEC_EVENT_LEN]; +	size_t buf_size = 0; +	long len; +	int n; + +	buf_size = ima_get_binary_runtime_size(); +	len = atomic_long_read(&ima_htable.len); + +	n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN, +		      "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;" +		      "ima_runtime_measurements_count=%ld;", +		      kexec_segment_size, buf_size, len); + +	ima_measure_critical_data("ima_kexec", event_name, ima_kexec_event, n, false, NULL, 0); +} + +static int ima_alloc_kexec_file_buf(size_t segment_size) +{ +	/* +	 * kexec 'load' may be called multiple times. +	 * Free and realloc the buffer only if the segment_size is +	 * changed from the previous kexec 'load' call. +	 */ +	if (ima_kexec_file.buf && ima_kexec_file.size == segment_size) +		goto out; + +	ima_free_kexec_file_buf(&ima_kexec_file); + +	/* segment size can't change between kexec load and execute */ +	ima_kexec_file.buf = vmalloc(segment_size); +	if (!ima_kexec_file.buf) +		return -ENOMEM; + +	ima_kexec_file.size = segment_size; + +out: +	ima_kexec_file.read_pos = 0; +	ima_kexec_file.count = sizeof(struct ima_kexec_hdr);	/* reserved space */ +	ima_measure_kexec_event("kexec_load"); + +	return 0; +} +  static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,  				     unsigned long segment_size)  {  	struct ima_queue_entry *qe; -	struct seq_file file;  	struct ima_kexec_hdr khdr;  	int ret = 0;  	/* segment size can't change between kexec load and execute */ -	file.buf = vmalloc(segment_size); -	if (!file.buf) { -		ret = -ENOMEM; -		goto out; +	if (!ima_kexec_file.buf) { +		pr_err("Kexec file buf not allocated\n"); +		return -EINVAL;  	} -	file.file = NULL; -	file.size = segment_size; -	file.read_pos = 0; -	file.count = sizeof(khdr);	/* reserved space */ -  	memset(&khdr, 0, sizeof(khdr));  	khdr.version = 1;  	/* This is an append-only list, no need to hold the RCU read lock */  	list_for_each_entry_rcu(qe, &ima_measurements, later, true) { -		if (file.count < file.size) { +		if (ima_kexec_file.count < ima_kexec_file.size) {  			khdr.count++; -			ima_measurements_show(&file, qe); +			ima_measurements_show(&ima_kexec_file, qe);  		} else {  			ret = -EINVAL;  			break;  		}  	} -	if (ret < 0) -		goto out; -  	/*  	 * fill in reserved space with some buffer details  	 * (eg. version, buffer size, number of measurements)  	 */ -	khdr.buffer_size = file.count; +	khdr.buffer_size = ima_kexec_file.count;  	if (ima_canonical_fmt) {  		khdr.version = cpu_to_le16(khdr.version);  		khdr.count = cpu_to_le64(khdr.count);  		khdr.buffer_size = cpu_to_le64(khdr.buffer_size);  	} -	memcpy(file.buf, &khdr, sizeof(khdr)); +	memcpy(ima_kexec_file.buf, &khdr, sizeof(khdr));  	print_hex_dump_debug("ima dump: ", DUMP_PREFIX_NONE, 16, 1, -			     file.buf, file.count < 100 ? file.count : 100, +			     ima_kexec_file.buf, ima_kexec_file.count < 100 ? +			     ima_kexec_file.count : 100,  			     true); -	*buffer_size = file.count; -	*buffer = file.buf; -out: -	if (ret == -EINVAL) -		vfree(file.buf); +	*buffer_size = ima_kexec_file.count; +	*buffer = ima_kexec_file.buf; +  	return ret;  } @@ -87,32 +139,39 @@ void ima_add_kexec_buffer(struct kimage *image)  				  .buf_min = 0, .buf_max = ULONG_MAX,  				  .top_down = true };  	unsigned long binary_runtime_size; +	unsigned long extra_memory;  	/* use more understandable variable names than defined in kbuf */ +	size_t kexec_buffer_size = 0;  	void *kexec_buffer = NULL; -	size_t kexec_buffer_size; -	size_t kexec_segment_size;  	int ret; +	if (image->type == KEXEC_TYPE_CRASH) +		return; +  	/* -	 * Reserve an extra half page of memory for additional measurements -	 * added during the kexec load. +	 * Reserve extra memory for measurements added during kexec.  	 */ -	binary_runtime_size = ima_get_binary_runtime_size(); +	if (CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB <= 0) +		extra_memory = PAGE_SIZE / 2; +	else +		extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024; + +	binary_runtime_size = ima_get_binary_runtime_size() + extra_memory; +  	if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)  		kexec_segment_size = ULONG_MAX;  	else -		kexec_segment_size = ALIGN(ima_get_binary_runtime_size() + -					   PAGE_SIZE / 2, PAGE_SIZE); +		kexec_segment_size = ALIGN(binary_runtime_size, PAGE_SIZE); +  	if ((kexec_segment_size == ULONG_MAX) ||  	    ((kexec_segment_size >> PAGE_SHIFT) > totalram_pages() / 2)) {  		pr_err("Binary measurement list too large.\n");  		return;  	} -	ima_dump_measurement_list(&kexec_buffer_size, &kexec_buffer, -				  kexec_segment_size); -	if (!kexec_buffer) { +	ret = ima_alloc_kexec_file_buf(kexec_segment_size); +	if (ret < 0) {  		pr_err("Not enough memory for the kexec measurement buffer.\n");  		return;  	} @@ -120,6 +179,7 @@ void ima_add_kexec_buffer(struct kimage *image)  	kbuf.buffer = kexec_buffer;  	kbuf.bufsz = kexec_buffer_size;  	kbuf.memsz = kexec_segment_size; +	image->is_ima_segment_index_set = false;  	ret = kexec_add_buffer(&kbuf);  	if (ret) {  		pr_err("Error passing over kexec measurement buffer.\n"); @@ -130,10 +190,80 @@ void ima_add_kexec_buffer(struct kimage *image)  	image->ima_buffer_addr = kbuf.mem;  	image->ima_buffer_size = kexec_segment_size;  	image->ima_buffer = kexec_buffer; +	image->ima_segment_index = image->nr_segments - 1; +	image->is_ima_segment_index_set = true;  	kexec_dprintk("kexec measurement buffer for the loaded kernel at 0x%lx.\n",  		      kbuf.mem);  } + +/* + * Called during kexec execute so that IMA can update the measurement list. + */ +static int ima_update_kexec_buffer(struct notifier_block *self, +				   unsigned long action, void *data) +{ +	size_t buf_size = 0; +	int ret = NOTIFY_OK; +	void *buf = NULL; + +	if (!kexec_in_progress) { +		pr_info("No kexec in progress.\n"); +		return ret; +	} + +	if (!ima_kexec_buffer) { +		pr_err("Kexec buffer not set.\n"); +		return ret; +	} + +	ret = ima_dump_measurement_list(&buf_size, &buf, kexec_segment_size); + +	if (ret) +		pr_err("Dump measurements failed. Error:%d\n", ret); + +	if (buf_size != 0) +		memcpy(ima_kexec_buffer, buf, buf_size); + +	kimage_unmap_segment(ima_kexec_buffer); +	ima_kexec_buffer = NULL; + +	return ret; +} + +static struct notifier_block update_buffer_nb = { +	.notifier_call = ima_update_kexec_buffer, +	.priority = INT_MIN +}; + +/* + * Create a mapping for the source pages that contain the IMA buffer + * so we can update it later. + */ +void ima_kexec_post_load(struct kimage *image) +{ +	if (ima_kexec_buffer) { +		kimage_unmap_segment(ima_kexec_buffer); +		ima_kexec_buffer = NULL; +	} + +	if (!image->ima_buffer_addr) +		return; + +	ima_kexec_buffer = kimage_map_segment(image, +					      image->ima_buffer_addr, +					      image->ima_buffer_size); +	if (!ima_kexec_buffer) { +		pr_err("Could not map measurements buffer.\n"); +		return; +	} + +	if (!ima_kexec_update_registered) { +		register_reboot_notifier(&update_buffer_nb); +		ima_kexec_update_registered = true; +	} +} +  #endif /* IMA_KEXEC */  /* diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index f99ab1a3b0f0..cdd225f65a62 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -27,6 +27,7 @@  #include <linux/fs.h>  #include <linux/iversion.h>  #include <linux/evm.h> +#include <linux/crash_dump.h>  #include "ima.h" @@ -38,11 +39,30 @@ int ima_appraise;  int __ro_after_init ima_hash_algo = HASH_ALGO_SHA1;  static int hash_setup_done; +static int ima_disabled __ro_after_init;  static struct notifier_block ima_lsm_policy_notifier = {  	.notifier_call = ima_lsm_policy_change,  }; +static int __init ima_setup(char *str) +{ +	if (!is_kdump_kernel()) { +		pr_info("Warning: ima setup option only permitted in kdump"); +		return 1; +	} + +	if (strncmp(str, "off", 3) == 0) +		ima_disabled = 1; +	else if (strncmp(str, "on", 2) == 0) +		ima_disabled = 0; +	else +		pr_err("Invalid ima setup option: \"%s\" , please specify ima=on|off.", str); + +	return 1; +} +__setup("ima=", ima_setup); +  static int __init hash_setup(char *str)  {  	struct ima_template_desc *template_desc = ima_template_desc_current(); @@ -1186,6 +1206,12 @@ static int __init init_ima(void)  {  	int error; +	/*Note that turning IMA off is intentionally limited to kdump kernel.*/ +	if (ima_disabled && is_kdump_kernel()) { +		pr_info("IMA functionality is disabled"); +		return 0; +	} +  	ima_appraise_parse_cmdline();  	ima_init_template_list();  	hash_setup(CONFIG_IMA_DEFAULT_HASH); diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 83d53824aa98..590637e81ad1 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -241,6 +241,11 @@ static int ima_reboot_notifier(struct notifier_block *nb,  			       unsigned long action,  			       void *data)  { +#ifdef CONFIG_IMA_KEXEC +	if (action == SYS_RESTART && data && !strcmp(data, "kexec reboot")) +		ima_measure_kexec_event("kexec_execute"); +#endif +  	ima_measurements_suspend();  	return NOTIFY_DONE; diff --git a/security/integrity/platform_certs/load_powerpc.c b/security/integrity/platform_certs/load_powerpc.c index c85febca3343..714c961a00f5 100644 --- a/security/integrity/platform_certs/load_powerpc.c +++ b/security/integrity/platform_certs/load_powerpc.c @@ -75,12 +75,13 @@ static int __init load_powerpc_certs(void)  		return -ENODEV;  	// Check for known secure boot implementations from OPAL or PLPKS -	if (strcmp("ibm,edk2-compat-v1", buf) && strcmp("ibm,plpks-sb-v1", buf)) { +	if (strcmp("ibm,edk2-compat-v1", buf) && strcmp("ibm,plpks-sb-v1", buf) && +	    strcmp("ibm,plpks-sb-v0", buf)) {  		pr_err("Unsupported secvar implementation \"%s\", not loading certs\n", buf);  		return -ENODEV;  	} -	if (strcmp("ibm,plpks-sb-v1", buf) == 0) +	if (strcmp("ibm,plpks-sb-v1", buf) == 0 || strcmp("ibm,plpks-sb-v0", buf) == 0)  		/* PLPKS authenticated variables ESL data is prefixed with 8 bytes of timestamp */  		offset = 8; diff --git a/security/ipe/Kconfig b/security/ipe/Kconfig index 3c75bf267da4..a110a6cd848b 100644 --- a/security/ipe/Kconfig +++ b/security/ipe/Kconfig @@ -6,6 +6,7 @@  menuconfig SECURITY_IPE  	bool "Integrity Policy Enforcement (IPE)"  	depends on SECURITY && SECURITYFS && AUDIT && AUDITSYSCALL +	select CRYPTO_LIB_SHA256  	select PKCS7_MESSAGE_PARSER  	select SYSTEM_DATA_VERIFICATION  	select IPE_PROP_DM_VERITY if DM_VERITY diff --git a/security/ipe/audit.c b/security/ipe/audit.c index f05f0caa4850..de5fed62592e 100644 --- a/security/ipe/audit.c +++ b/security/ipe/audit.c @@ -6,7 +6,7 @@  #include <linux/slab.h>  #include <linux/audit.h>  #include <linux/types.h> -#include <crypto/hash.h> +#include <crypto/sha2.h>  #include "ipe.h"  #include "eval.h" @@ -17,10 +17,12 @@  #define ACTSTR(x) ((x) == IPE_ACTION_ALLOW ? "ALLOW" : "DENY") -#define IPE_AUDIT_HASH_ALG "sha256" +#define IPE_AUDIT_HASH_ALG "sha256" /* keep in sync with audit_policy() */  #define AUDIT_POLICY_LOAD_FMT "policy_name=\"%s\" policy_version=%hu.%hu.%hu "\  			      "policy_digest=" IPE_AUDIT_HASH_ALG ":" +#define AUDIT_POLICY_LOAD_NULL_FMT "policy_name=? policy_version=? "\ +				   "policy_digest=?"  #define AUDIT_OLD_ACTIVE_POLICY_FMT "old_active_pol_name=\"%s\" "\  				    "old_active_pol_version=%hu.%hu.%hu "\  				    "old_policy_digest=" IPE_AUDIT_HASH_ALG ":" @@ -180,37 +182,14 @@ static void audit_policy(struct audit_buffer *ab,  			 const char *audit_format,  			 const struct ipe_policy *const p)  { -	SHASH_DESC_ON_STACK(desc, tfm); -	struct crypto_shash *tfm; -	u8 *digest = NULL; +	u8 digest[SHA256_DIGEST_SIZE]; -	tfm = crypto_alloc_shash(IPE_AUDIT_HASH_ALG, 0, 0); -	if (IS_ERR(tfm)) -		return; - -	desc->tfm = tfm; - -	digest = kzalloc(crypto_shash_digestsize(tfm), GFP_KERNEL); -	if (!digest) -		goto out; - -	if (crypto_shash_init(desc)) -		goto out; - -	if (crypto_shash_update(desc, p->pkcs7, p->pkcs7len)) -		goto out; - -	if (crypto_shash_final(desc, digest)) -		goto out; +	sha256(p->pkcs7, p->pkcs7len, digest);  	audit_log_format(ab, audit_format, p->parsed->name,  			 p->parsed->version.major, p->parsed->version.minor,  			 p->parsed->version.rev); -	audit_log_n_hex(ab, digest, crypto_shash_digestsize(tfm)); - -out: -	kfree(digest); -	crypto_free_shash(tfm); +	audit_log_n_hex(ab, digest, sizeof(digest));  }  /** @@ -248,22 +227,29 @@ void ipe_audit_policy_activation(const struct ipe_policy *const op,  }  /** - * ipe_audit_policy_load() - Audit a policy being loaded into the kernel. - * @p: Supplies a pointer to the policy to audit. + * ipe_audit_policy_load() - Audit a policy loading event. + * @p: Supplies a pointer to the policy to audit or an error pointer.   */  void ipe_audit_policy_load(const struct ipe_policy *const p)  {  	struct audit_buffer *ab; +	int err = 0;  	ab = audit_log_start(audit_context(), GFP_KERNEL,  			     AUDIT_IPE_POLICY_LOAD);  	if (!ab)  		return; -	audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p); -	audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=1", +	if (!IS_ERR(p)) { +		audit_policy(ab, AUDIT_POLICY_LOAD_FMT, p); +	} else { +		audit_log_format(ab, AUDIT_POLICY_LOAD_NULL_FMT); +		err = PTR_ERR(p); +	} + +	audit_log_format(ab, " auid=%u ses=%u lsm=ipe res=%d errno=%d",  			 from_kuid(&init_user_ns, audit_get_loginuid(current)), -			 audit_get_sessionid(current)); +			 audit_get_sessionid(current), !err, err);  	audit_log_end(ab);  } diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 5b6d19fb844a..0bb9468b8026 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -12,11 +12,8 @@  #include "policy.h"  #include "audit.h" -static struct dentry *np __ro_after_init;  static struct dentry *root __ro_after_init;  struct dentry *policy_root __ro_after_init; -static struct dentry *audit_node __ro_after_init; -static struct dentry *enforce_node __ro_after_init;  /**   * setaudit() - Write handler for the securityfs node, "ipe/success_audit" @@ -133,6 +130,8 @@ static ssize_t getenforce(struct file *f, char __user *data,   * * %-ERANGE			- Policy version number overflow   * * %-EINVAL			- Policy version parsing error   * * %-EEXIST			- Same name policy already deployed + * * %-ENOKEY			- Policy signing key not found + * * %-EKEYREJECTED		- Policy signature verification failed   */  static ssize_t new_policy(struct file *f, const char __user *data,  			  size_t len, loff_t *offset) @@ -141,12 +140,17 @@ static ssize_t new_policy(struct file *f, const char __user *data,  	char *copy = NULL;  	int rc = 0; -	if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) -		return -EPERM; +	if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) { +		rc = -EPERM; +		goto out; +	}  	copy = memdup_user_nul(data, len); -	if (IS_ERR(copy)) -		return PTR_ERR(copy); +	if (IS_ERR(copy)) { +		rc = PTR_ERR(copy); +		copy = NULL; +		goto out; +	}  	p = ipe_new_policy(NULL, 0, copy, len);  	if (IS_ERR(p)) { @@ -158,12 +162,14 @@ static ssize_t new_policy(struct file *f, const char __user *data,  	if (rc)  		goto out; -	ipe_audit_policy_load(p); -  out: -	if (rc < 0) -		ipe_free_policy(p);  	kfree(copy); +	if (rc < 0) { +		ipe_free_policy(p); +		ipe_audit_policy_load(ERR_PTR(rc)); +	} else { +		ipe_audit_policy_load(p); +	}  	return (rc < 0) ? rc : len;  } @@ -191,27 +197,26 @@ static int __init ipe_init_securityfs(void)  {  	int rc = 0;  	struct ipe_policy *ap; +	struct dentry *dentry;  	if (!ipe_enabled)  		return -EOPNOTSUPP;  	root = securityfs_create_dir("ipe", NULL); -	if (IS_ERR(root)) { -		rc = PTR_ERR(root); -		goto err; -	} +	if (IS_ERR(root)) +		return PTR_ERR(root); -	audit_node = securityfs_create_file("success_audit", 0600, root, +	dentry = securityfs_create_file("success_audit", 0600, root,  					    NULL, &audit_fops); -	if (IS_ERR(audit_node)) { -		rc = PTR_ERR(audit_node); +	if (IS_ERR(dentry)) { +		rc = PTR_ERR(dentry);  		goto err;  	} -	enforce_node = securityfs_create_file("enforce", 0600, root, NULL, +	dentry = securityfs_create_file("enforce", 0600, root, NULL,  					      &enforce_fops); -	if (IS_ERR(enforce_node)) { -		rc = PTR_ERR(enforce_node); +	if (IS_ERR(dentry)) { +		rc = PTR_ERR(dentry);  		goto err;  	} @@ -228,18 +233,14 @@ static int __init ipe_init_securityfs(void)  			goto err;  	} -	np = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops); -	if (IS_ERR(np)) { -		rc = PTR_ERR(np); +	dentry = securityfs_create_file("new_policy", 0200, root, NULL, &np_fops); +	if (IS_ERR(dentry)) { +		rc = PTR_ERR(dentry);  		goto err;  	}  	return 0;  err: -	securityfs_remove(np); -	securityfs_remove(policy_root); -	securityfs_remove(enforce_node); -	securityfs_remove(audit_node);  	securityfs_remove(root);  	return rc;  } diff --git a/security/ipe/policy.c b/security/ipe/policy.c index b628f696e32b..1c58c29886e8 100644 --- a/security/ipe/policy.c +++ b/security/ipe/policy.c @@ -84,8 +84,11 @@ static int set_pkcs7_data(void *ctx, const void *data, size_t len,   * ipe_new_policy.   *   * Context: Requires root->i_rwsem to be held. - * Return: %0 on success. If an error occurs, the function will return - * the -errno. + * Return: + * * %0	- Success + * * %-ENOENT	- Policy was deleted while updating + * * %-EINVAL	- Policy name mismatch + * * %-ESTALE	- Policy version too old   */  int ipe_update_policy(struct inode *root, const char *text, size_t textlen,  		      const char *pkcs7, size_t pkcs7len) @@ -146,10 +149,12 @@ err:   *   * Return:   * * a pointer to the ipe_policy structure	- Success - * * %-EBADMSG					- Policy is invalid - * * %-ENOMEM					- Out of memory (OOM) - * * %-ERANGE					- Policy version number overflow - * * %-EINVAL					- Policy version parsing error + * * %-EBADMSG				- Policy is invalid + * * %-ENOMEM				- Out of memory (OOM) + * * %-ERANGE				- Policy version number overflow + * * %-EINVAL				- Policy version parsing error + * * %-ENOKEY				- Policy signing key not found + * * %-EKEYREJECTED			- Policy signature verification failed   */  struct ipe_policy *ipe_new_policy(const char *text, size_t textlen,  				  const char *pkcs7, size_t pkcs7len) diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 4cb4dd7f5236..9d92d8a14b13 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -12,6 +12,7 @@  #include "policy.h"  #include "eval.h"  #include "fs.h" +#include "audit.h"  #define MAX_VERSION_SIZE ARRAY_SIZE("65535.65535.65535") @@ -286,8 +287,13 @@ static ssize_t getactive(struct file *f, char __user *data,   * On success this updates the policy represented by $name,   * in-place.   * - * Return: Length of buffer written on success. If an error occurs, - * the function will return the -errno. + * Return: + * * Length of buffer written		- Success + * * %-EPERM				- Insufficient permission + * * %-ENOMEM				- Out of memory (OOM) + * * %-ENOENT				- Policy was deleted while updating + * * %-EINVAL				- Policy name mismatch + * * %-ESTALE				- Policy version too old   */  static ssize_t update_policy(struct file *f, const char __user *data,  			     size_t len, loff_t *offset) @@ -296,21 +302,29 @@ static ssize_t update_policy(struct file *f, const char __user *data,  	char *copy = NULL;  	int rc = 0; -	if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) -		return -EPERM; +	if (!file_ns_capable(f, &init_user_ns, CAP_MAC_ADMIN)) { +		rc = -EPERM; +		goto out; +	}  	copy = memdup_user(data, len); -	if (IS_ERR(copy)) -		return PTR_ERR(copy); +	if (IS_ERR(copy)) { +		rc = PTR_ERR(copy); +		copy = NULL; +		goto out; +	}  	root = d_inode(f->f_path.dentry->d_parent);  	inode_lock(root);  	rc = ipe_update_policy(root, NULL, 0, copy, len);  	inode_unlock(root); +out:  	kfree(copy); -	if (rc) +	if (rc) { +		ipe_audit_policy_load(ERR_PTR(rc));  		return rc; +	}  	return len;  } @@ -424,7 +438,7 @@ static const struct ipefs_file policy_subdir[] = {   */  void ipe_del_policyfs_node(struct ipe_policy *p)  { -	securityfs_recursive_remove(p->policyfs); +	securityfs_remove(p->policyfs);  	p->policyfs = NULL;  } @@ -471,6 +485,6 @@ int ipe_new_policyfs_node(struct ipe_policy *p)  	return 0;  err: -	securityfs_recursive_remove(policyfs); +	securityfs_remove(policyfs);  	return rc;  } diff --git a/security/keys/gc.c b/security/keys/gc.c index f27223ea4578..748e83818a76 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -218,8 +218,8 @@ continue_scanning:  		key = rb_entry(cursor, struct key, serial_node);  		cursor = rb_next(cursor); -		if (test_bit(KEY_FLAG_FINAL_PUT, &key->flags)) { -			smp_mb(); /* Clobber key->user after FINAL_PUT seen. */ +		if (!test_bit_acquire(KEY_FLAG_USER_ALIVE, &key->flags)) { +			/* Clobber key->user after final put seen. */  			goto found_unreferenced_key;  		} diff --git a/security/keys/key.c b/security/keys/key.c index 7198cd2ac3a3..3bbdde778631 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -298,6 +298,7 @@ struct key *key_alloc(struct key_type *type, const char *desc,  	key->restrict_link = restrict_link;  	key->last_used_at = ktime_get_real_seconds(); +	key->flags |= 1 << KEY_FLAG_USER_ALIVE;  	if (!(flags & KEY_ALLOC_NOT_IN_QUOTA))  		key->flags |= 1 << KEY_FLAG_IN_QUOTA;  	if (flags & KEY_ALLOC_BUILT_IN) @@ -658,8 +659,8 @@ void key_put(struct key *key)  				key->user->qnbytes -= key->quotalen;  				spin_unlock_irqrestore(&key->user->lock, flags);  			} -			smp_mb(); /* key->user before FINAL_PUT set. */ -			set_bit(KEY_FLAG_FINAL_PUT, &key->flags); +			/* Mark key as safe for GC after key->user done. */ +			clear_bit_unlock(KEY_FLAG_USER_ALIVE, &key->flags);  			schedule_work(&key_gc_work);  		}  	} diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 6fee7c20f64d..c04f8879ad03 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -895,6 +895,7 @@ static bool is_access_to_paths_allowed(  		/* Stops when a rule from each layer grants access. */  		if (allowed_parent1 && allowed_parent2)  			break; +  jump_up:  		if (walker_path.dentry == walker_path.mnt->mnt_root) {  			if (follow_up(&walker_path)) { diff --git a/security/landlock/id.c b/security/landlock/id.c index 56f7cc0fc744..838c3ed7bb82 100644 --- a/security/landlock/id.c +++ b/security/landlock/id.c @@ -119,6 +119,12 @@ static u64 get_id_range(size_t number_of_ids, atomic64_t *const counter,  #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST +static u8 get_random_u8_positive(void) +{ +	/* max() evaluates its arguments once. */ +	return max(1, get_random_u8()); +} +  static void test_range1_rand0(struct kunit *const test)  {  	atomic64_t counter; @@ -127,9 +133,10 @@ static void test_range1_rand0(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 0), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 1); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 1);  }  static void test_range1_rand1(struct kunit *const test) @@ -140,9 +147,10 @@ static void test_range1_rand1(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 1), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 2); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 2);  }  static void test_range1_rand15(struct kunit *const test) @@ -153,9 +161,10 @@ static void test_range1_rand15(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 15), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 16); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 16);  }  static void test_range1_rand16(struct kunit *const test) @@ -166,9 +175,10 @@ static void test_range1_rand16(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(1, &counter, 16), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 1); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 1);  }  static void test_range2_rand0(struct kunit *const test) @@ -179,9 +189,10 @@ static void test_range2_rand0(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 0), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 2); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 2);  }  static void test_range2_rand1(struct kunit *const test) @@ -192,9 +203,10 @@ static void test_range2_rand1(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 1), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 3); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 3);  }  static void test_range2_rand2(struct kunit *const test) @@ -205,9 +217,10 @@ static void test_range2_rand2(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 2), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 4); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 4);  }  static void test_range2_rand15(struct kunit *const test) @@ -218,9 +231,10 @@ static void test_range2_rand15(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 15), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 17); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 17);  }  static void test_range2_rand16(struct kunit *const test) @@ -231,9 +245,10 @@ static void test_range2_rand16(struct kunit *const test)  	init = get_random_u32();  	atomic64_set(&counter, init);  	KUNIT_EXPECT_EQ(test, get_id_range(2, &counter, 16), init); -	KUNIT_EXPECT_EQ( -		test, get_id_range(get_random_u8(), &counter, get_random_u8()), -		init + 2); +	KUNIT_EXPECT_EQ(test, +			get_id_range(get_random_u8_positive(), &counter, +				     get_random_u8()), +			init + 2);  }  #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 33eafb71e4f3..0116e9f93ffe 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -303,7 +303,6 @@ static int get_path_from_fd(const s32 fd, struct path *const path)  	if ((fd_file(f)->f_op == &ruleset_fops) ||  	    (fd_file(f)->f_path.mnt->mnt_flags & MNT_INTERNAL) ||  	    (fd_file(f)->f_path.dentry->d_sb->s_flags & SB_NOUSER) || -	    d_is_negative(fd_file(f)->f_path.dentry) ||  	    IS_PRIVATE(d_backing_inode(fd_file(f)->f_path.dentry)))  		return -EBADFD; diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 1b942b4908a2..7d623b00495c 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -24,7 +24,6 @@  #include <net/ipv6.h>  #include <linux/tcp.h>  #include <linux/udp.h> -#include <linux/dccp.h>  #include <linux/sctp.h>  #include <linux/lsm_audit.h>  #include <linux/security.h> @@ -68,13 +67,6 @@ int ipv4_skb_to_auditdata(struct sk_buff *skb,  		ad->u.net->dport = uh->dest;  		break;  	} -	case IPPROTO_DCCP: { -		struct dccp_hdr *dh = dccp_hdr(skb); - -		ad->u.net->sport = dh->dccph_sport; -		ad->u.net->dport = dh->dccph_dport; -		break; -	}  	case IPPROTO_SCTP: {  		struct sctphdr *sh = sctp_hdr(skb); @@ -140,17 +132,6 @@ int ipv6_skb_to_auditdata(struct sk_buff *skb,  		ad->u.net->dport = uh->dest;  		break;  	} -	case IPPROTO_DCCP: { -		struct dccp_hdr _dccph, *dh; - -		dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); -		if (dh == NULL) -			break; - -		ad->u.net->sport = dh->dccph_sport; -		ad->u.net->dport = dh->dccph_dport; -		break; -	}  	case IPPROTO_SCTP: {  		struct sctphdr _sctph, *sh; diff --git a/security/security.c b/security/security.c index fb57e8fddd91..ad163f06bf7a 100644 --- a/security/security.c +++ b/security/security.c @@ -2181,7 +2181,7 @@ int security_inode_symlink(struct inode *dir, struct dentry *dentry,  }  /** - * security_inode_mkdir() - Check if creation a new director is allowed + * security_inode_mkdir() - Check if creating a new directory is allowed   * @dir: parent directory   * @dentry: new directory   * @mode: new directory mode @@ -2623,6 +2623,36 @@ void security_inode_post_removexattr(struct dentry *dentry, const char *name)  }  /** + * security_inode_file_setattr() - check if setting fsxattr is allowed + * @dentry: file to set filesystem extended attributes on + * @fa: extended attributes to set on the inode + * + * Called when file_setattr() syscall or FS_IOC_FSSETXATTR ioctl() is called on + * inode + * + * Return: Returns 0 if permission is granted. + */ +int security_inode_file_setattr(struct dentry *dentry, struct file_kattr *fa) +{ +	return call_int_hook(inode_file_setattr, dentry, fa); +} + +/** + * security_inode_file_getattr() - check if retrieving fsxattr is allowed + * @dentry: file to retrieve filesystem extended attributes from + * @fa: extended attributes to get + * + * Called when file_getattr() syscall or FS_IOC_FSGETXATTR ioctl() is called on + * inode + * + * Return: Returns 0 if permission is granted. + */ +int security_inode_file_getattr(struct dentry *dentry, struct file_kattr *fa) +{ +	return call_int_hook(inode_file_getattr, dentry, fa); +} + +/**   * security_inode_need_killpriv() - Check if security_inode_killpriv() required   * @dentry: associated dentry   * @@ -4277,24 +4307,6 @@ int security_setprocattr(int lsmid, const char *name, void *value, size_t size)  }  /** - * security_netlink_send() - Save info and check if netlink sending is allowed - * @sk: sending socket - * @skb: netlink message - * - * Save security information for a netlink message so that permission checking - * can be performed when the message is processed.  The security information - * can be saved using the eff_cap field of the netlink_skb_parms structure. - * Also may be used to provide fine grained control over message transmission. - * - * Return: Returns 0 if the information was successfully saved and message is - *         allowed to be transmitted. - */ -int security_netlink_send(struct sock *sk, struct sk_buff *skb) -{ -	return call_int_hook(netlink_send, sk, skb); -} - -/**   * security_ismaclabel() - Check if the named attribute is a MAC label   * @name: full extended attribute name   * @@ -4484,6 +4496,24 @@ int security_watch_key(struct key *key)  #ifdef CONFIG_SECURITY_NETWORK  /** + * security_netlink_send() - Save info and check if netlink sending is allowed + * @sk: sending socket + * @skb: netlink message + * + * Save security information for a netlink message so that permission checking + * can be performed when the message is processed.  The security information + * can be saved using the eff_cap field of the netlink_skb_parms structure. + * Also may be used to provide fine grained control over message transmission. + * + * Return: Returns 0 if the information was successfully saved and message is + *         allowed to be transmitted. + */ +int security_netlink_send(struct sock *sk, struct sk_buff *skb) +{ +	return call_int_hook(netlink_send, sk, skb); +} + +/**   * security_unix_stream_connect() - Check if a AF_UNIX stream is allowed   * @sock: originating sock   * @other: peer sock diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e7a7dcab81db..c95a5874bf7d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -65,7 +65,6 @@  #include <net/netlink.h>  #include <linux/tcp.h>  #include <linux/udp.h> -#include <linux/dccp.h>  #include <linux/sctp.h>  #include <net/sctp/structs.h>  #include <linux/quota.h> @@ -213,8 +212,10 @@ static void cred_init_security(void)  {  	struct task_security_struct *tsec; +	/* NOTE: the lsm framework zeros out the buffer on allocation */ +  	tsec = selinux_cred(unrcu_pointer(current->real_cred)); -	tsec->osid = tsec->sid = SECINITSID_KERNEL; +	tsec->osid = tsec->sid = tsec->avdcache.sid = SECINITSID_KERNEL;  }  /* @@ -278,27 +279,21 @@ static int __inode_security_revalidate(struct inode *inode,  				       struct dentry *dentry,  				       bool may_sleep)  { -	struct inode_security_struct *isec = selinux_inode(inode); +	if (!selinux_initialized()) +		return 0; -	might_sleep_if(may_sleep); +	if (may_sleep) +		might_sleep(); +	else +		return -ECHILD;  	/* -	 * The check of isec->initialized below is racy but -	 * inode_doinit_with_dentry() will recheck with -	 * isec->lock held. +	 * Check to ensure that an inode's SELinux state is valid and try +	 * reloading the inode security label if necessary.  This will fail if +	 * @dentry is NULL and no dentry for this inode can be found; in that +	 * case, continue using the old label.  	 */ -	if (selinux_initialized() && -	    data_race(isec->initialized != LABEL_INITIALIZED)) { -		if (!may_sleep) -			return -ECHILD; - -		/* -		 * Try reloading the inode security label.  This will fail if -		 * @opt_dentry is NULL and no dentry for this inode can be -		 * found; in that case, continue using the old label. -		 */ -		inode_doinit_with_dentry(inode, dentry); -	} +	inode_doinit_with_dentry(inode, dentry);  	return 0;  } @@ -307,41 +302,53 @@ static struct inode_security_struct *inode_security_novalidate(struct inode *ino  	return selinux_inode(inode);  } -static struct inode_security_struct *inode_security_rcu(struct inode *inode, bool rcu) +static inline struct inode_security_struct *inode_security_rcu(struct inode *inode, +							       bool rcu)  { -	int error; +	int rc; +	struct inode_security_struct *isec = selinux_inode(inode); -	error = __inode_security_revalidate(inode, NULL, !rcu); -	if (error) -		return ERR_PTR(error); -	return selinux_inode(inode); +	/* check below is racy, but revalidate will recheck with lock held */ +	if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) +		return isec; +	rc = __inode_security_revalidate(inode, NULL, !rcu); +	if (rc) +		return ERR_PTR(rc); +	return isec;  }  /*   * Get the security label of an inode.   */ -static struct inode_security_struct *inode_security(struct inode *inode) +static inline struct inode_security_struct *inode_security(struct inode *inode)  { +	struct inode_security_struct *isec = selinux_inode(inode); + +	/* check below is racy, but revalidate will recheck with lock held */ +	if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) +		return isec;  	__inode_security_revalidate(inode, NULL, true); -	return selinux_inode(inode); +	return isec;  } -static struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry) +static inline struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry)  { -	struct inode *inode = d_backing_inode(dentry); - -	return selinux_inode(inode); +	return selinux_inode(d_backing_inode(dentry));  }  /*   * Get the security label of a dentry's backing inode.   */ -static struct inode_security_struct *backing_inode_security(struct dentry *dentry) +static inline struct inode_security_struct *backing_inode_security(struct dentry *dentry)  {  	struct inode *inode = d_backing_inode(dentry); +	struct inode_security_struct *isec = selinux_inode(inode); +	/* check below is racy, but revalidate will recheck with lock held */ +	if (data_race(likely(isec->initialized == LABEL_INITIALIZED))) +		return isec;  	__inode_security_revalidate(inode, dentry, true); -	return selinux_inode(inode); +	return isec;  }  static void inode_free_security(struct inode *inode) @@ -1191,8 +1198,6 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc  				return SECCLASS_ICMP_SOCKET;  			else  				return SECCLASS_RAWIP_SOCKET; -		case SOCK_DCCP: -			return SECCLASS_DCCP_SOCKET;  		default:  			return SECCLASS_RAWIP_SOCKET;  		} @@ -1683,12 +1688,15 @@ static inline int dentry_has_perm(const struct cred *cred,  				  struct dentry *dentry,  				  u32 av)  { -	struct inode *inode = d_backing_inode(dentry);  	struct common_audit_data ad; +	struct inode *inode = d_backing_inode(dentry); +	struct inode_security_struct *isec = selinux_inode(inode);  	ad.type = LSM_AUDIT_DATA_DENTRY;  	ad.u.dentry = dentry; -	__inode_security_revalidate(inode, dentry, true); +	/* check below is racy, but revalidate will recheck with lock held */ +	if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED))) +		__inode_security_revalidate(inode, dentry, true);  	return inode_has_perm(cred, inode, av, &ad);  } @@ -1699,12 +1707,15 @@ static inline int path_has_perm(const struct cred *cred,  				const struct path *path,  				u32 av)  { -	struct inode *inode = d_backing_inode(path->dentry);  	struct common_audit_data ad; +	struct inode *inode = d_backing_inode(path->dentry); +	struct inode_security_struct *isec = selinux_inode(inode);  	ad.type = LSM_AUDIT_DATA_PATH;  	ad.u.path = *path; -	__inode_security_revalidate(inode, path->dentry, true); +	/* check below is racy, but revalidate will recheck with lock held */ +	if (data_race(unlikely(isec->initialized != LABEL_INITIALIZED))) +		__inode_security_revalidate(inode, path->dentry, true);  	return inode_has_perm(cred, inode, av, &ad);  } @@ -3088,44 +3099,152 @@ static noinline int audit_inode_permission(struct inode *inode,  			    audited, denied, result, &ad);  } -static int selinux_inode_permission(struct inode *inode, int mask) +/** + * task_avdcache_reset - Reset the task's AVD cache + * @tsec: the task's security state + * + * Clear the task's AVD cache in @tsec and reset it to the current policy's + * and task's info. + */ +static inline void task_avdcache_reset(struct task_security_struct *tsec) +{ +	memset(&tsec->avdcache.dir, 0, sizeof(tsec->avdcache.dir)); +	tsec->avdcache.sid = tsec->sid; +	tsec->avdcache.seqno = avc_policy_seqno(); +	tsec->avdcache.dir_spot = TSEC_AVDC_DIR_SIZE - 1; +} + +/** + * task_avdcache_search - Search the task's AVD cache + * @tsec: the task's security state + * @isec: the inode to search for in the cache + * @avdc: matching avd cache entry returned to the caller + * + * Search @tsec for a AVD cache entry that matches @isec and return it to the + * caller via @avdc.  Returns 0 if a match is found, negative values otherwise. + */ +static inline int task_avdcache_search(struct task_security_struct *tsec, +				       struct inode_security_struct *isec, +				       struct avdc_entry **avdc) +{ +	int orig, iter; + +	/* focused on path walk optimization, only cache directories */ +	if (isec->sclass != SECCLASS_DIR) +		return -ENOENT; + +	if (unlikely(tsec->sid != tsec->avdcache.sid || +		     tsec->avdcache.seqno != avc_policy_seqno())) { +		task_avdcache_reset(tsec); +		return -ENOENT; +	} + +	orig = iter = tsec->avdcache.dir_spot; +	do { +		if (tsec->avdcache.dir[iter].isid == isec->sid) { +			/* cache hit */ +			tsec->avdcache.dir_spot = iter; +			*avdc = &tsec->avdcache.dir[iter]; +			return 0; +		} +		iter = (iter - 1) & (TSEC_AVDC_DIR_SIZE - 1); +	} while (iter != orig); + +	return -ENOENT; +} + +/** + * task_avdcache_update - Update the task's AVD cache + * @tsec: the task's security state + * @isec: the inode associated with the cache entry + * @avd: the AVD to cache + * @audited: the permission audit bitmask to cache + * + * Update the AVD cache in @tsec with the @avdc and @audited info associated + * with @isec. + */ +static inline void task_avdcache_update(struct task_security_struct *tsec, +					struct inode_security_struct *isec, +					struct av_decision *avd, +					u32 audited) +{ +	int spot; + +	/* focused on path walk optimization, only cache directories */ +	if (isec->sclass != SECCLASS_DIR) +		return; + +	/* update cache */ +	spot = (tsec->avdcache.dir_spot + 1) & (TSEC_AVDC_DIR_SIZE - 1); +	tsec->avdcache.dir_spot = spot; +	tsec->avdcache.dir[spot].isid = isec->sid; +	tsec->avdcache.dir[spot].audited = audited; +	tsec->avdcache.dir[spot].allowed = avd->allowed; +	tsec->avdcache.dir[spot].permissive = avd->flags & AVD_FLAGS_PERMISSIVE; +	tsec->avdcache.permissive_neveraudit = +		(avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)); +} + +/** + * selinux_inode_permission - Check if the current task can access an inode + * @inode: the inode that is being accessed + * @requested: the accesses being requested + * + * Check if the current task is allowed to access @inode according to + * @requested.  Returns 0 if allowed, negative values otherwise. + */ +static int selinux_inode_permission(struct inode *inode, int requested)  { +	int mask;  	u32 perms; -	bool from_access; -	bool no_block = mask & MAY_NOT_BLOCK; +	struct task_security_struct *tsec;  	struct inode_security_struct *isec; -	u32 sid = current_sid(); -	struct av_decision avd; +	struct avdc_entry *avdc;  	int rc, rc2;  	u32 audited, denied; -	from_access = mask & MAY_ACCESS; -	mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); +	mask = requested & (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);  	/* No permission to check.  Existence test. */  	if (!mask)  		return 0; -	if (unlikely(IS_PRIVATE(inode))) +	tsec = selinux_cred(current_cred()); +	if (task_avdcache_permnoaudit(tsec))  		return 0; -	perms = file_mask_to_av(inode->i_mode, mask); - -	isec = inode_security_rcu(inode, no_block); +	isec = inode_security_rcu(inode, requested & MAY_NOT_BLOCK);  	if (IS_ERR(isec))  		return PTR_ERR(isec); +	perms = file_mask_to_av(inode->i_mode, mask); + +	rc = task_avdcache_search(tsec, isec, &avdc); +	if (likely(!rc)) { +		/* Cache hit. */ +		audited = perms & avdc->audited; +		denied = perms & ~avdc->allowed; +		if (unlikely(denied && enforcing_enabled() && +			     !avdc->permissive)) +			rc = -EACCES; +	} else { +		struct av_decision avd; + +		/* Cache miss. */ +		rc = avc_has_perm_noaudit(tsec->sid, isec->sid, isec->sclass, +					  perms, 0, &avd); +		audited = avc_audit_required(perms, &avd, rc, +			(requested & MAY_ACCESS) ? FILE__AUDIT_ACCESS : 0, +			&denied); +		task_avdcache_update(tsec, isec, &avd, audited); +	} -	rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, -				  &avd); -	audited = avc_audit_required(perms, &avd, rc, -				     from_access ? FILE__AUDIT_ACCESS : 0, -				     &denied);  	if (likely(!audited))  		return rc;  	rc2 = audit_inode_permission(inode, perms, audited, denied, rc);  	if (rc2)  		return rc2; +  	return rc;  } @@ -3160,6 +3279,13 @@ static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,  static int selinux_inode_getattr(const struct path *path)  { +	struct task_security_struct *tsec; + +	tsec = selinux_cred(current_cred()); + +	if (task_avdcache_permnoaudit(tsec)) +		return 0; +  	return path_has_perm(current_cred(), path, FILE__GETATTR);  } @@ -3366,6 +3492,18 @@ static int selinux_inode_removexattr(struct mnt_idmap *idmap,  	return -EACCES;  } +static int selinux_inode_file_setattr(struct dentry *dentry, +				      struct file_kattr *fa) +{ +	return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); +} + +static int selinux_inode_file_getattr(struct dentry *dentry, +				      struct file_kattr *fa) +{ +	return dentry_has_perm(current_cred(), dentry, FILE__GETATTR); +} +  static int selinux_path_notify(const struct path *path, u64 mask,  						unsigned int obj_type)  { @@ -4392,22 +4530,6 @@ static int selinux_parse_skb_ipv4(struct sk_buff *skb,  		break;  	} -	case IPPROTO_DCCP: { -		struct dccp_hdr _dccph, *dh; - -		if (ntohs(ih->frag_off) & IP_OFFSET) -			break; - -		offset += ihlen; -		dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); -		if (dh == NULL) -			break; - -		ad->u.net->sport = dh->dccph_sport; -		ad->u.net->dport = dh->dccph_dport; -		break; -	} -  #if IS_ENABLED(CONFIG_IP_SCTP)  	case IPPROTO_SCTP: {  		struct sctphdr _sctph, *sh; @@ -4486,18 +4608,6 @@ static int selinux_parse_skb_ipv6(struct sk_buff *skb,  		break;  	} -	case IPPROTO_DCCP: { -		struct dccp_hdr _dccph, *dh; - -		dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); -		if (dh == NULL) -			break; - -		ad->u.net->sport = dh->dccph_sport; -		ad->u.net->dport = dh->dccph_dport; -		break; -	} -  #if IS_ENABLED(CONFIG_IP_SCTP)  	case IPPROTO_SCTP: {  		struct sctphdr _sctph, *sh; @@ -4849,10 +4959,6 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in  			node_perm = UDP_SOCKET__NODE_BIND;  			break; -		case SECCLASS_DCCP_SOCKET: -			node_perm = DCCP_SOCKET__NODE_BIND; -			break; -  		case SECCLASS_SCTP_SOCKET:  			node_perm = SCTP_SOCKET__NODE_BIND;  			break; @@ -4908,11 +5014,10 @@ static int selinux_socket_connect_helper(struct socket *sock,  		return 0;  	/* -	 * If a TCP, DCCP or SCTP socket, check name_connect permission +	 * If a TCP or SCTP socket, check name_connect permission  	 * for the port.  	 */  	if (sksec->sclass == SECCLASS_TCP_SOCKET || -	    sksec->sclass == SECCLASS_DCCP_SOCKET ||  	    sksec->sclass == SECCLASS_SCTP_SOCKET) {  		struct common_audit_data ad;  		struct lsm_network_audit net = {0,}; @@ -4957,9 +5062,6 @@ static int selinux_socket_connect_helper(struct socket *sock,  		case SECCLASS_TCP_SOCKET:  			perm = TCP_SOCKET__NAME_CONNECT;  			break; -		case SECCLASS_DCCP_SOCKET: -			perm = DCCP_SOCKET__NAME_CONNECT; -			break;  		case SECCLASS_SCTP_SOCKET:  			perm = SCTP_SOCKET__NAME_CONNECT;  			break; @@ -7272,6 +7374,8 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {  	LSM_HOOK_INIT(inode_getxattr, selinux_inode_getxattr),  	LSM_HOOK_INIT(inode_listxattr, selinux_inode_listxattr),  	LSM_HOOK_INIT(inode_removexattr, selinux_inode_removexattr), +	LSM_HOOK_INIT(inode_file_getattr, selinux_inode_file_getattr), +	LSM_HOOK_INIT(inode_file_setattr, selinux_inode_file_setattr),  	LSM_HOOK_INIT(inode_set_acl, selinux_inode_set_acl),  	LSM_HOOK_INIT(inode_get_acl, selinux_inode_get_acl),  	LSM_HOOK_INIT(inode_remove_acl, selinux_inode_remove_acl), diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c index 48f537b41c58..470481cfe0e8 100644 --- a/security/selinux/ibpkey.c +++ b/security/selinux/ibpkey.c @@ -130,7 +130,7 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)  {  	int ret;  	struct sel_ib_pkey *pkey; -	struct sel_ib_pkey *new = NULL; +	struct sel_ib_pkey *new;  	unsigned long flags;  	spin_lock_irqsave(&sel_ib_pkey_lock, flags); @@ -146,12 +146,11 @@ static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid)  	if (ret)  		goto out; -	/* If this memory allocation fails still return 0. The SID -	 * is valid, it just won't be added to the cache. -	 */ -	new = kzalloc(sizeof(*new), GFP_ATOMIC); +	new = kmalloc(sizeof(*new), GFP_ATOMIC);  	if (!new) { -		ret = -ENOMEM; +		/* If this memory allocation fails still return 0. The SID +		 * is valid, it just won't be added to the cache. +		 */  		goto out;  	} @@ -184,7 +183,7 @@ int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid)  	rcu_read_lock();  	pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); -	if (pkey) { +	if (likely(pkey)) {  		*sid = pkey->psec.sid;  		rcu_read_unlock();  		return 0; diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 281f40103663..01b5167fee1a 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -65,6 +65,10 @@ static inline u32 avc_audit_required(u32 requested, struct av_decision *avd,  				     int result, u32 auditdeny, u32 *deniedp)  {  	u32 denied, audited; + +	if (avd->flags & AVD_FLAGS_NEVERAUDIT) +		return 0; +  	denied = requested & ~avd->allowed;  	if (unlikely(denied)) {  		audited = denied & avd->auditdeny; diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 04a9b480885e..5665aa5e7853 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -127,8 +127,6 @@ const struct security_class_mapping secclass_map[] = {  	{ "key",  	  { "view", "read", "write", "search", "link", "setattr", "create",  	    NULL } }, -	{ "dccp_socket", -	  { COMMON_SOCK_PERMS, "node_bind", "name_connect", NULL } },  	{ "memprotect", { "mmap_zero", NULL } },  	{ "peer", { "recv", NULL } },  	{ "capability2", { COMMON_CAP2_PERMS, NULL } }, diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h index 9b8b655a8cd3..e4dc904c3585 100644 --- a/security/selinux/include/netnode.h +++ b/security/selinux/include/netnode.h @@ -21,6 +21,6 @@  void sel_netnode_flush(void); -int sel_netnode_sid(void *addr, u16 family, u32 *sid); +int sel_netnode_sid(const void *addr, u16 family, u32 *sid);  #endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index c88cae81ee4c..1d7ac59015a1 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -29,6 +29,13 @@  #include "flask.h"  #include "avc.h" +struct avdc_entry { +	u32 isid; /* inode SID */ +	u32 allowed; /* allowed permission bitmask */ +	u32 audited; /* audited permission bitmask */ +	bool permissive; /* AVC permissive flag */ +}; +  struct task_security_struct {  	u32 osid; /* SID prior to last execve */  	u32 sid; /* current SID */ @@ -36,8 +43,23 @@ struct task_security_struct {  	u32 create_sid; /* fscreate SID */  	u32 keycreate_sid; /* keycreate SID */  	u32 sockcreate_sid; /* fscreate SID */ +#define TSEC_AVDC_DIR_SIZE (1 << 2) +	struct { +		u32 sid; /* current SID for cached entries */ +		u32 seqno; /* AVC sequence number */ +		unsigned int dir_spot; /* dir cache index to check first */ +		struct avdc_entry dir[TSEC_AVDC_DIR_SIZE]; /* dir entries */ +		bool permissive_neveraudit; /* permissive and neveraudit */ +	} avdcache;  } __randomize_layout; +static inline bool task_avdcache_permnoaudit(struct task_security_struct *tsec) +{ +	return (tsec->avdcache.permissive_neveraudit && +		tsec->sid == tsec->avdcache.sid && +		tsec->avdcache.seqno == avc_policy_seqno()); +} +  enum label_initialized {  	LABEL_INVALID, /* invalid or not initialized */  	LABEL_INITIALIZED, /* initialized */ @@ -82,7 +104,7 @@ struct ipc_security_struct {  };  struct netif_security_struct { -	struct net *ns; /* network namespace */ +	const struct net *ns; /* network namespace */  	int ifindex; /* device index */  	u32 sid; /* SID for this interface */  }; diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h index bd402d3fd3ae..7405154e6c42 100644 --- a/security/selinux/include/policycap.h +++ b/security/selinux/include/policycap.h @@ -16,6 +16,7 @@ enum {  	POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT,  	POLICYDB_CAP_NETLINK_XPERM,  	POLICYDB_CAP_NETIF_WILDCARD, +	POLICYDB_CAP_GENFS_SECLABEL_WILDCARD,  	__POLICYDB_CAP_MAX  };  #define POLICYDB_CAP_MAX (__POLICYDB_CAP_MAX - 1) diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h index ac1342d6d5bb..d8962fcf2ff9 100644 --- a/security/selinux/include/policycap_names.h +++ b/security/selinux/include/policycap_names.h @@ -19,6 +19,7 @@ const char *const selinux_policycap_names[__POLICYDB_CAP_MAX] = {  	"userspace_initial_context",  	"netlink_xperm",  	"netif_wildcard", +	"genfs_seclabel_wildcard",  };  /* clang-format on */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index e7827ed7be5f..8201e6a3ac0f 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -47,10 +47,11 @@  #define POLICYDB_VERSION_GLBLUB		     32  #define POLICYDB_VERSION_COMP_FTRANS	     33 /* compressed filename transitions */  #define POLICYDB_VERSION_COND_XPERMS	     34 /* extended permissions in conditional policies */ +#define POLICYDB_VERSION_NEVERAUDIT	     35 /* neveraudit types */  /* Range of policy versions we understand*/  #define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE -#define POLICYDB_VERSION_MAX POLICYDB_VERSION_COND_XPERMS +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_NEVERAUDIT  /* Mask for just the mount related flags */  #define SE_MNTMASK 0x0f @@ -260,6 +261,7 @@ struct extended_perms {  /* definitions of av_decision.flags */  #define AVD_FLAGS_PERMISSIVE 0x0001 +#define AVD_FLAGS_NEVERAUDIT  0x0002  void security_compute_av(u32 ssid, u32 tsid, u16 tclass,  			 struct av_decision *avd, @@ -309,7 +311,7 @@ int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid);  int security_netif_sid(const char *name, u32 *if_sid); -int security_node_sid(u16 domain, void *addr, u32 addrlen, u32 *out_sid); +int security_node_sid(u16 domain, const void *addr, u32 addrlen, u32 *out_sid);  int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid,  				 u16 tclass); diff --git a/security/selinux/netif.c b/security/selinux/netif.c index 43a0d3594b72..78afbecdbe57 100644 --- a/security/selinux/netif.c +++ b/security/selinux/netif.c @@ -156,7 +156,11 @@ static int sel_netif_sid_slow(struct net *ns, int ifindex, u32 *sid)  	ret = security_netif_sid(dev->name, sid);  	if (ret != 0)  		goto out; -	new = kzalloc(sizeof(*new), GFP_ATOMIC); + +	/* If this memory allocation fails still return 0. The SID +	 * is valid, it just won't be added to the cache. +	 */ +	new = kmalloc(sizeof(*new), GFP_ATOMIC);  	if (new) {  		new->nsec.ns = ns;  		new->nsec.ifindex = ifindex; diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c index 5c8c77e50aad..5d0ed08d46e5 100644 --- a/security/selinux/netnode.c +++ b/security/selinux/netnode.c @@ -187,7 +187,7 @@ static void sel_netnode_insert(struct sel_netnode *node)   * failure.   *   */ -static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) +static int sel_netnode_sid_slow(const void *addr, u16 family, u32 *sid)  {  	int ret;  	struct sel_netnode *node; @@ -201,19 +201,22 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid)  		return 0;  	} -	new = kzalloc(sizeof(*new), GFP_ATOMIC); +	/* If this memory allocation fails still return 0. The SID +	 * is valid, it just won't be added to the cache. +	 */ +	new = kmalloc(sizeof(*new), GFP_ATOMIC);  	switch (family) {  	case PF_INET:  		ret = security_node_sid(PF_INET,  					addr, sizeof(struct in_addr), sid);  		if (new) -			new->nsec.addr.ipv4 = *(__be32 *)addr; +			new->nsec.addr.ipv4 = *(const __be32 *)addr;  		break;  	case PF_INET6:  		ret = security_node_sid(PF_INET6,  					addr, sizeof(struct in6_addr), sid);  		if (new) -			new->nsec.addr.ipv6 = *(struct in6_addr *)addr; +			new->nsec.addr.ipv6 = *(const struct in6_addr *)addr;  		break;  	default:  		BUG(); @@ -247,13 +250,13 @@ static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid)   * on failure.   *   */ -int sel_netnode_sid(void *addr, u16 family, u32 *sid) +int sel_netnode_sid(const void *addr, u16 family, u32 *sid)  {  	struct sel_netnode *node;  	rcu_read_lock();  	node = sel_netnode_find(addr, family); -	if (node != NULL) { +	if (likely(node != NULL)) {  		*sid = node->nsec.sid;  		rcu_read_unlock();  		return 0; diff --git a/security/selinux/netport.c b/security/selinux/netport.c index 2e22ad9c2bd0..6fd7da4b3576 100644 --- a/security/selinux/netport.c +++ b/security/selinux/netport.c @@ -47,12 +47,6 @@ struct sel_netport {  	struct rcu_head rcu;  }; -/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason - * for this is that I suspect most users will not make heavy use of both - * address families at the same time so one table will usually end up wasted, - * if this becomes a problem we can always add a hash table for each address - * family later */ -  static DEFINE_SPINLOCK(sel_netport_lock);  static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; @@ -151,7 +145,11 @@ static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid)  	ret = security_port_sid(protocol, pnum, sid);  	if (ret != 0)  		goto out; -	new = kzalloc(sizeof(*new), GFP_ATOMIC); + +	/* If this memory allocation fails still return 0. The SID +	 * is valid, it just won't be added to the cache. +	 */ +	new = kmalloc(sizeof(*new), GFP_ATOMIC);  	if (new) {  		new->psec.port = pnum;  		new->psec.protocol = protocol; @@ -186,7 +184,7 @@ int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid)  	rcu_read_lock();  	port = sel_netport_find(protocol, pnum); -	if (port != NULL) { +	if (likely(port != NULL)) {  		*sid = port->psec.sid;  		rcu_read_unlock();  		return 0; diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 3a95986b134f..2c0b07f9fbbd 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -98,7 +98,6 @@ static const struct nlmsg_perm nlmsg_route_perms[] = {  static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = {  	{ TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, -	{ DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },  	{ SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ },  	{ SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE },  }; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 47480eb2189b..9aa1d03ab612 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -1072,6 +1072,7 @@ static ssize_t sel_write_user(struct file *file, char *buf, size_t size)  	pr_warn_ratelimited("SELinux: %s (%d) wrote to /sys/fs/selinux/user!"  		" This will not be supported in the future; please update your"  		" userspace.\n", current->comm, current->pid); +	ssleep(5);  	length = avc_has_perm(current_sid(), SECINITSID_SECURITY,  			      SECCLASS_SECURITY, SECURITY__COMPUTE_USER, @@ -2097,8 +2098,6 @@ err:  	pr_err("SELinux: %s:  failed while creating inodes\n",  		__func__); -	selinux_fs_info_free(sb); -  	return ret;  } @@ -2158,8 +2157,8 @@ static int __init init_sel_fs(void)  		return err;  	} -	selinux_null.dentry = d_hash_and_lookup(selinux_null.mnt->mnt_root, -						&null_name); +	selinux_null.dentry = try_lookup_noperm(&null_name, +						  selinux_null.mnt->mnt_root);  	if (IS_ERR(selinux_null.dentry)) {  		pr_err("selinuxfs:  could not lookup null!\n");  		err = PTR_ERR(selinux_null.dentry); diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c index 383fd2d70878..1382eb3bfde1 100644 --- a/security/selinux/ss/hashtab.c +++ b/security/selinux/ss/hashtab.c @@ -40,7 +40,8 @@ int hashtab_init(struct hashtab *h, u32 nel_hint)  	h->htable = NULL;  	if (size) { -		h->htable = kcalloc(size, sizeof(*h->htable), GFP_KERNEL); +		h->htable = kcalloc(size, sizeof(*h->htable), +				    GFP_KERNEL | __GFP_NOWARN);  		if (!h->htable)  			return -ENOMEM;  		h->size = size; diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 9ea971943713..91df3db6a88c 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -160,6 +160,11 @@ static const struct policydb_compat_info policydb_compat[] = {  		.sym_num = SYM_NUM,  		.ocon_num = OCON_NUM,  	}, +	{ +		.version = POLICYDB_VERSION_NEVERAUDIT, +		.sym_num = SYM_NUM, +		.ocon_num = OCON_NUM, +	},  };  static const struct policydb_compat_info * @@ -531,6 +536,7 @@ static void policydb_init(struct policydb *p)  	ebitmap_init(&p->filename_trans_ttypes);  	ebitmap_init(&p->policycaps);  	ebitmap_init(&p->permissive_map); +	ebitmap_init(&p->neveraudit_map);  }  /* @@ -852,6 +858,7 @@ void policydb_destroy(struct policydb *p)  	ebitmap_destroy(&p->filename_trans_ttypes);  	ebitmap_destroy(&p->policycaps);  	ebitmap_destroy(&p->permissive_map); +	ebitmap_destroy(&p->neveraudit_map);  }  /* @@ -2538,6 +2545,12 @@ int policydb_read(struct policydb *p, struct policy_file *fp)  			goto bad;  	} +	if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) { +		rc = ebitmap_read(&p->neveraudit_map, fp); +		if (rc) +			goto bad; +	} +  	rc = -EINVAL;  	info = policydb_lookup_compat(p->policyvers);  	if (!info) { @@ -3723,6 +3736,12 @@ int policydb_write(struct policydb *p, struct policy_file *fp)  			return rc;  	} +	if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) { +		rc = ebitmap_write(&p->neveraudit_map, fp); +		if (rc) +			return rc; +	} +  	num_syms = info->sym_num;  	for (i = 0; i < num_syms; i++) {  		struct policy_data pd; diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h index 25650224b6e7..89a180b1742f 100644 --- a/security/selinux/ss/policydb.h +++ b/security/selinux/ss/policydb.h @@ -300,6 +300,8 @@ struct policydb {  	struct ebitmap permissive_map; +	struct ebitmap neveraudit_map; +  	/* length of this policy when it was loaded */  	size_t len; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index e431772c6168..713130bd43c4 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -1153,6 +1153,14 @@ void security_compute_av(u32 ssid,  	if (ebitmap_get_bit(&policydb->permissive_map, scontext->type))  		avd->flags |= AVD_FLAGS_PERMISSIVE; +	/* neveraudit domain? */ +	if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type)) +		avd->flags |= AVD_FLAGS_NEVERAUDIT; + +	/* both permissive and neveraudit => allow */ +	if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)) +		goto allow; +  	tcontext = sidtab_search(sidtab, tsid);  	if (!tcontext) {  		pr_err("SELinux: %s:  unrecognized SID %d\n", @@ -1172,6 +1180,8 @@ void security_compute_av(u32 ssid,  		     policydb->allow_unknown);  out:  	rcu_read_unlock(); +	if (avd->flags & AVD_FLAGS_NEVERAUDIT) +		avd->auditallow = avd->auditdeny = 0;  	return;  allow:  	avd->allowed = 0xffffffff; @@ -1208,6 +1218,14 @@ void security_compute_av_user(u32 ssid,  	if (ebitmap_get_bit(&policydb->permissive_map, scontext->type))  		avd->flags |= AVD_FLAGS_PERMISSIVE; +	/* neveraudit domain? */ +	if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type)) +		avd->flags |= AVD_FLAGS_NEVERAUDIT; + +	/* both permissive and neveraudit => allow */ +	if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT)) +		goto allow; +  	tcontext = sidtab_search(sidtab, tsid);  	if (!tcontext) {  		pr_err("SELinux: %s:  unrecognized SID %d\n", @@ -1225,6 +1243,8 @@ void security_compute_av_user(u32 ssid,  				  NULL);   out:  	rcu_read_unlock(); +	if (avd->flags & AVD_FLAGS_NEVERAUDIT) +		avd->auditallow = avd->auditdeny = 0;  	return;  allow:  	avd->allowed = 0xffffffff; @@ -1909,11 +1929,17 @@ retry:  			goto out_unlock;  	}  	/* Obtain the sid for the context. */ -	rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); -	if (rc == -ESTALE) { -		rcu_read_unlock(); -		context_destroy(&newcontext); -		goto retry; +	if (context_equal(scontext, &newcontext)) +		*out_sid = ssid; +	else if (context_equal(tcontext, &newcontext)) +		*out_sid = tsid; +	else { +		rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid); +		if (rc == -ESTALE) { +			rcu_read_unlock(); +			context_destroy(&newcontext); +			goto retry; +		}  	}  out_unlock:  	rcu_read_unlock(); @@ -2643,7 +2669,7 @@ static bool match_ipv6_addrmask(const u32 input[4], const u32 addr[4], const u32   * @out_sid: security identifier   */  int security_node_sid(u16 domain, -		      void *addrp, +		      const void *addrp,  		      u32 addrlen,  		      u32 *out_sid)  { @@ -2672,7 +2698,7 @@ retry:  		if (addrlen != sizeof(u32))  			goto out; -		addr = *((u32 *)addrp); +		addr = *((const u32 *)addrp);  		c = policydb->ocontexts[OCON_NODE];  		while (c) { @@ -2872,6 +2898,7 @@ static inline int __security_genfs_sid(struct selinux_policy *policy,  	struct genfs *genfs;  	struct ocontext *c;  	int cmp = 0; +	bool wildcard;  	while (path[0] == '/' && path[1] == '/')  		path++; @@ -2888,11 +2915,20 @@ static inline int __security_genfs_sid(struct selinux_policy *policy,  	if (!genfs || cmp)  		return -ENOENT; +	wildcard = ebitmap_get_bit(&policy->policydb.policycaps, +				   POLICYDB_CAP_GENFS_SECLABEL_WILDCARD);  	for (c = genfs->head; c; c = c->next) { -		size_t len = strlen(c->u.name); -		if ((!c->v.sclass || sclass == c->v.sclass) && -		    (strncmp(c->u.name, path, len) == 0)) -			break; +		if (!c->v.sclass || sclass == c->v.sclass) { +			if (wildcard) { +				if (match_wildcard(c->u.name, path)) +					break; +			} else { +				size_t len = strlen(c->u.name); + +				if ((strncmp(c->u.name, path, len)) == 0) +					break; +			} +		}  	}  	if (!c) diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index 90ec4ef1b082..61d56b0c2be1 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -94,7 +94,7 @@ static int selinux_xfrm_alloc_user(struct xfrm_sec_ctx **ctxp,  	ctx->ctx_doi = XFRM_SC_DOI_LSM;  	ctx->ctx_alg = XFRM_SC_ALG_SELINUX; -	ctx->ctx_len = str_len; +	ctx->ctx_len = str_len + 1;  	memcpy(ctx->ctx_str, &uctx[1], str_len);  	ctx->ctx_str[str_len] = '\0';  	rc = security_context_to_sid(ctx->ctx_str, str_len, diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 99833168604e..fc340a6f0dde 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -24,7 +24,6 @@  #include <linux/ip.h>  #include <linux/tcp.h>  #include <linux/udp.h> -#include <linux/dccp.h>  #include <linux/icmpv6.h>  #include <linux/slab.h>  #include <linux/mutex.h> @@ -4061,7 +4060,6 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip)  	__be16 frag_off;  	struct tcphdr _tcph, *th;  	struct udphdr _udph, *uh; -	struct dccp_hdr _dccph, *dh;  	sip->sin6_port = 0; @@ -4090,11 +4088,6 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip)  		if (uh != NULL)  			sip->sin6_port = uh->source;  		break; -	case IPPROTO_DCCP: -		dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); -		if (dh != NULL) -			sip->sin6_port = dh->dccph_sport; -		break;  	}  	return proto;  } @@ -4216,7 +4209,7 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)  	case PF_INET6:  		proto = smk_skb_to_addr_ipv6(skb, &sadd);  		if (proto != IPPROTO_UDP && proto != IPPROTO_UDPLITE && -		    proto != IPPROTO_TCP && proto != IPPROTO_DCCP) +		    proto != IPPROTO_TCP)  			break;  #ifdef SMACK_IPV6_SECMARK_LABELING  		skp = smack_from_skb(skb); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 90a67e410808..b1e5e62f5cbd 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -1077,13 +1077,12 @@ static int smk_open_net4addr(struct inode *inode, struct file *file)  }  /** - * smk_net4addr_insert + * smk_net4addr_insert - insert a new entry into the net4addrs list   * @new : netlabel to insert   * - * This helper insert netlabel in the smack_net4addrs list + * This helper inserts netlabel in the smack_net4addrs list   * sorted by netmask length (longest to smallest) - * locked by &smk_net4addr_lock in smk_write_net4addr - * + * locked by &smk_net4addr_lock in smk_write_net4addr.   */  static void smk_net4addr_insert(struct smk_net4addr *new)  { @@ -1340,13 +1339,12 @@ static int smk_open_net6addr(struct inode *inode, struct file *file)  }  /** - * smk_net6addr_insert + * smk_net6addr_insert - insert a new entry into the net6addrs list   * @new : entry to insert   *   * This inserts an entry in the smack_net6addrs list   * sorted by netmask length (longest to smallest) - * locked by &smk_net6addr_lock in smk_write_net6addr - * + * locked by &smk_net6addr_lock in smk_write_net6addr.   */  static void smk_net6addr_insert(struct smk_net6addr *new)  {  | 
