diff mbox

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

Message ID 20170516125347.10574-6-roberto.sassu@huawei.com (mailing list archive)
State New, archived
Headers show

Commit Message

Roberto Sassu May 16, 2017, 12:53 p.m. UTC
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@huawei.com>
---
 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(-)

Comments

Mimi Zohar June 5, 2017, 6:04 a.m. UTC | #1
On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
> 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.

Please correct me if I'm wrong.  Your code walks the measurement list
calculating the total number of measurements and the memory size
needed to store in the kexec header.  Can't there be additional
measurements between the time these values - total number of
measurements and memory needed - were calculated and actually saving
the measurements?  How would that be any different than the problem
you're trying to solve?  In both cases, the number of measurements
might be less than the actual number of measurements.

As long as you query the number of measurements before getting the
memory needed, unless you're trying to verify a TPM quote, having
fewer measurements shouldn't be a problem for testing.

> 
> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> ---
>  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;

What is this?

> +
> +			if (!khdr)
> +				break;

Does this test need to be in the loop?

Mimi

> +
> +			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);

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roberto Sassu June 6, 2017, 8:49 a.m. UTC | #2
On 6/5/2017 8:04 AM, Mimi Zohar wrote:
> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
>> 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.
>
> Please correct me if I'm wrong.  Your code walks the measurement list
> calculating the total number of measurements and the memory size
> needed to store in the kexec header.  Can't there be additional
> measurements between the time these values - total number of
> measurements and memory needed - were calculated and actually saving
> the measurements?  How would that be any different than the problem
> you're trying to solve?  In both cases, the number of measurements
> might be less than the actual number of measurements.
>
> As long as you query the number of measurements before getting the
> memory needed, unless you're trying to verify a TPM quote, having
> fewer measurements shouldn't be a problem for testing.

The problem is that the total number of entries and the required
memory size might be inconsistent without taking ima_extend_list_mutex.
ima_measurements_start() could read the entries counter before
it is incremented by ima_add_digest_entry() and the required memory
size after it is updated. If this happens, the parser returns an error
because ENFORCE_BUFEND is set for the last entry and there would be
still data to read (the new entry added to the list).

Roberto


>
>>
>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>> ---
>>  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;
>
> What is this?
>
>> +
>> +			if (!khdr)
>> +				break;
>
> Does this test need to be in the loop?
>
> Mimi
>
>> +
>> +			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);
>
Roberto Sassu June 6, 2017, 9:13 a.m. UTC | #3
On 6/5/2017 8:04 AM, Mimi Zohar wrote:
> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
>> 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.
>
> Please correct me if I'm wrong.  Your code walks the measurement list
> calculating the total number of measurements and the memory size
> needed to store in the kexec header.  Can't there be additional
> measurements between the time these values - total number of
> measurements and memory needed - were calculated and actually saving
> the measurements?  How would that be any different than the problem
> you're trying to solve?  In both cases, the number of measurements
> might be less than the actual number of measurements.
>
> As long as you query the number of measurements before getting the
> memory needed, unless you're trying to verify a TPM quote, having
> fewer measurements shouldn't be a problem for testing.
>
>>
>> Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
>> ---
>>  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;
>
> What is this?

ima_measurements_start() should return the list entry at position *pos.
The line above prevents qe_found from being updated when the loop
continues until the last list entry.


>
>> +
>> +			if (!khdr)
>> +				break;
>
> Does this test need to be in the loop?

Yes. Otherwise, ima_measurements_start() would iterate over the whole
list when it is not necessary.

Roberto


>
> Mimi
>
>> +
>> +			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);
>
Mimi Zohar June 6, 2017, 10:56 a.m. UTC | #4
On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
> > On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
> >> 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.
> >
> > Please correct me if I'm wrong.  Your code walks the measurement list
> > calculating the total number of measurements and the memory size
> > needed to store in the kexec header.  Can't there be additional
> > measurements between the time these values - total number of
> > measurements and memory needed - were calculated and actually saving
> > the measurements?  How would that be any different than the problem
> > you're trying to solve?  In both cases, the number of measurements
> > might be less than the actual number of measurements.
> >
> > As long as you query the number of measurements before getting the
> > memory needed, unless you're trying to verify a TPM quote, having
> > fewer measurements shouldn't be a problem for testing.
> 
> The problem is that the total number of entries and the required
> memory size might be inconsistent without taking ima_extend_list_mutex.
> ima_measurements_start() could read the entries counter before
> it is incremented by ima_add_digest_entry() and the required memory
> size after it is updated. If this happens, the parser returns an error
> because ENFORCE_BUFEND is set for the last entry and there would be
> still data to read (the new entry added to the list).

