diff mbox series

[v5,5/5] IMA: introduce a new policy option func=SETXATTR_CHECK

Message ID 20210728132112.258606-6-simon.thoby@viveris.fr (mailing list archive)
State New, archived
Headers show
Series IMA: restrict the accepted digest algorithms for the security.ima xattr | expand

Commit Message

THOBY Simon July 28, 2021, 1:21 p.m. UTC
While users can restrict the accepted hash algorithms for the
security.ima xattr file signature when appraising said file, users
cannot restrict the algorithms that can be set on that attribute:
any algorithm built in the kernel is accepted on a write.

Define a new value for the ima policy option 'func' that restricts
globally the hash algorithms accepted when writing the security.ima
xattr.

When a policy contains a rule of the form
	appraise func=SETXATTR_CHECK appraise_hash=sha256,sha384,sha512
only values corresponding to one of these three digest algorithms
will be accepted for writing the security.ima xattr.
Attempting to write the attribute using another algorithm (or "free-form"
data) will be denied with an audit log message.
In the absence of such a policy rule, the default is still to only
accept hash algorithms built in the kernel (with all the limitations
that entails).

Signed-off-by: Simon Thoby <simon.thoby@viveris.fr>
---
 Documentation/ABI/testing/ima_policy  |  9 ++++-
 security/integrity/ima/ima.h          |  4 ++
 security/integrity/ima/ima_appraise.c | 31 ++++++++++++---
 security/integrity/ima/ima_policy.c   | 57 +++++++++++++++++++++++++++
 4 files changed, 94 insertions(+), 7 deletions(-)

Comments

Mimi Zohar July 28, 2021, 10:57 p.m. UTC | #1
Hi Simon,

On Wed, 2021-07-28 at 13:21 +0000, THOBY Simon wrote:
 
> @@ -914,6 +918,42 @@ int ima_check_policy(void)
>  	return 0;
>  }
>  
> +/** update_allowed_hash_algorithms - update the hash algorithms allowed

The first line of kernel-doc is just "/**" by itself, followed by the
function name and a brief description.  The brief description should
not wrap to the next line.  Refer to Documentation/doc-guide/kernel-
doc.rst.

> + * for setxattr writes
> + *
> + * Update the atomic variable holding the set of allowed hash algorithms
> + * that can be used to update the security.ima xattr of a file.
> + *
> + * Context: called when updating the IMA policy.
> + *
> + * SETXATTR_CHECK rules do not implement a full policy check because of
> + * the performance impact performing rules checking on setxattr() would
> + * have. The consequence is that only one SETXATTR_CHECK can be active at
> + * a time.
> + */
> +static void update_allowed_hash_algorithms(void)
> +{
> +	struct ima_rule_entry *entry;
> +
> +	/*
> +	 * We scan in reverse order because only the last entry with the
> +	 * 'func=SETXATTR_CHECK' apply: this allows runtime upgrades of the
> +	 * digest algorithm policy, unlike the other IMA rules that are
> +	 * usually append-only. Old rules will still be present in the
> +	 * ruleset, but inactive.
> +	 */

Oh, my!  I really hope this won't be used as precedent.  Before
agreeing to this, the existing policy rules must require loading of
only signed IMA policies.

thanks,

Mimi
  
> +	rcu_read_lock();
> +	list_for_each_entry_reverse(entry, ima_rules, list) {
> +		if (entry->func != SETXATTR_CHECK)
> +			continue;
> +
> +		atomic_xchg(&ima_setxattr_allowed_hash_algorithms,
> +			    entry->allowed_hashes);
> +		break;
> +	}
> +	rcu_read_unlock();
> +}
> +
THOBY Simon July 29, 2021, 7:47 a.m. UTC | #2
Hi Mimi,

