summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2025-12-03 11:08:03 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2025-12-03 11:08:03 -0800
commit777f8171602d5954cac024b66afa1b5b030641a4 (patch)
tree85a8cc4e5a4bea33c1f6ce7713d8fddc399c1aa4 /security
parent204a920f284e7264aa6dcd5876cbb1e03a7e4ebc (diff)
parent738c9738e690f5cea24a3ad6fd2d9a323cf614f6 (diff)
Merge tag 'integrity-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity
Pull integrity updates from Mimi Zohar: "Bug fixes: - defer credentials checking from the bprm_check_security hook to the bprm_creds_from_file security hook - properly ignore IMA policy rules based on undefined SELinux labels IMA policy rule extensions: - extend IMA to limit including file hashes in the audit logs (dont_audit action) - define a new filesystem subtype policy option (fs_subtype) Misc: - extend IMA to support in-kernel module decompression by deferring the IMA signature verification in kernel_read_file() to after the kernel module is decompressed" * tag 'integrity-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity: ima: Handle error code returned by ima_filter_rule_match() ima: Access decompressed kernel module to verify appended signature ima: add fs_subtype condition for distinguishing FUSE instances ima: add dont_audit action to suppress audit actions ima: Attach CREDS_CHECK IMA hook to bprm_creds_from_file LSM hook
Diffstat (limited to 'security')
-rw-r--r--security/integrity/ima/ima_main.c62
-rw-r--r--security/integrity/ima/ima_policy.c62
-rw-r--r--security/ipe/hooks.c1
-rw-r--r--security/selinux/hooks.c5
4 files changed, 106 insertions, 24 deletions
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index b703bfc2f470..5770cf691912 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -235,7 +235,8 @@ static void ima_file_free(struct file *file)
static int process_measurement(struct file *file, const struct cred *cred,
struct lsm_prop *prop, char *buf, loff_t size,
- int mask, enum ima_hooks func)
+ int mask, enum ima_hooks func,
+ enum kernel_read_file_id read_id)
{
struct inode *real_inode, *inode = file_inode(file);
struct ima_iint_cache *iint = NULL;
@@ -406,6 +407,12 @@ static int process_measurement(struct file *file, const struct cred *cred,
if (rc != 0 && rc != -EBADF && rc != -EINVAL)
goto out_locked;
+ /* Defer measuring/appraising kernel modules to READING_MODULE */
+ if (read_id == READING_MODULE_COMPRESSED) {
+ must_appraise = 0;
+ goto out_locked;
+ }
+
if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */
pathname = ima_d_path(&file->f_path, &pathbuf, filename);
@@ -486,14 +493,14 @@ static int ima_file_mmap(struct file *file, unsigned long reqprot,
if (reqprot & PROT_EXEC) {
ret = process_measurement(file, current_cred(), &prop, NULL,
- 0, MAY_EXEC, MMAP_CHECK_REQPROT);
+ 0, MAY_EXEC, MMAP_CHECK_REQPROT, 0);
if (ret)
return ret;
}
if (prot & PROT_EXEC)
return process_measurement(file, current_cred(), &prop, NULL,
- 0, MAY_EXEC, MMAP_CHECK);
+ 0, MAY_EXEC, MMAP_CHECK, 0);
return 0;
}
@@ -573,18 +580,41 @@ static int ima_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
*/
static int ima_bprm_check(struct linux_binprm *bprm)
{
- int ret;
struct lsm_prop prop;
security_current_getlsmprop_subj(&prop);
- ret = process_measurement(bprm->file, current_cred(),
- &prop, NULL, 0, MAY_EXEC, BPRM_CHECK);
- if (ret)
- return ret;
-
- security_cred_getlsmprop(bprm->cred, &prop);
- return process_measurement(bprm->file, bprm->cred, &prop, NULL, 0,
- MAY_EXEC, CREDS_CHECK);
+ return process_measurement(bprm->file, current_cred(),
+ &prop, NULL, 0, MAY_EXEC, BPRM_CHECK, 0);
+}
+
+/**
+ * ima_creds_check - based on policy, collect/store measurement.
+ * @bprm: contains the linux_binprm structure
+ * @file: contains the file descriptor of the binary being executed
+ *
+ * The OS protects against an executable file, already open for write,
+ * from being executed in deny_write_access() and an executable file,
+ * already open for execute, from being modified in get_write_access().
+ * So we can be certain that what we verify and measure here is actually
+ * what is being executed.
+ *
+ * The difference from ima_bprm_check() is that ima_creds_check() is invoked
+ * only after determining the final binary to be executed without interpreter,
+ * and not when searching for intermediate binaries. The reason is that since
+ * commit 56305aa9b6fab ("exec: Compute file based creds only once"), the
+ * credentials to be applied to the process are calculated only at that stage
+ * (bprm_creds_from_file security hook instead of bprm_check_security).
+ *
+ * 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_creds_check(struct linux_binprm *bprm, const struct file *file)
+{
+ struct lsm_prop prop;
+
+ security_current_getlsmprop_subj(&prop);
+ return process_measurement((struct file *)file, bprm->cred, &prop, NULL,
+ 0, MAY_EXEC, CREDS_CHECK, 0);
}
/**
@@ -632,7 +662,7 @@ static int ima_file_check(struct file *file, int mask)
security_current_getlsmprop_subj(&prop);
return process_measurement(file, current_cred(), &prop, NULL, 0,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC |
- MAY_APPEND), FILE_CHECK);
+ MAY_APPEND), FILE_CHECK, 0);
}
static int __ima_inode_hash(struct inode *inode, struct file *file, char *buf,
@@ -851,12 +881,13 @@ static int ima_read_file(struct file *file, enum kernel_read_file_id read_id,
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getlsmprop_subj(&prop);
return process_measurement(file, current_cred(), &prop, NULL, 0,
- MAY_READ, func);
+ MAY_READ, func, 0);
}
const int read_idmap[READING_MAX_ID] = {
[READING_FIRMWARE] = FIRMWARE_CHECK,
[READING_MODULE] = MODULE_CHECK,
+ [READING_MODULE_COMPRESSED] = MODULE_CHECK,
[READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK,
[READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK,
[READING_POLICY] = POLICY_CHECK
@@ -894,7 +925,7 @@ static int ima_post_read_file(struct file *file, char *buf, loff_t size,
func = read_idmap[read_id] ?: FILE_CHECK;
security_current_getlsmprop_subj(&prop);
return process_measurement(file, current_cred(), &prop, buf, size,
- MAY_READ, func);
+ MAY_READ, func, read_id);
}
/**
@@ -1242,6 +1273,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(bprm_creds_from_file, ima_creds_check),
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),
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 128fab897930..8fbd8755f5bc 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -38,6 +38,7 @@
#define IMA_GID 0x2000
#define IMA_EGID 0x4000
#define IMA_FGROUP 0x8000
+#define IMA_FS_SUBTYPE 0x10000
#define UNKNOWN 0
#define MEASURE 0x0001 /* same as IMA_MEASURE */
@@ -45,6 +46,7 @@
#define APPRAISE 0x0004 /* same as IMA_APPRAISE */
#define DONT_APPRAISE 0x0008
#define AUDIT 0x0040
+#define DONT_AUDIT 0x0080
#define HASH 0x0100
#define DONT_HASH 0x0200
@@ -119,6 +121,7 @@ struct ima_rule_entry {
int type; /* audit type */
} lsm[MAX_LSM_RULES];
char *fsname;
+ char *fs_subtype;
struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */
struct ima_rule_opt_list *label; /* Measure data grouped under this label */
struct ima_template_desc *template;
@@ -241,7 +244,8 @@ static struct ima_rule_entry build_appraise_rules[] __ro_after_init = {
static struct ima_rule_entry secure_boot_rules[] __ro_after_init = {
{.action = APPRAISE, .func = MODULE_CHECK,
- .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
+ .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED | IMA_MODSIG_ALLOWED |
+ IMA_CHECK_BLACKLIST},
{.action = APPRAISE, .func = FIRMWARE_CHECK,
.flags = IMA_FUNC | IMA_DIGSIG_REQUIRED},
{.action = APPRAISE, .func = KEXEC_KERNEL_CHECK,
@@ -397,6 +401,7 @@ static void ima_free_rule(struct ima_rule_entry *entry)
* the defined_templates list and cannot be freed here
*/
kfree(entry->fsname);
+ kfree(entry->fs_subtype);
ima_free_rule_opt_list(entry->keyrings);
ima_lsm_free_rule(entry);
kfree(entry);
@@ -601,6 +606,12 @@ static bool ima_match_rules(struct ima_rule_entry *rule,
if ((rule->flags & IMA_FSNAME)
&& strcmp(rule->fsname, inode->i_sb->s_type->name))
return false;
+ if (rule->flags & IMA_FS_SUBTYPE) {
+ if (!inode->i_sb->s_subtype)
+ return false;
+ if (strcmp(rule->fs_subtype, inode->i_sb->s_subtype))
+ return false;
+ }
if ((rule->flags & IMA_FSUUID) &&
!uuid_equal(&rule->fsuuid, &inode->i_sb->s_uuid))
return false;
@@ -674,7 +685,7 @@ retry:
goto retry;
}
}
- if (!rc) {
+ if (rc <= 0) {
result = false;
goto out;
}
@@ -1064,10 +1075,10 @@ void ima_update_policy(void)
enum policy_opt {
Opt_measure, Opt_dont_measure,
Opt_appraise, Opt_dont_appraise,
- Opt_audit, Opt_hash, Opt_dont_hash,
+ Opt_audit, Opt_dont_audit, Opt_hash, Opt_dont_hash,
Opt_obj_user, Opt_obj_role, Opt_obj_type,
Opt_subj_user, Opt_subj_role, Opt_subj_type,
- Opt_func, Opt_mask, Opt_fsmagic, Opt_fsname, Opt_fsuuid,
+ Opt_func, Opt_mask, Opt_fsmagic, Opt_fsname, Opt_fs_subtype, Opt_fsuuid,
Opt_uid_eq, Opt_euid_eq, Opt_gid_eq, Opt_egid_eq,
Opt_fowner_eq, Opt_fgroup_eq,
Opt_uid_gt, Opt_euid_gt, Opt_gid_gt, Opt_egid_gt,
@@ -1086,6 +1097,7 @@ static const match_table_t policy_tokens = {
{Opt_appraise, "appraise"},
{Opt_dont_appraise, "dont_appraise"},
{Opt_audit, "audit"},
+ {Opt_dont_audit, "dont_audit"},
{Opt_hash, "hash"},
{Opt_dont_hash, "dont_hash"},
{Opt_obj_user, "obj_user=%s"},
@@ -1098,6 +1110,7 @@ static const match_table_t policy_tokens = {
{Opt_mask, "mask=%s"},
{Opt_fsmagic, "fsmagic=%s"},
{Opt_fsname, "fsname=%s"},
+ {Opt_fs_subtype, "fs_subtype=%s"},
{Opt_fsuuid, "fsuuid=%s"},
{Opt_uid_eq, "uid=%s"},
{Opt_euid_eq, "euid=%s"},
@@ -1282,7 +1295,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC |
IMA_UID | IMA_FOWNER | IMA_FSUUID |
IMA_INMASK | IMA_EUID | IMA_PCR |
- IMA_FSNAME | IMA_GID | IMA_EGID |
+ IMA_FSNAME | IMA_FS_SUBTYPE |
+ IMA_GID | IMA_EGID |
IMA_FGROUP | IMA_DIGSIG_REQUIRED |
IMA_PERMIT_DIRECTIO | IMA_VALIDATE_ALGOS |
IMA_CHECK_BLACKLIST | IMA_VERITY_REQUIRED))
@@ -1295,7 +1309,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
if (entry->flags & ~(IMA_FUNC | IMA_MASK | IMA_FSMAGIC |
IMA_UID | IMA_FOWNER | IMA_FSUUID |
IMA_INMASK | IMA_EUID | IMA_PCR |
- IMA_FSNAME | IMA_GID | IMA_EGID |
+ IMA_FSNAME | IMA_FS_SUBTYPE |
+ IMA_GID | IMA_EGID |
IMA_FGROUP | IMA_DIGSIG_REQUIRED |
IMA_PERMIT_DIRECTIO | IMA_MODSIG_ALLOWED |
IMA_CHECK_BLACKLIST | IMA_VALIDATE_ALGOS))
@@ -1308,7 +1323,8 @@ static bool ima_validate_rule(struct ima_rule_entry *entry)
if (entry->flags & ~(IMA_FUNC | IMA_FSMAGIC | IMA_UID |
IMA_FOWNER | IMA_FSUUID | IMA_EUID |
- IMA_PCR | IMA_FSNAME | IMA_GID | IMA_EGID |
+ IMA_PCR | IMA_FSNAME | IMA_FS_SUBTYPE |
+ IMA_GID | IMA_EGID |
IMA_FGROUP))
return false;
@@ -1478,6 +1494,14 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
entry->action = AUDIT;
break;
+ case Opt_dont_audit:
+ ima_log_string(ab, "action", "dont_audit");
+
+ if (entry->action != UNKNOWN)
+ result = -EINVAL;
+
+ entry->action = DONT_AUDIT;
+ break;
case Opt_hash:
ima_log_string(ab, "action", "hash");
@@ -1587,6 +1611,22 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
result = 0;
entry->flags |= IMA_FSNAME;
break;
+ case Opt_fs_subtype:
+ ima_log_string(ab, "fs_subtype", args[0].from);
+
+ if (entry->fs_subtype) {
+ result = -EINVAL;
+ break;
+ }
+
+ entry->fs_subtype = kstrdup(args[0].from, GFP_KERNEL);
+ if (!entry->fs_subtype) {
+ result = -ENOMEM;
+ break;
+ }
+ result = 0;
+ entry->flags |= IMA_FS_SUBTYPE;
+ break;
case Opt_keyrings:
ima_log_string(ab, "keyrings", args[0].from);
@@ -2097,6 +2137,8 @@ int ima_policy_show(struct seq_file *m, void *v)
seq_puts(m, pt(Opt_dont_appraise));
if (entry->action & AUDIT)
seq_puts(m, pt(Opt_audit));
+ if (entry->action & DONT_AUDIT)
+ seq_puts(m, pt(Opt_dont_audit));
if (entry->action & HASH)
seq_puts(m, pt(Opt_hash));
if (entry->action & DONT_HASH)
@@ -2133,6 +2175,12 @@ int ima_policy_show(struct seq_file *m, void *v)
seq_puts(m, " ");
}
+ if (entry->flags & IMA_FS_SUBTYPE) {
+ snprintf(tbuf, sizeof(tbuf), "%s", entry->fs_subtype);
+ seq_printf(m, pt(Opt_fs_subtype), tbuf);
+ seq_puts(m, " ");
+ }
+
if (entry->flags & IMA_KEYRINGS) {
seq_puts(m, "keyrings=");
ima_show_rule_opt_list(m, entry->keyrings);
diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c
index d0323b81cd8f..1053a4acf589 100644
--- a/security/ipe/hooks.c
+++ b/security/ipe/hooks.c
@@ -118,6 +118,7 @@ int ipe_kernel_read_file(struct file *file, enum kernel_read_file_id id,
op = IPE_OP_FIRMWARE;
break;
case READING_MODULE:
+ case READING_MODULE_COMPRESSED:
op = IPE_OP_KERNEL_MODULE;
break;
case READING_KEXEC_INITRAMFS:
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 712d0b18a30c..d053ce562370 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -4296,7 +4296,7 @@ static int selinux_kernel_read_file(struct file *file,
{
int rc = 0;
- BUILD_BUG_ON_MSG(READING_MAX_ID > 7,
+ BUILD_BUG_ON_MSG(READING_MAX_ID > 8,
"New kernel_read_file_id introduced; update SELinux!");
switch (id) {
@@ -4304,6 +4304,7 @@ static int selinux_kernel_read_file(struct file *file,
rc = selinux_kernel_load_from_file(file, SYSTEM__FIRMWARE_LOAD);
break;
case READING_MODULE:
+ case READING_MODULE_COMPRESSED:
rc = selinux_kernel_load_from_file(file, SYSTEM__MODULE_LOAD);
break;
case READING_KEXEC_IMAGE:
@@ -4332,7 +4333,7 @@ static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents)
{
int rc = 0;
- BUILD_BUG_ON_MSG(LOADING_MAX_ID > 7,
+ BUILD_BUG_ON_MSG(LOADING_MAX_ID > 8,
"New kernel_load_data_id introduced; update SELinux!");
switch (id) {