I don't see this as being any different than what happens when the
kernel saves the measurement list. Originally, the memory size was
defined at kexec load, but only populated at kexec execute.  There was
plenty of time between the kexec load and execute for additional
measurement records to be added.

The upstreamed version defines the buffer size and populates it at
kexec load.  However kexec load itself generates additional
measurements, so it has to reserve more memory than what is returned
by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)

When restoring the buffer, it processes the number of records as
stored in the header, making sure it doesn't go beyond the buffer
size.

Mimi

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Mimi Zohar June 6, 2017, 11:33 a.m. UTC | #5
On Tue, 2017-06-06 at 11:13 +0200, Roberto Sassu wrote:
> >>  /* 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;
> >
> > What is this?
> 
> ima_measurements_start() should return the list entry at position *pos.
> The line above prevents qe_found from being updated when the loop
> continues until the last list entry.

Wouldn't a simple if/then be more appropriate here?

> 
> >
> >> +
> >> +                    if (!khdr)
> >> +                            break;
> >
> > Does this test need to be in the loop?
> 
> Yes. Otherwise, ima_measurements_start() would iterate over the whole
> list when it is not necessary.

Oh, for displaying the measurement list you need to set qe_found
before returning.

thanks,

Mimi

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roberto Sassu June 6, 2017, 12:45 p.m. UTC | #6
On 6/6/2017 12:56 PM, Mimi Zohar wrote:
> On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
>> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
>>> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
>>>> 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.
>>>
>>> Please correct me if I'm wrong.  Your code walks the measurement list
>>> calculating the total number of measurements and the memory size
>>> needed to store in the kexec header.  Can't there be additional
>>> measurements between the time these values - total number of
>>> measurements and memory needed - were calculated and actually saving
>>> the measurements?  How would that be any different than the problem
>>> you're trying to solve?  In both cases, the number of measurements
>>> might be less than the actual number of measurements.
>>>
>>> As long as you query the number of measurements before getting the
>>> memory needed, unless you're trying to verify a TPM quote, having
>>> fewer measurements shouldn't be a problem for testing.
>>
>> The problem is that the total number of entries and the required
>> memory size might be inconsistent without taking ima_extend_list_mutex.
>> ima_measurements_start() could read the entries counter before
>> it is incremented by ima_add_digest_entry() and the required memory
>> size after it is updated. If this happens, the parser returns an error
>> because ENFORCE_BUFEND is set for the last entry and there would be
>> still data to read (the new entry added to the list).
>
> I don't see this as being any different than what happens when the
> kernel saves the measurement list. Originally, the memory size was
> defined at kexec load, but only populated at kexec execute.  There was
> plenty of time between the kexec load and execute for additional
> measurement records to be added.
>
> The upstreamed version defines the buffer size and populates it at
> kexec load.  However kexec load itself generates additional
> measurements, so it has to reserve more memory than what is returned
> by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)

ima_dump_measurement_list() determines the total number of entries and
the required memory size (which are written to the kexec header) after
iterating over the whole list. Are new entries added to the kexec buffer
after ima_dump_measurement_list() is called?

Roberto


>
> When restoring the buffer, it processes the number of records as
> stored in the header, making sure it doesn't go beyond the buffer
> size.
>
> Mimi
>
Mimi Zohar June 6, 2017, 1:23 p.m. UTC | #7
On Tue, 2017-06-06 at 14:45 +0200, Roberto Sassu wrote:
> On 6/6/2017 12:56 PM, Mimi Zohar wrote:
> > On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
> >> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
> >>> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
> >>>> 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.
> >>>
> >>> Please correct me if I'm wrong.  Your code walks the measurement list
> >>> calculating the total number of measurements and the memory size
> >>> needed to store in the kexec header.  Can't there be additional
> >>> measurements between the time these values - total number of
> >>> measurements and memory needed - were calculated and actually saving
> >>> the measurements?  How would that be any different than the problem
> >>> you're trying to solve?  In both cases, the number of measurements
> >>> might be less than the actual number of measurements.
> >>>
> >>> As long as you query the number of measurements before getting the
> >>> memory needed, unless you're trying to verify a TPM quote, having
> >>> fewer measurements shouldn't be a problem for testing.
> >>
> >> The problem is that the total number of entries and the required
> >> memory size might be inconsistent without taking ima_extend_list_mutex.
> >> ima_measurements_start() could read the entries counter before
> >> it is incremented by ima_add_digest_entry() and the required memory
> >> size after it is updated. If this happens, the parser returns an error
> >> because ENFORCE_BUFEND is set for the last entry and there would be
> >> still data to read (the new entry added to the list).
> >
> > I don't see this as being any different than what happens when the
> > kernel saves the measurement list. Originally, the memory size was
> > defined at kexec load, but only populated at kexec execute.  There was
> > plenty of time between the kexec load and execute for additional
> > measurement records to be added.
> >
> > The upstreamed version defines the buffer size and populates it at
> > kexec load.  However kexec load itself generates additional
> > measurements, so it has to reserve more memory than what is returned
> > by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)
> 
> ima_dump_measurement_list() determines the total number of entries and
> the required memory size (which are written to the kexec header) after
> iterating over the whole list. Are new entries added to the kexec buffer
> after ima_dump_measurement_list() is called?

The upstreamed version allocates the segment in kexec load and then
fills the buffer.  However, in between getting the current memory size
needed and filling the buffer, additional measurements can be added.
Thus the segment size needs to be larger than the current memory
size. 

The header reflects the number of measurements and the actual buffer
size, not the segment size.  When restoring the measurement list,
however, we rely on the number of measurements and use the buffer size
as a reference to prevent accessing memory beyond the buffer.  The
buffer size does not need to be exact.

** Note **
This upstream version, which doesn't update the measurement list in
kexec execute, requires all files to be pre-measured, before calling
kexec load.  Otherwise, the measurement list will not validate against
the quoted TPM PCRs.

Mimi

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roberto Sassu June 13, 2017, 7:27 a.m. UTC | #8
On 6/6/2017 3:23 PM, Mimi Zohar wrote:
> On Tue, 2017-06-06 at 14:45 +0200, Roberto Sassu wrote:
>> On 6/6/2017 12:56 PM, Mimi Zohar wrote:
>>> On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
>>>> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
>>>>> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
>>>>>> 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.
>>>>>
>>>>> Please correct me if I'm wrong.  Your code walks the measurement list
>>>>> calculating the total number of measurements and the memory size
>>>>> needed to store in the kexec header.  Can't there be additional
>>>>> measurements between the time these values - total number of
>>>>> measurements and memory needed - were calculated and actually saving
>>>>> the measurements?  How would that be any different than the problem
>>>>> you're trying to solve?  In both cases, the number of measurements
>>>>> might be less than the actual number of measurements.
>>>>>
>>>>> As long as you query the number of measurements before getting the
>>>>> memory needed, unless you're trying to verify a TPM quote, having
>>>>> fewer measurements shouldn't be a problem for testing.
>>>>
>>>> The problem is that the total number of entries and the required
>>>> memory size might be inconsistent without taking ima_extend_list_mutex.
>>>> ima_measurements_start() could read the entries counter before
>>>> it is incremented by ima_add_digest_entry() and the required memory
>>>> size after it is updated. If this happens, the parser returns an error
>>>> because ENFORCE_BUFEND is set for the last entry and there would be
>>>> still data to read (the new entry added to the list).
>>>
>>> I don't see this as being any different than what happens when the
>>> kernel saves the measurement list. Originally, the memory size was
>>> defined at kexec load, but only populated at kexec execute.  There was
>>> plenty of time between the kexec load and execute for additional
>>> measurement records to be added.
>>>
>>> The upstreamed version defines the buffer size and populates it at
>>> kexec load.  However kexec load itself generates additional
>>> measurements, so it has to reserve more memory than what is returned
>>> by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)
>>
>> ima_dump_measurement_list() determines the total number of entries and
>> the required memory size (which are written to the kexec header) after
>> iterating over the whole list. Are new entries added to the kexec buffer
>> after ima_dump_measurement_list() is called?
>
> The upstreamed version allocates the segment in kexec load and then
> fills the buffer.  However, in between getting the current memory size
> needed and filling the buffer, additional measurements can be added.
> Thus the segment size needs to be larger than the current memory
> size.
>
> The header reflects the number of measurements and the actual buffer
> size, not the segment size.  When restoring the measurement list,
> however, we rely on the number of measurements and use the buffer size
> as a reference to prevent accessing memory beyond the buffer.  The
> buffer size does not need to be exact.

In this case, I have to modify patch 2/7 and remove ENFORCE_BUFEND
from the enforcing mask. Otherwise, ima_restore_measurement_list()
would return an error when parsing the last entry and buffer size
in the kexec header is greater than the exact size required to
store the measurements list.

Should I just send the modified patch with the [RESEND] tag
or should I send the whole patch set with an incremented version
number?

Also, since patches 4-6 are for testing, I would prefer to skip
them for now and push a new version of the patch set for the
Crypto Agile format first.

Thanks

Roberto
Mimi Zohar June 13, 2017, 12:09 p.m. UTC | #9
On Tue, 2017-06-13 at 09:27 +0200, Roberto Sassu wrote:
> On 6/6/2017 3:23 PM, Mimi Zohar wrote:
> > On Tue, 2017-06-06 at 14:45 +0200, Roberto Sassu wrote:
> >> On 6/6/2017 12:56 PM, Mimi Zohar wrote:
> >>> On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
> >>>> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
> >>>>> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
> >>>>>> 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.
> >>>>>
> >>>>> Please correct me if I'm wrong.  Your code walks the measurement list
> >>>>> calculating the total number of measurements and the memory size
> >>>>> needed to store in the kexec header.  Can't there be additional
> >>>>> measurements between the time these values - total number of
> >>>>> measurements and memory needed - were calculated and actually saving
> >>>>> the measurements?  How would that be any different than the problem
> >>>>> you're trying to solve?  In both cases, the number of measurements
> >>>>> might be less than the actual number of measurements.
> >>>>>
> >>>>> As long as you query the number of measurements before getting the
> >>>>> memory needed, unless you're trying to verify a TPM quote, having
> >>>>> fewer measurements shouldn't be a problem for testing.
> >>>>
> >>>> The problem is that the total number of entries and the required
> >>>> memory size might be inconsistent without taking ima_extend_list_mutex.
> >>>> ima_measurements_start() could read the entries counter before
> >>>> it is incremented by ima_add_digest_entry() and the required memory
> >>>> size after it is updated. If this happens, the parser returns an error
> >>>> because ENFORCE_BUFEND is set for the last entry and there would be
> >>>> still data to read (the new entry added to the list).
> >>>
> >>> I don't see this as being any different than what happens when the
> >>> kernel saves the measurement list. Originally, the memory size was
> >>> defined at kexec load, but only populated at kexec execute.  There was
> >>> plenty of time between the kexec load and execute for additional
> >>> measurement records to be added.
> >>>
> >>> The upstreamed version defines the buffer size and populates it at
> >>> kexec load.  However kexec load itself generates additional
> >>> measurements, so it has to reserve more memory than what is returned
> >>> by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)
> >>
> >> ima_dump_measurement_list() determines the total number of entries and
> >> the required memory size (which are written to the kexec header) after
> >> iterating over the whole list. Are new entries added to the kexec buffer
> >> after ima_dump_measurement_list() is called?
> >
> > The upstreamed version allocates the segment in kexec load and then
> > fills the buffer.  However, in between getting the current memory size
> > needed and filling the buffer, additional measurements can be added.
> > Thus the segment size needs to be larger than the current memory
> > size.
> >
> > The header reflects the number of measurements and the actual buffer
> > size, not the segment size.  When restoring the measurement list,
> > however, we rely on the number of measurements and use the buffer size
> > as a reference to prevent accessing memory beyond the buffer.  The
> > buffer size does not need to be exact.
> 
> In this case, I have to modify patch 2/7 and remove ENFORCE_BUFEND
> from the enforcing mask. Otherwise, ima_restore_measurement_list()
> would return an error when parsing the last entry and buffer size
> in the kexec header is greater than the exact size required to
> store the measurements list.

Or the testing patches could relax the ENFORCE_BUFEND requirement.
Without the ENFORCE_BUFEND requirement, I'm not sure this patch (5/7)
will be needed.

> Should I just send the modified patch with the [RESEND] tag
> or should I send the whole patch set with an incremented version
> number?

The testing patches could be upstreamed separately, if you prefer.

> Also, since patches 4-6 are for testing, I would prefer to skip
> them for now and push a new version of the patch set for the
> Crypto Agile format first?

That's fine.  I've pushed patches 1 - 3, and 7 to the next-4.12-
ima_parse_buf branch for a day or so, before staging them in the next
branch to be upstreamed. 

The patches can be found here
https://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-
integrity.git/next-4.12-ima_parse_buf.

Mimi

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Roberto Sassu June 13, 2017, 12:37 p.m. UTC | #10
On 6/13/2017 2:09 PM, Mimi Zohar wrote:
> On Tue, 2017-06-13 at 09:27 +0200, Roberto Sassu wrote:
>> On 6/6/2017 3:23 PM, Mimi Zohar wrote:
>>> On Tue, 2017-06-06 at 14:45 +0200, Roberto Sassu wrote:
>>>> On 6/6/2017 12:56 PM, Mimi Zohar wrote:
>>>>> On Tue, 2017-06-06 at 10:49 +0200, Roberto Sassu wrote:
>>>>>> On 6/5/2017 8:04 AM, Mimi Zohar wrote:
>>>>>>> On Tue, 2017-05-16 at 14:53 +0200, Roberto Sassu wrote:
>>>>>>>> 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.
>>>>>>>
>>>>>>> Please correct me if I'm wrong.  Your code walks the measurement list
>>>>>>> calculating the total number of measurements and the memory size
>>>>>>> needed to store in the kexec header.  Can't there be additional
>>>>>>> measurements between the time these values - total number of
>>>>>>> measurements and memory needed - were calculated and actually saving
>>>>>>> the measurements?  How would that be any different than the problem
>>>>>>> you're trying to solve?  In both cases, the number of measurements
>>>>>>> might be less than the actual number of measurements.
>>>>>>>
>>>>>>> As long as you query the number of measurements before getting the
>>>>>>> memory needed, unless you're trying to verify a TPM quote, having
>>>>>>> fewer measurements shouldn't be a problem for testing.
>>>>>>
>>>>>> The problem is that the total number of entries and the required
>>>>>> memory size might be inconsistent without taking ima_extend_list_mutex.
>>>>>> ima_measurements_start() could read the entries counter before
>>>>>> it is incremented by ima_add_digest_entry() and the required memory
>>>>>> size after it is updated. If this happens, the parser returns an error
>>>>>> because ENFORCE_BUFEND is set for the last entry and there would be
>>>>>> still data to read (the new entry added to the list).
>>>>>
>>>>> I don't see this as being any different than what happens when the
>>>>> kernel saves the measurement list. Originally, the memory size was
>>>>> defined at kexec load, but only populated at kexec execute.  There was
>>>>> plenty of time between the kexec load and execute for additional
>>>>> measurement records to be added.
>>>>>
>>>>> The upstreamed version defines the buffer size and populates it at
>>>>> kexec load.  However kexec load itself generates additional
>>>>> measurements, so it has to reserve more memory than what is returned
>>>>> by ima_get_binary_runtime_size(). (Refer to ima_add_kexec_buffer.)
>>>>
>>>> ima_dump_measurement_list() determines the total number of entries and
>>>> the required memory size (which are written to the kexec header) after
>>>> iterating over the whole list. Are new entries added to the kexec buffer
>>>> after ima_dump_measurement_list() is called?
>>>
>>> The upstreamed version allocates the segment in kexec load and then
>>> fills the buffer.  However, in between getting the current memory size
>>> needed and filling the buffer, additional measurements can be added.
>>> Thus the segment size needs to be larger than the current memory
>>> size.
>>>
>>> The header reflects the number of measurements and the actual buffer
>>> size, not the segment size.  When restoring the measurement list,
>>> however, we rely on the number of measurements and use the buffer size
>>> as a reference to prevent accessing memory beyond the buffer.  The
>>> buffer size does not need to be exact.
>>
>> In this case, I have to modify patch 2/7 and remove ENFORCE_BUFEND
>> from the enforcing mask. Otherwise, ima_restore_measurement_list()
>> would return an error when parsing the last entry and buffer size
>> in the kexec header is greater than the exact size required to
>> store the measurements list.
>
> Or the testing patches could relax the ENFORCE_BUFEND requirement.
> Without the ENFORCE_BUFEND requirement, I'm not sure this patch (5/7)
> will be needed.

The ENFORCE_BUFEND bit is set in patch 2/7:
---
+		enforce_mask |= (count == khdr->count) ? ENFORCE_BUFEND : 0;
---

If this requirement is too strict for restoring measurements,
then patch 2/7 should be modified.

Regarding this patch, I agree that it is not strictly necessary.
With an userspace tool, it would be possible to prepend the
kexec header to the binary list after parsing the entries.


>> Should I just send the modified patch with the [RESEND] tag
>> or should I send the whole patch set with an incremented version
>> number?
>
> The testing patches could be upstreamed separately, if you prefer.

Ok.

Thanks

Roberto


>> Also, since patches 4-6 are for testing, I would prefer to skip
>> them for now and push a new version of the patch set for the
>> Crypto Agile format first?
>
> That's fine.  I've pushed patches 1 - 3, and 7 to the next-4.12-
> ima_parse_buf branch for a day or so, before staging them in the next
> branch to be upstreamed.
>
> The patches can be found here
> https://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-
> integrity.git/next-4.12-ima_parse_buf.
>
> Mimi
>
diff mbox

Patch

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);