diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-12-14 20:36:37 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-12-14 20:36:37 -0800 |
commit | 67e2c3883828b39548cee2091b36656787775d95 (patch) | |
tree | 975a0f546a604beda30d4ede34f8e9cca9a88b71 /security/integrity/ima | |
parent | 6ae840e7cc4be0be3aa40d9f67c35c75cfc67d83 (diff) | |
parent | b2d1965dcea148100ffc4e7199470bf5fad13871 (diff) |
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security
Pull security layer updates from James Morris:
"In terms of changes, there's general maintenance to the Smack,
SELinux, and integrity code.
The IMA code adds a new kconfig option, IMA_APPRAISE_SIGNED_INIT,
which allows IMA appraisal to require signatures. Support for reading
keys from rootfs before init is call is also added"
* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: (23 commits)
selinux: Remove security_ops extern
security: smack: fix out-of-bounds access in smk_parse_smack()
VFS: refactor vfs_read()
ima: require signature based appraisal
integrity: provide a hook to load keys when rootfs is ready
ima: load x509 certificate from the kernel
integrity: provide a function to load x509 certificate from the kernel
integrity: define a new function integrity_read_file()
Security: smack: replace kzalloc with kmem_cache for inode_smack
Smack: Lock mode for the floor and hat labels
ima: added support for new kernel cmdline parameter ima_template_fmt
ima: allocate field pointers array on demand in template_desc_init_fields()
ima: don't allocate a copy of template_fmt in template_desc_init_fields()
ima: display template format in meas. list if template name length is zero
ima: added error messages to template-related functions
ima: use atomic bit operations to protect policy update interface
ima: ignore empty and with whitespaces policy lines
ima: no need to allocate entry for comment
ima: report policy load status
ima: use path names cache
...
Diffstat (limited to 'security/integrity/ima')
-rw-r--r-- | security/integrity/ima/Kconfig | 25 | ||||
-rw-r--r-- | security/integrity/ima/ima_api.c | 7 | ||||
-rw-r--r-- | security/integrity/ima/ima_crypto.c | 35 | ||||
-rw-r--r-- | security/integrity/ima/ima_fs.c | 37 | ||||
-rw-r--r-- | security/integrity/ima/ima_init.c | 17 | ||||
-rw-r--r-- | security/integrity/ima/ima_main.c | 5 | ||||
-rw-r--r-- | security/integrity/ima/ima_policy.c | 42 | ||||
-rw-r--r-- | security/integrity/ima/ima_template.c | 103 |
8 files changed, 164 insertions, 107 deletions
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index e099875643c5..b80a93ec1ccc 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -131,3 +131,28 @@ config IMA_TRUSTED_KEYRING help This option requires that all keys added to the .ima keyring be signed by a key on the system trusted keyring. + +config IMA_LOAD_X509 + bool "Load X509 certificate onto the '.ima' trusted keyring" + depends on IMA_TRUSTED_KEYRING + default n + help + File signature verification is based on the public keys + loaded on the .ima trusted keyring. These public keys are + X509 certificates signed by a trusted key on the + .system keyring. This option enables X509 certificate + loading from the kernel onto the '.ima' trusted keyring. + +config IMA_X509_PATH + string "IMA X509 certificate path" + depends on IMA_LOAD_X509 + default "/etc/keys/x509_ima.der" + help + This option defines IMA X509 certificate path. + +config IMA_APPRAISE_SIGNED_INIT + bool "Require signed user-space initialization" + depends on IMA_LOAD_X509 + default n + help + This option requires user-space init to be signed. diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index f92be1b14089..b8a27c5052d4 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -173,8 +173,7 @@ int ima_get_action(struct inode *inode, int mask, int function) { int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE; - if (!ima_appraise) - flags &= ~IMA_APPRAISE; + flags &= ima_policy_flag; return ima_match_policy(inode, function, mask, flags); } @@ -325,11 +324,11 @@ const char *ima_d_path(struct path *path, char **pathbuf) { char *pathname = NULL; - *pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + *pathbuf = __getname(); if (*pathbuf) { pathname = d_absolute_path(path, *pathbuf, PATH_MAX); if (IS_ERR(pathname)) { - kfree(*pathbuf); + __putname(*pathbuf); *pathbuf = NULL; pathname = NULL; } diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 78d66dae15f4..686355fea7fd 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -67,36 +67,6 @@ MODULE_PARM_DESC(ahash_bufsize, "Maximum ahash buffer size"); static struct crypto_shash *ima_shash_tfm; static struct crypto_ahash *ima_ahash_tfm; -/** - * ima_kernel_read - read file content - * - * This is a function for reading file content instead of kernel_read(). - * It does not perform locking checks to ensure it cannot be blocked. - * It does not perform security checks because it is irrelevant for IMA. - * - */ -static int ima_kernel_read(struct file *file, loff_t offset, - char *addr, unsigned long count) -{ - mm_segment_t old_fs; - char __user *buf = addr; - ssize_t ret = -EINVAL; - - if (!(file->f_mode & FMODE_READ)) - return -EBADF; - - old_fs = get_fs(); - set_fs(get_ds()); - if (file->f_op->read) - ret = file->f_op->read(file, buf, count, &offset); - else if (file->f_op->aio_read) - ret = do_sync_read(file, buf, count, &offset); - else if (file->f_op->read_iter) - ret = new_sync_read(file, buf, count, &offset); - set_fs(old_fs); - return ret; -} - int __init ima_init_crypto(void) { long rc; @@ -324,7 +294,8 @@ static int ima_calc_file_hash_atfm(struct file *file, } /* read buffer */ rbuf_len = min_t(loff_t, i_size - offset, rbuf_size[active]); - rc = ima_kernel_read(file, offset, rbuf[active], rbuf_len); + rc = integrity_kernel_read(file, offset, rbuf[active], + rbuf_len); if (rc != rbuf_len) goto out3; @@ -414,7 +385,7 @@ static int ima_calc_file_hash_tfm(struct file *file, while (offset < i_size) { int rbuf_len; - rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE); + rbuf_len = integrity_kernel_read(file, offset, rbuf, PAGE_SIZE); if (rbuf_len < 0) { rc = rbuf_len; break; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index da92fcc08d15..461215e5fd31 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -118,6 +118,7 @@ static int ima_measurements_show(struct seq_file *m, void *v) /* the list never shrinks, so we don't need a lock here */ struct ima_queue_entry *qe = v; struct ima_template_entry *e; + char *template_name; int namelen; u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; bool is_ima_template = false; @@ -128,6 +129,9 @@ static int ima_measurements_show(struct seq_file *m, void *v) if (e == NULL) return -1; + template_name = (e->template_desc->name[0] != '\0') ? + e->template_desc->name : e->template_desc->fmt; + /* * 1st: PCRIndex * PCR used is always the same (config option) in @@ -139,14 +143,14 @@ static int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, e->digest, TPM_DIGEST_SIZE); /* 3rd: template name size */ - namelen = strlen(e->template_desc->name); + namelen = strlen(template_name); ima_putc(m, &namelen, sizeof(namelen)); /* 4th: template name */ - ima_putc(m, e->template_desc->name, namelen); + ima_putc(m, template_name, namelen); /* 5th: template length (except for 'ima' template) */ - if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) + if (strcmp(template_name, IMA_TEMPLATE_IMA_NAME) == 0) is_ima_template = true; if (!is_ima_template) @@ -200,6 +204,7 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) /* the list never shrinks, so we don't need a lock here */ struct ima_queue_entry *qe = v; struct ima_template_entry *e; + char *template_name; int i; /* get entry */ @@ -207,6 +212,9 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) if (e == NULL) return -1; + template_name = (e->template_desc->name[0] != '\0') ? + e->template_desc->name : e->template_desc->fmt; + /* 1st: PCR used (config option) */ seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); @@ -214,7 +222,7 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) ima_print_digest(m, e->digest, TPM_DIGEST_SIZE); /* 3th: template name */ - seq_printf(m, " %s", e->template_desc->name); + seq_printf(m, " %s", template_name); /* 4th: template specific data */ for (i = 0; i < e->template_desc->num_fields; i++) { @@ -288,7 +296,12 @@ static struct dentry *runtime_measurements_count; static struct dentry *violations; static struct dentry *ima_policy; -static atomic_t policy_opencount = ATOMIC_INIT(1); +enum ima_fs_flags { + IMA_FS_BUSY, +}; + +static unsigned long ima_fs_flags; + /* * ima_open_policy: sequentialize access to the policy file */ @@ -297,9 +310,9 @@ static int ima_open_policy(struct inode *inode, struct file *filp) /* No point in being allowed to open it if you aren't going to write */ if (!(filp->f_flags & O_WRONLY)) return -EACCES; - if (atomic_dec_and_test(&policy_opencount)) - return 0; - return -EBUSY; + if (test_and_set_bit(IMA_FS_BUSY, &ima_fs_flags)) + return -EBUSY; + return 0; } /* @@ -311,10 +324,16 @@ static int ima_open_policy(struct inode *inode, struct file *filp) */ static int ima_release_policy(struct inode *inode, struct file *file) { + const char *cause = valid_policy ? "completed" : "failed"; + + pr_info("IMA: policy update %s\n", cause); + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, + "policy_update", cause, !valid_policy, 0); + if (!valid_policy) { ima_delete_rules(); valid_policy = 1; - atomic_set(&policy_opencount, 1); + clear_bit(IMA_FS_BUSY, &ima_fs_flags); return 0; } ima_update_policy(); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 9164fc8cac84..5e4c29d174ee 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -24,6 +24,12 @@ #include <crypto/hash_info.h> #include "ima.h" +#ifdef CONFIG_IMA_X509_PATH +#define IMA_X509_PATH CONFIG_IMA_X509_PATH +#else +#define IMA_X509_PATH "/etc/keys/x509_ima.der" +#endif + /* name for boot aggregate entry */ static const char *boot_aggregate_name = "boot_aggregate"; int ima_used_chip; @@ -91,6 +97,17 @@ err_out: return result; } +#ifdef CONFIG_IMA_LOAD_X509 +void __init ima_load_x509(void) +{ + int unset_flags = ima_policy_flag & IMA_APPRAISE; + + ima_policy_flag &= ~unset_flags; + integrity_load_x509(INTEGRITY_KEYRING_IMA, IMA_X509_PATH); + ima_policy_flag |= unset_flags; +} +#endif + int __init ima_init(void) { u8 pcr_i[TPM_DIGEST_SIZE]; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 62f59eca32d3..eeee00dce729 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -143,7 +143,7 @@ void ima_file_free(struct file *file) struct inode *inode = file_inode(file); struct integrity_iint_cache *iint; - if (!iint_initialized || !S_ISREG(inode->i_mode)) + if (!ima_policy_flag || !S_ISREG(inode->i_mode)) return; iint = integrity_iint_find(inode); @@ -246,7 +246,8 @@ out_digsig: rc = -EACCES; kfree(xattr_value); out_free: - kfree(pathbuf); + if (pathbuf) + __putname(pathbuf); out: mutex_unlock(&inode->i_mutex); if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE)) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index cdc620b2152f..d1eefb9d65fb 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -100,7 +100,13 @@ static struct ima_rule_entry default_appraise_rules[] = { {.action = DONT_APPRAISE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, +#ifndef CONFIG_IMA_APPRAISE_SIGNED_INIT {.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, .flags = IMA_FOWNER}, +#else + /* force signature */ + {.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, + .flags = IMA_FOWNER | IMA_DIGSIG_REQUIRED}, +#endif }; static LIST_HEAD(ima_default_rules); @@ -356,19 +362,8 @@ void __init ima_init_policy(void) */ void ima_update_policy(void) { - static const char op[] = "policy_update"; - const char *cause = "already-exists"; - int result = 1; - int audit_info = 0; - - if (ima_rules == &ima_default_rules) { - ima_rules = &ima_policy_rules; - ima_update_policy_flag(); - cause = "complete"; - result = 0; - } - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, - NULL, op, cause, result, audit_info); + ima_rules = &ima_policy_rules; + ima_update_policy_flag(); } enum { @@ -686,13 +681,12 @@ ssize_t ima_parse_add_rule(char *rule) ssize_t result, len; int audit_info = 0; - /* Prevent installed policy from changing */ - if (ima_rules != &ima_default_rules) { - integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, - NULL, op, "already-exists", - -EACCES, audit_info); - return -EACCES; - } + p = strsep(&rule, "\n"); + len = strlen(p) + 1; + p += strspn(p, " \t"); + + if (*p == '#' || *p == '\0') + return len; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { @@ -703,14 +697,6 @@ ssize_t ima_parse_add_rule(char *rule) INIT_LIST_HEAD(&entry->list); - p = strsep(&rule, "\n"); - len = strlen(p) + 1; - - if (*p == '#') { - kfree(entry); - return len; - } - result = ima_parse_rule(p, entry); if (result) { kfree(entry); diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index e854862c9337..0b7404ebfa80 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -24,6 +24,7 @@ static struct ima_template_desc defined_templates[] = { {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, {.name = "ima-ng", .fmt = "d-ng|n-ng"}, {.name = "ima-sig", .fmt = "d-ng|n-ng|sig"}, + {.name = "", .fmt = ""}, /* placeholder for a custom format */ }; static struct ima_template_field supported_fields[] = { @@ -41,19 +42,28 @@ static struct ima_template_field supported_fields[] = { static struct ima_template_desc *ima_template; static struct ima_template_desc *lookup_template_desc(const char *name); +static int template_desc_init_fields(const char *template_fmt, + struct ima_template_field ***fields, + int *num_fields); static int __init ima_template_setup(char *str) { struct ima_template_desc *template_desc; int template_len = strlen(str); + if (ima_template) + return 1; + /* * Verify that a template with the supplied name exists. * If not, use CONFIG_IMA_DEFAULT_TEMPLATE. */ template_desc = lookup_template_desc(str); - if (!template_desc) + if (!template_desc) { + pr_err("template %s not found, using %s\n", + str, CONFIG_IMA_DEFAULT_TEMPLATE); return 1; + } /* * Verify whether the current hash algorithm is supported @@ -70,6 +80,25 @@ static int __init ima_template_setup(char *str) } __setup("ima_template=", ima_template_setup); +static int __init ima_template_fmt_setup(char *str) +{ + int num_templates = ARRAY_SIZE(defined_templates); + + if (ima_template) + return 1; + + if (template_desc_init_fields(str, NULL, NULL) < 0) { + pr_err("format string '%s' not valid, using template %s\n", + str, CONFIG_IMA_DEFAULT_TEMPLATE); + return 1; + } + + defined_templates[num_templates - 1].fmt = str; + ima_template = defined_templates + num_templates - 1; + return 1; +} +__setup("ima_template_fmt=", ima_template_fmt_setup); + static struct ima_template_desc *lookup_template_desc(const char *name) { int i; @@ -113,43 +142,46 @@ static int template_desc_init_fields(const char *template_fmt, struct ima_template_field ***fields, int *num_fields) { - char *c, *template_fmt_copy, *template_fmt_ptr; + const char *template_fmt_ptr; + struct ima_template_field *found_fields[IMA_TEMPLATE_NUM_FIELDS_MAX]; int template_num_fields = template_fmt_size(template_fmt); - int i, result = 0; + int i, len; - if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) + if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) { + pr_err("format string '%s' contains too many fields\n", + template_fmt); return -EINVAL; - - /* copying is needed as strsep() modifies the original buffer */ - template_fmt_copy = kstrdup(template_fmt, GFP_KERNEL); - if (template_fmt_copy == NULL) - return -ENOMEM; - - *fields = kzalloc(template_num_fields * sizeof(*fields), GFP_KERNEL); - if (*fields == NULL) { - result = -ENOMEM; - goto out; } - template_fmt_ptr = template_fmt_copy; - for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && - i < template_num_fields; i++) { - struct ima_template_field *f = lookup_template_field(c); + for (i = 0, template_fmt_ptr = template_fmt; i < template_num_fields; + i++, template_fmt_ptr += len + 1) { + char tmp_field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN + 1]; + + len = strchrnul(template_fmt_ptr, '|') - template_fmt_ptr; + if (len == 0 || len > IMA_TEMPLATE_FIELD_ID_MAX_LEN) { + pr_err("Invalid field with length %d\n", len); + return -EINVAL; + } - if (!f) { - result = -ENOENT; - goto out; + memcpy(tmp_field_id, template_fmt_ptr, len); + tmp_field_id[len] = '\0'; + found_fields[i] = lookup_template_field(tmp_field_id); + if (!found_fields[i]) { + pr_err("field '%s' not found\n", tmp_field_id); + return -ENOENT; } - (*fields)[i] = f; } - *num_fields = i; -out: - if (result < 0) { - kfree(*fields); - *fields = NULL; + + if (fields && num_fields) { + *fields = kmalloc_array(i, sizeof(*fields), GFP_KERNEL); + if (*fields == NULL) + return -ENOMEM; + + memcpy(*fields, found_fields, i * sizeof(*fields)); + *num_fields = i; } - kfree(template_fmt_copy); - return result; + + return 0; } struct ima_template_desc *ima_template_desc_current(void) @@ -163,8 +195,15 @@ struct ima_template_desc *ima_template_desc_current(void) int __init ima_init_template(void) { struct ima_template_desc *template = ima_template_desc_current(); + int result; + + result = template_desc_init_fields(template->fmt, + &(template->fields), + &(template->num_fields)); + if (result < 0) + pr_err("template %s init failed, result: %d\n", + (strlen(template->name) ? + template->name : template->fmt), result); - return template_desc_init_fields(template->fmt, - &(template->fields), - &(template->num_fields)); + return result; } |