diff mbox series

ima: instantiate the bprm_creds_for_exec() hook

Message ID 20241127210234.121546-1-zohar@linux.ibm.com (mailing list archive)
State New
Headers show
Series ima: instantiate the bprm_creds_for_exec() hook | expand

Commit Message

Mimi Zohar Nov. 27, 2024, 9:02 p.m. UTC
Like direct file execution (e.g. ./script.sh), indirect file execution
(e.g. sh script.sh) needs to be measured and appraised.  Instantiate
the new security_bprm_creds_for_exec() hook to measure and verify the
indirect file's integrity.  Unlike direct file execution, indirect file
execution integrity is optionally enforced by the interpreter.

Update the audit messages to differentiate between kernel and userspace
enforced integrity.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
Signed-off-by: Mimi Zohar <zohar@linux.ibm.com>
---
 security/integrity/ima/ima_appraise.c | 84 ++++++++++++++++++++-------
 security/integrity/ima/ima_main.c     | 22 +++++++
 2 files changed, 86 insertions(+), 20 deletions(-)
diff mbox series

Patch

diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index 656c709b974f..b5f8e49cde9d 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -8,6 +8,7 @@ 
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/file.h>
+#include <linux/binfmts.h>
 #include <linux/fs.h>
 #include <linux/xattr.h>
 #include <linux/magic.h>
@@ -16,6 +17,7 @@ 
 #include <linux/fsverity.h>
 #include <keys/system_keyring.h>
 #include <uapi/linux/fsverity.h>
+#include <linux/securebits.h>
 
 #include "ima.h"
 
