[PATCH 5/7] ima: add securityfs interface to save a measurements list with kexec header

From: Roberto Sassu
Date: Tue May 16 2017 - 08:58:43 EST


Through the new interface binary_kexec_runtime_measurements, it will be
possible to read the same content returned by binary_runtime_measurements,
with the kexec header prepended.

The new interface has been added for testing ima_restore_measurement_list()
which, at the moment, works only on PPC systems. The interface for reading
the binary list with the kexec header will be provided in a separate patch.

The patch reuses ima_measurements_start() and ima_measurements_next()
to send the measurements list to userspace. Their behavior changes
depending on the current dentry.

To provide the correct information in the kexec header,
ima_measurements_start() has to iterate over the whole list and calculate
the number of entries and the total size. It is not possible to read
the value of the global variable binary_runtime_size and ima_htable.len
without taking ima_extend_list_mutex, because there might have been a list
add between the two read operations.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
security/integrity/ima/Kconfig | 8 ++++++
security/integrity/ima/ima.h | 2 ++
security/integrity/ima/ima_fs.c | 42 ++++++++++++++++++++++++++++---
security/integrity/ima/ima_kexec.c | 2 +-
security/integrity/ima/ima_template.c | 2 +-
security/integrity/ima/ima_template_lib.c | 19 ++++++++++++++
security/integrity/ima/ima_template_lib.h | 2 ++
7 files changed, 71 insertions(+), 6 deletions(-)

diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 370eb2f..0f60c04 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -39,6 +39,14 @@ config IMA_KEXEC
Depending on the IMA policy, the measurement list can grow to
be very large.

+config IMA_KEXEC_TESTING
+ bool "Enable securityfs interfaces to save/restore measurement list"
+ depends on IMA
+ default n
+ help
+ Use binary_kexec_runtime_measurements to save the binary list
+ with the kexec header; use restore_kexec_list to restore a list.
+
config IMA_MEASURE_PCR_IDX
int
depends on IMA
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 10ef9c8..416497b 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -49,6 +49,8 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 };
#define IMA_TEMPLATE_IMA_NAME "ima"
#define IMA_TEMPLATE_IMA_FMT "d|n"

+#define IMA_KEXEC_HDR_VERSION 1
+
/* current content of the policy */
extern int ima_policy_flag;

diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
index ca303e5..a93f941 100644
--- a/security/integrity/ima/ima_fs.c
+++ b/security/integrity/ima/ima_fs.c
@@ -25,6 +25,7 @@
#include <linux/vmalloc.h>

#include "ima.h"
+#include "ima_template_lib.h"

static DEFINE_MUTEX(ima_write_mutex);

@@ -75,28 +76,52 @@ static const struct file_operations ima_measurements_count_ops = {
.llseek = generic_file_llseek,
};

+static struct dentry *binary_kexec_runtime_measurements;
+
/* returns pointer to hlist_node */
static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
{
loff_t l = *pos;
struct ima_queue_entry *qe;
+ struct ima_queue_entry *qe_found = NULL;
+ unsigned long size = 0, count = 0;
+ bool khdr = m->file->f_path.dentry == binary_kexec_runtime_measurements;

/* we need a lock since pos could point beyond last element */
rcu_read_lock();
list_for_each_entry_rcu(qe, &ima_measurements, later) {
- if (!l--) {
- rcu_read_unlock();
- return qe;
+ if (!l) {
+ qe_found = qe_found ? qe_found : qe;
+
+ if (!khdr)
+ break;
+
+ if (*pos)
+ break;
+
+ size += ima_get_template_entry_size(qe->entry);
+ count++;
+ m->private = qe;
+ continue;
}
+ l--;
}
rcu_read_unlock();
- return NULL;
+
+ if (khdr && size)
+ ima_show_kexec_hdr(m, count, size);
+
+ return qe_found;
}

static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
{
+ bool khdr = m->file->f_path.dentry == binary_kexec_runtime_measurements;
struct ima_queue_entry *qe = v;

+ if (khdr && qe == m->private)
+ return NULL;
+
/* lock protects when reading beyond last element
* against concurrent list-extension
*/
@@ -490,8 +515,17 @@ int __init ima_fs_init(void)
if (IS_ERR(ima_policy))
goto out;

+#ifdef CONFIG_IMA_KEXEC_TESTING
+ binary_kexec_runtime_measurements =
+ securityfs_create_file("binary_kexec_runtime_measurements",
+ S_IRUSR | S_IRGRP, ima_dir, NULL,
+ &ima_measurements_ops);
+ if (IS_ERR(binary_kexec_runtime_measurements))
+ goto out;
+#endif
return 0;
out:
+ securityfs_remove(binary_kexec_runtime_measurements);
securityfs_remove(violations);
securityfs_remove(runtime_measurements_count);
securityfs_remove(ascii_runtime_measurements);
diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
index e473eee..b0b8ed2 100644
--- a/security/integrity/ima/ima_kexec.c
+++ b/security/integrity/ima/ima_kexec.c
@@ -36,7 +36,7 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
file.count = sizeof(khdr); /* reserved space */

memset(&khdr, 0, sizeof(khdr));
- khdr.version = 1;
+ khdr.version = IMA_KEXEC_HDR_VERSION;
list_for_each_entry_rcu(qe, &ima_measurements, later) {
if (file.count < file.size) {
khdr.count++;
diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c
index 7412d02..f86456c 100644
--- a/security/integrity/ima/ima_template.c
+++ b/security/integrity/ima/ima_template.c
@@ -347,7 +347,7 @@ int ima_restore_measurement_list(loff_t size, void *buf)
khdr->buffer_size = le64_to_cpu(khdr->buffer_size);
}

- if (khdr->version != 1) {
+ if (khdr->version != IMA_KEXEC_HDR_VERSION) {
pr_err("attempting to restore a incompatible measurement list");
return -EINVAL;
}
diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c
index 28af43f..de2b064 100644
--- a/security/integrity/ima/ima_template_lib.c
+++ b/security/integrity/ima/ima_template_lib.c
@@ -159,6 +159,25 @@ void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data);
}

+void ima_show_kexec_hdr(struct seq_file *m, unsigned long count,
+ unsigned long size)
+{
+ struct ima_kexec_hdr khdr;
+
+ memset(&khdr, 0, sizeof(khdr));
+ khdr.version = IMA_KEXEC_HDR_VERSION;
+ khdr.count = count;
+ khdr.buffer_size = sizeof(khdr) + size;
+
+ if (ima_canonical_fmt) {
+ khdr.version = cpu_to_le16(khdr.version);
+ khdr.count = cpu_to_le64(khdr.count);
+ khdr.buffer_size = cpu_to_le64(khdr.buffer_size);
+ }
+
+ ima_putc(m, &khdr, sizeof(khdr));
+}
+
/**
* ima_parse_buf() - Parses lengths and data from an input buffer
* @bufstartp: Buffer start address.
diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h
index 6a3d8b8..069e4ba 100644
--- a/security/integrity/ima/ima_template_lib.h
+++ b/security/integrity/ima/ima_template_lib.h
@@ -29,6 +29,8 @@ void ima_show_template_string(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
void ima_show_template_sig(struct seq_file *m, enum ima_show_type show,
struct ima_field_data *field_data);
+void ima_show_kexec_hdr(struct seq_file *m, unsigned long count,
+ unsigned long size);
int ima_parse_buf(void *bufstartp, void *bufendp, void **bufcurp,
int maxfields, struct ima_field_data *fields, int *curfields,
unsigned long *len_mask, int enforce_mask, char *bufname);
--
2.9.3