On 7/29/21 12:57 AM, Mimi Zohar wrote:
> Hi Simon,
> 
> On Wed, 2021-07-28 at 13:21 +0000, THOBY Simon wrote:
>  
>> @@ -914,6 +918,42 @@ int ima_check_policy(void)
>>  	return 0;
>>  }
>>  
>> +/** update_allowed_hash_algorithms - update the hash algorithms allowed
> 
> The first line of kernel-doc is just "/**" by itself, followed by the
> function name and a brief description.  The brief description should
> not wrap to the next line.  Refer to Documentation/doc-guide/kernel-
> doc.rst.
> 

Thanks, I will fix that in the next revision.

>> + * for setxattr writes
>> + *
>> + * Update the atomic variable holding the set of allowed hash algorithms
>> + * that can be used to update the security.ima xattr of a file.
>> + *
>> + * Context: called when updating the IMA policy.
>> + *
>> + * SETXATTR_CHECK rules do not implement a full policy check because of
>> + * the performance impact performing rules checking on setxattr() would
>> + * have. The consequence is that only one SETXATTR_CHECK can be active at
>> + * a time.
>> + */
>> +static void update_allowed_hash_algorithms(void)
>> +{
>> +	struct ima_rule_entry *entry;
>> +
>> +	/*
>> +	 * We scan in reverse order because only the last entry with the
>> +	 * 'func=SETXATTR_CHECK' apply: this allows runtime upgrades of the
>> +	 * digest algorithm policy, unlike the other IMA rules that are
>> +	 * usually append-only. Old rules will still be present in the
>> +	 * ruleset, but inactive.
>> +	 */
> 
> Oh, my!  I really hope this won't be used as precedent.  Before
> agreeing to this, the existing policy rules must require loading of
> only signed IMA policies.
> 


After some though, I think you're right, there is not much point to do anything
different from the other IMA rules, 

Below is the modified version that I will submit in the next patch.

However given the similarities between this function and ima_update_policy_flag,
I think maybe I should merge them together: they are mostly called from the
same places and they both serve the same role: updating some of the global ima
variables after a policy update or at system initialization.

Do you think it would be ok to add that functionality to ima_update_policy_flag?
Maybe updating the name to reflect that the function updates multiples flags?

 
+/**
+ * update_allowed_hash_algorithms() - update the hash allowlist for setxattr
+ *
+ * Update the atomic variable holding the set of allowed hash algorithms
+ * that can be used to update the security.ima xattr of a file.
+ *
+ * SETXATTR_CHECK rules do not implement a full policy check because
+ * of the performance impact performing rules checking on setxattr()
+ * would have. The consequence is that only one SETXATTR_CHECK can be
+ * active at a given time.
+ *
+ * Context: Called when updating the IMA policy.
+ */
+static void update_allowed_hash_algorithms(void)
+{
+	struct ima_rule_entry *entry;
+
+	rcu_read_lock();
+	list_for_each_entry(entry, ima_rules, list) {
+		if (entry->func != SETXATTR_CHECK)
+			continue;
+
+		/*
+		 * Two possibilities:
+		 * - the atomic was non-zero: a setxattr hash policy is
+		 *   already enforced -> do nothing
+		 * - the atomic was zero -> enable the setxattr hash policy
+		 *
+		 * While we could check if the atomic is non-zero at the
+		 * beginning of the function, doing it here prevent TOCTOU
+		 * races (not that I think there would be much interest for
+		 * an attacker to perform a TOCTOU race here)
+		 */
+		atomic_cmpxchg(&ima_setxattr_allowed_hash_algorithms, 0,
+			       entry->allowed_hashes);
+		break;
+	}
+	rcu_read_unlock();
+}
+

As a side note on the patchset, maybe I should refrain from posting for a few days to give people time
to comment on it, instead of sending new versions in such a quick succession?

Thanks,
Simon
Mimi Zohar July 29, 2021, 4:15 p.m. UTC | #3
Hi Simon,

On Thu, 2021-07-29 at 07:47 +0000, THOBY Simon wrote:
> 
> >> + * for setxattr writes
> >> + *
> >> + * Update the atomic variable holding the set of allowed hash algorithms
> >> + * that can be used to update the security.ima xattr of a file.
> >> + *
> >> + * Context: called when updating the IMA policy.
> >> + *
> >> + * SETXATTR_CHECK rules do not implement a full policy check because of
> >> + * the performance impact performing rules checking on setxattr() would
> >> + * have. The consequence is that only one SETXATTR_CHECK can be active at
> >> + * a time.
> >> + */
> >> +static void update_allowed_hash_algorithms(void)
> >> +{
> >> +	struct ima_rule_entry *entry;
> >> +
> >> +	/*
> >> +	 * We scan in reverse order because only the last entry with the
> >> +	 * 'func=SETXATTR_CHECK' apply: this allows runtime upgrades of the
> >> +	 * digest algorithm policy, unlike the other IMA rules that are
> >> +	 * usually append-only. Old rules will still be present in the
> >> +	 * ruleset, but inactive.
> >> +	 */
> > 
> > Oh, my!  I really hope this won't be used as precedent.  Before
> > agreeing to this, the existing policy rules must require loading of
> > only signed IMA policies.
> > 
> 
> 
> After some though, I think you're right, there is not much point to do anything
> different from the other IMA rules, 
> 
> Below is the modified version that I will submit in the next patch.
> 
> However given the similarities between this function and ima_update_policy_flag,
> I think maybe I should merge them together: they are mostly called from the
> same places and they both serve the same role: updating some of the global ima
> variables after a policy update or at system initialization.
> 
> Do you think it would be ok to add that functionality to ima_update_policy_flag?
> Maybe updating the name to reflect that the function updates multiples flags?

We've gone through a couple of iterations of this patch.  At this
point, it's defining a single set of setxattr permitted hash
algorithms.  Renaming and adding the change to
ima_update_policy_flags() definitely makes sense.

At the same time, I'd appreciate your fixing the RCU locking there.

> As a side note on the patchset, maybe I should refrain from posting for a few days to give people time
> to comment on it, instead of sending new versions in such a quick succession?

Definitely.

thanks,

Mimi
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy
index aeb622698047..537be0e1720e 100644
--- a/Documentation/ABI/testing/ima_policy
+++ b/Documentation/ABI/testing/ima_policy
@@ -30,9 +30,10 @@  Description:
 				[appraise_flag=] [appraise_hash=] [keyrings=]
 		  base:
 			func:= [BPRM_CHECK][MMAP_CHECK][CREDS_CHECK][FILE_CHECK][MODULE_CHECK]
-			        [FIRMWARE_CHECK]
+				[FIRMWARE_CHECK]
 				[KEXEC_KERNEL_CHECK] [KEXEC_INITRAMFS_CHECK]
 				[KEXEC_CMDLINE] [KEY_CHECK] [CRITICAL_DATA]
+				[SETXATTR_CHECK]
 			mask:= [[^]MAY_READ] [[^]MAY_WRITE] [[^]MAY_APPEND]
 			       [[^]MAY_EXEC]
 			fsmagic:= hex value
@@ -138,3 +139,9 @@  Description:
 		keys added to .builtin_trusted_keys or .ima keyring:
 
 			measure func=KEY_CHECK keyrings=.builtin_trusted_keys|.ima
+
+		Example of the special SETXATTR_CHECK appraise rule, that
+		restricts the hash algorithms allowed when writing to the
+		security.ima xattr of a file:
+
+			appraise func=SETXATTR_CHECK appraise_hash=sha256,sha384,sha512
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 7ef1b214d358..aeb3bf30c0f9 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -46,6 +46,9 @@  enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8, TPM_PCR10 = 10 };
 /* current content of the policy */
 extern int ima_policy_flag;
 
+/* bitset of digests algorithms allowed in the setxattr hook */
+extern atomic_t ima_setxattr_allowed_hash_algorithms;
+
 /* set during initialization */
 extern int ima_hash_algo __ro_after_init;
 extern int ima_sha1_idx __ro_after_init;
@@ -198,6 +201,7 @@  static inline unsigned int ima_hash_key(u8 *digest)
 	hook(KEXEC_CMDLINE, kexec_cmdline)		\
 	hook(KEY_CHECK, key)				\
 	hook(CRITICAL_DATA, critical_data)		\
+	hook(SETXATTR_CHECK, setxattr_check)		\
 	hook(MAX_CHECK, none)
 
 #define __ima_hook_enumify(ENUM, str)	ENUM,
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 12d406b5ab35..c4a43c84da5f 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -596,13 +596,33 @@  static int validate_hash_algo(struct dentry *dentry,
 {
 	char *path = NULL, *pathbuf = NULL;
 	enum hash_algo xattr_hash_algo;
+	const char *errmsg = "unavailable-hash-algorithm";
+	unsigned int allowed_hashes;
 
 	xattr_hash_algo = ima_get_hash_algo((struct evm_ima_xattr_data *)xattr_value,
 					    xattr_value_len);
 
-	if (likely(xattr_hash_algo == ima_hash_algo ||
-		   crypto_has_alg(hash_algo_name[xattr_hash_algo], 0, 0)))
-		return 0;
+	allowed_hashes = atomic_read(&ima_setxattr_allowed_hash_algorithms);
+
+	if (allowed_hashes) {
+		/* success if the algorithm is whitelisted in the ima policy */
+		if (allowed_hashes & (1U << xattr_hash_algo))
+			return 0;
+
+		/*
+		 * We use a different audit message when the hash algorithm
+		 * is denied by a policy rule, instead of not being built
+		 * in the kernel image
+		 */
+		errmsg = "denied-hash-algorithm";
+	} else {
+		if (likely(xattr_hash_algo == ima_hash_algo))
+			return 0;
+
+		/* allow any xattr using an algorithm built in the kernel */
+		if (crypto_has_alg(hash_algo_name[xattr_hash_algo], 0, 0))
+			return 0;
+	}
 
 	pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
 	if (pathbuf) {
@@ -610,11 +630,10 @@  static int validate_hash_algo(struct dentry *dentry,
 
 		/*
 		 * disallow xattr writes with algorithms disabled in the
-		 * kernel configuration
+		 * kernel configuration or denied by policy
 		 */
 		integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry),
-				    path, "collect_data",
-				    "unavailable-hash-algorithm",
+				    path, "collect_data", errmsg,
 				    -EACCES, 0);
 
 		kfree(pathbuf);
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index f944c3e7f340..4e2410b826e2 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -53,6 +53,8 @@  int ima_policy_flag;
 static int temp_ima_appraise;
 static int build_ima_appraise __ro_after_init;
 
+atomic_t ima_setxattr_allowed_hash_algorithms;
+
 #define MAX_LSM_RULES 6
 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE,
 	LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE
@@ -903,6 +905,8 @@  void __init ima_init_policy(void)
 			  ARRAY_SIZE(critical_data_rules),
 			  IMA_DEFAULT_POLICY);
 
+	atomic_xchg(&ima_setxattr_allowed_hash_algorithms, 0);
+
 	ima_update_policy_flag();
 }
 
@@ -914,6 +918,42 @@  int ima_check_policy(void)
 	return 0;
 }
 