@@ -276,7 +278,8 @@  static int calc_file_id_hash(enum evm_ima_xattr_type type,
  */
 static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 			struct evm_ima_xattr_data *xattr_value, int xattr_len,
-			enum integrity_status *status, const char **cause)
+			enum integrity_status *status, const char **cause,
+			bool is_check)
 {
 	struct ima_max_digest_data hash;
 	struct signature_v2_hdr *sig;
@@ -292,9 +295,11 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 		if (*status != INTEGRITY_PASS_IMMUTABLE) {
 			if (iint->flags & IMA_DIGSIG_REQUIRED) {
 				if (iint->flags & IMA_VERITY_REQUIRED)
-					*cause = "verity-signature-required";
+					*cause = !is_check ? "verity-signature-required" :
+						"verity-signature-required(userspace)";
 				else
-					*cause = "IMA-signature-required";
+					*cause = !is_check ? "IMA-signature-required" :
+						"IMA-signature-required(userspace)";
 				*status = INTEGRITY_FAIL;
 				break;
 			}
@@ -314,7 +319,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 		else
 			rc = -EINVAL;
 		if (rc) {
-			*cause = "invalid-hash";
+			*cause = !is_check ? "invalid-hash" :
+				"invalid-hash(userspace)";
 			*status = INTEGRITY_FAIL;
 			break;
 		}
@@ -325,14 +331,16 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 
 		mask = IMA_DIGSIG_REQUIRED | IMA_VERITY_REQUIRED;
 		if ((iint->flags & mask) == mask) {
-			*cause = "verity-signature-required";
+			*cause = !is_check ? "verity-signature-required" :
+				"verity-signature-required(userspace)";
 			*status = INTEGRITY_FAIL;
 			break;
 		}
 
 		sig = (typeof(sig))xattr_value;
 		if (sig->version >= 3) {
-			*cause = "invalid-signature-version";
+			*cause = !is_check ? "invalid-signature-version" :
+				"invalid-signature-version(userspace)";
 			*status = INTEGRITY_FAIL;
 			break;
 		}
@@ -353,7 +361,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 						     iint->ima_hash->digest,
 						     iint->ima_hash->length);
 		if (rc) {
-			*cause = "invalid-signature";
+			*cause = !is_check ? "invalid-signature" :
+				"invalid-signature(userspace)";
 			*status = INTEGRITY_FAIL;
 		} else {
 			*status = INTEGRITY_PASS;
@@ -364,7 +373,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 
 		if (iint->flags & IMA_DIGSIG_REQUIRED) {
 			if (!(iint->flags & IMA_VERITY_REQUIRED)) {
-				*cause = "IMA-signature-required";
+				*cause = !is_check ? "IMA-signature-required" :
+					"IMA-signature-required(userspace)";
 				*status = INTEGRITY_FAIL;
 				break;
 			}
@@ -372,7 +382,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 
 		sig = (typeof(sig))xattr_value;
 		if (sig->version != 3) {
-			*cause = "invalid-signature-version";
+			*cause = !is_check ? "invalid-signature-version" :
+				"invalid-signature-version(userspace)";
 			*status = INTEGRITY_FAIL;
 			break;
 		}
@@ -382,7 +393,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 				       container_of(&hash.hdr,
 					       struct ima_digest_data, hdr));
 		if (rc) {
-			*cause = "sigv3-hashing-error";
+			*cause = !is_check ? "sigv3-hashing-error" :
+				"sigv3-hashing-error(userspace)";
 			*status = INTEGRITY_FAIL;
 			break;
 		}
@@ -392,7 +404,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 					     xattr_len, hash.digest,
 					     hash.hdr.length);
 		if (rc) {
-			*cause = "invalid-verity-signature";
+			*cause = !is_check ? "invalid-verity-signature" :
+				"invalid-verify-signature(userspace)";
 			*status = INTEGRITY_FAIL;
 		} else {
 			*status = INTEGRITY_PASS;
@@ -401,7 +414,8 @@  static int xattr_verify(enum ima_hooks func, struct ima_iint_cache *iint,
 		break;
 	default:
 		*status = INTEGRITY_UNKNOWN;
-		*cause = "unknown-ima-data";
+		*cause = !is_check ? "unknown-ima-data" :
+			"unknown-ima-data(userspace)";
 		break;
 	}
 
@@ -469,6 +483,18 @@  int ima_check_blacklist(struct ima_iint_cache *iint,
 	return rc;
 }
 
+static int is_bprm_creds_for_exec(enum ima_hooks func, struct file *file)
+{
+	struct linux_binprm *bprm = NULL;
+
+	if (func == BPRM_CHECK) {
+		bprm = container_of(&file, struct linux_binprm, file);
+		if (bprm->is_check)
+			return 1;
+	}
+	return 0;
+}
+
 /*
  * ima_appraise_measurement - appraise file measurement
  *
@@ -489,11 +515,24 @@  int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
 	enum integrity_status status = INTEGRITY_UNKNOWN;
 	int rc = xattr_len;
 	bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig;
+	bool is_check = false;
 
 	/* If not appraising a modsig, we need an xattr. */
 	if (!(inode->i_opflags & IOP_XATTR) && !try_modsig)
 		return INTEGRITY_UNKNOWN;
 
+	/*
+	 * Unlike any of the other LSM hooks where the kernel enforces file
+	 * integrity, enforcing file integrity for the bprm_creds_for_exec()
+	 * LSM hook is left up to the discretion of the script interpreter
+	 * (userspace).
+	 *
+	 * Since the SECBIT_EXEC_RESTRICT_FILE flag is just a hint as to
+	 * userspace intentions, simply annotate the audit messages indicating
+	 * a userspace based query.
+	 */
+	is_check = is_bprm_creds_for_exec(func, file);
+
 	/* If reading the xattr failed and there's no modsig, error out. */
 	if (rc <= 0 && !try_modsig) {
 		if (rc && rc != -ENODATA)
@@ -501,11 +540,14 @@  int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
 
 		if (iint->flags & IMA_DIGSIG_REQUIRED) {
 			if (iint->flags & IMA_VERITY_REQUIRED)
-				cause = "verity-signature-required";
+				cause = !is_check ? "verity-signature-required" :
+					"verity-signature-required(userspace)";
 			else
-				cause = "IMA-signature-required";
+				cause = !is_check ? "IMA-signature-required" :
+					"IMA-signature-required(userspace)";
 		} else {
-			cause = "missing-hash";
+			cause = !is_check ? "missing-hash" :
+				"missing-hash(userspace)";
 		}
 
 		status = INTEGRITY_NOLABEL;
@@ -531,14 +573,15 @@  int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
 			break;
 		fallthrough;
 	case INTEGRITY_NOLABEL:		/* No security.evm xattr. */
-		cause = "missing-HMAC";
+		cause = !is_check ? "missing-HMAC" : "missing-HMAC(userspace)";
 		goto out;
 	case INTEGRITY_FAIL_IMMUTABLE:
 		set_bit(IMA_DIGSIG, &iint->atomic_flags);
-		cause = "invalid-fail-immutable";
+		cause = !is_check ? "invalid-fail-immutable" :
+		       "invalid-fail-immutable(userspace)";
 		goto out;
 	case INTEGRITY_FAIL:		/* Invalid HMAC/signature. */
-		cause = "invalid-HMAC";
+		cause = !is_check ? "invalid-HMAC" : "invalid-HMAC(userspace)";
 		goto out;
 	default:
 		WARN_ONCE(true, "Unexpected integrity status %d\n", status);
@@ -546,7 +589,7 @@  int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
 
 	if (xattr_value)
 		rc = xattr_verify(func, iint, xattr_value, xattr_len, &status,
-				  &cause);
+				  &cause, is_check);
 
 	/*
 	 * If we have a modsig and either no imasig or the imasig's key isn't
@@ -568,7 +611,8 @@  int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint,
 	    ((inode->i_sb->s_iflags & SB_I_UNTRUSTED_MOUNTER) ||
 	     (iint->flags & IMA_FAIL_UNVERIFIABLE_SIGS))) {
 		status = INTEGRITY_FAIL;
-		cause = "unverifiable-signature";
+		cause = !is_check ? "unverifiable-signature" :
+			"unverifiable-signature(userspace)";
 		integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename,
 				    op, cause, rc, 0);
 	} else if (status != INTEGRITY_PASS) {
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 06132cf47016..2b5d6bae77a4 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -554,6 +554,27 @@  static int ima_bprm_check(struct linux_binprm *bprm)
 				   MAY_EXEC, CREDS_CHECK);
 }
 
+/**
+ * ima_bprm_creds_for_exec - based on policy, collect/store/appraise measurement.
+ * @bprm: contains the linux_binprm structure
+ *
+ * Based on the IMA policy and the execvat(2) AT_CHECK flag, measure and
+ * appraise the integrity of a file to be executed by script interpreters.
+ * Unlike any of the other LSM hooks where the kernel enforces file integrity,
+ * enforcing file integrity is left up to the discretion of the script
+ * interpreter (userspace).
+ *
+ * On success return 0.  On integrity appraisal error, assuming the file
+ * is in policy and IMA-appraisal is in enforcing mode, return -EACCES.
+ */
+static int ima_bprm_creds_for_exec(struct linux_binprm *bprm)
+{
+	if (!bprm->is_check)
+		return 0;
+
+	return ima_bprm_check(bprm);
+}
+
 /**
  * ima_file_check - based on policy, collect/store measurement.
  * @file: pointer to the file to be measured
@@ -1177,6 +1198,7 @@  static int __init init_ima(void)
 
 static struct security_hook_list ima_hooks[] __ro_after_init = {
 	LSM_HOOK_INIT(bprm_check_security, ima_bprm_check),
+	LSM_HOOK_INIT(bprm_creds_for_exec, ima_bprm_creds_for_exec),
 	LSM_HOOK_INIT(file_post_open, ima_file_check),
 	LSM_HOOK_INIT(inode_post_create_tmpfile, ima_post_create_tmpfile),
 	LSM_HOOK_INIT(file_release, ima_file_free),