[PATCH v2 06/15] ima: add parser of digest lists metadata
From: Roberto Sassu
Date: Tue Nov 07 2017 - 05:42:49 EST
Digest lists can be uploaded to IMA by supplying the path of their
metadata.
Digest list metadata are:
- DATA_ALGO: algorithm of the digests to be uploaded
- DATA_DIGEST: digest of the file containing the digest list
- DATA_SIGNATURE: signature of the file containing the digest list
- DATA_FILE_PATH: pathname
- DATA_REF_ID: reference ID of the digest list
- DATA_TYPE: type of digest list
The new function ima_load_digest_list_metadata() loads digest list metadata
from a predefined position (/etc/ima/digest_lists/metadata), when rootfs
becomes available. Digest lists must be loaded before IMA appraisal is in
enforcing mode.
The new function ima_parse_digest_list_metadata() parses the metadata and
loads each digest list individually. Then, it parses the data according to
the data type specified.
To avoid the delay due to extending a PCR for each digest list, digests of
digest lists are added to the hash table. If appraisal is in enforcing
mode, this is done only if the signature verification succeeds. IMA does
not add the digest of an accessed file to the measurement list if the
digest is found in the hash table.
Changelog
v1:
- Verify signature of digest lists if appraisal is enabled
- Load digest lists when rootfs is available
- Ignore digest lists if no policy is loaded
Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
include/linux/fs.h | 2 +
security/integrity/iint.c | 1 +
security/integrity/ima/Kconfig | 19 ++++
security/integrity/ima/Makefile | 1 +
security/integrity/ima/ima.h | 12 +++
security/integrity/ima/ima_digest_list.c | 152 +++++++++++++++++++++++++++++++
security/integrity/integrity.h | 8 ++
7 files changed, 195 insertions(+)
create mode 100644 security/integrity/ima/ima_digest_list.c
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 8d7d2850963c..06737235665b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2793,6 +2793,8 @@ extern int do_pipe_flags(int *, int);
id(KEXEC_INITRAMFS, kexec-initramfs) \
id(POLICY, security-policy) \
id(X509_CERTIFICATE, x509-certificate) \
+ id(DIGEST_LIST_METADATA, digest-list-metadata) \
+ id(DIGEST_LIST, digest-list) \
id(MAX_ID, )
#define __fid_enumify(ENUM, dummy) READING_ ## ENUM,
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index c84e05866052..68c14d1dfc0c 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -209,4 +209,5 @@ void __init integrity_load_keys(void)
{
ima_load_x509();
evm_load_x509();
+ ima_load_digest_list_metadata();
}
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 35ef69312811..fa40cee1e1a2 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -227,3 +227,22 @@ config IMA_APPRAISE_SIGNED_INIT
default n
help
This option requires user-space init to be signed.
+
+config IMA_DIGEST_LIST
+ bool "Measure/appraise/audit files depending on uploaded digest lists"
+ depends on IMA
+ default n
+ help
+ This option allows users to load digest lists. If a measured file
+ has the same digest of one from loaded lists, IMA will not create
+ a new measurement entry or an audit log. They will be created only
+ when digest lists are loaded. If appraisal is enabled, access will
+ be permitted if the digest is in the digest list. File updates
+ will be permitted if, in addition, the digest is marked as mutable.
+
+config IMA_DIGEST_LIST_METADATA_PATH
+ string "IMA digest list metadata path"
+ depends on IMA_DIGEST_LIST
+ default "/etc/ima/digest_lists/metadata"
+ help
+ This option defines IMA digest list metadata path.
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index 29f198bde02b..00dbe3a8cb71 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -9,4 +9,5 @@ ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \
ima_policy.o ima_template.o ima_template_lib.o
ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o
ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o
+ima-$(CONFIG_IMA_DIGEST_LIST) += ima_digest_list.o
obj-$(CONFIG_IMA_BLACKLIST_KEYRING) += ima_mok.o
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 1f6591a57fea..1f43284788eb 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -158,6 +158,18 @@ int ima_restore_measurement_entry(struct ima_template_entry *entry);
int ima_restore_measurement_list(loff_t bufsize, void *buf);
struct ima_digest *ima_lookup_loaded_digest(u8 *digest);
int ima_add_digest_data_entry(u8 *digest, u8 is_mutable);
+#ifdef CONFIG_IMA_DIGEST_LIST
+void __init ima_load_digest_list_metadata(void);
+ssize_t ima_parse_digest_list_metadata(loff_t size, void *buf);
+#else
+static inline void ima_load_digest_list_metadata(void)
+{
+}
+static inline ssize_t ima_parse_digest_list_metadata(loff_t size, void *buf)
+{
+ return -ENOTSUPP;
+}
+#endif
int ima_measurements_show(struct seq_file *m, void *v);
unsigned long ima_get_binary_runtime_size(void);
int ima_init_template(void);
diff --git a/security/integrity/ima/ima_digest_list.c b/security/integrity/ima/ima_digest_list.c
new file mode 100644
index 000000000000..28172424e5a2
--- /dev/null
+++ b/security/integrity/ima/ima_digest_list.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2017 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * 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.
+ *
+ * File: ima_digest_list.c
+ * Functions to manage digest lists.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/vmalloc.h>
+
+#include "ima.h"
+#include "ima_template_lib.h"
+
+enum digest_metadata_fields {DATA_ALGO, DATA_DIGEST, DATA_SIGNATURE,
+ DATA_FILE_PATH, DATA_REF_ID, DATA_TYPE,
+ DATA__LAST};
+
+static int ima_parse_digest_list_data(struct ima_field_data *data)
+{
+ void *digest_list;
+ loff_t digest_list_size;
+ u16 data_algo = le16_to_cpu(*(u16 *)data[DATA_ALGO].data);
+ u16 data_type = le16_to_cpu(*(u16 *)data[DATA_TYPE].data);
+ int ret;
+
+ if (data_algo != ima_hash_algo) {
+ pr_err("Incompatible digest algorithm, expected %s\n",
+ hash_algo_name[ima_hash_algo]);
+ return -EINVAL;
+ }
+
+ ret = kernel_read_file_from_path(data[DATA_FILE_PATH].data,
+ &digest_list, &digest_list_size,
+ 0, READING_DIGEST_LIST);
+ if (ret < 0) {
+ pr_err("Unable to open file: %s (%d)",
+ data[DATA_FILE_PATH].data, ret);
+ return ret;
+ }
+
+ switch (data_type) {
+ default:
+ pr_err("Parser for data type %d not implemented\n", data_type);
+ ret = -EINVAL;
+ }
+
+ if (ret < 0) {
+ pr_err("Error parsing file: %s (%d)\n",
+ data[DATA_FILE_PATH].data, ret);
+ return ret;
+ }
+
+ vfree(digest_list);
+ return ret;
+}
+
+ssize_t ima_parse_digest_list_metadata(loff_t size, void *buf)
+{
+ struct ima_field_data entry;
+
+ struct ima_field_data entry_data[DATA__LAST] = {
+ [DATA_ALGO] = {.len = sizeof(u16)},
+ [DATA_TYPE] = {.len = sizeof(u16)},
+ };
+
+ DECLARE_BITMAP(data_mask, DATA__LAST);
+ void *bufp = buf, *bufendp = buf + size;
+ int ret;
+
+ bitmap_zero(data_mask, DATA__LAST);
+ bitmap_set(data_mask, DATA_ALGO, 1);
+ bitmap_set(data_mask, DATA_TYPE, 1);
+
+ ret = ima_parse_buf(bufp, bufendp, &bufp, 1, &entry, NULL, NULL,
+ ENFORCE_FIELDS, "metadata list entry");
+ if (ret < 0)
+ return ret;
+
+ ret = ima_parse_buf(entry.data, entry.data + entry.len, NULL,
+ DATA__LAST, entry_data, NULL, data_mask,
+ ENFORCE_FIELDS | ENFORCE_BUFEND,
+ "metadata entry data");
+ if (ret < 0)
+ goto out;
+
+ if (ima_policy_flag & IMA_APPRAISE) {
+ ret = integrity_digsig_verify(INTEGRITY_KEYRING_IMA,
+ (const char *)entry_data[DATA_SIGNATURE].data,
+ entry_data[DATA_SIGNATURE].len,
+ entry_data[DATA_DIGEST].data,
+ entry_data[DATA_DIGEST].len);
+ if (ret < 0) {
+ pr_err("Failed signature verification of: %s (%d)",
+ entry_data[DATA_FILE_PATH].data, ret);
+ goto out_parse_digest_list;
+ }
+ }
+
+ ret = ima_add_digest_data_entry(entry_data[DATA_DIGEST].data, 0);
+ if (ret < 0) {
+ if (ret == -EEXIST)
+ ret = 0;
+
+ goto out;
+ }
+
+out_parse_digest_list:
+ ret = ima_parse_digest_list_data(entry_data);
+out:
+ return ret < 0 ? ret : bufp - buf;
+}
+
+void __init ima_load_digest_list_metadata(void)
+{
+ void *datap;
+ loff_t size;
+ int ret;
+
+ int unset_flags = ima_policy_flag & IMA_APPRAISE;
+
+ if (!ima_policy_flag)
+ return;
+
+ ima_policy_flag &= ~unset_flags;
+ ret = kernel_read_file_from_path(CONFIG_IMA_DIGEST_LIST_METADATA_PATH,
+ &datap, &size, 0,
+ READING_DIGEST_LIST_METADATA);
+ if (ret < 0)
+ pr_err("Unable to open file: %s (%d)",
+ CONFIG_IMA_DIGEST_LIST_METADATA_PATH, ret);
+
+ ima_policy_flag |= unset_flags;
+
+ while (size > 0) {
+ ret = ima_parse_digest_list_metadata(size, datap);
+ if (ret < 0) {
+ pr_err("Unable to parse file: %s (%d)",
+ CONFIG_IMA_DIGEST_LIST_METADATA_PATH, ret);
+ break;
+ }
+ datap += ret;
+ size -= ret;
+ }
+}
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index e1bf040fb110..a5951879c15c 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.h
@@ -174,6 +174,14 @@ static inline void evm_load_x509(void)
}
#endif
+#ifdef CONFIG_IMA_DIGEST_LIST
+void __init ima_load_digest_list_metadata(void);
+#else
+static inline void ima_load_digest_list_metadata(void)
+{
+}
+#endif
+
#ifdef CONFIG_INTEGRITY_AUDIT
/* declarations */
void integrity_audit_msg(int audit_msgno, struct inode *inode,
--
2.11.0