+/** update_allowed_hash_algorithms - update the hash algorithms allowed
+ * for setxattr writes
+ *
+ * Update the atomic variable holding the set of allowed hash algorithms
+ * that can be used to update the security.ima xattr of a file.
+ *
+ * Context: called when updating the IMA policy.
+ *
+ * SETXATTR_CHECK rules do not implement a full policy check because of
+ * the performance impact performing rules checking on setxattr() would
+ * have. The consequence is that only one SETXATTR_CHECK can be active at
+ * a time.
+ */
+static void update_allowed_hash_algorithms(void)
+{
+	struct ima_rule_entry *entry;
+
+	/*
+	 * We scan in reverse order because only the last entry with the
+	 * 'func=SETXATTR_CHECK' apply: this allows runtime upgrades of the
+	 * digest algorithm policy, unlike the other IMA rules that are
+	 * usually append-only. Old rules will still be present in the
+	 * ruleset, but inactive.
+	 */
+	rcu_read_lock();
+	list_for_each_entry_reverse(entry, ima_rules, list) {
+		if (entry->func != SETXATTR_CHECK)
+			continue;
+
+		atomic_xchg(&ima_setxattr_allowed_hash_algorithms,
+			    entry->allowed_hashes);
+		break;
+	}
+	rcu_read_unlock();
+}
+
 /**
  * ima_update_policy - update default_rules with new measure rules
  *
@@ -931,6 +971,7 @@  void ima_update_policy(void)
 
 	list_splice_tail_init_rcu(&ima_temp_rules, policy, synchronize_rcu);
 
+
 	if (ima_rules != policy) {
 		ima_policy_flag = 0;
 		ima_rules = policy;
@@ -944,6 +985,7 @@  void ima_update_policy(void)
 		kfree(arch_policy_entry);
 	}
 	ima_update_policy_flag();
+	update_allowed_hash_algorithms();
 
 	/* Custom IMA policy has been loaded */
 	ima_process_queued_keys();
@@ -1176,6 +1218,19 @@  static bool ima_validate_rule(struct ima_rule_entry *entry)
 		if (ima_rule_contains_lsm_cond(entry))
 			return false;
 
+		break;
+	case SETXATTR_CHECK:
+		/* any action other than APPRAISE is unsupported */
+		if (entry->action != APPRAISE)
+			return false;
+
+		/*
+		 * full policies are not supported, they would have too
+		 * much of a performance impact
+		 */
+		if (entry->flags & ~(IMA_FUNC | IMA_VALIDATE_HASH))
+			return false;
+
 		break;
 	default:
 		return false;
@@ -1332,6 +1387,8 @@  static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 				entry->func = KEY_CHECK;
 			else if (strcmp(args[0].from, "CRITICAL_DATA") == 0)
 				entry->func = CRITICAL_DATA;
+			else if (strcmp(args[0].from, "SETXATTR_CHECK") == 0)
+				entry->func = SETXATTR_CHECK;
 			else
 				result = -EINVAL;
 			if (!result)